Schüler hinzufügen, Kompetenzen

This commit is contained in:
2026-06-23 13:46:39 +02:00
parent b2211270b4
commit 220969fdfa
23 changed files with 1230 additions and 88 deletions
@@ -0,0 +1,285 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Models;
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;
// ── Fächer ────────────────────────────────────────────────────────────────
[ObservableProperty] private string _newName = "";
[ObservableProperty] private string _newShort = "";
[ObservableProperty] private string _validationMessage = "";
public ObservableCollection<SubjectListItem> 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<DomainEditItem> Domains { get; } = [];
// ── Konstruktor ───────────────────────────────────────────────────────────
public SettingsViewModel(ISubjectRepository subjects, ICompetencyDomainRepository domainRepo)
{
_subjects = subjects;
_domainRepo = domainRepo;
LoadSubjects();
}
// ── 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<CatalogDto>(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<CompetencyItemVm> 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<CompetencyItemVm> 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));
}
}
// ── 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<DomainDto>? Domains { get; set; }
}
internal class DomainDto
{
[JsonPropertyName("name")] public string? Name { get; set; }
[JsonPropertyName("code")] public string? Code { get; set; }
[JsonPropertyName("competencies")] public List<CompetencyDto>? Competencies { get; set; }
}
internal class CompetencyDto
{
[JsonPropertyName("code")] public string? Code { get; set; }
[JsonPropertyName("description")] public string? Description { get; set; }
}