Unterrichtsmodus: Schnellbewertung und Hausaufgabe-Kontrolle direkt im Fenster
Zwei neue Buttons ("Mitarbeit", "Anwesenheit/Hausaufgabe") öffnen die
bestehenden Schnellbewertungs-Dialoge direkt aus dem Unterrichtsmodus, ohne
zum Mitarbeit-Tab wechseln zu müssen — TeachingModeViewModel bekommt dafür
eine eigene ParticipationTabViewModel-Instanz, vorselektiert auf die zu
dieser Stunde gehörende Sitzung. Die linke Spalte zeigt jetzt zusätzlich die
Hausaufgabe der letzten Stunde mit Kontrolliert-Checkbox sowie die
Hausaufgabe dieser Stunde als editierbares Feld, statt nur lesbar.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,17 @@ public sealed class TeachingModeViewModelTests
|
|||||||
new FakeStudents(students ?? []), new FakeMemberships(memberships ?? []),
|
new FakeStudents(students ?? []), new FakeMemberships(memberships ?? []),
|
||||||
sessions ?? new FakeSessions([]), new FakeEntries(), new FakeAspects());
|
sessions ?? new FakeSessions([]), new FakeEntries(), new FakeAspects());
|
||||||
|
|
||||||
|
/// Dieselben Fakes wie <see cref="BuildSeatingPlan"/>, 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]
|
[Fact]
|
||||||
public void Konstruktor_LaedtVerlaufsplanUndInitialisiertSitzplanFuerDieGruppe()
|
public void Konstruktor_LaedtVerlaufsplanUndInitialisiertSitzplanFuerDieGruppe()
|
||||||
{
|
{
|
||||||
@@ -24,7 +35,8 @@ public sealed class TeachingModeViewModelTests
|
|||||||
};
|
};
|
||||||
var seatingPlan = BuildSeatingPlan(group.Id);
|
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("Q1 Chemie", vm.GroupName);
|
||||||
Assert.Equal("Redox", vm.LessonInfo.Topic);
|
Assert.Equal("Redox", vm.LessonInfo.Topic);
|
||||||
@@ -41,13 +53,37 @@ public sealed class TeachingModeViewModelTests
|
|||||||
var sessions = new FakeSessions([]);
|
var sessions = new FakeSessions([]);
|
||||||
var seatingPlan = BuildSeatingPlan(group.Id, sessions);
|
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));
|
var created = Assert.Single(sessions.GetByGroup(group.Id));
|
||||||
Assert.Equal(lesson.Id, created.LessonId);
|
Assert.Equal(lesson.Id, created.LessonId);
|
||||||
Assert.Equal(vm.SeatingPlan.SelectedSession?.Id, created.Id);
|
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]
|
[Fact]
|
||||||
public void Konstruktor_BereitsExistierendeVerknuepfteSitzung_WirdWiederverwendet()
|
public void Konstruktor_BereitsExistierendeVerknuepfteSitzung_WirdWiederverwendet()
|
||||||
{
|
{
|
||||||
@@ -59,7 +95,8 @@ public sealed class TeachingModeViewModelTests
|
|||||||
var sessions = new FakeSessions([existing]);
|
var sessions = new FakeSessions([existing]);
|
||||||
var seatingPlan = BuildSeatingPlan(group.Id, sessions);
|
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.Single(sessions.GetByGroup(group.Id));
|
||||||
Assert.Equal(existing.Id, vm.SeatingPlan.SelectedSession?.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" };
|
{ GroupId = group.Id, Date = DateOnly.FromDateTime(DateTime.Today), Topic = "Redox" };
|
||||||
var seatingPlan = BuildSeatingPlan(group.Id);
|
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);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -236,6 +236,13 @@ public partial class SeatingPlanTabViewModel : ObservableObject
|
|||||||
NotifyCommands();
|
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()
|
private void RefreshSeatLessonData()
|
||||||
{
|
{
|
||||||
if (SelectedSession is null)
|
if (SelectedSession is null)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using LehrerApp.Core.Interfaces;
|
using LehrerApp.Core.Interfaces;
|
||||||
using LehrerApp.Core.Models;
|
using LehrerApp.Core.Models;
|
||||||
|
|
||||||
@@ -10,24 +12,104 @@ namespace LehrerApp.Desktop.ViewModels.Groups;
|
|||||||
/// der "Heute"-Tagesliste heraus gestartet.
|
/// der "Heute"-Tagesliste heraus gestartet.
|
||||||
///
|
///
|
||||||
/// Bewusst keine eigene DI-Registrierung als Singleton/Transient: wie LessonViewerViewModel wird
|
/// Bewusst keine eigene DI-Registrierung als Singleton/Transient: wie LessonViewerViewModel wird
|
||||||
/// diese ViewModel direkt vom Code-Behind konstruiert, das dafür nötige SeatingPlanTabViewModel
|
/// 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
|
/// ParticipationTabViewModel aber weiterhin über DI aufgelöst (transient, siehe AppBootstrapper)
|
||||||
/// entgegengenommen statt selbst aus App.Services zu ziehen - ViewModels greifen in diesem Code
|
/// und hier per Konstruktor entgegengenommen statt selbst aus App.Services zu ziehen -
|
||||||
/// nicht selbst auf den Service-Container zu, das bleibt Aufgabe des Code-Behind.
|
/// ViewModels greifen in diesem Code nicht selbst auf den Service-Container zu, das bleibt
|
||||||
|
/// Aufgabe des Code-Behind.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TeachingModeViewModel
|
public class TeachingModeViewModel
|
||||||
{
|
{
|
||||||
public string GroupName { get; }
|
public string GroupName { get; }
|
||||||
public LessonViewerViewModel LessonInfo { get; }
|
public LessonViewerViewModel LessonInfo { get; }
|
||||||
public SeatingPlanTabViewModel SeatingPlan { get; }
|
public SeatingPlanTabViewModel SeatingPlan { get; }
|
||||||
|
/// <summary>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 <see cref="ParticipationTabViewModel"/> wie im Mitarbeit-Tab,
|
||||||
|
/// aber auf die zu dieser Stunde gehörende Sitzung vorselektiert (siehe Konstruktor), damit die
|
||||||
|
/// über <c>OnQuickInput</c>/<c>OnStatusQuickInput</c> geöffneten Dialoge (Wiring in
|
||||||
|
/// TeachingModeWindow.axaml.cs, gleiches Muster wie ParticipationTabView.axaml.cs) direkt auf
|
||||||
|
/// der richtigen Sitzung starten.</summary>
|
||||||
|
public ParticipationTabViewModel Participation { get; }
|
||||||
|
public TeachingModeHomeworkViewModel Homework { get; }
|
||||||
|
|
||||||
public TeachingModeViewModel(Lesson lesson, LearningGroup group,
|
public TeachingModeViewModel(Lesson lesson, LearningGroup group,
|
||||||
IAlternativeLessonPathRepository alternativePaths, SeatingPlanTabViewModel seatingPlan)
|
IAlternativeLessonPathRepository alternativePaths, ILessonRepository lessons,
|
||||||
|
SeatingPlanTabViewModel seatingPlan, ParticipationTabViewModel participation)
|
||||||
{
|
{
|
||||||
GroupName = group.Name;
|
GroupName = group.Name;
|
||||||
LessonInfo = new LessonViewerViewModel(lesson, alternativePaths);
|
LessonInfo = new LessonViewerViewModel(lesson, alternativePaths);
|
||||||
|
|
||||||
SeatingPlan = seatingPlan;
|
SeatingPlan = seatingPlan;
|
||||||
SeatingPlan.Initialize(group.Id, !group.IsActive);
|
SeatingPlan.Initialize(group.Id, !group.IsActive);
|
||||||
SeatingPlan.SelectOrCreateSessionForLesson(lesson);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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
|
||||||
|
/// <see cref="LehrerApp.Desktop.Views.Groups.LessonDialog"/>-Editor zu öffnen (der zusätzlich Phasen/Reflexion usw. zeigt)
|
||||||
|
/// - manchmal ergibt sich die Hausaufgabe erst während der Stunde. Bewusst eine eigene, kleine
|
||||||
|
/// <see cref="ObservableObject"/> statt Felder direkt auf <see cref="TeachingModeViewModel"/>, da
|
||||||
|
/// diese Klasse (wie <see cref="LessonViewerViewModel"/>) selbst keine Bindable-Basisklasse hat.
|
||||||
|
/// </summary>
|
||||||
|
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.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,13 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
|
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
|
||||||
|
<!-- Nutzer-Feedback: die Schnellbewertungs-Dialoge gab es bisher nur über den
|
||||||
|
Mitarbeit-Tab der Gruppe — hier direkt auf die Sitzung dieser Stunde vorselektiert
|
||||||
|
(siehe TeachingModeViewModel), kein Umweg mehr über "Zur Mitarbeit". -->
|
||||||
|
<Button Content="⚡ Mitarbeit" Command="{Binding Participation.QuickInputCommand}"
|
||||||
|
ToolTip.Tip="Mitarbeit dieser Stunde schnell bewerten."/>
|
||||||
|
<Button Content="⚡ Anwesenheit/Hausaufgabe" Command="{Binding Participation.StatusQuickInputCommand}"
|
||||||
|
ToolTip.Tip="Anwesenheit und Hausaufgabenstatus dieser Stunde schnell erfassen."/>
|
||||||
<Button Content="Zur Mitarbeit" Command="{Binding LessonInfo.NavigateToParticipationCommand}"
|
<Button Content="Zur Mitarbeit" Command="{Binding LessonInfo.NavigateToParticipationCommand}"
|
||||||
ToolTip.Tip="Schließt den Unterrichtsmodus und springt zum Tab 'Mitarbeit' der Lerngruppe."/>
|
ToolTip.Tip="Schließt den Unterrichtsmodus und springt zum Tab 'Mitarbeit' der Lerngruppe."/>
|
||||||
<Button Content="Zu den Noten" Command="{Binding LessonInfo.NavigateToGradesCommand}"/>
|
<Button Content="Zu den Noten" Command="{Binding LessonInfo.NavigateToGradesCommand}"/>
|
||||||
@@ -80,9 +87,32 @@
|
|||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
|
|
||||||
<StackPanel Spacing="4" IsVisible="{Binding LessonInfo.Homework, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
<!-- Nutzer-Feedback: Hausaufgabe der letzten Stunde ansehen/als kontrolliert abhaken
|
||||||
<TextBlock Text="Hausaufgabe" FontSize="12" FontWeight="SemiBold" Opacity="0.6"/>
|
und die Hausaufgabe DIESER Stunde einsehen/ändern, ohne den vollen
|
||||||
<TextBlock Text="{Binding LessonInfo.Homework}" FontSize="13" TextWrapping="Wrap"/>
|
Verlaufsplan-Editor öffnen zu müssen — manchmal ergibt sich die Hausaufgabe erst
|
||||||
|
während des Unterrichts. Ersetzt die bisherige rein lesbare Anzeige
|
||||||
|
(LessonInfo.Homework), die dasselbe Feld doppelt und nur lesend gezeigt hätte. -->
|
||||||
|
<Border Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
||||||
|
CornerRadius="6" Padding="10,8" IsVisible="{Binding Homework.HasPreviousHomework}">
|
||||||
|
<StackPanel Spacing="4">
|
||||||
|
<TextBlock FontSize="12" FontWeight="SemiBold" Opacity="0.6">
|
||||||
|
<Run Text="Hausaufgabe letzte Stunde"/><Run Text=" · "/>
|
||||||
|
<Run Text="{Binding Homework.PreviousLessonLabel}"/>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock Text="{Binding Homework.PreviousHomeworkText}" FontSize="13" TextWrapping="Wrap"/>
|
||||||
|
<CheckBox Content="Kontrolliert" IsChecked="{Binding Homework.PreviousHomeworkChecked}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<StackPanel Spacing="4">
|
||||||
|
<TextBlock Text="Hausaufgabe (diese Stunde)" FontSize="12" FontWeight="SemiBold" Opacity="0.6"/>
|
||||||
|
<TextBox Text="{Binding Homework.CurrentHomework, Mode=TwoWay}" AcceptsReturn="True"
|
||||||
|
TextWrapping="Wrap" MinHeight="52" PlaceholderText="Noch keine Hausaufgabe eingetragen…"/>
|
||||||
|
<Grid ColumnDefinitions="Auto,*">
|
||||||
|
<Button Grid.Column="0" Content="Speichern" Command="{Binding Homework.SaveCurrentHomeworkCommand}"/>
|
||||||
|
<TextBlock Grid.Column="1" Text="{Binding Homework.SaveStatus}" FontSize="11" Opacity="0.55"
|
||||||
|
VerticalAlignment="Center" Margin="8,0,0,0"/>
|
||||||
|
</Grid>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Spacing="4" IsVisible="{Binding LessonInfo.Reflection, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
<StackPanel Spacing="4" IsVisible="{Binding LessonInfo.Reflection, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
||||||
|
|||||||
@@ -16,12 +16,38 @@ public partial class TeachingModeWindow : Window
|
|||||||
// Gleiches Muster wie LessonViewerDialog (4.5.3): Fenster schließt zuerst, dann Sprung
|
// Gleiches Muster wie LessonViewerDialog (4.5.3): Fenster schließt zuerst, dann Sprung
|
||||||
// über die MainWindowViewModel-Singleton-Navigation in den Ziel-Tab der Gruppe.
|
// über die MainWindowViewModel-Singleton-Navigation in den Ziel-Tab der Gruppe.
|
||||||
if (DataContext is TeachingModeViewModel vm)
|
if (DataContext is TeachingModeViewModel vm)
|
||||||
|
{
|
||||||
vm.LessonInfo.OnNavigateToTab = tabIndex =>
|
vm.LessonInfo.OnNavigateToTab = tabIndex =>
|
||||||
{
|
{
|
||||||
Close();
|
Close();
|
||||||
App.Services.GetRequiredService<MainWindowViewModel>()
|
App.Services.GetRequiredService<MainWindowViewModel>()
|
||||||
.NavigateToGroupDetail(vm.LessonInfo.GroupId, tabIndex);
|
.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();
|
private void OnClose(object? sender, RoutedEventArgs e) => Close();
|
||||||
|
|||||||
@@ -50,7 +50,9 @@ public partial class TimetableView : UserControl
|
|||||||
|
|
||||||
var teachingModeVm = new TeachingModeViewModel(lesson, group,
|
var teachingModeVm = new TeachingModeViewModel(lesson, group,
|
||||||
App.Services.GetRequiredService<IAlternativeLessonPathRepository>(),
|
App.Services.GetRequiredService<IAlternativeLessonPathRepository>(),
|
||||||
App.Services.GetRequiredService<SeatingPlanTabViewModel>());
|
App.Services.GetRequiredService<ILessonRepository>(),
|
||||||
|
App.Services.GetRequiredService<SeatingPlanTabViewModel>(),
|
||||||
|
App.Services.GetRequiredService<ParticipationTabViewModel>());
|
||||||
var window = new TeachingModeWindow { DataContext = teachingModeVm };
|
var window = new TeachingModeWindow { DataContext = teachingModeVm };
|
||||||
await window.ShowDialog(owner);
|
await window.ShowDialog(owner);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1892,6 +1892,33 @@ folgenden Punkte gehören direkt in `LehrerApp.Desktop`:
|
|||||||
stattdessen ein maximiertes, normal schließbares Fenster, um kein neues, ungetestetes
|
stattdessen ein maximiertes, normal schließbares Fenster, um kein neues, ungetestetes
|
||||||
UI-Verhalten einzuführen.
|
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,
|
**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
|
dass 5.2 wie ursprünglich beschrieben eine zweite, parallele Fehlzeiten-Erfassung neben dem
|
||||||
bereits bestehenden Anwesenheits-Tracking aus Kapitel 3 (`ParticipationEntry.Attendance`,
|
bereits bestehenden Anwesenheits-Tracking aus Kapitel 3 (`ParticipationEntry.Attendance`,
|
||||||
|
|||||||
Reference in New Issue
Block a user