From d00cea0bfebaf3abaa8ffe9b64863f7eea3d2ced Mon Sep 17 00:00:00 2001 From: Sebastian Hedtrich Date: Wed, 12 Aug 2026 00:14:28 +0200 Subject: [PATCH] =?UTF-8?q?Sidebar-Kompaktmodus=20polieren,=20Dashboard-Ka?= =?UTF-8?q?lender=20erg=C3=A4nzen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MainWindow: App-Titel durch ein kompaktes Logo-Badge ersetzt; im minimierten Zustand der Sidebar werden Nav-Icons automatisch größer und zentriert, Beschriftungen sowie Abschnittsüberschriften blenden aus (Classes-Binding auf DrawerPage.IsOpen). - LearningGroup.IsOwnClass: neues Feld, per Checkbox im Gruppen-Dialog editierbar ("Gehört zu meiner Klasse"), mehrere Gruppen markierbar. - Dashboard: kleiner Monatskalender mit Monatsnavigation, dezenter Einfärbung für Unterrichtstage, Klausur-Marker und Rahmen-Hervor- hebung für Tage der eigenen Klasse, inkl. Tooltip und Legende. Feste Kartenposition/-breite, damit weder Monatsnamenlänge noch die wachsende Lerngruppen-Liste das Layout verschieben. - TODO.md: geplante Kalender-Detailansicht (Tag auswählen → Termine des Tages mit Sprung in die jeweilige Ansicht) unter 9.8 vermerkt. Co-Authored-By: Claude Sonnet 5 --- LehrerApp.Core/Models/LearningGroup.cs | 1 + .../ViewModels/DashboardViewModel.cs | 124 +++++++++++++++++- .../ViewModels/Groups/GroupViewModels.cs | 3 + .../Views/Dashboard/DashboardView.axaml | 106 ++++++++++++++- .../Views/Groups/AddGroupDialog.axaml | 3 + LehrerApp.Desktop/Views/MainWindow.axaml | 114 ++++++++++++---- TODO.md | 7 + 7 files changed, 329 insertions(+), 29 deletions(-) diff --git a/LehrerApp.Core/Models/LearningGroup.cs b/LehrerApp.Core/Models/LearningGroup.cs index 88adea0..6ca2e01 100644 --- a/LehrerApp.Core/Models/LearningGroup.cs +++ b/LehrerApp.Core/Models/LearningGroup.cs @@ -12,6 +12,7 @@ public class LearningGroup public GradingSystem GradingSystem { get; set; } public int? HoursPerWeek { get; set; } public bool IsActive { get; set; } = true; + public bool IsOwnClass { get; set; } public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; } diff --git a/LehrerApp.Desktop/ViewModels/DashboardViewModel.cs b/LehrerApp.Desktop/ViewModels/DashboardViewModel.cs index c4f4f14..cf9a862 100644 --- a/LehrerApp.Desktop/ViewModels/DashboardViewModel.cs +++ b/LehrerApp.Desktop/ViewModels/DashboardViewModel.cs @@ -10,34 +10,44 @@ namespace LehrerApp.Desktop.ViewModels; public partial class DashboardViewModel : ObservableObject { + private static readonly CultureInfo De = new("de-DE"); + private readonly IGroupRepository _groups; private readonly ILessonRepository _lessons; + private readonly IExamRepository _exams; private readonly IWorkTaskRepository _tasks; private readonly SchoolYearService _sy; [ObservableProperty] private string _greeting = ""; [ObservableProperty] private string _currentDate = ""; [ObservableProperty] private string _currentSchoolYear = ""; + [ObservableProperty] private DateOnly _calendarMonth = FirstOfMonth(DateTime.Today); + + public string CalendarMonthLabel => CalendarMonth.ToString("MMMM yyyy", De); public ObservableCollection TodaysLessons { get; } = []; public ObservableCollection OpenTasks { get; } = []; public ObservableCollection CurrentGroups { get; } = []; + public ObservableCollection CalendarDays { get; } = []; + public static string[] CalendarWeekdayHeaders { get; } = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]; // Navigation-Callback – wird von App.axaml.cs verdrahtet public Action? OnNavigateToGroup { get; set; } public DashboardViewModel(IGroupRepository groups, ILessonRepository lessons, - IWorkTaskRepository tasks, SchoolYearService sy) + IExamRepository exams, IWorkTaskRepository tasks, SchoolYearService sy) { - _groups = groups; _lessons = lessons; _tasks = tasks; _sy = sy; + _groups = groups; _lessons = lessons; _exams = exams; _tasks = tasks; _sy = sy; Load(); } + private static DateOnly FirstOfMonth(DateTime d) => new(d.Year, d.Month, 1); + private void Load() { var now = DateTime.Now; var today = DateOnly.FromDateTime(now); - CurrentDate = now.ToString("dddd, d. MMMM yyyy", new CultureInfo("de-DE")); + CurrentDate = now.ToString("dddd, d. MMMM yyyy", De); CurrentSchoolYear = _sy.CurrentSchoolYear(); Greeting = now.Hour < 12 ? "Guten Morgen" : now.Hour < 18 ? "Guten Tag" : "Guten Abend"; @@ -61,12 +71,120 @@ public partial class DashboardViewModel : ObservableObject CurrentGroups.Clear(); foreach (var g in groups.Values.OrderBy(g => g.Name)) CurrentGroups.Add(new() { GroupId = g.Id, Name = g.Name, Subject = g.Subject ?? "" }); + + CalendarMonth = FirstOfMonth(now); + LoadCalendar(); + } + + private void LoadCalendar() + { + CalendarDays.Clear(); + var today = DateOnly.FromDateTime(DateTime.Today); + + // Rasterstart: Montag der Woche, die den 1. des Monats enthält – 6 Wochen (42 Tage) Raster. + var firstOfMonth = CalendarMonth; + var mondayOffset = ((int)firstOfMonth.DayOfWeek + 6) % 7; // Montag=0 ... Sonntag=6 + var gridStart = firstOfMonth.AddDays(-mondayOffset); + var gridEnd = gridStart.AddDays(41); + + var groups = _groups.GetBySchoolYear(_sy.CurrentSchoolYear()); + var byDay = new Dictionary(); + + DayAgg Agg(DateOnly d) + { + if (!byDay.TryGetValue(d, out var agg)) byDay[d] = agg = new DayAgg(); + return agg; + } + + foreach (var g in groups) + { + foreach (var lesson in _lessons.GetByGroupAndRange(g.Id, gridStart, gridEnd)) + { + var agg = Agg(lesson.Date); + agg.HasLesson = true; + if (g.IsOwnClass) agg.IsOwnClassDay = true; + agg.Details.Add($"Unterricht: {g.Name}" + + (string.IsNullOrWhiteSpace(lesson.Topic) ? "" : $" – {lesson.Topic}")); + } + + foreach (var exam in _exams.GetByGroup(g.Id).Where(e => e.Date >= gridStart && e.Date <= gridEnd)) + { + var agg = Agg(exam.Date); + agg.HasExam = true; + if (g.IsOwnClass) agg.IsOwnClassDay = true; + agg.Details.Add($"Klausur: {exam.Title} ({g.Name})"); + } + } + + for (var i = 0; i < 42; i++) + { + var date = gridStart.AddDays(i); + byDay.TryGetValue(date, out var agg); + CalendarDays.Add(new CalendarDayCell(date, date.Month == firstOfMonth.Month, date == today, + agg?.HasLesson ?? false, agg?.HasExam ?? false, agg?.IsOwnClassDay ?? false, + agg?.Details ?? [])); + } + } + + [RelayCommand] + private void PrevMonth() + { + CalendarMonth = CalendarMonth.AddMonths(-1); + OnPropertyChanged(nameof(CalendarMonthLabel)); + LoadCalendar(); + } + + [RelayCommand] + private void NextMonth() + { + CalendarMonth = CalendarMonth.AddMonths(1); + OnPropertyChanged(nameof(CalendarMonthLabel)); + LoadCalendar(); + } + + [RelayCommand] + private void CalendarToday() + { + CalendarMonth = FirstOfMonth(DateTime.Today); + OnPropertyChanged(nameof(CalendarMonthLabel)); + LoadCalendar(); } [RelayCommand] private void OpenGroup(GroupChip? c) { if (c is not null) OnNavigateToGroup?.Invoke(c.GroupId); } [RelayCommand] private void Refresh() => Load(); + + private class DayAgg + { + public bool HasLesson; + public bool HasExam; + public bool IsOwnClassDay; + public List Details { get; } = []; + } } public class LessonItem { public string GroupName { get; set; } = ""; public string Topic { get; set; } = ""; } public class TaskItem { public string Title { get; set; } = ""; public string DueDate { get; set; } = ""; public bool IsOverdue { get; set; } } public class GroupChip { public Guid GroupId { get; set; } public string Name { get; set; } = ""; public string Subject { get; set; } = ""; } + +public class CalendarDayCell +{ + public int DayNumber { get; } + public bool IsCurrentMonth { get; } + public bool IsToday { get; } + public bool HasLesson { get; } + public bool HasExam { get; } + public bool IsOwnClassDay { get; } + public string Tooltip { get; } + + internal CalendarDayCell(DateOnly date, bool isCurrentMonth, bool isToday, + bool hasLesson, bool hasExam, bool isOwnClassDay, List details) + { + DayNumber = date.Day; + IsCurrentMonth = isCurrentMonth; + IsToday = isToday; + HasLesson = hasLesson; + HasExam = hasExam; + IsOwnClassDay = isOwnClassDay; + Tooltip = details.Count == 0 ? date.ToString("dd.MM.yyyy") : string.Join("\n", details); + } +} diff --git a/LehrerApp.Desktop/ViewModels/Groups/GroupViewModels.cs b/LehrerApp.Desktop/ViewModels/Groups/GroupViewModels.cs index c766ef9..f49a029 100644 --- a/LehrerApp.Desktop/ViewModels/Groups/GroupViewModels.cs +++ b/LehrerApp.Desktop/ViewModels/Groups/GroupViewModels.cs @@ -529,6 +529,7 @@ public partial class AddGroupDialogViewModel : ObservableObject [ObservableProperty] private string _selectedGradingName = "Noten 1–6"; [ObservableProperty] private string _selectedSchoolYear = ""; [ObservableProperty] private int? _hoursPerWeek; + [ObservableProperty] private bool _isOwnClass; [ObservableProperty] private string _validationMessage = ""; public List SchoolYears { get; } @@ -565,6 +566,7 @@ public partial class AddGroupDialogViewModel : ObservableObject ? "Noten 1–6" : "Punkte 0–15"; SelectedSchoolYear = group.SchoolYear; HoursPerWeek = group.HoursPerWeek; + IsOwnClass = group.IsOwnClass; OnPropertyChanged(nameof(DialogTitle)); OnPropertyChanged(nameof(SaveButtonText)); } @@ -604,6 +606,7 @@ public partial class AddGroupDialogViewModel : ObservableObject ? GradingSystem.Points0To15 : GradingSystem.Grades1To6; Result.SchoolYear = SelectedSchoolYear; Result.HoursPerWeek = HoursPerWeek; + Result.IsOwnClass = IsOwnClass; _groups.Save(Result); if (_editingGroup is not null && _originalSchoolYear != SelectedSchoolYear) diff --git a/LehrerApp.Desktop/Views/Dashboard/DashboardView.axaml b/LehrerApp.Desktop/Views/Dashboard/DashboardView.axaml index bb69f40..aae50e7 100644 --- a/LehrerApp.Desktop/Views/Dashboard/DashboardView.axaml +++ b/LehrerApp.Desktop/Views/Dashboard/DashboardView.axaml @@ -13,7 +13,7 @@ - + - - + + + + + + + + + + + + + + + + - - + + + - - + diff --git a/TODO.md b/TODO.md index 45ac6a5..5d19b68 100644 --- a/TODO.md +++ b/TODO.md @@ -297,6 +297,8 @@ Format dokumentiert in [Kompetenzkatalog-KI-Prompt.md](docs/Kompetenzkatalog-KI- ## 9. Dashboard Basis vorhanden in [DashboardViewModel.cs](LehrerApp.Desktop/ViewModels/DashboardViewModel.cs). +Kleiner Monatskalender ist bereits umgesetzt (Farbcodierung Unterricht/Klausur, +Hervorhebung "eigene Klasse" über `LearningGroup.IsOwnClass`, feste Kartenbreite/-position). - [ ] **9.1** Heutige Stunden mit Uhrzeit und Raum anzeigen (Abhängigkeit zu 4.3). - [ ] **9.2** Direkter Absprung von einer Stunde in Mitarbeitserfassung bzw. Stundenplanung. @@ -305,6 +307,11 @@ Basis vorhanden in [DashboardViewModel.cs](LehrerApp.Desktop/ViewModels/Dashboar - [ ] **9.5** Kachel "Auffälligkeiten": Fehlzeitenüberschreitungen, Notenabfall, Versetzungsgefährdung. - [ ] **9.6** Dashboard-Kacheln ein-/ausblendbar und in der Reihenfolge konfigurierbar. - [ ] **9.7** Automatische Aktualisierung beim Zurücknavigieren (aktuell nur manueller Refresh). +- [ ] **9.8** Kalender-Detailansicht: Tag im Monatskalender anklickbar/auswählbar, zeigt in + einem angrenzenden Feld die Termine dieses Tages (Unterricht, Klausuren, perspektivisch + Konferenzen/Sondertermine) mit Details und Sprungmöglichkeit in die jeweilige Ansicht. + Sinnvoll erst, wenn weitere Terminarten existieren — Abhängigkeit zu Kapitel 4 + (Unterrichtsplanung/Termine) sowie ggf. einem neuen Termine-Modell. ---