2 Commits
Author SHA1 Message Date
adminandClaude Sonnet 5 a36b1d1d40 fix: Aufsicht-Zeilen im Stundenplan deutlich schmaler als Stundenblöcke
UniformGrid erzwingt für alle Zellen dieselbe, vom größten Kind
bestimmte Höhe - eine Aufsicht-Zeile konnte dadurch nie schmaler
werden als eine Stundenzeile, auch nicht durch kleinere Inhalte.

Cells/WeekItems werden jetzt zusätzlich zeilenweise in neue
GridRows/WeekRows-Collections gruppiert (TimetableRowItem/
WeekRowItem, je eine eigene, ganz normal bindbare RowHeight -
Aufsicht-Zeilen 22px statt 46px/76px bei normalen Stunden-/
Kopfzeilen). Innerhalb jeder Zeile bleibt UniformGrid Columns="6"
für die Spaltenaufteilung. Grid.RowDefinitions ließ sich dafür nicht
per {Binding} setzen (Avalonia lehnt jede Bindungsform dafür mit
AVLN3000 ab) - deshalb dieser Umweg über echte Unterzeilen statt
eines einzelnen Grids mit dynamischer RowDefinitions-Bindung.

$parent[ItemsControl]-Bindings in den Zell-Templates mussten auf
$parent[ItemsControl;1] angepasst werden, da durch die neue
Verschachtelung sonst das innere Zeilen-ItemsControl statt des
äußeren (mit TimetableViewModel als DataContext) getroffen wird.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 09:14:14 +02:00
adminandClaude Sonnet 5 4510c56bb4 fix: Schnellbewerten-Dialog - Datenverlust, Pfeiltasten, Shortcuts
- GetRating las aus einem nie aktualisierten ParticipationEntry-
  Snapshot statt aus der laufend gepflegten Cells-Liste. Beim
  Zurückwechseln zu einem vorherigen Schüler zeigte der Dialog
  dadurch fälschlich keine Bewertung, obwohl sie tatsächlich
  gespeichert war. Jetzt liest GetRating aus Cells, das ungenutzte
  _entry-Feld entfernt.
- Pfeiltasten waren unbehandelt und fielen auf Avalonias
  Standard-Fokusnavigation durch: der Fokus sprang auf den
  Schließen-Button, ein Enter danach schloss den Dialog statt zum
  nächsten Schüler zu springen. Jetzt echte Navigation: ↑/↓ voriger/
  nächster Aspekt (neue PreviousAspect-Methode), ←/→ voriger/
  nächster Schüler - zugleich rechtsseitiges Pendant zu Q/W/E/R/T
  links. Hotkey-Legende im Dialog aktualisiert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 09:14:00 +02:00
