Klassenlehrer-Tab: restliche Quick-Wins (responsive Balken, Tagesüberblick-Chart, Tooltips, Status-Symbol)

Balkenbreiten hingen bisher an einer im ViewModel hart codierten Pixelkonstante;
Grid.ColumnDefinitions ließ sich dafür nicht binden (AVLN3000, kein Setter bei
kompilierten Bindings), deshalb neuer FractionWidthConverter (MultiBinding
gegen die gerenderte Breite einer Track-Border statt Grid-Sternspalten).
Tagesüberblick zeigt jetzt denselben gestapelten Balken statt vier
Einzelzeilen. AbsenceTooltip und TodayUnexcusedPercent waren berechnet aber
ungebunden, jetzt sichtbar. Status-Zeilen bekommen zusätzlich zur Farbe ein
vorangestelltes Symbol (Barrierefreiheit).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-27 13:25:32 +02:00
co-authored by Claude Sonnet 5
parent 9d375caf14
commit b3b86f0cf4
6 changed files with 291 additions and 29 deletions
@@ -2,6 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:LehrerApp.Desktop.ViewModels.ClassTeacher"
xmlns:views="clr-namespace:LehrerApp.Desktop.Views.ClassTeacher"
xmlns:conv="clr-namespace:LehrerApp.Desktop.Converters"
x:Class="LehrerApp.Desktop.Views.ClassTeacher.ClassTeacherOverviewView"
x:DataType="vm:ClassTeacherOverviewViewModel">
@@ -98,7 +99,8 @@
Classes.danger="{Binding IsDangerStatus}"/>
<Border Grid.Column="1" Width="28" Height="28" CornerRadius="14"
BorderBrush="{DynamicResource AppAvatarBorderBrush}" BorderThickness="1"
HorizontalAlignment="Center" VerticalAlignment="Center">
HorizontalAlignment="Center" VerticalAlignment="Center"
ToolTip.Tip="{Binding AbsenceTooltip}">
<TextBlock Text="{Binding Initials}" FontSize="10"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
@@ -106,7 +108,9 @@
TextTrimming="CharacterEllipsis"/>
<StackPanel Grid.Column="3" VerticalAlignment="Center" Spacing="1"
ToolTip.Tip="{Binding YearSummaryTooltip}">
<TextBlock Text="{Binding StatusText}" Classes="status"
<!-- Quick-Win Barrierefreiheit: Status nicht ausschließlich über Farbe, sondern auch
über ein vorangestelltes Symbol bei Warnung/Gefahr (StatusTextWithGlyph). -->
<TextBlock Text="{Binding StatusTextWithGlyph}" Classes="status"
Classes.info="{Binding IsInfoStatus}"
Classes.warning="{Binding IsWarningStatus}"
Classes.danger="{Binding IsDangerStatus}"
@@ -178,7 +182,7 @@
<Grid ColumnDefinitions="36,*">
<TextBlock Text="!" FontSize="22" VerticalAlignment="Center"
Foreground="{DynamicResource AppStatusDangerBrush}"/>
<StackPanel Grid.Column="1"><TextBlock Text="{Binding TodayUnexcusedCount}" FontSize="22" FontWeight="SemiBold"/><TextBlock Text="unentschuldigt" FontSize="11" Opacity="0.6"/></StackPanel>
<StackPanel Grid.Column="1"><TextBlock Text="{Binding TodayUnexcusedCount}" FontSize="22" FontWeight="SemiBold"/><TextBlock Text="{Binding TodayUnexcusedSummaryLabel}" FontSize="11" Opacity="0.6"/></StackPanel>
</Grid>
</Border>
<Border Grid.Column="3" Classes="metric" Margin="0">
@@ -227,10 +231,58 @@
<Border Classes="sidePanel">
<StackPanel Spacing="9">
<TextBlock Text="Tagesüberblick" FontSize="14" FontWeight="SemiBold"/>
<Grid ColumnDefinitions="10,*,Auto,40"><Border Width="7" Height="7" CornerRadius="4" Classes="statusFill"/><TextBlock Grid.Column="1" Text="Anwesend" FontSize="12"/><TextBlock Grid.Column="2" Text="{Binding PresentCount}"/><TextBlock Grid.Column="3" Text="{Binding PresentPercent, StringFormat='{}{0} %'}" Opacity="0.6" HorizontalAlignment="Right"/></Grid>
<Grid ColumnDefinitions="10,*,Auto,40"><Border Width="7" Height="7" CornerRadius="4" Classes="statusFill warning"/><TextBlock Grid.Column="1" Text="Verspätet" FontSize="12"/><TextBlock Grid.Column="2" Text="{Binding LateCount}"/><TextBlock Grid.Column="3" Text="{Binding LatePercent, StringFormat='{}{0} %'}" Opacity="0.6" HorizontalAlignment="Right"/></Grid>
<Grid ColumnDefinitions="10,*,Auto,40"><Border Width="7" Height="7" CornerRadius="4" Classes="statusFill info"/><TextBlock Grid.Column="1" Text="Fehlzeit · entschuldigt" FontSize="12"/><TextBlock Grid.Column="2" Text="{Binding ExcusedAbsenceCount}"/><TextBlock Grid.Column="3" Text="{Binding ExcusedAbsencePercent, StringFormat='{}{0} %'}" Opacity="0.6" HorizontalAlignment="Right"/></Grid>
<Grid ColumnDefinitions="10,*,Auto,40"><Border Width="7" Height="7" CornerRadius="4" Classes="statusFill danger"/><TextBlock Grid.Column="1" Text="Fehlzeit · unentschuldigt" FontSize="12"/><TextBlock Grid.Column="2" Text="{Binding UnexcusedAbsenceCount}"/><TextBlock Grid.Column="3" Text="{Binding UnexcusedAbsencePercent, StringFormat='{}{0} %'}" Opacity="0.6" HorizontalAlignment="Right"/></Grid>
<!-- Quick-Win: ein gestapelter 100-%-Balken statt vier Einzelzeilen mit
separater Prozentspalte — Verteilung auf einen Blick statt aus vier
Prozentzahlen erschlossen. Jedes Segment multipliziert per MultiBinding
die gerenderte Breite der Track-Leiste (x:Name="DayOverviewTrack") mit
seinem Anteil 0…1 (FractionWidthConverter) — bleibt dadurch korrekt, egal
wie breit die Seitenspalte in XAML gerade ist. -->
<Grid Height="10" ToolTip.Tip="{Binding DayOverviewTooltip}">
<Border x:Name="DayOverviewTrack" Background="{DynamicResource AppTrackBackgroundBrush}"
CornerRadius="5"/>
<Border CornerRadius="5" ClipToBounds="True" HorizontalAlignment="Left">
<StackPanel Orientation="Horizontal">
<Border Classes="statusFill">
<Border.Width>
<MultiBinding Converter="{x:Static conv:FractionWidthConverter.Instance}">
<Binding ElementName="DayOverviewTrack" Path="Bounds.Width"/>
<Binding Path="PresentFraction"/>
</MultiBinding>
</Border.Width>
</Border>
<Border Classes="statusFill warning">
<Border.Width>
<MultiBinding Converter="{x:Static conv:FractionWidthConverter.Instance}">
<Binding ElementName="DayOverviewTrack" Path="Bounds.Width"/>
<Binding Path="LateFraction"/>
</MultiBinding>
</Border.Width>
</Border>
<Border Classes="statusFill info">
<Border.Width>
<MultiBinding Converter="{x:Static conv:FractionWidthConverter.Instance}">
<Binding ElementName="DayOverviewTrack" Path="Bounds.Width"/>
<Binding Path="ExcusedAbsenceFraction"/>
</MultiBinding>
</Border.Width>
</Border>
<Border Classes="statusFill danger">
<Border.Width>
<MultiBinding Converter="{x:Static conv:FractionWidthConverter.Instance}">
<Binding ElementName="DayOverviewTrack" Path="Bounds.Width"/>
<Binding Path="UnexcusedAbsenceFraction"/>
</MultiBinding>
</Border.Width>
</Border>
</StackPanel>
</Border>
</Grid>
<StackPanel Spacing="5">
<StackPanel Orientation="Horizontal" Spacing="6"><Border Width="7" Height="7" CornerRadius="4" Classes="statusFill"/><TextBlock Text="{Binding PresentSummaryLabel}" FontSize="12"/></StackPanel>
<StackPanel Orientation="Horizontal" Spacing="6"><Border Width="7" Height="7" CornerRadius="4" Classes="statusFill warning"/><TextBlock Text="{Binding LateSummaryLabel}" FontSize="12"/></StackPanel>
<StackPanel Orientation="Horizontal" Spacing="6"><Border Width="7" Height="7" CornerRadius="4" Classes="statusFill info"/><TextBlock Text="{Binding ExcusedAbsenceSummaryLabel}" FontSize="12"/></StackPanel>
<StackPanel Orientation="Horizontal" Spacing="6"><Border Width="7" Height="7" CornerRadius="4" Classes="statusFill danger"/><TextBlock Text="{Binding UnexcusedAbsenceSummaryLabel}" FontSize="12"/></StackPanel>
</StackPanel>
</StackPanel>
</Border>
@@ -242,17 +294,50 @@
<DataTemplate x:DataType="vm:ClassTeacherTrendDay">
<Grid ColumnDefinitions="42,*,24" Margin="0,2">
<TextBlock Text="{Binding DayLabel}" FontSize="11" Opacity="0.6" VerticalAlignment="Center"/>
<Grid Grid.Column="1" Height="8" VerticalAlignment="Center">
<Border Background="{DynamicResource AppTrackBackgroundBrush}" CornerRadius="4"/>
<Grid Grid.Column="1" Height="8" VerticalAlignment="Center"
ToolTip.Tip="{Binding DetailTooltip}">
<Border x:Name="TrendTrack" Background="{DynamicResource AppTrackBackgroundBrush}"
CornerRadius="4"/>
<!-- Gestapelter Balken: drei sich nicht überschneidende Segmente
(siehe ClassTeacherTrendDay), gemeinsam auf CornerRadius
zugeschnitten statt jedes Segment einzeln abzurunden. -->
<Border Width="{Binding TotalBarWidth}" HorizontalAlignment="Left"
CornerRadius="4" ClipToBounds="True">
(siehe ClassTeacherTrendDay). Jedes Segment multipliziert per
MultiBinding die gerenderte Breite von "TrendTrack" mit seinem
Anteil 0…1 (FractionWidthConverter) — bleibt damit auch dann
korrekt proportional, wenn die Seitenspalte in XAML mal eine
andere Breite bekommt. Der äußere Wrapper ist auf CornerRadius
zugeschnitten, damit nur die äußeren Kanten rund sind, nicht
jedes Segment einzeln. -->
<Border CornerRadius="4" ClipToBounds="True" HorizontalAlignment="Left">
<Border.Width>
<MultiBinding Converter="{x:Static conv:FractionWidthConverter.Instance}">
<Binding ElementName="TrendTrack" Path="Bounds.Width"/>
<Binding Path="TotalFraction"/>
</MultiBinding>
</Border.Width>
<StackPanel Orientation="Horizontal">
<Border Width="{Binding UnexcusedBarWidth}" Classes="statusFill danger"/>
<Border Width="{Binding LateExcusedBarWidth}" Classes="statusFill warning"/>
<Border Width="{Binding ExcusedBarWidth}" Classes="statusFill info"/>
<Border Classes="statusFill danger">
<Border.Width>
<MultiBinding Converter="{x:Static conv:FractionWidthConverter.Instance}">
<Binding ElementName="TrendTrack" Path="Bounds.Width"/>
<Binding Path="UnexcusedFraction"/>
</MultiBinding>
</Border.Width>
</Border>
<Border Classes="statusFill warning">
<Border.Width>
<MultiBinding Converter="{x:Static conv:FractionWidthConverter.Instance}">
<Binding ElementName="TrendTrack" Path="Bounds.Width"/>
<Binding Path="LateExcusedFraction"/>
</MultiBinding>
</Border.Width>
</Border>
<Border Classes="statusFill info">
<Border.Width>
<MultiBinding Converter="{x:Static conv:FractionWidthConverter.Instance}">
<Binding ElementName="TrendTrack" Path="Bounds.Width"/>
<Binding Path="ExcusedFraction"/>
</MultiBinding>
</Border.Width>
</Border>
</StackPanel>
</Border>
</Grid>