From 5a7bc5e10dd29db6a706946ced9804ab268134ae Mon Sep 17 00:00:00 2001 From: Till JS Date: Thu, 2 Apr 2026 17:15:49 +0200 Subject: [PATCH] feat(manacore/web): expand EventDetailModal with full feature set Complete rewrite of the calendar event detail modal: - Color accent bar matching calendar color - Date with weekday + time range + duration display - Recurrence-aware delete dialog (this event / entire series) - Tags display - Copy to clipboard button - Created/updated metadata - Cleaner layout with better typography Co-Authored-By: Claude Opus 4.6 (1M context) --- .../components/EventDetailModal.svelte | 374 ++++++++++++++---- 1 file changed, 301 insertions(+), 73 deletions(-) diff --git a/apps/manacore/apps/web/src/lib/modules/calendar/components/EventDetailModal.svelte b/apps/manacore/apps/web/src/lib/modules/calendar/components/EventDetailModal.svelte index 6d5aeb161..c928ec589 100644 --- a/apps/manacore/apps/web/src/lib/modules/calendar/components/EventDetailModal.svelte +++ b/apps/manacore/apps/web/src/lib/modules/calendar/components/EventDetailModal.svelte @@ -13,8 +13,12 @@ ArrowsClockwise, MapPin, TextAlignLeft, + Tag, + ShareNetwork, + Copy, + Check, } from '@manacore/shared-icons'; - import { format } from 'date-fns'; + import { format, differenceInDays, differenceInHours, differenceInMinutes } from 'date-fns'; import { de } from 'date-fns/locale'; interface Props { @@ -27,21 +31,42 @@ const calendarsCtx: { readonly value: Calendar[] } = getContext('calendars'); let isEditing = $state(false); + let showDeleteOptions = $state(false); + let copied = $state(false); let calendarName = $derived(getCalendarById(calendarsCtx.value, event.calendarId)?.name); let calendarColor = $derived(getCalendarColor(calendarsCtx.value, event.calendarId)); + let isRecurring = $derived(!!event.recurrenceRule); + let hasParent = $derived(!!event.parentEventId); + // Format time display function formatEventTime(ev: CalendarEvent): string { if (ev.isAllDay) return 'Ganztägig'; const start = toDate(ev.startTime); const end = toDate(ev.endTime); - return `${format(start, 'PPPp', { locale: de })} - ${format(end, 'p', { locale: de })}`; + const dateStr = format(start, 'EEEE, d. MMMM yyyy', { locale: de }); + const timeStr = `${format(start, 'HH:mm')} – ${format(end, 'HH:mm')}`; + return `${dateStr}\n${timeStr}`; + } + + // Format duration + function formatDuration(ev: CalendarEvent): string { + if (ev.isAllDay) return ''; + const start = toDate(ev.startTime); + const end = toDate(ev.endTime); + const mins = differenceInMinutes(end, start); + if (mins < 60) return `${mins} Min.`; + const hours = differenceInHours(end, start); + const remainMins = mins % 60; + if (remainMins === 0) return `${hours} Std.`; + return `${hours} Std. ${remainMins} Min.`; } function formatRecurrence(rule: string): string { if (!rule) return ''; if (rule.includes('FREQ=DAILY')) return 'Täglich'; if (rule.includes('FREQ=WEEKLY')) { + if (rule.includes('INTERVAL=2')) return 'Alle 2 Wochen'; if (rule.includes('BYDAY=')) { const days = rule.match(/BYDAY=([A-Z,]+)/)?.[1]; if (days) { @@ -54,11 +79,10 @@ SA: 'Sa', SU: 'So', }; - const translatedDays = days + return `Wöchentlich (${days .split(',') .map((d) => dayMap[d] || d) - .join(', '); - return `Wöchentlich (${translatedDays})`; + .join(', ')})`; } } return 'Wöchentlich'; @@ -73,18 +97,53 @@ isEditing = false; } - async function handleDelete() { - if (!confirm('Möchten Sie diesen Termin wirklich löschen?')) return; - await eventsStore.deleteEvent(event.id); + async function handleDelete(mode: 'this' | 'all') { + if (mode === 'this') { + await eventsStore.deleteEvent(event.id); + } else { + // Delete all: if this has a parent, delete parent; otherwise delete this + const targetId = event.parentEventId || event.id; + await eventsStore.deleteEvent(targetId); + } + showDeleteOptions = false; onClose(); } + function handleDeleteClick() { + if (isRecurring || hasParent) { + showDeleteOptions = true; + } else { + if (confirm('Diesen Termin löschen?')) { + eventsStore.deleteEvent(event.id); + onClose(); + } + } + } + + async function copyToClipboard() { + const start = toDate(event.startTime); + const text = [ + event.title, + event.isAllDay ? 'Ganztägig' : `${format(start, 'dd.MM.yyyy HH:mm')}`, + event.location ? `Ort: ${event.location}` : '', + event.description || '', + ] + .filter(Boolean) + .join('\n'); + await navigator.clipboard.writeText(text); + copied = true; + setTimeout(() => (copied = false), 1500); + } + function handleBackdropClick(e: MouseEvent) { if (e.target === e.currentTarget) onClose(); } function handleKeydown(e: KeyboardEvent) { - if (e.key === 'Escape') onClose(); + if (e.key === 'Escape') { + if (showDeleteOptions) showDeleteOptions = false; + else onClose(); + } } @@ -93,19 +152,36 @@ + +{#if showDeleteOptions} + +
(showDeleteOptions = false)}> + +
+{/if} +