Dashboard 9.1/9.2: Uhrzeit/Raum bei heutigen Stunden + Absprung zur Mitarbeit
TodaysLessons löst je Stunde den Raum über den passenden TimetableSlot auf und die Uhrzeit aus Lesson.StartTime bzw. dem Stundenraster - gleiche Quellen wie im Verlaufsplan-Editor und Stundenplan. Bewusst nicht dupliziert: die Vertretung/Ausfall-Logik der Stundenplan-eigenen "Heute"-Ansicht bleibt dort, das Dashboard zeigt nur die einfache geplante Stunde. Klick auf eine Stunde springt in die Mitarbeitserfassung der Gruppe - bewusst anderes Ziel als der bestehende Stundenplan-Sprung (dort "Planung"), da vom Dashboard aus morgens eher die Mitarbeitserfassung naheliegt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.ViewModels;
|
||||
using Xunit;
|
||||
|
||||
namespace LehrerApp.Desktop.Tests;
|
||||
|
||||
/// Tests für 9.1 (heutige Stunden mit Uhrzeit und Raum im Dashboard) und 9.2 (Absprung von einer
|
||||
/// Stunde). Deckt nur die neue Zeit-/Raum-Auflösung ab, nicht die übrigen, unveränderten
|
||||
/// Dashboard-Bausteine (Kalender, offene Aufgaben, Fehlzeiten-Warnungen, ...).
|
||||
public sealed class DashboardViewModelTests
|
||||
{
|
||||
private static PeriodScheduleService NewPeriodSchedule()
|
||||
{
|
||||
var tempPath = System.IO.Path.Combine(
|
||||
System.IO.Path.GetTempPath(), $"lehrerapp-dashboardvm-tests-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(tempPath);
|
||||
return new PeriodScheduleService(tempPath);
|
||||
}
|
||||
|
||||
private static DashboardViewModel BuildVm(LearningGroup group, Lesson lesson,
|
||||
FakeTimetableSlots? slots = null, PeriodScheduleService? periodSchedule = null)
|
||||
{
|
||||
var lessons = new FakeLessons();
|
||||
lessons.Add(lesson);
|
||||
return new DashboardViewModel(
|
||||
new FakeGroups([group]), new FakeSubjects([]), lessons,
|
||||
new FakeExams([]), new FakeWorkTasks(), new FakeSessions([]), new FakeEntries(),
|
||||
new FakeStudents([]), new FakeDocumentation(),
|
||||
slots ?? new FakeTimetableSlots(), periodSchedule ?? NewPeriodSchedule(),
|
||||
new AttendanceBalanceService(), new SchoolYearService());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TodaysLessons_LoestRaumUeberPassendenStundenplanSlotAuf()
|
||||
{
|
||||
var group = new LearningGroup { Name = "9c" };
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var lesson = new Lesson { GroupId = group.Id, Date = today, LessonNumber = 3, Topic = "Redox" };
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 3, Room = "R204" });
|
||||
|
||||
var vm = BuildVm(group, lesson, slots: slots);
|
||||
|
||||
var item = Assert.Single(vm.TodaysLessons);
|
||||
Assert.Equal("R204", item.Room);
|
||||
Assert.True(item.HasRoom);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TodaysLessons_OhnePassendenSlot_HatKeinenRaum()
|
||||
{
|
||||
var group = new LearningGroup { Name = "9c" };
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var lesson = new Lesson { GroupId = group.Id, Date = today, LessonNumber = 3, Topic = "Redox" };
|
||||
|
||||
var vm = BuildVm(group, lesson);
|
||||
|
||||
var item = Assert.Single(vm.TodaysLessons);
|
||||
Assert.False(item.HasRoom);
|
||||
Assert.Equal("", item.Room);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TodaysLessons_ZeitKommtAusLessonStartTimeWennGesetzt()
|
||||
{
|
||||
var group = new LearningGroup { Name = "9c" };
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var lesson = new Lesson
|
||||
{
|
||||
GroupId = group.Id, Date = today, LessonNumber = 3, Topic = "Redox",
|
||||
StartTime = new TimeOnly(9, 15),
|
||||
};
|
||||
var periodSchedule = NewPeriodSchedule();
|
||||
periodSchedule.SetPeriods([new PeriodTimeEntry { PeriodNumber = 3, Start = new TimeOnly(10, 0), End = new TimeOnly(10, 45) }]);
|
||||
|
||||
var vm = BuildVm(group, lesson, periodSchedule: periodSchedule);
|
||||
|
||||
Assert.Equal("09:15", Assert.Single(vm.TodaysLessons).TimeDisplay);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TodaysLessons_OhneStartTime_FaelltAufStundenrasterZurueck()
|
||||
{
|
||||
var group = new LearningGroup { Name = "9c" };
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var lesson = new Lesson { GroupId = group.Id, Date = today, LessonNumber = 3, Topic = "Redox" };
|
||||
var periodSchedule = NewPeriodSchedule();
|
||||
periodSchedule.SetPeriods([new PeriodTimeEntry { PeriodNumber = 3, Start = new TimeOnly(10, 0), End = new TimeOnly(10, 45) }]);
|
||||
|
||||
var vm = BuildVm(group, lesson, periodSchedule: periodSchedule);
|
||||
|
||||
Assert.Equal("10:00", Assert.Single(vm.TodaysLessons).TimeDisplay);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OpenLesson_RuftNavigationMitGruppenIdAuf()
|
||||
{
|
||||
var group = new LearningGroup { Name = "9c" };
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var lesson = new Lesson { GroupId = group.Id, Date = today, LessonNumber = 3, Topic = "Redox" };
|
||||
var vm = BuildVm(group, lesson);
|
||||
|
||||
Guid? navigatedTo = null;
|
||||
vm.OnNavigateToLesson = id => navigatedTo = id;
|
||||
|
||||
vm.OpenLessonCommand.Execute(vm.TodaysLessons[0]);
|
||||
|
||||
Assert.Equal(group.Id, navigatedTo);
|
||||
}
|
||||
}
|
||||
@@ -99,6 +99,7 @@ public class App : Application
|
||||
var dash = Services.GetRequiredService<DashboardViewModel>();
|
||||
dash.OnNavigateToGroup = id => main.NavigateToGroupDetail(id);
|
||||
dash.OnNavigateToStudent = id => main.NavigateToStudent(id);
|
||||
dash.OnNavigateToLesson = id => main.NavigateToGroupDetail(id, 2); // Tab "Mitarbeit"
|
||||
|
||||
// StudentList → StudentDetail + Anlegen
|
||||
var sl = Services.GetRequiredService<StudentListViewModel>();
|
||||
|
||||
@@ -21,6 +21,8 @@ public partial class DashboardViewModel : ObservableObject
|
||||
private readonly IParticipationRepository _participationEntries;
|
||||
private readonly IStudentRepository _students;
|
||||
private readonly IDocumentationRepository _documentation;
|
||||
private readonly ITimetableSlotRepository _timetableSlots;
|
||||
private readonly PeriodScheduleService _periodSchedule;
|
||||
private readonly AttendanceBalanceService _attendanceBalance;
|
||||
private readonly SchoolYearService _sy;
|
||||
|
||||
@@ -46,15 +48,22 @@ public partial class DashboardViewModel : ObservableObject
|
||||
// Navigation-Callback – wird von App.axaml.cs verdrahtet
|
||||
public Action<Guid>? OnNavigateToGroup { get; set; }
|
||||
public Action<Guid>? OnNavigateToStudent { get; set; }
|
||||
// Sprungziel eigens für eine Stunde in TodaysLessons (9.2) — bewusst getrennt von
|
||||
// OnNavigateToGroup (Lerngruppen-Kacheln, Tab "Übersicht"), da der Sprung von einer konkreten
|
||||
// Stunde aus sinnvollerweise direkt in die Mitarbeitserfassung führt.
|
||||
public Action<Guid>? OnNavigateToLesson { get; set; }
|
||||
|
||||
public DashboardViewModel(IGroupRepository groups, ISubjectRepository subjects, ILessonRepository lessons,
|
||||
IExamRepository exams, IWorkTaskRepository tasks, IParticipationSessionRepository participationSessions,
|
||||
IParticipationRepository participationEntries, IStudentRepository students,
|
||||
IDocumentationRepository documentation, AttendanceBalanceService attendanceBalance, SchoolYearService sy)
|
||||
IDocumentationRepository documentation, ITimetableSlotRepository timetableSlots,
|
||||
PeriodScheduleService periodSchedule, AttendanceBalanceService attendanceBalance, SchoolYearService sy)
|
||||
{
|
||||
_groups = groups; _subjects = subjects; _lessons = lessons; _exams = exams; _tasks = tasks;
|
||||
_participationSessions = participationSessions; _participationEntries = participationEntries;
|
||||
_students = students; _documentation = documentation; _attendanceBalance = attendanceBalance; _sy = sy;
|
||||
_students = students; _documentation = documentation;
|
||||
_timetableSlots = timetableSlots; _periodSchedule = periodSchedule;
|
||||
_attendanceBalance = attendanceBalance; _sy = sy;
|
||||
Load();
|
||||
}
|
||||
|
||||
@@ -73,8 +82,23 @@ public partial class DashboardViewModel : ObservableObject
|
||||
foreach (var l in groups.Keys.SelectMany(gid => _lessons.GetByGroupAndDate(gid, today))
|
||||
.OrderBy(l => l.LessonNumber))
|
||||
{
|
||||
if (groups.TryGetValue(l.GroupId, out var g))
|
||||
TodaysLessons.Add(new() { GroupName = g.Name, Topic = l.Topic });
|
||||
if (!groups.TryGetValue(l.GroupId, out var g)) continue;
|
||||
|
||||
// Raum kommt aus dem Stundenplan-Slot (4.3), sofern die Stunde eine Stundennummer hat
|
||||
// und dafür ein Slot am heutigen Wochentag existiert — Vertretungen/Ausfälle werden
|
||||
// hier bewusst NICHT berücksichtigt (das leistet bereits die "Heute"-Ansicht im
|
||||
// Stundenplan selbst, eine Dopplung dieser Logik wäre hier nicht sinnvoll).
|
||||
var slot = l.LessonNumber is int period
|
||||
? _timetableSlots.GetByGroup(l.GroupId).FirstOrDefault(s => s.Weekday == today.DayOfWeek && s.PeriodNumber == period)
|
||||
: null;
|
||||
var timeDisplay = l.StartTime is { } st ? st.ToString("HH:mm")
|
||||
: l.LessonNumber is int p2 && _periodSchedule.GetTimes(p2) is { } t ? t.Start.ToString("HH:mm") : "";
|
||||
|
||||
TodaysLessons.Add(new()
|
||||
{
|
||||
LessonId = l.Id, GroupId = l.GroupId, GroupName = g.Name, Topic = l.Topic,
|
||||
TimeDisplay = timeDisplay, Room = slot?.Room ?? "",
|
||||
});
|
||||
}
|
||||
|
||||
OpenTasks.Clear();
|
||||
@@ -264,6 +288,7 @@ public partial class DashboardViewModel : ObservableObject
|
||||
}
|
||||
|
||||
[RelayCommand] private void OpenGroup(GroupChip? c) { if (c is not null) OnNavigateToGroup?.Invoke(c.GroupId); }
|
||||
[RelayCommand] private void OpenLesson(LessonItem? l) { if (l is not null) OnNavigateToLesson?.Invoke(l.GroupId); }
|
||||
[RelayCommand] private void Refresh() => Load();
|
||||
|
||||
private class DayAgg
|
||||
@@ -275,7 +300,16 @@ public partial class DashboardViewModel : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
public class LessonItem { public string GroupName { get; set; } = ""; public string Topic { get; set; } = ""; }
|
||||
public class LessonItem
|
||||
{
|
||||
public Guid LessonId { get; set; }
|
||||
public Guid GroupId { get; set; }
|
||||
public string GroupName { get; set; } = "";
|
||||
public string Topic { get; set; } = "";
|
||||
public string TimeDisplay { get; set; } = "";
|
||||
public string Room { get; set; } = "";
|
||||
public bool HasRoom => !string.IsNullOrWhiteSpace(Room);
|
||||
}
|
||||
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; } = ""; }
|
||||
|
||||
|
||||
@@ -32,16 +32,28 @@
|
||||
<ItemsControl ItemsSource="{Binding TodaysLessons}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="vm:LessonItem">
|
||||
<Grid ColumnDefinitions="4,*" Margin="0,4">
|
||||
<Border Grid.Column="0" Width="4" CornerRadius="2"
|
||||
Background="{DynamicResource SystemAccentColor}"
|
||||
Margin="0,0,10,0"/>
|
||||
<StackPanel Grid.Column="1">
|
||||
<TextBlock Text="{Binding GroupName}" FontWeight="SemiBold" FontSize="13"/>
|
||||
<TextBlock Text="{Binding Topic}" FontSize="12" Opacity="0.7"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Button Command="{Binding $parent[ItemsControl].((vm:DashboardViewModel)DataContext).OpenLessonCommand}"
|
||||
CommandParameter="{Binding}"
|
||||
Background="Transparent" BorderThickness="0" Padding="0" Margin="0,4"
|
||||
HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch">
|
||||
<Grid ColumnDefinitions="4,*,Auto">
|
||||
<Border Grid.Column="0" Width="4" CornerRadius="2"
|
||||
Background="{DynamicResource SystemAccentColor}"
|
||||
Margin="0,0,10,0"/>
|
||||
<StackPanel Grid.Column="1">
|
||||
<TextBlock Text="{Binding GroupName}" FontWeight="SemiBold" FontSize="13"/>
|
||||
<TextBlock Text="{Binding Topic}" FontSize="12" Opacity="0.7"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2" HorizontalAlignment="Right" Margin="8,0,0,0">
|
||||
<TextBlock Text="{Binding TimeDisplay}" FontSize="12" Opacity="0.7"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding TimeDisplay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
<TextBlock Text="{Binding Room}" FontSize="11" Opacity="0.6"
|
||||
HorizontalAlignment="Right" IsVisible="{Binding HasRoom}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
@@ -1204,8 +1204,21 @@ Basis vorhanden in [DashboardViewModel.cs](LehrerApp.Desktop/ViewModels/Dashboar
|
||||
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.
|
||||
- [x] **9.1** Heutige Stunden mit Uhrzeit und Raum anzeigen (Abhängigkeit zu 4.3, jetzt erfüllt).
|
||||
**Umsetzung:** `DashboardViewModel.TodaysLessons` löst je Stunde den Raum über den zur
|
||||
Stundennummer und dem heutigen Wochentag passenden `TimetableSlot` auf, die Uhrzeit aus
|
||||
`Lesson.StartTime` (falls manuell gesetzt) oder sonst aus dem Stundenraster
|
||||
(`PeriodScheduleService.GetTimes`) — gleiche Quellen, wie sie auch im Verlaufsplan-Editor
|
||||
(4.2.2) und im Stundenplan verwendet werden. **Bewusst nicht dupliziert:** die reichhaltigere
|
||||
Logik der "Heute"-Ansicht im Stundenplan selbst (Vertretung/Ausfall/Sondereinsatz-bewusst,
|
||||
siehe 4.3 Nachträge) — das Dashboard zeigt hier nur die einfache, tatsächlich geplante
|
||||
Stunde, für den vollständigen Tagesüberblick bleibt der Stundenplan zuständig.
|
||||
- [x] **9.2** Direkter Absprung von einer Stunde in Mitarbeitserfassung bzw. Stundenplanung.
|
||||
**Umsetzung:** Klick auf eine Stunde in "Heute" springt in die Lerngruppe, Tab "Mitarbeit"
|
||||
(`DashboardViewModel.OnNavigateToLesson`, `NavigateToGroupDetail(id, 2)`) — bewusst anderes
|
||||
Sprungziel als der bereits bestehende Klick in der Stundenplan-eigenen "Heute"-Ansicht
|
||||
(springt dort auf Tab "Planung", siehe 4.4.2): vom Dashboard aus ist der naheliegende nächste
|
||||
Schritt morgens eher die Mitarbeitserfassung als die Planung.
|
||||
- [ ] **9.3** Kachel "Anstehende Termine": Klausuren, Förderplan-Überprüfungen, Abgabefristen.
|
||||
- [ ] **9.4** Kachel "Offene Korrekturen" mit Fortschritt (x von y Klausuren bewertet).
|
||||
- [ ] **9.5** Kachel "Auffälligkeiten": Fehlzeitenüberschreitungen, Notenabfall, Versetzungsgefährdung.
|
||||
|
||||
Reference in New Issue
Block a user