204 lines
6.4 KiB
TypeScript
204 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";
|
|
import { siteUrls } from "@/lib/config/sites";
|
|
|
|
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={`${siteUrls.googleCalendar}/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>
|
|
);
|
|
}
|