import { CalendarDays, ExternalLink, MapPin, Plus } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { Link } from "react-router"; import { Button, DismissibleAlert, LoadingFrame, SelectionList, SelectionListItem, SelectionListItemContent, hasScope, quickAccessLaunchState, useDashboardWidgetData, type QuickAccessToolRenderContext } from "@govoplan/core-webui"; import { listCalendarEvents, type CalendarEvent } from "../../api/calendar"; const AGENDA_DAYS = 21; const AGENDA_LIMIT = 7; type Props = Pick< QuickAccessToolRenderContext, "settings" | "auth" | "launchContext" | "complete" | "close" >; /** * Calendar-owned, range- and result-bounded agenda. The API applies tenant, * scope, and collection-visibility checks before any event reaches the rail. */ export default function CalendarQuickAccess({ settings, auth, launchContext, complete, close }: Props) { const [selectedKey, setSelectedKey] = useState(""); const rangeStart = useMemo( () => agendaStart(launchContext.temporalContext), [ launchContext.temporalContext.validAt, launchContext.temporalContext.validityMode ] ); const rangeEnd = useMemo(() => { const end = new Date(rangeStart); end.setDate(end.getDate() + AGENDA_DAYS); return end; }, [rangeStart]); const load = useCallback(async () => { const response = await listCalendarEvents(settings, { start_at: rangeStart.toISOString(), end_at: rangeEnd.toISOString(), expand_recurring: true, limit: AGENDA_LIMIT }); return response.events.filter( (event) => event.status.toUpperCase() !== "CANCELLED" ); }, [rangeEnd, rangeStart, settings]); const { data: events, loading, error } = useDashboardWidgetData(load, 0); const items = events ?? []; const selected = useMemo( () => items.find((event) => eventKey(event) === selectedKey) ?? items[0] ?? null, [items, selectedKey] ); const canCreate = hasScope(auth, "calendar:event:write"); useEffect(() => { if (!selectedKey && items[0]) setSelectedKey(eventKey(items[0])); if (selectedKey && !items.some((event) => eventKey(event) === selectedKey)) { setSelectedKey(items[0] ? eventKey(items[0]) : ""); } }, [items, selectedKey]); function selectForHost(event: CalendarEvent) { complete({ contractVersion: "1", outcome: "completed", action: "selected", reference: { ownerModule: "calendar", kind: "event", objectId: eventKey(event), tenantId: event.tenant_id, label: event.summary, version: `${event.sequence}:${event.updated_at}`, path: calendarFocusPath(event.start_at) } }); } return ( {error ? ( {error} ) : null}

i18n:govoplan-calendar.quick_access_range: {rangeLabel(rangeStart, rangeEnd)}

{items.length ? ( {items.map((event) => ( setSelectedKey(eventKey(event))} > ))} ) : !loading && !error ? (

i18n:govoplan-calendar.no_events.e339ba73

) : null} {selected ? (
{selected.summary} {eventTimeLabel(selected)} {selected.location ? ( ) : null} {selected.description ?

{selected.description}

: null}
selectForHost(selected)} >
) : null} {canCreate ? (
) : null}
); } function agendaStart( context: QuickAccessToolRenderContext["launchContext"]["temporalContext"] ): Date { if (context.validityMode === "at" && context.validAt) { const parsed = new Date(context.validAt); if (!Number.isNaN(parsed.getTime())) return parsed; } return new Date(); } function eventKey(event: CalendarEvent): string { return event.instance_id || event.id; } function calendarFocusPath(startAt: string): string { return `/calendar?focusDate=${encodeURIComponent(startAt)}`; } function calendarCreatePath(startAt: Date): string { return `/calendar?quickAction=create-event&startAt=${encodeURIComponent(startAt.toISOString())}`; } function eventTimeLabel(event: CalendarEvent): string { const start = new Date(event.start_at); if (event.all_day) { return new Intl.DateTimeFormat(undefined, { dateStyle: "medium" }).format(start); } return new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" }).format(start); } function rangeLabel(start: Date, end: Date): string { const formatter = new Intl.DateTimeFormat(undefined, { dateStyle: "medium" }); return `${formatter.format(start)} – ${formatter.format(end)}`; }