Files
LehrerApp/LehrerApp.Desktop/ViewModels/Groups/PersonalizeWorksheetDialogViewModel.cs
adminandClaude Sonnet 5 56ab5067e6
CI / build-and-test (push) Canceled after 0s
feat: Formulare-Menü mit Elternbrief-Einstieg + Arbeitsblatt-Personalisierung (Nutzer-Feedback)
Neue Menü-Kategorie "Formulare" in der Menüleiste: "Elternbrief erzeugen..." öffnet
jetzt einen Schüler-Picker statt nur aus der Schülerdetailansicht erreichbar zu sein,
"Arbeitsblatt personalisieren..." ist aktiv, sobald eine einzelne Lerngruppe geöffnet
ist, und erzeugt aus einer in TemplateDesigner gebauten .lavorlage-Vorlage ein PDF je
aktivem Gruppenmitglied - über eine von den Elternbrief-Vorlagen getrennte
WorksheetTemplateStore-Bibliothek.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 00:21:08 +02:00

87 lines
4.1 KiB
C#

using CommunityToolkit.Mvvm.ComponentModel;
using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Models;
using LehrerApp.Desktop.Services;
using LehrerApp.Desktop.ViewModels.Students;
using LehrerApp.Templating;
using System.Collections.ObjectModel;
namespace LehrerApp.Desktop.ViewModels.Groups;
/// <summary>Erzeugt aus einer in TemplateDesigner gebauten Arbeitsblatt-.lavorlage-Vorlage ein
/// personalisiertes PDF je aktivem Mitglied der aktuell geöffneten Lerngruppe. Nutzt bewusst
/// dieselbe Platzhalter-/Rendering-Infrastruktur wie der Elternbrief-Dialog
/// (<see cref="LetterPlaceholderBuilder"/>, <see cref="ITemplateRenderer"/>), aber eine getrennte
/// <see cref="WorksheetTemplateStore"/>-Vorlagenbibliothek.</summary>
public partial class PersonalizeWorksheetDialogViewModel : ObservableObject
{
private readonly LearningGroup _group;
private readonly WorksheetTemplateStore _templates;
private readonly ITemplateRenderer _renderer;
[ObservableProperty] private LetterTemplateChoice? _selectedTemplate;
[ObservableProperty] private string _statusMessage = "";
public string GroupName => $"{_group.Name} · {_group.SchoolYear}";
public ObservableCollection<LetterTemplateChoice> Templates { get; } = [];
public ObservableCollection<WorksheetStudentChoice> Students { get; } = [];
public ObservableCollection<WorksheetGenerationResult> Results { get; } = [];
public bool HasNoTemplates => Templates.Count == 0;
public bool HasResults => Results.Count > 0;
public PersonalizeWorksheetDialogViewModel(LearningGroup group, IReadOnlyList<Guid> studentIds,
IStudentRepository students, WorksheetTemplateStore templates, ITemplateRenderer renderer)
{
_group = group; _templates = templates; _renderer = renderer;
foreach (var template in templates.Store.GetTemplates()) Templates.Add(new(template));
SelectedTemplate = Templates.FirstOrDefault();
foreach (var id in studentIds)
if (students.GetById(id) is { } student) Students.Add(new(student));
}
public void Generate(string outputFolder)
{
Results.Clear();
if (SelectedTemplate is null) return;
LoadedTemplate loaded;
try { loaded = _templates.Store.Load(SelectedTemplate.Model); }
catch (Exception ex) when (ex is IOException or InvalidDataException or TemplateValidationException)
{ StatusMessage = $"Vorlage ist ungültig: {ex.Message}"; return; }
Directory.CreateDirectory(outputFolder);
foreach (var choice in Students.Where(x => x.IsIncluded))
{
var student = choice.Model;
try
{
var values = LetterPlaceholderBuilder.BuildStandardValues(student, contact: null, _group,
DateOnly.FromDateTime(DateTime.Now), "", "");
var pdf = _renderer.RenderToPdf(loaded, new LetterDataProvider(values));
var fileName = SanitizeFileName($"{SelectedTemplate.Name}_{student.LastName}_{student.FirstName}.pdf");
File.WriteAllBytes(Path.Combine(outputFolder, fileName), pdf);
Results.Add(new(student.FullName, true, ""));
}
catch (Exception ex) when (ex is IOException or InvalidDataException or UnauthorizedAccessException or TemplateValidationException)
{ Results.Add(new(student.FullName, false, ex.Message)); }
}
OnPropertyChanged(nameof(HasResults));
StatusMessage = $"{Results.Count(x => x.Success)} von {Results.Count} Arbeitsblättern erzeugt.";
}
private static string SanitizeFileName(string value)
{ foreach (var character in Path.GetInvalidFileNameChars()) value = value.Replace(character, '_'); return value; }
}
public partial class WorksheetStudentChoice(Student model) : ObservableObject
{
public Student Model { get; } = model;
public string FullName => Model.FullName;
[ObservableProperty] private bool _isIncluded = true;
}
public sealed record WorksheetGenerationResult(string StudentName, bool Success, string ErrorMessage)
{
public string Icon => Success ? "✓" : "⚠";
public string Color => Success ? "SeaGreen" : "#D97706";
}