using System.Collections.ObjectModel; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using LehrerApp.Core.Interfaces; using LehrerApp.Core.Models; using LehrerApp.Desktop.Services; namespace LehrerApp.Desktop.ViewModels.Planning; public sealed record WebUntisGroupOption(Guid Id, string DisplayName); public partial class WebUntisTimetableRow : ObservableObject { public required DayOfWeek Weekday { get; init; } public required int PeriodNumber { get; init; } public required string TimeLabel { get; init; } public required string UntisLabel { get; init; } public required string SuggestedGroupName { get; init; } public string? SubjectName { get; init; } public string? Room { get; init; } public ObservableCollection GroupOptions { get; } = []; [ObservableProperty] private WebUntisGroupOption? _selectedGroup; public string WeekdayLabel => Weekday switch { DayOfWeek.Monday => "Mo", DayOfWeek.Tuesday => "Di", DayOfWeek.Wednesday => "Mi", DayOfWeek.Thursday => "Do", DayOfWeek.Friday => "Fr", _ => Weekday.ToString()[..2], }; } public partial class WebUntisTimetableImportViewModel : ObservableObject { private readonly WebUntisIntegrationService _untis; private readonly WebUntisSettingsService _settings; private readonly ITimetableSlotRepository _slots; private readonly IGroupRepository _groups; public ObservableCollection Teachers { get; } = []; public ObservableCollection Rows { get; } = []; [ObservableProperty] private UntisTeacherDto? _selectedTeacher; [ObservableProperty] private DateTimeOffset? _weekDate = DateTimeOffset.Now; [ObservableProperty] private string _status = "Lehrkraft auswählen und Untis-Woche laden."; [ObservableProperty] private bool _busy; public bool Saved { get; private set; } public Func>? OnCreateGroup { get; set; } public WebUntisTimetableImportViewModel(WebUntisIntegrationService untis, WebUntisSettingsService settings, ITimetableSlotRepository slots, IGroupRepository groups) { _untis = untis; _settings = settings; _slots = slots; _groups = groups; } public async Task InitializeAsync() { Busy = true; try { foreach (var teacher in (await _untis.GetTeachersAsync()).Where(x => x.Active).OrderBy(x => x.DisplayName)) Teachers.Add(teacher); SelectedTeacher = Teachers.FirstOrDefault(x => x.UntisId == _settings.TeacherUntisId) ?? Teachers.FirstOrDefault(); Status = Teachers.Count == 0 ? "WebUntis hat keine Lehrkräfte geliefert." : "Bereit zum Laden."; } catch (WebUntisIntegrationException ex) { Status = ex.Message; } finally { Busy = false; } } [RelayCommand] private async Task Load() { if (SelectedTeacher is null) { Status = "Bitte eine Lehrkraft auswählen."; return; } Busy = true; Rows.Clear(); try { var selected = DateOnly.FromDateTime((WeekDate ?? DateTimeOffset.Now).LocalDateTime); var monday = selected.AddDays(-(((int)selected.DayOfWeek + 6) % 7)); var periods = await _untis.GetTimetableAsync(SelectedTeacher.UntisId, monday, monday.AddDays(6)); var grid = await _untis.GetTimeGridAsync(); var groupOptions = BuildGroupOptions(); var localSlots = _slots.GetAll().ToDictionary(x => (x.Weekday, x.PeriodNumber)); foreach (var period in periods.Where(x => string.IsNullOrWhiteSpace(x.Code) || x.Code != "cancelled") .GroupBy(x => (x.Date, x.StartTime, x.EndTime, x.StudentGroup, Class: string.Join("/", x.Classes.Select(c => c.Name)), Subject: string.Join("/", x.Subjects.Select(s => s.Name)), Room: string.Join("/", x.Rooms.Select(r => r.Name)))) .Select(x => x.First()).OrderBy(x => x.Date).ThenBy(x => x.StartTime)) { if (!TryDate(period.Date, out var date) || date.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday) continue; var dayGrid = grid.FirstOrDefault(x => x.Day == UntisDay(date.DayOfWeek)); var number = dayGrid?.TimeUnits.ToList().FindIndex(x => x.StartTime == period.StartTime) + 1 ?? 0; if (number <= 0) continue; var className = period.Classes.FirstOrDefault()?.Name ?? ""; var subject = period.Subjects.FirstOrDefault()?.Name; var suggested = !string.IsNullOrWhiteSpace(period.StudentGroup) ? period.StudentGroup! : className; var row = new WebUntisTimetableRow { Weekday = date.DayOfWeek, PeriodNumber = number, TimeLabel = $"{Time(period.StartTime)}–{Time(period.EndTime)}", UntisLabel = string.Join(" · ", new[] { subject, suggested, period.Rooms.FirstOrDefault()?.Name } .Where(x => !string.IsNullOrWhiteSpace(x))), SuggestedGroupName = suggested, SubjectName = subject, Room = period.Rooms.FirstOrDefault()?.Name, }; foreach (var option in groupOptions) row.GroupOptions.Add(option); if (localSlots.TryGetValue((row.Weekday, row.PeriodNumber), out var existing)) row.SelectedGroup = groupOptions.FirstOrDefault(x => x.Id == existing.GroupId); row.SelectedGroup ??= BestMatch(groupOptions, suggested, className, subject); Rows.Add(row); } _settings.SetTeacherUntisId(SelectedTeacher.UntisId); Status = Rows.Count == 0 ? "In dieser Woche wurde kein Unterricht gefunden." : $"{Rows.Count} regelmäßige Termine gefunden. Zuordnung prüfen und übernehmen."; } catch (WebUntisIntegrationException ex) { Status = ex.Message; } finally { Busy = false; } } [RelayCommand] private async Task CreateGroup(WebUntisTimetableRow row) { if (OnCreateGroup is null) return; var group = await OnCreateGroup(row); if (group is null) return; var option = new WebUntisGroupOption(group.Id, group.Name); foreach (var item in Rows.Where(x => x.SuggestedGroupName == row.SuggestedGroupName)) { item.GroupOptions.Add(option); item.SelectedGroup = option; } } [RelayCommand] private void Save() { var selected = Rows.Where(x => x.SelectedGroup is not null).ToList(); foreach (var row in selected) { var existing = _slots.GetAll().FirstOrDefault(x => x.Weekday == row.Weekday && x.PeriodNumber == row.PeriodNumber); var slot = existing ?? new TimetableSlot { Weekday = row.Weekday, PeriodNumber = row.PeriodNumber }; slot.GroupId = row.SelectedGroup!.Id; slot.Room = string.IsNullOrWhiteSpace(row.Room) ? null : row.Room; _slots.Save(slot); } Saved = true; Status = $"{selected.Count} Stundenplan-Einträge übernommen."; } private List BuildGroupOptions() => _groups.GetAll().OrderBy(x => x.Name) .Select(x => new WebUntisGroupOption(x.Id, x.Name)).ToList(); private static WebUntisGroupOption? BestMatch(IEnumerable options, params string?[] terms) => options.FirstOrDefault(x => terms.Any(term => !string.IsNullOrWhiteSpace(term) && (x.DisplayName.Equals(term, StringComparison.OrdinalIgnoreCase) || x.DisplayName.Contains(term, StringComparison.OrdinalIgnoreCase)))); private static int UntisDay(DayOfWeek day) => day == DayOfWeek.Sunday ? 7 : (int)day; private static bool TryDate(int value, out DateOnly date) => DateOnly.TryParseExact(value.ToString(), "yyyyMMdd", out date); private static string Time(int value) => $"{value / 100:00}:{value % 100:00}"; }