'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: () => (
),
}
);
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 (
);
}
if (error) {
return (
);
}
return (
Calendar
Your commitments, in time.
{/* Filters sidebar */}
Filters
{/* Entity types */}
Show
setShowTasks(checked === true)}
/>
setShowProjects(checked === true)}
/>
setShowMilestones(checked === true)}
/>
{/* Domains */}
Domains
{['personal', 'work', 'ots'].map((domain) => (
toggleDomain(domain)}
/>
))}
{selectedDomains.length > 0 && (
)}
{/* Legend */}
Legend
• Tasks show on due date
• Projects show on deadline
• Milestones show on due date
{/* Calendar */}
Loading calendar...
}
>
);
}