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
@@ -21,6 +21,21 @@
<Style Selector="Border.supervisioncell.substitution">
<Setter Property="Background" Value="#8E24AA"/>
</Style>
<Style Selector="Button.weekCellMenuTrigger">
<Setter Property="Width" Value="18"/>
<Setter Property="Height" Value="16"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="2"/>
<Setter Property="Background" Value="#33000000"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="11"/>
<Setter Property="CornerRadius" Value="4"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
</Style>
<Style Selector="Button.weekCellMenuTrigger:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="#55000000"/>
</Style>
</UserControl.Styles>
<Grid RowDefinitions="Auto,Auto,*">
@@ -111,24 +126,17 @@
Foreground="#8E6C00" FontWeight="SemiBold"
IsVisible="{Binding HasUnhandledHomework}"/>
</StackPanel>
<!-- Nutzer-Feedback: unklar, wie man aus der Tagesliste zwischen
Unterrichtsansicht/Sitzplan/Planung/Planungsviewer wechselt — jetzt
ein Dropdown mit ausdrücklich benannten Zielen statt zweier Buttons
mit vom Lesson-Status abhängiger Doppelbedeutung. Wiring/Routing in
TimetableView.axaml.cs (SelectionChanged setzt die Auswahl danach
bewusst zurück auf null — Menü-Charakter, keine dauerhafte
Auswahl). -->
<ComboBox Grid.Column="3" FontSize="11" MinWidth="150" VerticalAlignment="Center"
IsVisible="{Binding HasDestinationOptions}"
PlaceholderText="Öffnen ▾"
ItemsSource="{Binding DestinationOptions}"
SelectionChanged="OnLessonDestinationSelected">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:TimetableDestinationOption">
<TextBlock Text="{Binding Label}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<StackPanel Grid.Column="3" Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
<Button Content="▶ Unterricht" FontSize="11" Padding="9,4"
IsVisible="{Binding HasLesson}"
ToolTip.Tip="Unterrichtsmodus: Verlaufsplan und Sitzplan mit Schnellbewertung auf einem Bildschirm."
Command="{Binding $parent[ItemsControl].((vm:TimetableViewModel)DataContext).StartTeachingModeCommand}"
CommandParameter="{Binding}"/>
<Button Content="{Binding OpenButtonLabel}" FontSize="11" Padding="9,4"
IsVisible="{Binding HasGroupId}"
Command="{Binding $parent[ItemsControl].((vm:TimetableViewModel)DataContext).OpenTodayLessonCommand}"
CommandParameter="{Binding}"/>
</StackPanel>
</Grid>
</Border>
</DataTemplate>
@@ -233,36 +241,65 @@
</Border>
<Border Classes="timetablecell" Classes.assigned="{Binding IsAssigned}"
CornerRadius="6" IsVisible="{Binding IsSlotCell}">
<Button Background="{Binding ColorHex}" IsVisible="{Binding IsAssigned}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch" CornerRadius="6" Padding="6"
Command="{Binding $parent[ItemsControl;1].((vm:TimetableViewModel)DataContext).OpenWeekCellCommand}"
CommandParameter="{Binding}">
<StackPanel Spacing="1">
<TextBlock FontSize="11" FontWeight="Bold" Foreground="White" TextWrapping="Wrap">
<Run Text="{Binding SubjectLabel}"/><Run Text=" · "/><Run Text="{Binding GroupName}"/>
</TextBlock>
<TextBlock Text="{Binding Room}" FontSize="10" Foreground="White" Opacity="0.9"
IsVisible="{Binding HasRoom}"/>
<TextBlock Text="{Binding Topic}" FontSize="10" Foreground="White" Opacity="0.8"
TextWrapping="Wrap" IsVisible="{Binding HasTopic}"/>
<TextBlock Text="Ferien" FontSize="10" Foreground="White" FontWeight="SemiBold"
Opacity="0.9" IsVisible="{Binding IsHoliday}" Margin="0,2,0,0"/>
<TextBlock Text="Ausfall" FontSize="10" Foreground="White" FontWeight="SemiBold"
Opacity="0.9" IsVisible="{Binding IsCancelled}" Margin="0,2,0,0"/>
<StackPanel Orientation="Horizontal" Spacing="3" Margin="0,2,0,0"
IsVisible="{Binding !IsHoliday}">
<Border Background="#B71C1C" CornerRadius="7" Padding="4,0" IsVisible="{Binding HasHolidayBadge}">
<TextBlock Text="{Binding HolidayBadge}" FontSize="9" FontWeight="Bold" Foreground="White"/>
</Border>
<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 HasExperiment}" ToolTip.Tip="Experiment geplant"/>
<TextBlock Text="📓" FontSize="11" IsVisible="{Binding HasUnhandledHomework}"
ToolTip.Tip="Hausaufgabe aus letzter Stunde noch nicht kontrolliert"/>
<Panel IsVisible="{Binding IsAssigned}">
<Button Background="{Binding ColorHex}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch" CornerRadius="6" Padding="6"
Command="{Binding $parent[ItemsControl;1].((vm:TimetableViewModel)DataContext).OpenWeekCellCommand}"
CommandParameter="{Binding}">
<StackPanel Spacing="1">
<TextBlock FontSize="11" FontWeight="Bold" Foreground="White" TextWrapping="Wrap">
<Run Text="{Binding SubjectLabel}"/><Run Text=" · "/><Run Text="{Binding GroupName}"/>
</TextBlock>
<TextBlock Text="{Binding Room}" FontSize="10" Foreground="White" Opacity="0.9"
IsVisible="{Binding HasRoom}"/>
<TextBlock Text="{Binding Topic}" FontSize="10" Foreground="White" Opacity="0.8"
TextWrapping="Wrap" IsVisible="{Binding HasTopic}"/>
<TextBlock Text="Ferien" FontSize="10" Foreground="White" FontWeight="SemiBold"
Opacity="0.9" IsVisible="{Binding IsHoliday}" Margin="0,2,0,0"/>
<TextBlock Text="Ausfall" FontSize="10" Foreground="White" FontWeight="SemiBold"
Opacity="0.9" IsVisible="{Binding IsCancelled}" Margin="0,2,0,0"/>
<StackPanel Orientation="Horizontal" Spacing="3" Margin="0,2,0,0"
IsVisible="{Binding !IsHoliday}">
<Border Background="#B71C1C" CornerRadius="7" Padding="4,0" IsVisible="{Binding HasHolidayBadge}">
<TextBlock Text="{Binding HolidayBadge}" FontSize="9" FontWeight="Bold" Foreground="White"/>
</Border>
<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 HasExperiment}" ToolTip.Tip="Experiment geplant"/>
<TextBlock Text="📓" FontSize="11" IsVisible="{Binding HasUnhandledHomework}"
ToolTip.Tip="Hausaufgabe aus letzter Stunde noch nicht kontrolliert"/>
</StackPanel>
</StackPanel>
</StackPanel>
</Button>
</Button>
<!-- Nutzer-Feedback (zweite Runde): Popup-Menü statt Dropdown, hier
im Wochenraster statt in der Tagesliste (dort waren die
bisherigen zwei Buttons schon in Ordnung). Sibling-Button statt
verschachteltem Button/ContextMenu — Routing über
MenuItem.Click in TimetableView.axaml.cs, siehe Kommentar dort
zu DataContext-Vererbung vs. $parent-Vorfahrensuche im Flyout. -->
<Button Classes="weekCellMenuTrigger" Content="⋮"
HorizontalAlignment="Right" VerticalAlignment="Top"
IsVisible="{Binding HasGroupId}"
ToolTip.Tip="Weitere Ziele…">
<Button.Flyout>
<MenuFlyout Placement="BottomEdgeAlignedRight">
<MenuItem Header="▶ Unterrichtsansicht" IsVisible="{Binding HasLesson}"
Tag="{x:Static vm:TimetableLessonDestination.TeachingMode}"
Click="OnWeekCellMenuItemClick"/>
<MenuItem Header="📋 Planungsviewer" IsVisible="{Binding HasLesson}"
Tag="{x:Static vm:TimetableLessonDestination.Viewer}"
Click="OnWeekCellMenuItemClick"/>
<MenuItem Header="🪑 Sitzplan"
Tag="{x:Static vm:TimetableLessonDestination.SeatingPlan}"
Click="OnWeekCellMenuItemClick"/>
<MenuItem Header="✏️ Planung"
Tag="{x:Static vm:TimetableLessonDestination.Planning}"
Click="OnWeekCellMenuItemClick"/>
</MenuFlyout>
</Button.Flyout>
</Button>
</Panel>
</Border>
</Grid>
</DataTemplate>
@@ -1,4 +1,5 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Models;
using LehrerApp.Core.Services;
@@ -28,35 +29,42 @@ public partial class TimetableView : UserControl
}
}
/// Nutzer-Feedback: unklar, wie man aus der "Heute"-Tagesliste zwischen Unterrichtsansicht,
/// Sitzplan, Planung und Planungsviewer wechselt. Statt der bisherigen zwei Buttons mit
/// teils vom Lesson-Status abhängiger Doppelbedeutung ein Dropdown mit ausdrücklich benannten
/// Zielen (TodayLessonItem.DestinationOptions) — Menü-Charakter, die Auswahl wird danach
/// bewusst zurückgesetzt statt dauerhaft angezeigt. Drei der vier Ziele nutzen unverändert
/// die bestehenden TimetableViewModel-Commands, nur "Sitzplan" ist neu (bisher nur über den
/// Unterrichtsmodus erreichbar, siehe TODO.md 4.5.23-Nachtrag).
private void OnLessonDestinationSelected(object? sender, SelectionChangedEventArgs e)
/// Nutzer-Feedback (zweite Runde — die erste Fassung hatte das Problem an der falschen
/// Stelle gelöst, in der "Heute"-Tagesliste statt im Wochenraster): im Wochenraster oben
/// führt ein Klick auf eine Stunden-Kachel bislang entweder in den Planungsviewer oder zur
/// Einheitenplanung — je nachdem, ob schon eine Lesson existiert, ohne dass das von außen
/// erkennbar wäre. Popup-Menü (MenuFlyout, kein ComboBox mehr) mit allen vier Zielen als
/// Alternative zum Direktklick, siehe <see cref="TimetableViewModel.OpenWeekCellCommand"/>
/// für den "einheitlicheren" Standard-Klick (Unterrichtsansicht bei laufender Stunde, sonst
/// Planungsviewer, sonst Einheitenplanung). MenuItem.Click statt Command-Binding: ein
/// $parent[ItemsControl]-Vorfahrenpfad (wie beim Zeilen-Button) funktioniert innerhalb eines
/// Flyouts nicht zuverlässig, weil dessen Popup nicht im normalen visuellen Baum hängt (siehe
/// TODO.md-Nachtrag zum Klassenlehrer-Bereich) — DataContext-Vererbung (kein Pfad-Suchen,
/// nur der normale Eltern-Wert) funktioniert dort aber sehr wohl, deshalb liest der Handler
/// die Zelle über <c>((MenuItem)sender).DataContext</c> statt über eine Binding.
private async void OnWeekCellMenuItemClick(object? sender, RoutedEventArgs e)
{
if (sender is not ComboBox comboBox) return;
var item = comboBox.DataContext as TodayLessonItem;
var option = e.AddedItems.Count > 0 ? e.AddedItems[0] as TimetableDestinationOption : null;
comboBox.SelectedItem = null;
if (item is null || option is null || DataContext is not TimetableViewModel vm) return;
if (sender is not MenuItem { Tag: TimetableLessonDestination destination } menuItem) return;
if (menuItem.DataContext is not WeekCellItem cell) return;
if (DataContext is not TimetableViewModel vm) return;
switch (option.Kind)
switch (destination)
{
case TimetableLessonDestination.TeachingMode:
vm.StartTeachingModeCommand.Execute(item);
if (cell.Lesson is { } teachLesson && vm.OnOpenTeachingMode is not null)
await vm.OnOpenTeachingMode(teachLesson);
break;
case TimetableLessonDestination.Viewer:
vm.OpenTodayLessonCommand.Execute(item);
if (cell.Lesson is { } viewLesson && vm.OnOpenLessonViewer is not null)
await vm.OnOpenLessonViewer(viewLesson);
break;
case TimetableLessonDestination.Planning:
vm.OpenGroupCommand.Execute(item.GroupId);
if (cell.GroupId != Guid.Empty)
App.Services.GetRequiredService<MainWindowViewModel>().NavigateToGroupDetail(cell.GroupId, 6);
break;
case TimetableLessonDestination.SeatingPlan:
App.Services.GetRequiredService<MainWindowViewModel>()
.NavigateToGroupDetail(item.GroupId, 2);
if (cell.GroupId != Guid.Empty)
App.Services.GetRequiredService<MainWindowViewModel>().NavigateToGroupDetail(cell.GroupId, 2);
break;
}
}