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:
2026-08-20 12:06:26 +02:00
co-authored by Claude Sonnet 5
parent 391628ae36
commit c5a1157a7d
8 changed files with 334 additions and 8 deletions
@@ -108,6 +108,7 @@ public class SeatingPlanRepository(LiteDbContext db) : ISeatingPlanRepository
plan.Room = plan.Room?.Trim() ?? "";
plan.Assignments ??= [];
plan.ColumnGapWidths ??= [];
plan.HiddenSeats ??= [];
if (plan.Name.Length == 0)
throw new ArgumentException("Der Name des Sitzplans darf nicht leer sein.");
if (plan.Rows is < 1 or > 10 || plan.Columns is < 1 or > 10)
@@ -141,6 +142,18 @@ public class SeatingPlanRepository(LiteDbContext db) : ISeatingPlanRepository
if (plan.Assignments.Any(a => !memberIds.Contains(a.StudentId)))
throw new InvalidOperationException("Der Sitzplan enthält einen Schüler, der nicht zur Lerngruppe gehört.");
// Ein ausgeblendeter Platz darf nie belegt sein - die Zuordnung eines Schülers ist
// wertvoller als das Ausblenden, deshalb wird hier stillschweigend wieder eingeblendet
// statt die Zuordnung zu verwerfen. Sollte über die UI (SeatingPlanTabViewModel verhindert
// das Belegen ausgeblendeter Plätze) nie vorkommen - reine Absicherung bei fehlerhaften
// oder von einem älteren Client synchronisierten Daten.
var assignedPositions = plan.Assignments.Select(a => (a.Row, a.Column)).ToHashSet();
plan.HiddenSeats = plan.HiddenSeats
.Where(h => h.Row >= 0 && h.Row < plan.Rows && h.Column >= 0 && h.Column < plan.Columns)
.Where(h => !assignedPositions.Contains((h.Row, h.Column)))
.DistinctBy(h => (h.Row, h.Column))
.ToList();
plan.UpdatedAt = DateTime.UtcNow;
db.SeatingPlans.Upsert(plan);
db.OnChange?.Invoke(nameof(SeatingPlan), plan.Id.ToString(), "Save", plan);