using System.Collections.ObjectModel; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using LehrerApp.Desktop.Services; namespace LehrerApp.Desktop.ViewModels.ClassTeacher; /// Eine Zeile der Klassenlehrer-Übersicht ("auf einen Blick", Nutzer-Feedback) - pro /// Schüler*in ein Ampel-Symbol für "heute laut WebUntis keine gemeldete Fehlzeit" und ein Badge, /// falls in den letzten Tagen ein Klassenbucheintrag einer anderen Lehrkraft angelegt wurde. /// Bewusst kein Bezug zu eigenem Unterricht/eigener Anwesenheitserfassung - genau der Fall, den es /// abzudecken gilt, ist der Tag ohne eigenen Unterricht mit der Klasse. public sealed record ClassTeacherRosterRow(string StudentName, int? ExternKey, bool HasAbsenceToday, string? AbsenceTooltip, bool HasRecentClassRegisterEntry) { public string StatusIcon => HasAbsenceToday ? "⚠" : "✓"; /// Führt die WebUntis-Schülerliste der Klasse mit den heutigen Fehlzeiten und den /// jüngsten Klassenbucheinträgen zusammen. Zuordnung primär über ExternKey (beide /// stammen aus WebUntis, gleiche Kennung), Fallback auf Namensgleichheit für den /// Klassenbuch-Bericht, der keinen ExternKey liefert (siehe UntisForeignClassRegisterEventDto). public static IReadOnlyList Build( IReadOnlyList students, IReadOnlyList todayAbsences, IReadOnlyList recentClassRegisterEntries) { var absenceByKey = todayAbsences .Where(a => a.ExternKey is not null) .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); var recentNames = recentClassRegisterEntries .Select(e => e.StudentName) .ToHashSet(StringComparer.OrdinalIgnoreCase); return students .Select(s => { var absence = (s.ExternKey is { } key ? absenceByKey.GetValueOrDefault(key) : null) ?? absenceByName.GetValueOrDefault(s.DisplayName); 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)); }) .OrderBy(r => r.StudentName) .ToList(); } } /// /// Top-Level-Bereich "Klassenlehrer" (eigener Sidebar-Eintrag, siehe MainWindowViewModel) - bewusst /// nicht an eine LearningGroup gehängt, da man Klassenlehrer für eine ganze Klasse ist, unabhängig /// vom eigenen Unterricht (Nutzer-Feedback zur ursprünglichen Umsetzung als Gruppen-Tab). Die /// Klasse selbst kommt aus den WebUntis-Einstellungen (). /// Zwei Ebenen: Übersicht (dieses ViewModel, kompaktes Roster) und Details /// (, die bisherigen Rohdaten-Listen) - ein Klick auf /// eine Roster-Zeile springt gefiltert in die Details. /// public partial class ClassTeacherOverviewViewModel : ObservableObject { private readonly WebUntisSettingsService _settings; private readonly WebUntisIntegrationService _untis; private readonly UntisReportCacheService _cache; public ClassTeacherDetailsViewModel DetailsTab { get; } public ObservableCollection Roster { get; } = []; [ObservableProperty] private string? _homeroomClassName; [ObservableProperty] private int _activeTabIndex; [ObservableProperty] private string _status = ""; [ObservableProperty] private bool _busy; public bool HomeroomClassConfigured => !string.IsNullOrWhiteSpace(HomeroomClassName); /// Vom Code-Behind/MainWindowViewModel gesetzt: springt in die WebUntis-Einstellungen, wenn noch /// keine Klasse gewählt ist. public Func? OnNavigateToSettings { get; set; } public ClassTeacherOverviewViewModel(WebUntisSettingsService settings, WebUntisIntegrationService untis, UntisReportCacheService cache, ClassTeacherDetailsViewModel detailsTab) { _settings = settings; _untis = untis; _cache = cache; DetailsTab = detailsTab; } partial void OnHomeroomClassNameChanged(string? value) => OnPropertyChanged(nameof(HomeroomClassConfigured)); [RelayCommand] private async Task Load() { HomeroomClassName = _settings.HomeroomClassName; ActiveTabIndex = 0; Roster.Clear(); if (!HomeroomClassConfigured) { Status = "Noch keine Klasse ausgewählt."; return; } var className = HomeroomClassName!; DetailsTab.Initialize(className); Busy = true; try { var today = DateOnly.FromDateTime(DateTime.Today); var studentsTask = _untis.GetStudentsAsync(className); var absencesTask = _cache.GetAbsencesAsync(className, today, today); var classRegisterTask = _cache.GetClassRegisterEventsAsync(className, today.AddDays(-7), today); await Task.WhenAll(studentsTask, absencesTask, classRegisterTask); var todayAbsences = ClassAbsenceDaySummaryRow.GroupByStudentAndDay(absencesTask.Result); foreach (var row in ClassTeacherRosterRow.Build(studentsTask.Result.Students, todayAbsences, classRegisterTask.Result)) Roster.Add(row); Status = $"{Roster.Count} Schüler*innen · Stand heute."; } catch (WebUntisIntegrationException ex) { Status = ex.Message; } finally { Busy = false; } } [RelayCommand] private void ShowDetailsForStudent(ClassTeacherRosterRow? row) { if (row is null) return; DetailsTab.StudentFilter = row.StudentName; ActiveTabIndex = 1; DetailsTab.LoadCommand.Execute(null); } [RelayCommand] private async Task GoToSettings() { if (OnNavigateToSettings is not null) await OnNavigateToSettings(); } }