CI / build-and-test (push) Canceled after 0s
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>
161 lines
9.0 KiB
C#
161 lines
9.0 KiB
C#
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();
|
||
}
|