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:
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user