"use client"; import { useState, useEffect, useCallback, useRef, useMemo } from "react"; import { useRouter } from "@/i18n/navigation"; import { useTranslations } from "next-intl"; import { startOfMonth, endOfMonth, startOfWeek, endOfWeek, addMonths, subMonths, addWeeks, subWeeks, addDays, subDays, format, } from "date-fns"; import { useCalendarStore } from "@/stores/calendar-store"; import { useAuthStore } from "@/stores/auth-store"; import { useSettingsStore } from "@/stores/settings-store"; import { toast } from "@/stores/toast-store"; import { useIsMobile } from "@/hooks/use-media-query"; import { CalendarToolbar } from "@/components/calendar/calendar-toolbar"; import { CalendarMonthView } from "@/components/calendar/calendar-month-view"; import { CalendarWeekView } from "@/components/calendar/calendar-week-view"; import { CalendarDayView } from "@/components/calendar/calendar-day-view"; import { CalendarAgendaView } from "@/components/calendar/calendar-agenda-view"; import { MiniCalendar } from "@/components/calendar/mini-calendar"; import { CalendarSidebarPanel } from "@/components/calendar/calendar-sidebar-panel"; import { EventModal } from "@/components/calendar/event-modal"; import { ICalImportModal } from "@/components/calendar/ical-import-modal"; import type { CalendarEvent } from "@/lib/jmap/types"; export default function CalendarPage() { const router = useRouter(); const t = useTranslations("calendar"); const isMobile = useIsMobile(); const { client, isAuthenticated } = useAuthStore(); const { calendars, events, selectedDate, viewMode, selectedCalendarIds, isLoading, isLoadingEvents, supportsCalendar, error, fetchCalendars, fetchEvents, createEvent, updateEvent, deleteEvent, setSelectedDate, setViewMode, toggleCalendarVisibility, } = useCalendarStore(); const { firstDayOfWeek, timeFormat } = useSettingsStore(); const [showEventModal, setShowEventModal] = useState(false); const [showImportModal, setShowImportModal] = useState(false); const [editEvent, setEditEvent] = useState(null); const [defaultModalDate, setDefaultModalDate] = useState(); const [miniMonth, setMiniMonth] = useState(new Date()); const hasFetched = useRef(false); useEffect(() => { if (!isAuthenticated) { router.push("/login"); } else if (!supportsCalendar) { router.push("/"); } }, [isAuthenticated, supportsCalendar, router]); useEffect(() => { if (error) { toast.error(error); } }, [error]); useEffect(() => { if (client && !hasFetched.current) { hasFetched.current = true; fetchCalendars(client); } }, [client, fetchCalendars]); const dateRange = useMemo(() => { const d = selectedDate; switch (viewMode) { case "month": { const ms = startOfMonth(d); const me = endOfMonth(d); return { start: format(startOfWeek(ms, { weekStartsOn: firstDayOfWeek }), "yyyy-MM-dd'T'00:00:00"), end: format(endOfWeek(me, { weekStartsOn: firstDayOfWeek }), "yyyy-MM-dd'T'23:59:59"), }; } case "week": { const ws = startOfWeek(d, { weekStartsOn: firstDayOfWeek }); return { start: format(ws, "yyyy-MM-dd'T'00:00:00"), end: format(addDays(ws, 6), "yyyy-MM-dd'T'23:59:59"), }; } case "day": return { start: format(d, "yyyy-MM-dd'T'00:00:00"), end: format(d, "yyyy-MM-dd'T'23:59:59"), }; case "agenda": return { start: format(d, "yyyy-MM-dd'T'00:00:00"), end: format(addDays(d, 30), "yyyy-MM-dd'T'23:59:59"), }; } }, [selectedDate, viewMode, firstDayOfWeek]); useEffect(() => { if (client && calendars.length > 0) { fetchEvents(client, dateRange.start, dateRange.end); } }, [client, calendars.length, selectedCalendarIds, dateRange, fetchEvents]); const navigatePrev = useCallback(() => { let next: Date; switch (viewMode) { case "month": next = subMonths(selectedDate, 1); break; case "week": next = subWeeks(selectedDate, 1); break; case "day": next = subDays(selectedDate, 1); break; case "agenda": next = subMonths(selectedDate, 1); break; } setSelectedDate(next); setMiniMonth(next); }, [viewMode, selectedDate, setSelectedDate]); const navigateNext = useCallback(() => { let next: Date; switch (viewMode) { case "month": next = addMonths(selectedDate, 1); break; case "week": next = addWeeks(selectedDate, 1); break; case "day": next = addDays(selectedDate, 1); break; case "agenda": next = addMonths(selectedDate, 1); break; } setSelectedDate(next); setMiniMonth(next); }, [viewMode, selectedDate, setSelectedDate]); const goToToday = useCallback(() => { setSelectedDate(new Date()); setMiniMonth(new Date()); }, [setSelectedDate]); const handleSelectDate = useCallback((date: Date) => { setSelectedDate(date); setMiniMonth(date); }, [setSelectedDate]); const handleMiniMonthChange = useCallback((date: Date) => { setMiniMonth(date); setSelectedDate(date); }, [setSelectedDate]); const openCreateModal = useCallback((date?: Date) => { setEditEvent(null); setDefaultModalDate(date || selectedDate); setShowEventModal(true); }, [selectedDate]); const openEditModal = useCallback((event: CalendarEvent) => { setEditEvent(event); setDefaultModalDate(undefined); setShowEventModal(true); }, []); const handleSaveEvent = useCallback(async (data: Partial) => { if (!client) return; try { if (editEvent) { await updateEvent(client, editEvent.id, data); toast.success(t("notifications.event_updated")); } else { const created = await createEvent(client, data); if (!created) { toast.error(t("notifications.event_error")); return; } toast.success(t("notifications.event_created")); } setShowEventModal(false); setEditEvent(null); } catch { toast.error(t("notifications.event_error")); } }, [client, editEvent, createEvent, updateEvent, t]); const handleDeleteEvent = useCallback(async (id: string) => { if (!client) return; try { await deleteEvent(client, id); toast.success(t("notifications.event_deleted")); } catch { toast.error(t("notifications.event_error")); } }, [client, deleteEvent, t]); useEffect(() => { const handleKey = (e: KeyboardEvent) => { const target = e.target as HTMLElement; if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT") return; if (showEventModal) return; switch (e.key) { case "ArrowLeft": e.preventDefault(); navigatePrev(); break; case "ArrowRight": e.preventDefault(); navigateNext(); break; case "t": goToToday(); break; case "m": setViewMode("month"); break; case "w": setViewMode("week"); break; case "d": setViewMode("day"); break; case "a": setViewMode("agenda"); break; case "n": openCreateModal(); break; } }; window.addEventListener("keydown", handleKey); return () => window.removeEventListener("keydown", handleKey); }, [navigatePrev, navigateNext, goToToday, setViewMode, openCreateModal, showEventModal]); const visibleEvents = useMemo(() => events.filter((e) => { const calIds = Object.keys(e.calendarIds); return calIds.some((id) => selectedCalendarIds.includes(id)); }), [events, selectedCalendarIds] ); if (!isAuthenticated || !supportsCalendar) return null; const renderView = () => { if (isLoading && calendars.length === 0) { return (

{t("status.loading_calendars")}

); } const viewContent = (() => { switch (viewMode) { case "month": return ( ); case "week": return ( ); case "day": return ( ); case "agenda": return ( ); } })(); return (
{viewContent} {isLoadingEvents && calendars.length > 0 && (
)}
); }; return (
router.push("/")} onPrev={navigatePrev} onNext={navigateNext} onToday={goToToday} onViewModeChange={setViewMode} onCreateEvent={() => openCreateModal()} onImport={() => setShowImportModal(true)} isMobile={isMobile} />
{!isMobile && (
)} {renderView()}
{showEventModal && ( { setShowEventModal(false); setEditEvent(null); }} /> )} {showImportModal && client && ( setShowImportModal(false)} /> )}
); }