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:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user