Vernetzung Stundenplan ↔ Unterrichtsplanung: Terminvorschlag, Lesson-Viewer-Sprung, Verzweigung (4.5.1–4.5.3)

4.5.1: Terminvorschlag für neue Stunden schlägt jetzt auch die Stundennummer
vor (bei Doppelstunden die erste Periode), nicht nur den Wochentag —
zieht über den bestehenden LessonNumber-Changed-Hook automatisch auch den
Stundenbeginn nach.

4.5.2 (teilweise): Klick auf eine Stunde im Stundenplan ("Heute"-Liste und
read-only Wochenraster) springt bei bereits vorhandener Lesson direkt in
den Verlaufsplan-Viewer statt nur grob zum Planung-Tab. Das Anlegen einer
neuen Lesson direkt aus dem Stundenplan bleibt bewusst offen (ungeklärte
Unit-Zuordnung, siehe TODO.md).

4.5.3: Der Lesson-Viewer hat jetzt Buttons "Zur Mitarbeit"/"Zu den Noten"
zum Weiterverzweigen in die Lerngruppe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 23:39:27 +02:00
co-authored by Claude Sonnet 5
parent 52a1d45539
commit 391628ae36
10 changed files with 280 additions and 31 deletions
@@ -90,6 +90,7 @@ public partial class TimetableViewModel : ObservableObject
public Action<Guid>? OnNavigateToGroup { get; set; }
public Func<Task>? OnAddSubstitution { get; set; }
public Action<SettingsTab>? OnNavigateToSettings { get; set; }
public Func<Lesson, Task>? OnOpenLessonViewer { get; set; }
public TimetableViewModel(ITimetableSlotRepository slots, IGroupRepository groups,
ISubjectRepository subjects, ILessonRepository lessons, IExamRepository exams,
@@ -198,7 +199,7 @@ public partial class TimetableViewModel : ObservableObject
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,
HasUnhandledHomework(slot.GroupId, today)));
HasUnhandledHomework(slot.GroupId, today), lesson));
}
foreach (var sub in substitutionsToday.Where(s => s.Kind == SubstitutionKind.Lesson &&
@@ -245,6 +246,27 @@ public partial class TimetableViewModel : ObservableObject
OnNavigateToGroup?.Invoke(groupId);
}
/// Springt aus dem Stundenplan direkt in den Verlaufsplan-Viewer der zugehörigen Lesson (4.5.2)
/// — sofern für den Slot schon eine Lesson existiert. Ohne Lesson (Slot laut Stundenplan belegt,
/// aber noch keine konkrete Stunde geplant) bleibt es bei der bisherigen, gröberen Navigation
/// zum Planung-Tab der Gruppe: eine neue Lesson direkt von hier aus anzulegen bräuchte eine
/// Antwort auf "welcher Unit wird sie zugeordnet", die bewusst noch offen ist (siehe TODO 4.5.2).
[RelayCommand]
private async Task OpenTodayLesson(TodayLessonItem? item)
{
if (item is null || item.GroupId == Guid.Empty) return;
if (item.Lesson is { } lesson && OnOpenLessonViewer is not null) await OnOpenLessonViewer(lesson);
else OnNavigateToGroup?.Invoke(item.GroupId);
}
[RelayCommand]
private async Task OpenWeekCell(WeekCellItem? item)
{
if (item is null || item.GroupId == Guid.Empty) return;
if (item.Lesson is { } lesson && OnOpenLessonViewer is not null) await OnOpenLessonViewer(lesson);
else OnNavigateToGroup?.Invoke(item.GroupId);
}
[RelayCommand]
private void OpenSettings() => OnNavigateToSettings?.Invoke(SettingsTab.Holidays);
@@ -333,7 +355,7 @@ public partial class TimetableViewModel : ObservableObject
group?.Name ?? "?", slot.Room ?? "", lesson?.Topic ?? "",
colorHex, holidayBadge, hasExam, isLastBeforeExam,
MentionsExperiment(lesson), slot.GroupId, isHoliday,
HasUnhandledHomework(slot.GroupId, date)));
HasUnhandledHomework(slot.GroupId, date), lesson));
}
AddWeekSupervisionRowIfAny(period, dutiesByPeriod, substitutionsThisWeek, dateByWeekday);
}
@@ -652,6 +674,10 @@ public class WeekCellItem
public bool HasSupervision => SupervisionLocation.Length > 0;
public bool HasRoom => !string.IsNullOrWhiteSpace(Room);
public bool HasTopic => !string.IsNullOrWhiteSpace(Topic);
/// Nur bei einer regulären, zugewiesenen Zelle mit bereits existierender Lesson gesetzt —
/// Grundlage für den Direktsprung in den Verlaufsplan-Viewer (4.5.2).
public Lesson? Lesson { get; private init; }
public string OpenButtonLabel => Lesson is not null ? "Verlaufsplan ansehen" : "Zur Lerngruppe";
public static WeekCellItem Corner() => new() { IsHeader = true };
@@ -686,13 +712,13 @@ 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,
bool hasUnhandledHomework = false) => new()
bool hasUnhandledHomework = false, Lesson? lesson = null) => 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, HasUnhandledHomework = hasUnhandledHomework,
IsHoliday = isHoliday, HasUnhandledHomework = hasUnhandledHomework, Lesson = lesson,
};
public static WeekCellItem ForSubstitutionLesson(DayOfWeek day, int period, bool isToday, SubstitutionEntry entry) => new()
@@ -757,13 +783,18 @@ public class TodayLessonItem
public bool HasExam => ExamTitle is not null;
public bool HasGroupId => GroupId != Guid.Empty;
public bool HasUnhandledHomework { get; private init; }
/// Nur gesetzt, wenn für diesen Slot/Tag bereits eine Lesson existiert — Grundlage für den
/// Direktsprung in den Verlaufsplan-Viewer (4.5.2).
public Lesson? Lesson { get; private init; }
public string OpenButtonLabel => Lesson is not null ? "Verlaufsplan ansehen" : "Zur Lerngruppe";
public TodayLessonItem(Guid groupId, int periodNumber, string groupName, string room,
string colorHex, string? lessonTopic, string? examTitle, bool hasUnhandledHomework = false)
string colorHex, string? lessonTopic, string? examTitle, bool hasUnhandledHomework = false,
Lesson? lesson = null)
{
GroupId = groupId; PeriodNumber = periodNumber; GroupName = groupName; Room = room;
ColorHex = colorHex; LessonTopic = lessonTopic; ExamTitle = examTitle;
HasUnhandledHomework = hasUnhandledHomework;
HasUnhandledHomework = hasUnhandledHomework; Lesson = lesson;
}
public static TodayLessonItem ForSubstitution(int periodNumber, SubstitutionEntry entry) => new(