using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using LehrerApp.Core.Interfaces; using LehrerApp.Core.Models; using LehrerApp.Core.Services; using System.Collections.ObjectModel; using System.Globalization; namespace LehrerApp.Desktop.ViewModels.Groups; // ── Dialog: Klausur anlegen / bearbeiten / duplizieren ─────────────────────── public partial class ExamDialogViewModel : ObservableObject { private readonly IExamRepository _exams; private readonly ICompetencyDomainRepository _competencyDomains; private readonly Guid _groupId; private readonly Guid? _subjectId; private readonly int _gradeLevel; private readonly Exam? _editingExam; private readonly List _gradingKey; [ObservableProperty] private string _title = ""; [ObservableProperty] private string _dateText = DateOnly.FromDateTime(DateTime.Today).ToString("dd.MM.yyyy"); [ObservableProperty] private string _subject = ""; [ObservableProperty] private int? _examNumber; [ObservableProperty] private string _notes = ""; [ObservableProperty] private string _validationMessage = ""; [ObservableProperty] private double _totalPoints; [ObservableProperty] private bool _hasCompetencyCatalog; [ObservableProperty] private bool _useWeighting; public bool TotalPointsWarning => TotalPoints <= 0; public string TotalPointsDisplay => $"{TotalPoints.ToString("0.##", CultureInfo.InvariantCulture)} Punkte gesamt"; public ObservableCollection Tasks { get; } = []; public Exam? Result { get; private set; } public string DialogTitle => _editingExam is null ? "Neue Klausur anlegen" : "Klausur bearbeiten"; public string SaveButtonText => _editingExam is null ? "Anlegen" : "Speichern"; public ExamDialogViewModel(IExamRepository exams, ICompetencyDomainRepository competencyDomains, Guid groupId, Guid? subjectId, int gradeLevel, GradingSystem gradingSystem, string defaultSubjectName, Exam? editingExam, Exam? duplicateSource) { _exams = exams; _competencyDomains = competencyDomains; _groupId = groupId; _subjectId = subjectId; _gradeLevel = gradeLevel; _editingExam = editingExam; HasCompetencyCatalog = subjectId.HasValue && _competencyDomains.GetBySubjectAndGrade(subjectId.Value, gradeLevel).Count > 0; var source = editingExam ?? duplicateSource; if (source is not null) { var isDuplicate = duplicateSource is not null; Title = isDuplicate ? $"{source.Title} (Kopie)" : source.Title; DateText = isDuplicate ? DateOnly.FromDateTime(DateTime.Today).ToString("dd.MM.yyyy") : source.Date.ToString("dd.MM.yyyy"); Subject = source.Subject; ExamNumber = source.ExamNumber; Notes = source.Notes ?? ""; _gradingKey = source.GradingKey .Select(k => new GradingKeyEntry { Grade = k.Grade, MinPercent = k.MinPercent }).ToList(); foreach (var t in source.Tasks.OrderBy(t => t.Nr)) AddTaskInternal(t.Title, t.MaxPoints, t.Weight, [.. t.CompetencyCodes]); UseWeighting = Tasks.Any(t => Math.Abs(t.Weight - 1.0) > 0.0001); } else { Subject = defaultSubjectName; _gradingKey = gradingSystem == GradingSystem.Grades1To6 ? GradingService.DefaultKey1To6() : GradingService.DefaultKey0To15(); } foreach (var t in Tasks) t.ShowWeight = UseWeighting; RecomputeTotals(); } partial void OnUseWeightingChanged(bool value) { foreach (var t in Tasks) t.ShowWeight = value; } [RelayCommand] private void AddTask() { AddTaskInternal(null, 0, 1.0, []); Tasks[^1].ShowWeight = UseWeighting; RecomputeTotals(); } private void AddTaskInternal(string? title, double maxPoints, double weight, List competencyCodes) { var item = new ExamTaskEditItem(title, maxPoints, weight, competencyCodes, BuildCompetencyTagGroups(competencyCodes)) { OnChanged = RecomputeTotals, OnRemove = RemoveTask, OnMoveUp = MoveTaskUp, OnMoveDown = MoveTaskDown, }; Tasks.Add(item); RenumberTasks(); } private List BuildCompetencyTagGroups(List selectedCodes) { var groups = new List(); if (!_subjectId.HasValue) return groups; var selected = selectedCodes.ToHashSet(); foreach (var domain in _competencyDomains.GetBySubjectAndGrade(_subjectId.Value, _gradeLevel)) { var group = new CompetencyTagGroup(domain.Name, domain.Code); foreach (var item in domain.Items.OrderBy(i => i.SortOrder)) group.Items.Add(new CompetencyTag(item.Code, item.Description, selected.Contains(item.Code))); if (group.Items.Count > 0) groups.Add(group); } return groups; } private void RemoveTask(ExamTaskEditItem item) { Tasks.Remove(item); RenumberTasks(); RecomputeTotals(); } private void MoveTaskUp(ExamTaskEditItem item) { var idx = Tasks.IndexOf(item); if (idx <= 0) return; Tasks.Move(idx, idx - 1); RenumberTasks(); } private void MoveTaskDown(ExamTaskEditItem item) { var idx = Tasks.IndexOf(item); if (idx < 0 || idx >= Tasks.Count - 1) return; Tasks.Move(idx, idx + 1); RenumberTasks(); } private void RenumberTasks() { for (var i = 0; i < Tasks.Count; i++) Tasks[i].Nr = i + 1; } private void RecomputeTotals() { TotalPoints = Tasks.Sum(t => t.MaxPoints); OnPropertyChanged(nameof(TotalPointsWarning)); OnPropertyChanged(nameof(TotalPointsDisplay)); } [RelayCommand] private void Save() { if (string.IsNullOrWhiteSpace(Title)) { ValidationMessage = "Titel erforderlich."; return; } if (!DateOnly.TryParseExact(DateText, "dd.MM.yyyy", null, DateTimeStyles.None, out var date)) { ValidationMessage = "Datum im Format TT.MM.JJJJ eingeben."; return; } Result = _editingExam ?? new Exam { GroupId = _groupId }; Result.Title = Title.Trim(); Result.Date = date; Result.Subject = Subject.Trim(); Result.ExamNumber = ExamNumber; Result.Notes = string.IsNullOrWhiteSpace(Notes) ? null : Notes.Trim(); Result.Tasks = Tasks.Select(t => t.ToModel()).ToList(); Result.GradingKey = _gradingKey; _exams.Save(Result); } } // ── Zeile im Aufgaben-Editor (1.2.1 / 1.2.2 / 1.2.4) ───────────────────────── public partial class ExamTaskEditItem : ObservableObject { [ObservableProperty] private int _nr; [ObservableProperty] private string _title = ""; [ObservableProperty] private double _maxPoints; [ObservableProperty] private double _weight = 1.0; [ObservableProperty] private bool _isCompetencyPanelOpen; [ObservableProperty] private bool _showWeight; public List CompetencyCodes { get; } public ObservableCollection CompetencyTagGroups { get; } = []; public string CompetencySummary => CompetencyCodes.Count == 0 ? "Keine Kompetenzen" : $"{CompetencyCodes.Count} Kompetenz(en)"; public Action? OnChanged { get; set; } public Action? OnRemove { get; set; } public Action? OnMoveUp { get; set; } public Action? OnMoveDown { get; set; } public ExamTaskEditItem(string? title, double maxPoints, double weight, List competencyCodes, List tagGroups) { _title = title ?? ""; _maxPoints = maxPoints; _weight = weight; CompetencyCodes = competencyCodes; foreach (var g in tagGroups) { foreach (var tag in g.Items) tag.OnChanged = OnCompetencyToggled; CompetencyTagGroups.Add(g); } } private void OnCompetencyToggled(string code, bool selected) { if (selected) { if (!CompetencyCodes.Contains(code)) CompetencyCodes.Add(code); } else CompetencyCodes.Remove(code); OnPropertyChanged(nameof(CompetencySummary)); OnChanged?.Invoke(); } [RelayCommand] private void ToggleCompetencyPanel() => IsCompetencyPanelOpen = !IsCompetencyPanelOpen; [RelayCommand] private void Remove() => OnRemove?.Invoke(this); [RelayCommand] private void MoveUp() => OnMoveUp?.Invoke(this); [RelayCommand] private void MoveDown() => OnMoveDown?.Invoke(this); partial void OnMaxPointsChanged(double value) => OnChanged?.Invoke(); public ExamTask ToModel() => new() { Nr = Nr, Title = string.IsNullOrWhiteSpace(Title) ? null : Title.Trim(), MaxPoints = MaxPoints, Weight = Weight, CompetencyCodes = CompetencyCodes, }; }