220 lines
7.7 KiB
TypeScript
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'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>
|
|
);
|
|
}
|