fix: Namensabgleich und Tab-Wechsel im Klassenlehrer-Bereich, Fehltag-Hervorhebung

Zwei Bugs behoben:
- Gefilterte Listen blieben nach Klick auf eine Roster-Zeile leer: WebUntis
  liefert Schülernamen je nach Bericht in unterschiedlicher Reihenfolge
  (Schülerreport "Vorname Nachname", Fehlzeiten-/Klassenbuchberichte
  "Nachname Vorname"). Neuer reihenfolge-unabhängiger Namensvergleich
  (UntisNameMatching), betraf auch den Roster-Namensabgleich der Übersicht
  selbst (Ampel-Symbole/Badges waren ohne ExternKey vermutlich fast immer
  falsch).
- Nach dem ersten Tab-Wechsel (Klick auf Roster-Zeile) blieb ein zweiter
  Klick wirkungslos, sobald man zwischendurch manuell zur Übersicht
  zurückgeklickt hatte: TabbedPage.SelectedIndex war ohne Mode=TwoWay
  gebunden. Explizit ergänzt.

Zusätzlich: Fehlzeiten-Zeilen für einen kompletten Fehltag werden jetzt
dezent hervorgehoben (Nutzer-Feedback, bisher nicht erkennbar).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 23:27:14 +02:00
co-authored by Claude Sonnet 5
parent 2b29dea824
commit 69cc16bbd2
8 changed files with 131 additions and 29 deletions
@@ -17,8 +17,10 @@ public sealed record ClassTeacherRosterRow(string StudentName, int? ExternKey, b
/// <summary>Führt die WebUntis-Schülerliste der Klasse mit den heutigen Fehlzeiten und den
/// jüngsten Klassenbucheinträgen zusammen. Zuordnung primär über <c>ExternKey</c> (beide
/// stammen aus WebUntis, gleiche Kennung), Fallback auf Namensgleichheit für den
/// Klassenbuch-Bericht, der keinen ExternKey liefert (siehe UntisForeignClassRegisterEventDto).</summary>
/// stammen aus WebUntis, gleiche Kennung), Fallback auf reihenfolge-unabhängigen Namensabgleich
/// (<see cref="UntisNameMatching"/>) für den Klassenbuch-Bericht, der keinen ExternKey liefert
/// (siehe UntisForeignClassRegisterEventDto) — WebUntis liefert Namen dort in anderer
/// Reihenfolge als im Schülerreport.</summary>
public static IReadOnlyList<ClassTeacherRosterRow> Build(
IReadOnlyList<UntisStudentDto> students,
IReadOnlyList<ClassAbsenceDaySummaryRow> todayAbsences,
@@ -29,20 +31,21 @@ public sealed record ClassTeacherRosterRow(string StudentName, int? ExternKey, b
.GroupBy(a => a.ExternKey!.Value)
.ToDictionary(g => g.Key, g => g.First());
var absenceByName = todayAbsences
.GroupBy(a => a.StudentName, StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);
.GroupBy(a => UntisNameMatching.NameKey(a.StudentName))
.ToDictionary(g => g.Key, g => g.First());
var recentNames = recentClassRegisterEntries
.Select(e => e.StudentName)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
.Select(e => UntisNameMatching.NameKey(e.StudentName))
.ToHashSet();
return students
.Select(s =>
{
var nameKey = UntisNameMatching.NameKey(s.DisplayName);
var absence = (s.ExternKey is { } key ? absenceByKey.GetValueOrDefault(key) : null)
?? absenceByName.GetValueOrDefault(s.DisplayName);
?? absenceByName.GetValueOrDefault(nameKey);
return new ClassTeacherRosterRow(s.DisplayName, s.ExternKey, absence is not null,
absence is null ? null : $"{absence.TotalAbsentPeriods} Stunde(n) — {absence.StatusLabel}",
recentNames.Contains(s.DisplayName));
recentNames.Contains(nameKey));
})
.OrderBy(r => r.StudentName)
.ToList();