feat: Unterrichtsmodus - konsolidierte Ansicht für die laufende Stunde
Verlaufsplan und Sitzplan bisher über mehrere Tabs verteilt, obwohl im Unterricht selbst beides gleichzeitig gebraucht wird. Neuer Button "▶ Unterricht" in der "Heute"-Tagesliste des Stundenplans (nur bei vorhandener Lesson) öffnet ein maximiertes Fenster: links der schreibgeschützte Verlaufsplan als kompakte Kartenliste, rechts der volle, unverändert wiederverwendete SeatingPlanTabView (Drag&Drop, Schnellbewertung, Situations-Tags, PDF-Export). TeachingModeViewModel baut auf dem bestehenden LessonViewerViewModel auf, dadurch kommen "Zur Mitarbeit"/"Zu den Noten" ohne Duplizierung mit. Neue SeatingPlanTabViewModel.SelectOrCreateSessionForLesson verknüpft die Mitarbeitssitzung automatisch mit der konkreten Stunde - anders als die bestehende EnsureTodaySession darf das hier sofort beim Start passieren, da durch den expliziten Klick für genau diese Stunde eindeutig feststeht, worum es geht (keine Geistersitzungs-Gefahr wie beim bloßen Tab-Öffnen). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -109,6 +109,32 @@ public partial class SeatingPlanTabViewModel : ObservableObject
|
||||
return option;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Für den Unterrichtsmodus (14.x): wählt eine bereits mit dieser Stunde verknüpfte Sitzung
|
||||
/// aus (angelegt z.B. über PlanningTabViewModel.CreateParticipationSession, 3.3.1) oder legt
|
||||
/// bei Bedarf eine neue, EXPLIZIT verknüpfte an (<see cref="ParticipationSession.LessonId"/>).
|
||||
/// Anders als <see cref="EnsureTodaySession"/> (anonyme "Sitzplan"-Sitzung, erst bei der
|
||||
/// ersten tatsächlichen Aktion) ist das hier bewusst sofort beim Start des Unterrichtsmodus
|
||||
/// erlaubt: welche Stunde gemeint ist, steht durch die explizite Auswahl der Lehrkraft
|
||||
/// (Klick auf "Unterrichtsmodus starten" für genau diese Stunde) bereits unzweideutig fest -
|
||||
/// keine Geistersitzungs-Gefahr wie beim bloßen Öffnen eines Tabs.
|
||||
/// </summary>
|
||||
public void SelectOrCreateSessionForLesson(Lesson lesson)
|
||||
{
|
||||
var existing = TodaySessions.FirstOrDefault(s => s.LessonId == lesson.Id);
|
||||
if (existing is not null) { SelectedSession = existing; return; }
|
||||
if (!IsEditable) return;
|
||||
|
||||
var created = new ParticipationSession
|
||||
{
|
||||
GroupId = _groupId, Date = lesson.Date, LessonId = lesson.Id, Comment = lesson.Topic,
|
||||
};
|
||||
_sessions.Save(created);
|
||||
var option = new ParticipationSessionOption(created);
|
||||
TodaySessions.Add(option);
|
||||
SelectedSession = option;
|
||||
}
|
||||
|
||||
partial void OnSelectedSessionChanged(ParticipationSessionOption? value) => RefreshSeatLessonData();
|
||||
|
||||
private void LoadStudentOptions()
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Core.Models;
|
||||
|
||||
namespace LehrerApp.Desktop.ViewModels.Groups;
|
||||
|
||||
/// <summary>
|
||||
/// Unterrichtsmodus (14.x): konsolidierte Ansicht für eine konkrete, heute stattfindende Stunde -
|
||||
/// Verlaufsplan und Sitzplan mit Schnellbewertung auf einem Bildschirm, statt zwischen den Tabs
|
||||
/// der Gruppe hin- und herzuspringen. Wird über TimetableViewModel.StartTeachingModeCommand aus
|
||||
/// der "Heute"-Tagesliste heraus gestartet.
|
||||
///
|
||||
/// Bewusst keine eigene DI-Registrierung als Singleton/Transient: wie LessonViewerViewModel wird
|
||||
/// diese ViewModel direkt vom Code-Behind konstruiert, das dafür nötige SeatingPlanTabViewModel
|
||||
/// aber weiterhin über DI aufgelöst (transient, siehe AppBootstrapper) und hier per Konstruktor
|
||||
/// entgegengenommen statt selbst aus App.Services zu ziehen - ViewModels greifen in diesem Code
|
||||
/// nicht selbst auf den Service-Container zu, das bleibt Aufgabe des Code-Behind.
|
||||
/// </summary>
|
||||
public class TeachingModeViewModel
|
||||
{
|
||||
public string GroupName { get; }
|
||||
public LessonViewerViewModel LessonInfo { get; }
|
||||
public SeatingPlanTabViewModel SeatingPlan { get; }
|
||||
|
||||
public TeachingModeViewModel(Lesson lesson, LearningGroup group,
|
||||
IAlternativeLessonPathRepository alternativePaths, SeatingPlanTabViewModel seatingPlan)
|
||||
{
|
||||
GroupName = group.Name;
|
||||
LessonInfo = new LessonViewerViewModel(lesson, alternativePaths);
|
||||
SeatingPlan = seatingPlan;
|
||||
SeatingPlan.Initialize(group.Id, !group.IsActive);
|
||||
SeatingPlan.SelectOrCreateSessionForLesson(lesson);
|
||||
}
|
||||
}
|
||||
@@ -91,6 +91,7 @@ public partial class TimetableViewModel : ObservableObject
|
||||
public Func<Task>? OnAddSubstitution { get; set; }
|
||||
public Action<SettingsTab>? OnNavigateToSettings { get; set; }
|
||||
public Func<Lesson, Task>? OnOpenLessonViewer { get; set; }
|
||||
public Func<Lesson, Task>? OnOpenTeachingMode { get; set; }
|
||||
|
||||
public TimetableViewModel(ITimetableSlotRepository slots, IGroupRepository groups,
|
||||
ISubjectRepository subjects, ILessonRepository lessons, IExamRepository exams,
|
||||
@@ -259,6 +260,16 @@ public partial class TimetableViewModel : ObservableObject
|
||||
else OnNavigateToGroup?.Invoke(item.GroupId);
|
||||
}
|
||||
|
||||
/// Startet den Unterrichtsmodus (14.x) für eine konkrete, heute stattfindende Stunde —
|
||||
/// bewusst nur aus der "Heute"-Tagesliste heraus erreichbar (nicht dem Wochenraster), da der
|
||||
/// Modus für das aktive Unterrichten HEUTE gedacht ist, nicht zum Durchblättern anderer Tage.
|
||||
[RelayCommand]
|
||||
private async Task StartTeachingMode(TodayLessonItem? item)
|
||||
{
|
||||
if (item?.Lesson is not { } lesson || OnOpenTeachingMode is null) return;
|
||||
await OnOpenTeachingMode(lesson);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task OpenWeekCell(WeekCellItem? item)
|
||||
{
|
||||
@@ -784,8 +795,9 @@ public class TodayLessonItem
|
||||
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).
|
||||
/// Direktsprung in den Verlaufsplan-Viewer (4.5.2) und den Unterrichtsmodus (14.x).
|
||||
public Lesson? Lesson { get; private init; }
|
||||
public bool HasLesson => Lesson is not null;
|
||||
public string OpenButtonLabel => Lesson is not null ? "Verlaufsplan ansehen" : "Zur Lerngruppe";
|
||||
|
||||
public TodayLessonItem(Guid groupId, int periodNumber, string groupName, string room,
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="clr-namespace:LehrerApp.Desktop.ViewModels.Groups"
|
||||
xmlns:views="clr-namespace:LehrerApp.Desktop.Views.Groups"
|
||||
x:Class="LehrerApp.Desktop.Views.Groups.TeachingModeWindow"
|
||||
x:DataType="vm:TeachingModeViewModel"
|
||||
Title="{Binding LessonInfo.Topic, StringFormat='Unterrichtsmodus · {0}'}"
|
||||
Width="1400" Height="850" MinWidth="1000" MinHeight="600"
|
||||
WindowState="Maximized" CanResize="True" WindowStartupLocation="CenterScreen">
|
||||
|
||||
<Grid RowDefinitions="Auto,*" Margin="20">
|
||||
<Grid Grid.Row="0" ColumnDefinitions="*,Auto" Margin="0,0,0,14">
|
||||
<StackPanel Grid.Column="0" Spacing="3">
|
||||
<TextBlock FontSize="20" FontWeight="SemiBold">
|
||||
<Run Text="{Binding GroupName}"/><Run Text=" · "/><Run Text="{Binding LessonInfo.Topic}"/>
|
||||
</TextBlock>
|
||||
<StackPanel Orientation="Horizontal" Spacing="16">
|
||||
<TextBlock FontSize="12" Opacity="0.65">
|
||||
<Run Text="Datum: "/><Run Text="{Binding LessonInfo.DateDisplay}"/>
|
||||
</TextBlock>
|
||||
<TextBlock FontSize="12" Opacity="0.65">
|
||||
<Run Text="Beginn: "/><Run Text="{Binding LessonInfo.StartTimeDisplay}"/>
|
||||
</TextBlock>
|
||||
<TextBlock FontSize="12" Opacity="0.65">
|
||||
<Run Text="Status: "/><Run Text="{Binding LessonInfo.StatusLabel}"/>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
|
||||
<Button Content="Zur Mitarbeit" Command="{Binding LessonInfo.NavigateToParticipationCommand}"
|
||||
ToolTip.Tip="Schließt den Unterrichtsmodus und springt zum Tab 'Mitarbeit' der Lerngruppe."/>
|
||||
<Button Content="Zu den Noten" Command="{Binding LessonInfo.NavigateToGradesCommand}"/>
|
||||
<Button Content="Unterrichtsmodus beenden" Click="OnClose" Margin="16,0,0,0"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1" ColumnDefinitions="360,16,*">
|
||||
<Border Grid.Column="0" BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
|
||||
BorderThickness="0,0,1,0" Padding="0,0,16,0">
|
||||
<ScrollViewer>
|
||||
<StackPanel Spacing="14">
|
||||
<TextBlock Text="Verlaufsplan" FontSize="14" FontWeight="SemiBold"/>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding LessonInfo.PhaseGroups}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:PhaseGroupViewItem">
|
||||
<StackPanel Margin="0,0,0,10">
|
||||
<StackPanel IsVisible="{Binding $parent[ItemsControl].((vm:LessonViewerViewModel)DataContext).HasAlternatives}">
|
||||
<TextBlock Text="{Binding Label}" FontSize="12" FontWeight="SemiBold" Opacity="0.75" Margin="0,6,0,2"/>
|
||||
<TextBlock Text="{Binding Description}" FontSize="11" Opacity="0.55" TextWrapping="Wrap" Margin="0,0,0,6"
|
||||
IsVisible="{Binding Description, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
</StackPanel>
|
||||
<ItemsControl ItemsSource="{Binding Phases}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:PhaseViewItem">
|
||||
<Border Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
||||
CornerRadius="6" Padding="10,8" Margin="0,0,0,6">
|
||||
<StackPanel Spacing="2">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Text="{Binding Name}" FontSize="13" FontWeight="SemiBold"
|
||||
TextWrapping="Wrap"/>
|
||||
<TextBlock Grid.Column="1" FontSize="11" Opacity="0.6">
|
||||
<Run Text="{Binding TimeDisplay}"/><Run Text=" · "/>
|
||||
<Run Text="{Binding DurationMinutes, StringFormat='{}{0} Min.'}"/>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<TextBlock Text="{Binding Activity}" FontSize="12" TextWrapping="Wrap"
|
||||
IsVisible="{Binding Activity, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
<TextBlock FontSize="11" Opacity="0.55" TextWrapping="Wrap"
|
||||
IsVisible="{Binding Material, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
||||
<Run Text="Material: "/><Run Text="{Binding Material}"/>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<StackPanel Spacing="4" IsVisible="{Binding LessonInfo.Homework, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
||||
<TextBlock Text="Hausaufgabe" FontSize="12" FontWeight="SemiBold" Opacity="0.6"/>
|
||||
<TextBlock Text="{Binding LessonInfo.Homework}" FontSize="13" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Spacing="4" IsVisible="{Binding LessonInfo.Reflection, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
||||
<TextBlock Text="Reflexion" FontSize="12" FontWeight="SemiBold" Opacity="0.6"/>
|
||||
<TextBlock Text="{Binding LessonInfo.Reflection}" FontSize="13" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
<views:SeatingPlanTabView Grid.Column="2" DataContext="{Binding SeatingPlan}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,28 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using LehrerApp.Desktop.ViewModels;
|
||||
using LehrerApp.Desktop.ViewModels.Groups;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace LehrerApp.Desktop.Views.Groups;
|
||||
|
||||
public partial class TeachingModeWindow : Window
|
||||
{
|
||||
public TeachingModeWindow() => InitializeComponent();
|
||||
|
||||
protected override void OnDataContextChanged(EventArgs e)
|
||||
{
|
||||
base.OnDataContextChanged(e);
|
||||
// Gleiches Muster wie LessonViewerDialog (4.5.3): Fenster schließt zuerst, dann Sprung
|
||||
// über die MainWindowViewModel-Singleton-Navigation in den Ziel-Tab der Gruppe.
|
||||
if (DataContext is TeachingModeViewModel vm)
|
||||
vm.LessonInfo.OnNavigateToTab = tabIndex =>
|
||||
{
|
||||
Close();
|
||||
App.Services.GetRequiredService<MainWindowViewModel>()
|
||||
.NavigateToGroupDetail(vm.LessonInfo.GroupId, tabIndex);
|
||||
};
|
||||
}
|
||||
|
||||
private void OnClose(object? sender, RoutedEventArgs e) => Close();
|
||||
}
|
||||
@@ -100,10 +100,17 @@
|
||||
Foreground="#8E6C00" FontWeight="SemiBold"
|
||||
IsVisible="{Binding HasUnhandledHomework}"/>
|
||||
</StackPanel>
|
||||
<Button Grid.Column="3" Content="{Binding OpenButtonLabel}" FontSize="11" Padding="9,4"
|
||||
VerticalAlignment="Center" IsVisible="{Binding HasGroupId}"
|
||||
Command="{Binding $parent[ItemsControl].((vm:TimetableViewModel)DataContext).OpenTodayLessonCommand}"
|
||||
CommandParameter="{Binding}"/>
|
||||
<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>
|
||||
|
||||
@@ -21,9 +21,23 @@ public partial class TimetableView : UserControl
|
||||
vm.OnEditSlot = ShowSlotDialog;
|
||||
vm.OnAddSubstitution = ShowSubstitutionDialog;
|
||||
vm.OnOpenLessonViewer = ShowLessonViewerDialog;
|
||||
vm.OnOpenTeachingMode = ShowTeachingMode;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowTeachingMode(Lesson lesson)
|
||||
{
|
||||
var owner = TopLevel.GetTopLevel(this) as Window;
|
||||
var group = App.Services.GetRequiredService<IGroupRepository>().GetById(lesson.GroupId);
|
||||
if (owner is null || group is null) return;
|
||||
|
||||
var teachingModeVm = new TeachingModeViewModel(lesson, group,
|
||||
App.Services.GetRequiredService<IAlternativeLessonPathRepository>(),
|
||||
App.Services.GetRequiredService<SeatingPlanTabViewModel>());
|
||||
var window = new TeachingModeWindow { DataContext = teachingModeVm };
|
||||
await window.ShowDialog(owner);
|
||||
}
|
||||
|
||||
private async Task ShowLessonViewerDialog(Lesson lesson)
|
||||
{
|
||||
var owner = TopLevel.GetTopLevel(this) as Window;
|
||||
|
||||
Reference in New Issue
Block a user