import { useMemo, useState, type FormEvent, } from "react"; import { Trash2 } from "lucide-react"; import { Button, DateField, Dialog, TimeField, ToggleSwitch, useUnsavedDraftGuard, } from "@govoplan/core-webui"; import type { CalendarCollection, CalendarEvent, CalendarEventCreatePayload, } from "../../api/calendar"; import { addDays, addHours, addMinutesToTime, calendarDraftKey, errorText, localDateTime, startOfDay, toInputDate, toInputTime, } from "./calendarViewModel"; type CalendarEventEndMode = "end" | "duration"; type CalendarEventAdvancedPayload = Pick< CalendarEventCreatePayload, | "organizer" | "attendees" | "categories" | "rrule" | "rdate" | "exdate" | "reminders" | "attachments" | "related_to" | "icalendar" | "metadata" >; export function CalendarEventDialog({ calendars, defaultCalendarId, event, focusDate, saving, canWrite, canDelete, onCancel, onSave, onDelete }: {calendars: CalendarCollection[];defaultCalendarId: string;event: CalendarEvent | null;focusDate: Date;saving: boolean;canWrite: boolean;canDelete: boolean;onCancel: () => void;onSave: (payload: CalendarEventCreatePayload, event: CalendarEvent | null) => Promise;onDelete: (event: CalendarEvent) => Promise;}) { const initialStart = event ? new Date(event.start_at) : focusDate; const initialEnd = event?.end_at ? new Date(event.end_at) : addHours(initialStart, 1); const initialAllDayEnd = event?.all_day && event.end_at ? addDays(startOfDay(initialEnd), -1) : initialEnd; const initialDurationSeconds = event?.duration_seconds ?? Math.max(3600, Math.round((initialEnd.getTime() - initialStart.getTime()) / 1000)); const [summary, setSummary] = useState(event?.summary ?? ""); const [calendarId, setCalendarId] = useState(event?.calendar_id ?? defaultCalendarId); const [description, setDescription] = useState(event?.description ?? ""); const [location, setLocation] = useState(event?.location ?? ""); const [startDate, setStartDate] = useState(toInputDate(initialStart)); const [endDate, setEndDate] = useState(toInputDate(initialAllDayEnd < initialStart ? initialStart : initialAllDayEnd)); const [startTime, setStartTime] = useState(toInputTime(initialStart)); const [endTime, setEndTime] = useState(toInputTime(initialEnd)); const [allDay, setAllDay] = useState(event?.all_day ?? false); const [endMode, setEndMode] = useState(event && eventUsesDuration(event) ? "duration" : "end"); const [durationSeconds, setDurationSeconds] = useState(String(initialDurationSeconds)); const [uid, setUid] = useState(event?.uid ?? ""); const [recurrenceId, setRecurrenceId] = useState(event?.recurrence_id ?? ""); const [sequence, setSequence] = useState(String(event?.sequence ?? 0)); const [status, setStatus] = useState(event?.status ?? "CONFIRMED"); const [transparency, setTransparency] = useState(event?.transparency ?? "OPAQUE"); const [classification, setClassification] = useState(event?.classification ?? "PUBLIC"); const [timezone, setTimezone] = useState(event?.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone ?? "UTC"); const [categoriesText, setCategoriesText] = useState((event?.categories ?? []).join(", ")); const [organizerJson, setOrganizerJson] = useState(jsonTextareaValue(event?.organizer ?? null)); const [attendeesJson, setAttendeesJson] = useState(jsonTextareaValue(event?.attendees ?? [])); const [rruleText, setRruleText] = useState(event?.rrule ? serializeRRuleText(event.rrule) : ""); const [rdateJson, setRdateJson] = useState(jsonTextareaValue(event?.rdate ?? [])); const [exdateJson, setExdateJson] = useState(jsonTextareaValue(event?.exdate ?? [])); const [remindersJson, setRemindersJson] = useState(jsonTextareaValue(event?.reminders ?? [])); const [attachmentsJson, setAttachmentsJson] = useState(jsonTextareaValue(event?.attachments ?? [])); const [relatedToJson, setRelatedToJson] = useState(jsonTextareaValue(event?.related_to ?? [])); const [sourceKind, setSourceKind] = useState(event?.source_kind ?? "local"); const [sourceHref, setSourceHref] = useState(event?.source_href ?? ""); const [etag, setEtag] = useState(event?.etag ?? ""); const [icalendarJson, setICalendarJson] = useState(jsonTextareaValue(event?.icalendar ?? {})); const [metadataJson, setMetadataJson] = useState(jsonTextareaValue(event?.metadata ?? {})); const [formError, setFormError] = useState(""); const [confirmingDelete, setConfirmingDelete] = useState(false); const formId = "calendar-event-form"; const eventDraft = { summary, calendarId, description, location, startDate, endDate, startTime, endTime, allDay, endMode, durationSeconds, uid, recurrenceId, sequence, status, transparency, classification, timezone, categoriesText, organizerJson, attendeesJson, rruleText, rdateJson, exdateJson, remindersJson, attachmentsJson, relatedToJson, sourceKind, sourceHref, etag, icalendarJson, metadataJson }; const initialEventDraftKey = useMemo(() => calendarDraftKey(eventDraft), []); const eventDirty = calendarDraftKey(eventDraft) !== initialEventDraftKey; useUnsavedDraftGuard({ dirty: eventDirty, onSave: saveCurrent, onDiscard: onCancel }); function handleAllDayChange(next: boolean) { setAllDay(next); if (next) { setStartTime("00:00"); setEndTime("00:00"); } else if (startTime === "00:00" && endTime === "00:00") { setStartTime("09:00"); setEndTime("10:00"); } } function handleStartDateChange(next: string) { setStartDate(next); if (endDate < next) setEndDate(next); } function handleStartTimeChange(next: string) { setStartTime(next); if (startDate === endDate && endTime <= next) setEndTime(addMinutesToTime(next, 60)); } async function saveCurrent(): Promise { setFormError(""); const startAt = allDay ? localDateTime(startDate, "00:00") : localDateTime(startDate, startTime); const parsedDurationSeconds = Math.max(0, Number(durationSeconds) || 0); const usesDuration = !allDay && endMode === "duration"; const endAt = allDay ? addDays(localDateTime(endDate, "00:00"), 1) : usesDuration ? new Date(startAt.getTime() + parsedDurationSeconds * 1000) : localDateTime(endDate, endTime); if (usesDuration && parsedDurationSeconds <= 0) { setFormError("i18n:govoplan-calendar.duration_must_be_greater_than_zero.519292f7"); return false; } if (endAt <= startAt) { setFormError(allDay ? "i18n:govoplan-calendar.end_date_must_be_on_or_after_start_date.a2518865" : "i18n:govoplan-calendar.end_date_and_time_must_be_after_start_date_and_t.daf31831"); return false; } let advanced: CalendarEventAdvancedPayload; try { advanced = { organizer: parseJsonObjectOrNull(organizerJson, "i18n:govoplan-calendar.organizer.debd1720"), attendees: parseJsonArray(attendeesJson, "i18n:govoplan-calendar.attendees.a45a0962"), categories: commaSeparatedValues(categoriesText), rrule: parseRRuleInput(rruleText), rdate: parseJsonArray(rdateJson, "RDATE"), exdate: parseJsonArray(exdateJson, "EXDATE"), reminders: parseJsonArray(remindersJson, "i18n:govoplan-calendar.reminders.ae8c3939"), attachments: parseJsonArray(attachmentsJson, "i18n:govoplan-calendar.attachments.6771ade6"), related_to: parseJsonArray(relatedToJson, "i18n:govoplan-calendar.related_to.0e7989ff"), icalendar: withDurationMode(parseJsonObject(icalendarJson, "i18n:govoplan-calendar.icalendar.f388476d"), usesDuration), metadata: parseJsonObject(metadataJson, "i18n:govoplan-calendar.metadata.251edc0e") }; } catch (err) { setFormError(errorText(err)); return false; } const payload: CalendarEventCreatePayload = { calendar_id: calendarId, summary, description: description || null, location: location || null, sequence: Math.max(0, Number(sequence) || 0), status, transparency, classification, start_at: startAt.toISOString(), end_at: endAt.toISOString(), duration_seconds: usesDuration ? parsedDurationSeconds : null, all_day: allDay, timezone: timezone.trim() || null, source_kind: sourceKind.trim() || "local", source_href: sourceHref.trim() || null, etag: etag.trim() || null, ...advanced }; if (!event) { if (uid.trim()) payload.uid = uid.trim(); if (recurrenceId.trim()) payload.recurrence_id = recurrenceId.trim(); } return onSave(payload, event); } function submit(formEvent: FormEvent) { formEvent.preventDefault(); void saveCurrent(); } function requestDelete() { if (!event) return; if (!confirmingDelete) { setConfirmingDelete(true); return; } void onDelete(event); } return (
{event && canDelete && }
}>
{formError &&

{formError}

}