Files
LehrerApp/LehrerApp.Desktop/ViewModels/Settings/SettingsViewModel.Competencies.cs
T
admin 0f2a25feb5
CI / build-and-test (push) Canceled after 0s
Fix Kompetenzlisten ansicht
2026-09-19 23:52:46 +02:00

347 lines
13 KiB
C#

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 CompetencyCatalogVersion? _catalogVersion;
[ObservableProperty] private string _newDomainName = "";
[ObservableProperty] private string _newDomainCode = "";
[ObservableProperty] private string _catalogValidation = "";
[ObservableProperty] private string _newVersionName = "";
[ObservableProperty] private string _versionValidation = "";
public ObservableCollection<DomainEditItem> Domains { get; } = [];
public ObservableCollection<CompetencyCatalogVersion> CatalogVersions { get; } = [];
public bool HasNoCatalogDomains => CatalogSubject is not null && CatalogVersion is not null && Domains.Count == 0;
public string EmptyCatalogMessage => CatalogSubject is null || CatalogVersion is null
? ""
: $"Keine Kompetenzbereiche für {CatalogSubject.Name}, Klasse {CatalogGradeLevel}, Version „{CatalogVersion.Name}“ hinterlegt.";
// ── Katalog: Laden ────────────────────────────────────────────────────────
partial void OnCatalogSubjectChanged(SubjectListItem? value) => LoadVersions();
partial void OnCatalogGradeLevelChanged(int value) => LoadCatalog();
partial void OnCatalogVersionChanged(CompetencyCatalogVersion? value) => LoadCatalog();
[RelayCommand]
private void RefreshCompetencyCatalog()
{
var selectedSubjectId = CatalogSubject?.Id;
var selectedVersionId = CatalogVersion?.Id;
LoadSubjects();
if (selectedSubjectId is not Guid subjectId)
{
LoadCatalog();
return;
}
CatalogSubject = Subjects.FirstOrDefault(s => s.Id == subjectId);
if (CatalogSubject is null) return;
CatalogVersion = CatalogVersions.FirstOrDefault(v => v.Id == selectedVersionId)
?? CatalogVersions.FirstOrDefault(v => v.IsDefault)
?? CatalogVersions.FirstOrDefault();
LoadCatalog();
}
private void LoadVersions()
{
var selectedId = CatalogVersion?.Id;
CatalogVersions.Clear();
if (CatalogSubject is null) { LoadCatalog(); return; }
foreach (var v in _versionRepo.GetBySubject(CatalogSubject.Id))
CatalogVersions.Add(v);
if (CatalogVersions.Count == 0)
CatalogVersions.Add(_versionRepo.GetOrCreateDefault(CatalogSubject.Id));
CatalogVersion = CatalogVersions.FirstOrDefault(v => v.Id == selectedId)
?? CatalogVersions.FirstOrDefault(v => v.IsDefault)
?? CatalogVersions.FirstOrDefault();
}
private void LoadCatalog()
{
Domains.Clear();
CatalogValidation = "";
if (CatalogSubject is null || CatalogVersion is null)
{
OnPropertyChanged(nameof(HasNoCatalogDomains));
OnPropertyChanged(nameof(EmptyCatalogMessage));
return;
}
foreach (var d in _domainRepo.GetBySubjectAndGrade(CatalogSubject.Id, CatalogGradeLevel, CatalogVersion.Id))
Domains.Add(CreateDomainEditItem(d));
RefreshDomainMoveState();
OnPropertyChanged(nameof(HasNoCatalogDomains));
OnPropertyChanged(nameof(EmptyCatalogMessage));
}
// ── Versionen (Bildungspläne) verwalten ──────────────────────────────────
[RelayCommand]
private void AddCatalogVersion()
{
if (CatalogSubject is null) { VersionValidation = "Bitte zuerst ein Fach auswählen."; return; }
if (string.IsNullOrWhiteSpace(NewVersionName)) { VersionValidation = "Name der Version erforderlich."; return; }
var version = new CompetencyCatalogVersion
{
SubjectId = CatalogSubject.Id, Name = NewVersionName.Trim(),
SortOrder = CatalogVersions.Count,
};
_versionRepo.Save(version);
CatalogVersions.Add(version);
CatalogVersion = version;
NewVersionName = ""; VersionValidation = "";
}
[RelayCommand]
private void SetCatalogVersionAsDefault(CompetencyCatalogVersion? version)
{
if (version is null || CatalogSubject is null) return;
version.IsDefault = true;
_versionRepo.Save(version);
LoadVersions();
}
[RelayCommand]
private void DeleteCatalogVersion(CompetencyCatalogVersion? version)
{
if (version is null) return;
try { _versionRepo.Delete(version.Id); }
catch (InvalidOperationException ex) { VersionValidation = ex.Message; return; }
LoadVersions();
}
// ── Katalog: Bereich hinzufügen / löschen ────────────────────────────────
[RelayCommand]
private void AddDomain()
{
if (CatalogSubject is null) { CatalogValidation = "Bitte zuerst ein Fach auswählen."; return; }
if (CatalogVersion is null) { CatalogValidation = "Bitte zuerst eine Version auswählen."; return; }
if (string.IsNullOrWhiteSpace(NewDomainName)) { CatalogValidation = "Bereichsname erforderlich."; return; }
var domain = new CompetencyDomain
{
SubjectId = CatalogSubject.Id,
GradeLevel = CatalogGradeLevel,
VersionId = CatalogVersion.Id,
Name = NewDomainName.Trim(),
Code = NewDomainCode.Trim(),
SortOrder = Domains.Count,
};
_domainRepo.Save(domain);
Domains.Add(CreateDomainEditItem(domain));
RefreshDomainMoveState();
OnPropertyChanged(nameof(HasNoCatalogDomains));
OnPropertyChanged(nameof(EmptyCatalogMessage));
NewDomainName = ""; NewDomainCode = ""; CatalogValidation = "";
}
[RelayCommand]
private void DeleteDomain(DomainEditItem? item)
{
if (item is null) return;
_domainRepo.Delete(item.Id);
Domains.Remove(item);
PersistDomainOrder();
OnPropertyChanged(nameof(HasNoCatalogDomains));
OnPropertyChanged(nameof(EmptyCatalogMessage));
}
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<CompetencyItemVm> Items { get; } = [];
public IRelayCommand MoveUpCommand { get; }
public IRelayCommand MoveDownCommand { get; }
public DomainEditItem(CompetencyDomain domain, ICompetencyDomainRepository repo,
Action<DomainEditItem>? onMoveUp = null, Action<DomainEditItem>? 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<CompetencyItemVm> onDelete,
Action<CompetencyItemVm>? onMoveUp = null, Action<CompetencyItemVm>? 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) ─────────────────────────────────────────