using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using LehrerApp.Core.Interfaces; using LehrerApp.Core.Models; using LehrerApp.Core.Services; namespace LehrerApp.Desktop.ViewModels.Planning; /// Zuweisen/Bearbeiten/Entfernen eines Stundenplan-Termins (4.3.3). public partial class TimetableSlotDialogViewModel : ObservableObject { private readonly ITimetableSlotRepository _slots; private readonly Dictionary _groupIdsByLabel; private readonly TimetableSlot? _editing; public DayOfWeek Weekday { get; } public int PeriodNumber { get; } public string WeekdayLabel { get; } public string DialogTitle { get; } public bool IsEditing => _editing is not null; [ObservableProperty] private string _selectedGroupName = ""; [ObservableProperty] private string _room = ""; [ObservableProperty] private string _groupError = ""; public string[] GroupOptions { get; } /// null = unverändert/Abbruch, sonst das neue/aktualisierte Ergebnis. public TimetableSlot? Result { get; private set; } public bool Deleted { get; private set; } public TimetableSlotDialogViewModel(ITimetableSlotRepository slots, IGroupRepository groups, ISubjectRepository subjects, SchoolYearService schoolYear, DayOfWeek weekday, int periodNumber, TimetableSlot? editing) { _slots = slots; _editing = editing; Weekday = weekday; PeriodNumber = periodNumber; WeekdayLabel = weekday switch { DayOfWeek.Monday => "Montag", DayOfWeek.Tuesday => "Dienstag", DayOfWeek.Wednesday => "Mittwoch", DayOfWeek.Thursday => "Donnerstag", DayOfWeek.Friday => "Freitag", _ => weekday.ToString(), }; DialogTitle = $"{WeekdayLabel}, {periodNumber}. Stunde"; var availableGroups = groups.GetBySchoolYear(schoolYear.CurrentSchoolYear()).OrderBy(g => g.Name).ToList(); var subjectNames = subjects.GetAll().ToDictionary(s => s.Id, s => s.Name); var nameCounts = availableGroups.GroupBy(g => g.Name).ToDictionary(g => g.Key, g => g.Count()); // Der Gruppenname allein ist nicht eindeutig: dieselbe Klasse in mehreren Fächern hat // mehrere LearningGroup-Datensätze mit demselben Namen. Bei einem Namenskonflikt wird // deshalb das Fach angehängt ("10c (Chemie)") — ohne diese Absicherung würde die // Dictionary-Befüllung unten mit einer ArgumentException abstürzen (echter Nutzer-Fehler). _groupIdsByLabel = new Dictionary(); var labeledGroups = new List<(string Label, Guid Id)>(); foreach (var g in availableGroups) { var label = nameCounts[g.Name] > 1 ? $"{g.Name} ({(g.SubjectId is { } sid && subjectNames.TryGetValue(sid, out var sn) ? sn : "ohne Fach")})" : g.Name; var uniqueLabel = label; var suffix = 2; while (_groupIdsByLabel.ContainsKey(uniqueLabel)) uniqueLabel = $"{label} ({suffix++})"; _groupIdsByLabel[uniqueLabel] = g.Id; labeledGroups.Add((uniqueLabel, g.Id)); } GroupOptions = labeledGroups.Select(x => x.Label).ToArray(); if (editing is not null) { SelectedGroupName = labeledGroups.FirstOrDefault(x => x.Id == editing.GroupId).Label ?? ""; Room = editing.Room ?? ""; } } [RelayCommand] private void Save() { GroupError = ""; if (string.IsNullOrWhiteSpace(SelectedGroupName) || !_groupIdsByLabel.TryGetValue(SelectedGroupName, out var groupId)) { GroupError = "Bitte eine Gruppe auswählen."; return; } var slot = _editing ?? new TimetableSlot { Weekday = Weekday, PeriodNumber = PeriodNumber }; slot.GroupId = groupId; slot.Room = string.IsNullOrWhiteSpace(Room) ? null : Room.Trim(); try { _slots.Save(slot); Result = slot; } catch (InvalidOperationException ex) { GroupError = ex.Message; } } [RelayCommand] private void Delete() { if (_editing is null) return; _slots.Delete(_editing.Id); Deleted = true; } }