Klausurtermine im Kalender (4.4.3) + Stundenplan: Ausnahme eintragen, Zahnrad-Settings-Link

- 4.4.3: Box "ANSTEHENDE KLAUSUREN" im Heute-Tab, gruppenübergreifend, nächste 21 Tage.
  IExamRepository um GetAll() erweitert.
- Nutzer-Feedback: "Vertretung eintragen" deckt auch Ausfall/Sondereinsatz ab, daher
  umbenannt in "Ausnahme eintragen". "Stundenplan bearbeiten" (redundant zum Bearbeiten-Tab)
  ersetzt durch Zahnrad-Button, der direkt in die Einstellungen springt.
This commit is contained in:
2026-08-15 22:24:04 +02:00
parent d6dee72acc
commit 0800832ae5
13 changed files with 189 additions and 21 deletions
+2 -1
View File
@@ -105,8 +105,9 @@ public class App : Application
sl.OnNavigateToDetail = id => main.NavigateToStudent(id);
sl.OnAddStudent = () => ShowAddStudentDialog();
// Stundenplan "Heute" → GroupDetail (Tab "Planung")
// Stundenplan "Heute" → GroupDetail (Tab "Planung") / Einstellungen (Zahnrad, Tab "Ferien & Feiertage")
var timetable = Services.GetRequiredService<TimetableViewModel>();
timetable.OnNavigateToSettings = () => main.NavigateToSettings(7);
timetable.OnNavigateToGroup = id => main.NavigateToGroupDetail(id, 5);
}
@@ -97,6 +97,14 @@ public partial class MainWindowViewModel : ObservableObject
vm.LoadGroup(groupId); // dann Daten laden
}
public void NavigateToSettings(int initialTab = 0)
{
ActiveNavItem = NavItem.Settings;
var vm = _services.GetRequiredService<SettingsViewModel>();
vm.ActiveTabIndex = initialTab;
CurrentPage = vm;
}
public void NavigateToStudent(Guid studentId)
{
ActiveNavItem = NavItem.Students;
@@ -67,6 +67,7 @@ public partial class TimetableViewModel : ObservableObject
public ObservableCollection<TodayLessonItem> TodayItems { get; } = [];
public ObservableCollection<TodaySupervisionItem> TodaySupervisions { get; } = [];
public ObservableCollection<TodaySpecialAssignmentItem> TodaySpecialAssignments { get; } = [];
public ObservableCollection<UpcomingExamItem> UpcomingExams { get; } = [];
[ObservableProperty] private int _activeTabIndex;
[ObservableProperty] private string _todayLabel = "";
@@ -77,6 +78,7 @@ public partial class TimetableViewModel : ObservableObject
public Func<TimetableCellItem, Task>? OnEditSlot { get; set; }
public Action<Guid>? OnNavigateToGroup { get; set; }
public Func<Task>? OnAddSubstitution { get; set; }
public Action? OnNavigateToSettings { get; set; }
public TimetableViewModel(ITimetableSlotRepository slots, IGroupRepository groups,
ISubjectRepository subjects, ILessonRepository lessons, IExamRepository exams,
@@ -127,6 +129,31 @@ public partial class TimetableViewModel : ObservableObject
BuildWeekOverview(today, publicHolidayDates, duties);
BuildToday(today);
BuildHoursWarnings();
BuildUpcomingExams(today);
}
// ── Anstehende Klausurtermine (4.4.3) ────────────────────────────────────
/// <summary>
/// Mehrtägiger Vorausblick über alle Gruppen hinweg (nicht nur "heute", wie die Tagesliste,
/// und unabhängig von <see cref="WeekOffset"/>, anders als das Wochenraster) — Abgabefristen
/// sind bewusst nicht enthalten, dafür gibt es noch kein eigenes Datenmodell (siehe TODO.md).
/// </summary>
private const int UpcomingExamsHorizonDays = 21;
private void BuildUpcomingExams(DateOnly today)
{
UpcomingExams.Clear();
var groupNames = _groups.GetAll(includeInactive: true).ToDictionary(g => g.Id, g => g.Name);
var horizon = today.AddDays(UpcomingExamsHorizonDays);
var upcoming = _exams.GetAll()
.Where(e => e.Date >= today && e.Date <= horizon)
.OrderBy(e => e.Date)
.Take(10);
foreach (var exam in upcoming)
UpcomingExams.Add(new UpcomingExamItem(exam.Date, groupNames.GetValueOrDefault(exam.GroupId, "?"), exam.Title));
}
// ── "Heute": Tagesliste ──────────────────────────────────────────────────
@@ -207,7 +234,7 @@ public partial class TimetableViewModel : ObservableObject
}
[RelayCommand]
private void ShowEditor() => ActiveTabIndex = 1;
private void OpenSettings() => OnNavigateToSettings?.Invoke();
// ── "Heute": Wochenraster (Nutzer-Feedback) — wie das Bearbeiten-Raster, aber nur Anzeige ──
@@ -649,6 +676,14 @@ public class HoursWarningItem(string groupName, int assigned, int expected)
public string Text { get; } = $"{groupName}: {assigned} von {expected} Wochenstunden eingetragen";
}
public class UpcomingExamItem(DateOnly date, string groupName, string title)
{
public DateOnly Date { get; } = date;
public string DateDisplay { get; } = date.ToString("dd.MM.yyyy");
public string GroupName { get; } = groupName;
public string Title { get; } = title;
}
public class TodayLessonItem
{
public Guid GroupId { get; private init; }
@@ -30,6 +30,8 @@ public partial class SettingsViewModel : ObservableObject
private readonly IStudentRepository _students;
private readonly IShorthandCodeRepository _shorthandCodes;
[ObservableProperty] private int _activeTabIndex;
// ── Fächer ────────────────────────────────────────────────────────────────
[ObservableProperty] private string _newName = "";
@@ -4,14 +4,14 @@
xmlns:models="clr-namespace:LehrerApp.Core.Models;assembly=LehrerApp.Core"
x:Class="LehrerApp.Desktop.Views.Planning.SubstitutionEntryDialog"
x:DataType="vm:SubstitutionEntryDialogViewModel"
Title="Vertretung eintragen"
Title="Ausnahme eintragen"
Width="440" Height="560" MinWidth="400" MinHeight="480"
CanResize="True" WindowStartupLocation="CenterOwner">
<Grid RowDefinitions="*,Auto" Margin="24">
<ScrollViewer Grid.Row="0">
<StackPanel Spacing="14" Margin="0,0,12,0">
<TextBlock Text="Vertretung eintragen" Classes="dialogtitle"/>
<TextBlock Text="Ausnahme eintragen" Classes="dialogtitle"/>
<TextBlock FontSize="12" Opacity="0.6" TextWrapping="Wrap"
Text="Für einmalige Ausnahmen — Vertretungsaufsicht, Vertretungsstunde, Sondereinsatz (Ausflug, Berufsmesse, ...) oder schlichter Ausfall an einem konkreten Tag. Wiederkehrende Aufsicht wird in den Einstellungen gepflegt."/>
@@ -127,9 +127,10 @@
</TextBlock>
<Button Grid.Column="3" Content="Diese Woche" Margin="0,0,8,0"
Command="{Binding CurrentWeekCommand}" IsVisible="{Binding !IsCurrentWeek}"/>
<Button Grid.Column="4" Content="Vertretung eintragen" Margin="0,0,8,0"
<Button Grid.Column="4" Content="Ausnahme eintragen" Margin="0,0,8,0"
Command="{Binding AddSubstitutionCommand}"/>
<Button Grid.Column="5" Content="Stundenplan bearbeiten" Command="{Binding ShowEditorCommand}"/>
<Button Grid.Column="5" Content="⚙️" Command="{Binding OpenSettingsCommand}"
ToolTip.Tip="Einstellungen (Ferien, Aufsichten, Stundenraster)"/>
</Grid>
<ItemsControl ItemsSource="{Binding WeekItems}">
@@ -208,6 +209,25 @@
</StackPanel>
</Border>
<Border Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
CornerRadius="8" Padding="16" IsVisible="{Binding UpcomingExams.Count}">
<StackPanel Spacing="8">
<TextBlock Text="ANSTEHENDE KLAUSUREN" FontSize="11" FontWeight="Bold" Opacity="0.5"/>
<ItemsControl ItemsSource="{Binding UpcomingExams}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:UpcomingExamItem">
<Grid ColumnDefinitions="90,*" Margin="0,3">
<TextBlock Grid.Column="0" Text="{Binding DateDisplay}" FontSize="12" Opacity="0.6"/>
<TextBlock Grid.Column="1" FontSize="12">
<Run Text="{Binding GroupName}"/><Run Text=" — "/><Run Text="{Binding Title}"/>
</TextBlock>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</DockPanel>
@@ -9,7 +9,7 @@
<shared:PageHeader Grid.Row="0" Title="Einstellungen" Margin="32,28,32,0"/>
<TabbedPage Grid.Row="1" TabPlacement="Top">
<TabbedPage Grid.Row="1" TabPlacement="Top" SelectedIndex="{Binding ActiveTabIndex}">
<!-- Tab: Fächer -->
<ContentPage Header="Fächer">