CalendarDatePicker: DateTimeOffset-Felder nullbar machen
CI / build-and-test (push) Canceled after 0s

CalendarDatePicker.SelectedDate ist DateTimeOffset?, die per DatePicker-
zu-CalendarDatePicker-Umstellung betroffenen ViewModel-Eigenschaften
waren aber nicht-nullbares DateTimeOffset. Der alte DatePicker tolerierte
das, CalendarDatePickers Bindung nicht - Feld blieb leer und ein
Konvertierungsfehler wurde beim Auswählen geworfen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-01 17:19:07 +02:00
co-authored by Claude Sonnet 5
parent d297f9f109
commit 09e97efb61
5 changed files with 25 additions and 14 deletions
@@ -149,8 +149,8 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
public ObservableCollection<ClassTeacherCategoryAggregateRow> CategoryAggregates { get; } = []; public ObservableCollection<ClassTeacherCategoryAggregateRow> CategoryAggregates { get; } = [];
public ObservableCollection<DocumentationItem> OwnDocumentationEntries { get; } = []; public ObservableCollection<DocumentationItem> OwnDocumentationEntries { get; } = [];
[ObservableProperty] private DateTimeOffset _startDate = DateTimeOffset.Now.AddDays(-6); [ObservableProperty] private DateTimeOffset? _startDate = DateTimeOffset.Now.AddDays(-6);
[ObservableProperty] private DateTimeOffset _endDate = DateTimeOffset.Now; [ObservableProperty] private DateTimeOffset? _endDate = DateTimeOffset.Now;
[ObservableProperty] private string _status = "Zeitraum wählen und laden."; [ObservableProperty] private string _status = "Zeitraum wählen und laden.";
[ObservableProperty] private bool _busy; [ObservableProperty] private bool _busy;
/// Von der Übersicht gesetzt (Klick auf eine Roster-Zeile) - leer zeigt alle Schüler*innen. /// Von der Übersicht gesetzt (Klick auf eine Roster-Zeile) - leer zeigt alle Schüler*innen.
@@ -231,8 +231,8 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
private async Task LoadInternal(bool forceRefresh) private async Task LoadInternal(bool forceRefresh)
{ {
var start = DateOnly.FromDateTime(StartDate.LocalDateTime); var start = DateOnly.FromDateTime((StartDate ?? DateTimeOffset.Now).LocalDateTime);
var end = DateOnly.FromDateTime(EndDate.LocalDateTime); var end = DateOnly.FromDateTime((EndDate ?? DateTimeOffset.Now).LocalDateTime);
if (end < start) { Status = "Das Enddatum darf nicht vor dem Startdatum liegen."; return; } if (end < start) { Status = "Das Enddatum darf nicht vor dem Startdatum liegen."; return; }
if (string.IsNullOrWhiteSpace(_className)) { Status = "Keine Klasse ausgewählt."; return; } if (string.IsNullOrWhiteSpace(_className)) { Status = "Keine Klasse ausgewählt."; return; }
@@ -48,8 +48,8 @@ public partial class WebUntisLessonAbsenceComparisonViewModel : ObservableObject
new Dictionary<DateOnly, ParticipationSession>(); new Dictionary<DateOnly, ParticipationSession>();
public ObservableCollection<WebUntisLessonAbsenceRow> Rows { get; } = []; public ObservableCollection<WebUntisLessonAbsenceRow> Rows { get; } = [];
[ObservableProperty] private DateTimeOffset _startDate = DateTimeOffset.Now.AddMonths(-2); [ObservableProperty] private DateTimeOffset? _startDate = DateTimeOffset.Now.AddMonths(-2);
[ObservableProperty] private DateTimeOffset _endDate = DateTimeOffset.Now; [ObservableProperty] private DateTimeOffset? _endDate = DateTimeOffset.Now;
[ObservableProperty] private string _status = "Zeitraum wählen und Fehlzeiten laden."; [ObservableProperty] private string _status = "Zeitraum wählen und Fehlzeiten laden.";
[ObservableProperty] private bool _busy; [ObservableProperty] private bool _busy;
[ObservableProperty] private bool _markUnknownAsPresent; [ObservableProperty] private bool _markUnknownAsPresent;
@@ -73,8 +73,8 @@ public partial class WebUntisLessonAbsenceComparisonViewModel : ObservableObject
return; return;
} }
var start = DateOnly.FromDateTime(StartDate.LocalDateTime); var start = DateOnly.FromDateTime((StartDate ?? DateTimeOffset.Now).LocalDateTime);
var end = DateOnly.FromDateTime(EndDate.LocalDateTime); var end = DateOnly.FromDateTime((EndDate ?? DateTimeOffset.Now).LocalDateTime);
if (end < start) { Status = "Das Enddatum darf nicht vor dem Startdatum liegen."; return; } if (end < start) { Status = "Das Enddatum darf nicht vor dem Startdatum liegen."; return; }
Busy = true; Rows.Clear(); Busy = true; Rows.Clear();
try try
@@ -37,7 +37,7 @@ public partial class WebUntisTimetableImportViewModel : ObservableObject
public ObservableCollection<UntisTeacherDto> Teachers { get; } = []; public ObservableCollection<UntisTeacherDto> Teachers { get; } = [];
public ObservableCollection<WebUntisTimetableRow> Rows { get; } = []; public ObservableCollection<WebUntisTimetableRow> Rows { get; } = [];
[ObservableProperty] private UntisTeacherDto? _selectedTeacher; [ObservableProperty] private UntisTeacherDto? _selectedTeacher;
[ObservableProperty] private DateTimeOffset _weekDate = DateTimeOffset.Now; [ObservableProperty] private DateTimeOffset? _weekDate = DateTimeOffset.Now;
[ObservableProperty] private string _status = "Lehrkraft auswählen und Untis-Woche laden."; [ObservableProperty] private string _status = "Lehrkraft auswählen und Untis-Woche laden.";
[ObservableProperty] private bool _busy; [ObservableProperty] private bool _busy;
public bool Saved { get; private set; } public bool Saved { get; private set; }
@@ -71,7 +71,7 @@ public partial class WebUntisTimetableImportViewModel : ObservableObject
Busy = true; Rows.Clear(); Busy = true; Rows.Clear();
try try
{ {
var selected = DateOnly.FromDateTime(WeekDate.LocalDateTime); var selected = DateOnly.FromDateTime((WeekDate ?? DateTimeOffset.Now).LocalDateTime);
var monday = selected.AddDays(-(((int)selected.DayOfWeek + 6) % 7)); var monday = selected.AddDays(-(((int)selected.DayOfWeek + 6) % 7));
var periods = await _untis.GetTimetableAsync(SelectedTeacher.UntisId, monday, monday.AddDays(6)); var periods = await _untis.GetTimetableAsync(SelectedTeacher.UntisId, monday, monday.AddDays(6));
var grid = await _untis.GetTimeGridAsync(); var grid = await _untis.GetTimeGridAsync();
@@ -58,8 +58,8 @@ public partial class WebUntisDocumentationComparisonViewModel : ObservableObject
public ObservableCollection<WebUntisDocumentationRow> Rows { get; } = []; public ObservableCollection<WebUntisDocumentationRow> Rows { get; } = [];
public ObservableCollection<LocalOnlyDocumentationRow> LocalOnlyRows { get; } = []; public ObservableCollection<LocalOnlyDocumentationRow> LocalOnlyRows { get; } = [];
[ObservableProperty] private DateTimeOffset _startDate = DateTimeOffset.Now.AddDays(-7); [ObservableProperty] private DateTimeOffset? _startDate = DateTimeOffset.Now.AddDays(-7);
[ObservableProperty] private DateTimeOffset _endDate = DateTimeOffset.Now; [ObservableProperty] private DateTimeOffset? _endDate = DateTimeOffset.Now;
[ObservableProperty] private string _status = "Zeitraum wählen und Klassenbucheinträge laden."; [ObservableProperty] private string _status = "Zeitraum wählen und Klassenbucheinträge laden.";
[ObservableProperty] private bool _busy; [ObservableProperty] private bool _busy;
@@ -74,8 +74,8 @@ public partial class WebUntisDocumentationComparisonViewModel : ObservableObject
[RelayCommand] [RelayCommand]
private async Task Load() private async Task Load()
{ {
var start = DateOnly.FromDateTime(StartDate.LocalDateTime); var start = DateOnly.FromDateTime((StartDate ?? DateTimeOffset.Now).LocalDateTime);
var end = DateOnly.FromDateTime(EndDate.LocalDateTime); var end = DateOnly.FromDateTime((EndDate ?? DateTimeOffset.Now).LocalDateTime);
if (end < start) { Status = "Das Enddatum darf nicht vor dem Startdatum liegen."; return; } if (end < start) { Status = "Das Enddatum darf nicht vor dem Startdatum liegen."; return; }
Busy = true; Rows.Clear(); LocalOnlyRows.Clear(); Busy = true; Rows.Clear(); LocalOnlyRows.Clear();
try try
+11
View File
@@ -1405,6 +1405,17 @@ Bestätigungsschritt zu verlangen. Betroffen: [WebUntisLessonAbsenceComparisonDi
[WebUntisTimetableImportDialog.axaml](LehrerApp.Desktop/Views/Planning/WebUntisTimetableImportDialog.axaml) [WebUntisTimetableImportDialog.axaml](LehrerApp.Desktop/Views/Planning/WebUntisTimetableImportDialog.axaml)
und [WebUntisDocumentationComparisonDialog.axaml](LehrerApp.Desktop/Views/Students/WebUntisDocumentationComparisonDialog.axaml). und [WebUntisDocumentationComparisonDialog.axaml](LehrerApp.Desktop/Views/Students/WebUntisDocumentationComparisonDialog.axaml).
**Nachtrag (September 2026, Nutzer-Feedback) — `CalendarDatePicker` blieb nach der Umstellung leer
und warf beim Auswählen einen Konvertierungsfehler:** `CalendarDatePicker.SelectedDate` ist
`DateTimeOffset?`, die fünf oben umgestellten ViewModel-Eigenschaften (`StartDate`/`EndDate` in
`ClassTeacherDetailsViewModel`, `WebUntisLessonAbsenceComparisonViewModel`,
`WebUntisDocumentationComparisonViewModel`, `WeekDate` in `WebUntisTimetableImportViewModel`)
waren aber als nicht-nullbares `DateTimeOffset` deklariert — anders als `WithdrawStudentDialog`
und `CreateLetterDialog`, deren Datumsfelder von Anfang an `DateTimeOffset?` sind. Der alte
`DatePicker` toleriert diese Abweichung stillschweigend, `CalendarDatePicker`s Bindung nicht. Alle
fünf Eigenschaften auf `DateTimeOffset?` umgestellt (Fallback `?? DateTimeOffset.Now` an den
wenigen Leseseiten), damit sie exakt zum Steuerelement passen.
**Nachtrag zu 4.3, Fehlzeiten je Unterricht (August 2026):** Der ursprüngliche Fehlzeitenabgleich **Nachtrag zu 4.3, Fehlzeiten je Unterricht (August 2026):** Der ursprüngliche Fehlzeitenabgleich
rief `getTimetableWithAbsences` ohne Element auf und bekam damit den kompletten Lehrer-Stundenplan rief `getTimetableWithAbsences` ohne Element auf und bekam damit den kompletten Lehrer-Stundenplan
zurück (einmal pro Kursmitglied, siehe damalige Ineffizienz-Korrektur) — das erfordert mehr zurück (einmal pro Kursmitglied, siehe damalige Ineffizienz-Korrektur) — das erfordert mehr