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.Text.Json; using System.Text.Json.Serialization; namespace LehrerApp.Desktop.ViewModels.Settings; // ── Haupt-ViewModel ─────────────────────────────────────────────────────────── public partial class SettingsViewModel : ObservableObject { private readonly ISubjectRepository _subjects; private readonly ICompetencyDomainRepository _domainRepo; private readonly IGradingKeyTemplateRepository _gradingKeyTemplates; private readonly GradingService _grading; // ── Fächer ──────────────────────────────────────────────────────────────── [ObservableProperty] private string _newName = ""; [ObservableProperty] private string _newShort = ""; [ObservableProperty] private string _validationMessage = ""; public ObservableCollection Subjects { get; } = []; // ── Kompetenzkatalog ────────────────────────────────────────────────────── [ObservableProperty] private SubjectListItem? _catalogSubject; [ObservableProperty] private int _catalogGradeLevel = 10; [ObservableProperty] private string _newDomainName = ""; [ObservableProperty] private string _newDomainCode = ""; [ObservableProperty] private string _catalogValidation = ""; public ObservableCollection Domains { get; } = []; // ── Notenschlüssel-Vorlagen (1.3.2) ────────────────────────────────────── [ObservableProperty] private string _newTemplateName = ""; [ObservableProperty] private string _newTemplateGradingSystemName = "Noten 1–6"; [ObservableProperty] private string _templateValidationMessage = ""; public List GradingSystemOptions { get; } = ["Noten 1–6", "Punkte 0–15"]; public ObservableCollection GradingKeyTemplateList { get; } = []; // ── Konstruktor ─────────────────────────────────────────────────────────── public SettingsViewModel(ISubjectRepository subjects, ICompetencyDomainRepository domainRepo, IGradingKeyTemplateRepository gradingKeyTemplates, GradingService grading) { _subjects = subjects; _domainRepo = domainRepo; _gradingKeyTemplates = gradingKeyTemplates; _grading = grading; LoadSubjects(); LoadGradingKeyTemplates(); } // ── Notenschlüssel-Vorlagen: Laden / Hinzufügen / Löschen ──────────────── private void LoadGradingKeyTemplates() { GradingKeyTemplateList.Clear(); foreach (var t in _gradingKeyTemplates.GetAll()) GradingKeyTemplateList.Add(new GradingKeyTemplateEditItem(t, _gradingKeyTemplates, _grading)); } [RelayCommand] private void AddGradingKeyTemplate() { if (string.IsNullOrWhiteSpace(NewTemplateName)) { TemplateValidationMessage = "Vorlagenname erforderlich."; return; } var system = NewTemplateGradingSystemName == "Punkte 0–15" ? GradingSystem.Points0To15 : GradingSystem.Grades1To6; var defaults = system == GradingSystem.Grades1To6 ? GradingService.DefaultKey1To6() : GradingService.DefaultKey0To15(); var template = new GradingKeyTemplate { Name = NewTemplateName.Trim(), GradingSystem = system, Entries = defaults.Select(e => new GradingKeyEntry { Grade = e.Grade, MinPercent = e.MinPercent }).ToList(), }; _gradingKeyTemplates.Save(template); GradingKeyTemplateList.Add(new GradingKeyTemplateEditItem(template, _gradingKeyTemplates, _grading)); NewTemplateName = ""; TemplateValidationMessage = ""; } [RelayCommand] private void DeleteGradingKeyTemplate(GradingKeyTemplateEditItem? item) { if (item is null) return; _gradingKeyTemplates.Delete(item.Id); GradingKeyTemplateList.Remove(item); } // ── Fächer: Laden / Hinzufügen / Löschen ───────────────────────────────── public void LoadSubjects() { Subjects.Clear(); foreach (var s in _subjects.GetAll()) Subjects.Add(new SubjectListItem(s)); } [RelayCommand] private void AddSubject() { if (string.IsNullOrWhiteSpace(NewName)) { ValidationMessage = "Name erforderlich."; return; } _subjects.Save(new Subject { Name = NewName.Trim(), ShortName = NewShort.Trim() }); NewName = ""; NewShort = ""; ValidationMessage = ""; LoadSubjects(); } [RelayCommand] private void DeleteSubject(SubjectListItem? item) { if (item is null) return; _subjects.Delete(item.Id); if (CatalogSubject?.Id == item.Id) CatalogSubject = null; LoadSubjects(); } // ── Katalog: Laden ──────────────────────────────────────────────────────── partial void OnCatalogSubjectChanged(SubjectListItem? value) => LoadCatalog(); partial void OnCatalogGradeLevelChanged(int value) => LoadCatalog(); private void LoadCatalog() { Domains.Clear(); CatalogValidation = ""; if (CatalogSubject is null) return; foreach (var d in _domainRepo.GetBySubjectAndGrade(CatalogSubject.Id, CatalogGradeLevel)) Domains.Add(new DomainEditItem(d, _domainRepo)); } // ── Katalog: Bereich hinzufügen / löschen ──────────────────────────────── [RelayCommand] private void AddDomain() { if (CatalogSubject is null) { CatalogValidation = "Bitte zuerst ein Fach auswählen."; return; } if (string.IsNullOrWhiteSpace(NewDomainName)) { CatalogValidation = "Bereichsname erforderlich."; return; } var domain = new CompetencyDomain { SubjectId = CatalogSubject.Id, GradeLevel = CatalogGradeLevel, Name = NewDomainName.Trim(), Code = NewDomainCode.Trim(), SortOrder = Domains.Count, }; _domainRepo.Save(domain); Domains.Add(new DomainEditItem(domain, _domainRepo)); NewDomainName = ""; NewDomainCode = ""; CatalogValidation = ""; } [RelayCommand] private void DeleteDomain(DomainEditItem? item) { if (item is null) return; _domainRepo.Delete(item.Id); Domains.Remove(item); } // ── JSON Import / Export ────────────────────────────────────────────────── public void ImportCatalog(string json) { if (CatalogSubject is null) { CatalogValidation = "Bitte zuerst ein Fach auswählen."; return; } try { var opts = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; var dto = JsonSerializer.Deserialize(json, opts); if (dto?.Domains is null) { CatalogValidation = "Ungültiges JSON-Format."; return; } _domainRepo.DeleteBySubjectAndGrade(CatalogSubject.Id, CatalogGradeLevel); for (int i = 0; i < dto.Domains.Count; i++) { var d = dto.Domains[i]; var domain = new CompetencyDomain { SubjectId = CatalogSubject.Id, GradeLevel = CatalogGradeLevel, Name = d.Name ?? "", Code = d.Code ?? "", SortOrder = i, Items = (d.Competencies ?? []) .Select((c, j) => new CompetencyItem { Code = c.Code ?? "", Description = c.Description ?? "", SortOrder = j, }).ToList(), }; _domainRepo.Save(domain); } CatalogValidation = ""; LoadCatalog(); } catch { CatalogValidation = "Import fehlgeschlagen – bitte JSON-Format prüfen."; } } public string ExportCatalog() { var dto = new CatalogDto { Subject = CatalogSubject?.Name ?? "", GradeLevel = CatalogGradeLevel, Domains = Domains.Select(d => new DomainDto { Name = d.Name, Code = d.Code, Competencies = d.Items.Select(i => new CompetencyDto { Code = i.Code, Description = i.Description, }).ToList(), }).ToList(), }; return JsonSerializer.Serialize(dto, new JsonSerializerOptions { WriteIndented = true }); } } // ── DomainEditItem ──────────────────────────────────────────────────────────── public partial class DomainEditItem : ObservableObject { private readonly CompetencyDomain _domain; private readonly ICompetencyDomainRepository _repo; public Guid Id { get; } public string Name { get; } public string Code { get; } public string DisplayName { get; } [ObservableProperty] private string _newItemCode = ""; [ObservableProperty] private string _newItemDesc = ""; public ObservableCollection Items { get; } = []; public DomainEditItem(CompetencyDomain domain, ICompetencyDomainRepository repo) { _domain = domain; _repo = repo; Id = domain.Id; Name = domain.Name; Code = domain.Code; DisplayName = string.IsNullOrEmpty(domain.Code) ? domain.Name : $"{domain.Name} ({domain.Code})"; foreach (var item in domain.Items.OrderBy(i => i.SortOrder)) Items.Add(new CompetencyItemVm(item, DeleteItem)); } [RelayCommand] private void AddItem() { if (string.IsNullOrWhiteSpace(NewItemDesc)) return; var item = new CompetencyItem { Code = NewItemCode.Trim(), Description = NewItemDesc.Trim(), SortOrder = _domain.Items.Count, }; _domain.Items.Add(item); _repo.Save(_domain); Items.Add(new CompetencyItemVm(item, DeleteItem)); NewItemCode = ""; NewItemDesc = ""; } private void DeleteItem(CompetencyItemVm vm) { _domain.Items.RemoveAll(i => i.Id == vm.ItemId); _repo.Save(_domain); Items.Remove(vm); } } // ── CompetencyItemVm ────────────────────────────────────────────────────────── public class CompetencyItemVm { public Guid ItemId { get; } public string Code { get; } public string Description { get; } public string Display { get; } public IRelayCommand DeleteCommand { get; } public CompetencyItemVm(CompetencyItem item, Action onDelete) { ItemId = item.Id; Code = item.Code; Description = item.Description; Display = string.IsNullOrEmpty(item.Code) ? item.Description : $"[{item.Code}] {item.Description}"; DeleteCommand = new RelayCommand(() => onDelete(this)); } } // ── GradingKeyTemplateEditItem (1.3.2) ───────────────────────────────────────── public partial class GradingKeyTemplateEditItem : ObservableObject { private readonly GradingKeyTemplate _template; private readonly IGradingKeyTemplateRepository _repo; private readonly GradingService _grading; public Guid Id { get; } public string Name { get; } public string GradingSystemLabel { get; } [ObservableProperty] private string _newGrade = ""; [ObservableProperty] private double _newMinPercent; [ObservableProperty] private string _validation = ""; [ObservableProperty] private string _completenessWarning = ""; public ObservableCollection Entries { get; } = []; public GradingKeyTemplateEditItem(GradingKeyTemplate template, IGradingKeyTemplateRepository repo, GradingService grading) { _template = template; _repo = repo; _grading = grading; Id = template.Id; Name = template.Name; GradingSystemLabel = template.GradingSystem == GradingSystem.Grades1To6 ? "Noten 1–6" : "Punkte 0–15"; foreach (var e in template.Entries.OrderByDescending(e => e.MinPercent)) Entries.Add(new GradingKeyEntryVm(e, DeleteEntry)); RecomputeCompleteness(); } [RelayCommand] private void AddEntry() { if (string.IsNullOrWhiteSpace(NewGrade)) { Validation = "Bezeichnung erforderlich."; return; } if (NewMinPercent is < 0 or > 100) { Validation = "Prozentgrenze muss zwischen 0 und 100 liegen."; return; } if (_template.Entries.Any(e => Math.Abs(e.MinPercent - NewMinPercent) < 0.0001)) { Validation = "Diese Prozentgrenze existiert bereits."; return; } var entry = new GradingKeyEntry { Grade = NewGrade.Trim(), MinPercent = NewMinPercent }; _template.Entries.Add(entry); _template.Entries = _template.Entries.OrderByDescending(e => e.MinPercent).ToList(); _repo.Save(_template); Entries.Clear(); foreach (var e in _template.Entries) Entries.Add(new GradingKeyEntryVm(e, DeleteEntry)); NewGrade = ""; NewMinPercent = 0; Validation = ""; RecomputeCompleteness(); } private void DeleteEntry(GradingKeyEntryVm vm) { _template.Entries.RemoveAll(e => e.Grade == vm.Grade && Math.Abs(e.MinPercent - vm.MinPercent) < 0.0001); _repo.Save(_template); Entries.Remove(vm); RecomputeCompleteness(); } private void RecomputeCompleteness() => CompletenessWarning = _grading.ValidateGradingKey(_template.Entries) ?? ""; } // ── GradingKeyEntryVm ───────────────────────────────────────────────────────── public class GradingKeyEntryVm { public string Grade { get; } public double MinPercent { get; } public string Display { get; } public IRelayCommand DeleteCommand { get; } public GradingKeyEntryVm(GradingKeyEntry e, Action onDelete) { Grade = e.Grade; MinPercent = e.MinPercent; Display = $"{Grade} — ab {MinPercent.ToString("0.##")} %"; DeleteCommand = new RelayCommand(() => onDelete(this)); } } // ── Hilfklassen ─────────────────────────────────────────────────────────────── public class SubjectListItem(Subject s) { public Guid Id { get; } = s.Id; public string Name { get; } = s.Name; public string ShortName { get; } = s.ShortName; } // ── JSON DTOs ───────────────────────────────────────────────────────────────── internal class CatalogDto { [JsonPropertyName("subject")] public string? Subject { get; set; } [JsonPropertyName("gradeLevel")] public int GradeLevel { get; set; } [JsonPropertyName("domains")] public List? Domains { get; set; } } internal class DomainDto { [JsonPropertyName("name")] public string? Name { get; set; } [JsonPropertyName("code")] public string? Code { get; set; } [JsonPropertyName("competencies")] public List? Competencies { get; set; } } internal class CompetencyDto { [JsonPropertyName("code")] public string? Code { get; set; } [JsonPropertyName("description")] public string? Description { get; set; } }