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
@@ -197,7 +197,8 @@ public partial class TimetableViewModel : ObservableObject
var lesson = _lessons.GetByGroupAndDate(slot.GroupId, today).FirstOrDefault();
var exam = _exams.GetByGroup(slot.GroupId).FirstOrDefault(e => e.Date == today);
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 &&
@@ -331,7 +332,8 @@ public partial class TimetableViewModel : ObservableObject
subject?.ShortName is { Length: > 0 } sn ? sn : subject?.Name ?? "",
group?.Name ?? "?", slot.Room ?? "", lesson?.Topic ?? "",
colorHex, holidayBadge, hasExam, isLastBeforeExam,
MentionsExperiment(lesson), slot.GroupId, isHoliday));
MentionsExperiment(lesson), slot.GroupId, isHoliday,
HasUnhandledHomework(slot.GroupId, date)));
}
AddWeekSupervisionRowIfAny(period, dutiesByPeriod, substitutionsThisWeek, dateByWeekday);
}
@@ -392,6 +394,21 @@ public partial class TimetableViewModel : ObservableObject
p.Activity?.Contains("Experiment", StringComparison.OrdinalIgnoreCase) == 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>
private static bool IsFreeDay(DateOnly date, List<SchoolHoliday> schoolHolidays, HashSet<DateOnly> publicHolidayDates) =>
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 IsLastBeforeExam { get; private init; }
public bool HasExperiment { get; private init; }
public bool HasUnhandledHomework { get; private init; }
public bool IsHoliday { get; private init; }
public string SupervisionLocation { get; private init; } = "";
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,
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,
SubjectLabel = subjectLabel, GroupName = groupName, Room = room, Topic = topic,
ColorHex = colorHex, HolidayBadge = holidayBadge, HasExam = hasExam,
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()
@@ -737,12 +756,14 @@ public class TodayLessonItem
public bool HasLessonTopic => !string.IsNullOrWhiteSpace(LessonTopic);
public bool HasExam => ExamTitle is not null;
public bool HasGroupId => GroupId != Guid.Empty;
public bool HasUnhandledHomework { get; private init; }
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;
ColorHex = colorHex; LessonTopic = lessonTopic; ExamTitle = examTitle;
HasUnhandledHomework = hasUnhandledHomework;
}
public static TodayLessonItem ForSubstitution(int periodNumber, SubstitutionEntry entry) => new(