QuestPDF als PDF-Bibliothek eingeführt (11.3), bewusst nur in LehrerApp.Desktop referenziert - zieht SkiaSharp-Native-Binaries mit, die im Server-Docker-Image nichts verloren haben. Neuer PdfExportService mit gemeinsamem Kopf-/Fußzeilen-Layout (Titel, Stand-Datum, Seitenzahlen) und schlanken Druck-DTOs statt ViewModels, damit die Erzeugung ohne Avalonia-Bezug testbar bleibt. Speicherdialog läuft über den bestehenden ExportService (neues PDF-Format). Fünf Druckvorlagen (11.4) als "Als PDF"-Button direkt in der jeweiligen Ansicht: - Sitzplan: Raster mit Tafel-Banner, Tischabständen als echte Lücken, ausgeblendeten Plätzen als leere Rasterposition. - Notenliste: druckt exakt die aktuell angezeigte Matrix (Zeitraum/Darstellung/Sortierung). - Klausur-Notenspiegel: Kennzahlen, Notenverteilung und Aufgabenanalyse mit Balkendiagrammen, auffällig schwache Aufgaben rot markiert. - Kompetenzbericht: Analyse-Tabelle für den ausgewählten Schüler (erledigt zugleich 8.3.3). - Schülerdokumentation: alle Einträge chronologisch, vertrauliche/Entwurfs-Einträge markiert. Layout an gerenderten Muster-PDFs visuell geprüft, nicht nur per Unit-Test. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
54 lines
2.3 KiB
C#
54 lines
2.3 KiB
C#
using Avalonia.Controls;
|
|
using Avalonia.Interactivity;
|
|
using LehrerApp.Desktop.Services;
|
|
using LehrerApp.Desktop.ViewModels.Groups;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace LehrerApp.Desktop.Views.Groups;
|
|
|
|
public partial class ExamEvaluationDialog : Window
|
|
{
|
|
public ExamEvaluationDialog() => InitializeComponent();
|
|
|
|
private async void OnExportClick(object? sender, RoutedEventArgs e)
|
|
{
|
|
if (DataContext is not ExamEvaluationDialogViewModel vm) return;
|
|
var topLevel = TopLevel.GetTopLevel(this);
|
|
if (topLevel is null) return;
|
|
|
|
var export = ExportFile.Csv("Klausurauswertung exportieren",
|
|
$"Auswertung_{vm.ExamTitle}.csv", vm.ExportCsv());
|
|
await App.Services.GetRequiredService<ExportService>().SaveAsync(topLevel.StorageProvider, export);
|
|
}
|
|
|
|
private async void OnExportPdfClick(object? sender, RoutedEventArgs e)
|
|
{
|
|
if (DataContext is not ExamEvaluationDialogViewModel vm) return;
|
|
var topLevel = TopLevel.GetTopLevel(this);
|
|
if (topLevel is null) return;
|
|
|
|
var stats = new List<LabelValue>
|
|
{
|
|
new("Bewertet", vm.GradedCount.ToString()),
|
|
new("Abwesend", vm.AbsentCount.ToString()),
|
|
new("Durchschnitt", vm.AverageDisplay),
|
|
new("Median", vm.MedianDisplay),
|
|
};
|
|
if (!string.IsNullOrEmpty(vm.BelowThresholdLabel1))
|
|
stats.Add(new(vm.BelowThresholdLabel1, vm.BelowThresholdDisplay1));
|
|
if (!string.IsNullOrEmpty(vm.BelowThresholdLabel2))
|
|
stats.Add(new(vm.BelowThresholdLabel2, vm.BelowThresholdDisplay2));
|
|
|
|
var data = new ExamStatisticsPrintData(vm.ExamTitle, vm.ExamDateLabel, vm.NiveauLabel, stats,
|
|
vm.GradeDistribution.Select(g => new GradeDistributionPrintRow(g.Grade, g.Count,
|
|
vm.GradedCount == 0 ? 0 : g.Count * 100.0 / vm.GradedCount)).ToList(),
|
|
vm.TaskAnalysis.Select(t => new TaskAnalysisPrintRow(t.Label, t.AvgPercent, t.IsWeak)).ToList());
|
|
var pdf = App.Services.GetRequiredService<PdfExportService>().BuildExamStatisticsPdf(data);
|
|
|
|
await App.Services.GetRequiredService<ExportService>().SaveAsync(topLevel.StorageProvider,
|
|
ExportFile.Pdf("Klausurauswertung als PDF speichern", $"Auswertung_{vm.ExamTitle}", pdf));
|
|
}
|
|
|
|
private void OnClose(object? sender, RoutedEventArgs e) => Close();
|
|
}
|