Notenverwaltung (Kapitel 2) und Mitarbeits-Assistent

Notenübersicht der Gruppe (Matrix, Gesamt-Spalte, Sortierung, Halbjahresfilter),
Einzelnoten-Pflege samt Sammelerfassung, Gewichtungsschema mit Voreinstellung je
Gruppentyp, Zeugnisnoten-Berechnung mit Übersteuern/Festschreiben/Export und
Notenentwicklung im Schülerdetail.

Dazu Anwesenheits-/Hausaufgaben-Tracking je Mitarbeit-Sitzung, ein neuer
Mitarbeits-Assistent (Zeitleiste mit Abschnitten, automatischer Notenvorschlag,
Zusammenzug zur Halbjahresnote) und eine Dashboard-Kachel für offene
Entschuldigungen. Außerdem: verbliebene englische Begriffe in Auswahlfeldern
und Buttons auf Deutsch umgestellt.
This commit is contained in:
2026-08-12 19:19:22 +02:00
parent a1e722ace1
commit febcb0855a
34 changed files with 2503 additions and 39 deletions
@@ -28,6 +28,7 @@ public partial class ParticipationTabViewModel : ObservableObject
public Guid GroupId => _groupId;
public string SchoolYear => _schoolYear;
public GradingSystem GradingSystem => _gradingSystem;
public string GroupLabel { get; private set; } = "";
[ObservableProperty] private ParticipationSessionItem? _selectedSession;
[ObservableProperty] private string _noDataText = "Keine Bewertungssitzung ausgewählt.";
@@ -47,6 +48,7 @@ public partial class ParticipationTabViewModel : ObservableObject
public Func<Task<ParticipationSession?>>? OnAddSession { get; set; }
public Func<ParticipationTabViewModel, Task>? OnQuickInput { get; set; }
public Func<ParticipationTabViewModel, Task>? OnComputeGrade { get; set; }
public Func<ParticipationTabViewModel, Task>? OnOpenWizard { get; set; }
public ParticipationTabViewModel(
IParticipationSessionRepository sessions,
@@ -72,6 +74,7 @@ public partial class ParticipationTabViewModel : ObservableObject
_subjectId = group?.SubjectId;
_gradeLevel = group?.GradeLevel ?? 0;
_gradingSystem = group?.GradingSystem ?? GradingSystem.Grades1To6;
GroupLabel = group?.Name ?? "";
HasCompetencyCatalog = _subjectId.HasValue
&& _competencyDomains.GetBySubjectAndGrade(_subjectId.Value, _gradeLevel).Count > 0;
@@ -143,6 +146,8 @@ public partial class ParticipationTabViewModel : ObservableObject
var row = new ParticipationStudentRow(s.Id, s.FullName, entry, Aspects.ToList(), ActiveCompetencyCodes);
row.OnRatingChanged = (sid, key, val) => SaveRating(sessionId, sid, key, val);
row.OnCompetencyRatingChanged = (sid, code, val) => SaveCompetencyRating(sessionId, sid, code, val);
row.HomeworkChangedCallback = (sid, val) => SaveHomework(sessionId, sid, val);
row.AttendanceChangedCallback = (sid, val) => SaveAttendance(sessionId, sid, val);
StudentRows.Add(row);
}
QuickInputCommand.NotifyCanExecuteChanged();
@@ -185,6 +190,22 @@ public partial class ParticipationTabViewModel : ObservableObject
if (SelectedSession is not null) LoadGrid(SelectedSession.Id);
}
private void SaveHomework(Guid sessionId, Guid studentId, bool value)
{
var entry = _entries.GetBySessionAndStudent(sessionId, studentId)
?? new ParticipationEntry { SessionId = sessionId, StudentId = studentId };
entry.HomeworkMissing = value;
_entries.Save(entry);
}
private void SaveAttendance(Guid sessionId, Guid studentId, AttendanceStatus? value)
{
var entry = _entries.GetBySessionAndStudent(sessionId, studentId)
?? new ParticipationEntry { SessionId = sessionId, StudentId = studentId };
entry.Attendance = value;
_entries.Save(entry);
}
[RelayCommand]
private void ToggleCompetencyTags() => CompetencyTagsVisible = !CompetencyTagsVisible;
@@ -259,6 +280,13 @@ public partial class ParticipationTabViewModel : ObservableObject
await OnComputeGrade(this);
}
[RelayCommand]
private async Task OpenWizard()
{
if (OnOpenWizard is null) return;
await OnOpenWizard(this);
}
[RelayCommand]
private void DeleteSession()
{
@@ -310,8 +338,16 @@ public partial class ParticipationStudentRow : ObservableObject
public ObservableCollection<RatingCell> Cells { get; } = [];
public ObservableCollection<RatingCell> CompetencyCells { get; } = [];
[ObservableProperty] private bool _homeworkMissing;
[ObservableProperty] private AttendanceStatus? _attendance;
public string AttendanceLabel => AttendanceDisplay.ShortLabel(Attendance);
public string AttendanceTooltip => AttendanceDisplay.Label(Attendance);
public Action<Guid, string, int?>? OnRatingChanged { get; set; }
public Action<Guid, string, int?>? OnCompetencyRatingChanged { get; set; }
public Action<Guid, bool>? HomeworkChangedCallback { get; set; }
public Action<Guid, AttendanceStatus?>? AttendanceChangedCallback { get; set; }
public ParticipationStudentRow(Guid id, string name, ParticipationEntry entry,
List<AspectColumnDef> aspects, List<string> competencyCodes)
@@ -320,6 +356,8 @@ public partial class ParticipationStudentRow : ObservableObject
Name = name;
_entry = entry;
_aspectDefs = aspects;
_homeworkMissing = entry.HomeworkMissing;
_attendance = entry.Attendance;
foreach (var a in aspects)
{
@@ -347,6 +385,61 @@ public partial class ParticipationStudentRow : ObservableObject
cell?.SetValue(value);
OnRatingChanged?.Invoke(StudentId, key, value);
}
[RelayCommand]
private void ToggleHomework()
{
HomeworkMissing = !HomeworkMissing;
HomeworkChangedCallback?.Invoke(StudentId, HomeworkMissing);
}
[RelayCommand]
private void CycleAttendance()
{
Attendance = Attendance switch
{
null => AttendanceStatus.ExcusePending,
AttendanceStatus.ExcusePending => AttendanceStatus.Excused,
AttendanceStatus.Excused => AttendanceStatus.Unexcused,
AttendanceStatus.Unexcused => null,
_ => null,
};
OnPropertyChanged(nameof(AttendanceLabel));
OnPropertyChanged(nameof(AttendanceTooltip));
AttendanceChangedCallback?.Invoke(StudentId, Attendance);
}
// Direktes Setzen (z.B. aus dem Grading-Wizard heraus), ohne den Zyklus zu durchlaufen.
public void SetAttendance(AttendanceStatus? value)
{
Attendance = value;
OnPropertyChanged(nameof(AttendanceLabel));
OnPropertyChanged(nameof(AttendanceTooltip));
AttendanceChangedCallback?.Invoke(StudentId, value);
}
}
// ── Anwesenheits-Anzeige ──────────────────────────────────────────────────────
public static class AttendanceDisplay
{
public static string Label(AttendanceStatus? s) => s switch
{
null => "Anwesend",
AttendanceStatus.ExcusePending => "Krank (Entschuldigung offen)",
AttendanceStatus.Excused => "Krank, entschuldigt",
AttendanceStatus.Unexcused => "Krank, unentschuldigt",
_ => "Anwesend",
};
public static string ShortLabel(AttendanceStatus? s) => s switch
{
null => "",
AttendanceStatus.ExcusePending => "K ?",
AttendanceStatus.Excused => "K ✓",
AttendanceStatus.Unexcused => "K ✗",
_ => "",
};
}
// ── Eine Bewertungszelle ──────────────────────────────────────────────────────