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:
@@ -66,6 +66,7 @@ public class LiteDbContext : IDisposable
|
||||
public ILiteCollection<SchoolHoliday> SchoolHolidays => _db.GetCollection<SchoolHoliday>("school_holidays");
|
||||
public ILiteCollection<SupervisionDuty> SupervisionDuties => _db.GetCollection<SupervisionDuty>("supervision_duties");
|
||||
public ILiteCollection<SubstitutionEntry> SubstitutionEntries => _db.GetCollection<SubstitutionEntry>("substitution_entries");
|
||||
public ILiteCollection<TrashedItem> TrashedItems => _db.GetCollection<TrashedItem>("trash");
|
||||
|
||||
public void Checkpoint() => _db.Checkpoint();
|
||||
|
||||
@@ -182,6 +183,38 @@ public class LiteDbContext : IDisposable
|
||||
Documentation.Delete(id);
|
||||
}
|
||||
|
||||
// ── Papierkorb (14.3) ────────────────────────────────────────────────────
|
||||
//
|
||||
// Absichtlich hier statt in einer eigenen TrashRepository-Konstruktorabhängigkeit: jede
|
||||
// Fach-Repository hat db (LiteDbContext) bereits im Konstruktor, ein zusätzlicher Papierkorb-
|
||||
// Parameter wäre nur Boilerplate. Snapshot/Restore sind generisch über den Modelltyp, die
|
||||
// eigentliche Wiederherstellung ruft aber bewusst die jeweilige Repository.Save-Methode auf
|
||||
// (nicht direkt die LiteDB-Collection) — nur die kennt Validierung und OnChange-Ereignis für
|
||||
// ihr Modell.
|
||||
|
||||
/// <summary>Legt einen Papierkorb-Eintrag an, BEVOR die aufrufende Repository die Entität aus
|
||||
/// ihrer eigentlichen Collection löscht.</summary>
|
||||
internal void MoveToTrash<T>(string entityType, Guid entityId, T entity, string summary) =>
|
||||
TrashedItems.Insert(new TrashedItem
|
||||
{
|
||||
EntityType = entityType,
|
||||
EntityId = entityId,
|
||||
Snapshot = System.Text.Json.JsonSerializer.Serialize(entity),
|
||||
Summary = summary,
|
||||
});
|
||||
|
||||
/// <summary>Liefert die gelöschte Entität aus dem Papierkorb-Eintrag zurück und entfernt den
|
||||
/// Eintrag - unabhängig davon, ob die aufrufende Repository den Schnappschuss danach
|
||||
/// erfolgreich speichert (ein fehlgeschlagenes Restore soll nicht endlos wiederholbar sein,
|
||||
/// derselbe Datensatz landet notfalls einfach nicht wieder im Papierkorb).</summary>
|
||||
internal T? RestoreFromTrash<T>(Guid trashId)
|
||||
{
|
||||
var item = TrashedItems.FindById(trashId);
|
||||
if (item is null) return default;
|
||||
TrashedItems.Delete(trashId);
|
||||
return System.Text.Json.JsonSerializer.Deserialize<T>(item.Snapshot);
|
||||
}
|
||||
|
||||
internal void ExecuteInTransaction(Action action)
|
||||
{
|
||||
_db.BeginTrans();
|
||||
|
||||
@@ -161,11 +161,18 @@ public class SeatingPlanRepository(LiteDbContext db) : ISeatingPlanRepository
|
||||
|
||||
public void Delete(Guid id)
|
||||
{
|
||||
if (db.SeatingPlans.FindById(id) is { } plan)
|
||||
ArchivedGroupWriteGuard.EnsureActive(db, plan.GroupId);
|
||||
if (db.SeatingPlans.FindById(id) is not { } plan) return;
|
||||
ArchivedGroupWriteGuard.EnsureActive(db, plan.GroupId);
|
||||
db.MoveToTrash(nameof(SeatingPlan), id, plan,
|
||||
string.IsNullOrWhiteSpace(plan.Room) ? plan.Name : $"{plan.Name} (Raum {plan.Room})");
|
||||
db.SeatingPlans.Delete(id);
|
||||
db.OnChange?.Invoke(nameof(SeatingPlan), id.ToString(), "Delete", null);
|
||||
}
|
||||
|
||||
public void Restore(Guid trashId)
|
||||
{
|
||||
if (db.RestoreFromTrash<SeatingPlan>(trashId) is { } plan) Save(plan);
|
||||
}
|
||||
}
|
||||
|
||||
public class GroupMembershipRepository(LiteDbContext db) : IGroupMembershipRepository
|
||||
@@ -260,9 +267,16 @@ public class GradingKeyTemplateRepository(LiteDbContext db) : IGradingKeyTemplat
|
||||
}
|
||||
public void Delete(Guid id)
|
||||
{
|
||||
if (db.GradingKeyTemplates.FindById(id) is { } template)
|
||||
db.MoveToTrash(nameof(GradingKeyTemplate), id, template, template.Name);
|
||||
db.GradingKeyTemplates.Delete(id);
|
||||
db.OnChange?.Invoke(nameof(GradingKeyTemplate), id.ToString(), "Delete", null);
|
||||
}
|
||||
|
||||
public void Restore(Guid trashId)
|
||||
{
|
||||
if (db.RestoreFromTrash<GradingKeyTemplate>(trashId) is { } template) Save(template);
|
||||
}
|
||||
}
|
||||
|
||||
public class GradeRepository(LiteDbContext db) : IGradeRepository
|
||||
@@ -279,11 +293,18 @@ public class GradeRepository(LiteDbContext db) : IGradeRepository
|
||||
}
|
||||
public void Delete(Guid id)
|
||||
{
|
||||
if (db.Grades.FindById(id) is { } grade)
|
||||
ArchivedGroupWriteGuard.EnsureActive(db, grade.GroupId);
|
||||
if (db.Grades.FindById(id) is not { } grade) return;
|
||||
ArchivedGroupWriteGuard.EnsureActive(db, grade.GroupId);
|
||||
db.MoveToTrash(nameof(Grade), id, grade,
|
||||
$"Note {grade.Value} ({grade.Date:dd.MM.yyyy})");
|
||||
db.Grades.Delete(id);
|
||||
db.OnChange?.Invoke(nameof(Grade), id.ToString(), "Delete", null);
|
||||
}
|
||||
|
||||
public void Restore(Guid trashId)
|
||||
{
|
||||
if (db.RestoreFromTrash<Grade>(trashId) is { } grade) Save(grade);
|
||||
}
|
||||
}
|
||||
|
||||
public class GradingSchemeRepository(LiteDbContext db) : IGradingSchemeRepository
|
||||
@@ -421,9 +442,16 @@ public class WorkTaskRepository(LiteDbContext db) : IWorkTaskRepository
|
||||
}
|
||||
public void Delete(Guid id)
|
||||
{
|
||||
if (db.Tasks.FindById(id) is { } task)
|
||||
db.MoveToTrash(nameof(WorkTask), id, task, task.Title);
|
||||
db.Tasks.Delete(id);
|
||||
db.OnChange?.Invoke(nameof(WorkTask), id.ToString(), "Delete", null);
|
||||
}
|
||||
|
||||
public void Restore(Guid trashId)
|
||||
{
|
||||
if (db.RestoreFromTrash<WorkTask>(trashId) is { } task) Save(task);
|
||||
}
|
||||
}
|
||||
|
||||
public class TimeEntryRepository(LiteDbContext db) : ITimeEntryRepository
|
||||
@@ -441,9 +469,17 @@ public class TimeEntryRepository(LiteDbContext db) : ITimeEntryRepository
|
||||
}
|
||||
public void Delete(Guid id)
|
||||
{
|
||||
if (db.TimeEntries.FindById(id) is { } entry)
|
||||
db.MoveToTrash(nameof(TimeEntry), id, entry,
|
||||
$"{entry.DurationMinutes} Min. {entry.Category} ({entry.Date:dd.MM.yyyy})");
|
||||
db.TimeEntries.Delete(id);
|
||||
db.OnChange?.Invoke(nameof(TimeEntry), id.ToString(), "Delete", null);
|
||||
}
|
||||
|
||||
public void Restore(Guid trashId)
|
||||
{
|
||||
if (db.RestoreFromTrash<TimeEntry>(trashId) is { } entry) Save(entry);
|
||||
}
|
||||
}
|
||||
|
||||
public class ParticipationSessionRepository(LiteDbContext db) : IParticipationSessionRepository
|
||||
@@ -764,3 +800,18 @@ public class CompetencyDomainRepository(LiteDbContext db) : ICompetencyDomainRep
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Papierkorb (14.3) — bewusst nicht sync-fähig: TrashedItem wird nie über
|
||||
/// db.OnChange gemeldet, bleibt also rein lokal auf dem Gerät, auf dem gelöscht wurde. Ein
|
||||
/// "Fehlklick sofort rückgängig machen"-Werkzeug, kein geräteübergreifendes Archiv.</summary>
|
||||
public class TrashRepository(LiteDbContext db) : ITrashRepository
|
||||
{
|
||||
public List<TrashedItem> GetAll() =>
|
||||
db.TrashedItems.FindAll().OrderByDescending(t => t.DeletedAt).ToList();
|
||||
|
||||
public void PurgeOlderThan(DateTime cutoffUtc)
|
||||
{
|
||||
foreach (var item in db.TrashedItems.Find(t => t.DeletedAt < cutoffUtc).ToList())
|
||||
db.TrashedItems.Delete(item.Id);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user