using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Platform.Storage; using LehrerApp.Core.Interfaces; using LehrerApp.Core.Models; using LehrerApp.Desktop.Services; using LehrerApp.Desktop.ViewModels.Groups; using LehrerApp.Desktop.ViewModels.Students; using Microsoft.Extensions.DependencyInjection; namespace LehrerApp.Desktop.Views.Groups; public partial class LessonDialog : Window { public LessonDialog() => InitializeComponent(); protected override void OnDataContextChanged(EventArgs e) { base.OnDataContextChanged(e); if (DataContext is LessonDialogViewModel vm) vm.OnPickAlternativePath = ShowAlternativePathDialog; } private async Task ShowAlternativePathDialog(Guid? currentId) { var dialogVm = new AlternativePathDialogViewModel( App.Services.GetRequiredService(), currentId); var dialog = new AlternativePathDialog { DataContext = dialogVm }; var ok = await dialog.ShowDialog(this); return ok ? dialogVm.Result : null; } private void OnSave(object? s, RoutedEventArgs e) { if (DataContext is LessonDialogViewModel vm && vm.SaveCommand.CanExecute(null)) { vm.SaveCommand.Execute(null); if (vm.Result is not null) Close(true); } } private void OnCancel(object? s, RoutedEventArgs e) { if (DataContext is LessonDialogViewModel vm) vm.DiscardUnsavedAttachments(); Close(false); } private async void OnAddAttachment(object? sender, RoutedEventArgs e) { if (DataContext is not LessonDialogViewModel vm) return; var files = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions { Title = "Anhang auswählen", AllowMultiple = false, }); if (files.Count == 0) return; await using var stream = await files[0].OpenReadAsync(); vm.AddAttachment(files[0].Name, stream); } private async void OnOpenAttachment(object? sender, RoutedEventArgs e) { if (DataContext is not LessonDialogViewModel vm) return; if (sender is not Button { Tag: AttachmentItem item }) return; using var source = vm.OpenAttachment(item); if (source is null) return; var file = await StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions { Title = "Anhang speichern unter", SuggestedFileName = item.FileName, }); if (file is null) return; await using var target = await file.OpenWriteAsync(); await source.CopyToAsync(target); } private async void OnCreateHazardAssessment(object? sender, RoutedEventArgs e) { if (DataContext is not LessonDialogViewModel vm) return; var (unit, contextLesson, groupName) = BuildHazardAssessmentContext(vm); var dialogVm = new HazardAssessmentWizardViewModel(null, groupName, App.Services.GetRequiredService(), App.Services.GetRequiredService(), unit, contextLesson); var dialog = new HazardAssessmentWizardDialog { DataContext = dialogVm }; var ok = await dialog.ShowDialog(this); if (!ok || dialogVm.Result is null) return; UploadHazardAssessment(vm, dialogVm.Result); } private async void OnOpenHazardAssessment(object? sender, RoutedEventArgs e) { if (DataContext is not LessonDialogViewModel vm) return; if (sender is not Button { Tag: AttachmentItem item }) return; var editing = await ReadHazardAssessmentAsync(vm, item); if (editing is null) return; var (unit, contextLesson, _) = BuildHazardAssessmentContext(vm); var dialogVm = new HazardAssessmentWizardViewModel(editing, editing.GroupLabel, App.Services.GetRequiredService(), App.Services.GetRequiredService(), unit, contextLesson); var dialog = new HazardAssessmentWizardDialog { DataContext = dialogVm }; var ok = await dialog.ShowDialog(this); if (!ok || dialogVm.Result is null) return; vm.RemoveAttachmentCommand.Execute(item); UploadHazardAssessment(vm, dialogVm.Result); } private async void OnPrintHazardAssessment(object? sender, RoutedEventArgs e) { var topLevel = TopLevel.GetTopLevel(this); if (topLevel is null || DataContext is not LessonDialogViewModel vm) return; if (sender is not Button { Tag: AttachmentItem item }) return; var assessment = await ReadHazardAssessmentAsync(vm, item); if (assessment is null) return; var data = new HazardAssessmentPrintData( assessment.Title, assessment.GroupLabel, assessment.Date?.ToString("dd.MM.yyyy") ?? "", ExperimentKindDisplay.Label(assessment.Kind), assessment.Procedure, assessment.Substances.Select(s => new HazardSubstancePrintRow( s.Name, s.Amount, string.Join(", ", s.GhsPictograms.Select(GhsPictogramDisplay.Code)), s.HStatements, s.PStatements)).ToList(), assessment.Hazards, assessment.ProtectiveMeasures, assessment.FirstAid, assessment.Disposal, assessment.Notes, assessment.IsAiAssisted); var pdf = App.Services.GetRequiredService().BuildHazardAssessmentPdf(data); await App.Services.GetRequiredService().SaveAsync(topLevel.StorageProvider, ExportFile.Pdf("Gefährdungsbeurteilung als PDF speichern", $"GBU_{assessment.Title}", pdf)); } /// Baut den Kontext für den Gefährdungsbeurteilungs-Assistenten (KI-Anfrage + Vorbelegung) aus /// dem gerade im Dialog bearbeiteten, ggf. noch nicht gespeicherten Stand — funktioniert damit /// auch beim Neuanlegen einer Stunde, bevor überhaupt gespeichert wurde. private (Unit? Unit, Lesson ContextLesson, string GroupName) BuildHazardAssessmentContext(LessonDialogViewModel vm) { var unit = App.Services.GetRequiredService().GetById(vm.UnitId); var groupName = unit is null ? "" : App.Services.GetRequiredService().GetById(unit.GroupId)?.Name ?? ""; var contextLesson = new Lesson { Topic = vm.Topic, Phases = vm.Phases.Select(p => p.ToModel()).ToList() }; return (unit, contextLesson, groupName); } private static async Task ReadHazardAssessmentAsync(LessonDialogViewModel vm, AttachmentItem item) { using var source = vm.OpenAttachment(item); if (source is null) return null; using var reader = new StreamReader(source); var json = await reader.ReadToEndAsync(); return System.Text.Json.JsonSerializer.Deserialize(json); } private static void UploadHazardAssessment(LessonDialogViewModel vm, HazardAssessment result) { var json = System.Text.Json.JsonSerializer.Serialize(result); using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(json)); vm.AddAttachment(HazardAssessmentFileName(result.Title), stream); } private static string HazardAssessmentFileName(string title) { var invalid = Path.GetInvalidFileNameChars(); var safe = new string(title.Select(c => invalid.Contains(c) ? '_' : c).ToArray()).Trim(); if (safe.Length == 0) safe = "Gefaehrdungsbeurteilung"; return safe + AttachmentItem.HazardAssessmentSuffix; } /// KI-Unterstützung mit Fokus auf genau diese (bereits gespeicherte) Stunde, statt den Umweg /// über die Einheiten-Übersicht nehmen zu müssen (4.5.22, Nutzer-Feedback). Die Übernahme im /// AiAssistDialog speichert direkt ins Repository — dieser Dialog schließt sich danach mit dem /// frisch geladenen Stand, statt seine eigenen (jetzt veralteten) Feldwerte zu speichern. private async void OnAiAssist(object? s, RoutedEventArgs e) { if (DataContext is not LessonDialogViewModel vm || vm.EditingLesson is null) return; var unitRepo = App.Services.GetRequiredService(); var unit = unitRepo.GetById(vm.UnitId); if (unit is null) return; var lessonRepo = App.Services.GetRequiredService(); var currentLesson = lessonRepo.GetByUnit(vm.UnitId).FirstOrDefault(l => l.Id == vm.EditingLesson.Id); if (currentLesson is null) return; var dialogVm = new AiAssistDialogViewModel( App.Services.GetRequiredService(), App.Services.GetRequiredService(), lessonRepo, unit, focusLesson: currentLesson); var dialog = new AiAssistDialog { DataContext = dialogVm }; var ok = await dialog.ShowDialog(this); if (!ok) return; var updated = lessonRepo.GetByUnit(vm.UnitId).FirstOrDefault(l => l.Id == vm.EditingLesson.Id); if (updated is not null) vm.MarkAppliedExternally(updated); Close(true); } }