"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, Video, Users, CheckCircle2, XCircle, RotateCcw, Loader2, ArrowUpRight, Zap, StickyNote, } from "lucide-react" const EVENT_ICONS: Record = { meeting: Users, call: Phone, chat: Video, follow_up: Clock, demo: Calendar, other: Calendar, } 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 }[]>([]) const [formTitle, setFormTitle] = useState("") const [formDescription, setFormDescription] = useState("") const [formType, setFormType] = useState("meeting") const [formDate, setFormDate] = useState("") const [formTime, setFormTime] = useState("") const [formDuration, setFormDuration] = useState("30") const [formParticipantId, setFormParticipantId] = 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(() => {}) }, [user, router]) useEffect(() => { setLoading(true) fetchMonthEvents().catch(() => toast.error("Failed to load events")).finally(() => setLoading(false)) }, [fetchMonthEvents]) useEffect(() => { fetchUpcomingEvents().catch(() => {}) }, [fetchUpcomingEvents]) useEffect(() => { const interval = setInterval(() => { fetchMonthEvents().catch(() => {}) fetchUpcomingEvents().catch(() => {}) }, 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.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("meeting") setFormDuration("30") setFormParticipantId("") 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(new Date(event.startTime).toISOString().split("T")[0]) setFormTime(new Date(event.startTime).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false })) setFormParticipantId(event.participantId || "") setDialogOpen(true) } const handleSave = async () => { if (!formTitle.trim() || !formDate || !formTime) { toast.error("Title, date, and time are required") return } setFormSaving(true) try { const 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() } const body: Record = { title: formTitle.trim(), description: formDescription.trim() || null, eventType: formType, startTime, endTime, durationMinutes: formDuration ? parseInt(formDuration) : null, } if (formParticipantId) body.participantId = formParticipantId 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, lead: e.lead } : e)) toast.success("Event updated") } else { setEvents((prev) => [...prev, data.event]) 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, 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, 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" />