'use client'; import { useEffect, useState, useMemo, Suspense } from 'react'; import { Filter } from 'lucide-react'; import dynamic from 'next/dynamic'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Checkbox } from '@/components/ui/checkbox'; import { Label } from '@/components/ui/label'; // Lazy load react-big-calendar (~60KB + date-fns) const BigCalendar = dynamic( () => import('@/components/calendar/big-calendar-wrapper').then((m) => m.BigCalendarWrapper), { ssr: false, loading: () => (
Loading calendar...
), } ); interface CalendarEvent { id: string; title: string; start: Date; end: Date; type: 'task' | 'project' | 'milestone'; domain: string; color: string; href: string; } export default function CalendarPage() { const [events, setEvents] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [showTasks, setShowTasks] = useState(true); const [showProjects, setShowProjects] = useState(true); const [showMilestones, setShowMilestones] = useState(true); const [selectedDomains, setSelectedDomains] = useState([]); useEffect(() => { fetchEvents(); }, []); async function fetchEvents() { setLoading(true); setError(null); try { const [tasksResponse, projectsResponse, milestonesResponse] = await Promise.all([ fetch('/api/tasks?filter=due_date!%3D%22%22&perPage=500'), fetch('/api/projects?filter=due_date!%3D%22%22&perPage=500'), fetch('/api/milestones?filter=due_date!%3D%22%22&perPage=500'), ]); if (!tasksResponse.ok || !projectsResponse.ok || !milestonesResponse.ok) { throw new Error('One or more calendar sources could not be loaded.'); } const [tasksData, projectsData, milestonesData] = await Promise.all([ tasksResponse.json(), projectsResponse.json(), milestonesResponse.json(), ]); const calendarEvents: CalendarEvent[] = []; // Add tasks if (tasksData.items) { for (const task of tasksData.items) { if (task.due_date) { const date = new Date(task.due_date); calendarEvents.push({ id: `task-${task.id}`, title: task.title, start: date, end: date, type: 'task', domain: task.domain ?? 'personal', color: '#3b82f6', href: '/tasks', }); } } } // Add projects if (projectsData.items) { for (const project of projectsData.items) { if (project.due_date) { const date = new Date(project.due_date); calendarEvents.push({ id: `project-${project.id}`, title: project.name, start: date, end: date, type: 'project', domain: project.domain ?? 'personal', color: '#8b5cf6', href: `/projects/${project.id}`, }); } } } // Add milestones if (milestonesData.items) { for (const milestone of milestonesData.items) { if (milestone.due_date) { const date = new Date(milestone.due_date); calendarEvents.push({ id: `milestone-${milestone.id}`, title: milestone.name || milestone.title, start: date, end: date, type: 'milestone', domain: milestone.domain ?? 'work', color: '#f59e0b', href: milestone.project_id ? `/projects/${milestone.project_id}` : '/projects', }); } } } setEvents(calendarEvents); } catch (error) { console.error('Failed to fetch calendar events:', error); setError('Calendar events could not be loaded. Please try again.'); } finally { setLoading(false); } } const filteredEvents = useMemo(() => { return events.filter((event) => { // Filter by type if (event.type === 'task' && !showTasks) return false; if (event.type === 'project' && !showProjects) return false; if (event.type === 'milestone' && !showMilestones) return false; // Filter by domain if (selectedDomains.length > 0 && !selectedDomains.includes(event.domain)) { return false; } return true; }); }, [events, showTasks, showProjects, showMilestones, selectedDomains]); function toggleDomain(domain: string) { setSelectedDomains((prev) => prev.includes(domain) ? prev.filter((d) => d !== domain) : [...prev, domain] ); } if (loading) { return (

Loading calendar...

); } if (error) { return (

{error}

); } return (

Calendar

Your commitments, in time.

{/* Filters sidebar */} {/* Entity types */}

Show

setShowTasks(checked === true)} />
} >
); }