Files
Calendar_Frontend/lib/calendars.ts
PaddyandClaude Opus 5.5 0cda40f85e Rewrite the calendar frontend in Next.js
Replaces the Angular 18 app with Next.js 16 / React 19 / Tailwind 4 / shadcn,
the same stack and design system as nachklang-feedback, planned in
docs/rewrite-plan.md.

- Admin: all calendars in one mobile-friendly list (month groups, calendar
  chips, Kommend/Vergangen/Alle, search, sort, filters in the URL), an edit
  sheet for create/edit/duplicate/delete, publish confirmation, and an
  "Abonnieren" dialog with subscription links for every calendar.
- Public landing page: subscribe links from env instead of hardcoded, plus
  the next public events, server-rendered and revalidated every 5 minutes.
- All times handled explicitly as Europe/Berlin; whole-day events stored as
  00:00-23:59. Fixes carried over from the Angular app: the move dialog
  leaking a cancelled calendar change, saves that looked saved when they
  failed, in-place Date mutation, and duplicated calendar-id mappings.
- Same shared-session auth as before (proxy.ts gate, /admin/me, 401 vs 403).
- 112 Vitest tests over lib/; docs in docs/ (architecture, features,
  gotchas, API wish list, deployment).

Needs the API branch feature/calendar-ical-escaping-subscriptions deployed
first (GET /calendar/events/subscriptions).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:15:59 +02:00

83 lines
2.4 KiB
TypeScript

/**
* The calendars, in one place.
*
* The Angular app kept this mapping three times - a switch in the admin page,
* an index-into-array in the move popup (which only worked because ids 3 and 4
* happen to sit in that order) and `calendarId === 5` checks in the API service
* and the filter - and the copies had drifted: the move popup could not move an
* event to Birthdays.
*
* Ids and slugs mirror `calendarNames` in the API's events.router.ts; the API
* has no endpoint that lists them (docs/api-wishlist.md), so a new calendar
* needs an entry here too.
*
* Class names are written out in full so Tailwind's source scan finds them.
*/
export type CalendarSlug = "public" | "members" | "choir" | "management" | "birthdays";
export interface Calendar {
id: number;
slug: CalendarSlug;
label: string;
/** Shown by default in the admin list. Birthdays would flood it. */
defaultVisible: boolean;
/**
* A recurrence every event in this calendar gets, whatever the form says.
* Replaces the old `calendarId === 5` special case in api.service.ts.
*/
forcedRepeatFrequency?: string;
dotClass: string;
badgeClass: string;
}
export const CALENDARS: readonly Calendar[] = [
{
id: 1,
slug: "public",
label: "Öffentlich",
defaultVisible: true,
dotClass: "bg-sky-500",
badgeClass: "bg-sky-50 text-sky-800 ring-sky-200",
},
{
id: 2,
slug: "members",
label: "Mitglieder",
defaultVisible: true,
dotClass: "bg-emerald-500",
badgeClass: "bg-emerald-50 text-emerald-800 ring-emerald-200",
},
{
id: 4,
slug: "choir",
label: "Chor",
defaultVisible: true,
dotClass: "bg-violet-500",
badgeClass: "bg-violet-50 text-violet-800 ring-violet-200",
},
{
id: 3,
slug: "management",
label: "Management",
defaultVisible: true,
dotClass: "bg-amber-500",
badgeClass: "bg-amber-50 text-amber-900 ring-amber-200",
},
{
id: 5,
slug: "birthdays",
label: "Geburtstage",
defaultVisible: false,
forcedRepeatFrequency: "YEARLY",
dotClass: "bg-rose-500",
badgeClass: "bg-rose-50 text-rose-800 ring-rose-200",
},
];
export const calendarById = (id: number): Calendar | undefined => CALENDARS.find((c) => c.id === id);
export const calendarBySlug = (slug: string): Calendar | undefined => CALENDARS.find((c) => c.slug === slug);
export const DEFAULT_VISIBLE_CALENDAR_IDS: readonly number[] = CALENDARS.filter((c) => c.defaultVisible).map((c) => c.id);