Files
LehrerApp/LehrerApp.Desktop/ViewModels/AttentionItem.cs
T
adminandClaude Opus 5.5 770a1833c8
CI / build-and-test (push) Canceled after 0s
Zurückgestellte Dashboard-Warnungen in eigener Gruppe sammeln
Entschärfte Warnungen (zurückgestellt oder Vorgang in Bearbeitung) liefen
bisher grau am Ende ihrer Art-Gruppe mit und zählten weiter zu den offenen
Punkten. Sie stehen jetzt in einer eingeklappten "Zurückgestellt"-Gruppe
ganz unten, mit dem Grund als Untertitel, und zählen nicht mehr mit. Nach
Fristablauf kehren sie automatisch in ihre Gruppe und die Zählung zurück.

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

161 lines
9.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Windows.Input;
namespace LehrerApp.Desktop.ViewModels;
/// <summary>Die neun Dashboard-Kacheln, die tatsächlich "wo muss ich reagieren" beantworten
/// (siehe <see cref="DashboardViewModel.AttentionCount"/>), zusammengefasst zu einer Karte mit
/// Filter-Chips statt eigenen Sichtbarkeits-Schaltern je Art. Reihenfolge hier ist die Reihenfolge
/// der Gruppen in der zusammengefassten Ansicht (siehe DashboardViewModel.RebuildAttention) — sie
/// übernimmt bewusst die frühere Sortierung aus DashboardSettingsService.DefaultCardOrder, damit
/// sich nichts, was Nutzer bereits kennen, unvorhersehbar umsortiert.
/// "Klausurwochen" (examload) bleibt bewusst außen vor: die Einträge dort sind Wochen-Aggregate,
/// keine Einzelvorgänge, die sich abhaken oder anklicken lassen — sie passen nicht ins Item-Modell.
/// MissingHomework/Lateness (Hausaufgaben-/Verspätungs-Warnung) ergänzen Attendance um dieselbe
/// klassenübergreifende Logik — alle drei navigieren über OnNavigateToStudentCase direkt auf den
/// "Vorgänge"-Tab des Schülerdetails statt auf dessen Standard-Tab (siehe DashboardViewModel).</summary>
public enum AttentionKind
{
MissingTeachingTime, Excuse, Correction, Unplanned, Alert, Attendance, MissingHomework, Lateness, SupportPlan,
}
/// <summary>Eine Inline-Aktion auf einem Handlungsbedarf-Eintrag (z.B. "Entschuldigt"/"Unentschuldigt",
/// "Erfassen"). Verwendet ICommand statt Action, damit die bereits vorhandenen generierten
/// RelayCommands (OpenExcuseItem.MarkExcusedCommand, DashboardViewModel.AddMissingTeachingTimeCommand)
/// direkt weiterverwendet werden können, statt sie in einen zweiten Delegate-Typ zu verpacken.</summary>
public sealed record AttentionAction(string Label, ICommand Command, object? Parameter = null);
/// <summary>Gemeinsame Projektion für die sieben Handlungsbedarf-Arten. Trägt nur Anzeigedaten —
/// die eigentliche Beschaffung bleibt unverändert in den jeweiligen DashboardViewModel.LoadXxx-
/// Methoden; sie erzeugen am Ende ein AttentionItem statt (wie vorher) ihre eigene Item-Klasse in
/// ihre eigene ObservableCollection einzutragen.</summary>
public sealed class AttentionItem
{
public AttentionKind Kind { get; }
public string Title { get; }
public string Subtitle { get; }
/// <summary>Kurztext rechts neben dem Titel — Fehlzeitenquote, Alert-Kind-Label. Meist leer.</summary>
public string TrailingText { get; }
public string DateDisplay { get; }
public bool IsOverdue { get; }
public AlertSeverity? Severity { get; }
/// <summary>Nur bei Korrekturen gesetzt (0–100); steuert die ProgressBar. Eigenes HasProgress statt
/// Nullability, damit die ProgressBar-Bindung ein einfaches int (kompatibel mit Value:double) bleibt.</summary>
public int ProgressPercent { get; }
public bool HasProgress { get; }
/// <summary>Text unter der ProgressBar, z.B. "3 von 5 Arbeiten bewertet". Nur bei Korrekturen gesetzt.</summary>
public string ProgressLabel { get; }
/// <summary>Färbt TrailingText wie die frühere Fehlzeiten-Warnung (fest "Foreground=Red") ein —
/// bei Attendance/MissingHomework/Lateness gesetzt, weil ein AttentionItem dieser Arten per
/// Konstruktion nur entsteht, wenn der jeweilige Schwellenwert bereits überschritten ist.</summary>
public bool IsWarningTrailing { get; }
/// <summary>Nur bei Attendance/MissingHomework/Lateness gesetzt: ein offener Vorgang mit
/// passendem Tag wurde kürzlich bearbeitet, oder die Warnung wurde manuell zurückgestellt (siehe
/// DashboardViewModel.SnoozeWarningAsync) — beides zeitlich befristet, damit eine ausbleibende
/// Besserung nach Ablauf der Frist wieder auffällt statt dauerhaft entschärft zu bleiben. Steuert
/// die Optik (grau statt rot), die Einordnung in die eingeklappte "Zurückgestellt"-Gruppe und
/// das Herausnehmen aus DashboardViewModel.AttentionCount; der Schwellenwert-
/// Vergleich selbst bleibt unverändert, die Warnung verschwindet also ganz, sobald sich die
/// zugrunde liegende Zahl tatsächlich bessert.</summary>
public bool IsDefused { get; }
public IReadOnlyList<AttentionAction> Actions { get; }
public Action? Navigate { get; }
/// <summary>Nur bei Attendance/MissingHomework/Lateness gesetzt: Inhalt des "Details"-Popups
/// (Nutzer-Feedback: im Dashboard-Eintrag mehr erfahren). Erst beim ersten Zugriff erzeugt — die
/// Flyout-Bindung greift darauf erst beim Öffnen zu, damit das Dashboard nicht für jede Warnzeile
/// vorab alle Belege sammelt.</summary>
public StudentWarningDetailViewModel? Detail => _detail ??= _detailFactory?.Invoke();
private readonly Func<StudentWarningDetailViewModel>? _detailFactory;
private StudentWarningDetailViewModel? _detail;
public AttentionItem(AttentionKind kind, string title, string subtitle = "", string trailingText = "",
string dateDisplay = "", bool isOverdue = false, AlertSeverity? severity = null,
int progressPercent = 0, string progressLabel = "", bool hasProgress = false,
bool isWarningTrailing = false, bool isDefused = false,
IReadOnlyList<AttentionAction>? actions = null, Action? navigate = null,
Func<StudentWarningDetailViewModel>? detailFactory = null)
{
Kind = kind;
Title = title;
Subtitle = subtitle;
TrailingText = trailingText;
DateDisplay = dateDisplay;
IsOverdue = isOverdue;
Severity = severity;
ProgressPercent = progressPercent;
ProgressLabel = progressLabel;
HasProgress = hasProgress;
IsWarningTrailing = isWarningTrailing;
IsDefused = isDefused;
Actions = actions ?? [];
Navigate = navigate;
_detailFactory = detailFactory;
}
public bool HasSubtitle => !string.IsNullOrWhiteSpace(Subtitle);
public bool HasTrailingText => !string.IsNullOrWhiteSpace(TrailingText);
public bool HasDate => !string.IsNullOrWhiteSpace(DateDisplay);
public bool HasActions => Actions.Count > 0;
public bool HasDetail => _detailFactory is not null;
public bool HasFooter => HasActions || HasDetail;
public bool HasSeverity => Severity is not null;
public bool IsHighSeverity => Severity == AlertSeverity.High;
public bool IsMediumSeverity => Severity == AlertSeverity.Medium;
/// Färbt den linken Severity-Balken wie die frühere Fehlzeiten-Warnung ein — für alle drei
/// klassenübergreifenden Warnarten (Fehlzeiten/Hausaufgaben/Verspätungen), die per Konstruktion
/// nur entstehen, wenn ihr jeweiliger Schwellenwert bereits überschritten ist.
public bool IsCrossClassWarning => Kind is AttentionKind.Attendance or AttentionKind.MissingHomework or AttentionKind.Lateness;
}
/// <summary>Eine Kind-Gruppe innerhalb der zusammengefassten Handlungsbedarf-Liste. Items behalten
/// die Sortierung, die ihre jeweilige LoadXxx-Methode schon immer produziert hat (z.B. Fehlzeiten
/// nach Quote absteigend, Korrekturen nach Datum) — ein zweites, generisches "Importance"-Sortieren
/// quer über alle Arten würde diese bewusst gewählten Reihenfolgen wieder zerstören.
/// Sonderfall IsDeferred: die "Zurückgestellt"-Gruppe am Ende sammelt entschärfte Warnungen aller
/// drei klassenübergreifenden Arten (Kind deshalb null) und ist ein-/ausklappbar.</summary>
public sealed partial class AttentionGroup : ObservableObject
{
public AttentionKind? Kind { get; }
public string Header { get; }
public IReadOnlyList<AttentionItem> Items { get; }
public int Count => Items.Count;
public bool IsDeferred { get; }
[ObservableProperty] [NotifyPropertyChangedFor(nameof(ExpandGlyph))] private bool _isExpanded;
public string ExpandGlyph => IsExpanded ? "▾" : "▸";
private readonly Action<bool>? _onExpandedChanged;
public AttentionGroup(AttentionKind? kind, string header, IReadOnlyList<AttentionItem> items,
bool isDeferred = false, bool isExpanded = true, Action<bool>? onExpandedChanged = null)
{
Kind = kind;
Header = header;
Items = items;
IsDeferred = isDeferred;
_isExpanded = isExpanded;
_onExpandedChanged = onExpandedChanged;
}
partial void OnIsExpandedChanged(bool value) => _onExpandedChanged?.Invoke(value);
[RelayCommand] private void ToggleExpanded() => IsExpanded = !IsExpanded;
}
/// <summary>Ein Filter-Chip für eine Handlungsbedarf-Art in der zusammengefassten Karte — ersetzt
/// die frühere Sichtbarkeit je Einzelkachel (siehe DashboardViewModel.LoadAttentionFilters).</summary>
public partial class AttentionFilterOption : ObservableObject
{
[ObservableProperty] private bool _isActive = true;
public AttentionKind Kind { get; }
public string Label { get; }
public Action? OnChanged { get; set; }
public AttentionFilterOption(AttentionKind kind, string label)
{
Kind = kind;
Label = label;
}
partial void OnIsActiveChanged(bool value) => OnChanged?.Invoke();
}