using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using LehrerApp.Core.Interfaces; using LehrerApp.Core.Models; using LehrerApp.Core.Services; using LehrerApp.Data; using LehrerApp.Desktop.Services; using LehrerApp.Desktop.ViewModels.Planning; using LehrerApp.Sync; using LehrerApp.Sync.Crypto; using System.Collections.ObjectModel; using System.Globalization; using System.Security.Cryptography; using System.Text.Json; using System.Text.Json.Serialization; namespace LehrerApp.Desktop.ViewModels.Settings; public partial class SettingsViewModel { // ── 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; } = []; // ── 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(CreateDomainEditItem(d)); RefreshDomainMoveState(); } // ── 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(CreateDomainEditItem(domain)); RefreshDomainMoveState(); NewDomainName = ""; NewDomainCode = ""; CatalogValidation = ""; } [RelayCommand] private void DeleteDomain(DomainEditItem? item) { if (item is null) return; _domainRepo.Delete(item.Id); Domains.Remove(item); PersistDomainOrder(); } private DomainEditItem CreateDomainEditItem(CompetencyDomain domain) => new(domain, _domainRepo, item => MoveDomain(item, -1), item => MoveDomain(item, 1)); private void MoveDomain(DomainEditItem item, int offset) { var oldIndex = Domains.IndexOf(item); var newIndex = oldIndex + offset; if (oldIndex < 0 || newIndex < 0 || newIndex >= Domains.Count) return; Domains.Move(oldIndex, newIndex); PersistDomainOrder(); } private void PersistDomainOrder() { for (var i = 0; i < Domains.Count; i++) Domains[i].SetSortOrder(i); RefreshDomainMoveState(); } private void RefreshDomainMoveState() { for (var i = 0; i < Domains.Count; i++) Domains[i].SetMoveState(i > 0, i < Domains.Count - 1); } } // ── 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 IRelayCommand MoveUpCommand { get; } public IRelayCommand MoveDownCommand { get; } public DomainEditItem(CompetencyDomain domain, ICompetencyDomainRepository repo, Action? onMoveUp = null, Action? onMoveDown = null) { _domain = domain; _repo = repo; Id = domain.Id; Name = domain.Name; Code = domain.Code; DisplayName = string.IsNullOrEmpty(domain.Code) ? domain.Name : $"{domain.Name} ({domain.Code})"; MoveUpCommand = new RelayCommand(() => onMoveUp?.Invoke(this), () => _canMoveUp); MoveDownCommand = new RelayCommand(() => onMoveDown?.Invoke(this), () => _canMoveDown); _domain.Items = domain.Items.OrderBy(i => i.SortOrder).ToList(); foreach (var item in _domain.Items) Items.Add(CreateItemViewModel(item)); RefreshItemMoveState(); } private bool _canMoveUp; private bool _canMoveDown; internal void SetMoveState(bool canMoveUp, bool canMoveDown) { _canMoveUp = canMoveUp; _canMoveDown = canMoveDown; MoveUpCommand.NotifyCanExecuteChanged(); MoveDownCommand.NotifyCanExecuteChanged(); } internal void SetSortOrder(int sortOrder) { if (_domain.SortOrder == sortOrder) return; _domain.SortOrder = sortOrder; _repo.Save(_domain); } [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(CreateItemViewModel(item)); RefreshItemMoveState(); NewItemCode = ""; NewItemDesc = ""; } private void DeleteItem(CompetencyItemVm vm) { _domain.Items.RemoveAll(i => i.Id == vm.ItemId); Items.Remove(vm); PersistItemOrder(); } private CompetencyItemVm CreateItemViewModel(CompetencyItem item) => new(item, DeleteItem, vm => MoveItem(vm, -1), vm => MoveItem(vm, 1)); private void MoveItem(CompetencyItemVm item, int offset) { var oldIndex = Items.IndexOf(item); var newIndex = oldIndex + offset; if (oldIndex < 0 || newIndex < 0 || newIndex >= Items.Count) return; Items.Move(oldIndex, newIndex); PersistItemOrder(); } private void PersistItemOrder() { _domain.Items = Items.Select(x => x.Model).ToList(); for (var i = 0; i < _domain.Items.Count; i++) _domain.Items[i].SortOrder = i; _repo.Save(_domain); RefreshItemMoveState(); } private void RefreshItemMoveState() { for (var i = 0; i < Items.Count; i++) Items[i].SetMoveState(i > 0, i < Items.Count - 1); } } // ── CompetencyItemVm ────────────────────────────────────────────────────────── public class CompetencyItemVm { internal CompetencyItem Model { get; } public Guid ItemId { get; } public string Code { get; } public string Description { get; } public string Display { get; } public IRelayCommand DeleteCommand { get; } public IRelayCommand MoveUpCommand { get; } public IRelayCommand MoveDownCommand { get; } public CompetencyItemVm(CompetencyItem item, Action onDelete, Action? onMoveUp = null, Action? onMoveDown = null) { Model = item; 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)); MoveUpCommand = new RelayCommand(() => onMoveUp?.Invoke(this), () => _canMoveUp); MoveDownCommand = new RelayCommand(() => onMoveDown?.Invoke(this), () => _canMoveDown); } private bool _canMoveUp; private bool _canMoveDown; internal void SetMoveState(bool canMoveUp, bool canMoveDown) { _canMoveUp = canMoveUp; _canMoveDown = canMoveDown; MoveUpCommand.NotifyCanExecuteChanged(); MoveDownCommand.NotifyCanExecuteChanged(); } } // ── GradingKeyTemplateEditItem (1.3.2) ─────────────────────────────────────────