"use client" import { useState, useEffect, useCallback, Component, type ReactNode } from "react" import { useRouter } from "next/navigation" import { Button } from "@/components/ui/button" import { Card } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { CalendarEvent, EventType } from "@/types" import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Textarea } from "@/components/ui/textarea" import { cn } from "@/lib/utils" import { useUser } from "@/providers/user-provider" import { useNotifications } from "@/providers/notification-provider" import { toast } from "sonner" import { ChevronLeft, ChevronRight, Plus, Calendar, Clock, Phone, Users, CheckCircle2, XCircle, RotateCcw, Loader2, ArrowUpRight, Zap, StickyNote, Globe, } from "lucide-react" const EVENT_ICONS: Record = { call: Phone, follow_up: Clock, website_creation: Globe, } function eventVars(type: EventType) { return `--event-${type}` } function eventBgStyle(type: EventType): React.CSSProperties { const v = eventVars(type) return { backgroundColor: `hsl(var(${v}) / 0.1)`, color: `hsl(var(${v}))`, borderColor: `hsl(var(${v}) / 0.25)`, } } function eventDotStyle(type: EventType): React.CSSProperties { return { backgroundColor: `hsl(var(--event-${type}))` } } function getDaysInMonth(year: number, month: number) { return new Date(year, month + 1, 0).getDate() } function getFirstDayOfMonth(year: number, month: number) { return new Date(year, month, 1).getDay() } const MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] function formatTime(iso: string) { return new Date(iso).toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit", hour12: true }) } function formatDate(iso: string) { return new Date(iso).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }) } export default function CalendarPage() { const router = useRouter() const { user } = useUser() const { addNotification } = useNotifications() const [events, setEvents] = useState([]) const [upcomingEvents, setUpcomingEvents] = useState([]) const [loading, setLoading] = useState(true) const [today] = useState(new Date()) const [currentMonth, setCurrentMonth] = useState(today.getMonth()) const [currentYear, setCurrentYear] = useState(today.getFullYear()) const [selectedDate, setSelectedDate] = useState(null) const [dialogOpen, setDialogOpen] = useState(false) const [editingEvent, setEditingEvent] = useState(null) const [detailEvent, setDetailEvent] = useState(null) const [users, setUsers] = useState<{ id: string; name: string; email: string; role: string }[]>([]) const [leads, setLeads] = useState<{ id: string; contactName: string; companyName: string }[]>([]) const [formTitle, setFormTitle] = useState("") const [formDescription, setFormDescription] = useState("") const [formType, setFormType] = useState("website_creation") const [formDate, setFormDate] = useState("") const [formTime, setFormTime] = useState("") const [formDuration, setFormDuration] = useState("30") const [formParticipantId, setFormParticipantId] = useState("") const [formDeveloperId, setFormDeveloperId] = useState("") const [formLeadId, setFormLeadId] = useState("") const [formClientName, setFormClientName] = useState("") const [formClientEmail, setFormClientEmail] = useState("") const [formClientPhone, setFormClientPhone] = useState("") const [formSaving, setFormSaving] = useState(false) const [editNotes, setEditNotes] = useState(false) const [notesText, setNotesText] = useState("") const [notesSaving, setNotesSaving] = useState(false) const fetchMonthEvents = useCallback(async () => { const start = new Date(currentYear, currentMonth, 1).toISOString() const end = new Date(currentYear, currentMonth + 1, 0, 23, 59, 59).toISOString() const res = await fetch(`/api/events?start=${encodeURIComponent(start)}&end=${encodeURIComponent(end)}`) if (res.ok) { const data = await res.json() setEvents(data.events || []) } }, [currentYear, currentMonth]) const fetchUpcomingEvents = useCallback(async () => { const start = new Date().toISOString() const end = new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString() const res = await fetch(`/api/events?start=${encodeURIComponent(start)}&end=${encodeURIComponent(end)}&status=scheduled`) if (res.ok) { const data = await res.json() setUpcomingEvents((data.events || []).slice(0, 10)) } }, []) useEffect(() => { if (!user) { router.push("/login"); return } fetch("/api/event-users") .then((r) => r.json()) .then((data) => setUsers(data.users || [])) .catch((err) => console.error("Failed to fetch event users:", err)) fetch("/api/leads?limit=200") .then((r) => r.json()) .then((data) => setLeads((Array.isArray(data) ? data : data?.leads || []).map((l: any) => ({ id: l.id, contactName: l.contactName, companyName: l.companyName })))) .catch((err) => console.error("Failed to fetch leads:", err)) }, [user, router]) useEffect(() => { setLoading(true) fetchMonthEvents().catch(() => toast.error("Failed to load events")).finally(() => setLoading(false)) }, [fetchMonthEvents]) useEffect(() => { fetchUpcomingEvents().catch((err) => console.error("Failed to fetch upcoming events:", err)) }, [fetchUpcomingEvents]) useEffect(() => { const interval = setInterval(() => { fetchMonthEvents().catch((err) => console.error("Failed to fetch month events:", err)) fetchUpcomingEvents().catch((err) => console.error("Failed to fetch upcoming events:", err)) }, 15000) return () => clearInterval(interval) }, [fetchMonthEvents, fetchUpcomingEvents]) const daysInMonth = getDaysInMonth(currentYear, currentMonth) const firstDay = getFirstDayOfMonth(currentYear, currentMonth) const prevMonth = () => { if (currentMonth === 0) { setCurrentMonth(11); setCurrentYear(currentYear - 1) } else setCurrentMonth(currentMonth - 1) } const nextMonth = () => { if (currentMonth === 11) { setCurrentMonth(0); setCurrentYear(currentYear + 1) } else setCurrentMonth(currentMonth + 1) } const getEventsForDay = useCallback((day: number) => { const dateStr = `${currentYear}-${String(currentMonth + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}` return events.filter((e) => e.startTime && e.startTime.startsWith(dateStr)) }, [events, currentYear, currentMonth]) const isToday = (day: number) => { return today.getDate() === day && today.getMonth() === currentMonth && today.getFullYear() === currentYear } const openCreateDialog = (day?: number) => { setEditingEvent(null) setFormTitle("") setFormDescription("") setFormType("website_creation") setFormDuration("30") setFormParticipantId("") setFormDeveloperId("") setFormLeadId("") setFormClientName("") setFormClientEmail("") setFormClientPhone("") const d = day ? new Date(currentYear, currentMonth, day) : new Date() setFormDate(d.toISOString().split("T")[0]) setFormTime("10:00") setDialogOpen(true) } const openEditDialog = (event: CalendarEvent) => { setEditingEvent(event) setFormTitle(event.title) setFormDescription(event.description || "") setFormType(event.eventType) setFormDuration(String(event.durationMinutes || 30)) setFormDate(event.startTime ? new Date(event.startTime).toISOString().split("T")[0] : "") setFormTime(event.startTime ? new Date(event.startTime).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false }) : "10:00") setFormParticipantId(event.participantId || "") setFormDeveloperId(event.developerId || "") setFormLeadId(event.leadId || "") setFormClientName(event.clientName || "") setFormClientEmail(event.clientEmail || "") setFormClientPhone(event.clientPhone || "") setDialogOpen(true) } const handleSave = async () => { if (!formTitle.trim()) { toast.error("Title is required") return } if (formType !== "website_creation" && (!formDate || !formTime)) { toast.error("Date and time are required for this event type") return } setFormSaving(true) try { const body: Record = { title: formTitle.trim(), description: formDescription.trim() || null, eventType: formType, } let startTime: string | undefined if (formType !== "website_creation") { startTime = new Date(`${formDate}T${formTime}:00`).toISOString() let endTime = null if (formDuration) { const end = new Date(startTime) end.setMinutes(end.getMinutes() + parseInt(formDuration)) endTime = end.toISOString() } body.startTime = startTime body.endTime = endTime body.durationMinutes = formDuration ? parseInt(formDuration) : null } else if (formDate) { startTime = new Date(`${formDate}T00:00:00`).toISOString() body.startTime = startTime } if (formClientName) body.clientName = formClientName if (formClientEmail) body.clientEmail = formClientEmail if (formClientPhone) body.clientPhone = formClientPhone if (formParticipantId) body.participantId = formParticipantId if (formDeveloperId) body.developerId = formDeveloperId if (formLeadId) body.leadId = formLeadId let res if (editingEvent) { res = await fetch(`/api/events/${editingEvent.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }) } else { res = await fetch("/api/events", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }) } if (!res.ok) throw new Error("Failed to save") const data = await res.json() if (editingEvent) { setEvents((prev) => prev.map((e) => e.id === editingEvent.id ? { ...data.event, participant: e.participant, developer: e.developer, lead: e.lead } : e)) toast.success("Event updated") } else { setEvents((prev) => [...prev, data.event]) if (startTime) { addNotification("event_scheduled", `Event created: ${formTitle}`, `Scheduled for ${formatDate(startTime)} at ${formatTime(startTime)}`, "/calendar") } toast.success("Event created") } setDialogOpen(false) } catch { toast.error("Failed to save event") } finally { setFormSaving(false) } } const updateEventStatus = async (event: CalendarEvent, newStatus: string) => { try { const res = await fetch(`/api/events/${event.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ status: newStatus }), }) if (!res.ok) throw new Error("Failed to update") const data = await res.json() setEvents((prev) => prev.map((e) => e.id === event.id ? { ...data.event, participant: e.participant, developer: e.developer, lead: e.lead } : e)) toast.success(`Event ${newStatus}`) setDetailEvent(null) } catch { toast.error("Failed to update event") } } const saveNotes = async (event: CalendarEvent) => { if (notesText === (event.participantNotes || "")) { setEditNotes(false); return } setNotesSaving(true) try { const res = await fetch(`/api/events/${event.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ participantNotes: notesText || null }), }) if (!res.ok) throw new Error("Failed to save") const data = await res.json() setEvents((prev) => prev.map((e) => e.id === event.id ? { ...data.event, participant: e.participant, developer: e.developer, lead: e.lead } : e)) if (detailEvent && detailEvent.id === event.id) { setDetailEvent({ ...detailEvent, participantNotes: notesText || null }) } toast.success("Notes saved") setEditNotes(false) } catch { toast.error("Failed to save notes") } finally { setNotesSaving(false) } } const deleteEvent = async (event: CalendarEvent) => { try { const res = await fetch(`/api/events/${event.id}`, { method: "DELETE" }) if (!res.ok) throw new Error("Failed to delete") setEvents((prev) => prev.filter((e) => e.id !== event.id)) toast.success("Event deleted") setDetailEvent(null) setDialogOpen(false) } catch { toast.error("Failed to delete event") } } const calendarDays: (number | null)[] = [] for (let i = 0; i < firstDay; i++) calendarDays.push(null) for (let d = 1; d <= daysInMonth; d++) calendarDays.push(d) if (!user) return null return (
{/* Header */}

Calendar

{MONTHS[currentMonth]} {currentYear}
{/* Body - flex row with independent scroll */}
{loading ? (

Loading calendar...

) : ( <> {/* Left: Calendar Grid */}
{/* Stats bar */}
{events.filter(e => e.status === "scheduled").length} scheduled
{events.filter(e => e.status === "completed").length} completed
{events.length} total
{/* Calendar grid wrapper - scrollable */}
{/* Day headers */}
{DAYS.map((d, i) => (
{d}
))}
{/* Day cells */}
{calendarDays.map((day, i) => { if (day === null) return
const dayEvents = getEventsForDay(day) const todayMark = isToday(day) const hasEvents = dayEvents.length > 0 return (
{ setSelectedDate(new Date(currentYear, currentMonth, day)); openCreateDialog(day) }} >
{day} {hasEvents && ( {dayEvents.length} )}
{dayEvents.slice(0, 3).map((event) => { const Icon = EVENT_ICONS[event.eventType] return ( ) })} {dayEvents.length > 3 && ( )}
) })}
{/* Right: Upcoming sidebar */}

Upcoming

{upcomingEvents.length > 0 && ( {upcomingEvents.length} )}
{upcomingEvents.length === 0 ? (

No upcoming events

Schedule one to get started

) : (
{upcomingEvents.map((event) => { const Icon = EVENT_ICONS[event.eventType] return ( ) })}
)}
)}
{/* Create/Edit Dialog */}
{editingEvent ? : }
{editingEvent ? "Edit Event" : "Create Event"}

{editingEvent ? "Update the event details below" : "Fill in the details for your new event"}

setFormTitle(e.target.value)} placeholder="e.g. Product demo with client" className="h-10" />
setFormDate(e.target.value)} className="h-10" />
setFormTime(e.target.value)} className="h-10" />
{formType === "website_creation" && (

Client Details

setFormClientName(e.target.value)} placeholder="Client full name" className="h-10" />
setFormClientEmail(e.target.value)} placeholder="client@example.com" className="h-10" />
setFormClientPhone(e.target.value)} placeholder="+1 555-123-4567" className="h-10" />
)}