fix: Dashboard-Kachelraster - Luecken und falsche Margins bei ausgeblendeten Kacheln
ApplyCardLayout() zaehlte bisher IsVisible statt EffectiveIsVisible, wodurch eine eingeschaltete aber leere HideWhenEmpty-Kachel weiterhin einen Rasterplatz belegte. Da im Alltag meist mehrere der sieben HideWhenEmpty-Kacheln leer sind, war das der Normalfall, nicht die Ausnahme. UpdateDashboardSummary() setzte zudem alle IsEmpty- Werte, ohne das Layout danach neu zu berechnen. Zusaetzlich hatte jede Kachel ihren Grid-Margin fest im XAML verdrahtet (links/rechts), obwohl Spalte und Zeile erst zur Laufzeit aus Sichtbarkeit und Reihenfolge berechnet werden - beim Ausblenden einer Kachel wanderten die Nachbarn in die andere Spalte, der Rinnstein sass dann auf der falschen Seite. DashboardCardOption.Margin leitet den Wert jetzt aus Column ab; das XAML bindet darauf statt fixer Werte. Drei Regressionstests decken beide Faelle ab.
This commit is contained in:
@@ -28,6 +28,57 @@ public sealed class DashboardViewModelTests
|
|||||||
Assert.True(vm.CalendarCard.EffectiveIsVisible);
|
Assert.True(vm.CalendarCard.EffectiveIsVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Regression: ApplyCardLayout zaehlte frueher IsVisible statt EffectiveIsVisible. Eine
|
||||||
|
/// eingeschaltete, aber leere HideWhenEmpty-Kachel belegte damit einen Rasterplatz, den das
|
||||||
|
/// Grid nie fuellt — im Alltag der Normalfall, weil meist mehrere Hinweiskacheln leer sind.
|
||||||
|
[Fact]
|
||||||
|
public void Kachelraster_LeereAusgeblendeteKacheln_HinterlassenKeineLuecke()
|
||||||
|
{
|
||||||
|
var group = new LearningGroup { Name = "9c" };
|
||||||
|
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||||
|
|
||||||
|
var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today });
|
||||||
|
|
||||||
|
// Ohne mindestens eine leer ausgeblendete Kachel wuerde der Test nichts pruefen.
|
||||||
|
Assert.False(vm.ExcusesCard.EffectiveIsVisible);
|
||||||
|
|
||||||
|
var belegtePlaetze = vm.DashboardCards.Where(c => c.EffectiveIsVisible)
|
||||||
|
.Select(c => c.Row * 2 + c.Column).OrderBy(slot => slot).ToList();
|
||||||
|
Assert.Equal(Enumerable.Range(0, belegtePlaetze.Count), belegtePlaetze);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void KachelMargin_FolgtDerBerechnetenSpalte()
|
||||||
|
{
|
||||||
|
var group = new LearningGroup { Name = "9c" };
|
||||||
|
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||||
|
|
||||||
|
var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today });
|
||||||
|
|
||||||
|
foreach (var card in vm.DashboardCards.Where(c => c.EffectiveIsVisible))
|
||||||
|
Assert.Equal(card.Column == 0
|
||||||
|
? new Avalonia.Thickness(0, 0, 8, 8)
|
||||||
|
: new Avalonia.Thickness(8, 0, 0, 8), card.Margin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Regression: der Margin hing fest im XAML an der Kachel. Wandert sie durch Aus-/Einblenden
|
||||||
|
/// einer vorherigen Kachel in die andere Spalte, sass der Rinnstein auf der falschen Seite.
|
||||||
|
[Fact]
|
||||||
|
public void KachelAusblenden_DrehtDenMarginDerNachfolgendenKachel()
|
||||||
|
{
|
||||||
|
var group = new LearningGroup { Name = "9c" };
|
||||||
|
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||||
|
|
||||||
|
var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today });
|
||||||
|
Assert.Equal(1, vm.TasksCard.Column);
|
||||||
|
Assert.Equal(new Avalonia.Thickness(8, 0, 0, 8), vm.TasksCard.Margin);
|
||||||
|
|
||||||
|
vm.TodayCard.IsVisible = false;
|
||||||
|
|
||||||
|
Assert.Equal(0, vm.TasksCard.Column);
|
||||||
|
Assert.Equal(new Avalonia.Thickness(0, 0, 8, 8), vm.TasksCard.Margin);
|
||||||
|
}
|
||||||
|
|
||||||
private static PeriodScheduleService NewPeriodSchedule()
|
private static PeriodScheduleService NewPeriodSchedule()
|
||||||
{
|
{
|
||||||
var tempPath = System.IO.Path.Combine(
|
var tempPath = System.IO.Path.Combine(
|
||||||
|
|||||||
@@ -284,6 +284,10 @@ public partial class DashboardViewModel : ObservableObject
|
|||||||
SupportCard.IsEmpty = SupportPlanReviews.Count == 0;
|
SupportCard.IsEmpty = SupportPlanReviews.Count == 0;
|
||||||
GroupsCard.IsEmpty = CurrentGroups.Count == 0;
|
GroupsCard.IsEmpty = CurrentGroups.Count == 0;
|
||||||
|
|
||||||
|
// Erst nachdem alle IsEmpty-Werte stehen: welche Kachel tatsaechlich gerendert wird, haengt
|
||||||
|
// ueber EffectiveIsVisible daran, und davon wiederum die Zeilen-/Spaltenzuordnung.
|
||||||
|
ApplyCardLayout();
|
||||||
|
|
||||||
OnPropertyChanged(nameof(TodayLessonCount));
|
OnPropertyChanged(nameof(TodayLessonCount));
|
||||||
OnPropertyChanged(nameof(OpenTaskCount));
|
OnPropertyChanged(nameof(OpenTaskCount));
|
||||||
OnPropertyChanged(nameof(UpcomingCount));
|
OnPropertyChanged(nameof(UpcomingCount));
|
||||||
@@ -673,12 +677,15 @@ public partial class DashboardViewModel : ObservableObject
|
|||||||
"missingteachingtime" => "Unterrichtszeit nacherfassen", _ => key,
|
"missingteachingtime" => "Unterrichtszeit nacherfassen", _ => key,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Zaehlt bewusst EffectiveIsVisible, nicht IsVisible: eine eingeschaltete, aber gerade leere
|
||||||
|
// HideWhenEmpty-Kachel wird nicht gerendert und darf deshalb auch keinen Rasterplatz belegen,
|
||||||
|
// sonst bleibt an ihrer Stelle eine Luecke im zweispaltigen Grid.
|
||||||
private void ApplyCardLayout()
|
private void ApplyCardLayout()
|
||||||
{
|
{
|
||||||
var visibleIndex = 0;
|
var visibleIndex = 0;
|
||||||
foreach (var card in DashboardCards)
|
foreach (var card in DashboardCards)
|
||||||
{
|
{
|
||||||
var index = card.IsVisible ? visibleIndex++ : 0;
|
var index = card.EffectiveIsVisible ? visibleIndex++ : 0;
|
||||||
card.Row = index / 2;
|
card.Row = index / 2;
|
||||||
card.Column = index % 2;
|
card.Column = index % 2;
|
||||||
}
|
}
|
||||||
@@ -1262,6 +1269,14 @@ public partial class DashboardCardOption : ObservableObject
|
|||||||
public bool EffectiveIsVisible => IsVisible && (!HideWhenEmpty || !IsEmpty);
|
public bool EffectiveIsVisible => IsVisible && (!HideWhenEmpty || !IsEmpty);
|
||||||
public Action? OnVisibilityChanged { get; set; }
|
public Action? OnVisibilityChanged { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Rinnstein zur jeweils anderen Rasterspalte. Muss aus der berechneten
|
||||||
|
/// <see cref="Column"/> kommen und darf nicht im XAML fest an der Kachel haengen: welche Kachel
|
||||||
|
/// links und welche rechts landet, entscheidet sich erst zur Laufzeit aus Reihenfolge und
|
||||||
|
/// Sichtbarkeit, ein fester Margin sitzt dann bei jeder Umschaltung auf der falschen Seite.</summary>
|
||||||
|
public Avalonia.Thickness Margin => Column == 0
|
||||||
|
? new Avalonia.Thickness(0, 0, 8, 8)
|
||||||
|
: new Avalonia.Thickness(8, 0, 0, 8);
|
||||||
|
|
||||||
public DashboardCardOption(string key, string title, bool isVisible)
|
public DashboardCardOption(string key, string title, bool isVisible)
|
||||||
{
|
{
|
||||||
Key = key;
|
Key = key;
|
||||||
@@ -1278,4 +1293,6 @@ public partial class DashboardCardOption : ObservableObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
partial void OnIsEmptyChanged(bool value) => OnPropertyChanged(nameof(EffectiveIsVisible));
|
partial void OnIsEmptyChanged(bool value) => OnPropertyChanged(nameof(EffectiveIsVisible));
|
||||||
|
|
||||||
|
partial void OnColumnChanged(int value) => OnPropertyChanged(nameof(Margin));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,7 +146,7 @@
|
|||||||
|
|
||||||
<!-- Heutige Stunden -->
|
<!-- Heutige Stunden -->
|
||||||
<Border Grid.Column="{Binding TodayCard.Column}" Grid.Row="{Binding TodayCard.Row}"
|
<Border Grid.Column="{Binding TodayCard.Column}" Grid.Row="{Binding TodayCard.Row}"
|
||||||
IsVisible="{Binding TodayCard.EffectiveIsVisible}" Margin="0,0,8,8"
|
IsVisible="{Binding TodayCard.EffectiveIsVisible}" Margin="{Binding TodayCard.Margin}"
|
||||||
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
||||||
CornerRadius="8" Padding="16">
|
CornerRadius="8" Padding="16">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
@@ -187,7 +187,7 @@
|
|||||||
|
|
||||||
<!-- Offene Aufgaben -->
|
<!-- Offene Aufgaben -->
|
||||||
<Border Grid.Column="{Binding TasksCard.Column}" Grid.Row="{Binding TasksCard.Row}"
|
<Border Grid.Column="{Binding TasksCard.Column}" Grid.Row="{Binding TasksCard.Row}"
|
||||||
IsVisible="{Binding TasksCard.EffectiveIsVisible}" Margin="8,0,0,8"
|
IsVisible="{Binding TasksCard.EffectiveIsVisible}" Margin="{Binding TasksCard.Margin}"
|
||||||
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
||||||
CornerRadius="8" Padding="16">
|
CornerRadius="8" Padding="16">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
@@ -224,7 +224,7 @@
|
|||||||
<!-- Kalender: feste Position direkt unter Heute/Aufgaben, damit die wachsende
|
<!-- Kalender: feste Position direkt unter Heute/Aufgaben, damit die wachsende
|
||||||
Lerngruppen-Liste darunter ihn nicht nach unten verdrängt. -->
|
Lerngruppen-Liste darunter ihn nicht nach unten verdrängt. -->
|
||||||
<Border Grid.Column="{Binding CalendarCard.Column}" Grid.Row="{Binding CalendarCard.Row}"
|
<Border Grid.Column="{Binding CalendarCard.Column}" Grid.Row="{Binding CalendarCard.Row}"
|
||||||
IsVisible="{Binding CalendarCard.EffectiveIsVisible}" Margin="0,0,8,8"
|
IsVisible="{Binding CalendarCard.EffectiveIsVisible}" Margin="{Binding CalendarCard.Margin}"
|
||||||
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
||||||
CornerRadius="8" Padding="16" HorizontalAlignment="Left" MaxWidth="320">
|
CornerRadius="8" Padding="16" HorizontalAlignment="Left" MaxWidth="320">
|
||||||
<StackPanel Spacing="8">
|
<StackPanel Spacing="8">
|
||||||
@@ -368,7 +368,7 @@
|
|||||||
|
|
||||||
<!-- Offene Entschuldigungen: neben dem Kalender, ebenfalls feste Position -->
|
<!-- Offene Entschuldigungen: neben dem Kalender, ebenfalls feste Position -->
|
||||||
<Border Grid.Column="{Binding ExcusesCard.Column}" Grid.Row="{Binding ExcusesCard.Row}"
|
<Border Grid.Column="{Binding ExcusesCard.Column}" Grid.Row="{Binding ExcusesCard.Row}"
|
||||||
IsVisible="{Binding ExcusesCard.EffectiveIsVisible}" Margin="8,0,0,8" VerticalAlignment="Top"
|
IsVisible="{Binding ExcusesCard.EffectiveIsVisible}" Margin="{Binding ExcusesCard.Margin}" VerticalAlignment="Top"
|
||||||
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
||||||
CornerRadius="8" Padding="16">
|
CornerRadius="8" Padding="16">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
@@ -402,7 +402,7 @@
|
|||||||
|
|
||||||
<!-- Fehlzeiten-Warnung (5.2.3) -->
|
<!-- Fehlzeiten-Warnung (5.2.3) -->
|
||||||
<Border Grid.Column="{Binding AttendanceCard.Column}" Grid.Row="{Binding AttendanceCard.Row}"
|
<Border Grid.Column="{Binding AttendanceCard.Column}" Grid.Row="{Binding AttendanceCard.Row}"
|
||||||
IsVisible="{Binding AttendanceCard.EffectiveIsVisible}" Margin="0,0,8,8" VerticalAlignment="Top"
|
IsVisible="{Binding AttendanceCard.EffectiveIsVisible}" Margin="{Binding AttendanceCard.Margin}" VerticalAlignment="Top"
|
||||||
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
||||||
CornerRadius="8" Padding="16">
|
CornerRadius="8" Padding="16">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
@@ -432,7 +432,7 @@
|
|||||||
|
|
||||||
<!-- Klausurwochen (Nutzer-Feedback): eigene Klausurlast über alle Kurse hinweg -->
|
<!-- Klausurwochen (Nutzer-Feedback): eigene Klausurlast über alle Kurse hinweg -->
|
||||||
<Border Grid.Column="{Binding ExamLoadCard.Column}" Grid.Row="{Binding ExamLoadCard.Row}"
|
<Border Grid.Column="{Binding ExamLoadCard.Column}" Grid.Row="{Binding ExamLoadCard.Row}"
|
||||||
IsVisible="{Binding ExamLoadCard.EffectiveIsVisible}" Margin="0,0,8,8" VerticalAlignment="Top"
|
IsVisible="{Binding ExamLoadCard.EffectiveIsVisible}" Margin="{Binding ExamLoadCard.Margin}" VerticalAlignment="Top"
|
||||||
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
||||||
CornerRadius="8" Padding="16">
|
CornerRadius="8" Padding="16">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
@@ -456,7 +456,7 @@
|
|||||||
|
|
||||||
<!-- Unterrichtszeit nacherfassen (Nutzer-Feedback) -->
|
<!-- Unterrichtszeit nacherfassen (Nutzer-Feedback) -->
|
||||||
<Border Grid.Column="{Binding MissingTeachingTimeCard.Column}" Grid.Row="{Binding MissingTeachingTimeCard.Row}"
|
<Border Grid.Column="{Binding MissingTeachingTimeCard.Column}" Grid.Row="{Binding MissingTeachingTimeCard.Row}"
|
||||||
IsVisible="{Binding MissingTeachingTimeCard.EffectiveIsVisible}" Margin="0,0,8,8" VerticalAlignment="Top"
|
IsVisible="{Binding MissingTeachingTimeCard.EffectiveIsVisible}" Margin="{Binding MissingTeachingTimeCard.Margin}" VerticalAlignment="Top"
|
||||||
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
||||||
CornerRadius="8" Padding="16">
|
CornerRadius="8" Padding="16">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
@@ -482,7 +482,7 @@
|
|||||||
|
|
||||||
<!-- Förderplan-Wiedervorlage (5.3.2) -->
|
<!-- Förderplan-Wiedervorlage (5.3.2) -->
|
||||||
<Border Grid.Column="{Binding SupportCard.Column}" Grid.Row="{Binding SupportCard.Row}"
|
<Border Grid.Column="{Binding SupportCard.Column}" Grid.Row="{Binding SupportCard.Row}"
|
||||||
IsVisible="{Binding SupportCard.EffectiveIsVisible}" Margin="8,0,0,8" VerticalAlignment="Top"
|
IsVisible="{Binding SupportCard.EffectiveIsVisible}" Margin="{Binding SupportCard.Margin}" VerticalAlignment="Top"
|
||||||
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
||||||
CornerRadius="8" Padding="16">
|
CornerRadius="8" Padding="16">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
@@ -513,7 +513,7 @@
|
|||||||
|
|
||||||
<!-- Anstehende Termine (9.3) -->
|
<!-- Anstehende Termine (9.3) -->
|
||||||
<Border Grid.Column="{Binding UpcomingCard.Column}" Grid.Row="{Binding UpcomingCard.Row}"
|
<Border Grid.Column="{Binding UpcomingCard.Column}" Grid.Row="{Binding UpcomingCard.Row}"
|
||||||
IsVisible="{Binding UpcomingCard.EffectiveIsVisible}" Margin="0,0,8,8" VerticalAlignment="Top"
|
IsVisible="{Binding UpcomingCard.EffectiveIsVisible}" Margin="{Binding UpcomingCard.Margin}" VerticalAlignment="Top"
|
||||||
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
||||||
CornerRadius="8" Padding="16">
|
CornerRadius="8" Padding="16">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
@@ -550,7 +550,7 @@
|
|||||||
|
|
||||||
<!-- Offene Korrekturen (9.4) -->
|
<!-- Offene Korrekturen (9.4) -->
|
||||||
<Border Grid.Column="{Binding CorrectionsCard.Column}" Grid.Row="{Binding CorrectionsCard.Row}"
|
<Border Grid.Column="{Binding CorrectionsCard.Column}" Grid.Row="{Binding CorrectionsCard.Row}"
|
||||||
IsVisible="{Binding CorrectionsCard.EffectiveIsVisible}" Margin="8,0,0,8" VerticalAlignment="Top"
|
IsVisible="{Binding CorrectionsCard.EffectiveIsVisible}" Margin="{Binding CorrectionsCard.Margin}" VerticalAlignment="Top"
|
||||||
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
||||||
CornerRadius="8" Padding="16">
|
CornerRadius="8" Padding="16">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
@@ -583,7 +583,7 @@
|
|||||||
|
|
||||||
<!-- Ungeplante Stunden -->
|
<!-- Ungeplante Stunden -->
|
||||||
<Border Grid.Column="{Binding UnplannedCard.Column}" Grid.Row="{Binding UnplannedCard.Row}"
|
<Border Grid.Column="{Binding UnplannedCard.Column}" Grid.Row="{Binding UnplannedCard.Row}"
|
||||||
IsVisible="{Binding UnplannedCard.EffectiveIsVisible}" Margin="0,0,8,8" VerticalAlignment="Top"
|
IsVisible="{Binding UnplannedCard.EffectiveIsVisible}" Margin="{Binding UnplannedCard.Margin}" VerticalAlignment="Top"
|
||||||
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
||||||
CornerRadius="8" Padding="16">
|
CornerRadius="8" Padding="16">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
@@ -613,7 +613,7 @@
|
|||||||
|
|
||||||
<!-- Auffälligkeiten (9.5) -->
|
<!-- Auffälligkeiten (9.5) -->
|
||||||
<Border Grid.Column="{Binding AlertsCard.Column}" Grid.Row="{Binding AlertsCard.Row}"
|
<Border Grid.Column="{Binding AlertsCard.Column}" Grid.Row="{Binding AlertsCard.Row}"
|
||||||
IsVisible="{Binding AlertsCard.EffectiveIsVisible}" Margin="0,0,8,8" VerticalAlignment="Top"
|
IsVisible="{Binding AlertsCard.EffectiveIsVisible}" Margin="{Binding AlertsCard.Margin}" VerticalAlignment="Top"
|
||||||
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
||||||
CornerRadius="8" Padding="16">
|
CornerRadius="8" Padding="16">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
@@ -647,7 +647,7 @@
|
|||||||
|
|
||||||
<!-- Meine Lerngruppen -->
|
<!-- Meine Lerngruppen -->
|
||||||
<Border Grid.Column="{Binding GroupsCard.Column}" Grid.Row="{Binding GroupsCard.Row}"
|
<Border Grid.Column="{Binding GroupsCard.Column}" Grid.Row="{Binding GroupsCard.Row}"
|
||||||
IsVisible="{Binding GroupsCard.EffectiveIsVisible}"
|
IsVisible="{Binding GroupsCard.EffectiveIsVisible}" Margin="{Binding GroupsCard.Margin}"
|
||||||
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
||||||
CornerRadius="8" Padding="16">
|
CornerRadius="8" Padding="16">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
|
|||||||
Reference in New Issue
Block a user