mission-control/components/calendar/EventList.tsx

203 lines
6.4 KiB
TypeScript

"use client";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { useCalendar } from "./CalendarContext";
import {
Calendar as CalendarIcon,
Clock,
MapPin,
ExternalLink,
} from "lucide-react";
import { format, isSameDay, parseISO } from "date-fns";
import { ScrollArea } from "@/components/ui/scroll-area";
function getEventDate(event: { start: { dateTime?: string; date?: string } }): Date {
if (event.start.dateTime) {
return parseISO(event.start.dateTime);
}
if (event.start.date) {
return parseISO(event.start.date);
}
return new Date();
}
function formatEventTime(event: {
start: { dateTime?: string; date?: string };
end: { dateTime?: string; date?: string };
}): string {
if (event.start.date && !event.start.dateTime) {
return "All day";
}
const start = event.start.dateTime ? parseISO(event.start.dateTime) : null;
const end = event.end.dateTime ? parseISO(event.end.dateTime) : null;
if (!start) return "TBD";
if (end) {
return `${format(start, "h:mm a")} - ${format(end, "h:mm a")}`;
}
return format(start, "h:mm a");
}
function formatEventDate(event: {
start: { dateTime?: string; date?: string };
}): string {
if (event.start.dateTime) {
return format(parseISO(event.start.dateTime), "EEEE, MMMM d, yyyy");
}
if (event.start.date) {
return format(parseISO(event.start.date), "EEEE, MMMM d, yyyy");
}
return "TBD";
}
function getCalendarColor(calendarName: string): string {
const colors: Record<string, string> = {
personal: "bg-pink-500/10 text-pink-500 border-pink-500/20",
work: "bg-blue-500/10 text-blue-500 border-blue-500/20",
health: "bg-green-500/10 text-green-500 border-green-500/20",
social: "bg-purple-500/10 text-purple-500 border-purple-500/20",
family: "bg-yellow-500/10 text-yellow-500 border-yellow-500/20",
task: "bg-orange-500/10 text-orange-500 border-orange-500/20",
};
const lower = calendarName.toLowerCase();
for (const [key, color] of Object.entries(colors)) {
if (lower.includes(key)) return color;
}
return "bg-muted text-muted-foreground border-muted";
}
function groupEventsByDate(events: ReturnType<typeof useCalendar>["events"]) {
const groups: { date: Date; label: string; events: typeof events }[] = [];
for (const event of events) {
const eventDate = getEventDate(event);
const today = new Date();
today.setHours(0, 0, 0, 0);
let label: string;
if (isSameDay(eventDate, today)) {
label = "Today";
} else if (isSameDay(eventDate, new Date(today.getTime() + 86400000))) {
label = "Tomorrow";
} else {
label = format(eventDate, "EEEE, MMMM d");
}
const existingGroup = groups.find((g) => isSameDay(g.date, eventDate));
if (existingGroup) {
existingGroup.events.push(event);
} else {
groups.push({ date: eventDate, label, events: [event] });
}
}
return groups;
}
export function EventList() {
const { events } = useCalendar();
const groupedEvents = groupEventsByDate(events);
if (events.length === 0) {
return (
<div className="text-center py-12 text-muted-foreground">
<CalendarIcon className="w-12 h-12 mx-auto mb-4 opacity-50" />
<p>No upcoming events</p>
<p className="text-sm mt-1">Events from your Google Calendar will appear here</p>
</div>
);
}
return (
<ScrollArea className="h-[600px] pr-4">
<div className="space-y-6">
{groupedEvents.map((group) => (
<div key={group.label}>
<h3 className="text-sm font-semibold text-muted-foreground mb-3 sticky top-0 bg-background py-2 z-10">
{group.label}
</h3>
<div className="space-y-3">
{group.events.map((event) => (
<EventCard key={event.id} event={event} />
))}
</div>
</div>
))}
</div>
</ScrollArea>
);
}
function EventCard({
event,
}: {
event: ReturnType<typeof useCalendar>["events"][0];
}) {
const eventDate = getEventDate(event);
const isAllDay = !!event.start.date && !event.start.dateTime;
return (
<Card className="group hover:shadow-md transition-shadow">
<CardContent className="p-4">
<div className="flex items-start gap-4">
<div className="w-14 h-14 rounded-lg bg-secondary flex flex-col items-center justify-center shrink-0">
<span className="text-xs text-muted-foreground uppercase">
{format(eventDate, "MMM")}
</span>
<span className="text-lg font-bold">{format(eventDate, "d")}</span>
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<h3 className="font-semibold truncate">{event.summary}</h3>
<Badge variant="outline" className={getCalendarColor(event.calendarName)}>
{event.calendarName}
</Badge>
</div>
{event.description && (
<p className="text-sm text-muted-foreground mt-1 line-clamp-2">
{event.description}
</p>
)}
<div className="flex items-center gap-4 mt-2 text-xs text-muted-foreground flex-wrap">
<span className="flex items-center gap-1">
<Clock className="w-3 h-3" />
{formatEventTime(event)}
</span>
{!isAllDay && (
<span className="flex items-center gap-1">
<CalendarIcon className="w-3 h-3" />
{formatEventDate(event)}
</span>
)}
{event.location && (
<span className="flex items-center gap-1">
<MapPin className="w-3 h-3" />
<span className="truncate max-w-[150px]">{event.location}</span>
</span>
)}
</div>
</div>
<a
href={`https://calendar.google.com/calendar/event?eid=${btoa(
event.id
)}`}
target="_blank"
rel="noopener noreferrer"
className="opacity-0 group-hover:opacity-100 transition-opacity shrink-0"
title="Open in Google Calendar"
>
<ExternalLink className="w-4 h-4 text-muted-foreground hover:text-foreground" />
</a>
</div>
</CardContent>
</Card>
);
}