6 changed files with 261 additions and 109 deletions
@@ -0,0 +1,48 @@
using LehrerApp.Core.Models;
using LehrerApp.Desktop.ViewModels.Groups;
using Xunit;
namespace LehrerApp.Desktop.Tests;
public sealed class QuickInputViewModelTests
{
[Fact]
public void ZurueckZuVorherigemSchueler_ZeigtBereitsGesetzteBewertung()
{
var aspects = new List<AspectColumnDef>
{
new(new ParticipationAspect { Key = "mitarbeit", Label = "Mitarbeit" }),
};
var rows = new List<ParticipationStudentRow>
{
new(Guid.NewGuid(), "Anna", new ParticipationEntry(), aspects, []),
new(Guid.NewGuid(), "Ben", new ParticipationEntry(), aspects, []),
};
var vm = new QuickInputViewModel(rows, aspects);
vm.SetRatingByNumber(4); // Scale5, 4. Stufe -> Rohwert 1 ("+"), Anna bewerten
vm.NextStudent(); // zu Ben
vm.PreviousStudent(); // zurück zu Anna
Assert.Equal(1, vm.AspectRows[0].Value);
}
[Fact]
public void VorherigerAspekt_SpringtRueckwaertsMitUmlauf()
{
var aspects = new List<AspectColumnDef>
{
new(new ParticipationAspect { Key = "a", Label = "A" }),
new(new ParticipationAspect { Key = "b", Label = "B" }),
};
var rows = new List<ParticipationStudentRow>
{
new(Guid.NewGuid(), "Anna", new ParticipationEntry(), aspects, []),
};
var vm = new QuickInputViewModel(rows, aspects);
vm.PreviousAspect(); // von Index 0 rückwärts -> letzter Aspekt (Umlauf)
Assert.Equal(1, vm.AspectIndex);
}
}
@@ -442,6 +442,19 @@ public sealed class TimetableViewModelTests
Assert.DoesNotContain(vm.Cells, c => c.IsSupervisionRow); Assert.DoesNotContain(vm.Cells, c => c.IsSupervisionRow);
} }
[Fact]
public void Load_AufsichtEingetragen_BekommtSchmalereZeilenhoeheAlsStundenzeilen()
{
var duties = new FakeSupervisionDuties();
duties.Add(new SupervisionDuty { Weekday = DayOfWeek.Monday, AfterPeriod = 2, Location = "Pausenhof" });
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), supervisionDuties: duties);
var row = vm.GridRows.Single(r => r.Cells.Any(c => c.IsSupervisionRow && c.Text.Contains("n. 2.")));
Assert.Equal(22, row.RowHeight);
Assert.Contains(vm.GridRows, r => r.RowHeight == 46); // normale Stundenzeilen bleiben unverändert
}
// ── Aufsicht + Vertretung im Wochenraster ──────────────────────────────── // ── Aufsicht + Vertretung im Wochenraster ────────────────────────────────
[Fact] [Fact]
@@ -457,6 +470,19 @@ public sealed class TimetableViewModelTests
Assert.False(mondayCell.IsSubstitutionSupervision); Assert.False(mondayCell.IsSubstitutionSupervision);
} }
[Fact]
public void Load_Wochenraster_AufsichtBekommtSchmalereZeilenhoeheAlsStundenzeilen()
{
var duties = new FakeSupervisionDuties();
duties.Add(new SupervisionDuty { Weekday = DayOfWeek.Monday, AfterPeriod = 2, Location = "Pausenhof" });
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), supervisionDuties: duties);
var row = vm.WeekRows.Single(r => r.Cells.Any(c => c.IsSupervisionRow && c.Text.Contains("n. 2.")));
Assert.Equal(22, row.RowHeight);
Assert.Contains(vm.WeekRows, r => r.RowHeight == 76);
}
[Fact] [Fact]
public void Load_Wochenraster_VertretungsaufsichtUeberschreibtRegulaereAnzeige() public void Load_Wochenraster_VertretungsaufsichtUeberschreibtRegulaereAnzeige()
{ {
@@ -351,7 +351,6 @@ public partial class ParticipationStudentRow : ObservableObject
public Guid StudentId { get; } public Guid StudentId { get; }
public string Name { get; } public string Name { get; }
private readonly ParticipationEntry _entry;
private readonly IReadOnlyList<AspectColumnDef> _aspectDefs; private readonly IReadOnlyList<AspectColumnDef> _aspectDefs;
public ObservableCollection<RatingCell> Cells { get; } = []; public ObservableCollection<RatingCell> Cells { get; } = [];
@@ -375,7 +374,6 @@ public partial class ParticipationStudentRow : ObservableObject
{ {
StudentId = id; StudentId = id;
Name = name; Name = name;
_entry = entry;
_aspectDefs = aspects; _aspectDefs = aspects;
_homework = HomeworkDisplay.Effective(entry); _homework = HomeworkDisplay.Effective(entry);
_attendance = entry.Attendance; _attendance = entry.Attendance;
@@ -397,8 +395,12 @@ public partial class ParticipationStudentRow : ObservableObject
} }
} }
// Liest aus Cells (per SetValue laufend aktuell gehalten), NICHT aus dem ursprünglichen
// ParticipationEntry-Snapshot: SetRating() schreibt nur in Cells + feuert OnRatingChanged
// (der Callback speichert über eine EIGENE, neu aus dem Repository geladene Entry-Instanz —
// der hier ursprünglich referenzierte Entry-Snapshot bekommt diese Änderung nie zu sehen).
public int? GetRating(string key) => public int? GetRating(string key) =>
_entry.Ratings.FirstOrDefault(r => r.Key == key)?.Value; Cells.FirstOrDefault(c => c.AspectKey == key)?.Value;
public void SetRating(string key, int? value) public void SetRating(string key, int? value)
{ {
@@ -743,8 +745,8 @@ public partial class QuickInputViewModel : ObservableObject
AspectValueType.Points => $"09 Punkte eingeben (bis {CurrentAspectMaxPoints()})", AspectValueType.Points => $"09 Punkte eingeben (bis {CurrentAspectMaxPoints()})",
_ => "15 bewerten", _ => "15 bewerten",
}; };
return $"{ratingHint} · Q/W/E/R/T Aspekt wählen · Leertaste nächster Aspekt · " + return $"{ratingHint} · Q/W/E/R/T Aspekt wählen · Leertaste/↓ nächster Aspekt · ↑ vorheriger Aspekt · " +
"Enter nächster Schüler · Backspace vorheriger · +/ anpassen · Esc schließen"; "Enter/→ nächster Schüler · Backspace/← vorheriger Schüler · +/ anpassen · Esc schließen";
} }
} }
@@ -862,6 +864,12 @@ public partial class QuickInputViewModel : ObservableObject
UpdateCurrentAspect(); UpdateCurrentAspect();
} }
public void PreviousAspect()
{
AspectIndex = (AspectIndex - 1 + _aspects.Count) % _aspects.Count;
UpdateCurrentAspect();
}
public void NextStudent() public void NextStudent()
{ {
if (StudentIndex >= _rows.Count - 1) return; if (StudentIndex >= _rows.Count - 1) return;
@@ -45,6 +45,7 @@ public partial class TimetableViewModel : ObservableObject
{ {
private const int FirstPeriod = 1; private const int FirstPeriod = 1;
private const int LastPeriod = 10; private const int LastPeriod = 10;
private const int GridColumns = 6; // Label/Kopf-Spalte + 5 Wochentage (Mo-Fr)
private static readonly DayOfWeek[] Weekdays = private static readonly DayOfWeek[] Weekdays =
[DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday]; [DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday];
private static readonly string[] WeekdayColorPalette = private static readonly string[] WeekdayColorPalette =
@@ -70,6 +71,15 @@ public partial class TimetableViewModel : ObservableObject
public ObservableCollection<TodaySpecialAssignmentItem> TodaySpecialAssignments { get; } = []; public ObservableCollection<TodaySpecialAssignmentItem> TodaySpecialAssignments { get; } = [];
public ObservableCollection<UpcomingExamItem> UpcomingExams { get; } = []; public ObservableCollection<UpcomingExamItem> UpcomingExams { get; } = [];
/// Cells/WeekItems zeilenweise gruppiert (GridColumns Zellen je Zeile), zusätzlich zur
/// flachen Liste - Grundlage für die zeilenweise Höhensteuerung in der View (Aufsicht-Zeilen
/// deutlich schmaler als Stunden-/Kopfzeilen). Grid.RowDefinitions lässt sich in Avalonia
/// nicht per {Binding} setzen (Compiler lehnt jede Binding-Form dafür ab, siehe
/// AVLN3000-Fehler „Unable to find suitable setter or adder for property RowDefinitions“) -
/// deshalb hier stattdessen echte Unterzeilen mit je eigener, ganz normal bindbarer Height.
public ObservableCollection<TimetableRowItem> GridRows { get; } = [];
public ObservableCollection<WeekRowItem> WeekRows { get; } = [];
[ObservableProperty] private int _activeTabIndex; [ObservableProperty] private int _activeTabIndex;
[ObservableProperty] private string _todayLabel = ""; [ObservableProperty] private string _todayLabel = "";
[ObservableProperty] private string _weekRangeLabel = ""; [ObservableProperty] private string _weekRangeLabel = "";
@@ -325,6 +335,10 @@ public partial class TimetableViewModel : ObservableObject
} }
AddWeekSupervisionRowIfAny(period, dutiesByPeriod, substitutionsThisWeek, dateByWeekday); AddWeekSupervisionRowIfAny(period, dutiesByPeriod, substitutionsThisWeek, dateByWeekday);
} }
WeekRows.Clear();
foreach (var rowCells in WeekItems.Chunk(GridColumns))
WeekRows.Add(new WeekRowItem(rowCells));
} }
private void AddWeekSupervisionRowIfAny(int afterPeriod, ILookup<int, SupervisionDuty> dutiesByPeriod, private void AddWeekSupervisionRowIfAny(int afterPeriod, ILookup<int, SupervisionDuty> dutiesByPeriod,
@@ -409,6 +423,10 @@ public partial class TimetableViewModel : ObservableObject
} }
AddGridSupervisionRowIfAny(period, dutiesByPeriod); AddGridSupervisionRowIfAny(period, dutiesByPeriod);
} }
GridRows.Clear();
foreach (var rowCells in Cells.Chunk(GridColumns))
GridRows.Add(new TimetableRowItem(rowCells));
} }
/// <summary>Aufsicht wird nur in den Einstellungen gepflegt (siehe SettingsView) — im /// <summary>Aufsicht wird nur in den Einstellungen gepflegt (siehe SettingsView) — im
@@ -571,6 +589,18 @@ public class TimetableCellItem
}; };
} }
/// <summary>
/// Eine Zeile im Bearbeiten-Raster (GridColumns Zellen). Aufsicht-Zeilen bekommen eine deutlich
/// schmalere RowHeight als normale Kopf-/Stundenzeilen - eine echte, per Height ganz normal
/// bindbare Eigenschaft je Unterzeile statt eines vollflächigen UniformGrid-Rasters, das allen
/// Zeilen zwangsläufig dieselbe Höhe geben würde.
/// </summary>
public class TimetableRowItem(IReadOnlyList<TimetableCellItem> cells)
{
public IReadOnlyList<TimetableCellItem> Cells { get; } = cells;
public double RowHeight { get; } = cells[0].IsSupervisionRow ? 22 : 46;
}
/// <summary>Zelle im schreibgeschützten Wochenraster der "Heute"-Ansicht.</summary> /// <summary>Zelle im schreibgeschützten Wochenraster der "Heute"-Ansicht.</summary>
public class WeekCellItem public class WeekCellItem
{ {
@@ -671,6 +701,13 @@ public class WeekCellItem
}; };
} }
/// <summary>Zeile im Wochenraster - siehe TimetableRowItem, dieselbe Rolle für WeekItems.</summary>
public class WeekRowItem(IReadOnlyList<WeekCellItem> cells)
{
public IReadOnlyList<WeekCellItem> Cells { get; } = cells;
public double RowHeight { get; } = cells[0].IsSupervisionRow ? 22 : 76;
}
public class HoursWarningItem(string groupName, int assigned, int expected) public class HoursWarningItem(string groupName, int assigned, int expected)
{ {
public string GroupName { get; } = groupName; public string GroupName { get; } = groupName;
@@ -47,6 +47,15 @@ public partial class ParticipationQuickInputDialog : Window
break; break;
case Key.Back: vm.PreviousStudent(); e.Handled = true; break; case Key.Back: vm.PreviousStudent(); e.Handled = true; break;
// Rechtsseitiges Pendant zu Q/W/E/R/T (links)/Enter/Backspace/Leertaste: ohne diese
// Fälle fallen Pfeiltasten auf Avalonias Standard-Fokusnavigation durch und der Fokus
// springt auf den Schließen-Button — ein Enter danach schließt dann den Dialog statt
// zum nächsten Schüler zu springen.
case Key.Up: vm.PreviousAspect(); e.Handled = true; break;
case Key.Down: vm.NextAspect(); e.Handled = true; break;
case Key.Right: vm.NextStudent(); e.Handled = true; break;
case Key.Left: vm.PreviousStudent(); e.Handled = true; break;
case Key.OemPlus or Key.Add: vm.IncrementRating(); e.Handled = true; break; case Key.OemPlus or Key.Add: vm.IncrementRating(); e.Handled = true; break;
case Key.OemMinus or Key.Subtract: vm.DecrementRating(); e.Handled = true; break; case Key.OemMinus or Key.Subtract: vm.DecrementRating(); e.Handled = true; break;
@@ -133,63 +133,77 @@
ToolTip.Tip="Einstellungen (Ferien, Aufsichten, Stundenraster)"/> ToolTip.Tip="Einstellungen (Ferien, Aufsichten, Stundenraster)"/>
</Grid> </Grid>
<ItemsControl ItemsSource="{Binding WeekItems}"> <!-- Zeilenweise (WeekRows) statt eines einzigen flachen UniformGrid: so kann jede
<ItemsControl.ItemsPanel> Zeile ihre eigene Höhe haben (RowHeight je WeekRowItem) - Aufsicht-Zeilen
<ItemsPanelTemplate><UniformGrid Columns="6"/></ItemsPanelTemplate> deutlich schmaler als Stunden-/Kopfzeilen. Grid.RowDefinitions lässt sich in
</ItemsControl.ItemsPanel> Avalonia nicht per {Binding} setzen (AVLN3000 "Unable to find suitable setter
or adder"), daher dieser Umweg statt eines einzelnen Grids mit dynamischer
RowDefinitions-Bindung. Die innere UniformGrid Columns="6" bleibt unverändert
für die Spaltenaufteilung innerhalb je einer Zeile. -->
<ItemsControl ItemsSource="{Binding WeekRows}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:WeekCellItem"> <DataTemplate x:DataType="vm:WeekRowItem">
<Grid Margin="2" MinHeight="76"> <ItemsControl ItemsSource="{Binding Cells}" Height="{Binding RowHeight}">
<Border Classes="weekheader" Classes.today="{Binding IsToday}" CornerRadius="4" <ItemsControl.ItemsPanel>
IsVisible="{Binding IsHeader}"> <ItemsPanelTemplate><UniformGrid Columns="6"/></ItemsPanelTemplate>
<TextBlock Text="{Binding Text}" FontWeight="SemiBold" FontSize="12" Opacity="0.75" </ItemsControl.ItemsPanel>
HorizontalAlignment="Center" VerticalAlignment="Center" Margin="4"/> <ItemsControl.ItemTemplate>
</Border> <DataTemplate x:DataType="vm:WeekCellItem">
<TextBlock Text="{Binding Text}" IsVisible="{Binding IsPeriodLabel}" <Grid Margin="2">
FontWeight="SemiBold" FontSize="13" <Border Classes="weekheader" Classes.today="{Binding IsToday}" CornerRadius="4"
HorizontalAlignment="Center" VerticalAlignment="Center"/> IsVisible="{Binding IsHeader}">
<TextBlock Text="{Binding Text}" IsVisible="{Binding IsSupervisionRow}" <TextBlock Text="{Binding Text}" FontWeight="SemiBold" FontSize="12" Opacity="0.75"
FontSize="10" FontWeight="SemiBold" Opacity="0.55" TextWrapping="Wrap" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="4"/>
HorizontalAlignment="Center" VerticalAlignment="Center"/> </Border>
<Border Classes="supervisioncell" Classes.substitution="{Binding IsSubstitutionSupervision}" <TextBlock Text="{Binding Text}" IsVisible="{Binding IsPeriodLabel}"
CornerRadius="4" Padding="4" IsVisible="{Binding IsSupervisionCell}" FontWeight="SemiBold" FontSize="13"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock Text="{Binding SupervisionLocation}" FontSize="10" Foreground="White" <TextBlock Text="{Binding Text}" IsVisible="{Binding IsSupervisionRow}"
HorizontalAlignment="Center" VerticalAlignment="Center" TextWrapping="Wrap" FontSize="10" FontWeight="SemiBold" Opacity="0.55" TextWrapping="Wrap"
IsVisible="{Binding HasSupervision}"/> HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border> <Border Classes="supervisioncell" Classes.substitution="{Binding IsSubstitutionSupervision}"
<Border Classes="timetablecell" Classes.assigned="{Binding IsAssigned}" CornerRadius="4" Padding="4,1" IsVisible="{Binding IsSupervisionCell}"
CornerRadius="6" IsVisible="{Binding IsSlotCell}"> HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Button Background="{Binding ColorHex}" IsVisible="{Binding IsAssigned}" <TextBlock Text="{Binding SupervisionLocation}" FontSize="9" Foreground="White"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalAlignment="Center" VerticalAlignment="Center"
HorizontalContentAlignment="Stretch" CornerRadius="6" Padding="6" TextWrapping="NoWrap" TextTrimming="CharacterEllipsis"
Command="{Binding $parent[ItemsControl].((vm:TimetableViewModel)DataContext).OpenGroupCommand}" IsVisible="{Binding HasSupervision}"/>
CommandParameter="{Binding GroupId}"> </Border>
<StackPanel Spacing="1"> <Border Classes="timetablecell" Classes.assigned="{Binding IsAssigned}"
<TextBlock FontSize="11" FontWeight="Bold" Foreground="White" TextWrapping="Wrap"> CornerRadius="6" IsVisible="{Binding IsSlotCell}">
<Run Text="{Binding SubjectLabel}"/><Run Text=" · "/><Run Text="{Binding GroupName}"/> <Button Background="{Binding ColorHex}" IsVisible="{Binding IsAssigned}"
</TextBlock> HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
<TextBlock Text="{Binding Room}" FontSize="10" Foreground="White" Opacity="0.9" HorizontalContentAlignment="Stretch" CornerRadius="6" Padding="6"
IsVisible="{Binding HasRoom}"/> Command="{Binding $parent[ItemsControl;1].((vm:TimetableViewModel)DataContext).OpenGroupCommand}"
<TextBlock Text="{Binding Topic}" FontSize="10" Foreground="White" Opacity="0.8" CommandParameter="{Binding GroupId}">
TextWrapping="Wrap" IsVisible="{Binding HasTopic}"/> <StackPanel Spacing="1">
<TextBlock Text="Ferien" FontSize="10" Foreground="White" FontWeight="SemiBold" <TextBlock FontSize="11" FontWeight="Bold" Foreground="White" TextWrapping="Wrap">
Opacity="0.9" IsVisible="{Binding IsHoliday}" Margin="0,2,0,0"/> <Run Text="{Binding SubjectLabel}"/><Run Text=" · "/><Run Text="{Binding GroupName}"/>
<TextBlock Text="Ausfall" FontSize="10" Foreground="White" FontWeight="SemiBold" </TextBlock>
Opacity="0.9" IsVisible="{Binding IsCancelled}" Margin="0,2,0,0"/> <TextBlock Text="{Binding Room}" FontSize="10" Foreground="White" Opacity="0.9"
<StackPanel Orientation="Horizontal" Spacing="3" Margin="0,2,0,0" IsVisible="{Binding HasRoom}"/>
IsVisible="{Binding !IsHoliday}"> <TextBlock Text="{Binding Topic}" FontSize="10" Foreground="White" Opacity="0.8"
<Border Background="#B71C1C" CornerRadius="7" Padding="4,0" IsVisible="{Binding HasHolidayBadge}"> TextWrapping="Wrap" IsVisible="{Binding HasTopic}"/>
<TextBlock Text="{Binding HolidayBadge}" FontSize="9" FontWeight="Bold" Foreground="White"/> <TextBlock Text="Ferien" FontSize="10" Foreground="White" FontWeight="SemiBold"
</Border> Opacity="0.9" IsVisible="{Binding IsHoliday}" Margin="0,2,0,0"/>
<TextBlock Text="📝" FontSize="11" IsVisible="{Binding HasExam}" ToolTip.Tip="Klausur"/> <TextBlock Text="Ausfall" FontSize="10" Foreground="White" FontWeight="SemiBold"
<TextBlock Text="⏰" FontSize="11" IsVisible="{Binding IsLastBeforeExam}" ToolTip.Tip="Letzte Stunde vor der Klausur"/> Opacity="0.9" IsVisible="{Binding IsCancelled}" Margin="0,2,0,0"/>
<TextBlock Text="🧪" FontSize="11" IsVisible="{Binding HasExperiment}" ToolTip.Tip="Experiment geplant"/> <StackPanel Orientation="Horizontal" Spacing="3" Margin="0,2,0,0"
</StackPanel> IsVisible="{Binding !IsHoliday}">
</StackPanel> <Border Background="#B71C1C" CornerRadius="7" Padding="4,0" IsVisible="{Binding HasHolidayBadge}">
</Button> <TextBlock Text="{Binding HolidayBadge}" FontSize="9" FontWeight="Bold" Foreground="White"/>
</Border> </Border>
</Grid> <TextBlock Text="📝" FontSize="11" IsVisible="{Binding HasExam}" ToolTip.Tip="Klausur"/>
<TextBlock Text="⏰" FontSize="11" IsVisible="{Binding IsLastBeforeExam}" ToolTip.Tip="Letzte Stunde vor der Klausur"/>
<TextBlock Text="🧪" FontSize="11" IsVisible="{Binding HasExperiment}" ToolTip.Tip="Experiment geplant"/>
</StackPanel>
</StackPanel>
</Button>
</Border>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
@@ -237,55 +251,65 @@
<ContentPage Header="Bearbeiten"> <ContentPage Header="Bearbeiten">
<ScrollViewer> <ScrollViewer>
<StackPanel Margin="32,20,32,28" Spacing="10"> <StackPanel Margin="32,20,32,28" Spacing="10">
<ItemsControl ItemsSource="{Binding Cells}"> <!-- Zeilenweise (GridRows) statt eines flachen UniformGrid, siehe Kommentar im
<ItemsControl.ItemsPanel> Wochenraster oben (Heute-Tab) - gleicher Grund (Grid.RowDefinitions lässt sich
<ItemsPanelTemplate><UniformGrid Columns="6"/></ItemsPanelTemplate> nicht per {Binding} setzen). -->
</ItemsControl.ItemsPanel> <ItemsControl ItemsSource="{Binding GridRows}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:TimetableCellItem"> <DataTemplate x:DataType="vm:TimetableRowItem">
<Grid Margin="2" MinHeight="46"> <ItemsControl ItemsSource="{Binding Cells}" Height="{Binding RowHeight}">
<TextBlock Text="{Binding Text}" IsVisible="{Binding IsHeader}" <ItemsControl.ItemsPanel>
FontWeight="SemiBold" FontSize="12" Opacity="0.6" <ItemsPanelTemplate><UniformGrid Columns="6"/></ItemsPanelTemplate>
HorizontalAlignment="Center" VerticalAlignment="Center"/> </ItemsControl.ItemsPanel>
<TextBlock Text="{Binding Text}" IsVisible="{Binding IsPeriodLabel}" <ItemsControl.ItemTemplate>
FontWeight="SemiBold" FontSize="13" <DataTemplate x:DataType="vm:TimetableCellItem">
HorizontalAlignment="Center" VerticalAlignment="Center"/> <Grid Margin="2">
<TextBlock Text="{Binding Text}" IsVisible="{Binding IsSupervisionRow}" <TextBlock Text="{Binding Text}" IsVisible="{Binding IsHeader}"
FontSize="10" FontWeight="SemiBold" Opacity="0.55" TextWrapping="Wrap" FontWeight="SemiBold" FontSize="12" Opacity="0.6"
HorizontalAlignment="Center" VerticalAlignment="Center"/> HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Border Classes="supervisioncell" CornerRadius="4" Padding="4" <TextBlock Text="{Binding Text}" IsVisible="{Binding IsPeriodLabel}"
IsVisible="{Binding IsSupervisionCell}" FontWeight="SemiBold" FontSize="13"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock Text="{Binding SupervisionLocation}" FontSize="10" Foreground="White" <TextBlock Text="{Binding Text}" IsVisible="{Binding IsSupervisionRow}"
HorizontalAlignment="Center" VerticalAlignment="Center" TextWrapping="Wrap" FontSize="10" FontWeight="SemiBold" Opacity="0.55" TextWrapping="Wrap"
IsVisible="{Binding HasSupervision}"/> HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border> <Border Classes="supervisioncell" CornerRadius="4" Padding="4,1"
<Border Classes="timetablecell" Classes.assigned="{Binding IsAssigned}" IsVisible="{Binding IsSupervisionCell}"
CornerRadius="6" IsVisible="{Binding IsSlotCell}"> HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Panel> <TextBlock Text="{Binding SupervisionLocation}" FontSize="9" Foreground="White"
<Button Background="{Binding ColorHex}" HorizontalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding IsAssigned}" TextWrapping="NoWrap" TextTrimming="CharacterEllipsis"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" IsVisible="{Binding HasSupervision}"/>
HorizontalContentAlignment="Center" CornerRadius="6" </Border>
Command="{Binding $parent[ItemsControl].((vm:TimetableViewModel)DataContext).EditCellCommand}" <Border Classes="timetablecell" Classes.assigned="{Binding IsAssigned}"
CommandParameter="{Binding}"> CornerRadius="6" IsVisible="{Binding IsSlotCell}">
<TextBlock Text="{Binding Text}" FontSize="11" Foreground="White" <Panel>
TextWrapping="Wrap" TextAlignment="Center"/> <Button Background="{Binding ColorHex}"
</Button> IsVisible="{Binding IsAssigned}"
<Button Content="+" Opacity="0.35" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
IsVisible="{Binding !IsAssigned}" HorizontalContentAlignment="Center" CornerRadius="6"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Command="{Binding $parent[ItemsControl;1].((vm:TimetableViewModel)DataContext).EditCellCommand}"
HorizontalContentAlignment="Center" Background="Transparent" CommandParameter="{Binding}">
Command="{Binding $parent[ItemsControl].((vm:TimetableViewModel)DataContext).EditCellCommand}" <TextBlock Text="{Binding Text}" FontSize="11" Foreground="White"
CommandParameter="{Binding}"/> TextWrapping="Wrap" TextAlignment="Center"/>
<Border Background="#D85A30" CornerRadius="8" Padding="5,1" </Button>
IsVisible="{Binding HasBadge}" <Button Content="+" Opacity="0.35"
HorizontalAlignment="Right" VerticalAlignment="Top" Margin="2"> IsVisible="{Binding !IsAssigned}"
<TextBlock Text="{Binding BadgeText}" FontSize="10" FontWeight="Bold" Foreground="White"/> HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
</Border> HorizontalContentAlignment="Center" Background="Transparent"
</Panel> Command="{Binding $parent[ItemsControl;1].((vm:TimetableViewModel)DataContext).EditCellCommand}"
</Border> CommandParameter="{Binding}"/>
</Grid> <Border Background="#D85A30" CornerRadius="8" Padding="5,1"
IsVisible="{Binding HasBadge}"
HorizontalAlignment="Right" VerticalAlignment="Top" Margin="2">
<TextBlock Text="{Binding BadgeText}" FontSize="10" FontWeight="Bold" Foreground="White"/>
</Border>
</Panel>
</Border>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>