feat: unbenutzte Sitzplätze ausblenden können
Manche Räume sind unregelmäßig bestückt (z.B. eine Klasse mit 3 Reihen, in zwei davon fehlt ein Einzeltisch) - das feste Rows x Columns-Raster kennt aber keine per-Reihe abweichende Spaltenzahl. Neues Feld SeatingPlan.HiddenSeats für Plätze ohne physischen Tisch. Ein ausgeblendeter Platz bleibt Teil des Rasters (für die Spalten-/Reihenausrichtung von SeatingPlanPanel), wird im Ansichtsmodus aber nicht gerendert; im Bearbeitungsmodus bleibt er sichtbar (abgeblendet) mit einem Umschalt-Button direkt auf dem Platz. Nur leere Plätze lassen sich ausblenden; Zuweisen/ Verschieben eines Schülers auf einen ausgeblendeten Platz wird sowohl im ViewModel als auch im Drag&Drop-Ziel-Check verhindert. SeatingPlanRepository.Save filtert HiddenSeats zusätzlich auf das gültige Raster und blendet einen Platz automatisch wieder ein, falls er trotzdem belegt wird. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -167,6 +167,7 @@ public partial class SeatingPlanTabViewModel : ObservableObject
|
||||
ColumnGapWidths = Enumerable.Range(0, Math.Max(0, plan.Columns - 1))
|
||||
.Select(i => i < savedGapWidths.Count ? savedGapWidths[i] : 0)
|
||||
.ToArray();
|
||||
var hiddenSeats = plan.HiddenSeats ?? [];
|
||||
for (var row = 0; row < plan.Rows; row++)
|
||||
for (var column = 0; column < plan.Columns; column++)
|
||||
{
|
||||
@@ -175,8 +176,9 @@ public partial class SeatingPlanTabViewModel : ObservableObject
|
||||
? StudentSeatOption.Empty
|
||||
: StudentOptions.FirstOrDefault(o => o.StudentId == assignment.StudentId)
|
||||
?? StudentSeatOption.Empty;
|
||||
var isHidden = hiddenSeats.Any(h => h.Row == row && h.Column == column);
|
||||
Seats.Add(new SeatCellViewModel(row, column, StudentOptions, option, OnSeatChanged,
|
||||
CanEditLayout, ToggleSituationTag, IsEditable));
|
||||
CanEditLayout, ToggleSituationTag, IsEditable, isHidden, ToggleSeatHidden));
|
||||
}
|
||||
UpdateAssignmentSummary();
|
||||
RefreshSeatLessonData();
|
||||
@@ -275,7 +277,8 @@ public partial class SeatingPlanTabViewModel : ObservableObject
|
||||
|
||||
public void MoveSeat(SeatCellViewModel source, SeatCellViewModel target)
|
||||
{
|
||||
if (!CanEditLayout || source == target || !source.SelectedOption.StudentId.HasValue) return;
|
||||
if (!CanEditLayout || source == target || target.IsHidden
|
||||
|| !source.SelectedOption.StudentId.HasValue) return;
|
||||
var targetOption = target.SelectedOption;
|
||||
target.SetSelectionSilently(source.SelectedOption);
|
||||
source.SetSelectionSilently(targetOption);
|
||||
@@ -284,7 +287,7 @@ public partial class SeatingPlanTabViewModel : ObservableObject
|
||||
|
||||
public void AssignStudent(StudentSeatOption student, SeatCellViewModel target)
|
||||
{
|
||||
if (!CanEditLayout || !student.StudentId.HasValue) return;
|
||||
if (!CanEditLayout || target.IsHidden || !student.StudentId.HasValue) return;
|
||||
foreach (var other in Seats.Where(s => s != target && s.SelectedOption.StudentId == student.StudentId))
|
||||
other.SetSelectionSilently(StudentSeatOption.Empty);
|
||||
target.SetSelectionSilently(student);
|
||||
@@ -298,6 +301,26 @@ public partial class SeatingPlanTabViewModel : ObservableObject
|
||||
SaveSeatAssignments();
|
||||
}
|
||||
|
||||
/// <summary>Blendet einen leeren Platz aus/ein, für den im Raum physisch kein Tisch steht
|
||||
/// (z. B. eine Reihe mit einer Lücke). Nur für leere Plätze möglich, siehe
|
||||
/// SeatCellViewModel.CanToggleHidden.</summary>
|
||||
public void ToggleSeatHidden(SeatCellViewModel seat)
|
||||
{
|
||||
if (!CanEditLayout || seat.IsOccupied) return;
|
||||
seat.IsHidden = !seat.IsHidden;
|
||||
SaveHiddenSeats();
|
||||
}
|
||||
|
||||
private void SaveHiddenSeats()
|
||||
{
|
||||
if (_currentPlan is null) return;
|
||||
_currentPlan.HiddenSeats = Seats
|
||||
.Where(s => s.IsHidden)
|
||||
.Select(s => new HiddenSeat { Row = s.Row, Column = s.Column })
|
||||
.ToList();
|
||||
_plans.Save(_currentPlan);
|
||||
}
|
||||
|
||||
private void SaveSeatAssignments()
|
||||
{
|
||||
if (_currentPlan is null) return;
|
||||
@@ -405,6 +428,7 @@ public partial class SeatCellViewModel : ObservableObject
|
||||
private readonly Action<SeatCellViewModel> _onChanged;
|
||||
private bool _suppressChange;
|
||||
private readonly Action<SeatCellViewModel, string> _toggleSituationTag;
|
||||
private readonly Action<SeatCellViewModel> _toggleHidden;
|
||||
|
||||
[ObservableProperty] private StudentSeatOption _selectedOption;
|
||||
[ObservableProperty] private bool _isDropTarget;
|
||||
@@ -422,11 +446,28 @@ public partial class SeatCellViewModel : ObservableObject
|
||||
public bool ShowLessonOverview => IsOccupied && !CanEdit;
|
||||
[ObservableProperty] private bool _canRecordLesson;
|
||||
public bool IsOccupied => SelectedOption.StudentId.HasValue;
|
||||
public string StudentName => IsOccupied ? SelectedOption.DisplayName : "Freier Platz";
|
||||
public string StudentName => IsOccupied ? SelectedOption.DisplayName : IsHidden ? "Kein Tisch" : "Freier Platz";
|
||||
/// <summary>Kein Tisch an dieser Position im Raum vorhanden (siehe SeatingPlan.HiddenSeats).
|
||||
/// Bleibt außerhalb des Bearbeitungsmodus Teil des Rasters (für die Spaltenausrichtung), wird
|
||||
/// dort aber nicht gerendert - siehe ShowSeat.</summary>
|
||||
[ObservableProperty] private bool _isHidden;
|
||||
/// <summary>Im Ansichtsmodus werden ausgeblendete Plätze nicht gerendert; im Bearbeitungsmodus
|
||||
/// bleiben sie sichtbar (abgeblendet), damit sie wieder eingeblendet werden können.</summary>
|
||||
public bool ShowSeat => !IsHidden || CanEdit;
|
||||
/// <summary>Nur leere Plätze können ausgeblendet werden - ein belegter Platz müsste sonst
|
||||
/// erst geräumt werden, was beim bloßen Ausblenden überraschend wäre.</summary>
|
||||
public bool CanToggleHidden => CanEdit && !IsOccupied;
|
||||
public string HiddenToggleLabel => IsHidden ? "Tisch einblenden" : "Kein Tisch hier";
|
||||
/// <summary>Kombiniert LessonOpacity (Abwesenheits-Abblendung) mit dem Ausblenden-Zustand -
|
||||
/// Opacity ist im DataTemplate bereits lokal an LessonOpacity gebunden gewesen; ein lokal
|
||||
/// gebundener Wert überschreibt aber jeden Style-Setter für dieselbe Eigenschaft, daher muss
|
||||
/// die Abblendung für ausgeblendete Plätze hier statt per CSS-Klasse erfolgen.</summary>
|
||||
public double DisplayOpacity => IsHidden ? 0.4 : LessonOpacity;
|
||||
|
||||
public SeatCellViewModel(int row, int column, ObservableCollection<StudentSeatOption> options,
|
||||
StudentSeatOption selectedOption, Action<SeatCellViewModel> onChanged, bool canEdit,
|
||||
Action<SeatCellViewModel, string>? toggleSituationTag = null, bool canRecordLesson = false)
|
||||
Action<SeatCellViewModel, string>? toggleSituationTag = null, bool canRecordLesson = false,
|
||||
bool isHidden = false, Action<SeatCellViewModel>? toggleHidden = null)
|
||||
{
|
||||
Row = row;
|
||||
Column = column;
|
||||
@@ -436,6 +477,8 @@ public partial class SeatCellViewModel : ObservableObject
|
||||
_canEdit = canEdit;
|
||||
_toggleSituationTag = toggleSituationTag ?? ((_, _) => { });
|
||||
_canRecordLesson = canRecordLesson;
|
||||
_isHidden = isHidden;
|
||||
_toggleHidden = toggleHidden ?? (_ => { });
|
||||
foreach (var tag in SituationTagChoice.DefaultTags)
|
||||
SituationTags.Add(new SituationTagChoice(tag, false, value => _toggleSituationTag(this, value)));
|
||||
}
|
||||
@@ -445,10 +488,29 @@ public partial class SeatCellViewModel : ObservableObject
|
||||
OnPropertyChanged(nameof(IsOccupied));
|
||||
OnPropertyChanged(nameof(StudentName));
|
||||
OnPropertyChanged(nameof(ShowLessonOverview));
|
||||
OnPropertyChanged(nameof(CanToggleHidden));
|
||||
if (!_suppressChange) _onChanged(this);
|
||||
}
|
||||
|
||||
partial void OnCanEditChanged(bool value) => OnPropertyChanged(nameof(ShowLessonOverview));
|
||||
partial void OnCanEditChanged(bool value)
|
||||
{
|
||||
OnPropertyChanged(nameof(ShowLessonOverview));
|
||||
OnPropertyChanged(nameof(ShowSeat));
|
||||
OnPropertyChanged(nameof(CanToggleHidden));
|
||||
}
|
||||
|
||||
partial void OnIsHiddenChanged(bool value)
|
||||
{
|
||||
OnPropertyChanged(nameof(ShowSeat));
|
||||
OnPropertyChanged(nameof(StudentName));
|
||||
OnPropertyChanged(nameof(DisplayOpacity));
|
||||
OnPropertyChanged(nameof(HiddenToggleLabel));
|
||||
}
|
||||
|
||||
partial void OnLessonOpacityChanged(double value) => OnPropertyChanged(nameof(DisplayOpacity));
|
||||
|
||||
[RelayCommand]
|
||||
private void ToggleHidden() => _toggleHidden(this);
|
||||
|
||||
public void SetSelectionSilently(StudentSeatOption option)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user