Sidebar-Kompaktmodus polieren, Dashboard-Kalender ergänzen
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -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<LessonItem> TodaysLessons { get; } = [];
|
||||
public ObservableCollection<TaskItem> OpenTasks { get; } = [];
|
||||
public ObservableCollection<GroupChip> CurrentGroups { get; } = [];
|
||||
public ObservableCollection<CalendarDayCell> CalendarDays { get; } = [];
|
||||
public static string[] CalendarWeekdayHeaders { get; } = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
|
||||
|
||||
// Navigation-Callback – wird von App.axaml.cs verdrahtet
|
||||
public Action<Guid>? 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<DateOnly, DayAgg>();
|
||||
|
||||
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<string> 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<string> 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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user