Files
LehrerApp/LehrerApp.Desktop/Views/MainWindow.axaml
T
adminandClaude Sonnet 5 ba52109eaa
CI / build-and-test (push) Canceled after 0s
feat: Untis-Hub für WebUntis-Sync-Gesundheitsstatus
Zeigt die Fälligkeit der vier bestehenden, manuell ausgelösten WebUntis-Abgleiche
(Fehlzeiten je Lerngruppe, offene Stunden, Klassenbuch-, Hausaufgabenabgleich) in
einem neuen Hub-Fenster, ohne selbst WebUntis anzufragen - nur gespeicherte
Zeitstempel werden ausgewertet. Konsolidiert die bisher verstreuten Einstiegspunkte
(Sidebar-Button, Dashboard-Buttons) in ein neues WebUntis-Menü plus einen
kompakten Gesundheits-Indikator auf dem Dashboard.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 23:41:12 +02:00

421 lines
22 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:LehrerApp.Desktop.ViewModels"
xmlns:vmg="clr-namespace:LehrerApp.Desktop.ViewModels.Groups"
xmlns:vms="clr-namespace:LehrerApp.Desktop.ViewModels.Students"
xmlns:views="clr-namespace:LehrerApp.Desktop.Views"
xmlns:vd="clr-namespace:LehrerApp.Desktop.Views.Dashboard"
xmlns:vg="clr-namespace:LehrerApp.Desktop.Views.Groups"
xmlns:vs="clr-namespace:LehrerApp.Desktop.Views.Students"
xmlns:vset="clr-namespace:LehrerApp.Desktop.Views.Settings"
xmlns:vmset="clr-namespace:LehrerApp.Desktop.ViewModels.Settings"
xmlns:vp="clr-namespace:LehrerApp.Desktop.Views.Planning"
xmlns:vmp="clr-namespace:LehrerApp.Desktop.ViewModels.Planning"
xmlns:vw="clr-namespace:LehrerApp.Desktop.Views.Workload"
xmlns:vmw="clr-namespace:LehrerApp.Desktop.ViewModels.Workload"
xmlns:vct="clr-namespace:LehrerApp.Desktop.Views.ClassTeacher"
xmlns:vmct="clr-namespace:LehrerApp.Desktop.ViewModels.ClassTeacher"
xmlns:vex="clr-namespace:LehrerApp.Desktop.Views.Exams"
xmlns:vmex="clr-namespace:LehrerApp.Desktop.ViewModels.Exams"
xmlns:svc="clr-namespace:LehrerApp.Desktop.Services"
x:Class="LehrerApp.Desktop.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Title="LehrerApp"
Width="1280" Height="800"
MinWidth="640" MinHeight="480">
<Panel>
<!--
Avalonia 12: DrawerPage ersetzt die selbstgebaute Sidebar.
DrawerBreakpointWidth="900" → Sidebar ab 900px dauerhaft sichtbar,
darunter als Overlay-Drawer mit Hamburger-Button (automatisch).
Kein eigener Code nötig.
-->
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="WebUntis">
<MenuItem Header="Untis-Hub…" Click="OnOpenUntisHub"/>
<Separator/>
<MenuItem Header="Offene Stunden…" Click="OnOpenUntisPeriods"/>
<MenuItem Header="Klassenbuchabgleich…" Click="OnCompareUntisKlassenbuch"/>
<MenuItem Header="Hausaufgabenabgleich…" Click="OnCompareUntisHausaufgaben"/>
</MenuItem>
</Menu>
<DrawerPage x:Name="RootDrawer"
DrawerLength="220"
DrawerBehavior="Auto"
DrawerLayoutBehavior="CompactInline"
Content="{Binding CurrentPage}">
<DrawerPage.DataTemplates>
<DataTemplate DataType="vm:DashboardViewModel">
<vd:DashboardView/>
</DataTemplate>
<DataTemplate DataType="vmg:GroupListViewModel">
<vg:GroupListView/>
</DataTemplate>
<DataTemplate DataType="vmg:GroupDetailViewModel">
<vg:GroupDetailView/>
</DataTemplate>
<DataTemplate DataType="vms:StudentListViewModel">
<vs:StudentListView/>
</DataTemplate>
<DataTemplate DataType="vms:StudentDetailViewModel">
<vs:StudentDetailView/>
</DataTemplate>
<DataTemplate DataType="vmset:SettingsViewModel">
<vset:SettingsView/>
</DataTemplate>
<DataTemplate DataType="vmp:TimetableViewModel">
<vp:TimetableView/>
</DataTemplate>
<DataTemplate DataType="vmw:WorkloadViewModel">
<vw:WorkloadView/>
</DataTemplate>
<DataTemplate DataType="vmct:ClassTeacherOverviewViewModel">
<vct:ClassTeacherOverviewView/>
</DataTemplate>
<DataTemplate DataType="vmex:ExamsOverviewViewModel">
<vex:ExamsOverviewView/>
</DataTemplate>
<DataTemplate DataType="vm:PlaceholderViewModel">
<views:PlaceholderView/>
</DataTemplate>
</DrawerPage.DataTemplates>
<DrawerPage.Drawer>
<DockPanel Classes.compact="{Binding !#RootDrawer.IsOpen}">
<DockPanel.Styles>
<!-- Basisgröße der Icons als Style (nicht lokal), damit der Compact-Selector sie
überschreiben kann lokale Werte hätten immer Vorrang vor Style-Settern.
FontFamily explizit auf die farbige Emoji-Schrift gepinnt: Windows kann für
Emoji-Codepoints je nach Fallback-Auflösung sonst auf "Segoe UI Symbol"
(einfarbig/schwarz) statt "Segoe UI Emoji" (farbig) ausweichen. -->
<Style Selector="PathIcon.navicon">
<Setter Property="Width" Value="24"/>
<Setter Property="Height" Value="24"/>
</Style>
<Style Selector="Button.navitem">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Padding" Value="10,8"/>
<Setter Property="MinHeight" Value="40"/>
</Style>
<Style Selector="Button.navitem.active">
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundAccentBrush}"/>
<Setter Property="Foreground" Value="White"/>
</Style>
<!-- Minimierter Zustand: Icons größer & zentriert, Beschriftungen ausgeblendet -->
<Style Selector="DockPanel.compact TextBlock.navlabel">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="DockPanel.compact PathIcon.navicon">
<Setter Property="Width" Value="28"/>
<Setter Property="Height" Value="28"/>
</Style>
<Style Selector="DockPanel.compact Button.navitem">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="2,8"/>
</Style>
<Style Selector="DockPanel.compact StackPanel.navitems">
<Setter Property="Margin" Value="2,12,2,0"/>
</Style>
<Style Selector="DockPanel.compact StackPanel.navcontent">
<Setter Property="Spacing" Value="0"/>
</Style>
<Style Selector="DockPanel.compact Border.drawerheader">
<Setter Property="Padding" Value="8,20,8,14"/>
</Style>
<Style Selector="DockPanel.compact TextBlock.navsection">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="DockPanel.compact TextBlock.appsubtitle">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="DockPanel.compact Border.appheader">
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DockPanel.Styles>
<Border Classes="drawerheader" DockPanel.Dock="Top" Padding="16,20,16,14"
BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
BorderThickness="0,0,0,1">
<StackPanel Orientation="Horizontal" Spacing="10">
<Border Classes="appheader" Width="32" Height="32" CornerRadius="8"
Background="{DynamicResource SystemControlBackgroundAccentBrush}">
<TextBlock Text="L" FontSize="16" FontWeight="Bold" Foreground="White"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<StackPanel Classes="appsubtitle" VerticalAlignment="Center">
<TextBlock Text="LehrerApp" FontSize="15" FontWeight="SemiBold"/>
<TextBlock Text="{Binding CurrentSchoolYear}"
FontSize="11" Opacity="0.55"/>
</StackPanel>
</StackPanel>
</Border>
<Border DockPanel.Dock="Bottom" Padding="12,8"
BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
BorderThickness="0,1,0,0">
<views:SyncStatusBar DataContext="{Binding SyncStatus}"/>
</Border>
<ScrollViewer>
<StackPanel Classes="navitems" Margin="8,12,8,0" Spacing="2">
<Button Classes="navitem" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left" CornerRadius="6"
Click="OnOpenCommandPaletteClick"
ToolTip.Tip="Suchen und schnell erfassen (Strg/⌘+K)"
AutomationProperties.Name="Suchen und schnell erfassen">
<Grid ColumnDefinitions="Auto,*,Auto">
<PathIcon Classes="navicon" Data="{StaticResource IconSearch}"/>
<TextBlock Grid.Column="1" Classes="navlabel" Text="Suchen / Erfassen"/>
<TextBlock Grid.Column="2" Classes="navlabel" Text="⌘K" FontSize="10" Opacity="0.45"
VerticalAlignment="Center"/>
</Grid>
</Button>
<Separator Margin="4,6"/>
<Button Classes="navitem" Classes.active="{Binding IsDashboardActive}" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
CornerRadius="6"
Command="{Binding NavigateToCommand}"
CommandParameter="{x:Static vm:NavItem.Dashboard}"
ToolTip.Tip="Dashboard (Strg/⌘+1)" AutomationProperties.Name="Dashboard">
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
<PathIcon Classes="navicon" Data="{StaticResource IconDashboard}"/>
<TextBlock Classes="navlabel" Text="Dashboard"/>
</StackPanel>
</Button>
<TextBlock Classes="navsection" Text="UNTERRICHT" FontSize="10" FontWeight="Bold"
Opacity="0.4" Margin="10,14,0,4"/>
<Button Classes="navitem" Classes.active="{Binding IsGroupsActive}" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
CornerRadius="6"
Command="{Binding NavigateToCommand}"
CommandParameter="{x:Static vm:NavItem.Groups}"
ToolTip.Tip="Lerngruppen (Strg/⌘+2)" AutomationProperties.Name="Lerngruppen">
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
<PathIcon Classes="navicon" Data="{StaticResource IconGroups}"/>
<TextBlock Classes="navlabel" Text="Lerngruppen"/>
</StackPanel>
</Button>
<Button Classes="navitem" Classes.active="{Binding IsStudentsActive}" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
CornerRadius="6"
Command="{Binding NavigateToCommand}"
CommandParameter="{x:Static vm:NavItem.Students}"
ToolTip.Tip="Schüler (Strg/⌘+3)" AutomationProperties.Name="Schüler">
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
<PathIcon Classes="navicon" Data="{StaticResource IconStudents}"/>
<TextBlock Classes="navlabel" Text="Schüler"/>
</StackPanel>
</Button>
<Button Classes="navitem" Classes.active="{Binding IsExamsActive}" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
CornerRadius="6"
Command="{Binding NavigateToCommand}"
CommandParameter="{x:Static vm:NavItem.Exams}"
ToolTip.Tip="Klausuren (Strg/⌘+4)" AutomationProperties.Name="Klausuren">
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
<PathIcon Classes="navicon" Data="{StaticResource IconExams}"/>
<TextBlock Classes="navlabel" Text="Klausuren"/>
</StackPanel>
</Button>
<Button Classes="navitem" Classes.active="{Binding IsPlannerActive}" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
CornerRadius="6"
Command="{Binding NavigateToCommand}"
CommandParameter="{x:Static vm:NavItem.Planner}"
ToolTip.Tip="Planung (Strg/⌘+5)" AutomationProperties.Name="Planung">
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
<PathIcon Classes="navicon" Data="{StaticResource IconCalendar}"/>
<TextBlock Classes="navlabel" Text="Planung"/>
</StackPanel>
</Button>
<TextBlock Classes="navsection" Text="VERWALTUNG" FontSize="10" FontWeight="Bold"
Opacity="0.4" Margin="10,14,0,4"/>
<Button Classes="navitem" Classes.active="{Binding IsWorkloadActive}" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
CornerRadius="6"
Command="{Binding NavigateToCommand}"
CommandParameter="{x:Static vm:NavItem.Workload}"
ToolTip.Tip="Arbeitszeit (Strg/⌘+6)" AutomationProperties.Name="Arbeitszeit">
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
<PathIcon Classes="navicon" Data="{StaticResource IconClock}"/>
<TextBlock Classes="navlabel" Text="Arbeitszeit"/>
</StackPanel>
</Button>
<Button Classes="navitem" Classes.active="{Binding IsClassTeacherActive}" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
CornerRadius="6"
Command="{Binding NavigateToCommand}"
CommandParameter="{x:Static vm:NavItem.ClassTeacher}"
ToolTip.Tip="Klassenlehrer (Strg/⌘+7)" AutomationProperties.Name="Klassenlehrer">
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
<PathIcon Classes="navicon" Data="{StaticResource IconClassTeacher}"/>
<TextBlock Classes="navlabel" Text="Klassenlehrer"/>
</StackPanel>
</Button>
<Button Classes="navitem" Classes.active="{Binding IsSettingsActive}" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
CornerRadius="6"
Command="{Binding NavigateToCommand}"
CommandParameter="{x:Static vm:NavItem.Settings}"
ToolTip.Tip="Einstellungen (Strg/⌘+8)" AutomationProperties.Name="Einstellungen">
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
<PathIcon Classes="navicon" Data="{StaticResource IconSettings}"/>
<TextBlock Classes="navlabel" Text="Einstellungen"/>
</StackPanel>
</Button>
</StackPanel>
</ScrollViewer>
</DockPanel>
</DrawerPage.Drawer>
</DrawerPage>
</DockPanel>
<!-- Globale Suche und Schnellerfassung (14.2). Bewusst als Overlay auf der aktuellen Seite:
Der Nutzer behält den Kontext und kann mit Escape ohne Navigation zurückkehren. -->
<Border Background="#A0000000" IsVisible="{Binding IsCommandPaletteOpen}"
AutomationProperties.Name="Globale Suche und Schnellerfassung">
<Grid>
<Button Background="Transparent" BorderThickness="0"
Command="{Binding CloseCommandPaletteCommand}"
AutomationProperties.Name="Suche schließen"/>
<Border Width="680" MaxHeight="570" Margin="24" Padding="0"
HorizontalAlignment="Center" VerticalAlignment="Top"
Background="{DynamicResource AppCardBackgroundBrush}"
BorderBrush="{DynamicResource AppCardBorderBrush}" BorderThickness="1"
CornerRadius="12">
<Grid RowDefinitions="Auto,Auto,*,Auto">
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto" Margin="18,16,18,10">
<TextBlock Text="⌕" FontSize="24" VerticalAlignment="Center" Margin="0,0,10,0"/>
<TextBox x:Name="CommandPaletteSearchBox" Grid.Column="1"
Text="{Binding CommandPalette.Query, UpdateSourceTrigger=PropertyChanged}"
PlaceholderText="Schüler, Lerngruppe, Klausur oder Aufgabe suchen …"
FontSize="16" BorderThickness="0" Background="Transparent"
AutomationProperties.Name="Suchbegriff"/>
<Button Grid.Column="2" Content="Esc" FontSize="10" Padding="8,3"
Command="{Binding CloseCommandPaletteCommand}"
AutomationProperties.Name="Suche schließen"/>
</Grid>
<Separator Grid.Row="1"/>
<ListBox Grid.Row="2" ItemsSource="{Binding CommandPalette.Results}"
SelectedItem="{Binding CommandPalette.SelectedResult}"
Background="Transparent" BorderThickness="0" Margin="8"
MaxHeight="420">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="vm:GlobalSearchResult">
<Button Background="Transparent" BorderThickness="0" Padding="10,8"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
Command="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).CommandPalette.ExecuteCommand}"
CommandParameter="{Binding}">
<Grid ColumnDefinitions="38,*,Auto">
<Border Width="30" Height="30" CornerRadius="7"
Background="{DynamicResource AppAccentSoftBackgroundBrush}"
VerticalAlignment="Center">
<TextBlock Text="{Binding Icon}" FontWeight="SemiBold"
Foreground="{DynamicResource AppAccentOnSoftBrush}"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<StackPanel Grid.Column="1" Margin="10,0">
<TextBlock Text="{Binding Title}" FontSize="14" FontWeight="SemiBold"/>
<TextBlock Text="{Binding Subtitle}" FontSize="11" Opacity="0.6"
TextTrimming="CharacterEllipsis"/>
</StackPanel>
<Border Grid.Column="2" Padding="7,3" CornerRadius="8"
Background="{DynamicResource AppChipBackgroundBrush}"
VerticalAlignment="Center">
<TextBlock Text="{Binding KindLabel}" FontSize="10" Opacity="0.7"/>
</Border>
</Grid>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Grid.Row="2" Text="Keine passenden Ergebnisse."
Classes="emptyhint" HorizontalAlignment="Center" Margin="20"
IsVisible="{Binding CommandPalette.ShowNoResults}"/>
<Grid Grid.Row="3" ColumnDefinitions="*,Auto" Margin="18,8,18,14">
<TextBlock Text="↑↓ auswählen · Enter öffnen · Esc schließen" FontSize="10" Opacity="0.5"/>
<TextBlock Grid.Column="1" Text="Strg/⌘ + K" FontSize="10" Opacity="0.5"/>
</Grid>
</Grid>
</Border>
</Grid>
</Border>
<!-- Dauerhafte Meldung bei einer echten Protokoll-Inkompatibilität. Als Overlay außerhalb
von DrawerPage.Content bleibt der DataContext das MainWindowViewModel; innerhalb des
ContentPresenters würde eine fehlgeschlagene Bindung IsVisible auf true stehen lassen. -->
<Border IsVisible="{Binding SyncStatus.IsIncompatibleVersion}"
HorizontalAlignment="Right" VerticalAlignment="Top"
MaxWidth="760" Margin="0,12,12,0"
Background="#B3261E" CornerRadius="8" Padding="16,12">
<StackPanel Orientation="Horizontal" Spacing="10">
<TextBlock Text="⚠" FontSize="18" Foreground="White"
VerticalAlignment="Center"/>
<StackPanel>
<TextBlock Text="Software-Update erforderlich"
Foreground="White" FontWeight="SemiBold"/>
<TextBlock Text="{x:Static vm:SyncStatusViewModel.IncompatibleVersionMessage}"
Foreground="White" Opacity="0.92" TextWrapping="Wrap"/>
</StackPanel>
</StackPanel>
</Border>
<!-- Toast-Benachrichtigungen (13.2.3): schweben über der aktuellen Seite, unten rechts. -->
<ItemsControl ItemsSource="{Binding Toasts}"
HorizontalAlignment="Right" VerticalAlignment="Bottom"
Margin="0,0,20,20" IsHitTestVisible="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Spacing="8"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Styles>
<Style Selector="Border.toast">
<Setter Property="Background" Value="#323232"/>
</Style>
<Style Selector="Border.toast.error">
<Setter Property="Background" Value="#B3261E"/>
</Style>
</ItemsControl.Styles>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="svc:ToastItem">
<Border Classes="toast" Classes.error="{Binding IsError}"
CornerRadius="8" Padding="14,10" MaxWidth="360">
<TextBlock Text="{Binding Message}" Foreground="White" FontSize="13" TextWrapping="Wrap"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- App-Sperre nach Inaktivität (13.3.5): blockiert die gesamte Oberfläche. -->
<Border Background="#E6101014" IsVisible="{Binding AppLock.IsLocked}">
<Border Width="320" Padding="24" CornerRadius="10"
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Spacing="12">
<TextBlock Text="🔒" FontSize="28" HorizontalAlignment="Center"/>
<TextBlock Text="LehrerApp gesperrt" FontSize="16" FontWeight="SemiBold"
HorizontalAlignment="Center"/>
<TextBox PlaceholderText="Passwort" PasswordChar="●"
Text="{Binding AppLock.PasswordAttempt}"
KeyDown="OnLockScreenKeyDown"/>
<TextBlock Text="{Binding AppLock.UnlockError}" Foreground="Red" FontSize="12"
HorizontalAlignment="Center"
IsVisible="{Binding AppLock.UnlockError, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
<Button Content="Entsperren" Command="{Binding AppLock.UnlockCommand}"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"/>
</StackPanel>
</Border>
</Border>
</Panel>
</Window>