docs: Handbuchseite zur Wochenplanung

Erklärt nicht nur, wo man zieht, sondern warum der Kalender immer gewinnt,
warum sich das nicht aufschaukelt und was das Warnzeichen im Block
bedeutet.

Dazu der ehrliche Abschnitt "Was noch nicht geht": die Dauer lässt sich
nicht am Balken ziehen, und ein Zeitblock landet immer im ersten
verbundenen Kalender.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-09-10 23:17:23 +02:00
co-authored by Claude Opus 5
parent 5b1e9981e4
commit 0974695d93
7 changed files with 189 additions and 13 deletions
+28
View File
@@ -6,6 +6,34 @@ die Versionierung [Semantic Versioning](https://semver.org/lang/de/).
## [Unveröffentlicht]
### Hinzugefügt
- **Wochenansicht** mit Ziehen und Ablegen: Aufgaben aus der linken Spalte in
ein Zeitfenster ziehen legt einen Zeitblock an und schreibt ihn in den
Kalender. Die Dauer kommt aus dem geschätzten Aufwand der Aufgabe.
- Zeitblöcke verschieben und ausplanen; der Kalendertermin zieht mit.
- **Rückweg**: Wird ein Zeitblock im Kalender verschoben, übernimmt der Planer
das beim nächsten Abgleich. Wird er dort gelöscht, verschwindet auch der
Zeitblock — die Aufgabe bleibt und fällt zurück auf offen.
- Kapazität und Belegung je Wochentag über jeder Spalte; überbuchte Tage
werden rot, sperren aber nichts.
- Eine eingeplante Aufgabe wechselt selbsttätig von _Offen_ auf _Geplant_ und
wieder zurück, wenn nichts mehr für sie geplant ist.
- Handbuchseite zur Wochenplanung.
### Sicherheit
- **Schleifenschutz** über `hash = sha256(startISO|endeISO)`: Der Planer
schreibt nur bei eigener Änderung und übernimmt aus dem Kalender nur, wenn
der Fingerabdruck abweicht. Ohne das würden App und Kalender einander
endlos hinterherschreiben.
- Jeder Schreibzugriff auf einen bestehenden Termin geht mit `If-Match`
heraus. Bei HTTP 412 **gewinnt der Kalender**: der Planer schreibt nicht,
verwirft seinen Fingerabdruck und überlässt dem nächsten Abgleich, den
fremden Stand zu holen.
- Zeitblöcke gehören dem Benutzer über sein Kalenderkonto; die Kennung geht
in jede where-Bedingung, nicht in einen nachträglichen Vergleich.
## [0.3.0] – 2026-09-10
### Hinzugefügt
+7 -4
View File
@@ -7,9 +7,10 @@ lesen und Aufgaben per Ziehen und Ablegen als Zeitblöcke direkt in diese
Kalender schreiben — inklusive Rückweg, wenn ein Block im Kalender verschoben
wird.
> **Stand:** Meilenstein 3 von 8. Aufgaben, Projekte und das Lesen der
> Kalender stehen: der Planer weiß jetzt, wann überhaupt Platz ist. Das
> Schreiben der Zeitblöcke per Ziehen und Ablegen kommt mit M4. Der
> **Stand:** Meilenstein 4 von 8. Die Kernidee steht: Aufgaben lassen sich
> per Ziehen und Ablegen als Zeitblöcke in den Kalender schreiben, und
> Verschiebungen von dort kommen zurück. Es folgen Benachrichtigungen,
> Integrationen, Schnellerfassung vom Handy und ein MCP-Server. Der
> [Fahrplan](ROADMAP.md) sagt, was wann folgt.
- **[Schnellstart](docs/SCHNELLSTART.md)** – in zehn Minuten von `git clone`
@@ -30,6 +31,8 @@ wird.
- **Projekte** mit Arbeitspaketen, Meilensteinen und Fortschritt
- **Kalender lesen** aus Microsoft 365 und Google; die Startseite zeigt je
Bereich, wie voll der heutige Tag schon ist
- **Wochenplanung** per Ziehen und Ablegen: Aufgaben werden zu echten
Kalenderterminen, Verschiebungen im Kalender kommen zurück
- Anmeldung über einen OIDC-Anbieter, über Passkeys oder mit E-Mail und
Passwort — je Instanz einzeln zuschaltbar
- Rollen `Benutzer` und `Verwaltung`; jede schreibende Aktion wird
@@ -40,7 +43,6 @@ wird.
### Geplant
Zeitblöcke in den Kalender schreiben, Wochenplanung per Ziehen und Ablegen,
Benachrichtigungen, Integrationen, Schnellerfassung vom Handy und ein
MCP-Server. Siehe [ROADMAP.md](ROADMAP.md).
@@ -51,6 +53,7 @@ MCP-Server. Siehe [ROADMAP.md](ROADMAP.md).
| Anmelden | ![Anmeldeseite, hell](docs/bilder/anmelden-hell-schreibtisch.png) | ![Anmeldeseite, dunkel](docs/bilder/anmelden-dunkel-schreibtisch.png) |
| Start | ![Startseite, hell](docs/bilder/start-hell-schreibtisch.png) | ![Startseite, dunkel](docs/bilder/start-dunkel-schreibtisch.png) |
| Inbox | ![Inbox, hell](docs/bilder/inbox-hell-schreibtisch.png) | ![Inbox, dunkel](docs/bilder/inbox-dunkel-schreibtisch.png) |
| Woche | ![Woche, hell](docs/bilder/woche-hell-schreibtisch.png) | ![Woche, dunkel](docs/bilder/woche-dunkel-schreibtisch.png) |
| Aufgaben | ![Aufgaben, hell](docs/bilder/aufgaben-hell-schreibtisch.png) | ![Aufgaben, dunkel](docs/bilder/aufgaben-dunkel-schreibtisch.png) |
| Projekte | ![Projekte, hell](docs/bilder/projekte-hell-schreibtisch.png) | ![Projekte, dunkel](docs/bilder/projekte-dunkel-schreibtisch.png) |
| Ideen | ![Ideen, hell](docs/bilder/ideen-hell-schreibtisch.png) | ![Ideen, dunkel](docs/bilder/ideen-dunkel-schreibtisch.png) |
+13 -7
View File
@@ -8,7 +8,7 @@ grünen Prüfungen, aktueller Dokumentation und einem Eintrag im DEVLOG.
| [M1](#m1--fundament) | Fundament | erledigt |
| [M2](#m2--aufgaben-und-projekte) | Aufgaben und Projekte | erledigt |
| [M3](#m3--kalender-lesen) | Kalender lesen | erledigt |
| [M4](#m4--wochenplanung-und-zeitblöcke) | Wochenplanung und Zeitblöcke | geplant |
| [M4](#m4--wochenplanung-und-zeitblöcke) | Wochenplanung und Zeitblöcke | erledigt |
| [M5](#m5--benachrichtigungen) | Benachrichtigungen | geplant |
| [M6](#m6--integrationen) | Integrationen | geplant |
| [M7](#m7--schnellerfassung-und-api) | Schnellerfassung und API | geplant |
@@ -76,15 +76,21 @@ später" und wartet darauf, ob es im Alltag wirklich fehlt.
## M4 – Wochenplanung und Zeitblöcke
**Erledigt am 10.09.2026 mit Version 0.4.0.**
**Ziel:** Aufgaben per Ziehen und Ablegen in den Kalender schreiben — und
Verschiebungen von dort zurückholen.
- [ ] `Zeitblock` an genau einer Aufgabe und einem Kalenderkonto
- [ ] Wochenansicht mit `@dnd-kit/core`
- [ ] Schreiben in den Kalender, Rückweg beim Verschieben
- [ ] Schleifenschutz über `hash = sha256(startISO|endeISO)`
- [ ] Konflikte: der Kalender gewinnt (HTTP 412 auf If-Match)
- [ ] Belegung gegen die Tageskapazität des Bereichs
- [x] `Zeitblock` an genau einer Aufgabe und einem Kalenderkonto
- [x] Wochenansicht mit `@dnd-kit/core`
- [x] Schreiben in den Kalender, Rückweg beim Verschieben
- [x] Schleifenschutz über `hash = sha256(startISO|endeISO)`
- [x] Konflikte: der Kalender gewinnt (HTTP 412 auf If-Match)
- [x] Belegung gegen die Tageskapazität des Bereichs
**Bewusst offen gelassen:** Die Dauer lässt sich nicht am Balken ziehen, und
ein Zeitblock landet immer im ersten verbundenen Kalender. Beides steht unter
"Ideen für später" — erst zeigt der Alltag, ob es wirklich fehlt.
## M5 – Benachrichtigungen
+3 -2
View File
@@ -18,10 +18,12 @@ Anleitung für den Alltag mit dem Planer. Eine Seite je Thema.
Fortschritt
6. **[Kalenderkonten](kalenderkonten.md)** – Microsoft 365 und Google
verbinden, Belegung verstehen
7. **[Wochenplanung](woche.md)** – Aufgaben per Ziehen und Ablegen in den
Kalender legen
## Verwaltung
7. **[Protokoll](protokoll.md)** – nachsehen, wer wann was geändert hat
8. **[Protokoll](protokoll.md)** – nachsehen, wer wann was geändert hat
## Was noch kommt
@@ -29,7 +31,6 @@ Diese Seiten entstehen mit den nächsten Meilensteinen:
| Thema | Meilenstein |
| ------------------------------- | ----------- |
| Wochenplanung und Zeitblöcke | M4 |
| Benachrichtigungen | M5 |
| Integrationen | M6 |
| Schnellerfassung vom Handy, API | M7 |
+106
View File
@@ -0,0 +1,106 @@
# Wochenplanung und Zeitblöcke
Hier wird aus einer Aufgabe ein Termin. Du ziehst sie in ein Zeitfenster, und
der Planer legt sie als **Zeitblock** in deinen echten Kalender.
![Die Wochenansicht](../bilder/woche-hell-schreibtisch.png)
## Der Aufbau
| Wo | Was |
| --------------------- | ------------------------------------------------------------------------------------------------------------- |
| **Links** | Alles, was noch eingeplant werden kann — offen, geplant oder wartend. Ideen bleiben draußen |
| **Rechts** | Sieben Tage im Viertelstundenraster |
| **Über jeder Spalte** | Belegte Zeit gegen die Kapazität dieses Tages |
| **Graue Balken** | Termine aus deinem Kalender. Sie gehören dem Kalender, nicht dem Planer — hier lassen sie sich nicht anfassen |
| **Farbige Balken** | Deine Zeitblöcke, in der Farbe ihres Bereichs |
Das sichtbare Stundenfenster ergibt sich aus den Arbeitsfenstern **aller**
Bereiche, mit einer Stunde Luft nach oben und unten.
## Eine Aufgabe einplanen
Aufgabe links am Griff packen und in ein Zeitfenster ziehen. Fertig — der
Zeitblock steht sofort im Raster und wenige Augenblicke später in deinem
Kalender.
> **Die Dauer kommt aus dem geschätzten Aufwand.** Wer bei der Aufgabe
> „90 Minuten" einträgt, bekommt einen 90-Minuten-Block. Ohne Schätzung sind
> es 60 Minuten, die du danach anpassen kannst.
Eine eingeplante Aufgabe wechselt automatisch von _Offen_ auf _Geplant_.
## Verschieben
Am Griff im Block packen und woandershin ziehen. Der Planer zieht den
Kalendertermin nach.
## Ausplanen
Das Kreuz im Block. Der Termin verschwindet aus dem Kalender; die Aufgabe
bleibt und fällt zurück auf _Offen_, wenn nichts anderes mehr für sie geplant
ist.
## Wenn du im Kalender verschiebst
Das darfst du — und der Planer zieht nach. Beim nächsten Abgleich (spätestens
nach 15 Minuten) steht der Block dort, wo du ihn im Kalender hingelegt hast.
> **Der Kalender gewinnt.** Immer. Wer dort etwas verschiebt, meint es so;
> säße der Planer am längeren Hebel, würde er dir den Termin wieder wegziehen
> und du wüsstest nicht, warum.
>
> Löschst du den Termin im Kalender, verschwindet auch der Zeitblock. Die
> Aufgabe selbst bleibt.
### Warum sich das nicht aufschaukelt
Der Planer merkt sich zu jedem Zeitblock einen Fingerabdruck des Zeitraums,
den er zuletzt geschrieben hat. Findet er beim Abgleich genau diesen
Fingerabdruck wieder, war es seine eigene Änderung — er tut nichts. Weicht er
ab, hat jemand anderes verschoben.
Ohne diesen Kniff würden App und Kalender einander endlos hinterherschreiben.
## Das Warnzeichen im Block
Ein kleines Dreieck heißt: **noch nicht im Kalender**. Meist war der Anbieter
gerade nicht erreichbar.
Der Zeitblock bleibt trotzdem stehen — deine Planung soll nicht verlorengehen,
bloß weil ein Server hustet. Verschiebe den Block einmal, dann trägt der
Planer ihn nach.
## Die Zahl über der Spalte
`3 h / 6 h` heißt: sechs Stunden Kapazität an diesem Tag, drei davon schon
belegt. Gezählt werden Kalendertermine **und** deine Zeitblöcke.
Wird die Zahl rot, ist der Tag überbucht. Das ist keine Sperre — der Planer
lässt dich weiterplanen. Es ist ein Hinweis, dass der Tag nicht aufgeht.
Ganztägige Termine zählen nicht mit. Ein Urlaubstag im Kalender blockiert
sonst rechnerisch den ganzen Tag, obwohl er gar keine Arbeitszeit kostet.
## Blättern
**Zurück**, **Diese Woche** und **Weiter**. Die Woche steht in der Adresse —
eine bestimmte Woche lässt sich also als Lesezeichen ablegen.
## Wenn es klemmt
| Was du siehst | Was dahintersteckt |
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| „Ohne verbundenen Kalender lässt sich nichts einplanen." | Erst unter [Kalender](kalenderkonten.md) ein Konto verbinden |
| „Eingeplant, aber nicht in den Kalender geschrieben" | Der Anbieter war nicht erreichbar. Der Block steht, verschiebe ihn einmal zum Nachtragen |
| „Der Termin wurde inzwischen im Kalender geändert." | Jemand war schneller. Der Kalender gewinnt; der nächste Abgleich holt den dortigen Stand |
| „Zeitblöcke rasten in 15-Minuten-Schritten ein." | Das Raster ist Absicht. Genauer zu planen bringt nichts |
| Ein Block ist nicht zu sehen | Er liegt außerhalb des sichtbaren Stundenfensters. Das Fenster folgt den Arbeitsfenstern der Bereiche — siehe [Bereiche](bereiche.md) |
## Was noch nicht geht
- **Ein Zeitblock landet immer im ersten verbundenen Kalender.** Die Auswahl
je Bereich ist im Datenmodell vorbereitet, aber noch nicht in der
Oberfläche.
- **Die Dauer lässt sich nicht am Balken ziehen.** Wer eine andere Dauer will,
ändert den Aufwand der Aufgabe und plant neu ein.
+31
View File
@@ -373,6 +373,37 @@ async function main(): Promise<void> {
},
})
// --- Zeitblöcke: eingeplante Aufgaben im Kalender -------------------------
await db.zeitblock.deleteMany({})
const einzuplanen = await db.aufgabe.findMany({
where: {
titel: {
in: [
'Wochenbericht an die Geschäftsführung',
'Angebot Heizungstausch nachfassen',
],
},
},
select: { id: true, titel: true, aufwandMinuten: true },
})
let blockStunde = 15
for (const aufgabe of einzuplanen) {
const beginn = new Date(Date.UTC(2026, 2, 17, blockStunde - 1, 0))
await db.zeitblock.create({
data: {
aufgabeId: aufgabe.id,
kalenderkontoId: kalenderkonto.id,
beginn,
ende: new Date(beginn.getTime() + (aufgabe.aufwandMinuten ?? 60) * 60_000),
extId: `demo-block-${aufgabe.id}`,
hash: 'demo',
},
})
await db.aufgabe.update({ where: { id: aufgabe.id }, data: { status: 'GEPLANT' } })
blockStunde += 2
}
// Eine Aufgabe, die von außen kam – zeigt die Herkunft in der Liste.
await db.aufgabe.create({
data: {
+1
View File
@@ -31,6 +31,7 @@ const AUFNAHMEN: Aufnahme[] = [
{ name: 'start', pfad: '/' },
{ name: 'inbox', pfad: '/inbox' },
{ name: 'aufgaben', pfad: '/aufgaben' },
{ name: 'woche', pfad: '/woche' },
{ name: 'projekte', pfad: '/projekte' },
{ name: 'ideen', pfad: '/ideen' },
{ name: 'bereiche', pfad: '/bereiche' },