diff --git a/apps/calendar/apps/web/src/lib/components/calendar/CalendarHeader.svelte b/apps/calendar/apps/web/src/lib/components/calendar/CalendarHeader.svelte index 3cff869bd..9f1169c0e 100644 --- a/apps/calendar/apps/web/src/lib/components/calendar/CalendarHeader.svelte +++ b/apps/calendar/apps/web/src/lib/components/calendar/CalendarHeader.svelte @@ -19,7 +19,15 @@ }; // Views to show in selector - const visibleViews: CalendarViewType[] = ['day', '5day', 'week', '10day', '14day', 'month', 'year']; + const visibleViews: CalendarViewType[] = [ + 'day', + '5day', + 'week', + '10day', + '14day', + 'month', + 'year', + ]; // Format title based on view type let title = $derived.by(() => { @@ -30,9 +38,17 @@ // Helper to format date range const formatRange = () => { if (rangeStart.getMonth() === rangeEnd.getMonth()) { - return format(rangeStart, 'd.', { locale: de }) + ' - ' + format(rangeEnd, 'd. MMMM yyyy', { locale: de }); + return ( + format(rangeStart, 'd.', { locale: de }) + + ' - ' + + format(rangeEnd, 'd. MMMM yyyy', { locale: de }) + ); } - return format(rangeStart, 'd. MMM', { locale: de }) + ' - ' + format(rangeEnd, 'd. MMM yyyy', { locale: de }); + return ( + format(rangeStart, 'd. MMM', { locale: de }) + + ' - ' + + format(rangeEnd, 'd. MMM yyyy', { locale: de }) + ); }; switch (viewStore.viewType) { @@ -61,14 +77,17 @@
- + -
+
{#each hours as hour}
{/each} @@ -308,7 +315,6 @@ .month-view { display: flex; flex-direction: column; - } .weekday-headers { @@ -408,7 +414,10 @@ text-overflow: ellipsis; touch-action: none; user-select: none; - transition: transform 150ms ease, box-shadow 150ms ease, opacity 150ms ease; + transition: + transform 150ms ease, + box-shadow 150ms ease, + opacity 150ms ease; } .event-pill:hover { @@ -429,7 +438,8 @@ } @keyframes pulse-outline { - 0%, 100% { + 0%, + 100% { outline-color: hsl(var(--color-primary)); } 50% { diff --git a/apps/calendar/apps/web/src/lib/components/calendar/MultiDayView.svelte b/apps/calendar/apps/web/src/lib/components/calendar/MultiDayView.svelte index 8736a724d..89d638460 100644 --- a/apps/calendar/apps/web/src/lib/components/calendar/MultiDayView.svelte +++ b/apps/calendar/apps/web/src/lib/components/calendar/MultiDayView.svelte @@ -32,7 +32,9 @@ // Get date-fns locale based on current app locale const dateLocales = { de, en: enUS, fr, es, it }; - let currentDateLocale = $derived(dateLocales[$locale?.substring(0, 2) as keyof typeof dateLocales] || de); + let currentDateLocale = $derived( + dateLocales[$locale?.substring(0, 2) as keyof typeof dateLocales] || de + ); // Generate days based on view range, optionally filtering weekends let allDays = $derived( @@ -55,7 +57,9 @@ ); // Calculate visible hours range for positioning - let firstVisibleHour = $derived(settingsStore.filterHoursEnabled ? settingsStore.dayStartHour : 0); + let firstVisibleHour = $derived( + settingsStore.filterHoursEnabled ? settingsStore.dayStartHour : 0 + ); let lastVisibleHour = $derived(settingsStore.filterHoursEnabled ? settingsStore.dayEndHour : 24); let totalVisibleHours = $derived(lastVisibleHour - firstVisibleHour); @@ -125,16 +129,16 @@ // Split all-day events by display mode function getHeaderAllDayEventsForDay(day: Date) { - return getAllDayEventsForDay(day).filter(e => getEventDisplayMode(e) === 'header'); + return getAllDayEventsForDay(day).filter((e) => getEventDisplayMode(e) === 'header'); } function getBlockAllDayEventsForDay(day: Date) { - return getAllDayEventsForDay(day).filter(e => getEventDisplayMode(e) === 'block'); + return getAllDayEventsForDay(day).filter((e) => getEventDisplayMode(e) === 'block'); } // Check if there are any all-day events to show in header let hasAnyHeaderAllDayEvents = $derived( - days.some(day => getHeaderAllDayEventsForDay(day).length > 0) + days.some((day) => getHeaderAllDayEventsForDay(day).length > 0) ); function getEventStyle(event: any) { @@ -162,7 +166,9 @@ if (isDragging || isResizing || hasMoved) { e.preventDefault(); e.stopPropagation(); - setTimeout(() => { hasMoved = false; }, 100); + setTimeout(() => { + hasMoved = false; + }, 100); return; } goto(`/?event=${event.id}`); @@ -248,7 +254,10 @@ const newMinutes = getMinutesFromY(e.clientY) - dragOffsetMinutes; // Clamp to valid range (firstVisibleHour to lastVisibleHour) - const clampedMinutes = Math.max(firstVisibleHour * 60, Math.min(lastVisibleHour * 60 - 15, newMinutes)); + const clampedMinutes = Math.max( + firstVisibleHour * 60, + Math.min(lastVisibleHour * 60 - 15, newMinutes) + ); // Update preview dragPreviewTop = minutesToPercent(clampedMinutes); @@ -268,8 +277,14 @@ return; } - const start = typeof draggedEvent.startTime === 'string' ? parseISO(draggedEvent.startTime) : draggedEvent.startTime; - const end = typeof draggedEvent.endTime === 'string' ? parseISO(draggedEvent.endTime) : draggedEvent.endTime; + const start = + typeof draggedEvent.startTime === 'string' + ? parseISO(draggedEvent.startTime) + : draggedEvent.startTime; + const end = + typeof draggedEvent.endTime === 'string' + ? parseISO(draggedEvent.endTime) + : draggedEvent.endTime; const duration = differenceInMinutes(end, start); // Calculate new start time @@ -336,17 +351,24 @@ hasMoved = true; const currentMinutes = getMinutesFromY(e.clientY); - const originalStartMinutes = resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes(); + const originalStartMinutes = + resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes(); const originalEndMinutes = resizeOriginalEnd.getHours() * 60 + resizeOriginalEnd.getMinutes(); if (resizeEdge === 'bottom') { // Resize from bottom - change end time - const newEndMinutes = Math.max(originalStartMinutes + 15, Math.min(lastVisibleHour * 60, currentMinutes)); + const newEndMinutes = Math.max( + originalStartMinutes + 15, + Math.min(lastVisibleHour * 60, currentMinutes) + ); const newDuration = newEndMinutes - originalStartMinutes; resizePreviewHeight = (newDuration / (totalVisibleHours * 60)) * 100; } else { // Resize from top - change start time - const newStartMinutes = Math.max(firstVisibleHour * 60, Math.min(originalEndMinutes - 15, currentMinutes)); + const newStartMinutes = Math.max( + firstVisibleHour * 60, + Math.min(originalEndMinutes - 15, currentMinutes) + ); const newDuration = originalEndMinutes - newStartMinutes; resizePreviewTop = minutesToPercent(newStartMinutes); resizePreviewHeight = (newDuration / (totalVisibleHours * 60)) * 100; @@ -367,20 +389,27 @@ } const currentMinutes = getMinutesFromY(e.clientY); - const originalStartMinutes = resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes(); + const originalStartMinutes = + resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes(); const originalEndMinutes = resizeOriginalEnd.getHours() * 60 + resizeOriginalEnd.getMinutes(); let newStart = resizeOriginalStart; let newEnd = resizeOriginalEnd; if (resizeEdge === 'bottom') { - const newEndMinutes = Math.max(originalStartMinutes + 15, Math.min(lastVisibleHour * 60, currentMinutes)); + const newEndMinutes = Math.max( + originalStartMinutes + 15, + Math.min(lastVisibleHour * 60, currentMinutes) + ); const newHours = Math.floor(newEndMinutes / 60); const newMins = newEndMinutes % 60; newEnd = setHours(new Date(resizeOriginalEnd), newHours); newEnd = setMinutes(newEnd, newMins); } else { - const newStartMinutes = Math.max(firstVisibleHour * 60, Math.min(originalEndMinutes - 15, currentMinutes)); + const newStartMinutes = Math.max( + firstVisibleHour * 60, + Math.min(originalEndMinutes - 15, currentMinutes) + ); const newHours = Math.floor(newStartMinutes / 60); const newMins = newStartMinutes % 60; newStart = setHours(new Date(resizeOriginalStart), newHours); @@ -434,7 +463,11 @@ }); -
+
{#if hasAnyHeaderAllDayEvents}
@@ -461,7 +494,9 @@
{#each days as day}
- {format(day, columnClass === 'very-compact' ? 'EEEEE' : 'EEE', { locale: de })} + {format(day, columnClass === 'very-compact' ? 'EEEEE' : 'EEE', { locale: de })} {format(day, 'd')}
{/each} @@ -553,10 +588,12 @@ {/each} - {#if isDragging && draggedEvent && dragTargetDay && isSameDay(day, dragTargetDay) && !getEventsForDay(day).some(e => e.id === draggedEvent.id)} + {#if isDragging && draggedEvent && dragTargetDay && isSameDay(day, dragTargetDay) && !getEventsForDay(day).some((e) => e.id === draggedEvent.id)}
{#if columnClass !== 'very-compact'} {formatEventTime(draggedEvent.startTime)} @@ -795,7 +832,9 @@ padding: 2px 4px; border-radius: var(--radius-sm); overflow: hidden; - transition: box-shadow 0.15s ease, opacity 0.15s ease; + transition: + box-shadow 0.15s ease, + opacity 0.15s ease; touch-action: none; user-select: none; } @@ -826,7 +865,8 @@ } @keyframes pulse-outline { - 0%, 100% { + 0%, + 100% { outline-color: hsl(var(--color-primary)); } 50% { diff --git a/apps/calendar/apps/web/src/lib/components/calendar/WeekView.svelte b/apps/calendar/apps/web/src/lib/components/calendar/WeekView.svelte index df0a5f2da..ed05c57a1 100644 --- a/apps/calendar/apps/web/src/lib/components/calendar/WeekView.svelte +++ b/apps/calendar/apps/web/src/lib/components/calendar/WeekView.svelte @@ -33,7 +33,9 @@ // Get date-fns locale based on current app locale const dateLocales = { de, en: enUS, fr, es, it }; - let currentDateLocale = $derived(dateLocales[$locale?.substring(0, 2) as keyof typeof dateLocales] || de); + let currentDateLocale = $derived( + dateLocales[$locale?.substring(0, 2) as keyof typeof dateLocales] || de + ); // Generate days of the week, optionally filtering weekends let allDays = $derived( @@ -61,7 +63,9 @@ ); // Calculate visible hours range for positioning - let firstVisibleHour = $derived(settingsStore.filterHoursEnabled ? settingsStore.dayStartHour : 0); + let firstVisibleHour = $derived( + settingsStore.filterHoursEnabled ? settingsStore.dayStartHour : 0 + ); let lastVisibleHour = $derived(settingsStore.filterHoursEnabled ? settingsStore.dayEndHour : 24); let totalVisibleHours = $derived(lastVisibleHour - firstVisibleHour); @@ -124,16 +128,16 @@ // Split all-day events by display mode function getHeaderAllDayEventsForDay(day: Date) { - return getAllDayEventsForDay(day).filter(e => getEventDisplayMode(e) === 'header'); + return getAllDayEventsForDay(day).filter((e) => getEventDisplayMode(e) === 'header'); } function getBlockAllDayEventsForDay(day: Date) { - return getAllDayEventsForDay(day).filter(e => getEventDisplayMode(e) === 'block'); + return getAllDayEventsForDay(day).filter((e) => getEventDisplayMode(e) === 'block'); } // Check if there are any all-day events to show in header let hasAnyHeaderAllDayEvents = $derived( - days.some(day => getHeaderAllDayEventsForDay(day).length > 0) + days.some((day) => getHeaderAllDayEventsForDay(day).length > 0) ); function getEventStyle(event: any) { @@ -162,7 +166,9 @@ e.preventDefault(); e.stopPropagation(); // Reset hasMoved after a short delay to allow for the next clean click - setTimeout(() => { hasMoved = false; }, 100); + setTimeout(() => { + hasMoved = false; + }, 100); return; } goto(`/?event=${event.id}`); @@ -248,7 +254,10 @@ const newMinutes = getMinutesFromY(e.clientY) - dragOffsetMinutes; // Clamp to valid range (firstVisibleHour to lastVisibleHour) - const clampedMinutes = Math.max(firstVisibleHour * 60, Math.min(lastVisibleHour * 60 - 15, newMinutes)); + const clampedMinutes = Math.max( + firstVisibleHour * 60, + Math.min(lastVisibleHour * 60 - 15, newMinutes) + ); // Update preview dragPreviewTop = minutesToPercent(clampedMinutes); @@ -268,8 +277,14 @@ return; } - const start = typeof draggedEvent.startTime === 'string' ? parseISO(draggedEvent.startTime) : draggedEvent.startTime; - const end = typeof draggedEvent.endTime === 'string' ? parseISO(draggedEvent.endTime) : draggedEvent.endTime; + const start = + typeof draggedEvent.startTime === 'string' + ? parseISO(draggedEvent.startTime) + : draggedEvent.startTime; + const end = + typeof draggedEvent.endTime === 'string' + ? parseISO(draggedEvent.endTime) + : draggedEvent.endTime; const duration = differenceInMinutes(end, start); // Calculate new start time @@ -336,17 +351,24 @@ hasMoved = true; const currentMinutes = getMinutesFromY(e.clientY); - const originalStartMinutes = resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes(); + const originalStartMinutes = + resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes(); const originalEndMinutes = resizeOriginalEnd.getHours() * 60 + resizeOriginalEnd.getMinutes(); if (resizeEdge === 'bottom') { // Resize from bottom - change end time - const newEndMinutes = Math.max(originalStartMinutes + 15, Math.min(lastVisibleHour * 60, currentMinutes)); + const newEndMinutes = Math.max( + originalStartMinutes + 15, + Math.min(lastVisibleHour * 60, currentMinutes) + ); const newDuration = newEndMinutes - originalStartMinutes; resizePreviewHeight = (newDuration / (totalVisibleHours * 60)) * 100; } else { // Resize from top - change start time - const newStartMinutes = Math.max(firstVisibleHour * 60, Math.min(originalEndMinutes - 15, currentMinutes)); + const newStartMinutes = Math.max( + firstVisibleHour * 60, + Math.min(originalEndMinutes - 15, currentMinutes) + ); const newDuration = originalEndMinutes - newStartMinutes; resizePreviewTop = minutesToPercent(newStartMinutes); resizePreviewHeight = (newDuration / (totalVisibleHours * 60)) * 100; @@ -367,20 +389,27 @@ } const currentMinutes = getMinutesFromY(e.clientY); - const originalStartMinutes = resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes(); + const originalStartMinutes = + resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes(); const originalEndMinutes = resizeOriginalEnd.getHours() * 60 + resizeOriginalEnd.getMinutes(); let newStart = resizeOriginalStart; let newEnd = resizeOriginalEnd; if (resizeEdge === 'bottom') { - const newEndMinutes = Math.max(originalStartMinutes + 15, Math.min(lastVisibleHour * 60, currentMinutes)); + const newEndMinutes = Math.max( + originalStartMinutes + 15, + Math.min(lastVisibleHour * 60, currentMinutes) + ); const newHours = Math.floor(newEndMinutes / 60); const newMins = newEndMinutes % 60; newEnd = setHours(new Date(resizeOriginalEnd), newHours); newEnd = setMinutes(newEnd, newMins); } else { - const newStartMinutes = Math.max(firstVisibleHour * 60, Math.min(originalEndMinutes - 15, currentMinutes)); + const newStartMinutes = Math.max( + firstVisibleHour * 60, + Math.min(originalEndMinutes - 15, currentMinutes) + ); const newHours = Math.floor(newStartMinutes / 60); const newMins = newStartMinutes % 60; newStart = setHours(new Date(resizeOriginalStart), newHours); @@ -563,10 +592,12 @@ {/each} - {#if isDragging && draggedEvent && dragTargetDay && isSameDay(day, dragTargetDay) && !getEventsForDay(day).some(e => e.id === draggedEvent.id)} + {#if isDragging && draggedEvent && dragTargetDay && isSameDay(day, dragTargetDay) && !getEventsForDay(day).some((e) => e.id === draggedEvent.id)}
{formatEventTime(draggedEvent.startTime)} {draggedEvent.title} @@ -764,7 +795,9 @@ cursor: grab; z-index: 1; overflow: hidden; - transition: box-shadow 0.15s ease, opacity 0.15s ease; + transition: + box-shadow 0.15s ease, + opacity 0.15s ease; touch-action: none; user-select: none; } @@ -801,7 +834,8 @@ } @keyframes pulse-outline { - 0%, 100% { + 0%, + 100% { outline-color: hsl(var(--color-primary)); } 50% { diff --git a/apps/calendar/apps/web/src/lib/components/event/EventDetailModal.svelte b/apps/calendar/apps/web/src/lib/components/event/EventDetailModal.svelte index b85cf4e44..2c5334b45 100644 --- a/apps/calendar/apps/web/src/lib/components/event/EventDetailModal.svelte +++ b/apps/calendar/apps/web/src/lib/components/event/EventDetailModal.svelte @@ -104,11 +104,9 @@ // Get calendar info for the event let calendarName = $derived( - event ? calendarsStore.calendars.find(c => c.id === event!.calendarId)?.name : undefined - ); - let calendarColor = $derived( - event ? calendarsStore.getColor(event.calendarId) : '#3b82f6' + event ? calendarsStore.calendars.find((c) => c.id === event!.calendarId)?.name : undefined ); + let calendarColor = $derived(event ? calendarsStore.getColor(event.calendarId) : '#3b82f6'); // Format recurrence rule to human readable text function formatRecurrence(rule: string): string { @@ -120,9 +118,18 @@ const days = rule.match(/BYDAY=([A-Z,]+)/)?.[1]; if (days) { const dayMap: Record = { - MO: 'Mo', TU: 'Di', WE: 'Mi', TH: 'Do', FR: 'Fr', SA: 'Sa', SU: 'So' + MO: 'Mo', + TU: 'Di', + WE: 'Mi', + TH: 'Do', + FR: 'Fr', + SA: 'Sa', + SU: 'So', }; - const translatedDays = days.split(',').map(d => dayMap[d] || d).join(', '); + const translatedDays = days + .split(',') + .map((d) => dayMap[d] || d) + .join(', '); return `Wöchentlich (${translatedDays})`; } } @@ -153,20 +160,35 @@ {#if !isEditing} {/if}
@@ -194,7 +216,12 @@
- +
@@ -208,7 +235,12 @@
- +
@@ -223,8 +255,18 @@
- - + +
@@ -257,15 +299,30 @@
- +
Videokonferenz - + Beitreten - +
@@ -277,15 +334,30 @@
- + @@ -297,7 +369,12 @@
- +
@@ -312,7 +389,12 @@
- +
@@ -322,8 +404,17 @@
{attendee.name || attendee.email} {#if attendee.status} - - {attendee.status === 'accepted' ? '✓' : attendee.status === 'declined' ? '✗' : '?'} + + {attendee.status === 'accepted' + ? '✓' + : attendee.status === 'declined' + ? '✗' + : '?'} {/if}
diff --git a/apps/calendar/apps/web/src/lib/components/event/EventForm.svelte b/apps/calendar/apps/web/src/lib/components/event/EventForm.svelte index 6e0edb42d..251df6fd0 100644 --- a/apps/calendar/apps/web/src/lib/components/event/EventForm.svelte +++ b/apps/calendar/apps/web/src/lib/components/event/EventForm.svelte @@ -1,7 +1,12 @@