feat(calendar): Wiederholungen, Tagesansicht, Terminfarben, Farbpunkt als Schalter

Fünf Wünsche aus dem Durchtesten:

1. Wiederholungen einstellbar (täglich/wöchentlich/monatlich/jährlich,
   alle N, Ende nie/nach Anzahl/an einem Datum). Der eigentliche Aufwand
   lag nicht im Formular, sondern im Aufklappen: eine wöchentliche Reihe
   erschien sonst als *ein* Eintrag. Der Server klappt sie jetzt im
   angefragten Zeitfenster auf (?from=&to=, rrule-go über go-icals
   RecurrenceSet), die Ansichten fragen ihren sichtbaren Ausschnitt an.
   Geprüft: 5 Termine wöchentlich, nach Umstellung auf 14-tägig korrekt
   neu gerechnet, Fenster einer Woche liefert genau einen.

   Regeln, die das Formular nicht abbildet (etwa jeder zweite Dienstag
   im Monat), werden weiterhin korrekt *angezeigt* -- geprüft, die vier
   Termine stimmen -- aber nicht zum Bearbeiten angeboten (409). Ein
   halb verstandenes Speichern würde die Regel sonst still beschneiden.

2. Tag wird erst geöffnet, statt sofort einen Termin anzulegen: Klick
   auf einen Tag zeigt dessen Termine als Liste, dort wählt man einen
   aus oder legt bewusst einen neuen an.

3. Eigene Farbe je Termin (COLOR nach RFC 7986), leer = Kalenderfarbe.

4. Kalenderliste ohne Kästchen: der Farbpunkt selbst ist der Schalter --
   gefüllt heißt sichtbar, leerer Ring ausgeblendet.

5. Ganztägig als Schiebeschalter statt Kästchen -- es stellt das
   Formular um, das ist keine Nebenangabe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-09-04 00:56:28 +02:00
1 parent 105ce8de4a
commit 79a7cc6a3d
8 files changed
+795 -218

No files matched your search

+34 -6
View File
@@ -902,20 +902,40 @@ export interface CalendarItem {
created_at: string
}
/** Die Wiederholung, die das Formular anbietet: alle N Tage/Wochen/Monate/
* Jahre, wahlweise mit Ende. Verwickeltere Regeln aus fremden Clients
* bleiben erhalten, kommen hier aber als `recurrence: undefined` an und
* sind dann nur lesbar (siehe events_api.go). */
export interface Recurrence {
freq: 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY'
interval: number
count?: number
/** ISO 8601. Der Tag, an dem die Reihe endet (einschließlich). */
until?: string
}
export interface CalendarEvent {
uid: string
summary: string
description: string
location: string
/** ISO 8601, immer UTC ("...Z") – der Server rechnet nicht um, das
* übernimmt der Browser beim Anzeigen (new Date(...) kennt die lokale
* Zeitzone bereits). */
/** ISO 8601, immer UTC ("...Z"). Bei ganztägigen Terminen Mitternacht UTC
* als reine Datumsangabe – nie über new Date(...) in Ortszeit umrechnen,
* sondern über eventDay() aus components/calendar/dates.ts. */
start: string
end?: string
all_day: boolean
/** Über einen echten CalDAV-Client mit Wiederholung angelegt – hier nur
* sichtbar, nicht bearbeitbar (siehe events_api.go, UpdateEvent). */
/** Leer = Farbe des Kalenders. */
color?: string
/** Es hängt überhaupt eine Wiederholung dran. */
recurring: boolean
/** Die Regel – nur gesetzt, wenn sie sich im Formular abbilden lässt. */
recurrence?: Recurrence
/** Bei aufgeklappten Wiederholungen: Anfang/Ende der **Reihe**, während
* start/end den einzelnen Termin tragen. Das Formular bearbeitet immer
* die Reihe. */
series_start?: string
series_end?: string
etag: string
}
@@ -926,6 +946,8 @@ export interface EventInput {
start: string
end?: string
all_day: boolean
color?: string
recurrence?: Recurrence | null
}
export const calendarApi = {
@@ -949,7 +971,13 @@ export const calendarApi = {
return json.data
},
listEvents: (id: string) => request<CalendarEvent[]>('GET', `/calendars/${id}/events`),
/** Termine im Zeitfenster. Wiederholungen kommen vom Server bereits in
* einzelne Termine aufgeklappt zurück – deshalb braucht es die Grenzen:
* eine tägliche Reihe ohne Ende hat sonst kein natürliches Ende. */
listEvents: (id: string, from?: Date, to?: Date) => {
const qs = from && to ? `?from=${from.toISOString()}&to=${to.toISOString()}` : ''
return request<CalendarEvent[]>('GET', `/calendars/${id}/events${qs}`)
},
createEvent: (id: string, input: EventInput) =>
request<CalendarEvent>('POST', `/calendars/${id}/events`, input),
updateEvent: (id: string, uid: string, input: EventInput) =>