187 lines
5.5 KiB
TypeScript
187 lines
5.5 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Calendar, Clock, MapPin, ExternalLink, FileText, X } from "lucide-react";
|
|
import { format, parseISO } from "date-fns";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
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;
|
|
}
|
|
|
|
interface EventDetailModalProps {
|
|
event: CalendarEvent | null;
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
}
|
|
|
|
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 getEventEndDate(event: { end: { dateTime?: string; date?: string } }): Date | null {
|
|
if (event.end.dateTime) {
|
|
return parseISO(event.end.dateTime);
|
|
}
|
|
if (event.end.date) {
|
|
return parseISO(event.end.date);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function isAllDayEvent(event: { start: { dateTime?: string; date?: string } }): boolean {
|
|
return !!event.start.date && !event.start.dateTime;
|
|
}
|
|
|
|
function getCalendarColor(calendarName: string): string {
|
|
const colors: Record<string, string> = {
|
|
personal: "bg-pink-500 text-white",
|
|
work: "bg-blue-500 text-white",
|
|
health: "bg-green-500 text-white",
|
|
social: "bg-purple-500 text-white",
|
|
family: "bg-yellow-500 text-black",
|
|
task: "bg-orange-500 text-white",
|
|
primary: "bg-blue-600 text-white",
|
|
};
|
|
|
|
const lower = calendarName.toLowerCase();
|
|
for (const [key, color] of Object.entries(colors)) {
|
|
if (lower.includes(key)) return color;
|
|
}
|
|
|
|
return "bg-muted text-muted-foreground";
|
|
}
|
|
|
|
function formatEventTime(event: CalendarEvent): string {
|
|
if (isAllDayEvent(event)) {
|
|
return "All day";
|
|
}
|
|
|
|
const start = getEventDate(event);
|
|
const end = getEventEndDate(event);
|
|
|
|
if (end) {
|
|
return `${format(start, "h:mm a")} - ${format(end, "h:mm a")}`;
|
|
}
|
|
|
|
return format(start, "h:mm a");
|
|
}
|
|
|
|
function formatEventDate(event: CalendarEvent): string {
|
|
const date = getEventDate(event);
|
|
return format(date, "EEEE, MMMM d, yyyy");
|
|
}
|
|
|
|
export function EventDetailModal({ event, isOpen, onClose }: EventDetailModalProps) {
|
|
if (!event) return null;
|
|
|
|
const eventUrl = event.htmlLink || `https://calendar.google.com/calendar/event?eid=${btoa(event.id)}`;
|
|
|
|
return (
|
|
<Dialog open={isOpen} onOpenChange={onClose}>
|
|
<DialogContent className="sm:max-w-lg">
|
|
<DialogHeader>
|
|
<div className="flex items-start gap-3">
|
|
<Badge className={cn("shrink-0", getCalendarColor(event.calendarName))}>
|
|
{event.calendarName}
|
|
</Badge>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="shrink-0 -mr-2 -mt-2"
|
|
onClick={onClose}
|
|
>
|
|
<X className="w-4 h-4" />
|
|
</Button>
|
|
</div>
|
|
<DialogTitle className="text-xl mt-2">{event.summary}</DialogTitle>
|
|
</DialogHeader>
|
|
|
|
<div className="space-y-4 mt-2">
|
|
{/* Date and Time */}
|
|
<div className="flex items-start gap-3">
|
|
<div className="w-8 h-8 rounded-lg bg-secondary flex items-center justify-center shrink-0">
|
|
<Calendar className="w-4 h-4 text-muted-foreground" />
|
|
</div>
|
|
<div>
|
|
<p className="font-medium">{formatEventDate(event)}</p>
|
|
<p className="text-sm text-muted-foreground flex items-center gap-1">
|
|
<Clock className="w-3 h-3" />
|
|
{formatEventTime(event)}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Location */}
|
|
{event.location && (
|
|
<div className="flex items-start gap-3">
|
|
<div className="w-8 h-8 rounded-lg bg-secondary flex items-center justify-center shrink-0">
|
|
<MapPin className="w-4 h-4 text-muted-foreground" />
|
|
</div>
|
|
<div>
|
|
<p className="font-medium">Location</p>
|
|
<p className="text-sm text-muted-foreground">{event.location}</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Description */}
|
|
{event.description && (
|
|
<div className="flex items-start gap-3">
|
|
<div className="w-8 h-8 rounded-lg bg-secondary flex items-center justify-center shrink-0">
|
|
<FileText className="w-4 h-4 text-muted-foreground" />
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<p className="font-medium">Description</p>
|
|
<div className="text-sm text-muted-foreground mt-1 whitespace-pre-wrap break-words max-h-40 overflow-y-auto">
|
|
{event.description}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Actions */}
|
|
<div className="flex gap-2 pt-4 border-t">
|
|
<Button
|
|
variant="outline"
|
|
className="flex-1 gap-2"
|
|
onClick={() => window.open(eventUrl, "_blank")}
|
|
>
|
|
<ExternalLink className="w-4 h-4" />
|
|
Open in Google Calendar
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|