feat(calendar): future-events accordion, ICS/Google export, multi-day

Right panel now shows all upcoming events as a card-style accordion
(date block + title + location/time chips visible, body collapsed).
Day-grouped with sticky headers + relative countdown.

- ICS download per event (RFC-5545, client-side blob)
- Google Calendar render-template link per event
- Maps link via location field (new schema field)
- Multi-day events via terminEnde (new schema field): grid spans,
  range "Mai / 14–16 / 2026" date block, expanded eventsByDay map
- Tag filter chips (new schema field) above calendar
- Past events <details> at list end, expanded if no future
- Live status badge: "läuft gerade" / "beginnt in 45 min"
  (60s tick interval)
- Today marker on calendar grid (outline + sr-only label)
- Auto-scroll next event into panel viewport on mount
- "Im Kalender anzeigen" jumps grid to event month
- Urgency-colored left border per event: now/today/tomorrow/week/
  later in fire/erde/himmel/stein
- Zebra striping + 4px separator between day-groups
- ARIA: aria-current=date, aria-pressed on day/tag buttons
- Print CSS: details auto-open, day-group break-inside: avoid
- Timezone fix: dayKey uses local Y/M/D not ISO string slice
- Empty-title fallback to "Termin"
- Side-by-side layout from md+ (was always stacked)

Extends CalendarItemData with terminEnde, location, tags
inline until OpenAPI regen catches up.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Peter Meier
2026-05-07 15:45:16 +02:00
parent 4790d571fd
commit a1c59b549d
3 changed files with 815 additions and 156 deletions
+16 -5
View File
@@ -337,9 +337,11 @@ export interface OrganisationsBlockData {
export interface LiveStrommixBlockData {
_type?: "live_strommix";
_slug?: string;
/** `full` = card with argument + breakdown disclosure. `compact` = single
* horizontal strip for sidebar / homepage placement. Default: `full`. */
variant?: "full" | "compact";
/** `full` = card with argument + breakdown disclosure + history accordion.
* `hero` = pct + status + sparkline + trend, no sub-cards or accordion.
* `compact` = single horizontal strip for sidebar / homepage placement.
* Default: `full`. */
variant?: "full" | "hero" | "compact";
intro_headline?: string;
intro_text?: string;
threshold_dunkelflaute?: number;
@@ -380,8 +382,17 @@ export interface LiveStrommixBlockData {
layout?: BlockLayout;
}
/** Kalender-Item (calendar_item) aus OpenAPI; terminZeit = ISO date-time. */
export type CalendarItemData = components["schemas"]["calendar_item"];
/** Kalender-Item (calendar_item) aus OpenAPI; terminZeit = ISO date-time.
* Lokale Erweiterungen (terminEnde, location, tags) sind im RustyCMS-Schema
* ergänzt und werden bis zur OpenAPI-Regen hier inline mitgeführt. */
export type CalendarItemData = components["schemas"]["calendar_item"] & {
/** Optionales Endedatum für Mehrtagesveranstaltungen (ISO date-time). */
terminEnde?: string;
/** Optionale Veranstaltungsort-Beschreibung (Adresse oder Name). */
location?: string;
/** Optionale Tags zur Filterung. */
tags?: string[];
};
/** Kalender-Block (_type: "calendar"). items nach Resolve: CalendarItemData[]. Basis aus OpenAPI (calendar). */
export type CalendarBlockData = Omit<