Kurs-Übersicht: fehlende Hausaufgaben; Erinnerungen als eigene Aufgabenart; Kalender-/Sitzplan-Bugfixes

Kurs-Übersicht: neue Karte "Fehlende Hausaufgaben" listet Schüler mit nicht
gemachter Hausaufgabe aus der letzten Mitarbeitssitzung.

Aufgabenverwaltung: WorkTask.Kind unterscheidet jetzt Arbeitsaufträge von
reinen Erinnerungen ohne Zeitbezug (z.B. "morgen Ansage an die Klasse
machen") - eigener Button "🔔 Erinnerung", eigenes Icon in Liste/Dashboard,
läuft nicht in die Zeitauswertung ein.

Drei Bugfixes aus Nutzer-Feedback:
- Dashboard-Kalender zeigte eine Stunde doppelt, wenn sie über "Sitzung
  erzeugen" mit einer Mitarbeitssitzung verknüpft war.
- Der Sitzplan-Tab legte beim bloßen Öffnen einer Gruppe (nicht erst bei
  echter Nutzung) eine leere "Sitzplan"-Sitzung für heute an.
- Der "Meine Klasse"-Ring im Kalender erschien erst, sobald für den Tag
  eine Lesson existierte, statt schon laut Stundenplan.

Außerdem: Drawer-Icons pinnen jetzt explizit auf die farbige Emoji-Schrift
(Windows kann sonst je nach Font-Fallback auf eine einfarbige Variante
ausweichen).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 23:17:18 +02:00
co-authored by Claude Sonnet 5
parent 4d2a89b14b
commit 52a1d45539
16 changed files with 505 additions and 57 deletions
@@ -97,7 +97,8 @@ public partial class WorkTaskListViewModel : ObservableObject
public ObservableCollection<WorkTaskListItem> Tasks { get; } = [];
public string CountSummary => $"{Tasks.Count} Aufgabe(n)";
public Func<WorkTask?, Task<WorkTask?>>? OnEditTask { get; set; }
/// Zweiter Parameter = Dialog startet vorbelegt als Erinnerung (nur bei AddReminder true).
public Func<WorkTask?, bool, Task<WorkTask?>>? OnEditTask { get; set; }
private Dictionary<Guid, string> _groupNames = [];
@@ -166,7 +167,20 @@ public partial class WorkTaskListViewModel : ObservableObject
private async Task AddTask()
{
if (OnEditTask is null) return;
var result = await OnEditTask(null);
var result = await OnEditTask(null, false);
if (result is null) return;
_tasks.Save(result);
Refresh();
}
/// Eigener Einstieg statt eines Umschalters im selben Dialog-Aufruf wie AddTask (6.1.1
/// Nutzer-Feedback): "Erinnerung" soll sich nicht wie eine Variante der Zeiterfassung anfühlen,
/// sondern wie eine eigene, schnelle Aktion.
[RelayCommand]
private async Task AddReminder()
{
if (OnEditTask is null) return;
var result = await OnEditTask(null, true);
if (result is null) return;
_tasks.Save(result);
Refresh();
@@ -176,7 +190,7 @@ public partial class WorkTaskListViewModel : ObservableObject
private async Task EditTask(WorkTaskListItem? item)
{
if (item is null || OnEditTask is null) return;
var result = await OnEditTask(item.Model);
var result = await OnEditTask(item.Model, false);
if (result is null) return;
_tasks.Save(result);
Refresh();
@@ -203,6 +217,7 @@ public partial class WorkTaskListViewModel : ObservableObject
? dueDate.AddDays(7) : dueDate.AddMonths(1),
EstimatedMinutes = item.Model.EstimatedMinutes,
Recurrence = item.Model.Recurrence,
Kind = item.Model.Kind,
Notes = item.Model.Notes,
Status = WorkTaskStatus.Open,
});
@@ -224,7 +239,8 @@ public class WorkTaskListItem(WorkTask model, string? groupName, int actualMinut
{
public WorkTask Model { get; } = model;
public string Title => Model.Title;
public string CategoryLabel => TaskCategoryDisplay.Label(Model.Category);
public bool IsReminder => Model.Kind == TaskKind.Reminder;
public string CategoryLabel => IsReminder ? "Erinnerung" : TaskCategoryDisplay.Label(Model.Category);
public string GroupName => groupName ?? "";
public string DueDateDisplay => Model.DueDate?.ToString("dd.MM.yyyy", CultureInfo.InvariantCulture) ?? "";
public bool IsOverdue => Model.DueDate is { } d && d < DateOnly.FromDateTime(DateTime.Today)
@@ -258,21 +274,26 @@ public partial class AddEditWorkTaskDialogViewModel : ObservableObject
[ObservableProperty] private string _estimatedMinutesText = "";
[ObservableProperty] private string _selectedRecurrence = TaskRecurrenceDisplay.Options[0];
[ObservableProperty] private string _notes = "";
[ObservableProperty] private bool _isReminder;
[ObservableProperty] private string _titleError = "";
[ObservableProperty] private string _dueDateError = "";
[ObservableProperty] private string _estimatedMinutesError = "";
public string DialogTitle => _source is null ? "Aufgabe anlegen" : "Aufgabe bearbeiten";
public string DialogTitle => _source is null
? (IsReminder ? "Erinnerung anlegen" : "Aufgabe anlegen")
: (IsReminder ? "Erinnerung bearbeiten" : "Aufgabe bearbeiten");
public List<string> CategoryOptions { get; } = [.. TaskCategoryDisplay.Options];
public List<string> RecurrenceOptions { get; } = [.. TaskRecurrenceDisplay.Options];
public List<LearningGroup> Groups { get; }
public WorkTask? Result { get; private set; }
public AddEditWorkTaskDialogViewModel(WorkTask? source, List<LearningGroup> groups)
partial void OnIsReminderChanged(bool value) => OnPropertyChanged(nameof(DialogTitle));
public AddEditWorkTaskDialogViewModel(WorkTask? source, List<LearningGroup> groups, bool startAsReminder = false)
{
_source = source;
Groups = groups;
if (source is null) return;
if (source is null) { IsReminder = startAsReminder; return; }
Title = source.Title;
SelectedCategory = TaskCategoryDisplay.Label(source.Category);
@@ -281,6 +302,7 @@ public partial class AddEditWorkTaskDialogViewModel : ObservableObject
EstimatedMinutesText = source.EstimatedMinutes?.ToString(CultureInfo.InvariantCulture) ?? "";
SelectedRecurrence = TaskRecurrenceDisplay.Label(source.Recurrence);
Notes = source.Notes ?? "";
IsReminder = source.Kind == TaskKind.Reminder;
}
[RelayCommand]
@@ -303,8 +325,9 @@ public partial class AddEditWorkTaskDialogViewModel : ObservableObject
if (recurrence != TaskRecurrence.None && dueDate is null)
{ DueDateError = "Fälligkeitsdatum erforderlich, damit die nächste Instanz geplant werden kann."; valid = false; }
// Erinnerungen haben bewusst keinen Zeitbezug — die Minutenschätzung entfällt.
int? estimatedMinutes = null;
if (!string.IsNullOrWhiteSpace(EstimatedMinutesText))
if (!IsReminder && !string.IsNullOrWhiteSpace(EstimatedMinutesText))
{
if (!int.TryParse(EstimatedMinutesText, out var m) || m <= 0)
{ EstimatedMinutesError = "Ganze Zahl > 0 erwartet."; valid = false; }
@@ -322,6 +345,7 @@ public partial class AddEditWorkTaskDialogViewModel : ObservableObject
DueDate = dueDate,
EstimatedMinutes = estimatedMinutes,
Recurrence = recurrence,
Kind = IsReminder ? TaskKind.Reminder : TaskKind.WorkItem,
Status = _source?.Status ?? WorkTaskStatus.Open,
Notes = string.IsNullOrWhiteSpace(Notes) ? null : Notes.Trim(),
CreatedAt = _source?.CreatedAt ?? DateTime.UtcNow,