Punkteeingabe & Korrektur für Klausuren (1.4)

Neuer Dialog ExamGradingDialog, erreichbar über "Punkte eingeben" im
Klausuren-Tab (Button + Kontextmenü bei ausgewählter Klausur):

- Eingaberaster: Schüler-Zeilen × Aufgaben-Spalten, Summe und Note live
  über GradingService.CalculateGrade() berechnet.
- Tab bewegt sich nativ zur nächsten Zelle; Enter/Pfeil-Hoch/Pfeil-Runter
  springen zur gleichen Spalte in der Nachbarzeile (auch über noch nicht
  realisierte, virtualisierte Zeilen hinweg via ScrollIntoView). Komma
  oder Punkt als Dezimaltrennzeichen werden beide akzeptiert.
- Abwesend-Checkbox (Note zeigt dann "abwesend") und Kommentarfeld pro
  Schüler.
- Punkte außerhalb 0..Maximalpunkte werden rot markiert und bewusst
  nicht gespeichert; der zuletzt gültige Stand bleibt erhalten.
- Autosave nach jeder Zelle, kein Speichern-Button nötig.

Dabei nebenbei behoben: die Schülerliste berücksichtigt jetzt Enrollment-
Zeiträume (H1/H2/Custom) relativ zum Klausurdatum, analog zur bereits
bestehenden Logik im Mitarbeit-Tab, statt pauschal alle im Schuljahr
eingeschriebenen Schüler zu zeigen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 00:52:30 +02:00
co-authored by Claude Sonnet 5
parent 3fc506adc1
commit 725de3813f
7 changed files with 411 additions and 7 deletions
@@ -176,6 +176,7 @@ public partial class GroupDetailViewModel : ObservableObject
public Func<Exam, Task<bool>>? OnEditExam { get; set; }
public Func<Exam, Task<bool>>? OnDuplicateExam { get; set; }
public Func<ExamSummary, Task<bool>>? OnConfirmDeleteExam { get; set; }
public Func<Exam, Task>? OnGradeExam { get; set; }
public GroupDetailViewModel(IGroupRepository groups, IStudentRepository students,
IEnrollmentRepository enrollments, IExamRepository exams, IGradeRepository grades,
@@ -276,6 +277,15 @@ public partial class GroupDetailViewModel : ObservableObject
}
}
[RelayCommand(CanExecute = nameof(HasSelectedExam))]
private async Task GradeExam()
{
if (SelectedExam is null || OnGradeExam is null) return;
var exam = _exams.GetById(SelectedExam.Id);
if (exam is null) return;
await OnGradeExam(exam);
}
[RelayCommand(CanExecute = nameof(HasSelectedExam))]
private async Task DuplicateExam()
{
@@ -330,6 +340,7 @@ public partial class GroupDetailViewModel : ObservableObject
partial void OnSelectedExamChanged(ExamSummary? value)
{
EditExamCommand.NotifyCanExecuteChanged();
GradeExamCommand.NotifyCanExecuteChanged();
DuplicateExamCommand.NotifyCanExecuteChanged();
DeleteExamCommand.NotifyCanExecuteChanged();
AdvanceExamStatusCommand.NotifyCanExecuteChanged();