mission-control/app/calendar/page.tsx

220 lines
7.7 KiB
TypeScript

"use client";
// Force dynamic rendering to avoid prerendering issues with client components
export const dynamic = "force-dynamic";
import { useState } from "react";
import { DashboardLayout } from "@/components/layout/sidebar";
import { PageHeader } from "@/components/layout/page-header";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Calendar as CalendarIcon, LayoutGrid } from "lucide-react";
import {
CalendarProvider,
useCalendar,
CalendarAuth,
EventList,
CalendarSelector,
CalendarControls,
CalendarGrid,
EventDetailModal,
CalendarViewSwitcher,
TaskCalendarIntegration,
type CalendarView,
} from "@/components/calendar";
import { GoogleOAuthProvider } from "@react-oauth/google";
import { format } from "date-fns";
// Google OAuth Client ID - should be from environment variable
const GOOGLE_CLIENT_ID = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID || "";
interface CalendarEvent {
id: string;
summary: string;
description?: string;
start: {
dateTime?: string;
date?: string;
};
end: {
dateTime?: string;
date?: string;
};
location?: string;
calendarId: string;
calendarName: string;
htmlLink?: string;
}
function CalendarContent() {
const { isAuthenticated, selectedCalendars, calendars, events } = useCalendar();
const [currentView, setCurrentView] = useState<CalendarView>("month");
const [selectedEvent, setSelectedEvent] = useState<CalendarEvent | null>(null);
const today = format(new Date(), "EEEE, MMMM d, yyyy");
// Calculate today's stats
const todayEvents = events.filter((event) => {
const eventDate = event.start.dateTime || event.start.date;
if (!eventDate) return false;
const date = new Date(eventDate);
const now = new Date();
return (
date.getDate() === now.getDate() &&
date.getMonth() === now.getMonth() &&
date.getFullYear() === now.getFullYear()
);
});
const upcomingEvents = events.filter((event) => {
const eventDate = event.start.dateTime || event.start.date;
if (!eventDate) return false;
return new Date(eventDate) > new Date();
});
return (
<DashboardLayout>
<div className="space-y-4 sm:space-y-6">
<PageHeader
title="Calendar"
description={today}
/>
{!isAuthenticated ? (
<div className="flex items-center justify-center min-h-[300px] sm:min-h-[400px]">
<CalendarAuth />
</div>
) : (
<>
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<CalendarControls />
<CalendarViewSwitcher
currentView={currentView}
onViewChange={(view) => setCurrentView(view)}
/>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 sm:gap-6">
{/* Main Content - Calendar Grid or List */}
<div className="lg:col-span-2 space-y-3 sm:space-y-4">
{currentView === "month" ? (
<CalendarGrid onEventClick={setSelectedEvent} />
) : (
<>
<div className="flex items-center gap-2">
<CalendarIcon className="w-4 h-4 sm:w-5 sm:h-5 text-muted-foreground" />
<h2 className="text-base sm:text-lg font-semibold">Upcoming Events</h2>
</div>
<EventList />
</>
)}
</div>
{/* Sidebar */}
<div className="space-y-4 sm:space-y-6">
{/* Today's Summary */}
<Card className="hover:shadow-md transition-shadow">
<CardHeader className="pb-3">
<CardTitle className="text-sm sm:text-base">Today&apos;s Summary</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Events Today</span>
<span className="font-medium">{todayEvents.length}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Upcoming</span>
<span className="font-medium">{upcomingEvents.length}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Calendars</span>
<span className="font-medium">{selectedCalendars.length} / {calendars.length}</span>
</div>
</CardContent>
</Card>
{/* Calendar Selector */}
<CalendarSelector />
{/* Task-Calendar Integration */}
<TaskCalendarIntegration maxTasks={8} />
{/* Quick Actions */}
<Card className="hover:shadow-md transition-shadow">
<CardHeader className="pb-3">
<CardTitle className="text-sm sm:text-base">Quick Links</CardTitle>
</CardHeader>
<CardContent className="space-y-1">
<a
href="https://calendar.google.com"
target="_blank"
rel="noopener noreferrer"
className="block w-full text-left px-3 py-2 rounded-lg text-sm hover:bg-accent transition-colors"
>
Open Google Calendar
</a>
<a
href="https://calendar.google.com/calendar/u/0/r/settings"
target="_blank"
rel="noopener noreferrer"
className="block w-full text-left px-3 py-2 rounded-lg text-sm hover:bg-accent transition-colors"
>
Calendar Settings
</a>
<a
href="https://gantt-board.vercel.app"
target="_blank"
rel="noopener noreferrer"
className="block w-full text-left px-3 py-2 rounded-lg text-sm hover:bg-accent transition-colors"
>
Open Gantt Board
</a>
</CardContent>
</Card>
</div>
</div>
</>
)}
{/* Event Detail Modal */}
<EventDetailModal
event={selectedEvent}
isOpen={!!selectedEvent}
onClose={() => setSelectedEvent(null)}
/>
</div>
</DashboardLayout>
);
}
export default function CalendarPage() {
// If no Google Client ID is configured, show a message
if (!GOOGLE_CLIENT_ID) {
return (
<DashboardLayout>
<div className="flex items-center justify-center min-h-[300px] sm:min-h-[400px]">
<Card className="w-full max-w-md mx-4">
<CardHeader>
<CardTitle className="text-base sm:text-lg">Google Calendar Not Configured</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
To use Google Calendar integration, please set up a Google OAuth
Client ID and add it to your environment variables as
NEXT_PUBLIC_GOOGLE_CLIENT_ID.
</p>
</CardContent>
</Card>
</div>
</DashboardLayout>
);
}
return (
<GoogleOAuthProvider clientId={GOOGLE_CLIENT_ID}>
<CalendarProvider>
<CalendarContent />
</CalendarProvider>
</GoogleOAuthProvider>
);
}