diff --git a/LehrerApp.Desktop.Tests/TeachingModeViewModelTests.cs b/LehrerApp.Desktop.Tests/TeachingModeViewModelTests.cs
index a3336ad..9873012 100644
--- a/LehrerApp.Desktop.Tests/TeachingModeViewModelTests.cs
+++ b/LehrerApp.Desktop.Tests/TeachingModeViewModelTests.cs
@@ -13,6 +13,17 @@ public sealed class TeachingModeViewModelTests
new FakeStudents(students ?? []), new FakeMemberships(memberships ?? []),
sessions ?? new FakeSessions([]), new FakeEntries(), new FakeAspects());
+ /// Dieselben Fakes wie , aber für ParticipationTabViewModel —
+ /// beide brauchen eigene IParticipationSessionRepository/IParticipationRepository-Instanzen
+ /// im Produktivcode (siehe TeachingModeWindow-Kommentar zu ReloadSeatBadgesFromRepository),
+ /// hier reicht für die Tests aber eine gemeinsame FakeSessions-Instanz, damit beide
+ /// ViewModels dieselbe verknüpfte Sitzung sehen.
+ private static ParticipationTabViewModel BuildParticipation(LearningGroup group,
+ FakeSessions? sessions = null) =>
+ new(sessions ?? new FakeSessions([]), new FakeEntries(), new FakeAspects(),
+ new FakeStudents([]), new FakeMemberships([]), new FakeGroups([group]),
+ new FakeCompetencyDomains());
+
[Fact]
public void Konstruktor_LaedtVerlaufsplanUndInitialisiertSitzplanFuerDieGruppe()
{
@@ -24,7 +35,8 @@ public sealed class TeachingModeViewModelTests
};
var seatingPlan = BuildSeatingPlan(group.Id);
- var vm = new TeachingModeViewModel(lesson, group, new FakeAlternativeLessonPaths([]), seatingPlan);
+ var vm = new TeachingModeViewModel(lesson, group, new FakeAlternativeLessonPaths([]),
+ new FakeLessons(), seatingPlan, BuildParticipation(group));
Assert.Equal("Q1 Chemie", vm.GroupName);
Assert.Equal("Redox", vm.LessonInfo.Topic);
@@ -41,13 +53,37 @@ public sealed class TeachingModeViewModelTests
var sessions = new FakeSessions([]);
var seatingPlan = BuildSeatingPlan(group.Id, sessions);
- var vm = new TeachingModeViewModel(lesson, group, new FakeAlternativeLessonPaths([]), seatingPlan);
+ var vm = new TeachingModeViewModel(lesson, group, new FakeAlternativeLessonPaths([]),
+ new FakeLessons(), seatingPlan, BuildParticipation(group, sessions));
var created = Assert.Single(sessions.GetByGroup(group.Id));
Assert.Equal(lesson.Id, created.LessonId);
Assert.Equal(vm.SeatingPlan.SelectedSession?.Id, created.Id);
}
+ /// Nutzer-Feedback: Schnellbewertungs-Dialoge (Mitarbeit, Anwesenheit/Hausaufgabe) sollen sich
+ /// im Unterrichtsmodus direkt öffnen lassen — das setzt voraus, dass die dafür verwendete
+ /// ParticipationTabViewModel-Instanz von Anfang an auf der zu dieser Stunde gehörenden
+ /// Sitzung steht, nicht auf einer beliebigen anderen Sitzung der Gruppe.
+ [Fact]
+ public void Konstruktor_SelektiertDieselbeSitzungAuchInDerParticipationViewModel()
+ {
+ var group = new LearningGroup { Name = "Q1 Chemie" };
+ var lesson = new Lesson
+ { GroupId = group.Id, Date = DateOnly.FromDateTime(DateTime.Today), Topic = "Redox" };
+ var otherSession = new ParticipationSession
+ { GroupId = group.Id, Date = lesson.Date.AddDays(-1), Comment = "Andere Stunde" };
+ var sessions = new FakeSessions([otherSession]);
+ var seatingPlan = BuildSeatingPlan(group.Id, sessions);
+
+ var vm = new TeachingModeViewModel(lesson, group, new FakeAlternativeLessonPaths([]),
+ new FakeLessons(), seatingPlan, BuildParticipation(group, sessions));
+
+ Assert.NotNull(vm.SeatingPlan.SelectedSession);
+ Assert.Equal(vm.SeatingPlan.SelectedSession!.Id, vm.Participation.SelectedSession?.Id);
+ Assert.NotEqual(otherSession.Id, vm.Participation.SelectedSession?.Id);
+ }
+
[Fact]
public void Konstruktor_BereitsExistierendeVerknuepfteSitzung_WirdWiederverwendet()
{
@@ -59,7 +95,8 @@ public sealed class TeachingModeViewModelTests
var sessions = new FakeSessions([existing]);
var seatingPlan = BuildSeatingPlan(group.Id, sessions);
- var vm = new TeachingModeViewModel(lesson, group, new FakeAlternativeLessonPaths([]), seatingPlan);
+ var vm = new TeachingModeViewModel(lesson, group, new FakeAlternativeLessonPaths([]),
+ new FakeLessons(), seatingPlan, BuildParticipation(group, sessions));
Assert.Single(sessions.GetByGroup(group.Id));
Assert.Equal(existing.Id, vm.SeatingPlan.SelectedSession?.Id);
@@ -75,8 +112,92 @@ public sealed class TeachingModeViewModelTests
{ GroupId = group.Id, Date = DateOnly.FromDateTime(DateTime.Today), Topic = "Redox" };
var seatingPlan = BuildSeatingPlan(group.Id);
- var vm = new TeachingModeViewModel(lesson, group, new FakeAlternativeLessonPaths([]), seatingPlan);
+ var vm = new TeachingModeViewModel(lesson, group, new FakeAlternativeLessonPaths([]),
+ new FakeLessons(), seatingPlan, BuildParticipation(group));
Assert.False(vm.SeatingPlan.IsEditable);
}
+
+ // ── Hausaufgabe: letzte Stunde ansehen/abhaken, aktuelle einsehen/ändern ─────
+
+ [Fact]
+ public void Konstruktor_FindetHausaufgabeDerVorherigenStunde()
+ {
+ var group = new LearningGroup { Name = "Q1 Chemie" };
+ var today = DateOnly.FromDateTime(DateTime.Today);
+ var previous = new Lesson
+ {
+ GroupId = group.Id, Date = today.AddDays(-7), Topic = "Säuren",
+ Homework = "S. 42, Aufgabe 3", HomeworkChecked = false,
+ };
+ var lesson = new Lesson { GroupId = group.Id, Date = today, Topic = "Redox" };
+ var lessons = new FakeLessons();
+ lessons.Add(previous);
+ var seatingPlan = BuildSeatingPlan(group.Id);
+
+ var vm = new TeachingModeViewModel(lesson, group, new FakeAlternativeLessonPaths([]),
+ lessons, seatingPlan, BuildParticipation(group));
+
+ Assert.True(vm.Homework.HasPreviousHomework);
+ Assert.Equal("S. 42, Aufgabe 3", vm.Homework.PreviousHomeworkText);
+ Assert.Contains("Säuren", vm.Homework.PreviousLessonLabel);
+ Assert.False(vm.Homework.PreviousHomeworkChecked);
+ }
+
+ [Fact]
+ public void Konstruktor_IgnoriertVorherigeStundeOhneHausaufgabe()
+ {
+ var group = new LearningGroup { Name = "Q1 Chemie" };
+ var today = DateOnly.FromDateTime(DateTime.Today);
+ var previous = new Lesson { GroupId = group.Id, Date = today.AddDays(-7), Topic = "Säuren" };
+ var lesson = new Lesson { GroupId = group.Id, Date = today, Topic = "Redox" };
+ var lessons = new FakeLessons();
+ lessons.Add(previous);
+ var seatingPlan = BuildSeatingPlan(group.Id);
+
+ var vm = new TeachingModeViewModel(lesson, group, new FakeAlternativeLessonPaths([]),
+ lessons, seatingPlan, BuildParticipation(group));
+
+ Assert.False(vm.Homework.HasPreviousHomework);
+ }
+
+ [Fact]
+ public void PreviousHomeworkChecked_SpeichertSofortAnDerVorherigenStunde()
+ {
+ var group = new LearningGroup { Name = "Q1 Chemie" };
+ var today = DateOnly.FromDateTime(DateTime.Today);
+ var previous = new Lesson
+ { GroupId = group.Id, Date = today.AddDays(-7), Topic = "Säuren", Homework = "S. 42" };
+ var lesson = new Lesson { GroupId = group.Id, Date = today, Topic = "Redox" };
+ var lessons = new FakeLessons();
+ lessons.Add(previous);
+ var seatingPlan = BuildSeatingPlan(group.Id);
+ var vm = new TeachingModeViewModel(lesson, group, new FakeAlternativeLessonPaths([]),
+ lessons, seatingPlan, BuildParticipation(group));
+
+ vm.Homework.PreviousHomeworkChecked = true;
+
+ var saved = Assert.Single(lessons.GetByGroupAndRange(group.Id, today.AddDays(-120), today.AddDays(-1)));
+ Assert.True(saved.HomeworkChecked);
+ }
+
+ [Fact]
+ public void SaveCurrentHomework_SchreibtAufDieAktuelleStunde()
+ {
+ var group = new LearningGroup { Name = "Q1 Chemie" };
+ var lesson = new Lesson
+ { GroupId = group.Id, Date = DateOnly.FromDateTime(DateTime.Today), Topic = "Redox" };
+ var lessons = new FakeLessons();
+ lessons.Add(lesson);
+ var seatingPlan = BuildSeatingPlan(group.Id);
+ var vm = new TeachingModeViewModel(lesson, group, new FakeAlternativeLessonPaths([]),
+ lessons, seatingPlan, BuildParticipation(group));
+
+ vm.Homework.CurrentHomework = "AB 7 fertigstellen";
+ vm.Homework.SaveCurrentHomeworkCommand.Execute(null);
+
+ var saved = Assert.Single(lessons.GetByGroupAndRange(group.Id, lesson.Date, lesson.Date));
+ Assert.Equal("AB 7 fertigstellen", saved.Homework);
+ Assert.Equal("Gespeichert.", vm.Homework.SaveStatus);
+ }
}
diff --git a/LehrerApp.Desktop/ViewModels/Groups/SeatingPlanViewModels.cs b/LehrerApp.Desktop/ViewModels/Groups/SeatingPlanViewModels.cs
index ac15337..b76ebc6 100644
--- a/LehrerApp.Desktop/ViewModels/Groups/SeatingPlanViewModels.cs
+++ b/LehrerApp.Desktop/ViewModels/Groups/SeatingPlanViewModels.cs
@@ -236,6 +236,13 @@ public partial class SeatingPlanTabViewModel : ObservableObject
NotifyCommands();
}
+ /// Öffentlich statt intern (kein InternalsVisibleTo in dieser Codebasis, siehe
+ /// UntisSyncService-Kommentar): der Unterrichtsmodus bewertet über eine zweite, unabhängige
+ /// ParticipationTabViewModel-Instanz (Schnellbewertungs-Dialoge, siehe TeachingModeWindow) —
+ /// diese Instanz hier bekommt davon nichts automatisch mit und muss nach jedem Dialog explizit
+ /// neu aus dem Repository laden, damit die Sitzplatz-Badges nicht veraltet bleiben.
+ public void ReloadSeatBadgesFromRepository() => RefreshSeatLessonData();
+
private void RefreshSeatLessonData()
{
if (SelectedSession is null)
diff --git a/LehrerApp.Desktop/ViewModels/Groups/TeachingModeViewModel.cs b/LehrerApp.Desktop/ViewModels/Groups/TeachingModeViewModel.cs
index ba3a9d2..e8de90f 100644
--- a/LehrerApp.Desktop/ViewModels/Groups/TeachingModeViewModel.cs
+++ b/LehrerApp.Desktop/ViewModels/Groups/TeachingModeViewModel.cs
@@ -1,3 +1,5 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Models;
@@ -10,24 +12,104 @@ namespace LehrerApp.Desktop.ViewModels.Groups;
/// der "Heute"-Tagesliste heraus gestartet.
///
/// Bewusst keine eigene DI-Registrierung als Singleton/Transient: wie LessonViewerViewModel wird
-/// diese ViewModel direkt vom Code-Behind konstruiert, das dafür nötige SeatingPlanTabViewModel
-/// aber weiterhin über DI aufgelöst (transient, siehe AppBootstrapper) und hier per Konstruktor
-/// entgegengenommen statt selbst aus App.Services zu ziehen - ViewModels greifen in diesem Code
-/// nicht selbst auf den Service-Container zu, das bleibt Aufgabe des Code-Behind.
+/// diese ViewModel direkt vom Code-Behind konstruiert, das dafür nötige SeatingPlanTabViewModel/
+/// ParticipationTabViewModel aber weiterhin über DI aufgelöst (transient, siehe AppBootstrapper)
+/// und hier per Konstruktor entgegengenommen statt selbst aus App.Services zu ziehen -
+/// ViewModels greifen in diesem Code nicht selbst auf den Service-Container zu, das bleibt
+/// Aufgabe des Code-Behind.
///
public class TeachingModeViewModel
{
public string GroupName { get; }
public LessonViewerViewModel LessonInfo { get; }
public SeatingPlanTabViewModel SeatingPlan { get; }
+ /// Nutzer-Feedback: die Schnellbewertungs-Dialoge für Mitarbeit/Anwesenheit-Hausaufgabe
+ /// gab es bislang nur über den Mitarbeit-Tab der Gruppe — im Unterrichtsmodus musste man dafür
+ /// erst wieder raus. Hier dieselbe wie im Mitarbeit-Tab,
+ /// aber auf die zu dieser Stunde gehörende Sitzung vorselektiert (siehe Konstruktor), damit die
+ /// über OnQuickInput/OnStatusQuickInput geöffneten Dialoge (Wiring in
+ /// TeachingModeWindow.axaml.cs, gleiches Muster wie ParticipationTabView.axaml.cs) direkt auf
+ /// der richtigen Sitzung starten.
+ public ParticipationTabViewModel Participation { get; }
+ public TeachingModeHomeworkViewModel Homework { get; }
public TeachingModeViewModel(Lesson lesson, LearningGroup group,
- IAlternativeLessonPathRepository alternativePaths, SeatingPlanTabViewModel seatingPlan)
+ IAlternativeLessonPathRepository alternativePaths, ILessonRepository lessons,
+ SeatingPlanTabViewModel seatingPlan, ParticipationTabViewModel participation)
{
GroupName = group.Name;
LessonInfo = new LessonViewerViewModel(lesson, alternativePaths);
+
SeatingPlan = seatingPlan;
SeatingPlan.Initialize(group.Id, !group.IsActive);
SeatingPlan.SelectOrCreateSessionForLesson(lesson);
+
+ Participation = participation;
+ Participation.Initialize(group.Id, group.SchoolYear, !group.IsActive);
+ if (SeatingPlan.SelectedSession is { } linkedSession)
+ Participation.SelectedSession =
+ Participation.Sessions.FirstOrDefault(s => s.Id == linkedSession.Id);
+
+ // 4.5.4: dieselbe "letzte Stunde vor dieser"-Suche wie das Stundenplan-Badge
+ // "Hausaufgabe kontrollieren" (TimetableViewModel.HasUnhandledHomework) - 120 Tage
+ // Lookback deckt auch längere Ferienpausen ab, ohne unbegrenzt weit zurückzuscannen.
+ var previousLesson = lessons.GetByGroupAndRange(group.Id, lesson.Date.AddDays(-120), lesson.Date.AddDays(-1))
+ .OrderByDescending(l => l.Date).ThenByDescending(l => l.LessonNumber ?? 0)
+ .FirstOrDefault();
+ Homework = new TeachingModeHomeworkViewModel(lesson, previousLesson, lessons);
+ }
+}
+
+///
+/// Nutzer-Feedback: im Unterrichtsmodus sollte man die Hausaufgabe aus der letzten Stunde als
+/// kontrolliert abhaken und die neue Hausaufgabe eintragen/ändern können, ohne dafür den vollen
+/// -Editor zu öffnen (der zusätzlich Phasen/Reflexion usw. zeigt)
+/// - manchmal ergibt sich die Hausaufgabe erst während der Stunde. Bewusst eine eigene, kleine
+/// statt Felder direkt auf , da
+/// diese Klasse (wie ) selbst keine Bindable-Basisklasse hat.
+///
+public partial class TeachingModeHomeworkViewModel : ObservableObject
+{
+ private readonly ILessonRepository _lessons;
+ private readonly Lesson _lesson;
+ private readonly Lesson? _previousLesson;
+
+ public bool HasPreviousHomework =>
+ _previousLesson is not null && !string.IsNullOrWhiteSpace(_previousLesson.Homework);
+ public string PreviousHomeworkText => _previousLesson?.Homework ?? "";
+ public string PreviousLessonLabel => _previousLesson is null ? "" :
+ $"{_previousLesson.Date:dd.MM.yyyy}" + (string.IsNullOrWhiteSpace(_previousLesson.Topic)
+ ? "" : $" · {_previousLesson.Topic}");
+
+ [ObservableProperty] private bool _previousHomeworkChecked;
+ [ObservableProperty] private string _currentHomework;
+ [ObservableProperty] private string _saveStatus = "";
+
+ public TeachingModeHomeworkViewModel(Lesson lesson, Lesson? previousLesson, ILessonRepository lessons)
+ {
+ _lesson = lesson;
+ _previousLesson = previousLesson;
+ _lessons = lessons;
+ _previousHomeworkChecked = previousLesson?.HomeworkChecked ?? false;
+ _currentHomework = lesson.Homework ?? "";
+ }
+
+ /// Sofort beim Umschalten gespeichert (kein separater Speichern-Klick nötig) - dieselbe
+ /// Checkbox-Semantik wie im LessonDialog (4.5.4): "kontrolliert" und "nicht kontrollieren"
+ /// schließen sich aus.
+ partial void OnPreviousHomeworkCheckedChanged(bool value)
+ {
+ if (_previousLesson is null) return;
+ _previousLesson.HomeworkChecked = value;
+ if (value) _previousLesson.HomeworkCheckDismissed = false;
+ _lessons.Save(_previousLesson);
+ }
+
+ [RelayCommand]
+ private void SaveCurrentHomework()
+ {
+ _lesson.Homework = CurrentHomework;
+ _lessons.Save(_lesson);
+ SaveStatus = "Gespeichert.";
}
}
diff --git a/LehrerApp.Desktop/Views/Groups/TeachingModeWindow.axaml b/LehrerApp.Desktop/Views/Groups/TeachingModeWindow.axaml
index fd7a0d8..1578210 100644
--- a/LehrerApp.Desktop/Views/Groups/TeachingModeWindow.axaml
+++ b/LehrerApp.Desktop/Views/Groups/TeachingModeWindow.axaml
@@ -27,6 +27,13 @@
+
+
+
@@ -80,9 +87,32 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LehrerApp.Desktop/Views/Groups/TeachingModeWindow.axaml.cs b/LehrerApp.Desktop/Views/Groups/TeachingModeWindow.axaml.cs
index 7db36a0..c516a5f 100644
--- a/LehrerApp.Desktop/Views/Groups/TeachingModeWindow.axaml.cs
+++ b/LehrerApp.Desktop/Views/Groups/TeachingModeWindow.axaml.cs
@@ -16,12 +16,38 @@ public partial class TeachingModeWindow : Window
// Gleiches Muster wie LessonViewerDialog (4.5.3): Fenster schließt zuerst, dann Sprung
// über die MainWindowViewModel-Singleton-Navigation in den Ziel-Tab der Gruppe.
if (DataContext is TeachingModeViewModel vm)
+ {
vm.LessonInfo.OnNavigateToTab = tabIndex =>
{
Close();
App.Services.GetRequiredService()
.NavigateToGroupDetail(vm.LessonInfo.GroupId, tabIndex);
};
+ // Nutzer-Feedback: Schnellbewertung (Mitarbeit) und Schnellanwesenheit/Hausaufgabe
+ // direkt aus dem Unterrichtsmodus statt erst zum Mitarbeit-Tab wechseln zu müssen —
+ // dieselben Dialoge wie in ParticipationTabView.axaml.cs, hier auf die zu dieser
+ // Stunde gehörende Sitzung vorselektiert (siehe TeachingModeViewModel-Konstruktor).
+ vm.Participation.OnQuickInput = ShowQuickInputDialog;
+ vm.Participation.OnStatusQuickInput = ShowStatusQuickInputDialog;
+ }
+ }
+
+ private async Task ShowQuickInputDialog(ParticipationTabViewModel tabVm)
+ {
+ if (tabVm.StudentRows.Count == 0) return;
+ var quickVm = new QuickInputViewModel(tabVm.StudentRows.ToList(), tabVm.Aspects.ToList());
+ var dialog = new ParticipationQuickInputDialog { DataContext = quickVm };
+ await dialog.ShowDialog(this);
+ (DataContext as TeachingModeViewModel)?.SeatingPlan.ReloadSeatBadgesFromRepository();
+ }
+
+ private async Task ShowStatusQuickInputDialog(ParticipationTabViewModel tabVm)
+ {
+ if (tabVm.StudentRows.Count == 0 || tabVm.SelectedSession is null) return;
+ var quickVm = new AttendanceHomeworkQuickInputViewModel(tabVm.StudentRows, tabVm.SelectedSessionDisplay);
+ var dialog = new AttendanceHomeworkQuickInputDialog { DataContext = quickVm };
+ await dialog.ShowDialog(this);
+ (DataContext as TeachingModeViewModel)?.SeatingPlan.ReloadSeatBadgesFromRepository();
}
private void OnClose(object? sender, RoutedEventArgs e) => Close();
diff --git a/LehrerApp.Desktop/Views/Planning/TimetableView.axaml.cs b/LehrerApp.Desktop/Views/Planning/TimetableView.axaml.cs
index f7dd000..de37fea 100644
--- a/LehrerApp.Desktop/Views/Planning/TimetableView.axaml.cs
+++ b/LehrerApp.Desktop/Views/Planning/TimetableView.axaml.cs
@@ -50,7 +50,9 @@ public partial class TimetableView : UserControl
var teachingModeVm = new TeachingModeViewModel(lesson, group,
App.Services.GetRequiredService(),
- App.Services.GetRequiredService());
+ App.Services.GetRequiredService(),
+ App.Services.GetRequiredService(),
+ App.Services.GetRequiredService());
var window = new TeachingModeWindow { DataContext = teachingModeVm };
await window.ShowDialog(owner);
}
diff --git a/TODO.md b/TODO.md
index 1334054..afc85ec 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1892,6 +1892,33 @@ folgenden Punkte gehören direkt in `LehrerApp.Desktop`:
stattdessen ein maximiertes, normal schließbares Fenster, um kein neues, ungetestetes
UI-Verhalten einzuführen.
+ **Nachtrag (August 2026, Nutzer-Feedback) — Schnellbewertung und Hausaufgabe direkt im
+ Unterrichtsmodus:** zwei neue Buttons oben ("⚡ Mitarbeit", "⚡ Anwesenheit/Hausaufgabe")
+ öffnen dieselben Schnellbewertungs-Dialoge wie der Mitarbeit-Tab der Gruppe
+ (`ParticipationQuickInputDialog`/`AttendanceHomeworkQuickInputDialog`), ohne den
+ Unterrichtsmodus verlassen zu müssen. Dafür bekommt `TeachingModeViewModel` jetzt eine
+ eigene, per DI aufgelöste `ParticipationTabViewModel`-Instanz (gleiches Muster wie
+ `SeatingPlanTabViewModel`), initialisiert auf die Gruppe und explizit auf die zu dieser
+ Stunde gehörende Sitzung vorselektiert (`SeatingPlan.SelectedSession.Id` nach
+ `SelectOrCreateSessionForLesson`). **Wichtige Falle dabei:** die beiden ViewModels laufen
+ als unabhängige Instanzen mit jeweils eigenem, lokal geladenem Zustand — eine über die
+ Schnellbewertung geänderte Anwesenheit/Hausaufgabe spiegelt sich nicht automatisch in den
+ Sitzplan-Badges (`SeatCellViewModel.AttendanceBadge`/`HomeworkBadge`), da
+ `SeatingPlanTabViewModel.RefreshSeatLessonData()` nur auf eigene Änderungen reagiert. Neue
+ öffentliche Methode `SeatingPlanTabViewModel.ReloadSeatBadgesFromRepository()` (öffentlich
+ statt intern, wie der Rest der Codebasis es hält — kein `InternalsVisibleTo`), von
+ `TeachingModeWindow.axaml.cs` nach jedem geschlossenen Schnellbewertungs-Dialog aufgerufen.
+
+ Zusätzlich: die linke Spalte zeigt jetzt statt der rein lesbaren Hausaufgaben-Anzeige einen
+ interaktiven Block — Hausaufgabe der **letzten** Stunde (120-Tage-Rückschau wie beim
+ bestehenden Stundenplan-Badge, `TimetableViewModel.HasUnhandledHomework`) mit
+ "Kontrolliert"-Checkbox (speichert sofort, dieselbe Semantik wie im `LessonDialog`, 4.5.4),
+ und die Hausaufgabe **dieser** Stunde als Textfeld mit Speichern-Button — Nutzer-Feedback:
+ die Hausaufgabe ergibt sich manchmal erst während der Stunde, dafür sollte man nicht extra
+ den vollen Verlaufsplan-Editor öffnen müssen. Neue kleine
+ `TeachingModeHomeworkViewModel : ObservableObject`, da `TeachingModeViewModel` selbst (wie
+ `LessonViewerViewModel`) keine Bindable-Basisklasse hat.
+
**Wichtige Abweichung von der ursprünglichen Planung (5.2):** Vor der Umsetzung zeigte sich,
dass 5.2 wie ursprünglich beschrieben eine zweite, parallele Fehlzeiten-Erfassung neben dem
bereits bestehenden Anwesenheits-Tracking aus Kapitel 3 (`ParticipationEntry.Attendance`,