Stundenplan: Dropdown für Unterrichtsansicht/Sitzplan/Planung/Planungsviewer

Die zwei Buttons in der "Heute"-Tagesliste mit teils vom Lesson-Status
abhängiger Doppelbedeutung ("Verlaufsplan ansehen"/"Zur Lerngruppe") waren
unklar. Ersetzt durch ein Dropdown mit vier ausdrücklich benannten Zielen;
drei nutzen bestehende Commands, "Sitzplan" (Sitzpläne-Tab) ist neu und
bisher nur indirekt über den Unterrichtsmodus erreichbar gewesen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-27 17:03:11 +02:00
co-authored by Claude Sonnet 5
parent 442e3d5e3d
commit cd08bbbbc4
5 changed files with 136 additions and 11 deletions
@@ -1177,3 +1177,45 @@ public sealed class TimetableViewModelTests
Assert.False(vm.HasUntisMismatch); Assert.False(vm.HasUntisMismatch);
} }
} }
/// Nutzer-Feedback: unklar, wie man aus der "Heute"-Tagesliste zwischen Unterrichtsansicht,
/// Sitzplan, Planung und Planungsviewer wechselt — TodayLessonItem.DestinationOptions ersetzt die
/// bisherigen zwei Buttons durch ein Dropdown mit ausdrücklich benannten Zielen.
public sealed class TodayLessonItemDestinationOptionsTests
{
[Fact]
public void MitLesson_BietetAlleVierZiele()
{
var lesson = new Lesson { Topic = "Redox" };
var item = new TodayLessonItem(Guid.NewGuid(), 3, "Q1 Chemie", "R204",
"#4C8DFF", "Redox", null, lesson: lesson);
Assert.True(item.HasDestinationOptions);
Assert.Equal(
[
TimetableLessonDestination.TeachingMode, TimetableLessonDestination.Viewer,
TimetableLessonDestination.SeatingPlan, TimetableLessonDestination.Planning,
],
item.DestinationOptions.Select(o => o.Kind));
}
[Fact]
public void OhneLesson_BietetNurSitzplanUndPlanung()
{
var item = new TodayLessonItem(Guid.NewGuid(), 3, "Q1 Chemie", "R204",
"#4C8DFF", null, null);
Assert.Equal(
[TimetableLessonDestination.SeatingPlan, TimetableLessonDestination.Planning],
item.DestinationOptions.Select(o => o.Kind));
}
[Fact]
public void OhneGroupId_BietetKeineZiele()
{
var item = TodayLessonItem.ForSubstitution(3, new SubstitutionEntry { GroupId = null, Description = "Vertretung" });
Assert.False(item.HasDestinationOptions);
Assert.Empty(item.DestinationOptions);
}
}
@@ -900,6 +900,9 @@ public class UpcomingExamItem(DateOnly date, string groupName, string title)
public string Title { get; } = title; public string Title { get; } = title;
} }
public enum TimetableLessonDestination { TeachingMode, Viewer, SeatingPlan, Planning }
public sealed record TimetableDestinationOption(TimetableLessonDestination Kind, string Label);
public class TodayLessonItem public class TodayLessonItem
{ {
public Guid GroupId { get; private init; } public Guid GroupId { get; private init; }
@@ -922,6 +925,33 @@ public class TodayLessonItem
public bool HasLesson => Lesson is not null; public bool HasLesson => Lesson is not null;
public string OpenButtonLabel => Lesson is not null ? "Verlaufsplan ansehen" : "Zur Lerngruppe"; 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, 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) Lesson? lesson = null)
@@ -111,17 +111,24 @@
Foreground="#8E6C00" FontWeight="SemiBold" Foreground="#8E6C00" FontWeight="SemiBold"
IsVisible="{Binding HasUnhandledHomework}"/> IsVisible="{Binding HasUnhandledHomework}"/>
</StackPanel> </StackPanel>
<StackPanel Grid.Column="3" Orientation="Horizontal" Spacing="6" VerticalAlignment="Center"> <!-- Nutzer-Feedback: unklar, wie man aus der Tagesliste zwischen
<Button Content="▶ Unterricht" FontSize="11" Padding="9,4" Unterrichtsansicht/Sitzplan/Planung/Planungsviewer wechselt — jetzt
IsVisible="{Binding HasLesson}" ein Dropdown mit ausdrücklich benannten Zielen statt zweier Buttons
ToolTip.Tip="Unterrichtsmodus: Verlaufsplan und Sitzplan mit Schnellbewertung auf einem Bildschirm." mit vom Lesson-Status abhängiger Doppelbedeutung. Wiring/Routing in
Command="{Binding $parent[ItemsControl].((vm:TimetableViewModel)DataContext).StartTeachingModeCommand}" TimetableView.axaml.cs (SelectionChanged setzt die Auswahl danach
CommandParameter="{Binding}"/> bewusst zurück auf null — Menü-Charakter, keine dauerhafte
<Button Content="{Binding OpenButtonLabel}" FontSize="11" Padding="9,4" Auswahl). -->
IsVisible="{Binding HasGroupId}" <ComboBox Grid.Column="3" FontSize="11" MinWidth="150" VerticalAlignment="Center"
Command="{Binding $parent[ItemsControl].((vm:TimetableViewModel)DataContext).OpenTodayLessonCommand}" IsVisible="{Binding HasDestinationOptions}"
CommandParameter="{Binding}"/> PlaceholderText="Öffnen ▾"
</StackPanel> ItemsSource="{Binding DestinationOptions}"
SelectionChanged="OnLessonDestinationSelected">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:TimetableDestinationOption">
<TextBlock Text="{Binding Label}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid> </Grid>
</Border> </Border>
</DataTemplate> </DataTemplate>
@@ -2,6 +2,7 @@ using Avalonia.Controls;
using LehrerApp.Core.Interfaces; using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Models; using LehrerApp.Core.Models;
using LehrerApp.Core.Services; using LehrerApp.Core.Services;
using LehrerApp.Desktop.ViewModels;
using LehrerApp.Desktop.ViewModels.Planning; using LehrerApp.Desktop.ViewModels.Planning;
using LehrerApp.Desktop.ViewModels.Groups; using LehrerApp.Desktop.ViewModels.Groups;
using LehrerApp.Desktop.Views.Groups; using LehrerApp.Desktop.Views.Groups;
@@ -27,6 +28,39 @@ 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)
{
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;
switch (option.Kind)
{
case TimetableLessonDestination.TeachingMode:
vm.StartTeachingModeCommand.Execute(item);
break;
case TimetableLessonDestination.Viewer:
vm.OpenTodayLessonCommand.Execute(item);
break;
case TimetableLessonDestination.Planning:
vm.OpenGroupCommand.Execute(item.GroupId);
break;
case TimetableLessonDestination.SeatingPlan:
App.Services.GetRequiredService<MainWindowViewModel>()
.NavigateToGroupDetail(item.GroupId, 2);
break;
}
}
private async Task ShowWebUntisTimetableDialog() private async Task ShowWebUntisTimetableDialog()
{ {
var owner = TopLevel.GetTopLevel(this) as Window; var owner = TopLevel.GetTopLevel(this) as Window;
+12
View File
@@ -1918,6 +1918,18 @@ folgenden Punkte gehören direkt in `LehrerApp.Desktop`:
den vollen Verlaufsplan-Editor öffnen müssen. Neue kleine den vollen Verlaufsplan-Editor öffnen müssen. Neue kleine
`TeachingModeHomeworkViewModel : ObservableObject`, da `TeachingModeViewModel` selbst (wie `TeachingModeHomeworkViewModel : ObservableObject`, da `TeachingModeViewModel` selbst (wie
`LessonViewerViewModel`) keine Bindable-Basisklasse hat. `LessonViewerViewModel`) keine Bindable-Basisklasse hat.
- [x] **4.5.24** Dropdown in der "Heute"-Tagesliste für Unterrichtsansicht/Sitzplan/Planung/
Planungsviewer (August 2026, Nutzer-Feedback): unklar, wie man aus dem Stundenplan zwischen
diesen vier Ansichten wechselt — bisher zwei Buttons mit teils vom Lesson-Status abhängiger
Doppelbedeutung ("Verlaufsplan ansehen"/"Zur Lerngruppe"). Ersetzt durch eine `ComboBox` pro
Zeile mit vier ausdrücklich benannten Zielen (`TodayLessonItem.DestinationOptions`,
`TimetableLessonDestination`-Enum) — Unterrichtsansicht/Planungsviewer nur wenn für den Slot
schon eine `Lesson` existiert, Sitzplan/Planung immer. Menü-Charakter: `SelectionChanged` in
`TimetableView.axaml.cs` setzt `SelectedItem` nach jeder Auswahl bewusst auf `null` zurück,
statt die Auswahl dauerhaft anzuzeigen. Drei der vier Ziele nutzen unverändert bestehende
`TimetableViewModel`-Commands (`StartTeachingModeCommand`/`OpenTodayLessonCommand`/
`OpenGroupCommand` — letzterer navigiert bereits zum Planung-Tab, Index 6); "Sitzplan" ist
neu (Sitzpläne-Tab, Index 2 — bisher nur indirekt über den Unterrichtsmodus erreichbar).
**Wichtige Abweichung von der ursprünglichen Planung (5.2):** Vor der Umsetzung zeigte sich, **Wichtige Abweichung von der ursprünglichen Planung (5.2):** Vor der Umsetzung zeigte sich,
dass 5.2 wie ursprünglich beschrieben eine zweite, parallele Fehlzeiten-Erfassung neben dem dass 5.2 wie ursprünglich beschrieben eine zweite, parallele Fehlzeiten-Erfassung neben dem