Skip to content

Commit 16b0e23

Browse files
Strehkclaude
andauthored
feat(calendar): Add conference calendar management (#381)
* feat(calendar): Add conference calendar with days, tracks, and entries Implement a full calendar system for conferences including: - Database schema: CalendarDay, CalendarTrack, CalendarEntry models with migrations - GraphQL resolvers for CRUD operations on all calendar entities - CASL ability definitions for calendar entity authorization - Calendar management page with tabs for days, tracks, entries, and preview - Calendar display components (CalendarDisplay, CalendarDayView, CalendarEntryCard) - Dashboard calendar section for conference overview - Color palette system for track styling - Conference configuration option to show/hide calendar - Seed data for development - German and English translations Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(calendar): Add reusable Place model for calendar entries Replace plain place strings on CalendarEntry with a proper Place model that stores venue name, address, coordinates, directions, info/warnings, website URL, and site plan PDF. This enables venue reuse across entries and structured data for maps and navigation. - Add Place model with Prisma migration and GraphQL CRUD resolver - Add Places tab to calendar management with full CRUD including PDF upload - Change entry form place input from text to select dropdown - Add DaisyUI tooltips showing track descriptions on hover - Update calendar display components to use place object interface - Add seed data for Landtag and Kulturzentrum venues Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(calendar): Add clickable calendar entries with detail drawer - Install vaul-svelte for animated drawer with touch gestures - Make CalendarEntryCard clickable (div→button with hover feedback) - Thread onEntryClick callback through CalendarDayView to CalendarDisplay - Create CalendarEntryDrawer with responsive direction (bottom on mobile, right on desktop), colored header, time/track info, description, location details with directions/warnings, interactive map via sveaflet, and platform-aware map links (Apple Maps, Google Maps, OpenStreetMap) - Expand place fields in all three calendar GraphQL queries - Add translateCalendarEntryColor to enumTranslations utility - Use localized color names in management entries table - Add translation keys for drawer UI (EN + DE) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(calendar): Add track filter dropdown to entries tab Allows filtering entries by track when a day has multiple tracks, improving overview when managing many entries. Filter resets automatically when switching days. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(calendar): Improve calendar UX with overlap warnings, today auto-select, and trackless day support - Add overlap detection and warning indicators in entries table - Auto-select today's date in calendar preview if it matches a day - Fix calendar day view to handle days with no tracks (single unnamed column) - Add site plan download button in entry detail drawer - Refine entry card styling and color palette Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(calendar): Add bulk copy entries from one day to another Allows copying all entries from the currently selected day to a target day, matching tracks by name and preserving times, places, and other properties. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(dashboard): Collapse calendar section by default Add collapsible support to DashboardSection with a visible toggle button, and default the calendar to collapsed so users don't have to scroll past it. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(calendar): Add Plus Code input and map preview to place modals Allow users to paste Google Maps Plus Codes (both short codes like "84W3+7X Kiel" and full codes) to auto-fill latitude/longitude when creating or editing places. Short codes are resolved via Nominatim geocoding. A live OpenStreetMap preview shows the marker position below the coordinate fields. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(calendar): Add JSON export/import for calendar days Allows exporting a day's tracks, entries, and places as JSON and importing them when creating a new day, so schedule structures can be reused across days or conferences. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(calendar): Resolve lint errors in calendar components - Use void operator for $effect dependency tracking expressions - Replace URLSearchParams with manual URL construction in drawer - Add eslint-disable for non-reactive Date/Set usage in functions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(deps): Bump nodemailer 6.10.1 → 7.0.13 (CVE-2025-14874) Fixes DoS via crafted email address header causing infinite recursion in the address parser. Only breaking change in v7 is SES SDK removal which does not affect this project (uses SMTP transport). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * ci: Increase Node.js heap size for PR build job The vite build OOMs on the default ~2GB heap in GitHub Actions. Match the Dockerfile's 8GB setting for the CI build step. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(calendar): Address PR review feedback - Tighten time regex to reject invalid hours/minutes (e.g. 99:99) - Use z.nativeEnum(CalendarEntryColor) instead of manual z.enum - Remove unnecessary type casts (as string, as CalendarEntryColor) - Replace type assertions with instanceof/typeof narrowing in file upload handlers - Fix end-time auto-calculation overflow (23:00 + 1h → 00:00 instead of 24:00) - Parallelize copyDayEntries mutations with Promise.allSettled - Derive allColors from colorMap keys instead of duplicating enum values - Add keyboard accessibility to DashboardSection collapsible header - Prevent headerAction clicks from toggling collapse via stopPropagation - Use fa-duotone consistently for chevron and info icons Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(calendar): Address second round of PR review feedback - Wrap JSON.parse in try/catch for consistent error message on malformed input - Use setUTCHours instead of setHours for timezone-deterministic entry times - Add aria-expanded attribute to DashboardSection collapsible header - Add response.ok check before parsing Nominatim geocoding response Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(calendar): Fix export/import timezone round-trip Export now uses getUTCHours to produce timezone-agnostic time strings, matching the import mutation's setUTCHours. This ensures entries survive an export→import cycle without shifting by the browser's UTC offset. All other operations (create, edit, copy, move, display, time marker) consistently use browser-local timezone methods, which is correct for co-located conference attendees. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(calendar): Add conference timezone for calendar time marker The CalendarTimeMarker "now" line previously used UTC time, causing it to show the wrong position for conferences not in UTC. This adds a timezone field to Conference (defaulting to Europe/Berlin) and threads it through the component chain so the marker uses Intl.DateTimeFormat to compute conference-local "now". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(calendar): Address PR review feedback and add PARTICIPANT_CARE permissions - Grant PARTICIPANT_CARE role CRUD access to all calendar entities (CalendarDay, CalendarTrack, CalendarEntry, Place) in both CASL abilities and resolver-level create mutation checks - Fix CalendarTimeMarker midnight boundary with hourCycle: 'h23' - Fix CalendarDisplay today-tab selection to use conference timezone - Add end time > start time validation in entry create/edit modals - Fix informal "du" in German translation for calendarImportInvalidJSON - Document Calendar components in CLAUDE-UI.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(calendar): Adjust calendar entry color palette and fix indentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(calendar): Add edit buttons to calendar entry drawer on management pages Pass optional onEditEntry/onEditPlace callbacks through CalendarDisplay to CalendarEntryDrawer, rendering edit buttons in the drawer footer only on management pages. Dashboard view remains unaffected. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent bf279dc commit 16b0e23

File tree

48 files changed

+6247
-10
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+6247
-10
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ jobs:
7777
build:
7878
if: github.event_name == 'pull_request'
7979
runs-on: ubuntu-latest
80+
env:
81+
NODE_OPTIONS: '--max-old-space-size=8192'
8082
steps:
8183
- uses: actions/checkout@v4
8284
- uses: ./.github/actions/setup-bun

CLAUDE-UI.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ This document provides guidance for building consistent user interfaces in MUNif
1515

1616
Components are located in `src/lib/components/`. Key directories:
1717

18+
- `Calendar/` - Conference calendar display (day views, time markers, entry cards)
1819
- `Form/` - Form inputs integrated with sveltekit-superforms
1920
- `Dashboard/` - Dashboard section layouts and widgets
2021
- `DataTable/` - Searchable, sortable data tables
@@ -182,6 +183,93 @@ interface Props {
182183

183184
---
184185

186+
## Calendar Components
187+
188+
Components in `src/lib/components/Calendar/` for displaying conference calendar schedules.
189+
190+
### CalendarDisplay
191+
192+
`src/lib/components/Calendar/CalendarDisplay.svelte`
193+
194+
Main calendar container that renders day tabs (small screens) or side-by-side columns (3xl+). Handles day selection, track filtering, and entry click → drawer.
195+
196+
#### Props Interface
197+
198+
```typescript
199+
interface Props {
200+
days: Day[]; // Array of calendar days with tracks and entries
201+
timezone?: string; // IANA timezone (default: 'UTC') — controls "now" marker and today detection
202+
}
203+
```
204+
205+
#### Usage Example
206+
207+
```svelte
208+
<script lang="ts">
209+
import CalendarDisplay from '$lib/components/Calendar/CalendarDisplay.svelte';
210+
</script>
211+
212+
<CalendarDisplay days={previewDays} timezone="Europe/Berlin" />
213+
```
214+
215+
#### Features
216+
217+
- Responsive layout: tabs on small screens, side-by-side grid on 3xl+
218+
- Automatic "today" tab selection using conference timezone
219+
- Per-day track filtering
220+
- Entry click opens `CalendarEntryDrawer` with details
221+
222+
### CalendarDayView
223+
224+
`src/lib/components/Calendar/CalendarDayView.svelte`
225+
226+
Renders a single day's timeline with hour grid, entries positioned by time, and a live "now" marker.
227+
228+
#### Props Interface
229+
230+
```typescript
231+
interface Props {
232+
dayName: string;
233+
date: Date;
234+
tracks: Track[];
235+
entries: Entry[];
236+
filterTrackId?: string | null;
237+
timezone?: string; // Passed to CalendarTimeMarker
238+
onEntryClick?: (entry: Entry) => void;
239+
}
240+
```
241+
242+
### CalendarTimeMarker
243+
244+
`src/lib/components/Calendar/CalendarTimeMarker.svelte`
245+
246+
Displays a red "now" line on the calendar timeline. Uses `Intl.DateTimeFormat` with conference timezone to compute position.
247+
248+
#### Props Interface
249+
250+
```typescript
251+
interface Props {
252+
startHour: number;
253+
endHour: number;
254+
hourHeight: number;
255+
timezone?: string; // IANA timezone (default: 'UTC')
256+
}
257+
```
258+
259+
### CalendarEntryCard
260+
261+
`src/lib/components/Calendar/CalendarEntryCard.svelte`
262+
263+
Renders a single calendar entry as a colored card positioned on the timeline. Shows icon, name, time range, room, and track.
264+
265+
### CalendarEntryDrawer
266+
267+
`src/lib/components/Calendar/CalendarEntryDrawer.svelte`
268+
269+
Slide-out drawer showing full entry details including place information, map, and site plan.
270+
271+
---
272+
185273
## Form Components (Critical)
186274

187275
Forms use `sveltekit-superforms` for validation and state management. Always structure forms consistently.

bun.lock

Lines changed: 19 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

messages/de.json

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,86 @@
195195
"bulletList": "Aufzählung",
196196
"byDelegationSize": "Nach Delegationsgröße",
197197
"byRegionalGroups": "Nach Regionalgruppen",
198+
"calendar": "Kalender",
199+
"calendarAddDay": "Tag hinzufügen",
200+
"calendarAddEntry": "Eintrag hinzufügen",
201+
"calendarAddPlace": "Ort hinzufügen",
202+
"calendarAddTrack": "Track hinzufügen",
203+
"calendarAllTracks": "Alle Tracks",
204+
"calendarBreak": "Pause",
205+
"calendarCeremony": "Zeremonie",
206+
"calendarChangeDay": "Auf anderen Tag verschieben",
207+
"calendarColor": "Farbe",
208+
"calendarConfirmDeleteDay": "Möchtest du diesen Tag wirklich löschen? Alle zugehörigen Tracks und Einträge werden ebenfalls gelöscht.",
209+
"calendarConfirmDeleteEntry": "Möchtest du diesen Eintrag wirklich löschen?",
210+
"calendarConfirmDeletePlace": "Soll dieser Ort wirklich gelöscht werden? Kalendereinträge behalten ihre anderen Daten, verlieren aber die Ortsreferenz.",
211+
"calendarConfirmDeleteTrack": "Möchtest du diesen Track wirklich löschen?",
212+
"calendarCopyDayEntries": "Einträge auf Tag kopieren",
213+
"calendarCopyDayEntriesConfirm": "{count} Einträge kopieren",
214+
"calendarCopyDayEntriesDescription": "Alle {count} Einträge von \"{day}\" auf einen anderen Tag kopieren. Track-Zuweisungen werden nach Name zugeordnet.",
215+
"calendarDay": "Tag",
216+
"calendarDays": "Tage",
217+
"calendarDeleteDay": "Tag löschen",
218+
"calendarDeleteEntry": "Eintrag löschen",
219+
"calendarDeletePlace": "Ort löschen",
220+
"calendarDeleteTrack": "Track löschen",
221+
"calendarDescription": "Zeitplan der Konferenz",
222+
"calendarEditDay": "Tag bearbeiten",
223+
"calendarEditEntry": "Eintrag bearbeiten",
224+
"calendarEditPlace": "Ort bearbeiten",
225+
"calendarEditTrack": "Track bearbeiten",
226+
"calendarEndTime": "Endzeit",
227+
"calendarEntries": "Einträge",
228+
"calendarEntryDetails": "Veranstaltungsdetails",
229+
"calendarEntryEndBeforeStart": "Die Endzeit muss nach der Startzeit liegen",
230+
"calendarEntryLocation": "Ort",
231+
"calendarEntryOverlap": "Dieser Eintrag überlappt mit einem anderen Eintrag im selben Track",
232+
"calendarEntryTime": "Zeit",
233+
"calendarExportDay": "Tag exportieren",
234+
"calendarHighlight": "Highlight",
235+
"calendarIcon": "Icon",
236+
"calendarImportFromFile": "Aus Datei importieren",
237+
"calendarImportInvalidJSON": "Ungültiges Dateiformat. Bitte wähle eine gültige Kalendertag-Exportdatei.",
238+
"calendarImportPreview": "{tracks} Tracks, {entries} Einträge werden importiert",
239+
"calendarInfo": "Info",
240+
"calendarLogistics": "Logistik",
241+
"calendarNoDays": "Noch keine Tage angelegt",
242+
"calendarNoEntries": "Noch keine Einträge angelegt",
243+
"calendarNoPlace": "Kein Ort",
244+
"calendarNoPlaces": "Noch keine Orte angelegt",
245+
"calendarNoTracks": "Noch keine Tracks angelegt",
246+
"calendarOpenInMaps": "In Karten öffnen",
247+
"calendarPlace": "Ort",
248+
"calendarPlaceAddress": "Adresse",
249+
"calendarPlaceDetails": "Ortsinformationen",
250+
"calendarPlaceDirections": "Anfahrt",
251+
"calendarPlaceInfo": "Hinweise / Warnungen",
252+
"calendarPlaceLatitude": "Breitengrad",
253+
"calendarPlaceLongitude": "Längengrad",
254+
"calendarPlaceName": "Name",
255+
"calendarPlacePlusCode": "Plus Code",
256+
"calendarPlacePlusCodeCityNotFound": "Referenzort konnte nicht gefunden werden. Bitte den Stadtnamen überprüfen.",
257+
"calendarPlacePlusCodeHint": "Plus Code aus Google Maps einfügen (z.B. 84W3+7X Kiel). Kurze Codes mit Stadtname und vollständige Codes werden unterstützt.",
258+
"calendarPlacePlusCodeInvalid": "Ungültiger Plus Code",
259+
"calendarPlacePlusCodeShort": "Dies ist ein kurzer Plus Code. Bitte den Stadtnamen angeben (z.B. 84W3+7X Kiel).",
260+
"calendarPlaceSitePlan": "Lageplan (PDF)",
261+
"calendarPlaceSitePlanDownload": "Lageplan herunterladen",
262+
"calendarPlaceSitePlanReplace": "PDF ersetzen",
263+
"calendarPlaceSitePlanUpload": "PDF hochladen",
264+
"calendarPlaceWebsite": "Webseite",
265+
"calendarPlaces": "Orte",
266+
"calendarPreview": "Vorschau",
267+
"calendarRoom": "Raum",
268+
"calendarSectionDescription": "Hier siehst du den Zeitplan der Konferenz.",
269+
"calendarSectionTitle": "Zeitplan",
270+
"calendarSession": "Sitzung",
271+
"calendarSocial": "Social Event",
272+
"calendarSortOrder": "Reihenfolge",
273+
"calendarStartTime": "Startzeit",
274+
"calendarTargetDay": "Zieltag",
275+
"calendarTrack": "Track",
276+
"calendarTracks": "Tracks",
277+
"calendarWorkshop": "Workshop",
198278
"cameraSettings": "Kameraeinstellung",
199279
"cancel": "Abbruch",
200280
"cannotBeUndone": "Bitte bestätige die Eingabe! Die Änderung kann nicht rückgängig gemacht werden!",
@@ -320,6 +400,8 @@
320400
"conferenceStatusPreparation": "Vorbereitung",
321401
"conferenceStatusPreparationDescription": "Anmeldung geschlossen. Teilnehmende bereiten sich auf die Konferenz vor.",
322402
"conferenceTimeSettings": "Einstellung der Konferenzphasen",
403+
"conferenceTimezone": "Zeitzone der Konferenz",
404+
"conferenceTimezoneHint": "Bestimmt die Position der 'Jetzt'-Markierung im Kalender. Wähle die Zeitzone des Konferenzortes.",
323405
"conferenceTitle": "Konferenzname",
324406
"conferenceWebsite": "Website",
325407
"confirm": "Bestätigen",
@@ -378,6 +460,7 @@
378460
"currentTasks": "Aktuelle Aufgaben",
379461
"dangerZone": "Danger Zone",
380462
"dashboard": "Dashboard",
463+
"date": "Datum",
381464
"dateMustBeBefore": "Das Datum muss vor dem {date} liegen.",
382465
"days": "Tag(e)",
383466
"daysUntilConference": "Tage bis zur Konferenz",
@@ -1138,6 +1221,7 @@
11381221
"sha": "SHA",
11391222
"shortWaitingList": "Kurze Warteliste",
11401223
"show": "Anzeigen",
1224+
"showCalendar": "Kalender für Teilnehmende anzeigen",
11411225
"showInfoExpandedDescription": "Wenn aktiviert, wird der Ankündigungsbereich im Dashboard vollständig angezeigt. Nützlich für dringende Ankündigungen.",
11421226
"showInfoExpandedLabel": "Ankündigungen standardmäßig ausgeklappt anzeigen",
11431227
"showLess": "Weniger anzeigen",

0 commit comments

Comments
 (0)