QuestPDF-Vorlagensystem und Designer hinzufügen
CI / build-and-test (push) Canceled after 0s

This commit is contained in:
2026-08-30 15:37:22 +02:00
parent a088823b30
commit 2f2761caf8
40 changed files with 2177 additions and 738 deletions
@@ -1,37 +1,19 @@
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 LehrerApp.Templating;
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
{
// ── Word-Briefvorlagen (7.1.4 / 11.5) ───────────────────────────────────
[ObservableProperty] private string _letterTemplateStatus = "";
public ObservableCollection<LetterTemplateListItem> LetterTemplateList { get; } = [];
public IReadOnlyList<LetterPlaceholder> SupportedLetterPlaceholders =>
LetterTemplateService.SupportedPlaceholders;
// ── Word-Briefvorlagen: Import und Validierung ──────────────────────────
private void LoadLetterTemplates()
{
LetterTemplateList.Clear();
foreach (var template in _letterTemplates.GetTemplates())
LetterTemplateList.Add(new LetterTemplateListItem(template, _letterTemplates.Validate(template)));
foreach (var template in _letterTemplates.GetTemplates()) LetterTemplateList.Add(CreateItem(template));
}
public void ImportLetterTemplate(string path)
@@ -40,75 +22,59 @@ public partial class SettingsViewModel
try
{
var template = _letterTemplates.Import(path);
var validation = _letterTemplates.Validate(template);
LoadLetterTemplates();
LetterTemplateStatus = validation.Issues.Count == 0
? "Vorlage importiert und ohne Auffälligkeiten geprüft."
: $"Vorlage importiert. Die Prüfung meldet {validation.Issues.Count} Hinweis(e).";
}
catch (Exception ex) when (ex is IOException or InvalidDataException or UnauthorizedAccessException)
{
LetterTemplateStatus = $"Import fehlgeschlagen: {ex.Message}";
LetterTemplateStatus = $"„{template.Name}“ wurde geprüft und lokal importiert.";
}
catch (Exception ex) when (ex is IOException or InvalidDataException or UnauthorizedAccessException or TemplateValidationException)
{ LetterTemplateStatus = $"Import fehlgeschlagen: {ex.Message}"; }
}
[RelayCommand]
private void ValidateLetterTemplate(LetterTemplateListItem? item)
{
if (item is null) return;
var index = LetterTemplateList.IndexOf(item);
var refreshed = new LetterTemplateListItem(item.Model, _letterTemplates.Validate(item.Model));
if (index >= 0) LetterTemplateList[index] = refreshed;
LetterTemplateStatus = refreshed.Validation.Issues.Count == 0
? $"„{item.Name}“ ist ohne Auffälligkeiten."
: $"„{item.Name}“: {refreshed.Validation.Issues.Count} Hinweis(e).";
try
{
var refreshed = CreateItem(item.Model);
var index = LetterTemplateList.IndexOf(item);
if (index >= 0) LetterTemplateList[index] = refreshed;
LetterTemplateStatus = $"„{item.Name}“ ist gültig (Schema {refreshed.SchemaVersion}).";
}
catch (Exception ex) when (ex is IOException or InvalidDataException or TemplateValidationException)
{ LetterTemplateStatus = $"„{item.Name}“ ist ungültig: {ex.Message}"; }
}
[RelayCommand]
private void DeleteLetterTemplate(LetterTemplateListItem? item)
{
if (item is null) return;
_letterTemplates.Delete(item.Id);
LetterTemplateList.Remove(item);
LetterTemplateStatus = "Vorlage gelöscht.";
_letterTemplates.Delete(item.Id); LetterTemplateList.Remove(item); LetterTemplateStatus = "Vorlage gelöscht.";
}
private LetterTemplateListItem CreateItem(InstalledTemplate template)
{
var loaded = _letterTemplates.Load(template);
return new(template, loaded.Manifest.SchemaVersion, loaded.Manifest.Placeholders.Count,
loaded.Manifest.Placeholders.Count(x => x.Required));
}
}
public sealed class LetterTemplateListItem
public sealed class LetterTemplateListItem(InstalledTemplate model, int schemaVersion, int fieldCount, int requiredCount)
{
public LetterTemplateInfo Model { get; }
public TemplateValidationResult Validation { get; }
public Guid Id => Model.Id;
public InstalledTemplate Model { get; } = model;
public string Id => Model.Id;
public string Name => Model.Name;
public string OriginalFileName => Model.OriginalFileName;
public bool HasIssues => Validation.Issues.Count > 0;
public bool HasNoIssues => !HasIssues;
public string ValidationSummary => HasNoIssues
? $"{Validation.Tags.Count} Feld(er) · keine Auffälligkeiten"
: $"{Validation.Tags.Count} Feld(er) · {Validation.Issues.Count} Hinweis(e)";
public ObservableCollection<LetterTemplateIssueItem> Issues { get; }
public LetterTemplateListItem(LetterTemplateInfo model, TemplateValidationResult validation)
{
Model = model;
Validation = validation;
Issues = new(validation.Issues.Select(i => new LetterTemplateIssueItem(i)));
}
public string PackageFileName => Path.GetFileName(Model.PackagePath);
public int SchemaVersion { get; } = schemaVersion;
public bool HasIssues => false;
public bool HasNoIssues => true;
public string ValidationSummary => $"Schema {SchemaVersion} · {fieldCount} Felder ({requiredCount} Pflicht)";
public ObservableCollection<LetterTemplateIssueItem> Issues { get; } = [];
}
public sealed class LetterTemplateIssueItem(TemplateValidationIssue issue)
public sealed class LetterTemplateIssueItem(ValidationIssue issue)
{
public string Icon => issue.Severity switch
{
TemplateIssueSeverity.Error => "⛔",
TemplateIssueSeverity.StrongWarning => "⚠",
_ => "ⓘ",
};
public string Icon => issue.Severity == ValidationSeverity.Error ? "⛔" : "ⓘ";
public string Message => issue.Message;
public string Color => issue.Severity switch
{
TemplateIssueSeverity.Error => "#DC2626",
TemplateIssueSeverity.StrongWarning => "#D97706",
_ => "#6B7280",
};
public string Color => issue.Severity == ValidationSeverity.Error ? "#DC2626" : "#6B7280";
}
@@ -13,6 +13,7 @@ using System.Globalization;
using System.Security.Cryptography;
using System.Text.Json;
using System.Text.Json.Serialization;
using LehrerApp.Templating;
namespace LehrerApp.Desktop.ViewModels.Settings;
@@ -59,7 +60,7 @@ public partial class SettingsViewModel : ObservableObject
private readonly IDocumentationRepository _documentation;
private readonly IStudentRepository _students;
private readonly IShorthandCodeRepository _shorthandCodes;
private readonly LetterTemplateService _letterTemplates;
private readonly TemplateStore _letterTemplates;
[ObservableProperty] private int _activeTabIndex;
@@ -97,7 +98,7 @@ public partial class SettingsViewModel : ObservableObject
IDocumentationRepository documentation, IStudentRepository students,
IShorthandCodeRepository shorthandCodes, ISchoolHolidayRepository schoolHolidays,
SchoolCalendarSettingsService calendarSettings, PeriodScheduleService periodSchedule,
ISupervisionDutyRepository supervisionDuties, LetterTemplateService letterTemplates,
ISupervisionDutyRepository supervisionDuties, TemplateStore letterTemplates,
AiSettingsService aiSettings, AiPlanningService aiPlanning,
WebUntisSettingsService untisSettings,
AnnualPlanSettingsService annualPlanSettings,