Improve desktop UX and accessibility

This commit is contained in:
2026-08-29 20:56:55 +02:00
parent b7a105af73
commit 89a6376fb6
17 changed files with 239 additions and 69 deletions
@@ -29,6 +29,20 @@ public sealed class ExamsOverviewViewModelTests
Assert.Equal("Neue Klausur", vm.Rows[1].Title); Assert.Equal("Neue Klausur", vm.Rows[1].Title);
} }
[Fact]
public void LeereListe_BietetAbsprungZuLerngruppen()
{
var vm = BuildVm([], []);
var navigated = false;
vm.OnNavigateToGroups = () => navigated = true;
vm.LoadCommand.Execute(null);
vm.GoToGroupsCommand.Execute(null);
Assert.True(vm.HasNoExams);
Assert.True(navigated);
}
[Fact] [Fact]
public void Load_ParallelkurseWerdenAlsGeschwisterErkannt() public void Load_ParallelkurseWerdenAlsGeschwisterErkannt()
{ {
@@ -51,6 +51,18 @@ public class StudentListViewModelTests
Assert.Equal("M", vm.Students.Single(s => s.Id == _ben.Id).AvatarLabel); Assert.Equal("M", vm.Students.Single(s => s.Id == _ben.Id).AvatarLabel);
} }
[Fact]
public void SucheOhneTreffer_LiefertHilfreichenLeerzustand()
{
var vm = BuildViewModel();
vm.SearchText = "nicht vorhanden";
Assert.True(vm.HasNoStudents);
Assert.False(vm.HasStudents);
Assert.Contains("Suche", vm.EmptyListMessage);
}
private StudentListViewModel BuildViewModel() => new( private StudentListViewModel BuildViewModel() => new(
new FakeStudents([_anna, _ben]), new FakeStudents([_anna, _ben]),
new FakeGroups([_group]), new FakeGroups([_group]),
+23
View File
@@ -7,6 +7,17 @@
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://LehrerApp.Desktop/Styles/SemanticBrushes.axaml"/> <ResourceInclude Source="avares://LehrerApp.Desktop/Styles/SemanticBrushes.axaml"/>
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<!-- Plattformunabhängige Vektoricons für die Hauptnavigation (14.10). -->
<StreamGeometry x:Key="IconSearch">M10,2 A8,8 0 1 0 10,18 A8,8 0 1 0 10,2 M10,5 A5,5 0 1 1 10,15 A5,5 0 1 1 10,5 M15,14 L22,21 L20,23 L13,16 Z</StreamGeometry>
<StreamGeometry x:Key="IconDashboard">M3,3 H10 V10 H3 Z M14,3 H21 V7 H14 Z M14,11 H21 V21 H14 Z M3,14 H10 V21 H3 Z</StreamGeometry>
<StreamGeometry x:Key="IconGroups">M3,8 L12,3 L21,8 V10 H3 Z M5,11 H8 V18 H5 Z M10,11 H14 V18 H10 Z M16,11 H19 V18 H16 Z M3,20 H21 V22 H3 Z</StreamGeometry>
<StreamGeometry x:Key="IconStudents">M12,3 A4,4 0 1 0 12,11 A4,4 0 1 0 12,3 M4,21 C4,16 7,13 12,13 C17,13 20,16 20,21 Z</StreamGeometry>
<StreamGeometry x:Key="IconExams">M5,2 H15 L20,7 V22 H5 Z M7,5 V19 H18 V9 H13 V4 H7 Z M9,11 H16 V13 H9 Z M9,15 H16 V17 H9 Z</StreamGeometry>
<StreamGeometry x:Key="IconCalendar">M3,4 H21 V22 H3 Z M5,10 V20 H19 V10 Z M7,2 H9 V7 H7 Z M15,2 H17 V7 H15 Z M7,12 H10 V15 H7 Z M12,12 H15 V15 H12 Z M7,17 H10 V19 H7 Z M12,17 H15 V19 H12 Z</StreamGeometry>
<StreamGeometry x:Key="IconClock">M12,2 A10,10 0 1 0 12,22 A10,10 0 1 0 12,2 M12,5 A7,7 0 1 1 12,19 A7,7 0 1 1 12,5 M11,7 H13 V12 L17,14 L16,16 L11,13 Z</StreamGeometry>
<StreamGeometry x:Key="IconClassTeacher">M2,8 L12,3 L22,8 L12,13 Z M6,11 V16 C9,19 15,19 18,16 V11 M22,8 V15</StreamGeometry>
<StreamGeometry x:Key="IconSettings">M3,5 H21 V7 H3 Z M8,3 A3,3 0 1 0 8,9 A3,3 0 1 0 8,3 M3,11 H21 V13 H3 Z M16,9 A3,3 0 1 0 16,15 A3,3 0 1 0 16,9 M3,17 H21 V19 H3 Z M10,15 A3,3 0 1 0 10,21 A3,3 0 1 0 10,15</StreamGeometry>
<StreamGeometry x:Key="IconRefresh">M12,3 A9,9 0 0 1 21,12 H18 A6,6 0 0 0 8,7 L11,10 H3 V2 L6,5 A9,9 0 0 1 12,3 M12,21 A9,9 0 0 1 3,12 H6 A6,6 0 0 0 16,17 L13,14 H21 V22 L18,19 A9,9 0 0 1 12,21</StreamGeometry>
</ResourceDictionary> </ResourceDictionary>
</Application.Resources> </Application.Resources>
@@ -24,5 +35,17 @@
<Setter Property="Opacity" Value="0.4"/> <Setter Property="Opacity" Value="0.4"/>
<Setter Property="FontSize" Value="13"/> <Setter Property="FontSize" Value="13"/>
</Style> </Style>
<Style Selector="Button.touchTarget">
<Setter Property="MinWidth" Value="40"/>
<Setter Property="MinHeight" Value="40"/>
</Style>
<Style Selector="Button:focus-visible">
<Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColor}"/>
<Setter Property="BorderThickness" Value="2"/>
</Style>
<Style Selector="TextBox:focus-visible, ComboBox:focus-visible">
<Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColor}"/>
<Setter Property="BorderThickness" Value="2"/>
</Style>
</Application.Styles> </Application.Styles>
</Application> </Application>
+3
View File
@@ -154,6 +154,9 @@ public class App : Application
dash.OnNavigateToExam = id => main.NavigateToGroupDetail(id, 4); // Tab "Klausuren" dash.OnNavigateToExam = id => main.NavigateToGroupDetail(id, 4); // Tab "Klausuren"
dash.OnNavigateToUnplannedLesson = id => main.NavigateToGroupDetail(id, 6); // Tab "Planung" dash.OnNavigateToUnplannedLesson = id => main.NavigateToGroupDetail(id, 6); // Tab "Planung"
var examsOverview = Services.GetRequiredService<ViewModels.Exams.ExamsOverviewViewModel>();
examsOverview.OnNavigateToGroups = () => main.NavigateToCommand.Execute(NavItem.Groups);
// Globale Suche/Schnellerfassung (14.2): Navigation bleibt im MainWindow-VM, die // Globale Suche/Schnellerfassung (14.2): Navigation bleibt im MainWindow-VM, die
// vorhandenen Dialog-Helfer übernehmen Eingabe und Validierung. // vorhandenen Dialog-Helfer übernehmen Eingabe und Validierung.
var search = Services.GetRequiredService<GlobalSearchViewModel>(); var search = Services.GetRequiredService<GlobalSearchViewModel>();
@@ -43,6 +43,8 @@ public partial class ExamsOverviewViewModel : ObservableObject
public Func<Exam, Task>? OnGradeExam { get; set; } public Func<Exam, Task>? OnGradeExam { get; set; }
public Func<Exam, Task>? OnEvaluateExam { get; set; } public Func<Exam, Task>? OnEvaluateExam { get; set; }
public Action<Guid>? OnNavigateToGroup { get; set; } public Action<Guid>? OnNavigateToGroup { get; set; }
public Action? OnNavigateToGroups { get; set; }
public bool HasNoExams => Rows.Count == 0;
public ExamsOverviewViewModel(IExamRepository exams, IExamResultRepository examResults, public ExamsOverviewViewModel(IExamRepository exams, IExamResultRepository examResults,
IGroupRepository groups, IGroupMembershipRepository memberships, GradingService grading, IGroupRepository groups, IGroupMembershipRepository memberships, GradingService grading,
@@ -85,6 +87,7 @@ public partial class ExamsOverviewViewModel : ObservableObject
Rows.Add(row); Rows.Add(row);
EmptyHint = Rows.Count == 0 ? "Keine Klausuren angelegt." : ""; EmptyHint = Rows.Count == 0 ? "Keine Klausuren angelegt." : "";
OnPropertyChanged(nameof(HasNoExams));
SelectedRow = selectedId is { } id SelectedRow = selectedId is { } id
? Rows.FirstOrDefault(r => r.Exam.Id == id) ?? Rows.FirstOrDefault() ? Rows.FirstOrDefault(r => r.Exam.Id == id) ?? Rows.FirstOrDefault()
: Rows.FirstOrDefault(); : Rows.FirstOrDefault();
@@ -173,6 +176,9 @@ public partial class ExamsOverviewViewModel : ObservableObject
OnNavigateToGroup?.Invoke(SelectedRow.GroupId); OnNavigateToGroup?.Invoke(SelectedRow.GroupId);
} }
[RelayCommand]
private void GoToGroups() => OnNavigateToGroups?.Invoke();
[RelayCommand] [RelayCommand]
private void ToggleApproval() => ToggleDate(SelectedRow?.Exam, e => e.ApprovalGrantedAt, private void ToggleApproval() => ToggleDate(SelectedRow?.Exam, e => e.ApprovalGrantedAt,
(e, v) => e.ApprovalGrantedAt = v); (e, v) => e.ApprovalGrantedAt = v);
@@ -64,9 +64,15 @@ public partial class StudentListViewModel : ObservableObject
[ObservableProperty] private string _searchText = ""; [ObservableProperty] private string _searchText = "";
[ObservableProperty] private bool _showInactive; [ObservableProperty] private bool _showInactive;
[ObservableProperty] private StudentListItem? _selectedStudent; [ObservableProperty] private StudentListItem? _selectedStudent;
[ObservableProperty] private bool _isImporting;
public ObservableCollection<StudentListItem> Students { get; } = []; public ObservableCollection<StudentListItem> Students { get; } = [];
public string CountSummary => $"{Students.Count} Schüler gesamt"; public string CountSummary => $"{Students.Count} Schüler gesamt";
public bool HasNoStudents => Students.Count == 0;
public bool HasStudents => !HasNoStudents;
public string EmptyListMessage => string.IsNullOrWhiteSpace(SearchText)
? ShowInactive ? "Noch keine Schüler vorhanden." : "Noch keine aktiven Schüler vorhanden."
: "Keine Schüler passen zur aktuellen Suche.";
public StudentListViewModel(IStudentRepository students, IGroupRepository groups, public StudentListViewModel(IStudentRepository students, IGroupRepository groups,
IGroupMembershipRepository memberships) IGroupMembershipRepository memberships)
@@ -105,6 +111,9 @@ public partial class StudentListViewModel : ObservableObject
} }
foreach (var s in f) Students.Add(new StudentListItem(s)); foreach (var s in f) Students.Add(new StudentListItem(s));
OnPropertyChanged(nameof(CountSummary)); OnPropertyChanged(nameof(CountSummary));
OnPropertyChanged(nameof(HasNoStudents));
OnPropertyChanged(nameof(HasStudents));
OnPropertyChanged(nameof(EmptyListMessage));
} }
public Func<Task>? OnAddStudent { get; set; } public Func<Task>? OnAddStudent { get; set; }
@@ -20,17 +20,17 @@
<StackPanel Spacing="20"> <StackPanel Spacing="20">
<!-- Begrüßung --> <!-- Begrüßung -->
<Grid ColumnDefinitions="*,Auto"> <Grid RowDefinitions="Auto,Auto">
<StackPanel> <StackPanel>
<TextBlock Text="{Binding Greeting}" FontSize="14" Opacity="0.6"/> <TextBlock Text="{Binding Greeting}" FontSize="14" Opacity="0.6"/>
<TextBlock Text="{Binding CurrentDate}" FontSize="24" FontWeight="SemiBold"/> <TextBlock Text="{Binding CurrentDate}" FontSize="24" FontWeight="SemiBold"/>
</StackPanel> </StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8"> <WrapPanel Grid.Row="1" Orientation="Horizontal" ItemSpacing="8" LineSpacing="8" Margin="0,10,0,0">
<Button Content="Klassenbuch abgleichen…" Click="OnCompareWebUntisDocumentationClick" <Button Content="Klassenbuch abgleichen…" Click="OnCompareWebUntisDocumentationClick"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
<Button Content="Bereiche anpassen" Command="{Binding ToggleDashboardSettingsCommand}" <Button Content="Bereiche anpassen" Command="{Binding ToggleDashboardSettingsCommand}"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
</StackPanel> </WrapPanel>
</Grid> </Grid>
<!-- Der Tagesfokus beantwortet zuerst die vier Fragen, die beim Öffnen der App zählen: <!-- Der Tagesfokus beantwortet zuerst die vier Fragen, die beim Öffnen der App zählen:
@@ -72,9 +72,13 @@
<StackPanel Orientation="Horizontal" Spacing="6"> <StackPanel Orientation="Horizontal" Spacing="6">
<CheckBox Content="{Binding Title}" IsChecked="{Binding IsVisible}" VerticalAlignment="Center"/> <CheckBox Content="{Binding Title}" IsChecked="{Binding IsVisible}" VerticalAlignment="Center"/>
<Button Content="↑" Padding="6,2" <Button Content="↑" Padding="6,2"
ToolTip.Tip="Bereich nach oben verschieben"
AutomationProperties.Name="Dashboard-Bereich nach oben verschieben"
Command="{Binding $parent[ItemsControl].((vm:DashboardViewModel)DataContext).MoveCardUpCommand}" Command="{Binding $parent[ItemsControl].((vm:DashboardViewModel)DataContext).MoveCardUpCommand}"
CommandParameter="{Binding}"/> CommandParameter="{Binding}"/>
<Button Content="↓" Padding="6,2" <Button Content="↓" Padding="6,2"
ToolTip.Tip="Bereich nach unten verschieben"
AutomationProperties.Name="Dashboard-Bereich nach unten verschieben"
Command="{Binding $parent[ItemsControl].((vm:DashboardViewModel)DataContext).MoveCardDownCommand}" Command="{Binding $parent[ItemsControl].((vm:DashboardViewModel)DataContext).MoveCardDownCommand}"
CommandParameter="{Binding}"/> CommandParameter="{Binding}"/>
</StackPanel> </StackPanel>
@@ -250,8 +254,10 @@
Width="130" Margin="12,0"/> Width="130" Margin="12,0"/>
<Button Grid.Column="2" Content="Heute" FontSize="11" Padding="8,2" <Button Grid.Column="2" Content="Heute" FontSize="11" Padding="8,2"
Command="{Binding CalendarTodayCommand}" Margin="0,0,4,0"/> Command="{Binding CalendarTodayCommand}" Margin="0,0,4,0"/>
<Button Grid.Column="3" Content="" Padding="8,2" Command="{Binding PrevMonthCommand}"/> <Button Grid.Column="3" Content="" Padding="8,2" Command="{Binding PrevMonthCommand}"
<Button Grid.Column="4" Content="" Padding="8,2" Command="{Binding NextMonthCommand}" Margin="4,0,0,0"/> ToolTip.Tip="Vorheriger Monat" AutomationProperties.Name="Vorheriger Monat"/>
<Button Grid.Column="4" Content="" Padding="8,2" Command="{Binding NextMonthCommand}" Margin="4,0,0,0"
ToolTip.Tip="Nächster Monat" AutomationProperties.Name="Nächster Monat"/>
</Grid> </Grid>
<ItemsControl ItemsSource="{Binding CalendarWeekdayHeaders}"> <ItemsControl ItemsSource="{Binding CalendarWeekdayHeaders}">
@@ -182,8 +182,14 @@
<!-- Liste aller Klausuren, nach Priorität sortiert (nicht nach Datum) ─────────── --> <!-- Liste aller Klausuren, nach Priorität sortiert (nicht nach Datum) ─────────── -->
<ScrollViewer Grid.Row="2" Margin="20,0,20,16"> <ScrollViewer Grid.Row="2" Margin="20,0,20,16">
<StackPanel> <StackPanel>
<TextBlock Text="{Binding EmptyHint}" Classes="emptyhint" Margin="0,20,0,0" <StackPanel Margin="0,28,0,0" Spacing="10" HorizontalAlignment="Center"
IsVisible="{Binding EmptyHint, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/> IsVisible="{Binding HasNoExams}">
<TextBlock Text="Noch keine Klausuren im aktuellen Schuljahr." Classes="emptyhint"
FontSize="15" TextAlignment="Center"/>
<TextBlock Text="Klausuren werden im jeweiligen Kurs angelegt." FontSize="12" Opacity="0.55"/>
<Button Content="Zu den Lerngruppen" Command="{Binding GoToGroupsCommand}"
HorizontalAlignment="Center"/>
</StackPanel>
<ItemsControl ItemsSource="{Binding Rows}"> <ItemsControl ItemsSource="{Binding Rows}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:ExamListRowViewModel"> <DataTemplate x:DataType="vm:ExamListRowViewModel">
@@ -13,9 +13,9 @@
<Border Grid.Row="0" Padding="20,16" <Border Grid.Row="0" Padding="20,16"
BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}" BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
BorderThickness="0,0,0,1"> BorderThickness="0,0,0,1">
<Grid ColumnDefinitions="*,Auto"> <Grid RowDefinitions="Auto,Auto">
<shared:PageHeader Grid.Column="0" Title="{Binding GroupTitle}" Subtitle="{Binding GroupSubtitle}"/> <shared:PageHeader Title="{Binding GroupTitle}" Subtitle="{Binding GroupSubtitle}"/>
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8"> <WrapPanel Grid.Row="1" Orientation="Horizontal" ItemSpacing="8" LineSpacing="8" Margin="0,10,0,0">
<TextBlock VerticalAlignment="Center" Opacity="0.6" FontSize="13"> <TextBlock VerticalAlignment="Center" Opacity="0.6" FontSize="13">
<Run Text="{Binding StudentCount}"/> <Run Text="{Binding StudentCount}"/>
<Run Text=" Schüler"/> <Run Text=" Schüler"/>
@@ -34,7 +34,7 @@
<Button Content="Austragung zurücknehmen" Command="{Binding ReinstateStudentCommand}" <Button Content="Austragung zurücknehmen" Command="{Binding ReinstateStudentCommand}"
IsVisible="{Binding SelectedStudent.HasExitDate}"/> IsVisible="{Binding SelectedStudent.HasExitDate}"/>
<Button Content=" Klausur" Command="{Binding AddExamCommand}" IsEnabled="{Binding IsEditable}"/> <Button Content=" Klausur" Command="{Binding AddExamCommand}" IsEnabled="{Binding IsEditable}"/>
</StackPanel> </WrapPanel>
</Grid> </Grid>
</Border> </Border>
@@ -11,13 +11,14 @@
<Border Grid.Row="0" Padding="20,16" <Border Grid.Row="0" Padding="20,16"
BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}" BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
BorderThickness="0,0,0,1"> BorderThickness="0,0,0,1">
<Grid ColumnDefinitions="*,Auto,Auto"> <Grid RowDefinitions="Auto,Auto" ColumnDefinitions="*,Auto,Auto">
<shared:PageHeader Grid.Column="0" Title="Lerngruppen" Subtitle="{Binding ListSummary}"/> <shared:PageHeader Grid.ColumnSpan="3" Title="Lerngruppen" Subtitle="{Binding ListSummary}"/>
<ComboBox Grid.Column="1" ItemsSource="{Binding SchoolYears}" <ComboBox Grid.Row="1" Grid.Column="1" ItemsSource="{Binding SchoolYears}"
SelectedItem="{Binding SelectedSchoolYear}" SelectedItem="{Binding SelectedSchoolYear}"
Width="100" Margin="0,0,8,0" VerticalAlignment="Center"/> Width="110" Margin="0,10,8,0" VerticalAlignment="Center"
<Button Grid.Column="2" Content=" Neue Gruppe" AutomationProperties.Name="Schuljahr auswählen"/>
Command="{Binding AddGroupCommand}" VerticalAlignment="Center"/> <Button Grid.Row="1" Grid.Column="2" Content=" Neue Gruppe"
Command="{Binding AddGroupCommand}" VerticalAlignment="Center" Margin="0,10,0,0"/>
</Grid> </Grid>
</Border> </Border>
@@ -76,7 +77,7 @@
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</Button> </Button>
<Button Grid.Column="2" Content="⋯" Width="36" Height="32" Margin="0,10,10,0" <Button Grid.Column="2" Content="⋯" Width="40" Height="40" Margin="0,10,10,0"
Padding="0" VerticalAlignment="Top" Padding="0" VerticalAlignment="Top"
ToolTip.Tip="Lerngruppe verwalten" ToolTip.Tip="Lerngruppe verwalten"
AutomationProperties.Name="{Binding ManageAutomationName}"> AutomationProperties.Name="{Binding ManageAutomationName}">
+31 -23
View File
@@ -22,7 +22,7 @@
x:DataType="vm:MainWindowViewModel" x:DataType="vm:MainWindowViewModel"
Title="LehrerApp" Title="LehrerApp"
Width="1280" Height="800" Width="1280" Height="800"
MinWidth="900" MinHeight="600"> MinWidth="640" MinHeight="480">
<Panel> <Panel>
<!-- <!--
@@ -81,14 +81,14 @@
FontFamily explizit auf die farbige Emoji-Schrift gepinnt: Windows kann für FontFamily explizit auf die farbige Emoji-Schrift gepinnt: Windows kann für
Emoji-Codepoints je nach Fallback-Auflösung sonst auf "Segoe UI Symbol" Emoji-Codepoints je nach Fallback-Auflösung sonst auf "Segoe UI Symbol"
(einfarbig/schwarz) statt "Segoe UI Emoji" (farbig) ausweichen. --> (einfarbig/schwarz) statt "Segoe UI Emoji" (farbig) ausweichen. -->
<Style Selector="TextBlock.navicon"> <Style Selector="PathIcon.navicon">
<Setter Property="FontSize" Value="15"/>
<Setter Property="Width" Value="24"/> <Setter Property="Width" Value="24"/>
<Setter Property="FontFamily" Value="Segoe UI Emoji,Segoe UI Symbol,Segoe UI"/> <Setter Property="Height" Value="24"/>
</Style> </Style>
<Style Selector="Button.navitem"> <Style Selector="Button.navitem">
<Setter Property="Background" Value="Transparent"/> <Setter Property="Background" Value="Transparent"/>
<Setter Property="Padding" Value="10,8"/> <Setter Property="Padding" Value="10,8"/>
<Setter Property="MinHeight" Value="40"/>
</Style> </Style>
<Style Selector="Button.navitem.active"> <Style Selector="Button.navitem.active">
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundAccentBrush}"/> <Setter Property="Background" Value="{DynamicResource SystemControlBackgroundAccentBrush}"/>
@@ -98,9 +98,9 @@
<Style Selector="DockPanel.compact TextBlock.navlabel"> <Style Selector="DockPanel.compact TextBlock.navlabel">
<Setter Property="IsVisible" Value="False"/> <Setter Property="IsVisible" Value="False"/>
</Style> </Style>
<Style Selector="DockPanel.compact TextBlock.navicon"> <Style Selector="DockPanel.compact PathIcon.navicon">
<Setter Property="FontSize" Value="20"/>
<Setter Property="Width" Value="28"/> <Setter Property="Width" Value="28"/>
<Setter Property="Height" Value="28"/>
</Style> </Style>
<Style Selector="DockPanel.compact Button.navitem"> <Style Selector="DockPanel.compact Button.navitem">
<Setter Property="HorizontalContentAlignment" Value="Center"/> <Setter Property="HorizontalContentAlignment" Value="Center"/>
@@ -157,7 +157,7 @@
ToolTip.Tip="Suchen und schnell erfassen (Strg/⌘+K)" ToolTip.Tip="Suchen und schnell erfassen (Strg/⌘+K)"
AutomationProperties.Name="Suchen und schnell erfassen"> AutomationProperties.Name="Suchen und schnell erfassen">
<Grid ColumnDefinitions="Auto,*,Auto"> <Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Classes="navicon" Text="⌕" TextAlignment="Center"/> <PathIcon Classes="navicon" Data="{StaticResource IconSearch}"/>
<TextBlock Grid.Column="1" Classes="navlabel" Text="Suchen / Erfassen"/> <TextBlock Grid.Column="1" Classes="navlabel" Text="Suchen / Erfassen"/>
<TextBlock Grid.Column="2" Classes="navlabel" Text="⌘K" FontSize="10" Opacity="0.45" <TextBlock Grid.Column="2" Classes="navlabel" Text="⌘K" FontSize="10" Opacity="0.45"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
@@ -169,9 +169,10 @@
HorizontalContentAlignment="Left" HorizontalContentAlignment="Left"
CornerRadius="6" CornerRadius="6"
Command="{Binding NavigateToCommand}" Command="{Binding NavigateToCommand}"
CommandParameter="{x:Static vm:NavItem.Dashboard}"> CommandParameter="{x:Static vm:NavItem.Dashboard}"
ToolTip.Tip="Dashboard (Strg/⌘+1)" AutomationProperties.Name="Dashboard">
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10"> <StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
<TextBlock Classes="navicon" Text="📊" TextAlignment="Center"/> <PathIcon Classes="navicon" Data="{StaticResource IconDashboard}"/>
<TextBlock Classes="navlabel" Text="Dashboard"/> <TextBlock Classes="navlabel" Text="Dashboard"/>
</StackPanel> </StackPanel>
</Button> </Button>
@@ -183,9 +184,10 @@
HorizontalContentAlignment="Left" HorizontalContentAlignment="Left"
CornerRadius="6" CornerRadius="6"
Command="{Binding NavigateToCommand}" Command="{Binding NavigateToCommand}"
CommandParameter="{x:Static vm:NavItem.Groups}"> CommandParameter="{x:Static vm:NavItem.Groups}"
ToolTip.Tip="Lerngruppen (Strg/⌘+2)" AutomationProperties.Name="Lerngruppen">
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10"> <StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
<TextBlock Classes="navicon" Text="🏫" TextAlignment="Center"/> <PathIcon Classes="navicon" Data="{StaticResource IconGroups}"/>
<TextBlock Classes="navlabel" Text="Lerngruppen"/> <TextBlock Classes="navlabel" Text="Lerngruppen"/>
</StackPanel> </StackPanel>
</Button> </Button>
@@ -193,9 +195,10 @@
HorizontalContentAlignment="Left" HorizontalContentAlignment="Left"
CornerRadius="6" CornerRadius="6"
Command="{Binding NavigateToCommand}" Command="{Binding NavigateToCommand}"
CommandParameter="{x:Static vm:NavItem.Students}"> CommandParameter="{x:Static vm:NavItem.Students}"
ToolTip.Tip="Schüler (Strg/⌘+3)" AutomationProperties.Name="Schüler">
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10"> <StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
<TextBlock Classes="navicon" Text="👤" TextAlignment="Center"/> <PathIcon Classes="navicon" Data="{StaticResource IconStudents}"/>
<TextBlock Classes="navlabel" Text="Schüler"/> <TextBlock Classes="navlabel" Text="Schüler"/>
</StackPanel> </StackPanel>
</Button> </Button>
@@ -203,9 +206,10 @@
HorizontalContentAlignment="Left" HorizontalContentAlignment="Left"
CornerRadius="6" CornerRadius="6"
Command="{Binding NavigateToCommand}" Command="{Binding NavigateToCommand}"
CommandParameter="{x:Static vm:NavItem.Exams}"> CommandParameter="{x:Static vm:NavItem.Exams}"
ToolTip.Tip="Klausuren (Strg/⌘+4)" AutomationProperties.Name="Klausuren">
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10"> <StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
<TextBlock Classes="navicon" Text="📝" TextAlignment="Center"/> <PathIcon Classes="navicon" Data="{StaticResource IconExams}"/>
<TextBlock Classes="navlabel" Text="Klausuren"/> <TextBlock Classes="navlabel" Text="Klausuren"/>
</StackPanel> </StackPanel>
</Button> </Button>
@@ -213,9 +217,10 @@
HorizontalContentAlignment="Left" HorizontalContentAlignment="Left"
CornerRadius="6" CornerRadius="6"
Command="{Binding NavigateToCommand}" Command="{Binding NavigateToCommand}"
CommandParameter="{x:Static vm:NavItem.Planner}"> CommandParameter="{x:Static vm:NavItem.Planner}"
ToolTip.Tip="Planung (Strg/⌘+5)" AutomationProperties.Name="Planung">
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10"> <StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
<TextBlock Classes="navicon" Text="📅" TextAlignment="Center"/> <PathIcon Classes="navicon" Data="{StaticResource IconCalendar}"/>
<TextBlock Classes="navlabel" Text="Planung"/> <TextBlock Classes="navlabel" Text="Planung"/>
</StackPanel> </StackPanel>
</Button> </Button>
@@ -227,9 +232,10 @@
HorizontalContentAlignment="Left" HorizontalContentAlignment="Left"
CornerRadius="6" CornerRadius="6"
Command="{Binding NavigateToCommand}" Command="{Binding NavigateToCommand}"
CommandParameter="{x:Static vm:NavItem.Workload}"> CommandParameter="{x:Static vm:NavItem.Workload}"
ToolTip.Tip="Arbeitszeit (Strg/⌘+6)" AutomationProperties.Name="Arbeitszeit">
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10"> <StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
<TextBlock Classes="navicon" Text="⏱" TextAlignment="Center"/> <PathIcon Classes="navicon" Data="{StaticResource IconClock}"/>
<TextBlock Classes="navlabel" Text="Arbeitszeit"/> <TextBlock Classes="navlabel" Text="Arbeitszeit"/>
</StackPanel> </StackPanel>
</Button> </Button>
@@ -237,9 +243,10 @@
HorizontalContentAlignment="Left" HorizontalContentAlignment="Left"
CornerRadius="6" CornerRadius="6"
Command="{Binding NavigateToCommand}" Command="{Binding NavigateToCommand}"
CommandParameter="{x:Static vm:NavItem.ClassTeacher}"> CommandParameter="{x:Static vm:NavItem.ClassTeacher}"
ToolTip.Tip="Klassenlehrer (Strg/⌘+7)" AutomationProperties.Name="Klassenlehrer">
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10"> <StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
<TextBlock Classes="navicon" Text="🎓" TextAlignment="Center"/> <PathIcon Classes="navicon" Data="{StaticResource IconClassTeacher}"/>
<TextBlock Classes="navlabel" Text="Klassenlehrer"/> <TextBlock Classes="navlabel" Text="Klassenlehrer"/>
</StackPanel> </StackPanel>
</Button> </Button>
@@ -247,9 +254,10 @@
HorizontalContentAlignment="Left" HorizontalContentAlignment="Left"
CornerRadius="6" CornerRadius="6"
Command="{Binding NavigateToCommand}" Command="{Binding NavigateToCommand}"
CommandParameter="{x:Static vm:NavItem.Settings}"> CommandParameter="{x:Static vm:NavItem.Settings}"
ToolTip.Tip="Einstellungen (Strg/⌘+8)" AutomationProperties.Name="Einstellungen">
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10"> <StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
<TextBlock Classes="navicon" Text="⚙️" TextAlignment="Center"/> <PathIcon Classes="navicon" Data="{StaticResource IconSettings}"/>
<TextBlock Classes="navlabel" Text="Einstellungen"/> <TextBlock Classes="navlabel" Text="Einstellungen"/>
</StackPanel> </StackPanel>
</Button> </Button>
@@ -36,6 +36,13 @@ public partial class MainWindow : Window
return; return;
} }
if (commandModifier && TryGetNavigationShortcut(e.Key, out var destination))
{
vm.NavigateToCommand.Execute(destination);
e.Handled = true;
return;
}
if (!vm.IsCommandPaletteOpen) return; if (!vm.IsCommandPaletteOpen) return;
if (e.Key == Key.Escape) if (e.Key == Key.Escape)
{ {
@@ -54,6 +61,23 @@ public partial class MainWindow : Window
} }
} }
private static bool TryGetNavigationShortcut(Key key, out NavItem destination)
{
destination = key switch
{
Key.D1 or Key.NumPad1 => NavItem.Dashboard,
Key.D2 or Key.NumPad2 => NavItem.Groups,
Key.D3 or Key.NumPad3 => NavItem.Students,
Key.D4 or Key.NumPad4 => NavItem.Exams,
Key.D5 or Key.NumPad5 => NavItem.Planner,
Key.D6 or Key.NumPad6 => NavItem.Workload,
Key.D7 or Key.NumPad7 => NavItem.ClassTeacher,
Key.D8 or Key.NumPad8 => NavItem.Settings,
_ => default,
};
return key is >= Key.D1 and <= Key.D8 or >= Key.NumPad1 and <= Key.NumPad8;
}
private void OnOpenCommandPaletteClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e) private void OnOpenCommandPaletteClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{ {
if (DataContext is MainWindowViewModel vm) if (DataContext is MainWindowViewModel vm)
@@ -154,9 +154,11 @@
<Grid ColumnDefinitions="Auto,Auto,*,Auto,Auto,Auto"> <Grid ColumnDefinitions="Auto,Auto,*,Auto,Auto,Auto">
<Button Grid.Column="0" Content="" FontWeight="Bold" Padding="10,4" <Button Grid.Column="0" Content="" FontWeight="Bold" Padding="10,4"
Command="{Binding PreviousWeekCommand}" ToolTip.Tip="Vorherige Woche"/> Command="{Binding PreviousWeekCommand}" ToolTip.Tip="Vorherige Woche"
AutomationProperties.Name="Vorherige Woche"/>
<Button Grid.Column="1" Content="" FontWeight="Bold" Padding="10,4" Margin="4,0,0,0" <Button Grid.Column="1" Content="" FontWeight="Bold" Padding="10,4" Margin="4,0,0,0"
Command="{Binding NextWeekCommand}" ToolTip.Tip="Nächste Woche"/> Command="{Binding NextWeekCommand}" ToolTip.Tip="Nächste Woche"
AutomationProperties.Name="Nächste Woche"/>
<TextBlock Grid.Column="2" FontSize="16" FontWeight="SemiBold" <TextBlock Grid.Column="2" FontSize="16" FontWeight="SemiBold"
VerticalAlignment="Center" Margin="12,0,0,0"> VerticalAlignment="Center" Margin="12,0,0,0">
<Run Text="Woche "/><Run Text="{Binding WeekRangeLabel}"/> <Run Text="Woche "/><Run Text="{Binding WeekRangeLabel}"/>
@@ -166,7 +168,8 @@
<Button Grid.Column="4" Content="Ausnahme eintragen" Margin="0,0,8,0" <Button Grid.Column="4" Content="Ausnahme eintragen" Margin="0,0,8,0"
Command="{Binding AddSubstitutionCommand}"/> Command="{Binding AddSubstitutionCommand}"/>
<Button Grid.Column="5" Content="⚙️" Command="{Binding OpenSettingsCommand}" <Button Grid.Column="5" Content="⚙️" Command="{Binding OpenSettingsCommand}"
ToolTip.Tip="Einstellungen (Ferien, Aufsichten, Stundenraster)"/> ToolTip.Tip="Einstellungen (Ferien, Aufsichten, Stundenraster)"
AutomationProperties.Name="Stundenplan-Einstellungen"/>
</Grid> </Grid>
<Border Background="#33F59E0B" BorderBrush="#F59E0B" BorderThickness="1" <Border Background="#33F59E0B" BorderBrush="#F59E0B" BorderThickness="1"
@@ -281,7 +284,7 @@
<Button Classes="weekCellMenuTrigger" Content="⋮" <Button Classes="weekCellMenuTrigger" Content="⋮"
HorizontalAlignment="Right" VerticalAlignment="Top" HorizontalAlignment="Right" VerticalAlignment="Top"
IsVisible="{Binding HasGroupId}" IsVisible="{Binding HasGroupId}"
ToolTip.Tip="Weitere Ziele…"> ToolTip.Tip="Weitere Ziele…" AutomationProperties.Name="Weitere Ziele">
<Button.Flyout> <Button.Flyout>
<MenuFlyout Placement="BottomEdgeAlignedRight"> <MenuFlyout Placement="BottomEdgeAlignedRight">
<MenuItem Header="▶ Unterrichtsansicht" IsVisible="{Binding HasLesson}" <MenuItem Header="▶ Unterrichtsansicht" IsVisible="{Binding HasLesson}"
@@ -404,7 +407,8 @@
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalContentAlignment="Center" Background="Transparent" HorizontalContentAlignment="Center" Background="Transparent"
Command="{Binding $parent[ItemsControl;1].((vm:TimetableViewModel)DataContext).EditCellCommand}" Command="{Binding $parent[ItemsControl;1].((vm:TimetableViewModel)DataContext).EditCellCommand}"
CommandParameter="{Binding}"/> CommandParameter="{Binding}"
AutomationProperties.Name="Stundenplan-Eintrag hinzufügen"/>
<Border Background="#D85A30" CornerRadius="8" Padding="5,1" <Border Background="#D85A30" CornerRadius="8" Padding="5,1"
IsVisible="{Binding HasBadge}" IsVisible="{Binding HasBadge}"
HorizontalAlignment="Right" VerticalAlignment="Top" Margin="2"> HorizontalAlignment="Right" VerticalAlignment="Top" Margin="2">
@@ -8,18 +8,21 @@
<Border Grid.Row="0" Padding="20,16" <Border Grid.Row="0" Padding="20,16"
BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}" BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
BorderThickness="0,0,0,1"> BorderThickness="0,0,0,1">
<Grid ColumnDefinitions="*,Auto,Auto,Auto"> <Grid RowDefinitions="Auto,Auto" ColumnDefinitions="*,Auto,Auto,Auto">
<shared:PageHeader Grid.Column="0" Title="Schüler" Subtitle="{Binding CountSummary}"/> <shared:PageHeader Grid.ColumnSpan="4" Title="Schüler" Subtitle="{Binding CountSummary}"/>
<CheckBox Grid.Column="1" Content="Inaktive anzeigen" <CheckBox Grid.Row="1" Grid.Column="1" Content="Inaktive anzeigen"
IsChecked="{Binding ShowInactive}" IsChecked="{Binding ShowInactive}"
VerticalAlignment="Center" Margin="0,0,12,0"/> VerticalAlignment="Center" Margin="0,10,12,0"/>
<Button Grid.Column="2" Content="⇩ Importieren…" Click="OnImportClick" <Button Grid.Row="1" Grid.Column="2" Content="⇩ Importieren…" Click="OnImportClick"
VerticalAlignment="Center" Margin="0,0,8,0"/> IsEnabled="{Binding !IsImporting}"
<Button Grid.Column="3" Content=" Neuer Schüler" VerticalAlignment="Center" Margin="0,10,8,0"
Command="{Binding AddStudentCommand}" VerticalAlignment="Center"/> AutomationProperties.Name="Schüler importieren"/>
<Button Grid.Row="1" Grid.Column="3" Content=" Neuer Schüler"
Command="{Binding AddStudentCommand}" VerticalAlignment="Center" Margin="0,10,0,0"/>
</Grid> </Grid>
</Border> </Border>
<DockPanel Grid.Row="1"> <Grid Grid.Row="1">
<DockPanel IsVisible="{Binding HasStudents}">
<TextBox DockPanel.Dock="Top" Text="{Binding SearchText}" <TextBox DockPanel.Dock="Top" Text="{Binding SearchText}"
PlaceholderText="Name oder Lerngruppe suchen…" Margin="16,10,16,4"/> PlaceholderText="Name oder Lerngruppe suchen…" Margin="16,10,16,4"/>
<DataGrid ItemsSource="{Binding Students}" <DataGrid ItemsSource="{Binding Students}"
@@ -45,5 +48,22 @@
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</DockPanel> </DockPanel>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="10"
IsVisible="{Binding HasNoStudents}">
<TextBlock Text="{Binding EmptyListMessage}" Classes="emptyhint" FontSize="15"
TextAlignment="Center"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="8"
IsVisible="{Binding SearchText, Converter={x:Static StringConverters.IsNullOrEmpty}}">
<Button Content=" Ersten Schüler anlegen" Command="{Binding AddStudentCommand}"/>
<Button Content="⇩ Schülerliste importieren" Click="OnImportClick"/>
</StackPanel>
</StackPanel>
<Border Background="#70000000" IsVisible="{Binding IsImporting}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="10">
<ProgressBar IsIndeterminate="True" Width="220" Height="5"/>
<TextBlock Text="Schülerliste wird analysiert …" Foreground="White"/>
</StackPanel>
</Border>
</Grid>
</Grid> </Grid>
</UserControl> </UserControl>
@@ -36,6 +36,7 @@ public partial class StudentListView : UserControl
}); });
if (files.Count == 0) return; if (files.Count == 0) return;
list.IsImporting = true;
try try
{ {
await using var source = await files[0].OpenReadAsync(); await using var source = await files[0].OpenReadAsync();
@@ -67,5 +68,9 @@ public partial class StudentListView : UserControl
{ {
App.Services.GetRequiredService<NotificationService>().ShowError(ex.Message); App.Services.GetRequiredService<NotificationService>().ShowError(ex.Message);
} }
finally
{
list.IsImporting = false;
}
} }
} }
+10 -3
View File
@@ -14,10 +14,17 @@
Foreground="#D32F2F"/> Foreground="#D32F2F"/>
<TextBlock Text="Sync angehalten" FontSize="10" Foreground="#D32F2F" Opacity="0.8"/> <TextBlock Text="Sync angehalten" FontSize="10" Foreground="#D32F2F" Opacity="0.8"/>
</StackPanel> </StackPanel>
<Button Grid.Column="1" Content="↻" FontSize="14" <Button Grid.Column="1" Classes="touchTarget"
Command="{Binding SyncNowCommand}" Command="{Binding SyncNowCommand}"
IsVisible="{Binding CanAttemptSync}" IsVisible="{Binding CanAttemptSync}"
Background="Transparent" Padding="6,4" Background="Transparent" Padding="8"
ToolTip.Tip="Jetzt synchronisieren"/> ToolTip.Tip="Jetzt synchronisieren" AutomationProperties.Name="Jetzt synchronisieren">
<Grid>
<PathIcon Data="{StaticResource IconRefresh}" Width="18" Height="18"
IsVisible="{Binding !IsSyncing}"/>
<ProgressBar Width="22" Height="4" IsIndeterminate="True"
IsVisible="{Binding IsSyncing}"/>
</Grid>
</Button>
</Grid> </Grid>
</UserControl> </UserControl>
+33 -11
View File
@@ -3514,15 +3514,18 @@ von 4.2.2) der Tab "Kürzel-Katalog" für die Von/Nach-Kürzel des Stundenverlau
## 14. UX-Querschnitt ## 14. UX-Querschnitt
- [ ] **14.1** Tastaturbedienung durchgängig: alle Hauptfunktionen ohne Maus erreichbar - [~] **14.1** Tastaturbedienung durchgängig: alle Hauptfunktionen ohne Maus erreichbar
(Vorbild: Mitarbeit-Schnelleingabe). (Vorbild: Mitarbeit-Schnelleingabe). **Hauptnavigation umgesetzt:** `Strg/⌘+1…8` öffnet
Dashboard, Lerngruppen, Schüler, Klausuren, Planung, Arbeitszeit, Klassenlehrer und
Einstellungen direkt; `Strg/⌘+K`, Pfeiltasten, Enter und Escape bedienen weiterhin die
globale Suche. Die vollständige Tastaturprüfung aller Fachdialoge bleibt offen.
- [x] **14.2** Globale Suche (Schüler, Gruppe, Klausur) über Tastenkürzel. - [x] **14.2** Globale Suche (Schüler, Gruppe, Klausur) über Tastenkürzel.
**Umsetzung:** `Strg+K` (Windows/Linux) bzw. `⌘K` (macOS) öffnet aus jeder Hauptansicht eine **Umsetzung:** `Strg+K` (Windows/Linux) bzw. `⌘K` (macOS) öffnet aus jeder Hauptansicht eine
modale Befehlspalette. `GlobalSearchViewModel` durchsucht lokal und ohne zusätzlichen Index modale Befehlspalette. `GlobalSearchViewModel` durchsucht lokal und ohne zusätzlichen Index
ausschließlich aktive Schüler/Lerngruppen sowie deren Klausuren und Aufgaben; Treffer springen direkt ausschließlich aktive Schüler/Lerngruppen sowie deren Klausuren und Aufgaben; Treffer springen direkt
ins Schülerdetail, Gruppendetail, den Klausuren-Tab oder die Aufgabenverwaltung. Pfeiltasten, ins Schülerdetail, Gruppendetail, den Klausuren-Tab oder die Aufgabenverwaltung. Pfeiltasten,
Enter und Escape bedienen die Palette vollständig ohne Maus. Bei leerer Suche stehen die Enter und Escape bedienen die Palette vollständig ohne Maus. Gruppentreffer werden
Gruppentreffer werden zusätzlich über den zugeordneten Fachnamen gefunden und zeigen diesen zusätzlich über den zugeordneten Fachnamen gefunden und zeigen diesen
im Untertitel, damit gleichnamige Klassen/Kurse eindeutig bleiben. Bei leerer Suche stehen die im Untertitel, damit gleichnamige Klassen/Kurse eindeutig bleiben. Bei leerer Suche stehen die
Schnellaktionen „Aufgabe anlegen“, „Erinnerung anlegen“, „Schüler anlegen“ und Schnellaktionen „Aufgabe anlegen“, „Erinnerung anlegen“, „Schüler anlegen“ und
„Lerngruppen-Eintrag“ bereit und „Lerngruppen-Eintrag“ bereit und
@@ -3570,8 +3573,15 @@ von 4.2.2) der Tab "Kürzel-Katalog" für die Von/Nach-Kürzel des Stundenverlau
[TrashTests.cs](LehrerApp.Data.Tests/TrashTests.cs) (Löschen/Wiederherstellen je [TrashTests.cs](LehrerApp.Data.Tests/TrashTests.cs) (Löschen/Wiederherstellen je
Repository, Sortierung, Bereinigung), 6 in Repository, Sortierung, Bereinigung), 6 in
[TrashViewModelTests.cs](LehrerApp.Desktop.Tests/TrashViewModelTests.cs). [TrashViewModelTests.cs](LehrerApp.Desktop.Tests/TrashViewModelTests.cs).
- [ ] **14.4** Ladeanzeigen bei längeren Operationen (Import, Sync, Export). - [~] **14.4** Ladeanzeigen bei längeren Operationen (Import, Sync, Export).
- [ ] **14.5** Leere Zustände mit Handlungsaufforderung statt leerer Tabellen. Schülerimporte blockieren die Liste während der asynchronen Dateianalyse mit Fortschritts-
anzeige und Status; der Sync-Button wechselt während des Abgleichs vom Vektoricon auf eine
indeterminierte Fortschrittsanzeige. Bereits vorhandene WebUntis-/KI-Ladevorgänge bleiben
erhalten. Eine einheitliche Anzeige für sämtliche PDF-/CSV-Exporte ist noch offen.
- [~] **14.5** Leere Zustände mit Handlungsaufforderung statt leerer Tabellen. Lerngruppen hatten
bereits „Erste Lerngruppe anlegen“; Schüler bieten jetzt „Ersten Schüler anlegen“ und
„Schülerliste importieren“, die Klausuren-Hauptseite erklärt den Anlageort und springt zu
den Lerngruppen. Fachspezifische Untertabellen werden schrittweise ergänzt.
- [~] **14.6** Fenstergröße und Spaltenbreiten über Sitzungen hinweg merken. - [~] **14.6** Fenstergröße und Spaltenbreiten über Sitzungen hinweg merken.
**Umsetzung (Fenstergröße):** **Umsetzung (Fenstergröße):**
@@ -3593,14 +3603,23 @@ von 4.2.2) der Tab "Kürzel-Katalog" für die Von/Nach-Kürzel des Stundenverlau
stabile Spalten-Identität, an der eine gespeicherte Breite verlässlich hängen könnte — stabile Spalten-Identität, an der eine gespeicherte Breite verlässlich hängen könnte —
eine generische Lösung ist damit kein "billiger" Zusatz, sondern ein eigener, größerer eine generische Lösung ist damit kein "billiger" Zusatz, sondern ein eigener, größerer
Umbau. Nicht in diesem Durchgang umgesetzt. Umbau. Nicht in diesem Durchgang umgesetzt.
- [ ] **14.7** Bedienung auf Touch-Geräten prüfen (Tablet im Unterricht). - [~] **14.7** Bedienung auf Touch-Geräten prüfen (Tablet im Unterricht). Hauptnavigation und
- [ ] **14.8** Responsive Layout und Windows-DPI prüfen (kleine Notebook-Auflösungen sowie Sync-Aktion haben mindestens 40 px große Ziele; besonders kleine Karten-/Menüaktionen wurden
vergrößert. Ein Test auf echter Tablet-Hardware bleibt offen.
- [~] **14.8** Responsive Layout und Windows-DPI prüfen (kleine Notebook-Auflösungen sowie
125/150/200 % Skalierung; starre Master-Detail-Spalten bei Bedarf stapeln). Der kompakte 125/150/200 % Skalierung; starre Master-Detail-Spalten bei Bedarf stapeln). Der kompakte
Drawer berücksichtigt bereits die schmalere verfügbare Breite mit reduziertem Außen-/ Drawer berücksichtigt bereits die schmalere verfügbare Breite mit reduziertem Außen-/
Innenabstand und einer eigenen Iconfläche, damit Windows-Emoji nicht abgeschnitten werden. Innenabstand und einer eigenen Iconfläche, damit Windows-Emoji nicht abgeschnitten werden.
- [ ] **14.9** Barrierefreiheit prüfen: Automation-Namen für Icon-Buttons, sichtbare Fokusrahmen, Das Hauptfenster kann jetzt bis 640×480 verkleinert werden und wechselt dadurch tatsächlich
in den Overlay-Drawer; zuvor verhinderte `MinWidth=900` exakt diesen Zustand. Aktionsleisten
in Dashboard, Schüler-, Gruppenliste und Gruppendetail umbrechen bzw. liegen unter dem Titel.
Ein echter Windows-DPI-Test bleibt offen.
- [~] **14.9** Barrierefreiheit prüfen: Automation-Namen für Icon-Buttons, sichtbare Fokusrahmen,
Kontraste und Status nicht ausschließlich über Farbe/Emoji vermitteln. Kontraste und Status nicht ausschließlich über Farbe/Emoji vermitteln.
- [ ] **14.10** Plattformübergreifend konsistentes SVG-/`PathIcon`-Set statt systemabhängiger Sichtbare Akzent-Fokusrahmen sind zentral für Buttons, Textfelder und Comboboxen definiert;
Hauptnavigation, Kalender-/Dashboard-Sortierung und zentrale Stundenplan-Iconaktionen haben
sprechende Automation-Namen und Tooltips. Die vollständige Prüfung aller Dialoge bleibt offen.
- [~] **14.10** Plattformübergreifend konsistentes SVG-/`PathIcon`-Set statt systemabhängiger
Emoji-Darstellung einführen. **Konkreter Bericht (Nutzer-Feedback):** Drawer-Icons erscheinen Emoji-Darstellung einführen. **Konkreter Bericht (Nutzer-Feedback):** Drawer-Icons erscheinen
auf einem Windows-PC gegen 22 Uhr einfarbig schwarz statt farbig — passend zum bekannten auf einem Windows-PC gegen 22 Uhr einfarbig schwarz statt farbig — passend zum bekannten
Windows-Verhalten, dass Emoji-Codepunkte je nach Font-Fallback statt der farbigen Windows-Verhalten, dass Emoji-Codepunkte je nach Font-Fallback statt der farbigen
@@ -3610,7 +3629,10 @@ von 4.2.2) der Tab "Kürzel-Katalog" für die Von/Nach-Kürzel des Stundenverlau
explizit `FontFamily="Segoe UI Emoji,Segoe UI Symbol,Segoe UI"` — auf anderen Plattformen explizit `FontFamily="Segoe UI Emoji,Segoe UI Symbol,Segoe UI"` — auf anderen Plattformen
folgenlos, da unbekannte Fontnamen einfach übersprungen werden. Nur eine Abmilderung für die folgenlos, da unbekannte Fontnamen einfach übersprungen werden. Nur eine Abmilderung für die
Drawer-Navigation, kein Nachweis der Ursache und keine Lösung für die übrigen Emoji im Rest Drawer-Navigation, kein Nachweis der Ursache und keine Lösung für die übrigen Emoji im Rest
der App — die eigentliche, dauerhafte Lösung bleibt dieser Punkt (echtes Icon-Set). der App. **Hauptnavigation jetzt dauerhaft gelöst:** Suche und alle acht Navigationsziele
verwenden zentral hinterlegte `StreamGeometry`/`PathIcon`-Ressourcen; auch der Sync-Button
verwendet ein Vektoricon. Fachaktionen im restlichen UI enthalten teilweise weiterhin Emoji
und werden in einem späteren, separaten Austausch migriert.
- [x] **14.11** Aktiven Navigationspunkt in der Seitenleiste sichtbar hervorheben; Zustand wird - [x] **14.11** Aktiven Navigationspunkt in der Seitenleiste sichtbar hervorheben; Zustand wird
über `MainWindowViewModel.ActiveNavItem` gesteuert. über `MainWindowViewModel.ActiveNavItem` gesteuert.
- [x] **14.12** Tab "Übersicht" im Kurs (`GroupDetailView`, bislang nur Platzhaltertext) gefüllt — - [x] **14.12** Tab "Übersicht" im Kurs (`GroupDetailView`, bislang nur Platzhaltertext) gefüllt —