Stundenplan: Popup-Menü im Wochenraster statt Dropdown in der Tagesliste

Nutzer-Feedback: die Tagesliste-Buttons waren schon in Ordnung, das Problem
lag im Wochenraster darüber — ein Klick auf eine Stunden-Kachel führt dort
entweder in den Planungsviewer oder zur Einheitenplanung, ohne dass von
außen erkennbar wäre welches Ziel man bekommt. Tagesliste auf den
ursprünglichen Stand zurückgesetzt.

Neu: ein kleiner "⋮"-Button pro Kachel öffnet ein Popup-Menü mit vier
ausdrücklich benannten Zielen (Unterrichtsansicht/Planungsviewer/Sitzplan/
Planung). MenuFlyout statt ComboBox — dabei verstanden, dass ein
$parent[ItemsControl]-Vorfahrenpfad im Flyout nicht funktioniert, eine
normale {Binding} über die DataContext-Vererbung aber sehr wohl. Der
Direktklick springt jetzt außerdem "einheitlicher": bei einer Lesson
während der eigentlichen Unterrichtszeit direkt in den Unterrichtsmodus
statt in den Viewer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-27 23:21:49 +02:00
co-authored by Claude Sonnet 5
parent cd08bbbbc4
commit 229ef79e75
5 changed files with 275 additions and 135 deletions
@@ -65,6 +65,7 @@ public partial class TimetableViewModel : ObservableObject
private readonly ISubstitutionEntryRepository _substitutions;
private readonly IUntisSlotMappingRepository _untisMappings;
private readonly WebUntisSettingsService _untisSettings;
private readonly PeriodScheduleService _periodSchedule;
private readonly SchoolWeatherService? _schoolWeather;
private readonly SemaphoreSlim _weatherGate = new(1, 1);
private WeatherSnapshot? _weatherSnapshot;
@@ -114,13 +115,14 @@ public partial class TimetableViewModel : ObservableObject
PublicHolidayService publicHolidays, SchoolYearService schoolYear,
ISupervisionDutyRepository supervisionDuties, ISubstitutionEntryRepository substitutions,
IUntisSlotMappingRepository untisMappings, WebUntisSettingsService untisSettings,
SchoolWeatherService? schoolWeather = null)
PeriodScheduleService periodSchedule, SchoolWeatherService? schoolWeather = null)
{
_slots = slots; _groups = groups; _subjects = subjects; _lessons = lessons; _exams = exams;
_schoolHolidays = schoolHolidays; _calendarSettings = calendarSettings;
_publicHolidays = publicHolidays; _schoolYear = schoolYear;
_supervisionDuties = supervisionDuties; _substitutions = substitutions;
_untisMappings = untisMappings; _untisSettings = untisSettings;
_periodSchedule = periodSchedule;
_schoolWeather = schoolWeather;
Load();
}
@@ -368,14 +370,41 @@ public partial class TimetableViewModel : ObservableObject
await OnOpenTeachingMode(lesson);
}
/// Nutzer-Feedback (zweite Runde): der Direktklick auf eine Wochenraster-Kachel soll
/// "einheitlicher" springen. Existiert eine Lesson UND ist gerade (heute, mit Toleranz vor/
/// nach der Stunde) Unterrichtszeit, geht es direkt in den Unterrichtsmodus — sonst wie
/// bisher in den (schreibgeschützten) Planungsviewer bzw., ohne Lesson, zur Einheitenplanung
/// der Gruppe. Das Popup-Menü (siehe TimetableView.axaml, MenuFlyout je Kachel) bietet
/// daneben immer alle vier Ziele explizit an, unabhängig von dieser Automatik.
[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);
if (item.Lesson is { } lesson)
{
if (IsAroundTeachingTime(lesson.Date, item.PeriodNumber) && OnOpenTeachingMode is not null)
await OnOpenTeachingMode(lesson);
else if (OnOpenLessonViewer is not null) await OnOpenLessonViewer(lesson);
}
else OnNavigateToGroup?.Invoke(item.GroupId);
}
/// Bewusst mit Toleranz vor/nach der eingetragenen Stundenzeit (siehe
/// <see cref="PeriodScheduleService"/>, "Stundenraster" in den Einstellungen) statt exakt an
/// Start-/Endzeit gebunden — man klickt auch kurz vor Stundenbeginn oder in einer kurzen
/// Verzögerung danach noch typischerweise in Unterrichtsabsicht. Ohne konfiguriertes
/// Stundenraster oder an einem anderen Tag als heute bleibt es beim Planungsviewer.
/// Nimmt bewusst das Datum der Lesson selbst statt WeekCellItem.Date entgegen — Date ist dort
/// nur bei Kopfzeilen (WeekdayHeader) gesetzt, nicht bei regulären Stunden-Kacheln (ForSlot).
private static readonly TimeSpan TeachingTimeTolerance = TimeSpan.FromMinutes(10);
private bool IsAroundTeachingTime(DateOnly lessonDate, int periodNumber)
{
if (lessonDate != DateOnly.FromDateTime(DateTime.Today)) return false;
if (_periodSchedule.GetTimes(periodNumber) is not { } times) return false;
var now = TimeOnly.FromDateTime(DateTime.Now);
return now >= times.Start.Add(-TeachingTimeTolerance) && now <= times.End.Add(TeachingTimeTolerance);
}
[RelayCommand]
private void OpenSettings() => OnNavigateToSettings?.Invoke(SettingsTab.Holidays);
@@ -784,9 +813,11 @@ public partial class WeekCellItem : ObservableObject
public bool HasSupervision => SupervisionLocation.Length > 0;
public bool HasRoom => !string.IsNullOrWhiteSpace(Room);
public bool HasTopic => !string.IsNullOrWhiteSpace(Topic);
public bool HasGroupId => GroupId != Guid.Empty;
/// 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 bool HasLesson => Lesson is not null;
public string OpenButtonLabel => Lesson is not null ? "Verlaufsplan ansehen" : "Zur Lerngruppe";
[ObservableProperty] private string _weatherSymbol = "";
[ObservableProperty] private string _weatherTooltip = "";
@@ -925,33 +956,6 @@ public class TodayLessonItem
public bool HasLesson => Lesson is not null;
public string OpenButtonLabel => Lesson is not null ? "Verlaufsplan ansehen" : "Zur Lerngruppe";
/// Nutzer-Feedback: es war unklar, wie man aus der "Heute"-Tagesliste zwischen
/// Unterrichtsansicht, Sitzplan, Planung und Planungsviewer wechselt — bisher zwei Buttons
/// mit unterschiedlicher, teils vom Lesson-Status abhängiger Bedeutung
/// ("Verlaufsplan ansehen"/"Zur Lerngruppe"). Ein Dropdown mit ausdrücklich benannten Zielen
/// statt dessen (Wiring/Routing in TimetableView.axaml.cs). Unterrichtsansicht/Planungsviewer
/// brauchen eine existierende Lesson, Sitzplan/Planung sind auch ohne bereits möglich (Planung
/// ist ohnehin der Ort, an dem man eine Lesson für den Slot erst anlegt).
public IReadOnlyList<TimetableDestinationOption> DestinationOptions
{
get
{
var options = new List<TimetableDestinationOption>();
if (HasLesson)
{
options.Add(new(TimetableLessonDestination.TeachingMode, "▶ Unterrichtsansicht"));
options.Add(new(TimetableLessonDestination.Viewer, "📋 Planungsviewer"));
}
if (HasGroupId)
{
options.Add(new(TimetableLessonDestination.SeatingPlan, "🪑 Sitzplan"));
options.Add(new(TimetableLessonDestination.Planning, "✏️ Planung"));
}
return options;
}
}
public bool HasDestinationOptions => DestinationOptions.Count > 0;
public TodayLessonItem(Guid groupId, int periodNumber, string groupName, string room,
string colorHex, string? lessonTopic, string? examTitle, bool hasUnhandledHomework = false,
Lesson? lesson = null)