feat(sprint7): Phase 2.5 — Club Event Calendar

- Flyway V14: club_events + event_rsvps tables with reminder_sent tracking
- Enums: EventType, RsvpStatus, RecurrenceRule + extend AuditEventType/NotificationType
- Entities: ClubEvent (extends AbstractTenantEntity), EventRsvp (unique event+member)
- Repositories: ClubEventRepository, EventRsvpRepository with date-range and status queries
- EventService: CRUD, RSVP with maxAttendees enforcement (409 if full), iCal RFC 5545 generation, recurring event virtual expansion, notifications on create/cancel, auto-post to Info Board
- EventReminderScheduler: hourly check, 24h reminder to ACCEPTED/MAYBE attendees
- EventController: admin CRUD (MANAGE_INFO_BOARD permission), portal upcoming events, RSVP endpoint, iCal download (text/calendar), attendee list
- Frontend: events.ts service (React Query hooks matching apiClient pattern), admin calendar page (month grid with event dots, create dialog, event cards), portal events page (RSVP buttons, capacity display)
- Navigation: added Kalender with Calendar icon
- i18n: events.* keys in de.json and en.json
- UI: added @radix-ui/react-switch + Switch component
This commit is contained in:
Patrick Plate
2026-06-13 20:16:56 +02:00
parent 4aa27cd4f9
commit 05fd679c4d
27 changed files with 2044 additions and 1 deletions
+36
View File
@@ -772,5 +772,41 @@
"noPosts": "Noch keine Beiträge vorhanden. Erstelle den ersten Beitrag!",
"confirmDelete": "Möchtest du diesen Beitrag wirklich löschen?",
"unreadCount": "{count} ungelesen"
},
"events": {
"title": "Kalender",
"description": "Veranstaltungen und Termine des Vereins",
"portalTitle": "Veranstaltungen",
"portalDescription": "Kommende Termine und Events deines Vereins",
"createEvent": "Veranstaltung erstellen",
"upcomingEvents": "Nächste Termine",
"noUpcomingEvents": "Keine anstehenden Veranstaltungen",
"noEventsOnDay": "Keine Veranstaltungen an diesem Tag",
"cancel": "Absagen",
"full": "Ausgebucht",
"form": {
"title": "Titel",
"type": "Art",
"start": "Beginn",
"end": "Ende",
"location": "Ort",
"description": "Beschreibung",
"maxAttendees": "Max. Teilnehmer",
"recurring": "Wiederkehrend",
"recurrenceRule": "Wiederholung",
"recurrenceEnd": "Enddatum"
},
"rsvp": {
"accept": "Zusage",
"decline": "Absage",
"maybe": "Vielleicht"
},
"types": {
"MEETING": "Mitgliederversammlung",
"HARVEST_FESTIVAL": "Erntefest",
"BOARD_MEETING": "Vorstandssitzung",
"WORKSHOP": "Workshop",
"OTHER": "Sonstiges"
}
}
}
+36
View File
@@ -717,5 +717,41 @@
"noPosts": "No posts yet. Create the first one!",
"confirmDelete": "Are you sure you want to delete this post?",
"unreadCount": "{count} unread"
},
"events": {
"title": "Calendar",
"description": "Club events and appointments",
"portalTitle": "Events",
"portalDescription": "Upcoming events from your club",
"createEvent": "Create Event",
"upcomingEvents": "Upcoming Events",
"noUpcomingEvents": "No upcoming events",
"noEventsOnDay": "No events on this day",
"cancel": "Cancel",
"full": "Fully booked",
"form": {
"title": "Title",
"type": "Type",
"start": "Start",
"end": "End",
"location": "Location",
"description": "Description",
"maxAttendees": "Max Attendees",
"recurring": "Recurring",
"recurrenceRule": "Recurrence",
"recurrenceEnd": "End Date"
},
"rsvp": {
"accept": "Accept",
"decline": "Decline",
"maybe": "Maybe"
},
"types": {
"MEETING": "Member Meeting",
"HARVEST_FESTIVAL": "Harvest Festival",
"BOARD_MEETING": "Board Meeting",
"WORKSHOP": "Workshop",
"OTHER": "Other"
}
}
}