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:
@@ -79,6 +79,9 @@ public partial class DashboardViewModel : ObservableObject
|
||||
// OnNavigateToGroup (Lerngruppen-Kacheln, Tab "Übersicht"), da der Sprung von einer konkreten
|
||||
// Stunde aus sinnvollerweise direkt in die Mitarbeitserfassung führt.
|
||||
public Action<Guid>? OnNavigateToLesson { get; set; }
|
||||
// Direkter Anlege-Einstieg aus der "Offene Aufgaben"-Kachel (Nutzer-Feedback), statt erst über
|
||||
// "Arbeitszeit" navigieren zu müssen — gleiches Dialog-Delegate-Muster wie im Kurs-Dashboard.
|
||||
public Func<bool, Task<WorkTask?>>? OnAddTask { get; set; }
|
||||
public Action<Guid>? OnNavigateToExam { get; set; }
|
||||
// Sprungziel für eine ungeplante Stunde — führt direkt in den Verlaufsplan-Tab der Gruppe
|
||||
// (nicht den Standard-Tab von OnNavigateToGroup), damit das Thema gleich ergänzt werden kann.
|
||||
@@ -162,7 +165,8 @@ public partial class DashboardViewModel : ObservableObject
|
||||
OpenTasks.Add(new() { Title = t.Title,
|
||||
DueDate = t.DueDate?.ToString("dd.MM.") ?? "",
|
||||
IsOverdue = t.DueDate.HasValue && t.DueDate < today,
|
||||
IsReminder = t.Kind == TaskKind.Reminder });
|
||||
IsReminder = t.Kind == TaskKind.Reminder,
|
||||
IsHighPriority = t.Priority == TaskPriority.High });
|
||||
|
||||
CurrentGroups.Clear();
|
||||
foreach (var g in groups.Values.OrderBy(g => g.Name))
|
||||
@@ -678,6 +682,18 @@ public partial class DashboardViewModel : ObservableObject
|
||||
{ if (item is not null) OnNavigateToStudent?.Invoke(item.StudentId); }
|
||||
[RelayCommand] private void Refresh() => Load();
|
||||
|
||||
[RelayCommand] private Task AddTask() => AddTaskInternal(startAsReminder: false);
|
||||
[RelayCommand] private Task AddReminder() => AddTaskInternal(startAsReminder: true);
|
||||
|
||||
private async Task AddTaskInternal(bool startAsReminder)
|
||||
{
|
||||
if (OnAddTask is null) return;
|
||||
var result = await OnAddTask(startAsReminder);
|
||||
if (result is null) return;
|
||||
_tasks.Save(result);
|
||||
Load();
|
||||
}
|
||||
|
||||
private class DayAgg
|
||||
{
|
||||
public bool HasLesson;
|
||||
@@ -698,7 +714,7 @@ public class LessonItem
|
||||
public string Room { get; set; } = "";
|
||||
public bool HasRoom => !string.IsNullOrWhiteSpace(Room);
|
||||
}
|
||||
public class TaskItem { public string Title { get; set; } = ""; public string DueDate { get; set; } = ""; public bool IsOverdue { get; set; } public bool IsReminder { get; set; } }
|
||||
public class TaskItem { public string Title { get; set; } = ""; public string DueDate { get; set; } = ""; public bool IsOverdue { get; set; } public bool IsReminder { get; set; } public bool IsHighPriority { get; set; } }
|
||||
public class GroupChip { public Guid GroupId { get; set; } public string Name { get; set; } = ""; public string Subject { get; set; } = ""; }
|
||||
|
||||
// ── Offene Entschuldigungen (aus Mitarbeit-Fehltagen) ────────────────────────
|
||||
|
||||
@@ -4,6 +4,7 @@ using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.ViewModels;
|
||||
using LehrerApp.Desktop.ViewModels.Workload;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace LehrerApp.Desktop.ViewModels.Groups;
|
||||
@@ -36,6 +37,7 @@ public partial class GroupOverviewViewModel : ObservableObject
|
||||
private readonly IParticipationRepository _entries;
|
||||
private readonly IStudentRepository _students;
|
||||
private readonly IDocumentationRepository _documentation;
|
||||
private readonly IWorkTaskRepository _tasks;
|
||||
private readonly AttendanceBalanceService _attendanceBalance;
|
||||
private readonly SchoolYearService _schoolYear;
|
||||
|
||||
@@ -69,18 +71,46 @@ public partial class GroupOverviewViewModel : ObservableObject
|
||||
/// 6 Planung, 7 Kompetenzen, 8 Dokumentation).
|
||||
public Action<int>? OnNavigateToTab { get; set; }
|
||||
|
||||
/// Anders als OnNavigateToTab kein Tab innerhalb dieser Detailansicht, sondern ein Sprung in
|
||||
/// den eigenständigen Top-Level-Aufgabenbereich (MainWindowViewModel.NavigateToWorkload) — von
|
||||
/// hier aus nicht direkt aufrufbar (keine ViewModel-zu-ViewModel-Referenz, siehe
|
||||
/// GroupDetailView.axaml.cs, gleiches Muster wie LessonViewerDialog/TeachingModeWindow).
|
||||
public Action? OnNavigateToWorkload { get; set; }
|
||||
|
||||
[RelayCommand] private void NavigateToPlanning() => OnNavigateToTab?.Invoke(6);
|
||||
[RelayCommand] private void NavigateToExams() => OnNavigateToTab?.Invoke(4);
|
||||
[RelayCommand] private void NavigateToParticipation() => OnNavigateToTab?.Invoke(3);
|
||||
[RelayCommand] private void NavigateToDocumentation() => OnNavigateToTab?.Invoke(8);
|
||||
[RelayCommand] private void NavigateToWorkload() => OnNavigateToWorkload?.Invoke();
|
||||
|
||||
// ── Anstehende Aufgaben für diese Klasse (Nutzer-Feedback: pädagogische Erinnerungen/Aufgaben
|
||||
// sollen "gleichberechtigt" auch im Kurs-Dashboard stehen, nicht nur im Hauptdashboard) ───────
|
||||
private const int GroupTasksMaxCount = 5;
|
||||
public ObservableCollection<GroupTaskItem> GroupTasks { get; } = [];
|
||||
public bool HasGroupTasks => GroupTasks.Count > 0;
|
||||
|
||||
/// Öffnet den Aufgaben-Dialog mit dieser Gruppe vorbelegt (Nutzer-Feedback: direkter
|
||||
/// Anlege-Einstieg aus dem Kurs-Dashboard, statt erst über "Zu den Aufgaben" springen zu
|
||||
/// müssen). Gleiches View-Code-Behind-Delegate-Muster wie OnNavigateToWorkload.
|
||||
public Func<Guid, Task<WorkTask?>>? OnAddGroupTask { get; set; }
|
||||
|
||||
[RelayCommand]
|
||||
private async Task AddGroupTask()
|
||||
{
|
||||
if (OnAddGroupTask is null) return;
|
||||
var result = await OnAddGroupTask(_groupId);
|
||||
if (result is null) return;
|
||||
_tasks.Save(result);
|
||||
LoadGroupTasks(DateOnly.FromDateTime(DateTime.Today));
|
||||
}
|
||||
|
||||
public GroupOverviewViewModel(ILessonRepository lessons, IExamRepository exams,
|
||||
IParticipationSessionRepository sessions, IParticipationRepository entries,
|
||||
IStudentRepository students, IDocumentationRepository documentation,
|
||||
IStudentRepository students, IDocumentationRepository documentation, IWorkTaskRepository tasks,
|
||||
AttendanceBalanceService attendanceBalance, SchoolYearService schoolYear)
|
||||
{
|
||||
_lessons = lessons; _exams = exams; _sessions = sessions;
|
||||
_entries = entries; _students = students; _documentation = documentation;
|
||||
_entries = entries; _students = students; _documentation = documentation; _tasks = tasks;
|
||||
_attendanceBalance = attendanceBalance; _schoolYear = schoolYear;
|
||||
}
|
||||
|
||||
@@ -102,6 +132,7 @@ public partial class GroupOverviewViewModel : ObservableObject
|
||||
LoadDraftDocumentationCount();
|
||||
LoadAttendanceNotices(today);
|
||||
LoadMissingHomework();
|
||||
LoadGroupTasks(today);
|
||||
}
|
||||
|
||||
private void LoadNextLesson(DateOnly today)
|
||||
@@ -260,6 +291,19 @@ public partial class GroupOverviewViewModel : ObservableObject
|
||||
}
|
||||
OnPropertyChanged(nameof(HasMissingHomework));
|
||||
}
|
||||
|
||||
private void LoadGroupTasks(DateOnly today)
|
||||
{
|
||||
GroupTasks.Clear();
|
||||
foreach (var t in _tasks.GetByGroup(_groupId)
|
||||
.Where(t => t.Status != WorkTaskStatus.Done)
|
||||
.OrderBy(t => t.DueDate ?? DateOnly.MaxValue).Take(GroupTasksMaxCount))
|
||||
GroupTasks.Add(new GroupTaskItem(t.Title, t.Kind == TaskKind.Reminder,
|
||||
t.DueDate?.ToString("dd.MM.yyyy") ?? "",
|
||||
t.DueDate is { } d && d < today, TaskPriorityDisplay.ColorHex(t.Priority),
|
||||
t.Priority == TaskPriority.High));
|
||||
OnPropertyChanged(nameof(HasGroupTasks));
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class MissingHomeworkItem(string studentName, string statusLabel)
|
||||
@@ -267,3 +311,14 @@ public sealed class MissingHomeworkItem(string studentName, string statusLabel)
|
||||
public string StudentName { get; } = studentName;
|
||||
public string StatusLabel { get; } = statusLabel;
|
||||
}
|
||||
|
||||
public sealed class GroupTaskItem(string title, bool isReminder, string dueDateDisplay, bool isOverdue,
|
||||
string priorityColorHex, bool isHighPriority)
|
||||
{
|
||||
public string Title { get; } = title;
|
||||
public bool IsReminder { get; } = isReminder;
|
||||
public string DueDateDisplay { get; } = dueDateDisplay;
|
||||
public bool IsOverdue { get; } = isOverdue;
|
||||
public string PriorityColorHex { get; } = priorityColorHex;
|
||||
public bool IsHighPriority { get; } = isHighPriority;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.ViewModels.Students;
|
||||
using LehrerApp.Desktop.ViewModels.Workload;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace LehrerApp.Desktop.ViewModels.Groups;
|
||||
@@ -12,8 +13,13 @@ namespace LehrerApp.Desktop.ViewModels.Groups;
|
||||
|
||||
public partial class GroupListViewModel : ObservableObject
|
||||
{
|
||||
private const int QuickTasksMaxCount = 3;
|
||||
|
||||
private readonly IGroupRepository _groups;
|
||||
private readonly ISubjectRepository _subjects;
|
||||
private readonly ILessonRepository _lessons;
|
||||
private readonly IExamRepository _exams;
|
||||
private readonly IWorkTaskRepository _tasks;
|
||||
|
||||
public Action<Guid, int>? OnNavigateToDetail { get; set; }
|
||||
public Func<Task>? OnAddGroup { get; set; }
|
||||
@@ -39,9 +45,22 @@ public partial class GroupListViewModel : ObservableObject
|
||||
public ObservableCollection<string> SchoolYears { get; } = [];
|
||||
public ObservableCollection<GroupListItem> Groups { get; } = [];
|
||||
|
||||
public GroupListViewModel(IGroupRepository groups, ISubjectRepository subjects, SchoolYearService sy)
|
||||
// ── Schnellüberblick im Auswahl-Panel (Nutzer-Feedback: "oberhalb der Buttonliste ein paar
|
||||
// Daten auswerfen. Nächste Stunde, nächste Arbeit, wichtige Todos") — bewusst dieselben
|
||||
// kompakten Kennzahlen wie die obersten Karten des Kurs-Dashboards (GroupOverviewViewModel),
|
||||
// hier nur ohne eigenen Tab-Wechsel, da man ohnehin schon auf der Gruppenliste steht.
|
||||
[ObservableProperty] private bool _quickHasNextLesson;
|
||||
[ObservableProperty] private string _quickNextLessonLabel = "";
|
||||
[ObservableProperty] private bool _quickHasNextExam;
|
||||
[ObservableProperty] private string _quickNextExamLabel = "";
|
||||
public ObservableCollection<GroupTaskItem> QuickTasks { get; } = [];
|
||||
public bool QuickHasTasks => QuickTasks.Count > 0;
|
||||
public bool QuickHasAnything => QuickHasNextLesson || QuickHasNextExam || QuickHasTasks;
|
||||
|
||||
public GroupListViewModel(IGroupRepository groups, ISubjectRepository subjects, SchoolYearService sy,
|
||||
ILessonRepository lessons, IExamRepository exams, IWorkTaskRepository tasks)
|
||||
{
|
||||
_groups = groups; _subjects = subjects;
|
||||
_groups = groups; _subjects = subjects; _lessons = lessons; _exams = exams; _tasks = tasks;
|
||||
foreach (var y in sy.RecentSchoolYears()) SchoolYears.Add(y);
|
||||
SelectedSchoolYear = sy.CurrentSchoolYear();
|
||||
}
|
||||
@@ -58,6 +77,44 @@ public partial class GroupListViewModel : ObservableObject
|
||||
RollOverGroupCommand.NotifyCanExecuteChanged();
|
||||
ToggleArchiveCommand.NotifyCanExecuteChanged();
|
||||
DeleteGroupCommand.NotifyCanExecuteChanged();
|
||||
LoadQuickInfo();
|
||||
}
|
||||
|
||||
private void LoadQuickInfo()
|
||||
{
|
||||
QuickTasks.Clear();
|
||||
if (SelectedGroup is null)
|
||||
{
|
||||
QuickHasNextLesson = false; QuickNextLessonLabel = "";
|
||||
QuickHasNextExam = false; QuickNextExamLabel = "";
|
||||
OnPropertyChanged(nameof(QuickHasTasks));
|
||||
OnPropertyChanged(nameof(QuickHasAnything));
|
||||
return;
|
||||
}
|
||||
|
||||
var groupId = SelectedGroup.Id;
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
|
||||
var nextLesson = _lessons.GetByGroupAndRange(groupId, today, today.AddDays(90))
|
||||
.Where(l => l.Status == LessonStatus.Planned)
|
||||
.OrderBy(l => l.Date).ThenBy(l => l.LessonNumber ?? 0).FirstOrDefault();
|
||||
QuickHasNextLesson = nextLesson is not null;
|
||||
QuickNextLessonLabel = nextLesson is null ? ""
|
||||
: string.IsNullOrWhiteSpace(nextLesson.Topic)
|
||||
? nextLesson.Date.ToString("dd.MM.yyyy")
|
||||
: $"{nextLesson.Date:dd.MM.yyyy} — {nextLesson.Topic}";
|
||||
|
||||
var nextExam = _exams.GetByGroup(groupId).Where(e => e.Date >= today).MinBy(e => e.Date);
|
||||
QuickHasNextExam = nextExam is not null;
|
||||
QuickNextExamLabel = nextExam is null ? "" : $"{nextExam.Date:dd.MM.yyyy} — {nextExam.Title}";
|
||||
|
||||
foreach (var t in _tasks.GetByGroup(groupId).Where(t => t.Status != WorkTaskStatus.Done)
|
||||
.OrderBy(t => t.DueDate ?? DateOnly.MaxValue).Take(QuickTasksMaxCount))
|
||||
QuickTasks.Add(new GroupTaskItem(t.Title, t.Kind == TaskKind.Reminder,
|
||||
t.DueDate?.ToString("dd.MM.yyyy") ?? "", t.DueDate is { } d && d < today,
|
||||
TaskPriorityDisplay.ColorHex(t.Priority), t.Priority == TaskPriority.High));
|
||||
OnPropertyChanged(nameof(QuickHasTasks));
|
||||
OnPropertyChanged(nameof(QuickHasAnything));
|
||||
}
|
||||
|
||||
public void LoadGroups()
|
||||
|
||||
@@ -168,6 +168,11 @@ public partial class MainWindowViewModel : ObservableObject
|
||||
}
|
||||
|
||||
public void NavigateToStudents() => NavigateTo(NavItem.Students);
|
||||
|
||||
/// Sprungziel aus dem Kurs-Dashboard ("Anstehende Aufgaben für diese Klasse") in den
|
||||
/// Aufgaben-Bereich — anders als NavigateToGroupDetail/NavigateToSettings kein Tab innerhalb
|
||||
/// einer Detailansicht, sondern ein eigener Top-Level-Bereich (NavItem.Workload).
|
||||
public void NavigateToWorkload() => NavigateTo(NavItem.Workload);
|
||||
}
|
||||
|
||||
public enum NavItem { Dashboard, Groups, Students, Exams, Planner, Workload, Settings }
|
||||
|
||||
@@ -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