import { useState } from "react"; import { AlignLeft, Bell, Calendar as CalIcon, Check, Clock, HelpCircle, Link2, MapPin, Pencil, Repeat, Trash2, Users, X, Mail } from "lucide-react"; import { useCalendar, myParticipantKeys, isRecurring, eventRule, participantEmail, type EventInstance } from "@/store/calendar"; import { Popover, type Anchor } from "@/ui/popover"; import { confirmDialog } from "@/ui/dialog"; import { toast } from "@/ui/toast"; import { formatTimeRange, humanDuration, parseDuration } from "@/lib/dates"; import { describeRule } from "@/lib/recurrence"; import { useCompose } from "@/store/compose"; import { useSettings } from "@/store/settings"; import { categoryOf, eventColor } from "./CalendarContextMenu"; export function EventPopover({ inst, anchor, onClose, onEdit }: { inst: EventInstance; anchor: Anchor; onClose: () => void; onEdit: () => void }) { const cal = useCalendar(); const ev = inst.event; const [busy, setBusy] = useState(false); const categories = useSettings((s) => s.settings.eventCategories); const color = eventColor(ev, inst.calendar?.color, categories); const category = categoryOf(ev, categories); const participants = Object.entries(ev.participants ?? {}); const myKeys = myParticipantKeys(ev, cal.identities); const myStatus = myKeys.length ? ev.participants?.[myKeys[0]!]?.participationStatus : undefined; const isOrganizer = ev.isOrigin !== false && (!participants.length || participants.some(([k, p]) => p.roles?.owner && myKeys.includes(k))); const canEdit = inst.calendar?.myRights.mayWriteAll || (inst.calendar?.myRights.mayWriteOwn && isOrganizer) || !inst.calendar; const baseId = ev.baseEventId ?? ev.id; const location = Object.values(ev.locations ?? {})[0]; const vloc = Object.values(ev.virtualLocations ?? {})[0]; const alerts = Object.values(ev.alerts ?? {}); const openCompose = useCompose((s) => s.open); const del = async () => { const recurring = isRecurring(ev); const ok = await confirmDialog({ title: recurring ? "Delete all occurrences?" : "Delete this event?", message: recurring ? "This will delete the entire series." : undefined, confirmLabel: "Delete", danger: true }); if (!ok) return; setBusy(true); try { await cal.destroyEvent(baseId, participants.length > 1); toast.success("Event deleted"); onClose(); } catch (err) { toast.error((err as Error).message); } finally { setBusy(false); } }; const rsvp = async (status: "accepted" | "tentative" | "declined") => { setBusy(true); try { await cal.rsvp(baseId, status); toast.success("Response sent"); onClose(); } catch (err) { toast.error((err as Error).message); } finally { setBusy(false); } }; return (
{canEdit && } {canEdit && }

{ev.title || "(untitled)"}

{formatTimeRange(inst.start, inst.end, inst.allDay)}{ev.timeZone && !inst.allDay ? · {ev.timeZone} : null}
{eventRule(ev) &&
{describeRule(eventRule(ev)!)}
} {location?.name &&
{location.name}
} {vloc?.uri &&
{vloc.name || vloc.uri}
} {ev.description &&
{ev.description}
} {alerts.length > 0 &&
{alerts.map((a) => ("offset" in a.trigger ? humanDuration(parseDuration(a.trigger.offset)) + (parseDuration(a.trigger.offset) < 0 ? " before" : " after") : "at " + a.trigger.when)).join(", ")}
} {category &&
{category.name}
}
{inst.calendar?.name ?? "Calendar"}{ev.status === "cancelled" ? " · cancelled" : ev.status === "tentative" ? " · tentative" : ""}{ev.privacy && ev.privacy !== "public" ? ` · ${ev.privacy}` : ""}{ev.freeBusyStatus === "free" ? " · shown as free" : ""}
{participants.length > 0 && (
{participants.length} participant{participants.length === 1 ? "" : "s"}
{participants.map(([k, p]) => (
{p.name || participantEmail(p)} {p.roles?.owner && organizer} {p.roles?.optional && optional}
))}
)} {myKeys.length > 0 && !isOrganizer && (
Going?
)}
); }