From cdbed69d8bfaf7bf3741df40c59a82f7f19c3477 Mon Sep 17 00:00:00 2001 From: Sebastian Hedtrich Date: Mon, 31 Aug 2026 11:56:35 +0200 Subject: [PATCH] =?UTF-8?q?Klassenlehrer:=20Versp=C3=A4tungen=20nicht=20me?= =?UTF-8?q?hr=20als=20fehlende=20Entschuldigung,=20echter=20Ferienkalender?= =?UTF-8?q?=20f=C3=BCr=20Fehlquote?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verspätungen sind i.d.R. nicht entschuldigungsfähig - "Offene Entschuldigungen" erinnert dafür nicht mehr, stattdessen eskaliert die Mustererkennung ab 5 Verspätungen im Schuljahr zu einem Elterngespräch/Brief-Hinweis mit Wiedervorlage-Option. Die Jahresfehlquote nutzte bislang den 1. August als fest verdrahteten Schuljahresbeginn und zählte damit noch laufende Sommerferien als Schultage mit, was die Quote kurz nach Schuljahresbeginn stark verfälschte (~57% statt ~100% bei durchgehend fehlenden Schülern). Nutzt jetzt den echten WebUntis-Ferienkalender (GetHolidaysAsync, gecacht in WebUntisSettingsService). Co-Authored-By: Claude Sonnet 5 --- .../ClassTeacherViewModelsTests.cs | 107 ++++++++++++++ .../Services/WebUntisIntegrationService.cs | 5 + .../Services/WebUntisSettingsService.cs | 17 +++ .../ClassTeacherOverviewViewModel.cs | 135 +++++++++++++++--- .../ClassTeacherOverviewView.axaml | 7 +- TODO.md | 54 +++++++ 6 files changed, 304 insertions(+), 21 deletions(-) diff --git a/LehrerApp.Desktop.Tests/ClassTeacherViewModelsTests.cs b/LehrerApp.Desktop.Tests/ClassTeacherViewModelsTests.cs index 2a68443..8fd3104 100644 --- a/LehrerApp.Desktop.Tests/ClassTeacherViewModelsTests.cs +++ b/LehrerApp.Desktop.Tests/ClassTeacherViewModelsTests.cs @@ -264,6 +264,32 @@ public sealed class ClassTeacherViewModelsTests Assert.Equal(row.StatusText, row.StatusTextWithGlyph); } + // ── Nenner der Jahresfehlquote: echter WebUntis-Ferienkalender statt grober Werktagszählung ── + + [Fact] + public void CountSchoolWeekdays_SchliesstFerientageAus() + { + // 1.-31.08.2026 hat 21 Werktage; die (fiktiven) Sommerferien bis 11.08. nehmen davon 7 + // Werktage weg (Mo 03. - Fr 07., Mo 10. - Di 11.) - Nutzer-Feedback: Schüler, die seit + // Unterrichtsbeginn jeden Tag fehlten, kamen wegen dieser mitgezählten Ferienzeit nur auf + // ~57 % statt ~100 % Fehlquote. + var holidays = new[] { new CachedUntisHoliday("Sommerferien", new DateOnly(2026, 7, 1), new DateOnly(2026, 8, 11)) }; + + var schoolDays = ClassTeacherOverviewViewModel.CountSchoolWeekdays( + new DateOnly(2026, 8, 1), new DateOnly(2026, 8, 31), holidays); + + Assert.Equal(14, schoolDays); + } + + [Fact] + public void CountSchoolWeekdays_OhneFerienVerhaeltSichWieReineWerktagszaehlung() + { + var schoolDays = ClassTeacherOverviewViewModel.CountSchoolWeekdays( + new DateOnly(2026, 8, 1), new DateOnly(2026, 8, 31), []); + + Assert.Equal(21, schoolDays); + } + // ── Offene Entschuldigungen (Feature-Idee 2) ───────────────────────────── [Fact] @@ -290,6 +316,87 @@ public sealed class ClassTeacherViewModelsTests Assert.False(rows[1].IsOverdue); } + [Fact] + public void OpenExcuseBuild_IgnoriertUnentschuldigteVerspaetungen() + { + // Verspätungen sind i.d.R. nicht entschuldigungsfähig - "unentschuldigt" darf hier keine + // Erinnerung an eine fehlende Entschuldigung auslösen (Nutzer-Feedback). + var absences = new[] + { + new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 25), "Neu Ben", 1002, 0, 15, + ["Deu"], [1], ["nicht entsch."], ["Verspätung"], null, null, false), + new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 24), "Fehlt Cem", 1003, 1, 45, + ["Deu"], [1], ["nicht entsch."], ["Absent"], null, null, false), + }; + + var rows = ClassTeacherOpenExcuseRow.Build(absences, new DateOnly(2026, 8, 26)); + + Assert.Equal(["Cem Fehlt"], rows.Select(r => r.StudentName)); + } + + // ── Verspätungsmuster: Elterngespräch-Eskalation (Nutzer-Feedback) ─────── + + [Fact] + public void DetectLatePatterns_EskaliertBeiHoherJahressummeMitWiedervorlageOption() + { + var absences = Enumerable.Range(0, 5) + .Select(i => new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 3 + i), "Spaet Timo", 1001, 0, 10, + ["Deu"], [1], ["entsch."], ["Verspätung"], null, null, false)) + .ToArray(); + var names = new Dictionary { [UntisNameMatching.NameKey("Spaet Timo")] = "Timo Spät" }; + + var notices = ClassTeacherOverviewViewModel.DetectLatePatterns( + absences, names, sevenDayStart: new DateOnly(2026, 8, 31)); + + var notice = Assert.Single(notices); + Assert.Equal("Timo Spät", notice.StudentName); + Assert.Contains("5 Verspätungen seit Schuljahresbeginn", notice.Message); + Assert.Equal(ClassTeacherStatusKind.Danger, notice.Kind); + Assert.True(notice.CanCreateReminder); + } + + [Fact] + public void DetectLatePatterns_KurzfristigesClusterBleibtHinweisOhneAktion() + { + var absences = new[] + { + new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 24), "Spaet Timo", 1001, 0, 10, + ["Deu"], [1], ["entsch."], ["Verspätung"], null, null, false), + new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 25), "Spaet Timo", 1001, 0, 10, + ["Deu"], [1], ["entsch."], ["Verspätung"], null, null, false), + }; + var names = new Dictionary { [UntisNameMatching.NameKey("Spaet Timo")] = "Timo Spät" }; + + var notices = ClassTeacherOverviewViewModel.DetectLatePatterns( + absences, names, sevenDayStart: new DateOnly(2026, 8, 20)); + + var notice = Assert.Single(notices); + Assert.Equal("2-mal verspätet in 7 Tagen", notice.Message); + Assert.Equal(ClassTeacherStatusKind.Warning, notice.Kind); + Assert.False(notice.CanCreateReminder); + } + + [Fact] + public void DetectLatePatterns_UnentschuldigteFehltageGehenVorVerspaetungseskalation() + { + var absences = new[] + { + new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 24), "Fehlt Cem", 1001, 1, 45, + ["Deu"], [1], ["nicht entsch."], ["Absent"], null, null, false), + new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 25), "Fehlt Cem", 1001, 1, 45, + ["Deu"], [1], ["nicht entsch."], ["Absent"], null, null, false), + }; + var names = new Dictionary { [UntisNameMatching.NameKey("Fehlt Cem")] = "Cem Fehlt" }; + + var notices = ClassTeacherOverviewViewModel.DetectLatePatterns( + absences, names, sevenDayStart: new DateOnly(2026, 8, 20)); + + var notice = Assert.Single(notices); + Assert.Contains("unentschuldigte Fehltage", notice.Message); + Assert.Equal(ClassTeacherStatusKind.Danger, notice.Kind); + Assert.False(notice.CanCreateReminder); + } + // ── Aggregierte Klassenbuch-Kategorien (Feature-Idee 5) ────────────────── [Fact] diff --git a/LehrerApp.Desktop/Services/WebUntisIntegrationService.cs b/LehrerApp.Desktop/Services/WebUntisIntegrationService.cs index 99ddc4f..7d0c3e0 100644 --- a/LehrerApp.Desktop/Services/WebUntisIntegrationService.cs +++ b/LehrerApp.Desktop/Services/WebUntisIntegrationService.cs @@ -5,6 +5,7 @@ namespace LehrerApp.Desktop.Services; public sealed class WebUntisIntegrationException(string message) : Exception(message); public sealed record UntisSchoolYearDto(int UntisId, string Name, int StartDate, int EndDate); +public sealed record UntisHolidayDto(int UntisId, string Name, string? LongName, int StartDate, int EndDate); public sealed record UntisClassDto(int UntisId, string Name, string? LongName); public sealed record UntisTeacherDto(int UntisId, string Name, string? ForeName, string? LongName, string? Title, bool Active, IReadOnlyList DepartmentUntisIds) @@ -81,6 +82,10 @@ public sealed class WebUntisIntegrationService(HttpClient http, WebUntisSettings async client => (IReadOnlyList)(await client.GetSchoolYearsAsync(token)) .Select(x => new UntisSchoolYearDto(x.UntisId, x.Name, x.StartDate, x.EndDate)).ToList(), token); + public Task> GetHolidaysAsync(CancellationToken token = default) => ExecuteAsync( + async client => (IReadOnlyList)(await client.GetHolidaysAsync(token)) + .Select(x => new UntisHolidayDto(x.UntisId, x.Name, x.LongName, x.StartDate, x.EndDate)).ToList(), token); + public Task> GetClassesAsync(int schoolYearId, CancellationToken token = default) => ExecuteAsync( async client => (IReadOnlyList)(await client.GetClassesAsync(schoolYearId, token)) .Select(x => new UntisClassDto(x.UntisId, x.Name, x.LongName)).ToList(), token); diff --git a/LehrerApp.Desktop/Services/WebUntisSettingsService.cs b/LehrerApp.Desktop/Services/WebUntisSettingsService.cs index b9eebe5..a9c9ad9 100644 --- a/LehrerApp.Desktop/Services/WebUntisSettingsService.cs +++ b/LehrerApp.Desktop/Services/WebUntisSettingsService.cs @@ -13,10 +13,17 @@ internal class WebUntisSettingsConfig public int? TeacherUntisId { get; set; } public int? HomeroomClassUntisId { get; set; } public string? HomeroomClassName { get; set; } + public List? CachedHolidays { get; set; } + public DateTime? HolidaysFetchedAt { get; set; } } public sealed record WebUntisCredentials(string School, string Host, string Username, string Password); +/// Ferienzeitraum aus WebUntis' getHolidays-Bericht, hier auf die für die +/// Fehlquoten-Berechnung ("Klassenlehrer"-Feature) relevanten Felder reduziert. Kein Geheimnis +/// (anders als iCal-URL/API-Zugangsdaten in dieser Datei), deshalb unverschlüsselt gecacht. +public sealed record CachedUntisHoliday(string Name, DateOnly Start, DateOnly End); + /// /// Einstellungen für den WebUntis-iCal-Abgleich (Nutzer-Feedback, siehe TODO.md). Liegt wie /// AiSettingsService/SyncSettingsService in LehrerApp.Desktop statt LehrerApp.Core, da die @@ -115,6 +122,16 @@ public class WebUntisSettingsService Save(); } + public DateTime? HolidaysFetchedAt => _config.HolidaysFetchedAt; + public IReadOnlyList? GetCachedHolidays() => _config.CachedHolidays; + + public void SetCachedHolidays(IReadOnlyList holidays, DateTime at) + { + _config.CachedHolidays = holidays.ToList(); + _config.HolidaysFetchedAt = at; + Save(); + } + private byte[] GenerateAndSaveKey() { var key = SyncCrypto.GenerateKey(); diff --git a/LehrerApp.Desktop/ViewModels/ClassTeacher/ClassTeacherOverviewViewModel.cs b/LehrerApp.Desktop/ViewModels/ClassTeacher/ClassTeacherOverviewViewModel.cs index dc6db34..2be8809 100644 --- a/LehrerApp.Desktop/ViewModels/ClassTeacher/ClassTeacherOverviewViewModel.cs +++ b/LehrerApp.Desktop/ViewModels/ClassTeacher/ClassTeacherOverviewViewModel.cs @@ -157,7 +157,7 @@ public sealed record ClassTeacherTrendDay(string DayLabel, int AlertCount, int U $"{UnexcusedCount} unentschuldigt · {LateExcusedCount} verspätet · {ExcusedCount} entschuldigt"; } public sealed record ClassTeacherPatternNotice(string StudentName, string Message, - ClassTeacherStatusKind Kind) + ClassTeacherStatusKind Kind, bool CanCreateReminder = false) { public bool IsDangerStatus => Kind == ClassTeacherStatusKind.Danger; public bool IsWarningStatus => Kind == ClassTeacherStatusKind.Warning; @@ -182,7 +182,10 @@ public sealed record ClassTeacherOpenExcuseRow(string StudentName, DateOnly Date public static IReadOnlyList Build( IReadOnlyList absenceDays, DateOnly today) => - absenceDays.Where(a => a.IsUnexcused) + // Verspätungen ausgeschlossen: dafür gibt es i.d.R. keine Entschuldigungspflicht, eine + // "Entschuldigung fehlt"-Erinnerung wäre hier gegenstandslos. Wiederholte Verspätungen + // laufen stattdessen über die Mustererkennung (siehe BuildPatternNotices). + absenceDays.Where(a => a.IsUnexcused && !a.IsLate) .Select(a => new ClassTeacherOpenExcuseRow(a.StudentDisplayName, a.Date, today.DayNumber - a.Date.DayNumber)) .OrderByDescending(r => r.DaysOpen).ThenBy(r => r.StudentName) @@ -192,6 +195,7 @@ public sealed record ClassTeacherOpenExcuseRow(string StudentName, DateOnly Date public partial class ClassTeacherOverviewViewModel : ObservableObject { private readonly WebUntisSettingsService _settings; + private readonly WebUntisIntegrationService _untis; private readonly UntisReportCacheService _cache; private readonly SchoolYearService _schoolYear; private readonly IWorkTaskRepository _workTasks; @@ -268,12 +272,13 @@ public partial class ClassTeacherOverviewViewModel : ObservableObject public Func? OnNavigateToSettings { get; set; } public Func? OnNavigateToWorkload { get; set; } - public ClassTeacherOverviewViewModel(WebUntisSettingsService settings, + public ClassTeacherOverviewViewModel(WebUntisSettingsService settings, WebUntisIntegrationService untis, UntisReportCacheService cache, SchoolYearService schoolYear, IWorkTaskRepository workTasks, IStudentRepository students, IParticipationRepository participation, IParticipationSessionRepository participationSessions, ClassTeacherDetailsViewModel detailsTab) { _settings = settings; + _untis = untis; _cache = cache; _schoolYear = schoolYear; _workTasks = workTasks; @@ -325,12 +330,12 @@ public partial class ClassTeacherOverviewViewModel : ObservableObject var studentsTask = _cache.GetStudentRosterAsync(className); var absencesTask = _cache.GetAbsencesAsync(className, yearStart, today); var classRegisterTask = _cache.GetClassRegisterEventsAsync(className, sevenDayStart, today); - await Task.WhenAll(studentsTask, absencesTask, classRegisterTask); + var holidaysTask = GetHolidaysAsync(); + await Task.WhenAll(studentsTask, absencesTask, classRegisterTask, holidaysTask); var absenceDaysYear = ClassAbsenceDaySummaryRow.GroupByStudentAndDay(absencesTask.Result); var todayAbsences = absenceDaysYear.Where(a => a.Date == today).ToList(); - var weekAbsenceDays = absenceDaysYear.Where(a => a.Date >= sevenDayStart).ToList(); - var schoolDaysElapsed = CountWeekdays(yearStart, today); + var schoolDaysElapsed = CountSchoolWeekdays(yearStart, today, holidaysTask.Result); foreach (var row in ClassTeacherRosterRow.Build(studentsTask.Result, todayAbsences, classRegisterTask.Result, today, absenceDaysYear, schoolDaysElapsed)) Roster.Add(row); @@ -343,7 +348,7 @@ public partial class ClassTeacherOverviewViewModel : ObservableObject UnexcusedAbsenceCount = Roster.Count(r => r.HasAbsenceToday && !r.IsLate && r.IsUnexcused); RecentClassRegisterCount = classRegisterTask.Result.Count; BuildTrend(absenceDaysYear, trendDays); - BuildPatternNotices(weekAbsenceDays); + BuildPatternNotices(absenceDaysYear, sevenDayStart); BuildWeekdayPatternNotices(absenceDaysYear); BuildAttendanceParticipationNotices(); BuildOpenExcuses(absenceDaysYear, today); @@ -462,23 +467,106 @@ public partial class ClassTeacherOverviewViewModel : ObservableObject return count; } - private void BuildPatternNotices(IReadOnlyList absenceDays) + /// Nutzer-Feedback: Schüler*innen, die seit Unterrichtsbeginn nachweislich an jedem Tag + /// fehlten, zeigten trotzdem nur ~57 % Fehlquote statt der erwarteten ~100 %. Ursache: der + /// Nenner zählte bislang jeden Werktag ab dem fest verdrahteten 1. August + /// () als "Schultag" mit — die Sommerferien + /// enden je nach Bundesland/Jahr aber erst Wochen später, und genau zu Schuljahresbeginn macht + /// diese Ferienzeit einen großen Teil des bis dahin "verstrichenen" Zeitraums aus. WebUntis + /// kennt den echten Ferienkalender bereits (getHolidays-Bericht, hier zwischen den + /// Werktagen ausgeschlossen statt wie bisher grob geschätzt) — behebt zusätzlich zum + /// Schuljahresbeginn auch die kleineren Verzerrungen durch Herbst-/Weihnachts-/Osterferien + /// im weiteren Jahresverlauf (bislang bewusst hingenommene Vereinfachung, siehe TODO.md + /// "Klassenlehrer"-Feature — Trend & Fehlquote). + public static int CountSchoolWeekdays(DateOnly start, DateOnly end, IReadOnlyList holidays) { - foreach (var group in absenceDays.GroupBy(row => UntisNameMatching.NameKey(row.StudentName))) + if (end < start) return 0; + var count = 0; + for (var d = start; d <= end; d = d.AddDays(1)) + if (d.DayOfWeek is not (DayOfWeek.Saturday or DayOfWeek.Sunday) && + !holidays.Any(h => d >= h.Start && d <= h.End)) + count++; + return count; + } + + /// Ferien ändern sich innerhalb eines Schuljahrs praktisch nie (anders als die + /// Fehlzeiten-/Klassenbuchberichte, deshalb hier keine der stündlichen "heißes Fenster"-Logik + /// aus , sondern ein einfacher tagesgenauer Cache über + /// — kein zusätzliches LiteDB-Repository nötig). Schlägt + /// der Live-Abruf fehl (z. B. kurzzeitig kein Netz), wird der zuletzt bekannte Stand + /// weiterverwendet statt die ganze Übersicht mit einem Fehler zu blockieren; ist noch nie + /// erfolgreich abgerufen worden, bleibt die Liste leer und + /// verhält sich wie vorher (reine Werktagszählung ohne Ferienabzug). + private static readonly TimeSpan HolidaysRefreshInterval = TimeSpan.FromDays(1); + + private async Task> GetHolidaysAsync() + { + var cached = _settings.GetCachedHolidays(); + if (cached is not null && _settings.HolidaysFetchedAt is { } fetchedAt && + DateTime.UtcNow - fetchedAt < HolidaysRefreshInterval) + return cached; + try + { + var fresh = (await _untis.GetHolidaysAsync()) + .Select(h => new CachedUntisHoliday(h.Name, ParseDate(h.StartDate), ParseDate(h.EndDate))) + .ToList(); + _settings.SetCachedHolidays(fresh, DateTime.UtcNow); + return fresh; + } + catch (WebUntisIntegrationException) + { + return cached ?? []; + } + } + + private static DateOnly ParseDate(int value) => DateOnly.ParseExact(value.ToString(), "yyyyMMdd"); + + private const int LateYearEscalationThreshold = 5; + + private void BuildPatternNotices(IReadOnlyList absenceDaysYear, DateOnly sevenDayStart) + { + var displayNames = Roster.GroupBy(r => UntisNameMatching.NameKey(r.StudentName)) + .ToDictionary(g => g.Key, g => g.First().StudentName); + foreach (var notice in DetectLatePatterns(absenceDaysYear, displayNames, sevenDayStart)) + PatternNotices.Add(notice); + OnPropertyChanged(nameof(HasPatternNotices)); + } + + /// Verspätungen sind i.d.R. nicht entschuldigungsfähig (siehe ) + /// — eine Häufung ist trotzdem ein Signal, nur eines für ein Elterngespräch/einen Brief statt + /// für eine fehlende Entschuldigung. Zwei Stufen, Nutzer-Feedback: ein kurzfristiges Cluster + /// (..heute) bleibt ein sanfter Hinweis ohne Aktion, eine hohe + /// Jahressumme eskaliert zu Danger mit Wiedervorlage-Option + /// (). Reine, ohne ViewModel-Zustand + /// testbare Kernlogik (gleiches Muster wie ). + public static IReadOnlyList DetectLatePatterns( + IReadOnlyList absenceDaysYear, + IReadOnlyDictionary displayNamesByKey, + DateOnly sevenDayStart, + int lateYearEscalationThreshold = LateYearEscalationThreshold) + { + var notices = new List(); + foreach (var group in absenceDaysYear.GroupBy(row => UntisNameMatching.NameKey(row.StudentName))) { var rows = group.ToList(); - var displayName = Roster.FirstOrDefault(row => - UntisNameMatching.NameKey(row.StudentName) == group.Key)?.StudentName ?? rows[0].StudentDisplayName; - var lateDays = rows.Count(row => row.IsLate); - var unexcusedDays = rows.Count(row => row.IsUnexcused && !row.IsLate); - if (unexcusedDays >= 2) - PatternNotices.Add(new ClassTeacherPatternNotice(displayName, - $"{unexcusedDays} unentschuldigte Fehltage in 7 Tagen", ClassTeacherStatusKind.Danger)); - else if (lateDays >= 2) - PatternNotices.Add(new ClassTeacherPatternNotice(displayName, - $"{lateDays}-mal verspätet in 7 Tagen", ClassTeacherStatusKind.Warning)); + var displayName = displayNamesByKey.GetValueOrDefault(group.Key) ?? rows[0].StudentDisplayName; + var weekRows = rows.Where(row => row.Date >= sevenDayStart).ToList(); + var lateDaysWeek = weekRows.Count(row => row.IsLate); + var unexcusedDaysWeek = weekRows.Count(row => row.IsUnexcused && !row.IsLate); + var lateDaysYear = rows.Count(row => row.IsLate); + + if (unexcusedDaysWeek >= 2) + notices.Add(new ClassTeacherPatternNotice(displayName, + $"{unexcusedDaysWeek} unentschuldigte Fehltage in 7 Tagen", ClassTeacherStatusKind.Danger)); + else if (lateDaysYear >= lateYearEscalationThreshold) + notices.Add(new ClassTeacherPatternNotice(displayName, + $"{lateDaysYear} Verspätungen seit Schuljahresbeginn – Elterngespräch oder Brief erwägen", + ClassTeacherStatusKind.Danger, CanCreateReminder: true)); + else if (lateDaysWeek >= 2) + notices.Add(new ClassTeacherPatternNotice(displayName, + $"{lateDaysWeek}-mal verspätet in 7 Tagen", ClassTeacherStatusKind.Warning)); } - OnPropertyChanged(nameof(HasPatternNotices)); + return notices; } /// Erweiterte Mustererkennung (Nutzer-Feedback): die obigen Regeln schauen nur auf die letzten @@ -635,6 +723,13 @@ public partial class ClassTeacherOverviewViewModel : ObservableObject urgent: row.IsOverdue); } + [RelayCommand] + private void CreateReminderForPatternNotice(ClassTeacherPatternNotice? notice) + { + if (notice is null || !notice.CanCreateReminder) return; + SaveReminder(notice.StudentName, notice.Message, urgent: true); + } + private void SaveReminder(string studentName, string notes, bool urgent) { var task = new WorkTask diff --git a/LehrerApp.Desktop/Views/ClassTeacher/ClassTeacherOverviewView.axaml b/LehrerApp.Desktop/Views/ClassTeacher/ClassTeacherOverviewView.axaml index c7aec4a..0548e54 100644 --- a/LehrerApp.Desktop/Views/ClassTeacher/ClassTeacherOverviewView.axaml +++ b/LehrerApp.Desktop/Views/ClassTeacher/ClassTeacherOverviewView.axaml @@ -439,7 +439,7 @@ - + +