feat: Dunkelmodus, Fenstergröße merken, Papierkorb für Löschvorgänge (12.4, 14.3, 14.6)

Dunkelmodus über neuen Einstellungen-Tab "Darstellung" (Systemvorgabe/Hell/Dunkel),
Fenstergröße/Maximiert-Status wird über Sitzungen hinweg gemerkt (bewusst ohne
Fensterposition), und ein generischer Snapshot-basierter Papierkorb (30 Tage) für
Sitzpläne, Noten, Notenschlüssel-Vorlagen, Aufgaben und Zeiteinträge. Details und
bewusste Scope-Entscheidungen (Spaltenbreiten zurückgestellt, Farb-Audit für
Dunkelmodus offen, welche Entitäten der Papierkorb abdeckt) in TODO.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 16:26:10 +02:00
co-authored by Claude Sonnet 5
parent d9d007cd4b
commit 331033db5c
20 changed files with 1023 additions and 12 deletions
+76 -3
View File
@@ -2217,7 +2217,20 @@ von 4.2.2) der Tab "Kürzel-Katalog" für die Von/Nach-Kürzel des Stundenverlau
- [ ] **12.2** Schuljahr-Einstellungen: Beginn/Ende, Halbjahresgrenze, Ferien
(`SchoolYearService` erweitern).
- [ ] **12.3** Standard-Notenschlüssel und Standard-Gewichtungsschema hinterlegen (2.3.3, 1.3.2).
- [ ] **12.4** Darstellung: Hell/Dunkel-Modus, Schriftgröße.
- [~] **12.4** Darstellung: Hell/Dunkel-Modus, Schriftgröße.
**Umsetzung (Hell/Dunkel):** neuer Tab "Darstellung" in
[SettingsView.axaml](LehrerApp.Desktop/Views/Settings/SettingsView.axaml) mit Auswahl
Systemvorgabe/Hell/Dunkel, gespeichert über
[AppearanceSettingsService.cs](LehrerApp.Desktop/Services/AppearanceSettingsService.cs)
(gleiches Muster wie `DashboardSettingsService`). `App.axaml` folgte über
`RequestedThemeVariant="Default"` ohnehin schon der Systemvorgabe; neu ist die manuelle
Umschaltung zur Laufzeit über `App.ApplyTheme(...)` (setzt `Application.Current!.
RequestedThemeVariant`), ohne Neustart. Der gespeicherte Stand wird beim App-Start noch
vor dem ersten Fenster angewendet. **Bewusst nicht Teil dieser Umsetzung:** ein Audit der
ca. 23 XAML-Dateien mit fest codierten Hex-Farben statt Theme-Ressourcen — die
Umschaltung selbst funktioniert, aber einzelne Stellen dürften im Dunkelmodus optisch
nicht passen. Eigene, größere Aufgabe. Schriftgröße nicht umgesetzt.
- [ ] **12.5** Speicherort der Datenbank anzeigen und ändern.
- [ ] **12.6** Backup-Verwaltung (siehe 13.3) in den Einstellungen zugänglich machen.
@@ -2397,10 +2410,70 @@ von 4.2.2) der Tab "Kürzel-Katalog" für die Von/Nach-Kürzel des Stundenverlau
- [ ] **14.1** Tastaturbedienung durchgängig: alle Hauptfunktionen ohne Maus erreichbar
(Vorbild: Mitarbeit-Schnelleingabe).
- [ ] **14.2** Globale Suche (Schüler, Gruppe, Klausur) über Tastenkürzel.
- [ ] **14.3** Rückgängig-Funktion für Löschvorgänge (mindestens Bestätigungsdialog überall).
- [~] **14.3** Rückgängig-Funktion für Löschvorgänge (mindestens Bestätigungsdialog überall).
**Umsetzung:** generischer, nicht-invasiver "Papierkorb light" statt einer
`IsDeleted`-Markierung pro Modell (Letzteres hätte jede Lese-Abfrage der betroffenen
Collections ändern müssen — bereits bei `Documentation.IsDeleted` sichtbar, das genau
dieses Muster nutzt und **keine** Wiederherstellungs-Oberfläche hat). Stattdessen:
[Trash.cs](LehrerApp.Core/Models/Trash.cs) definiert `TrashedItem` (Id, EntityType,
EntityId, JSON-Snapshot, Anzeigetext, Löschzeitpunkt) als eigene LiteDB-Collection.
`LiteDbContext.MoveToTrash<T>()`/`RestoreFromTrash<T>()` sind neue interne Hilfsmethoden,
die von den bestehenden `Delete(id)`-Methoden einzelner Repositories vor dem eigentlichen
Löschen aufgerufen werden — **keine neue Konstruktor-Abhängigkeit** für diese
Repositories. `Restore(Guid trashId)` (neu auf den betroffenen Repository-Interfaces)
dedserialisiert den Snapshot und ruft die **eigene** `Save()`-Methode auf, damit
Validierung und `OnChange`-Sync-Ereignis wie bei jedem normalen Speichern greifen. Neuer
Tab "Papierkorb" in
[SettingsView.axaml](LehrerApp.Desktop/Views/Settings/SettingsView.axaml)
([TrashViewModel.cs](LehrerApp.Desktop/ViewModels/Settings/TrashViewModel.cs)) listet alle
Einträge mit Typ, Löschzeitpunkt und Restlaufzeit; "Wiederherstellen" dispatcht anhand des
`EntityType` an das passende Repository. Aufbewahrung 30 Tage, Bereinigung
(`ITrashRepository.PurgeOlderThan`) läuft beim App-Start.
**Bewusst nur für 5 Entitäten ohne Kaskaden umgesetzt:** `SeatingPlan`, `Grade`,
`GradingKeyTemplate`, `WorkTask`, `TimeEntry` — jeweils hoher Alltagswert (Fehlklick beim
Löschen eines Sitzplans/einer Note ist besonders ärgerlich) bei überschaubarem Risiko
(keine abhängigen Collections). **Bewusst ausgeschlossen:** `LearningGroup` (14
kaskadierende Collections beim Löschen — deutlich riskanter, eigener Umbau nötig),
`Exam` (kaskadiert `ExamResult`), `ParticipationSession` (kaskadiert
`ParticipationEntry`), sowie alle übrigen, einfacheren Entitäten (u. a. `ShorthandCode`,
`SubstitutionEntry`, `SupervisionDuty`, `TimetableSlot`, `SchoolHoliday`,
`CompetencyDomain`, `Subject`, `Student`, `Unit`, `GroupMembership`,
`ParticipationAspect`, `ParticipationSection`, `AlternativeLessonPath`, `GradingScheme`,
`ReportGrade`) — der Mechanismus ist bewusst so gebaut, dass sich weitere Entitäten später
mit demselben Muster (drei Zeilen in `Delete()`, eine `Restore()`-Methode) ergänzen lassen.
Der Papierkorb ist **rein lokal, nicht Teil des Geräte-Sync** — `TrashedItems` löst nie
`db.OnChange` aus und landet damit nie im Sync-Ereignisstrom; nur das eigentliche
Save-/Delete-Ereignis der betroffenen Entität synchronisiert wie bisher. Bewusste
Vereinfachung, um keine neue Sync-Infrastruktur (Ereignistyp, Konfliktbehandlung für
Papierkorb-Einträge) einzuführen. Tests: 10 in
[TrashTests.cs](LehrerApp.Data.Tests/TrashTests.cs) (Löschen/Wiederherstellen je
Repository, Sortierung, Bereinigung), 6 in
[TrashViewModelTests.cs](LehrerApp.Desktop.Tests/TrashViewModelTests.cs).
- [ ] **14.4** Ladeanzeigen bei längeren Operationen (Import, Sync, Export).
- [ ] **14.5** Leere Zustände mit Handlungsaufforderung statt leerer Tabellen.
- [ ] **14.6** Fenstergröße und Spaltenbreiten über Sitzungen hinweg merken.
- [~] **14.6** Fenstergröße und Spaltenbreiten über Sitzungen hinweg merken.
**Umsetzung (Fenstergröße):**
[WindowSettingsService.cs](LehrerApp.Desktop/Services/WindowSettingsService.cs)
(gleiches Muster wie `DashboardSettingsService`) speichert Breite/Höhe/Maximiert-Status
beim Schließen und stellt sie beim Start wieder her
(`MainWindow.EnableWindowSizePersistence`). **Bewusst nicht gespeichert: die
Fensterposition** — bei wechselnder Monitor-Konfiguration (Laptop im Unterricht, externer
Monitor zuhause) könnte das Fenster sonst außerhalb des sichtbaren Bereichs landen. War die
App beim Schließen maximiert, bleibt die zuletzt bekannte "normale" Größe erhalten statt
der (dann bedeutungslosen) Bildschirmgröße. Tests in
[WindowSettingsServiceTests.cs](LehrerApp.Desktop.Tests/WindowSettingsServiceTests.cs).
**Spaltenbreiten: zurückgestellt.** Bestandsaufnahme aller 9 `DataGrid`s ergab: 5 haben
eine feste, in XAML deklarierte Spaltenliste (dort wäre Persistierung tatsächlich billig),
aber 4 bauen ihre Spalten im Code-behind dynamisch aus den Daten neu auf — je eine Spalte
pro Klausur bzw. Mitarbeits-Aspekt (`GradeOverviewTabView`, `ParticipationTabView`,
`ExamGradingDialog`, `AttendanceHomeworkQuickInputDialog`). Für diese vier gibt es keine
stabile Spalten-Identität, an der eine gespeicherte Breite verlässlich hängen könnte —
eine generische Lösung ist damit kein "billiger" Zusatz, sondern ein eigener, größerer
Umbau. Nicht in diesem Durchgang umgesetzt.
- [ ] **14.7** Bedienung auf Touch-Geräten prüfen (Tablet im Unterricht).
- [ ] **14.8** Responsive Layout und Windows-DPI prüfen (kleine Notebook-Auflösungen sowie
125/150/200 % Skalierung; starre Master-Detail-Spalten bei Bedarf stapeln). Der kompakte