From 23abe7af63ae4d116cbcb98c227376a3bf1ec42a Mon Sep 17 00:00:00 2001 From: Sebastian Hedtrich Date: Sun, 30 Aug 2026 19:38:57 +0200 Subject: [PATCH] =?UTF-8?q?Dashboard-Erinnerung=20f=C3=BCr=20fehlende=20Un?= =?UTF-8?q?terrichtszeit-Erfassung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neue Karte "Unterrichtszeit nacherfassen" schaut 14 Tage zurück und listet Schultage mit Unterricht laut Stundenplan ohne zugehörigen "Unterricht"-Zeiteintrag (Ferien/Feiertage/komplett ausgefallene Tage ausgenommen). Für heute erscheint der Eintrag erst 30 Minuten nach dem laut Stundenplan letzten Unterrichtsende. Klick auf "Erfassen" öffnet den bestehenden Nacherfassen-Dialog vorbelegt mit Datum, Kategorie "Unterricht" und berechnetem Zeitfenster. Co-Authored-By: Claude Sonnet 5 --- .../Services/DashboardSettingsService.cs | 1 + .../DashboardViewModelTests.cs | 119 +++++++++++++++++- LehrerApp.Desktop/App.axaml.cs | 25 ++++ .../ViewModels/DashboardViewModel.cs | 95 +++++++++++++- .../Views/Dashboard/DashboardView.axaml | 26 ++++ TODO.md | 14 +++ 6 files changed, 276 insertions(+), 4 deletions(-) diff --git a/LehrerApp.Core/Services/DashboardSettingsService.cs b/LehrerApp.Core/Services/DashboardSettingsService.cs index 9366a8f..04b6278 100644 --- a/LehrerApp.Core/Services/DashboardSettingsService.cs +++ b/LehrerApp.Core/Services/DashboardSettingsService.cs @@ -16,6 +16,7 @@ public sealed class DashboardSettingsService [ "today", "tasks", "calendar", "excuses", "upcoming", "corrections", "unplanned", "alerts", "attendance", "support", "groups", "examload", + "missingteachingtime", ]; private readonly string _configPath; diff --git a/LehrerApp.Desktop.Tests/DashboardViewModelTests.cs b/LehrerApp.Desktop.Tests/DashboardViewModelTests.cs index dc6235f..d2f958c 100644 --- a/LehrerApp.Desktop.Tests/DashboardViewModelTests.cs +++ b/LehrerApp.Desktop.Tests/DashboardViewModelTests.cs @@ -60,7 +60,8 @@ public sealed class DashboardViewModelTests DashboardSettingsService? dashboardSettings = null, FakeSchoolHolidays? schoolHolidays = null, FakeLessons? lessons = null, FakeSubstitutionEntries? substitutions = null, FakeSessions? sessions = null, FakeEntries? entries = null, - FakeAnnualPlanEvents? annualPlanEvents = null, List? allGroups = null) + FakeAnnualPlanEvents? annualPlanEvents = null, List? allGroups = null, + FakeTimeEntries? timeEntries = null) { lessons ??= new FakeLessons(); lessons.Add(lesson); @@ -73,7 +74,7 @@ public sealed class DashboardViewModelTests slots ?? new FakeTimetableSlots(), periodSchedule ?? NewPeriodSchedule(), new AttendanceBalanceService(), new SchoolYearService(), dashboardSettings ?? NewDashboardSettings(), schoolHolidays ?? new FakeSchoolHolidays(), new PublicHolidayService(), NewCalendarSettings(), - substitutions ?? new FakeSubstitutionEntries(), annualPlanEvents); + substitutions ?? new FakeSubstitutionEntries(), timeEntries ?? new FakeTimeEntries(), annualPlanEvents); } /// Montag einer Woche, die garantiert in der Zukunft liegt und innerhalb der @@ -137,6 +138,120 @@ public sealed class DashboardViewModelTests Assert.Empty(vm.ExamWeekLoads); } + [Fact] + public void MissingTeachingTime_VergangenerUnterrichtstagOhneErfassung_WirdGemeldet() + { + var group = new LearningGroup { Name = "9c" }; + var today = DateOnly.FromDateTime(DateTime.Today); + var pastDay = today.AddDays(-1); + var slots = new FakeTimetableSlots(); + slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = pastDay.DayOfWeek, PeriodNumber = 1 }); + var periodSchedule = NewPeriodSchedule(); + periodSchedule.SetPeriods([new PeriodTimeEntry + { PeriodNumber = 1, Start = new TimeOnly(8, 0), End = new TimeOnly(8, 45) }]); + + var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, slots: slots, periodSchedule: periodSchedule); + + Assert.Contains(vm.MissingTeachingTimeEntries, i => i.Date == pastDay); + Assert.True(vm.MissingTeachingTimeCard.EffectiveIsVisible); + } + + [Fact] + public void MissingTeachingTime_BereitsErfassterTag_WirdNichtGemeldet() + { + var group = new LearningGroup { Name = "9c" }; + var today = DateOnly.FromDateTime(DateTime.Today); + var pastDay = today.AddDays(-1); + var slots = new FakeTimetableSlots(); + slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = pastDay.DayOfWeek, PeriodNumber = 1 }); + var periodSchedule = NewPeriodSchedule(); + periodSchedule.SetPeriods([new PeriodTimeEntry + { PeriodNumber = 1, Start = new TimeOnly(8, 0), End = new TimeOnly(8, 45) }]); + var timeEntries = new FakeTimeEntries(); + timeEntries.Add(new TimeEntry { Date = pastDay, Category = "Unterricht", DurationMinutes = 45 }); + + var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, + slots: slots, periodSchedule: periodSchedule, timeEntries: timeEntries); + + Assert.DoesNotContain(vm.MissingTeachingTimeEntries, i => i.Date == pastDay); + } + + [Fact] + public void MissingTeachingTime_KomplettAusgefallenerTag_WirdNichtGemeldet() + { + var group = new LearningGroup { Name = "9c" }; + var today = DateOnly.FromDateTime(DateTime.Today); + var pastDay = today.AddDays(-1); + var slots = new FakeTimetableSlots(); + slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = pastDay.DayOfWeek, PeriodNumber = 1 }); + var periodSchedule = NewPeriodSchedule(); + periodSchedule.SetPeriods([new PeriodTimeEntry + { PeriodNumber = 1, Start = new TimeOnly(8, 0), End = new TimeOnly(8, 45) }]); + var substitutions = new FakeSubstitutionEntries(); + substitutions.Add(new SubstitutionEntry { Date = pastDay, Kind = SubstitutionKind.Cancelled, PeriodNumber = 1 }); + + var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, + slots: slots, periodSchedule: periodSchedule, substitutions: substitutions); + + Assert.DoesNotContain(vm.MissingTeachingTimeEntries, i => i.Date == pastDay); + } + + [Fact] + public void MissingTeachingTime_SchulferienTagWirdNichtGemeldet() + { + var group = new LearningGroup { Name = "9c" }; + var today = DateOnly.FromDateTime(DateTime.Today); + var pastDay = today.AddDays(-1); + var slots = new FakeTimetableSlots(); + slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = pastDay.DayOfWeek, PeriodNumber = 1 }); + var periodSchedule = NewPeriodSchedule(); + periodSchedule.SetPeriods([new PeriodTimeEntry + { PeriodNumber = 1, Start = new TimeOnly(8, 0), End = new TimeOnly(8, 45) }]); + var schoolHolidays = new FakeSchoolHolidays(); + schoolHolidays.Add(new SchoolHoliday { Name = "Ferien", StartDate = pastDay, EndDate = pastDay }); + + var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, + slots: slots, periodSchedule: periodSchedule, schoolHolidays: schoolHolidays); + + Assert.DoesNotContain(vm.MissingTeachingTimeEntries, i => i.Date == pastDay); + } + + [Fact] + public void MissingTeachingTime_HeuteVorAblaufDerWartezeit_WirdNichtGemeldet() + { + var group = new LearningGroup { Name = "9c" }; + var today = DateOnly.FromDateTime(DateTime.Today); + var slots = new FakeTimetableSlots(); + slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 }); + var periodSchedule = NewPeriodSchedule(); + // Unterrichtsende liegt garantiert noch keine 30 Minuten zurück. + var futureEnd = TimeOnly.FromDateTime(DateTime.Now).AddHours(2); + periodSchedule.SetPeriods([new PeriodTimeEntry + { PeriodNumber = 1, Start = futureEnd.AddHours(-1), End = futureEnd }]); + + var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, slots: slots, periodSchedule: periodSchedule); + + Assert.DoesNotContain(vm.MissingTeachingTimeEntries, i => i.Date == today); + } + + [Fact] + public void MissingTeachingTime_HeuteNachAblaufDerWartezeit_WirdGemeldet() + { + var group = new LearningGroup { Name = "9c" }; + var today = DateOnly.FromDateTime(DateTime.Today); + var slots = new FakeTimetableSlots(); + slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 }); + var periodSchedule = NewPeriodSchedule(); + // Unterrichtsende liegt garantiert mehr als 30 Minuten zurück. + var pastEnd = TimeOnly.FromDateTime(DateTime.Now).AddHours(-2); + periodSchedule.SetPeriods([new PeriodTimeEntry + { PeriodNumber = 1, Start = pastEnd.AddHours(-1), End = pastEnd }]); + + var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, slots: slots, periodSchedule: periodSchedule); + + Assert.Contains(vm.MissingTeachingTimeEntries, i => i.Date == today); + } + [Fact] public void TodaysLessons_LoestRaumUeberPassendenStundenplanSlotAuf() { diff --git a/LehrerApp.Desktop/App.axaml.cs b/LehrerApp.Desktop/App.axaml.cs index fdf5c91..aa8d80a 100644 --- a/LehrerApp.Desktop/App.axaml.cs +++ b/LehrerApp.Desktop/App.axaml.cs @@ -3,6 +3,7 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using Avalonia.Styling; using LehrerApp.Core.Interfaces; +using LehrerApp.Core.Models; using LehrerApp.Core.Services; using LehrerApp.Data; using LehrerApp.Desktop.Services; @@ -153,6 +154,7 @@ public class App : Application dash.OnNavigateToLesson = id => main.NavigateToGroupDetail(id, 3); // Tab "Mitarbeit" dash.OnNavigateToExam = id => main.NavigateToGroupDetail(id, 4); // Tab "Klausuren" dash.OnNavigateToUnplannedLesson = id => main.NavigateToGroupDetail(id, 6); // Tab "Planung" + dash.OnAddMissingTeachingTime = item => ShowMissingTeachingTimeDialog(item, dash); var examsOverview = Services.GetRequiredService(); examsOverview.OnNavigateToGroups = () => main.NavigateToCommand.Execute(NavItem.Groups); @@ -208,6 +210,29 @@ public class App : Application Services.GetRequiredService().Load(); } + private static async Task ShowMissingTeachingTimeDialog(MissingTeachingTimeItem item, DashboardViewModel dashboard) + { + if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime + { MainWindow: { } owner }) return; + + var tasks = Services.GetRequiredService() + .GetAll().Where(t => t.Status != WorkTaskStatus.Done).ToList(); + var vm = new AddTimeEntryDialogViewModel(tasks) + { + DateText = item.Date.ToString("dd.MM.yyyy"), + SelectedCategory = "Unterricht", + StartTimeText = item.WindowStart.ToString("HH:mm"), + EndTimeText = item.WindowEnd.ToString("HH:mm"), + }; + var dialog = new AddTimeEntryDialog { DataContext = vm }; + await dialog.ShowDialog(owner); + if (vm.Result is null) return; + + Services.GetRequiredService().Save(vm.Result); + dashboard.RefreshCommand.Execute(null); + Services.GetRequiredService().Load(); + } + private static async Task ShowQuickGroupDocumentationDialog(DashboardViewModel dashboard) { if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime diff --git a/LehrerApp.Desktop/ViewModels/DashboardViewModel.cs b/LehrerApp.Desktop/ViewModels/DashboardViewModel.cs index 7924cda..0c991ba 100644 --- a/LehrerApp.Desktop/ViewModels/DashboardViewModel.cs +++ b/LehrerApp.Desktop/ViewModels/DashboardViewModel.cs @@ -35,6 +35,7 @@ public partial class DashboardViewModel : ObservableObject private readonly PublicHolidayService _publicHolidays; private readonly SchoolCalendarSettingsService _calendarSettings; private readonly ISubstitutionEntryRepository _substitutions; + private readonly ITimeEntryRepository _timeEntries; private readonly IAnnualPlanEventRepository? _annualPlanEvents; private readonly SchoolWeatherService? _schoolWeather; @@ -50,6 +51,18 @@ public partial class DashboardViewModel : ObservableObject /// hat rein rechnerisch schon 100 %, das ist noch kein auffälliges Muster, nur eine zu kleine /// Stichprobe. Dieselbe Konstante wie GroupOverviewViewModel.AttendanceMinSampleSize. private const int AttendanceMinSampleSize = 8; + /// Nutzer-Feedback: "ich vergesse es, und fasse die App auch außerhalb der Schule manchmal + /// nicht mehr an" — die Erinnerung an fehlende Unterrichtszeit-Erfassung schaut deshalb nicht + /// nur auf heute zurück, sondern auf ein paar Tage, damit ein vergessener Tag nicht verloren + /// geht, sobald der nächste Schultag anbricht. + private const int MissingTeachingTimeLookbackDays = 14; + /// Für den heutigen Tag soll die Erinnerung nicht schon mitten im Unterricht auftauchen — + /// erst diese Zeitspanne nach dem laut Stundenplan letzten Unterrichtsende. + private const int MissingTeachingTimeTodayDelayMinutes = 30; + /// Gleiche Puffer-Idee wie TimeTrackingViewModel.ComputeTodaysTeachingWindow (bewusst hier + /// noch einmal definiert statt geteilt — der Vorschlag ist nur zwei Zeilen Rechnung). + private const int TeachingWindowBufferBeforeMinutes = 15; + private const int TeachingWindowBufferAfterMinutes = 10; /// Vorausschau für die Klausurwochen-Karte (Nutzer-Feedback): weit genug, um eine sich /// anbahnende Häufung noch rechtzeitig vor dem Anlegen weiterer Klausuren zu zeigen, aber /// keine Vorschau auf das ganze Schuljahr. @@ -77,6 +90,7 @@ public partial class DashboardViewModel : ObservableObject public ObservableCollection OpenExcuses { get; } = []; public ObservableCollection AttendanceWarnings { get; } = []; public ObservableCollection ExamWeekLoads { get; } = []; + public ObservableCollection MissingTeachingTimeEntries { get; } = []; public ObservableCollection SupportPlanReviews { get; } = []; public ObservableCollection UpcomingDates { get; } = []; public ObservableCollection OpenCorrections { get; } = []; @@ -101,6 +115,10 @@ public partial class DashboardViewModel : ObservableObject // Sprungziel für eine ungeplante Stunde — führt direkt in den Verlaufsplan-Tab der Gruppe // (nicht den Standard-Tab von OnNavigateToGroup), damit das Thema gleich ergänzt werden kann. public Action? OnNavigateToUnplannedLesson { get; set; } + // Öffnet den Nacherfassen-Dialog für einen Tag mit fehlender Unterrichtszeit-Erfassung + // (Nutzer-Feedback), vorbelegt mit Datum, Kategorie "Unterricht" und dem laut Stundenplan + // berechneten Zeitfenster — echtes Speichern bleibt eine bewusste Bestätigung im Dialog. + public Func? OnAddMissingTeachingTime { get; set; } public DashboardCardOption TodayCard => Card("today"); public DashboardCardOption TasksCard => Card("tasks"); @@ -112,6 +130,7 @@ public partial class DashboardViewModel : ObservableObject public DashboardCardOption AlertsCard => Card("alerts"); public DashboardCardOption AttendanceCard => Card("attendance"); public DashboardCardOption ExamLoadCard => Card("examload"); + public DashboardCardOption MissingTeachingTimeCard => Card("missingteachingtime"); public DashboardCardOption SupportCard => Card("support"); public DashboardCardOption GroupsCard => Card("groups"); public int TodayLessonCount => TodaysLessons.Count; @@ -133,7 +152,8 @@ public partial class DashboardViewModel : ObservableObject PeriodScheduleService periodSchedule, AttendanceBalanceService attendanceBalance, SchoolYearService sy, DashboardSettingsService dashboardSettings, ISchoolHolidayRepository schoolHolidays, PublicHolidayService publicHolidays, SchoolCalendarSettingsService calendarSettings, - ISubstitutionEntryRepository substitutions, IAnnualPlanEventRepository? annualPlanEvents = null, + ISubstitutionEntryRepository substitutions, ITimeEntryRepository timeEntries, + IAnnualPlanEventRepository? annualPlanEvents = null, AnnualPlanSyncService? annualPlanSync = null, SchoolWeatherService? schoolWeather = null) { _groups = groups; _subjects = subjects; _lessons = lessons; _exams = exams; _tasks = tasks; @@ -143,6 +163,7 @@ public partial class DashboardViewModel : ObservableObject _timetableSlots = timetableSlots; _periodSchedule = periodSchedule; _attendanceBalance = attendanceBalance; _sy = sy; _dashboardSettings = dashboardSettings; _schoolHolidays = schoolHolidays; _publicHolidays = publicHolidays; _calendarSettings = calendarSettings; + _timeEntries = timeEntries; _substitutions = substitutions; _annualPlanEvents = annualPlanEvents; _schoolWeather = schoolWeather; @@ -216,6 +237,7 @@ public partial class DashboardViewModel : ObservableObject LoadOpenExcuses(groups.Values.ToList(), today); LoadAttendanceWarnings(today); LoadExamWeekLoads(groups, today); + LoadMissingTeachingTime(today); LoadSupportPlanReviews(today); LoadUpcomingDates(groups, today); LoadOpenCorrections(groups, today); @@ -236,6 +258,7 @@ public partial class DashboardViewModel : ObservableObject AlertsCard.IsEmpty = Alerts.Count == 0; AttendanceCard.IsEmpty = AttendanceWarnings.Count == 0; ExamLoadCard.IsEmpty = ExamWeekLoads.Count == 0; + MissingTeachingTimeCard.IsEmpty = MissingTeachingTimeEntries.Count == 0; SupportCard.IsEmpty = SupportPlanReviews.Count == 0; GroupsCard.IsEmpty = CurrentGroups.Count == 0; @@ -351,6 +374,54 @@ public partial class DashboardViewModel : ObservableObject ExamWeekLoads.Add(new ExamWeekLoadItem(week.IsoWeek, week.WeekStart, week.WeekEnd, week.ExamCount)); } + // ── Unterrichtszeit-Erfassung nachholen (Nutzer-Feedback) ───────────────── + // + // "Ich vergesse es, und fasse die App außerhalb der Schule manchmal nicht mehr an" — schaut + // deshalb bewusst ein paar Tage zurück statt nur auf heute. Ein Tag zählt als erfasst, sobald + // irgendein TimeEntry der Kategorie "Unterricht" an diesem Datum existiert; die genaue + // Zeitüberschneidung mit dem Stundenplan wird nicht geprüft (dieselbe grobe Betrachtung wie + // beim bestehenden "Unterrichtszeit übernehmen"-Vorschlag für heute). + + private void LoadMissingTeachingTime(DateOnly today) + { + MissingTeachingTimeEntries.Clear(); + var firstDay = today.AddDays(-MissingTeachingTimeLookbackDays); + var publicHolidayDates = Enumerable.Range(firstDay.Year, today.Year - firstDay.Year + 1) + .SelectMany(y => _publicHolidays.GetHolidays(y, _calendarSettings.State)) + .Select(h => h.Date).ToHashSet(); + var schoolHolidays = _schoolHolidays.GetAll(); + var nowTime = TimeOnly.FromDateTime(DateTime.Now); + + var items = new List(); + for (var date = firstDay; date <= today; date = date.AddDays(1)) + { + if (IsFreeDay(date, schoolHolidays, publicHolidayDates)) continue; + + var daySlots = _timetableSlots.GetAll().Where(s => s.Weekday == date.DayOfWeek).ToList(); + if (daySlots.Count == 0) continue; + + var cancelledPeriods = _substitutions.GetByDate(date) + .Where(s => s.Kind == SubstitutionKind.Cancelled) + .Select(s => s.PeriodNumber).ToHashSet(); + var periodTimes = daySlots.Where(s => !cancelledPeriods.Contains(s.PeriodNumber)) + .Select(s => _periodSchedule.GetTimes(s.PeriodNumber)) + .Where(t => t is not null).Select(t => t!.Value).ToList(); + if (periodTimes.Count == 0) continue; // alle Stunden entfallen oder kein Zeitraster hinterlegt + + var lastPeriodEnd = periodTimes.Max(t => t.End); + if (date == today && nowTime < lastPeriodEnd.AddMinutes(MissingTeachingTimeTodayDelayMinutes)) + continue; // heute: erst 30 Minuten nach Unterrichtsschluss erinnern + + if (_timeEntries.GetByDate(date).Any(e => e.Category == "Unterricht")) continue; + + var windowStart = periodTimes.Min(t => t.Start).AddMinutes(-TeachingWindowBufferBeforeMinutes); + var windowEnd = lastPeriodEnd.AddMinutes(TeachingWindowBufferAfterMinutes); + items.Add(new MissingTeachingTimeItem(date, windowStart, windowEnd)); + } + foreach (var item in items.OrderBy(i => i.Date)) + MissingTeachingTimeEntries.Add(item); + } + // ── Förderplan-Wiedervorlage (5.3.2) ────────────────────────────────────── private void LoadSupportPlanReviews(DateOnly today) @@ -576,7 +647,8 @@ public partial class DashboardViewModel : ObservableObject "excuses" => "Offene Entschuldigungen", "upcoming" => "Anstehende Termine", "corrections" => "Offene Korrekturen", "unplanned" => "Ungeplante Stunden", "alerts" => "Auffälligkeiten", "attendance" => "Fehlzeiten-Warnung", "support" => "Förderplan-Wiedervorlage", - "groups" => "Meine Lerngruppen", "examload" => "Klausurwochen", _ => key, + "groups" => "Meine Lerngruppen", "examload" => "Klausurwochen", + "missingteachingtime" => "Unterrichtszeit nacherfassen", _ => key, }; private void ApplyCardLayout() @@ -626,6 +698,15 @@ public partial class DashboardViewModel : ObservableObject [RelayCommand] private void OpenStudentSupportPlan(SupportPlanDueItem? item) { if (item is not null) OnNavigateToStudent?.Invoke(item.StudentId); } + [RelayCommand] + private async Task AddMissingTeachingTime(MissingTeachingTimeItem? item) + { + if (item is null || OnAddMissingTeachingTime is null) return; + await OnAddMissingTeachingTime(item); + LoadMissingTeachingTime(DateOnly.FromDateTime(DateTime.Today)); + UpdateDashboardSummary(); + } + private void LoadOpenExcuses(List groups, DateOnly today) { OpenExcuses.Clear(); @@ -1002,6 +1083,16 @@ public class ExamWeekLoadItem(int isoWeek, DateOnly weekStart, DateOnly weekEnd, public string CountDisplay => ExamCount == 1 ? "1 Klausur" : $"{ExamCount} Klausuren"; } +public class MissingTeachingTimeItem(DateOnly date, TimeOnly windowStart, TimeOnly windowEnd) +{ + private static readonly CultureInfo De = new("de-DE"); + + public DateOnly Date { get; } = date; + public TimeOnly WindowStart { get; } = windowStart; + public TimeOnly WindowEnd { get; } = windowEnd; + public string DateDisplay { get; } = date.ToString("dddd, dd.MM.", De); +} + // ── Förderplan-Wiedervorlage (5.3.2) ────────────────────────────────────────── public class SupportPlanDueItem diff --git a/LehrerApp.Desktop/Views/Dashboard/DashboardView.axaml b/LehrerApp.Desktop/Views/Dashboard/DashboardView.axaml index 1f735ef..d6782e5 100644 --- a/LehrerApp.Desktop/Views/Dashboard/DashboardView.axaml +++ b/LehrerApp.Desktop/Views/Dashboard/DashboardView.axaml @@ -444,6 +444,32 @@ + + + + + + + + + +