feat: Pädagogische Klassen-Aufgaben, Prioritäten/Checklisten, Dashboard-Schnellzugriffe
Neue Aufgabenart über bestehende Felder (WorkTask.Kind=Reminder + GroupId) statt Vererbung, mit Priorität (TaskPriority) und optionaler Abhak-Liste (ChecklistItems, wahlweise Kurs- Roster oder Freitext). Kurs-Dashboard zeigt jetzt eine "Anstehende Aufgaben"-Karte samt direktem Anlege-Button; derselbe Anlege-Einstieg wurde auch ins Hauptdashboard und (als Schnellüberblick samt fehlender Übersicht/Mitarbeit-Buttons) in die Gruppenliste gezogen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -57,6 +57,30 @@ public static class TaskCategoryDisplay
|
||||
Enum.GetValues<TaskCategory>().FirstOrDefault(c => Label(c) == label, TaskCategory.Other);
|
||||
}
|
||||
|
||||
public static class TaskPriorityDisplay
|
||||
{
|
||||
public static string Label(TaskPriority p) => p switch
|
||||
{
|
||||
TaskPriority.Low => "Niedrig",
|
||||
TaskPriority.Normal => "Normal",
|
||||
TaskPriority.High => "Hoch",
|
||||
_ => p.ToString(),
|
||||
};
|
||||
|
||||
public static string ColorHex(TaskPriority p) => p switch
|
||||
{
|
||||
TaskPriority.Low => "#9E9E9E",
|
||||
TaskPriority.Normal => "#1976D2",
|
||||
TaskPriority.High => "#D32F2F",
|
||||
_ => "#9E9E9E",
|
||||
};
|
||||
|
||||
public static string[] Options { get; } = Enum.GetValues<TaskPriority>().Select(Label).ToArray();
|
||||
|
||||
public static TaskPriority FromLabel(string? label) =>
|
||||
Enum.GetValues<TaskPriority>().FirstOrDefault(p => Label(p) == label, TaskPriority.Normal);
|
||||
}
|
||||
|
||||
public static class TaskRecurrenceDisplay
|
||||
{
|
||||
public static string Label(TaskRecurrence r) => r switch
|
||||
@@ -218,6 +242,7 @@ public partial class WorkTaskListViewModel : ObservableObject
|
||||
EstimatedMinutes = item.Model.EstimatedMinutes,
|
||||
Recurrence = item.Model.Recurrence,
|
||||
Kind = item.Model.Kind,
|
||||
Priority = item.Model.Priority,
|
||||
Notes = item.Model.Notes,
|
||||
Status = WorkTaskStatus.Open,
|
||||
});
|
||||
@@ -250,6 +275,17 @@ public class WorkTaskListItem(WorkTask model, string? groupName, int actualMinut
|
||||
public string StatusColorHex => WorkTaskStatusDisplay.ColorHex(Model.Status);
|
||||
public string EstimatedMinutesDisplay => Model.EstimatedMinutes is { } m ? $"{m} min" : "";
|
||||
|
||||
// Priorität (Nutzer-Feedback: pädagogische Erinnerungen farblich nach Dringlichkeit absetzen) —
|
||||
// nur bei High überhaupt anzeigen, Normal/Low sollen nicht zusätzlich "schreien".
|
||||
public bool ShowPriority => Model.Priority == TaskPriority.High;
|
||||
public string PriorityLabel => TaskPriorityDisplay.Label(Model.Priority);
|
||||
public string PriorityColorHex => TaskPriorityDisplay.ColorHex(Model.Priority);
|
||||
|
||||
// Abhak-Liste (Nutzer-Feedback: Namensliste/Teil-Punkte optional zuschaltbar).
|
||||
public bool HasChecklist => Model.ChecklistItems.Count > 0;
|
||||
public string ChecklistProgressDisplay => Model.ChecklistItems.Count == 0 ? ""
|
||||
: $"{Model.ChecklistItems.Count(c => c.IsDone)} / {Model.ChecklistItems.Count} erledigt";
|
||||
|
||||
// Wiederkehrende Aufgabe (6.1.4).
|
||||
public bool IsRecurring => Model.Recurrence != TaskRecurrence.None;
|
||||
public string RecurrenceLabel => $"🔁 {TaskRecurrenceDisplay.Label(Model.Recurrence)}";
|
||||
@@ -266,6 +302,10 @@ public class WorkTaskListItem(WorkTask model, string? groupName, int actualMinut
|
||||
public partial class AddEditWorkTaskDialogViewModel : ObservableObject
|
||||
{
|
||||
private readonly WorkTask? _source;
|
||||
/// Liefert die aktiven Mitglieder einer Gruppe (StudentId, Anzeigename) für "Aus Kursliste
|
||||
/// befüllen" — optional/injizierbar statt eines festen Repository-Typs, damit dieses ViewModel
|
||||
/// (wie bisher) auch ohne DI-Container in Tests per `new` gebaut werden kann.
|
||||
private readonly Func<Guid, List<(Guid StudentId, string Name)>>? _loadRoster;
|
||||
|
||||
[ObservableProperty] private string _title = "";
|
||||
[ObservableProperty] private string _selectedCategory = TaskCategoryDisplay.Options[0];
|
||||
@@ -273,8 +313,10 @@ public partial class AddEditWorkTaskDialogViewModel : ObservableObject
|
||||
[ObservableProperty] private string _dueDateText = "";
|
||||
[ObservableProperty] private string _estimatedMinutesText = "";
|
||||
[ObservableProperty] private string _selectedRecurrence = TaskRecurrenceDisplay.Options[0];
|
||||
[ObservableProperty] private string _selectedPriority = TaskPriorityDisplay.Label(TaskPriority.Normal);
|
||||
[ObservableProperty] private string _notes = "";
|
||||
[ObservableProperty] private bool _isReminder;
|
||||
[ObservableProperty] private string _newChecklistItemText = "";
|
||||
[ObservableProperty] private string _titleError = "";
|
||||
[ObservableProperty] private string _dueDateError = "";
|
||||
[ObservableProperty] private string _estimatedMinutesError = "";
|
||||
@@ -284,15 +326,24 @@ public partial class AddEditWorkTaskDialogViewModel : ObservableObject
|
||||
: (IsReminder ? "Erinnerung bearbeiten" : "Aufgabe bearbeiten");
|
||||
public List<string> CategoryOptions { get; } = [.. TaskCategoryDisplay.Options];
|
||||
public List<string> RecurrenceOptions { get; } = [.. TaskRecurrenceDisplay.Options];
|
||||
public List<string> PriorityOptions { get; } = [.. TaskPriorityDisplay.Options];
|
||||
public List<LearningGroup> Groups { get; }
|
||||
public WorkTask? Result { get; private set; }
|
||||
|
||||
partial void OnIsReminderChanged(bool value) => OnPropertyChanged(nameof(DialogTitle));
|
||||
// Abhak-Liste (Nutzer-Feedback: wahlweise Namensliste der Kurs-Schüler oder freie Teil-Punkte,
|
||||
// beides über denselben Zeilentyp).
|
||||
public ObservableCollection<ChecklistItemRow> ChecklistItems { get; } = [];
|
||||
public bool CanFillFromRoster => SelectedGroup is not null && _loadRoster is not null;
|
||||
|
||||
public AddEditWorkTaskDialogViewModel(WorkTask? source, List<LearningGroup> groups, bool startAsReminder = false)
|
||||
partial void OnIsReminderChanged(bool value) => OnPropertyChanged(nameof(DialogTitle));
|
||||
partial void OnSelectedGroupChanged(LearningGroup? value) => OnPropertyChanged(nameof(CanFillFromRoster));
|
||||
|
||||
public AddEditWorkTaskDialogViewModel(WorkTask? source, List<LearningGroup> groups, bool startAsReminder = false,
|
||||
Func<Guid, List<(Guid StudentId, string Name)>>? loadRoster = null)
|
||||
{
|
||||
_source = source;
|
||||
Groups = groups;
|
||||
_loadRoster = loadRoster;
|
||||
if (source is null) { IsReminder = startAsReminder; return; }
|
||||
|
||||
Title = source.Title;
|
||||
@@ -301,8 +352,37 @@ public partial class AddEditWorkTaskDialogViewModel : ObservableObject
|
||||
DueDateText = source.DueDate?.ToString("dd.MM.yyyy", CultureInfo.InvariantCulture) ?? "";
|
||||
EstimatedMinutesText = source.EstimatedMinutes?.ToString(CultureInfo.InvariantCulture) ?? "";
|
||||
SelectedRecurrence = TaskRecurrenceDisplay.Label(source.Recurrence);
|
||||
SelectedPriority = TaskPriorityDisplay.Label(source.Priority);
|
||||
Notes = source.Notes ?? "";
|
||||
IsReminder = source.Kind == TaskKind.Reminder;
|
||||
foreach (var item in source.ChecklistItems)
|
||||
ChecklistItems.Add(new ChecklistItemRow(item));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void AddChecklistItem()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(NewChecklistItemText)) return;
|
||||
ChecklistItems.Add(new ChecklistItemRow(NewChecklistItemText.Trim()));
|
||||
NewChecklistItemText = "";
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void RemoveChecklistItem(ChecklistItemRow? row)
|
||||
{
|
||||
if (row is not null) ChecklistItems.Remove(row);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void FillFromRoster()
|
||||
{
|
||||
if (SelectedGroup is null || _loadRoster is null) return;
|
||||
var existingStudentIds = ChecklistItems.Where(c => c.StudentId is not null).Select(c => c.StudentId!.Value).ToHashSet();
|
||||
foreach (var (studentId, name) in _loadRoster(SelectedGroup.Id))
|
||||
{
|
||||
if (!existingStudentIds.Contains(studentId))
|
||||
ChecklistItems.Add(new ChecklistItemRow(name, studentId));
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -346,13 +426,39 @@ public partial class AddEditWorkTaskDialogViewModel : ObservableObject
|
||||
EstimatedMinutes = estimatedMinutes,
|
||||
Recurrence = recurrence,
|
||||
Kind = IsReminder ? TaskKind.Reminder : TaskKind.WorkItem,
|
||||
Priority = TaskPriorityDisplay.FromLabel(SelectedPriority),
|
||||
Status = _source?.Status ?? WorkTaskStatus.Open,
|
||||
Notes = string.IsNullOrWhiteSpace(Notes) ? null : Notes.Trim(),
|
||||
ChecklistItems = ChecklistItems.Select(c => new ChecklistItem
|
||||
{ Id = c.Id, Label = c.Label, IsDone = c.IsDone, StudentId = c.StudentId }).ToList(),
|
||||
CreatedAt = _source?.CreatedAt ?? DateTime.UtcNow,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public partial class ChecklistItemRow : ObservableObject
|
||||
{
|
||||
public Guid Id { get; }
|
||||
public Guid? StudentId { get; }
|
||||
[ObservableProperty] private string _label;
|
||||
[ObservableProperty] private bool _isDone;
|
||||
|
||||
public ChecklistItemRow(ChecklistItem source)
|
||||
{
|
||||
Id = source.Id;
|
||||
StudentId = source.StudentId;
|
||||
_label = source.Label;
|
||||
_isDone = source.IsDone;
|
||||
}
|
||||
|
||||
public ChecklistItemRow(string label, Guid? studentId = null)
|
||||
{
|
||||
Id = Guid.NewGuid();
|
||||
StudentId = studentId;
|
||||
_label = label;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Zeiterfassung (6.2) ──────────────────────────────────────────────────────
|
||||
|
||||
public partial class TimeTrackingViewModel : ObservableObject
|
||||
|
||||
Reference in New Issue
Block a user