CI / build-and-test (push) Canceled after 0s
Neuer 4. Tab in der Klassenlehreransicht: bündelt Titel, Beschreibung, Schlagwörter, verknüpfte Dokumentation und angeheftete (eingefrorene) WebUntis-Klassenbucheinträge zu einem laufenden Problem mit einer/einem oder mehreren Schüler*innen. Anheften direkt aus dem Klassenbuch-Tab per Rechtsklick. Sync-fähig nach dem bestehenden Documentation-Muster. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
205 lines
8.4 KiB
C#
205 lines
8.4 KiB
C#
using System.Collections.ObjectModel;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using LehrerApp.Core.Models;
|
|
using LehrerApp.Desktop.ViewModels.Students;
|
|
|
|
namespace LehrerApp.Desktop.ViewModels.ClassTeacher;
|
|
|
|
// ── Anzeige-Helfer ────────────────────────────────────────────────────────
|
|
|
|
public static class VorgangTagDisplay
|
|
{
|
|
// Vom Nutzer selbst genannte Schlagwörter — eigene Labels bleiben trotzdem frei möglich
|
|
// (AutoCompleteBox, gleiches Muster wie DocumentationTagDisplay.Suggestions).
|
|
public static string[] Suggestions { get; } =
|
|
["Absentismus", "Hausaufgaben", "Verspätungen", "Konflikte", "Mitarbeit", "Elternkontakt", "Eskalation"];
|
|
}
|
|
|
|
public static class VorgangStatusDisplay
|
|
{
|
|
public static string Label(VorgangStatus status) => status == VorgangStatus.Closed ? "Geschlossen" : "Offen";
|
|
}
|
|
|
|
/// Ergebnis des "→ Vorgang"-Auswahldialogs beim Anheften einer Klassenbuchzeile: entweder ein
|
|
/// bestehender, offener Vorgang oder der Titel für einen neu anzulegenden.
|
|
public sealed record PinToVorgangChoice(Guid? ExistingVorgangId, string? NewVorgangTitle);
|
|
|
|
/// Eingefrorene WebUntis-Klassenbuchzeile mit deutscher Anzeige-Aufbereitung — das Core-Modell
|
|
/// <see cref="VorgangClassRegisterEntry"/> bleibt bewusst framework-frei ohne Formatierungslogik.
|
|
public sealed record VorgangClassRegisterEntryRow(VorgangClassRegisterEntry Model)
|
|
{
|
|
public string DateLabel => Model.Date.ToString("dd.MM.yyyy");
|
|
public string StudentName => Model.StudentName;
|
|
public string SummaryLabel => string.Join(" · ", new[] { Model.Subject, Model.CategoryName, Model.Text }
|
|
.Where(v => !string.IsNullOrWhiteSpace(v)));
|
|
}
|
|
|
|
// ── Mehrfachauswahl Schüler*innen (Anlegen/Bearbeiten-Dialog) ────────────────
|
|
|
|
public partial class VorgangStudentOption(Guid studentId, string name) : ObservableObject
|
|
{
|
|
public Guid StudentId { get; } = studentId;
|
|
public string Name { get; } = name;
|
|
[ObservableProperty] private bool _isSelected;
|
|
}
|
|
|
|
// ── Listen-Eintrag mit aufklappbarer Detailansicht ───────────────────────────
|
|
|
|
/// <summary>
|
|
/// Ein Vorgang in der Liste des "Vorgänge"-Tabs. Verknüpfte Dokumentation/Klassenbucheinträge
|
|
/// werden von <see cref="ClassTeacherCasesViewModel"/> beim Laden befüllt; Link/Unlink-Aktionen
|
|
/// laufen über die hier übergebenen Callbacks zurück ins ViewModel (statt eigenem Repository-
|
|
/// Zugriff hier), damit dieser Wrapper ein reiner Anzeige-Baustein bleibt — analog
|
|
/// <see cref="DocumentationItem"/>.
|
|
/// </summary>
|
|
public partial class VorgangItem : ObservableObject
|
|
{
|
|
private readonly Func<VorgangItem, DocumentationItem, Task> _onLink;
|
|
private readonly Func<VorgangItem, DocumentationItem, Task> _onUnlink;
|
|
private readonly Func<VorgangItem, VorgangClassRegisterEntryRow, Task> _onRemoveClassRegisterEntry;
|
|
|
|
public Vorgang Model { get; }
|
|
public string StudentNames { get; }
|
|
public List<TagChip> TagChips { get; }
|
|
public string StatusLabel => VorgangStatusDisplay.Label(Model.Status);
|
|
public bool IsOpen => Model.Status == VorgangStatus.Open;
|
|
public string CreatedLabel => $"Angelegt {Model.CreatedAt:dd.MM.yyyy}";
|
|
|
|
public ObservableCollection<DocumentationItem> LinkedDocumentation { get; } = [];
|
|
public ObservableCollection<DocumentationItem> AvailableDocumentation { get; } = [];
|
|
public ObservableCollection<VorgangClassRegisterEntryRow> ClassRegisterRows { get; } = [];
|
|
|
|
public bool HasLinkedDocumentation => LinkedDocumentation.Count > 0;
|
|
public bool HasAvailableDocumentation => AvailableDocumentation.Count > 0;
|
|
public bool HasClassRegisterRows => ClassRegisterRows.Count > 0;
|
|
|
|
[ObservableProperty] private bool _isRevealed;
|
|
|
|
public VorgangItem(Vorgang model, string studentNames,
|
|
Func<VorgangItem, DocumentationItem, Task> onLink,
|
|
Func<VorgangItem, DocumentationItem, Task> onUnlink,
|
|
Func<VorgangItem, VorgangClassRegisterEntryRow, Task> onRemoveClassRegisterEntry)
|
|
{
|
|
Model = model;
|
|
StudentNames = studentNames;
|
|
TagChips = model.Tags.Select(t => new TagChip(t)).ToList();
|
|
_onLink = onLink;
|
|
_onUnlink = onUnlink;
|
|
_onRemoveClassRegisterEntry = onRemoveClassRegisterEntry;
|
|
}
|
|
|
|
[RelayCommand] private void Reveal() => IsRevealed = !IsRevealed;
|
|
|
|
[RelayCommand]
|
|
private async Task Link(DocumentationItem? doc)
|
|
{
|
|
if (doc is not null) await _onLink(this, doc);
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task Unlink(DocumentationItem? doc)
|
|
{
|
|
if (doc is not null) await _onUnlink(this, doc);
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task RemoveClassRegisterEntry(VorgangClassRegisterEntryRow? row)
|
|
{
|
|
if (row is not null) await _onRemoveClassRegisterEntry(this, row);
|
|
}
|
|
}
|
|
|
|
// ── Dialog: Vorgang anlegen/bearbeiten ────────────────────────────────────
|
|
|
|
public partial class VorgangDialogViewModel : ObservableObject
|
|
{
|
|
private readonly Vorgang? _editing;
|
|
|
|
public ObservableCollection<VorgangStudentOption> StudentOptions { get; }
|
|
|
|
[ObservableProperty] private string _title = "";
|
|
[ObservableProperty] private string _description = "";
|
|
[ObservableProperty] private string _newTag = "";
|
|
public ObservableCollection<string> Tags { get; } = [];
|
|
public string[] TagSuggestions => VorgangTagDisplay.Suggestions;
|
|
|
|
[ObservableProperty] private string _titleError = "";
|
|
[ObservableProperty] private string _studentsError = "";
|
|
|
|
public string DialogTitle => _editing is null ? "Vorgang anlegen" : "Vorgang bearbeiten";
|
|
public Vorgang? Result { get; private set; }
|
|
|
|
public VorgangDialogViewModel(List<StudentOption> rosterStudents, Vorgang? editing)
|
|
{
|
|
_editing = editing;
|
|
StudentOptions = new ObservableCollection<VorgangStudentOption>(rosterStudents.Select(s =>
|
|
new VorgangStudentOption(s.Id, s.Name) { IsSelected = editing?.StudentIds.Contains(s.Id) == true }));
|
|
if (editing is null) return;
|
|
|
|
Title = editing.Title;
|
|
Description = editing.Description;
|
|
foreach (var tag in editing.Tags) Tags.Add(tag);
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void AddTag()
|
|
{
|
|
if (string.IsNullOrWhiteSpace(NewTag)) return;
|
|
var tag = NewTag.Trim();
|
|
if (!Tags.Contains(tag)) Tags.Add(tag);
|
|
NewTag = "";
|
|
}
|
|
|
|
[RelayCommand] private void RemoveTag(string? tag) { if (tag is not null) Tags.Remove(tag); }
|
|
|
|
[RelayCommand]
|
|
private void Save()
|
|
{
|
|
TitleError = ""; StudentsError = "";
|
|
var valid = true;
|
|
|
|
if (string.IsNullOrWhiteSpace(Title)) { TitleError = "Titel erforderlich."; valid = false; }
|
|
var selectedIds = StudentOptions.Where(s => s.IsSelected).Select(s => s.StudentId).ToList();
|
|
if (selectedIds.Count == 0) { StudentsError = "Mindestens eine/n Schüler*in auswählen."; valid = false; }
|
|
|
|
if (!valid) return;
|
|
|
|
Result = _editing ?? new Vorgang();
|
|
Result.Title = Title.Trim();
|
|
Result.Description = (Description ?? "").Trim();
|
|
Result.StudentIds = selectedIds;
|
|
Result.Tags = Tags.ToList();
|
|
}
|
|
}
|
|
|
|
// ── Dialog: Klassenbuchzeile an (bestehenden oder neuen) Vorgang anheften ────
|
|
|
|
public partial class PinToVorgangDialogViewModel : ObservableObject
|
|
{
|
|
public string RowSummary { get; }
|
|
public List<VorgangItem> MatchingOpenCases { get; }
|
|
public bool HasMatchingOpenCases => MatchingOpenCases.Count > 0;
|
|
|
|
[ObservableProperty] private VorgangItem? _selectedCase;
|
|
[ObservableProperty] private string _newTitle = "";
|
|
[ObservableProperty] private string _error = "";
|
|
|
|
public PinToVorgangChoice? Result { get; private set; }
|
|
|
|
public PinToVorgangDialogViewModel(string rowSummary, List<VorgangItem> matchingOpenCases)
|
|
{
|
|
RowSummary = rowSummary;
|
|
MatchingOpenCases = matchingOpenCases;
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void Confirm()
|
|
{
|
|
Error = "";
|
|
if (SelectedCase is not null) { Result = new PinToVorgangChoice(SelectedCase.Model.Id, null); return; }
|
|
if (!string.IsNullOrWhiteSpace(NewTitle)) { Result = new PinToVorgangChoice(null, NewTitle.Trim()); return; }
|
|
Error = "Bestehenden Vorgang wählen oder Titel für einen neuen Vorgang eingeben.";
|
|
}
|
|
}
|