diff --git a/LehrerApp.Desktop.Tests/GroupDetailViewModelTests.cs b/LehrerApp.Desktop.Tests/GroupDetailViewModelTests.cs index ff28984..a607970 100644 --- a/LehrerApp.Desktop.Tests/GroupDetailViewModelTests.cs +++ b/LehrerApp.Desktop.Tests/GroupDetailViewModelTests.cs @@ -18,6 +18,8 @@ public sealed class GroupDetailViewModelTests var grades = new FakeGrades(); 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 ParticipationTabViewModel(new FakeSessions([]), new FakeEntries(), new FakeAspects(), students, memberships, groups, new FakeCompetencyDomains()), new GradeOverviewTabViewModel(grades, exams, new FakeResults(), students, memberships, new GradingService()), diff --git a/LehrerApp.Desktop.Tests/GroupOverviewViewModelTests.cs b/LehrerApp.Desktop.Tests/GroupOverviewViewModelTests.cs new file mode 100644 index 0000000..74d2660 --- /dev/null +++ b/LehrerApp.Desktop.Tests/GroupOverviewViewModelTests.cs @@ -0,0 +1,257 @@ +using LehrerApp.Core.Models; +using LehrerApp.Core.Services; +using LehrerApp.Desktop.ViewModels; +using LehrerApp.Desktop.ViewModels.Groups; +using Xunit; + +namespace LehrerApp.Desktop.Tests; + +/// Tests für die Kurs-Übersicht: sie liest bewusst keine eigenen Daten, sondern zieht nur +/// zusammen, was Planung/Klausuren/Mitarbeit/Dokumentation ohnehin schon verwalten. +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) => + new(lessons ?? new FakeLessons(), exams ?? new FakeExams([]), sessions ?? new FakeSessions([]), + entries ?? new FakeEntries(), students ?? new FakeStudents([]), documentation ?? new FakeDocumentation(), + new AttendanceBalanceService(), new SchoolYearService()); + + private static (GroupOverviewViewModel Vm, FakeLessons Lessons, FakeExams Exams, + FakeSessions Sessions, FakeEntries Entries, FakeStudents Students, Guid GroupId) BuildScenario( + List? lessons = null, List? exams = null) + { + var groupId = Guid.NewGuid(); + var lessonsRepo = new FakeLessons(); + foreach (var l in lessons ?? []) lessonsRepo.Add(l); + var examsRepo = new FakeExams(exams ?? []); + var sessions = new FakeSessions([]); + var entries = new FakeEntries(); + var students = new FakeStudents([]); + + var vm = NewVm(lessonsRepo, examsRepo, sessions, entries, students); + vm.Initialize(groupId, "Testkurs"); + + return (vm, lessonsRepo, examsRepo, sessions, entries, students, groupId); + } + + [Fact] + public void NaechsteStunde_FindetDieNaechsteGeplanteStundeInnerhalbDesZeitfensters() + { + var today = DateOnly.FromDateTime(DateTime.Today); + var groupId = Guid.NewGuid(); + var lessons = new FakeLessons(); + lessons.Add(new Lesson { GroupId = groupId, Date = today.AddDays(10), Topic = "Später", Status = LessonStatus.Planned }); + lessons.Add(new Lesson { GroupId = groupId, Date = today.AddDays(3), Topic = "Brechung", Status = LessonStatus.Planned }); + lessons.Add(new Lesson { GroupId = groupId, Date = today.AddDays(1), Topic = "Schon vorbei", Status = LessonStatus.Conducted }); + + var vm = NewVm(lessons: lessons); + vm.Initialize(groupId, "Testkurs"); + + Assert.True(vm.HasNextLesson); + Assert.Contains("Brechung", vm.NextLessonLabel); + } + + [Fact] + public void NaechsteStunde_OhneGeplanteStundeIstNichtVorhanden() + { + var (vm, _, _, _, _, _, _) = BuildScenario(); + Assert.False(vm.HasNextLesson); + } + + [Fact] + public void NaechsteKlausur_FindetFruehesteKuenftigeKlausur() + { + var today = DateOnly.FromDateTime(DateTime.Today); + var groupId = Guid.NewGuid(); + var exams = new FakeExams([ + new Exam { GroupId = groupId, Date = today.AddDays(20), Title = "Später" }, + new Exam { GroupId = groupId, Date = today.AddDays(5), Title = "Klassenarbeit 2" }, + new Exam { GroupId = groupId, Date = today.AddDays(-3), Title = "Vergangen" }, + ]); + var vm = NewVm(exams: exams); + vm.Initialize(groupId, "Testkurs"); + + Assert.True(vm.HasNextExam); + Assert.Contains("Klassenarbeit 2", vm.NextExamLabel); + } + + [Fact] + public void Mitarbeit_OhneSitzungenZeigtHinweisOhneWarnung() + { + var (vm, _, _, _, _, _, _) = BuildScenario(); + Assert.Contains("Noch keine Mitarbeitssitzung", vm.ParticipationHintLabel); + Assert.False(vm.ParticipationHintIsStale); + } + + [Fact] + public void Mitarbeit_LetzteSitzungVorMehrAls14TagenGiltAlsVeraltet() + { + var today = DateOnly.FromDateTime(DateTime.Today); + var groupId = Guid.NewGuid(); + var sessions = new FakeSessions([new ParticipationSession { GroupId = groupId, Date = today.AddDays(-20) }]); + var vm = NewVm(sessions: sessions); + vm.Initialize(groupId, "Testkurs"); + + Assert.True(vm.ParticipationHintIsStale); + } + + [Fact] + public void Mitarbeit_LetzteSitzungKuerzlichGiltNichtAlsVeraltet() + { + var today = DateOnly.FromDateTime(DateTime.Today); + var groupId = Guid.NewGuid(); + var sessions = new FakeSessions([new ParticipationSession { GroupId = groupId, Date = today.AddDays(-2) }]); + var vm = NewVm(sessions: sessions); + vm.Initialize(groupId, "Testkurs"); + + Assert.False(vm.ParticipationHintIsStale); + } + + [Fact] + public void OffeneHausaufgabenKontrolle_ZeigtUnkontrollierteHausaufgabeDerLetztenStunde() + { + var today = DateOnly.FromDateTime(DateTime.Today); + var groupId = Guid.NewGuid(); + var lessons = new FakeLessons(); + lessons.Add(new Lesson + { + GroupId = groupId, Date = today.AddDays(-2), Homework = "S. 42 Nr. 3", + HomeworkChecked = false, HomeworkCheckDismissed = false, + }); + var vm = NewVm(lessons: lessons); + vm.Initialize(groupId, "Testkurs"); + + Assert.True(vm.HasOpenHomeworkCheck); + Assert.Contains("S. 42 Nr. 3", vm.OpenHomeworkCheckLabel); + } + + [Fact] + public void OffeneHausaufgabenKontrolle_VerschwindetSobaldKontrolliert() + { + var today = DateOnly.FromDateTime(DateTime.Today); + var groupId = Guid.NewGuid(); + var lessons = new FakeLessons(); + lessons.Add(new Lesson + { + GroupId = groupId, Date = today.AddDays(-2), Homework = "S. 42 Nr. 3", + HomeworkChecked = true, + }); + var vm = NewVm(lessons: lessons); + vm.Initialize(groupId, "Testkurs"); + + Assert.False(vm.HasOpenHomeworkCheck); + } + + [Fact] + public void OffeneEntschuldigungen_ListetOffenePendenteEntschuldigungenUndErlaubtAufloesen() + { + var today = DateOnly.FromDateTime(DateTime.Today); + var groupId = Guid.NewGuid(); + var student = new Student { FirstName = "Anna", LastName = "Beispiel" }; + var session = new ParticipationSession { GroupId = groupId, Date = today.AddDays(-2) }; + var sessions = new FakeSessions([session]); + var entries = new FakeEntries(); + var entry = new ParticipationEntry { SessionId = session.Id, StudentId = student.Id, Attendance = AttendanceStatus.ExcusePending }; + entries.Add(entry); + var students = new FakeStudents([student]); + + var vm = NewVm(sessions: sessions, entries: entries, students: students); + vm.Initialize(groupId, "Testkurs"); + + var item = Assert.Single(vm.OpenExcuses); + Assert.Equal(student.FullName, item.StudentName); + + item.MarkExcusedCommand.Execute(null); + + Assert.Empty(vm.OpenExcuses); + Assert.Equal(AttendanceStatus.Excused, entries.GetBySessionAndStudent(session.Id, student.Id)!.Attendance); + } + + [Fact] + public void OffeneDokumentationen_ZaehltNurEntwuerfeDieserGruppeOderOhneGruppe() + { + var groupId = Guid.NewGuid(); + var otherGroupId = Guid.NewGuid(); + var student = new Student { FirstName = "Anna", LastName = "Beispiel" }; + var students = new FakeStudents([student]); + var documentation = new FakeDocumentation(); + documentation.Add(new Documentation { StudentId = student.Id, GroupId = groupId, IsDraft = true, Title = "Beobachtung" }); + documentation.Add(new Documentation { StudentId = student.Id, GroupId = null, IsDraft = true, Title = "Ohne Gruppe" }); + documentation.Add(new Documentation { StudentId = student.Id, GroupId = otherGroupId, IsDraft = true, Title = "Anderer Kurs" }); + documentation.Add(new Documentation { StudentId = student.Id, GroupId = groupId, IsDraft = false, Title = "Fertig" }); + + var vm = NewVm(students: students, documentation: documentation); + vm.Initialize(groupId, "Testkurs"); + + Assert.Equal(2, vm.DraftDocumentationCount); + } + + [Fact] + public void AuffaelligeFehlzeiten_WirdErstAbMindeststichprobeGemeldet() + { + var today = DateOnly.FromDateTime(DateTime.Today); + var groupId = Guid.NewGuid(); + var student = new Student { FirstName = "Anna", LastName = "Beispiel" }; + var students = new FakeStudents([student]); + var sessions = new FakeSessions([]); + var entries = new FakeEntries(); + + // Nur 3 erfasste Termine, davon 3 unentschuldigt (100 %) — trotz hoher Quote noch keine + // Meldung, weil die Stichprobe zu klein ist (Schuljahresbeginn-Problem). + for (var i = 0; i < 3; i++) + { + var session = new ParticipationSession { GroupId = groupId, Date = today.AddDays(-i - 1) }; + sessions.Save(session); + entries.Add(new ParticipationEntry { SessionId = session.Id, StudentId = student.Id, Attendance = AttendanceStatus.Unexcused }); + } + + var vm = NewVm(sessions: sessions, entries: entries, students: students); + vm.Initialize(groupId, "Testkurs"); + + Assert.False(vm.HasAttendanceNotices); + } + + [Fact] + public void AuffaelligeFehlzeiten_MeldetHoheQuoteAbMindeststichprobe() + { + var today = DateOnly.FromDateTime(DateTime.Today); + var groupId = Guid.NewGuid(); + var student = new Student { FirstName = "Anna", LastName = "Beispiel" }; + var students = new FakeStudents([student]); + var sessions = new FakeSessions([]); + var entries = new FakeEntries(); + + // 10 erfasste Termine, 3 davon unentschuldigt (30 % > 20 % Schwelle) — jetzt genug + // Stichprobe für eine Meldung. + for (var i = 0; i < 10; i++) + { + var session = new ParticipationSession { GroupId = groupId, Date = today.AddDays(-i - 1) }; + sessions.Save(session); + var status = i < 3 ? AttendanceStatus.Unexcused : AttendanceStatus.Present; + entries.Add(new ParticipationEntry { SessionId = session.Id, StudentId = student.Id, Attendance = status }); + } + + var vm = NewVm(sessions: sessions, entries: entries, students: students); + vm.Initialize(groupId, "Testkurs"); + + var item = Assert.Single(vm.AttendanceNotices); + Assert.Equal(student.FullName, item.StudentName); + Assert.Equal(30.0, item.AbsenceRatePercent); + } + + [Fact] + public void NavigationCommands_RufenDelegatMitPassendemTabIndexAuf() + { + var (vm, _, _, _, _, _, _) = BuildScenario(); + var indices = new List(); + vm.OnNavigateToTab = idx => indices.Add(idx); + + vm.NavigateToPlanningCommand.Execute(null); + vm.NavigateToExamsCommand.Execute(null); + vm.NavigateToParticipationCommand.Execute(null); + vm.NavigateToDocumentationCommand.Execute(null); + + Assert.Equal([6, 4, 3, 8], indices); + } +} diff --git a/LehrerApp.Desktop/AppBootstrapper.cs b/LehrerApp.Desktop/AppBootstrapper.cs index eeab833..a7ad607 100644 --- a/LehrerApp.Desktop/AppBootstrapper.cs +++ b/LehrerApp.Desktop/AppBootstrapper.cs @@ -249,6 +249,7 @@ public static class AppBootstrapper // Transient: neue Instanz pro Navigation (für Detailseiten) services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/LehrerApp.Desktop/ViewModels/Groups/GroupOverviewViewModel.cs b/LehrerApp.Desktop/ViewModels/Groups/GroupOverviewViewModel.cs new file mode 100644 index 0000000..72df9a3 --- /dev/null +++ b/LehrerApp.Desktop/ViewModels/Groups/GroupOverviewViewModel.cs @@ -0,0 +1,239 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using LehrerApp.Core.Interfaces; +using LehrerApp.Core.Models; +using LehrerApp.Core.Services; +using LehrerApp.Desktop.ViewModels; +using System.Collections.ObjectModel; + +namespace LehrerApp.Desktop.ViewModels.Groups; + +// ── Tab: Übersicht — kompakte "Was steht an"-Zusammenfassung eines Kurses ──── + +/// +/// Zieht keine eigenen neuen Daten — jede Karte liest nur, was die anderen Tabs (Planung, +/// Klausuren, Mitarbeit) ohnehin schon verwalten, und verweist per Klick dorthin. Bewusst kein +/// Ersatz für die Detail-Tabs, sondern ein schneller Überblick "was ist als nächstes dran / +/// was wurde vergessen". +/// +public partial class GroupOverviewViewModel : ObservableObject +{ + private const int OpenExcuseMaxAgeDays = 21; + private const int HomeworkCheckLookbackDays = 120; + private const int NextLessonLookaheadDays = 90; + + /// Unterhalb dieser Anzahl erfasster Anwesenheits-Einträge im laufenden Schuljahr wird eine + /// hohe Fehlquote bewusst NICHT gemeldet — wer zu Schuljahresbeginn zweimal fehlt, hat rein + /// rechnerisch schon 100 %, das ist noch kein auffälliges Muster, nur eine zu kleine + /// Stichprobe. Dasselbe Problem hat aktuell noch die Fehlzeiten-Warnung im Hauptdashboard + /// (AttendanceBalanceService/AttendanceWarningItem) — bewusst unangetastet, wird dort separat + /// nachgezogen. + private const int AttendanceMinSampleSize = 8; + + private readonly ILessonRepository _lessons; + private readonly IExamRepository _exams; + private readonly IParticipationSessionRepository _sessions; + private readonly IParticipationRepository _entries; + private readonly IStudentRepository _students; + private readonly IDocumentationRepository _documentation; + private readonly AttendanceBalanceService _attendanceBalance; + private readonly SchoolYearService _schoolYear; + + private Guid _groupId; + private string _groupName = ""; + + [ObservableProperty] private bool _hasNextLesson; + [ObservableProperty] private string _nextLessonLabel = ""; + [ObservableProperty] private bool _hasNextExam; + [ObservableProperty] private string _nextExamLabel = ""; + [ObservableProperty] private string _participationHintLabel = ""; + [ObservableProperty] private bool _participationHintIsStale; + [ObservableProperty] private bool _hasOpenHomeworkCheck; + [ObservableProperty] private string _openHomeworkCheckLabel = ""; + [ObservableProperty] private int _draftDocumentationCount; + + public ObservableCollection OpenExcuses { get; } = []; + public bool HasOpenExcuses => OpenExcuses.Count > 0; + + /// Absichtlich zurückhaltender formuliert/gestylt als das Hauptdashboard (kein "Warnfall") — + /// siehe . + public ObservableCollection AttendanceNotices { get; } = []; + public bool HasAttendanceNotices => AttendanceNotices.Count > 0; + + /// Ein Delegate statt einem pro Karte, mit dem Ziel-Tab-Index von GroupDetailView.axaml als + /// Parameter (0 Übersicht, 1 Schüler, 2 Sitzpläne, 3 Mitarbeit, 4 Klausuren, 5 Noten, + /// 6 Planung, 7 Kompetenzen, 8 Dokumentation). + public Action? OnNavigateToTab { 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); + + public GroupOverviewViewModel(ILessonRepository lessons, IExamRepository exams, + IParticipationSessionRepository sessions, IParticipationRepository entries, + IStudentRepository students, IDocumentationRepository documentation, + AttendanceBalanceService attendanceBalance, SchoolYearService schoolYear) + { + _lessons = lessons; _exams = exams; _sessions = sessions; + _entries = entries; _students = students; _documentation = documentation; + _attendanceBalance = attendanceBalance; _schoolYear = schoolYear; + } + + public void Initialize(Guid groupId, string groupName) + { + _groupId = groupId; + _groupName = groupName; + Refresh(); + } + + public void Refresh() + { + var today = DateOnly.FromDateTime(DateTime.Today); + LoadNextLesson(today); + LoadNextExam(today); + LoadParticipationHint(today); + LoadOpenHomeworkCheck(today); + LoadOpenExcuses(today); + LoadDraftDocumentationCount(); + LoadAttendanceNotices(today); + } + + private void LoadNextLesson(DateOnly today) + { + var next = _lessons.GetByGroupAndRange(_groupId, today, today.AddDays(NextLessonLookaheadDays)) + .Where(l => l.Status == LessonStatus.Planned) + .OrderBy(l => l.Date).ThenBy(l => l.LessonNumber ?? 0) + .FirstOrDefault(); + + HasNextLesson = next is not null; + NextLessonLabel = next is null ? "" + : string.IsNullOrWhiteSpace(next.Topic) + ? next.Date.ToString("dd.MM.yyyy") + : $"{next.Date:dd.MM.yyyy} — {next.Topic}"; + } + + private void LoadNextExam(DateOnly today) + { + var next = _exams.GetByGroup(_groupId).Where(e => e.Date >= today).MinBy(e => e.Date); + HasNextExam = next is not null; + NextExamLabel = next is null ? "" : $"{next.Date:dd.MM.yyyy} — {next.Title}"; + } + + /// Erinnert nicht an eine Note, sondern schlicht daran, überhaupt wieder eine Sitzung + /// anzulegen — genau das vergisst man in Kursen, die man seltener unterrichtet, zuerst. + private void LoadParticipationHint(DateOnly today) + { + var last = _sessions.GetByGroup(_groupId).OrderByDescending(s => s.Date).FirstOrDefault(); + if (last is null) + { + ParticipationHintLabel = "Noch keine Mitarbeitssitzung angelegt."; + ParticipationHintIsStale = false; + return; + } + + var daysAgo = today.DayNumber - last.Date.DayNumber; + ParticipationHintLabel = daysAgo <= 0 + ? "Letzte Sitzung: heute." + : $"Letzte Sitzung: {last.Date:dd.MM.yyyy} (vor {daysAgo} Tag(en))."; + ParticipationHintIsStale = daysAgo > 14; + } + + /// Dieselbe Erkennung wie das Stundenplan-Badge "Hausaufgabe kontrollieren" + /// (TimetableViewModel.HasUnhandledHomework) — bewusst nur die unmittelbar letzte Lesson, + /// nicht die gesamte Historie. + private void LoadOpenHomeworkCheck(DateOnly today) + { + var previous = _lessons.GetByGroupAndRange(_groupId, today.AddDays(-HomeworkCheckLookbackDays), today.AddDays(-1)) + .OrderByDescending(l => l.Date).ThenByDescending(l => l.LessonNumber ?? 0) + .FirstOrDefault(); + + var open = previous is not null && !string.IsNullOrWhiteSpace(previous.Homework) + && !previous.HomeworkChecked && !previous.HomeworkCheckDismissed; + + HasOpenHomeworkCheck = open; + OpenHomeworkCheckLabel = open + ? $"Aus der Stunde vom {previous!.Date:dd.MM.yyyy}: {previous.Homework}" + : ""; + } + + private void LoadOpenExcuses(DateOnly today) + { + OpenExcuses.Clear(); + var cutoff = today.AddDays(-OpenExcuseMaxAgeDays); + + var items = new List(); + foreach (var session in _sessions.GetByGroup(_groupId).Where(s => s.Date >= cutoff && s.Date <= today)) + { + foreach (var entry in _entries.GetBySession(session.Id) + .Where(e => e.Attendance == AttendanceStatus.ExcusePending)) + { + var student = _students.GetById(entry.StudentId); + if (student is null) continue; + var item = new OpenExcuseItem(session.Id, entry.StudentId, student.FullName, _groupName, session.Date); + item.OnResolve = ResolveExcuse; + items.Add(item); + } + } + foreach (var item in items.OrderBy(i => i.Date)) + OpenExcuses.Add(item); + OnPropertyChanged(nameof(HasOpenExcuses)); + } + + private void ResolveExcuse(OpenExcuseItem item, AttendanceStatus status) + { + var entry = _entries.GetBySessionAndStudent(item.SessionId, item.StudentId); + if (entry is null) return; + entry.Attendance = status; + _entries.Save(entry); + OpenExcuses.Remove(item); + OnPropertyChanged(nameof(HasOpenExcuses)); + } + + /// Entwürfe (5.1.1: schnelle Notiz beim Sitzplan, noch nicht zu einem echten Dokumentations- + /// eintrag ausformuliert) dieser Gruppe — dieselbe Zählung wie das Badge in + /// GroupDocumentationTabViewModel.Load, hier nur ohne die vollständige Entwurfsliste. + private void LoadDraftDocumentationCount() + { + DraftDocumentationCount = _students.GetByGroup(_groupId) + .SelectMany(s => _documentation.GetByStudent(s.Id)) + .Count(d => d.IsDraft && (d.GroupId is null || d.GroupId == _groupId)); + } + + /// Bewusst kein 1:1-Abbild der Dashboard-Fehlzeiten-Warnung: dieselbe Quote + /// (AttendanceBalanceService.WarningThresholdPercent), aber erst ab + /// erfassten Terminen, damit ein Fehltag in der ersten + /// Schulwoche nicht sofort als auffällig gilt. + private void LoadAttendanceNotices(DateOnly today) + { + AttendanceNotices.Clear(); + var schoolYear = _schoolYear.CurrentSchoolYear(today); + var from = _schoolYear.SchoolYearStart(schoolYear); + var to = _schoolYear.SchoolYearEnd(schoolYear); + + var entriesByStudent = new Dictionary>(); + foreach (var session in _sessions.GetByGroup(_groupId).Where(s => s.Date >= from && s.Date <= to)) + { + foreach (var entry in _entries.GetBySession(session.Id)) + { + if (!entriesByStudent.TryGetValue(entry.StudentId, out var list)) + entriesByStudent[entry.StudentId] = list = []; + list.Add((session.Date, entry.Attendance)); + } + } + + var items = new List(); + foreach (var student in _students.GetByGroup(_groupId)) + { + if (!entriesByStudent.TryGetValue(student.Id, out var entries) || entries.Count < AttendanceMinSampleSize) + continue; + + var balance = _attendanceBalance.Calculate(entries, from, to); + if (balance.ExceedsThreshold) + items.Add(new AttendanceWarningItem(student.Id, student.FullName, balance.AbsenceRatePercent)); + } + foreach (var item in items.OrderByDescending(i => i.AbsenceRatePercent)) + AttendanceNotices.Add(item); + OnPropertyChanged(nameof(HasAttendanceNotices)); + } +} diff --git a/LehrerApp.Desktop/ViewModels/Groups/GroupViewModels.cs b/LehrerApp.Desktop/ViewModels/Groups/GroupViewModels.cs index 7c0fa49..3fe7104 100644 --- a/LehrerApp.Desktop/ViewModels/Groups/GroupViewModels.cs +++ b/LehrerApp.Desktop/ViewModels/Groups/GroupViewModels.cs @@ -208,12 +208,14 @@ public partial class GroupDetailViewModel : ObservableObject partial void OnShowFormerStudentsChanged(bool value) => LoadStudents(); partial void OnActiveTabIndexChanged(int value) { + if (value == 0) OverviewTab.Refresh(); if (value == 7) CompetencyOverviewTab.Refresh(); } public ObservableCollection Students { get; } = []; public ObservableCollection Exams { get; } = []; + public GroupOverviewViewModel OverviewTab { get; } public ParticipationTabViewModel ParticipationTab { get; } public GradeOverviewTabViewModel GradeOverviewTab { get; } public PlanningTabViewModel PlanningTab { get; } @@ -233,12 +235,14 @@ public partial class GroupDetailViewModel : ObservableObject public GroupDetailViewModel(IGroupRepository groups, IStudentRepository students, IGroupMembershipRepository memberships, ISubjectRepository subjects, IExamRepository exams, IGradeRepository grades, IWorkTaskRepository tasks, - ParticipationTabViewModel participationTab, GradeOverviewTabViewModel gradeOverviewTab, + GroupOverviewViewModel overviewTab, ParticipationTabViewModel participationTab, + GradeOverviewTabViewModel gradeOverviewTab, PlanningTabViewModel planningTab, CompetencyOverviewTabViewModel competencyOverviewTab, SeatingPlanTabViewModel seatingPlanTab, GroupDocumentationTabViewModel groupDocumentationTab) { _groups = groups; _students = students; _memberships = memberships; _subjects = subjects; _exams = exams; _grades = grades; _tasks = tasks; + OverviewTab = overviewTab; ParticipationTab = participationTab; GradeOverviewTab = gradeOverviewTab; PlanningTab = planningTab; @@ -251,6 +255,7 @@ public partial class GroupDetailViewModel : ObservableObject ParticipationTab.RefreshCurrentGrid(); }; SeatingPlanTab.OnDocumentationChanged = GroupDocumentationTab.Refresh; + OverviewTab.OnNavigateToTab = idx => ActiveTabIndex = idx; } public void LoadGroup(Guid id) @@ -266,6 +271,7 @@ public partial class GroupDetailViewModel : ObservableObject $"Stufe {Group.GradeLevel} · Noten {(Group.GradingSystem == GradingSystem.Grades1To6 ? "1–6" : "0–15")}"; LoadStudents(); ReloadExams(); + OverviewTab.Initialize(Group.Id, GroupTitle); ParticipationTab.Initialize(Group.Id, Group.SchoolYear, IsReadOnly); GradeOverviewTab.Initialize(Group.Id, Group.GradingSystem, Group.Type, GroupTitle, Group.SchoolYear, IsReadOnly); PlanningTab.Initialize(Group.Id, IsReadOnly); diff --git a/LehrerApp.Desktop/Views/Groups/GroupDetailView.axaml b/LehrerApp.Desktop/Views/Groups/GroupDetailView.axaml index 411a202..e2be1d7 100644 --- a/LehrerApp.Desktop/Views/Groups/GroupDetailView.axaml +++ b/LehrerApp.Desktop/Views/Groups/GroupDetailView.axaml @@ -53,13 +53,7 @@ - - - - - - + diff --git a/LehrerApp.Desktop/Views/Groups/GroupOverviewTabView.axaml b/LehrerApp.Desktop/Views/Groups/GroupOverviewTabView.axaml new file mode 100644 index 0000000..1eddcb8 --- /dev/null +++ b/LehrerApp.Desktop/Views/Groups/GroupOverviewTabView.axaml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + +