feat: Stundenplan-Badge für unkontrollierte Hausaufgaben (4.5.4)

Lesson bekommt HomeworkChecked/HomeworkCheckDismissed. Der Stundenplan
(Tagesliste und Wochenraster im "Heute"-Tab) zeigt ein 📓-Badge, wenn die
letzte Stunde einer Gruppe eine Hausaufgabe hatte, die noch nicht
kontrolliert oder bewusst übersprungen wurde. Umschaltbar über zwei sich
gegenseitig ausschließende Checkboxen ("Kontrolliert"/"Nicht
kontrollieren") im Verlaufsplan-Editor, direkt neben dem
Hausaufgabenfeld.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 20:52:22 +02:00
co-authored by Claude Sonnet 5
parent 462080ab92
commit 41bf2c0756
8 changed files with 215 additions and 10 deletions
+6
View File
@@ -56,6 +56,12 @@ public class Lesson
public TimeOnly? StartTime { get; set; } public TimeOnly? StartTime { get; set; }
public List<LessonPhaseStep> Phases { get; set; } = []; public List<LessonPhaseStep> Phases { get; set; } = [];
public string? Homework { get; set; } public string? Homework { get; set; }
/// Markiert, dass die hier eingetragene Hausaufgabe in einer Folgestunde besprochen/kontrolliert
/// wurde — treibt das Stundenplan-Badge "Hausaufgabe kontrollieren" (4.5.4).
public bool HomeworkChecked { get; set; }
/// Bewusstes Abschalten der Erinnerung, ohne eine tatsächliche Kontrolle vorzutäuschen (z.B.
/// wenn die Hausaufgabe absichtlich nicht mehr kontrolliert wird).
public bool HomeworkCheckDismissed { get; set; }
public string? Reflection { get; set; } public string? Reflection { get; set; }
public LessonStatus Status { get; set; } = LessonStatus.Planned; public LessonStatus Status { get; set; } = LessonStatus.Planned;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
@@ -179,6 +179,59 @@ public sealed class LessonDialogViewModelTests
Assert.Single(lessons.GetByUnit(unitId)); Assert.Single(lessons.GetByUnit(unitId));
} }
[Fact]
public void HasHomeworkText_OhneHausaufgabe_IstFalse()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
Assert.False(vm.HasHomeworkText);
vm.Homework = " ";
Assert.False(vm.HasHomeworkText);
vm.Homework = "S. 42";
Assert.True(vm.HasHomeworkText);
}
[Fact]
public void Save_UebernimmtHomeworkCheckedUndDismissed()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
vm.Topic = "Brechung"; vm.DateText = "01.09.2025";
vm.Homework = "S. 42"; vm.HomeworkChecked = true;
vm.SaveCommand.Execute(null);
Assert.True(vm.Result!.HomeworkChecked);
Assert.False(vm.Result.HomeworkCheckDismissed);
}
[Fact]
public void HomeworkChecked_UndHomeworkCheckDismissed_SchliessenSichGegenseitigAus()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
vm.HomeworkChecked = true;
Assert.False(vm.HomeworkCheckDismissed);
vm.HomeworkCheckDismissed = true;
Assert.False(vm.HomeworkChecked);
}
[Fact]
public void Bearbeiten_UebernimmtGespeicherteHomeworkFlags()
{
var editing = new Lesson
{
Topic = "Alt", Date = new DateOnly(2025, 9, 1), Homework = "S. 42",
HomeworkCheckDismissed = true,
};
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid(), editing);
Assert.True(vm.HomeworkCheckDismissed);
Assert.False(vm.HomeworkChecked);
}
[Fact] [Fact]
public void HasAlternativePath_AngehaktOeffnetZuweisungUndUebernimmtErgebnis() public void HasAlternativePath_AngehaktOeffnetZuweisungUndUebernimmtErgebnis()
{ {
@@ -132,6 +132,88 @@ public sealed class TimetableViewModelTests
Assert.True(item.HasExam); Assert.True(item.HasExam);
} }
[Fact]
public void Load_HeutigeStunde_VorherigeUnkontrollierteHausaufgabe_ZeigtBadge()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 });
var lessons = new FakeLessons();
lessons.Add(new Lesson { GroupId = group.Id, Date = today.AddDays(-3), Homework = "S. 42, Aufgabe 3" });
var vm = BuildViewModel(slots, new FakeGroups([group]), lessons: lessons);
var item = Assert.Single(vm.TodayItems);
Assert.True(item.HasUnhandledHomework);
}
[Fact]
public void Load_HeutigeStunde_HausaufgabeKontrolliert_KeinBadge()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 });
var lessons = new FakeLessons();
lessons.Add(new Lesson
{
GroupId = group.Id, Date = today.AddDays(-3), Homework = "S. 42, Aufgabe 3", HomeworkChecked = true,
});
var vm = BuildViewModel(slots, new FakeGroups([group]), lessons: lessons);
var item = Assert.Single(vm.TodayItems);
Assert.False(item.HasUnhandledHomework);
}
[Fact]
public void Load_HeutigeStunde_HausaufgabenKontrolleBewusstAbgeschaltet_KeinBadge()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 });
var lessons = new FakeLessons();
lessons.Add(new Lesson
{
GroupId = group.Id, Date = today.AddDays(-3), Homework = "S. 42, Aufgabe 3",
HomeworkCheckDismissed = true,
});
var vm = BuildViewModel(slots, new FakeGroups([group]), lessons: lessons);
var item = Assert.Single(vm.TodayItems);
Assert.False(item.HasUnhandledHomework);
}
[Fact]
public void Load_HeutigeStunde_VorherigeStundeOhneHausaufgabe_KeinBadge()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 });
var lessons = new FakeLessons();
lessons.Add(new Lesson { GroupId = group.Id, Date = today.AddDays(-3) });
var vm = BuildViewModel(slots, new FakeGroups([group]), lessons: lessons);
var item = Assert.Single(vm.TodayItems);
Assert.False(item.HasUnhandledHomework);
}
[Fact]
public void Load_Wochenkachel_VorherigeUnkontrollierteHausaufgabe_ZeigtBadge()
{
var group = new LearningGroup { Name = "Q1 Chemie" };
var date = DateInCurrentWeek(DayOfWeek.Wednesday);
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Wednesday, PeriodNumber = 1 });
var lessons = new FakeLessons();
lessons.Add(new Lesson { GroupId = group.Id, Date = date.AddDays(-2), Homework = "Vokabeln lernen" });
var vm = BuildViewModel(slots, new FakeGroups([group]), lessons: lessons);
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Wednesday && c.PeriodNumber == 1);
Assert.True(cell.HasUnhandledHomework);
}
[Fact] [Fact]
public void OpenGroup_RuftOnNavigateToGroupMitDerGroupIdAuf() public void OpenGroup_RuftOnNavigateToGroupMitDerGroupIdAuf()
{ {
@@ -635,6 +635,8 @@ public partial class LessonDialogViewModel : ObservableObject
[ObservableProperty] private string _topic = ""; [ObservableProperty] private string _topic = "";
[ObservableProperty] private string _startTimeText = ""; [ObservableProperty] private string _startTimeText = "";
[ObservableProperty] private string _homework = ""; [ObservableProperty] private string _homework = "";
[ObservableProperty] private bool _homeworkChecked;
[ObservableProperty] private bool _homeworkCheckDismissed;
[ObservableProperty] private string _reflection = ""; [ObservableProperty] private string _reflection = "";
[ObservableProperty] private string _statusName = LessonStatusDisplay.Options[0]; [ObservableProperty] private string _statusName = LessonStatusDisplay.Options[0];
[ObservableProperty] private string _dateTextError = ""; [ObservableProperty] private string _dateTextError = "";
@@ -646,6 +648,9 @@ public partial class LessonDialogViewModel : ObservableObject
[ObservableProperty] private bool _hasTimeBudgetInfo; [ObservableProperty] private bool _hasTimeBudgetInfo;
public string[] StatusOptions => LessonStatusDisplay.Options; public string[] StatusOptions => LessonStatusDisplay.Options;
/// Nur bei vorhandenem Hausaufgabentext sinnvoll — steuert die Sichtbarkeit von
/// HomeworkChecked/HomeworkCheckDismissed im Dialog (4.5.4).
public bool HasHomeworkText => !string.IsNullOrWhiteSpace(Homework);
public string[] MaterialSuggestions { get; } public string[] MaterialSuggestions { get; }
public string[] ShorthandSuggestions { get; } public string[] ShorthandSuggestions { get; }
public ObservableCollection<PhaseStepEditItem> Phases { get; } = []; public ObservableCollection<PhaseStepEditItem> Phases { get; } = [];
@@ -707,6 +712,8 @@ public partial class LessonDialogViewModel : ObservableObject
Topic = editingLesson.Topic; Topic = editingLesson.Topic;
StartTimeText = editingLesson.StartTime?.ToString("HH:mm") ?? ""; StartTimeText = editingLesson.StartTime?.ToString("HH:mm") ?? "";
Homework = editingLesson.Homework ?? ""; Homework = editingLesson.Homework ?? "";
HomeworkChecked = editingLesson.HomeworkChecked;
HomeworkCheckDismissed = editingLesson.HomeworkCheckDismissed;
Reflection = editingLesson.Reflection ?? ""; Reflection = editingLesson.Reflection ?? "";
StatusName = LessonStatusDisplay.ToName(editingLesson.Status); StatusName = LessonStatusDisplay.ToName(editingLesson.Status);
foreach (var p in editingLesson.Phases) AddPhaseInternal(p); foreach (var p in editingLesson.Phases) AddPhaseInternal(p);
@@ -803,6 +810,11 @@ public partial class LessonDialogViewModel : ObservableObject
RecomputeTimes(); RecomputeTimes();
} }
partial void OnDateTextChanged(string value) => RecomputeTimes(); partial void OnDateTextChanged(string value) => RecomputeTimes();
partial void OnHomeworkChanged(string value) => OnPropertyChanged(nameof(HasHomeworkText));
// "Kontrolliert" und "bewusst nicht kontrollieren" schließen sich gegenseitig aus — beides sind
// "erledigt"-Zustände mit unterschiedlicher Bedeutung, nie gleichzeitig sinnvoll.
partial void OnHomeworkCheckedChanged(bool value) { if (value) HomeworkCheckDismissed = false; }
partial void OnHomeworkCheckDismissedChanged(bool value) { if (value) HomeworkChecked = false; }
/// Dauer ist die primäre Eingabe je Phase; die Uhrzeit wird daraus nur zur Anzeige /// Dauer ist die primäre Eingabe je Phase; die Uhrzeit wird daraus nur zur Anzeige
/// abgeleitet — kumulativ ab "Beginn", sofern gesetzt (sonst bleibt sie leer). /// abgeleitet — kumulativ ab "Beginn", sofern gesetzt (sonst bleibt sie leer).
@@ -907,6 +919,8 @@ public partial class LessonDialogViewModel : ObservableObject
Result.StartTime = startTime; Result.StartTime = startTime;
Result.Phases = Phases.Select(p => p.ToModel()).ToList(); Result.Phases = Phases.Select(p => p.ToModel()).ToList();
Result.Homework = string.IsNullOrWhiteSpace(Homework) ? null : Homework.Trim(); Result.Homework = string.IsNullOrWhiteSpace(Homework) ? null : Homework.Trim();
Result.HomeworkChecked = HomeworkChecked;
Result.HomeworkCheckDismissed = HomeworkCheckDismissed;
Result.Reflection = string.IsNullOrWhiteSpace(Reflection) ? null : Reflection.Trim(); Result.Reflection = string.IsNullOrWhiteSpace(Reflection) ? null : Reflection.Trim();
Result.Status = LessonStatusDisplay.FromName(StatusName); Result.Status = LessonStatusDisplay.FromName(StatusName);
_lessons.Save(Result); _lessons.Save(Result);
@@ -197,7 +197,8 @@ public partial class TimetableViewModel : ObservableObject
var lesson = _lessons.GetByGroupAndDate(slot.GroupId, today).FirstOrDefault(); var lesson = _lessons.GetByGroupAndDate(slot.GroupId, today).FirstOrDefault();
var exam = _exams.GetByGroup(slot.GroupId).FirstOrDefault(e => e.Date == today); var exam = _exams.GetByGroup(slot.GroupId).FirstOrDefault(e => e.Date == today);
items.Add(new TodayLessonItem(slot.GroupId, slot.PeriodNumber, group.Name, items.Add(new TodayLessonItem(slot.GroupId, slot.PeriodNumber, group.Name,
slot.Room ?? "", ColorFor(group.Name), lesson?.Topic, exam?.Title)); slot.Room ?? "", ColorFor(group.Name), lesson?.Topic, exam?.Title,
HasUnhandledHomework(slot.GroupId, today)));
} }
foreach (var sub in substitutionsToday.Where(s => s.Kind == SubstitutionKind.Lesson && foreach (var sub in substitutionsToday.Where(s => s.Kind == SubstitutionKind.Lesson &&
@@ -331,7 +332,8 @@ public partial class TimetableViewModel : ObservableObject
subject?.ShortName is { Length: > 0 } sn ? sn : subject?.Name ?? "", subject?.ShortName is { Length: > 0 } sn ? sn : subject?.Name ?? "",
group?.Name ?? "?", slot.Room ?? "", lesson?.Topic ?? "", group?.Name ?? "?", slot.Room ?? "", lesson?.Topic ?? "",
colorHex, holidayBadge, hasExam, isLastBeforeExam, colorHex, holidayBadge, hasExam, isLastBeforeExam,
MentionsExperiment(lesson), slot.GroupId, isHoliday)); MentionsExperiment(lesson), slot.GroupId, isHoliday,
HasUnhandledHomework(slot.GroupId, date)));
} }
AddWeekSupervisionRowIfAny(period, dutiesByPeriod, substitutionsThisWeek, dateByWeekday); AddWeekSupervisionRowIfAny(period, dutiesByPeriod, substitutionsThisWeek, dateByWeekday);
} }
@@ -392,6 +394,21 @@ public partial class TimetableViewModel : ObservableObject
p.Activity?.Contains("Experiment", StringComparison.OrdinalIgnoreCase) == true || p.Activity?.Contains("Experiment", StringComparison.OrdinalIgnoreCase) == true ||
p.Material?.Contains("Experiment", StringComparison.OrdinalIgnoreCase) == true)) == true; p.Material?.Contains("Experiment", StringComparison.OrdinalIgnoreCase) == true)) == true;
/// <summary>4.5.4: Hat die letzte vor <paramref name="date"/> liegende Lesson dieser Gruppe eine
/// Hausaufgabe, die weder als kontrolliert noch als bewusst übersprungen markiert ist? Schaut
/// bewusst nur auf die unmittelbar vorherige Lesson (nicht auf die gesamte Historie) — sobald
/// eine neuere Lesson stattfindet, ist eine noch ältere offene Hausaufgabe nicht mehr das, worauf
/// sich "letzte Stunde" bezieht. Lookback-Fenster von 120 Tagen deckt auch längere Ferienpausen
/// ab, ohne unbegrenzt weit zurückzuscannen.</summary>
private bool HasUnhandledHomework(Guid groupId, DateOnly date)
{
var previousLesson = _lessons.GetByGroupAndRange(groupId, date.AddDays(-120), date.AddDays(-1))
.OrderByDescending(l => l.Date).ThenByDescending(l => l.LessonNumber ?? 0)
.FirstOrDefault();
if (previousLesson is null || string.IsNullOrWhiteSpace(previousLesson.Homework)) return false;
return !previousLesson.HomeworkChecked && !previousLesson.HomeworkCheckDismissed;
}
/// <summary>Fällt <paramref name="date"/> auf einen gesetzlichen Feiertag oder in Schulferien?</summary> /// <summary>Fällt <paramref name="date"/> auf einen gesetzlichen Feiertag oder in Schulferien?</summary>
private static bool IsFreeDay(DateOnly date, List<SchoolHoliday> schoolHolidays, HashSet<DateOnly> publicHolidayDates) => private static bool IsFreeDay(DateOnly date, List<SchoolHoliday> schoolHolidays, HashSet<DateOnly> publicHolidayDates) =>
publicHolidayDates.Contains(date) || schoolHolidays.Any(h => date >= h.StartDate && date <= h.EndDate); publicHolidayDates.Contains(date) || schoolHolidays.Any(h => date >= h.StartDate && date <= h.EndDate);
@@ -629,6 +646,7 @@ public class WeekCellItem
public bool HasExam { get; private init; } public bool HasExam { get; private init; }
public bool IsLastBeforeExam { get; private init; } public bool IsLastBeforeExam { get; private init; }
public bool HasExperiment { get; private init; } public bool HasExperiment { get; private init; }
public bool HasUnhandledHomework { get; private init; }
public bool IsHoliday { get; private init; } public bool IsHoliday { get; private init; }
public string SupervisionLocation { get; private init; } = ""; public string SupervisionLocation { get; private init; } = "";
public bool HasSupervision => SupervisionLocation.Length > 0; public bool HasSupervision => SupervisionLocation.Length > 0;
@@ -667,13 +685,14 @@ public class WeekCellItem
public static WeekCellItem ForSlot(DayOfWeek day, int period, bool isToday, string subjectLabel, public static WeekCellItem ForSlot(DayOfWeek day, int period, bool isToday, string subjectLabel,
string groupName, string room, string topic, string colorHex, string holidayBadge, string groupName, string room, string topic, string colorHex, string holidayBadge,
bool hasExam, bool isLastBeforeExam, bool hasExperiment, Guid groupId, bool isHoliday) => new() bool hasExam, bool isLastBeforeExam, bool hasExperiment, Guid groupId, bool isHoliday,
bool hasUnhandledHomework = false) => new()
{ {
Weekday = day, PeriodNumber = period, IsAssigned = true, IsToday = isToday, Weekday = day, PeriodNumber = period, IsAssigned = true, IsToday = isToday,
SubjectLabel = subjectLabel, GroupName = groupName, Room = room, Topic = topic, SubjectLabel = subjectLabel, GroupName = groupName, Room = room, Topic = topic,
ColorHex = colorHex, HolidayBadge = holidayBadge, HasExam = hasExam, ColorHex = colorHex, HolidayBadge = holidayBadge, HasExam = hasExam,
IsLastBeforeExam = isLastBeforeExam, HasExperiment = hasExperiment, GroupId = groupId, IsLastBeforeExam = isLastBeforeExam, HasExperiment = hasExperiment, GroupId = groupId,
IsHoliday = isHoliday, IsHoliday = isHoliday, HasUnhandledHomework = hasUnhandledHomework,
}; };
public static WeekCellItem ForSubstitutionLesson(DayOfWeek day, int period, bool isToday, SubstitutionEntry entry) => new() public static WeekCellItem ForSubstitutionLesson(DayOfWeek day, int period, bool isToday, SubstitutionEntry entry) => new()
@@ -737,12 +756,14 @@ public class TodayLessonItem
public bool HasLessonTopic => !string.IsNullOrWhiteSpace(LessonTopic); public bool HasLessonTopic => !string.IsNullOrWhiteSpace(LessonTopic);
public bool HasExam => ExamTitle is not null; public bool HasExam => ExamTitle is not null;
public bool HasGroupId => GroupId != Guid.Empty; public bool HasGroupId => GroupId != Guid.Empty;
public bool HasUnhandledHomework { get; private init; }
public TodayLessonItem(Guid groupId, int periodNumber, string groupName, string room, public TodayLessonItem(Guid groupId, int periodNumber, string groupName, string room,
string colorHex, string? lessonTopic, string? examTitle) string colorHex, string? lessonTopic, string? examTitle, bool hasUnhandledHomework = false)
{ {
GroupId = groupId; PeriodNumber = periodNumber; GroupName = groupName; Room = room; GroupId = groupId; PeriodNumber = periodNumber; GroupName = groupName; Room = room;
ColorHex = colorHex; LessonTopic = lessonTopic; ExamTitle = examTitle; ColorHex = colorHex; LessonTopic = lessonTopic; ExamTitle = examTitle;
HasUnhandledHomework = hasUnhandledHomework;
} }
public static TodayLessonItem ForSubstitution(int periodNumber, SubstitutionEntry entry) => new( public static TodayLessonItem ForSubstitution(int periodNumber, SubstitutionEntry entry) => new(
@@ -126,6 +126,12 @@
<StackPanel Spacing="4"> <StackPanel Spacing="4">
<TextBlock Text="Hausaufgabe" FontSize="12" Opacity="0.7"/> <TextBlock Text="Hausaufgabe" FontSize="12" Opacity="0.7"/>
<TextBox Text="{Binding Homework}" AcceptsReturn="True" Height="48" TextWrapping="Wrap"/> <TextBox Text="{Binding Homework}" AcceptsReturn="True" Height="48" TextWrapping="Wrap"/>
<StackPanel Orientation="Horizontal" Spacing="16" IsVisible="{Binding HasHomeworkText}">
<CheckBox Content="Kontrolliert" IsChecked="{Binding HomeworkChecked}"
ToolTip.Tip="Hausaufgabe wurde in einer Folgestunde besprochen — blendet das Stundenplan-Badge aus."/>
<CheckBox Content="Nicht kontrollieren" IsChecked="{Binding HomeworkCheckDismissed}"
ToolTip.Tip="Erinnerung bewusst abschalten, ohne eine Kontrolle vorzutäuschen."/>
</StackPanel>
</StackPanel> </StackPanel>
<StackPanel Spacing="4"> <StackPanel Spacing="4">
@@ -96,6 +96,9 @@
TextWrapping="Wrap" IsVisible="{Binding HasLessonTopic}"/> TextWrapping="Wrap" IsVisible="{Binding HasLessonTopic}"/>
<TextBlock Text="{Binding ExamTitle}" FontSize="11" Foreground="#D85A30" FontWeight="SemiBold" <TextBlock Text="{Binding ExamTitle}" FontSize="11" Foreground="#D85A30" FontWeight="SemiBold"
IsVisible="{Binding HasExam}"/> IsVisible="{Binding HasExam}"/>
<TextBlock Text="📓 Hausaufgabe aus letzter Stunde noch nicht kontrolliert" FontSize="11"
Foreground="#8E6C00" FontWeight="SemiBold"
IsVisible="{Binding HasUnhandledHomework}"/>
</StackPanel> </StackPanel>
<Button Grid.Column="3" Content="Zur Lerngruppe" FontSize="11" Padding="9,4" <Button Grid.Column="3" Content="Zur Lerngruppe" FontSize="11" Padding="9,4"
VerticalAlignment="Center" IsVisible="{Binding HasGroupId}" VerticalAlignment="Center" IsVisible="{Binding HasGroupId}"
@@ -196,6 +199,8 @@
<TextBlock Text="📝" FontSize="11" IsVisible="{Binding HasExam}" ToolTip.Tip="Klausur"/> <TextBlock Text="📝" FontSize="11" IsVisible="{Binding HasExam}" ToolTip.Tip="Klausur"/>
<TextBlock Text="⏰" FontSize="11" IsVisible="{Binding IsLastBeforeExam}" ToolTip.Tip="Letzte Stunde vor der Klausur"/> <TextBlock Text="⏰" FontSize="11" IsVisible="{Binding IsLastBeforeExam}" ToolTip.Tip="Letzte Stunde vor der Klausur"/>
<TextBlock Text="🧪" FontSize="11" IsVisible="{Binding HasExperiment}" ToolTip.Tip="Experiment geplant"/> <TextBlock Text="🧪" FontSize="11" IsVisible="{Binding HasExperiment}" ToolTip.Tip="Experiment geplant"/>
<TextBlock Text="📓" FontSize="11" IsVisible="{Binding HasUnhandledHomework}"
ToolTip.Tip="Hausaufgabe aus letzter Stunde noch nicht kontrolliert"/>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</Button> </Button>
+23 -5
View File
@@ -723,11 +723,29 @@ Ausdrücklich als Idee für später festgehalten, **nicht** jetzt umsetzen:
Rückfrage an die Lehrkraft?). Muss überdacht werden, bevor das gebaut wird. Rückfrage an die Lehrkraft?). Muss überdacht werden, bevor das gebaut wird.
- [ ] **4.5.3** Aus diesem Lesson-Viewer heraus weiter verzweigen können: in die Zeugnisnote/ - [ ] **4.5.3** Aus diesem Lesson-Viewer heraus weiter verzweigen können: in die Zeugnisnote/
Bewertung der Gruppe, und in die Schnelldialoge für Mitarbeit sowie Anwesenheit/Hausaufgaben. Bewertung der Gruppe, und in die Schnelldialoge für Mitarbeit sowie Anwesenheit/Hausaufgaben.
- [ ] **4.5.4** Badges am Stundenplan/Lesson-Viewer, die anzeigen, ob eine Stunde bereits - [x] **4.5.4** Badges am Stundenplan, die anzeigen, ob eine Hausaufgabe aus der letzten Stunde
"kontrolliert" ist — u.a. ob in der vorherigen Stunde Hausaufgaben erteilt wurden und ob noch nicht kontrolliert ist.
diese noch nicht kontrolliert sind. Muss sich pro Fall abschalten lassen: manchmal wird eine
Hausaufgabe bewusst nicht kontrolliert, ohne dass bis Schuljahresende ständig daran erinnert **Umsetzung:** `Lesson` bekam zwei neue Felder, `HomeworkChecked`/`HomeworkCheckDismissed`
werden soll. (LiteDB füllt sie für bestehende Datensätze automatisch mit `false`, keine Migration nötig).
`TimetableViewModel.HasUnhandledHomework(groupId, date)` sucht die letzte vor `date`
liegende Lesson dieser Gruppe (`ILessonRepository.GetByGroupAndRange`, 120-Tage-Fenster
wegen möglicher Ferienlücken) und meldet `true`, wenn diese eine nicht-leere `Homework`
hat und weder `HomeworkChecked` noch `HomeworkCheckDismissed` gesetzt ist. Bewusst nur die
unmittelbar vorherige Lesson, nicht die gesamte Historie — sobald eine neuere Stunde
stattfindet, ist eine noch ältere offene Hausaufgabe nicht mehr das, worauf sich "letzte
Stunde" bezieht. 📓-Badge dafür sowohl in der Tagesliste als auch im Wochenraster des
"Heute"-Tabs (`TodayLessonItem.HasUnhandledHomework`/`WeekCellItem.HasUnhandledHomework`).
**Abweichung vom ursprünglichen Vorschlag** ("Klick-Toggle direkt am Badge"): jede
Wochenraster-Zelle ist bereits ein klickbarer `Button` (Navigation zur Lerngruppe) —
ein zweites klickbares Element im selben Badge hätte in Avalonia einen verschachtelten
Button-in-Button ergeben, mit unsicherem Klick-Routing. Stattdessen leben die beiden
Checkboxen "Kontrolliert"/"Nicht kontrollieren" (gegenseitig exklusiv,
`LessonDialogViewModel.HomeworkChecked`/`HomeworkCheckDismissed`) direkt im Verlaufsplan-
Editor neben dem Hausaufgabenfeld — genau dort, wo `Homework` ohnehin schon gepflegt wird.
Klick auf die Stunde im Stundenplan navigiert wie gehabt zur Lerngruppe/Planung, von dort
ist die betroffene (vorherige) Stunde ein Klick entfernt.
- [ ] **4.5.5** Stunden aus dieser Ansicht heraus verschieben können, wenn kurzfristig etwas - [ ] **4.5.5** Stunden aus dieser Ansicht heraus verschieben können, wenn kurzfristig etwas
dazwischenkommt. dazwischenkommt.
- [ ] **4.5.6** Bei Doppelstunden (90-Minuten-Planung laut Verlaufsplan, 4.2.2) den Inhalt - [ ] **4.5.6** Bei Doppelstunden (90-Minuten-Planung laut Verlaufsplan, 4.2.2) den Inhalt