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:
@@ -462,4 +462,33 @@ public sealed class DashboardViewModelTests
|
||||
Assert.Equal(student.FullName, item.StudentName);
|
||||
Assert.Equal(30.0, item.AbsenceRatePercent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddTask_SpeichertErgebnisUndAktualisiertOffeneAufgaben()
|
||||
{
|
||||
var group = new LearningGroup { Name = "9c" };
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var tasks = new FakeWorkTasks();
|
||||
var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, tasks: tasks);
|
||||
vm.OnAddTask = startAsReminder => Task.FromResult<WorkTask?>(
|
||||
new WorkTask { Title = "Ansage an die Klasse", Kind = startAsReminder ? TaskKind.Reminder : TaskKind.WorkItem });
|
||||
|
||||
await vm.AddReminderCommand.ExecuteAsync(null);
|
||||
|
||||
Assert.Single(tasks.GetAll());
|
||||
Assert.Contains(vm.OpenTasks, t => t.Title == "Ansage an die Klasse" && t.IsReminder);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddTask_OhneDelegatMachtNichts()
|
||||
{
|
||||
var group = new LearningGroup { Name = "9c" };
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var tasks = new FakeWorkTasks();
|
||||
var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, tasks: tasks);
|
||||
|
||||
await vm.AddTaskCommand.ExecuteAsync(null);
|
||||
|
||||
Assert.Empty(tasks.GetAll());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,6 +432,7 @@ public class FakeWorkTasks : IWorkTaskRepository
|
||||
public void Add(WorkTask t) => _all.Add(t);
|
||||
public List<WorkTask> GetByStatus(WorkTaskStatus status) => _all.Where(t => t.Status == status).ToList();
|
||||
public List<WorkTask> GetAll() => _all.ToList();
|
||||
public List<WorkTask> GetByGroup(Guid groupId) => _all.Where(t => t.GroupId == groupId).ToList();
|
||||
public void Save(WorkTask task) { _all.RemoveAll(t => t.Id == task.Id); _all.Add(task); }
|
||||
public void Delete(Guid id) => _all.RemoveAll(t => t.Id == id);
|
||||
public void Restore(Guid trashId) { } // siehe FakeSeatingPlans.Restore
|
||||
|
||||
@@ -19,7 +19,7 @@ public sealed class GroupDetailViewModelTests
|
||||
|
||||
var vm = new GroupDetailViewModel(groups, students, memberships, subjects, exams, grades, tasks,
|
||||
new GroupOverviewViewModel(new FakeLessons(), exams, new FakeSessions([]), new FakeEntries(), students,
|
||||
new FakeDocumentation(), new AttendanceBalanceService(), new SchoolYearService()),
|
||||
new FakeDocumentation(), tasks, new AttendanceBalanceService(), new SchoolYearService()),
|
||||
new ParticipationTabViewModel(new FakeSessions([]), new FakeEntries(), new FakeAspects(),
|
||||
students, memberships, groups, new FakeCompetencyDomains()),
|
||||
new GradeOverviewTabViewModel(grades, exams, new FakeResults(), students, memberships, new GradingService()),
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.ViewModels.Groups;
|
||||
using Xunit;
|
||||
|
||||
namespace LehrerApp.Desktop.Tests;
|
||||
|
||||
/// Tests für den Schnellüberblick im Auswahl-Panel der Gruppenliste (Nutzer-Feedback:
|
||||
/// "oberhalb der Buttonliste ein paar Daten auswerfen. Nächste Stunde, nächste Arbeit,
|
||||
/// wichtige Todos").
|
||||
public sealed class GroupListViewModelTests
|
||||
{
|
||||
private static GroupListViewModel BuildVm(LearningGroup group, FakeLessons? lessons = null,
|
||||
FakeExams? exams = null, FakeWorkTasks? tasks = null) =>
|
||||
new(new FakeGroups([group]), new FakeSubjects([]), new SchoolYearService(),
|
||||
lessons ?? new FakeLessons(), exams ?? new FakeExams([]), tasks ?? new FakeWorkTasks());
|
||||
|
||||
[Fact]
|
||||
public void SelectedGroup_OhneDatenZeigtKeinenSchnellueberblick()
|
||||
{
|
||||
var group = new LearningGroup { Name = "9c" };
|
||||
var vm = BuildVm(group);
|
||||
|
||||
vm.SelectedGroup = vm.Groups.Count > 0 ? vm.Groups[0] : new GroupListItem(group, "");
|
||||
|
||||
Assert.False(vm.QuickHasAnything);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectedGroup_ZeigtNaechsteGeplanteStundeUndKlausur()
|
||||
{
|
||||
var group = new LearningGroup { Name = "9c" };
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var lessons = new FakeLessons();
|
||||
lessons.Add(new Lesson { GroupId = group.Id, Date = today.AddDays(3), Topic = "Redox", Status = LessonStatus.Planned });
|
||||
var exams = new FakeExams([new Exam { GroupId = group.Id, Date = today.AddDays(10), Title = "Klausur 1" }]);
|
||||
var vm = BuildVm(group, lessons: lessons, exams: exams);
|
||||
|
||||
vm.SelectedGroup = new GroupListItem(group, "");
|
||||
|
||||
Assert.True(vm.QuickHasNextLesson);
|
||||
Assert.Contains("Redox", vm.QuickNextLessonLabel);
|
||||
Assert.True(vm.QuickHasNextExam);
|
||||
Assert.Contains("Klausur 1", vm.QuickNextExamLabel);
|
||||
Assert.True(vm.QuickHasAnything);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectedGroup_ZeigtOffeneAufgabenDieserGruppeSortiertNachFaelligkeit()
|
||||
{
|
||||
var group = new LearningGroup { Name = "9c" };
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var tasks = new FakeWorkTasks();
|
||||
tasks.Add(new WorkTask { GroupId = group.Id, Title = "Bald fällig", DueDate = today.AddDays(1), Status = WorkTaskStatus.Open });
|
||||
tasks.Add(new WorkTask { GroupId = group.Id, Title = "Erledigt", DueDate = today, Status = WorkTaskStatus.Done });
|
||||
tasks.Add(new WorkTask { GroupId = Guid.NewGuid(), Title = "Andere Gruppe", DueDate = today, Status = WorkTaskStatus.Open });
|
||||
var vm = BuildVm(group, tasks: tasks);
|
||||
|
||||
vm.SelectedGroup = new GroupListItem(group, "");
|
||||
|
||||
Assert.True(vm.QuickHasTasks);
|
||||
Assert.Equal("Bald fällig", Assert.Single(vm.QuickTasks).Title);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectedGroup_Zuruecksetzen_LeertSchnellueberblick()
|
||||
{
|
||||
var group = new LearningGroup { Name = "9c" };
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var exams = new FakeExams([new Exam { GroupId = group.Id, Date = today.AddDays(10), Title = "Klausur 1" }]);
|
||||
var vm = BuildVm(group, exams: exams);
|
||||
vm.SelectedGroup = new GroupListItem(group, "");
|
||||
Assert.True(vm.QuickHasAnything);
|
||||
|
||||
vm.SelectedGroup = null;
|
||||
|
||||
Assert.False(vm.QuickHasAnything);
|
||||
Assert.Empty(vm.QuickTasks);
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,10 @@ public sealed class GroupOverviewViewModelTests
|
||||
{
|
||||
private static GroupOverviewViewModel NewVm(FakeLessons? lessons = null, FakeExams? exams = null,
|
||||
FakeSessions? sessions = null, FakeEntries? entries = null, FakeStudents? students = null,
|
||||
FakeDocumentation? documentation = null) =>
|
||||
FakeDocumentation? documentation = null, FakeWorkTasks? tasks = null) =>
|
||||
new(lessons ?? new FakeLessons(), exams ?? new FakeExams([]), sessions ?? new FakeSessions([]),
|
||||
entries ?? new FakeEntries(), students ?? new FakeStudents([]), documentation ?? new FakeDocumentation(),
|
||||
new AttendanceBalanceService(), new SchoolYearService());
|
||||
tasks ?? new FakeWorkTasks(), new AttendanceBalanceService(), new SchoolYearService());
|
||||
|
||||
private static (GroupOverviewViewModel Vm, FakeLessons Lessons, FakeExams Exams,
|
||||
FakeSessions Sessions, FakeEntries Entries, FakeStudents Students, Guid GroupId) BuildScenario(
|
||||
@@ -301,4 +301,68 @@ public sealed class GroupOverviewViewModelTests
|
||||
var (vm, _, _, _, _, _, _) = BuildScenario();
|
||||
Assert.False(vm.HasMissingHomework);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnstehendeAufgaben_ZeigtNurOffeneAufgabenDieserGruppeSortiertNachFaelligkeit()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var groupId = Guid.NewGuid();
|
||||
var otherGroupId = Guid.NewGuid();
|
||||
var tasks = new FakeWorkTasks();
|
||||
tasks.Add(new WorkTask { GroupId = groupId, Title = "Später fällig", DueDate = today.AddDays(10), Status = WorkTaskStatus.Open });
|
||||
tasks.Add(new WorkTask { GroupId = groupId, Title = "Bald fällig", DueDate = today.AddDays(1), Status = WorkTaskStatus.Open });
|
||||
tasks.Add(new WorkTask { GroupId = groupId, Title = "Erledigt", DueDate = today, Status = WorkTaskStatus.Done });
|
||||
tasks.Add(new WorkTask { GroupId = otherGroupId, Title = "Anderer Kurs", DueDate = today, Status = WorkTaskStatus.Open });
|
||||
|
||||
var vm = NewVm(tasks: tasks);
|
||||
vm.Initialize(groupId, "Testkurs");
|
||||
|
||||
Assert.True(vm.HasGroupTasks);
|
||||
Assert.Equal(["Bald fällig", "Später fällig"], vm.GroupTasks.Select(t => t.Title));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnstehendeAufgaben_OhneAufgabenIstLeer()
|
||||
{
|
||||
var (vm, _, _, _, _, _, _) = BuildScenario();
|
||||
Assert.False(vm.HasGroupTasks);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NavigateToWorkload_RuftDelegatAuf()
|
||||
{
|
||||
var (vm, _, _, _, _, _, _) = BuildScenario();
|
||||
var called = false;
|
||||
vm.OnNavigateToWorkload = () => called = true;
|
||||
|
||||
vm.NavigateToWorkloadCommand.Execute(null);
|
||||
|
||||
Assert.True(called);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddGroupTask_SpeichertErgebnisMitGruppeUndAktualisiertListe()
|
||||
{
|
||||
var tasks = new FakeWorkTasks();
|
||||
var (vm, _, _, _, _, _, groupId) = BuildScenario();
|
||||
var vmWithTasks = NewVm(tasks: tasks);
|
||||
vmWithTasks.Initialize(groupId, "Testkurs");
|
||||
vmWithTasks.OnAddGroupTask = requestedGroupId =>
|
||||
Task.FromResult<WorkTask?>(new WorkTask { Title = "Material einsammeln", GroupId = requestedGroupId });
|
||||
|
||||
await vmWithTasks.AddGroupTaskCommand.ExecuteAsync(null);
|
||||
|
||||
var saved = Assert.Single(tasks.GetByGroup(groupId));
|
||||
Assert.Equal("Material einsammeln", saved.Title);
|
||||
Assert.True(vmWithTasks.HasGroupTasks);
|
||||
Assert.Equal("Material einsammeln", Assert.Single(vmWithTasks.GroupTasks).Title);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddGroupTask_OhneDelegatMachtNichts()
|
||||
{
|
||||
var (vm, _, _, _, _, _, _) = BuildScenario();
|
||||
await vm.AddGroupTaskCommand.ExecuteAsync(null);
|
||||
Assert.False(vm.HasGroupTasks);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,6 +337,90 @@ public sealed class AddEditWorkTaskDialogViewModelTests
|
||||
var vm = new AddEditWorkTaskDialogViewModel(source, []);
|
||||
Assert.True(vm.IsReminder);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Save_MitPrioritaet_UebernimmtSieInsResult()
|
||||
{
|
||||
var vm = new AddEditWorkTaskDialogViewModel(null, [])
|
||||
{
|
||||
Title = "Wichtig",
|
||||
SelectedPriority = TaskPriorityDisplay.Label(TaskPriority.High),
|
||||
};
|
||||
|
||||
vm.SaveCommand.Execute(null);
|
||||
|
||||
Assert.Equal(TaskPriority.High, vm.Result!.Priority);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Konstruktor_OhneQuelle_StartetMitNormalerPrioritaet()
|
||||
{
|
||||
var vm = new AddEditWorkTaskDialogViewModel(null, []);
|
||||
Assert.Equal(TaskPriorityDisplay.Label(TaskPriority.Normal), vm.SelectedPriority);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChecklistItems_HinzufuegenUndEntfernen_WirdInResultUebernommen()
|
||||
{
|
||||
var vm = new AddEditWorkTaskDialogViewModel(null, []) { Title = "Material einsammeln" };
|
||||
vm.NewChecklistItemText = "Erste Position";
|
||||
vm.AddChecklistItemCommand.Execute(null);
|
||||
vm.NewChecklistItemText = "Zweite Position";
|
||||
vm.AddChecklistItemCommand.Execute(null);
|
||||
|
||||
Assert.Equal(["Erste Position", "Zweite Position"], vm.ChecklistItems.Select(c => c.Label));
|
||||
Assert.Equal("", vm.NewChecklistItemText);
|
||||
|
||||
vm.RemoveChecklistItemCommand.Execute(vm.ChecklistItems[0]);
|
||||
vm.SaveCommand.Execute(null);
|
||||
|
||||
var item = Assert.Single(vm.Result!.ChecklistItems);
|
||||
Assert.Equal("Zweite Position", item.Label);
|
||||
Assert.Null(item.StudentId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Konstruktor_BestehendeAufgabeMitChecklist_UebernimmtEintraege()
|
||||
{
|
||||
var source = new WorkTask
|
||||
{
|
||||
Title = "Alt",
|
||||
ChecklistItems = [new ChecklistItem { Label = "Schon da", IsDone = true }],
|
||||
};
|
||||
var vm = new AddEditWorkTaskDialogViewModel(source, []);
|
||||
|
||||
var row = Assert.Single(vm.ChecklistItems);
|
||||
Assert.Equal("Schon da", row.Label);
|
||||
Assert.True(row.IsDone);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FillFromRoster_OhneGruppeOderLader_MachtNichts()
|
||||
{
|
||||
var vm = new AddEditWorkTaskDialogViewModel(null, []);
|
||||
vm.FillFromRosterCommand.Execute(null);
|
||||
Assert.Empty(vm.ChecklistItems);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FillFromRoster_BefuelltAusRosterUndUeberspringtDoppelteSchueler()
|
||||
{
|
||||
var group = new LearningGroup { Name = "8a" };
|
||||
var studentId = Guid.NewGuid();
|
||||
var otherStudentId = Guid.NewGuid();
|
||||
var vm = new AddEditWorkTaskDialogViewModel(null, [group], loadRoster: _ =>
|
||||
[(studentId, "Anna Beispiel"), (otherStudentId, "Ben Muster")])
|
||||
{
|
||||
SelectedGroup = group,
|
||||
};
|
||||
|
||||
Assert.True(vm.CanFillFromRoster);
|
||||
vm.FillFromRosterCommand.Execute(null);
|
||||
Assert.Equal(2, vm.ChecklistItems.Count);
|
||||
|
||||
vm.FillFromRosterCommand.Execute(null);
|
||||
Assert.Equal(2, vm.ChecklistItems.Count); // kein Duplikat beim zweiten Aufruf
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TimeTrackingViewModelTests
|
||||
|
||||
Reference in New Issue
Block a user