Tagesflagge im Sitzplan, Leistungsüberblick mit Zielnoten-Rechner
CI / build-and-test (push) Canceled after 0s

- Mitarbeit: Tagesflagge (👑 Spitzentag / 😴 Schlaftag /  Schlechter Tag)
  je Schüler und Sitzung. Primär im Sitzplatz-Dialog (⇧1/2/3, ⇧X) mit
  Badge auf der Sitzplatz-Kachel; Fallback in der Schnelleingabe für
  Lerngruppen ohne Sitzplan.
- Noten: neuer Dialog "Überblick" in der Notenübersicht zeigt Klausuren,
  Mitarbeit- und sonstige Noten eines Schülers samt berechneter
  Zeugnisnote. Zielnoten-Rechner (ReportGradeTargetCalculator) beantwortet
  "was brauche ich noch für Note X", ein Was-wäre-wenn-Rechner simuliert
  eine zusätzliche Klausurnote. Bewerter-/Schülermodus per Umschalter im
  selben Fenster — Bewertermodus zeigt zusätzlich Kursdurchschnitt je
  Klausur und erlaubt das Bearbeiten von Mitarbeit-/Sonstige-Noten.
  Klausurverlauf als Balken-Sparkline wie die bestehende Notenentwicklung.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 11:43:47 +02:00
co-authored by Claude Sonnet 5
parent 84c03962ef
commit 1215d4ac22
20 changed files with 1087 additions and 4 deletions
@@ -547,11 +547,13 @@ public partial class SeatCellViewModel : ObservableObject
[ObservableProperty] private double _lessonOpacity = 1.0;
[ObservableProperty] private string _attendanceBadge = "";
[ObservableProperty] private string _homeworkBadge = "";
[ObservableProperty] private string _dayHighlightBadge = "";
[ObservableProperty] private int _raisedHandCount;
[ObservableProperty] private int _calledOnCount;
public ObservableCollection<SituationTagChoice> SituationTags { get; } = [];
public bool HasAttendanceBadge => AttendanceBadge.Length > 0;
public bool HasHomeworkBadge => HomeworkBadge.Length > 0;
public bool HasDayHighlightBadge => DayHighlightBadge.Length > 0;
public bool ShowLessonOverview => IsOccupied && !CanEdit;
[ObservableProperty] private bool _canRecordLesson;
public bool IsOccupied => SelectedOption.StudentId.HasValue;
@@ -644,6 +646,7 @@ public partial class SeatCellViewModel : ObservableObject
var attendance = entry?.Attendance;
AttendanceBadge = attendance is null ? "" : AttendanceDisplay.ShortLabel(attendance);
HomeworkBadge = entry is null ? "" : HomeworkDisplay.Symbol(HomeworkDisplay.Effective(entry));
DayHighlightBadge = DayHighlightDisplay.Symbol(entry?.DayHighlight);
RaisedHandCount = entry?.RaisedHandCount ?? 0;
CalledOnCount = entry?.CalledOnCount ?? 0;
LessonOpacity = attendance is not null and not AttendanceStatus.Present
@@ -652,6 +655,7 @@ public partial class SeatCellViewModel : ObservableObject
foreach (var choice in SituationTags) choice.IsSelected = selected.Contains(choice.Text);
OnPropertyChanged(nameof(HasAttendanceBadge));
OnPropertyChanged(nameof(HasHomeworkBadge));
OnPropertyChanged(nameof(HasDayHighlightBadge));
}
}
@@ -677,6 +681,7 @@ public partial class SeatAssessmentViewModel : ObservableObject
[ObservableProperty] private int _selectedAspectIndex;
[ObservableProperty] private string _attendanceLabel = "Noch nicht kontrolliert";
[ObservableProperty] private string _homeworkLabel = "Keine Hausaufgabe aufgegeben";
[ObservableProperty] private string _dayHighlightLabel = "Keine Markierung";
public string StudentName { get; }
public string SessionDisplay { get; }
@@ -687,6 +692,7 @@ public partial class SeatAssessmentViewModel : ObservableObject
public ObservableCollection<SeatAssessmentAspectRow> AspectRows { get; } = [];
public ObservableCollection<SeatAttendanceChoice> AttendanceChoices { get; } = [];
public ObservableCollection<SeatHomeworkChoice> HomeworkChoices { get; } = [];
public ObservableCollection<SeatDayHighlightChoice> DayHighlightChoices { get; } = [];
public SeatAssessmentViewModel(IParticipationSessionRepository sessions,
IParticipationRepository entries, IParticipationAspectRepository aspects,
@@ -729,6 +735,7 @@ public partial class SeatAssessmentViewModel : ObservableObject
BuildAttendanceChoices();
BuildHomeworkChoices();
BuildDayHighlightChoices();
RefreshStatusChoices();
SelectAspect(0);
}
@@ -761,6 +768,17 @@ public partial class SeatAssessmentViewModel : ObservableObject
HomeworkChoices.Add(new("·", "Keine aufgegeben", "⌥X", null, SetHomework));
}
private void BuildDayHighlightChoices()
{
DayHighlightChoices.Add(new(DayHighlightDisplay.Symbol(DayHighlightKind.Standout),
DayHighlightDisplay.Label(DayHighlightKind.Standout), "⇧1", DayHighlightKind.Standout, SetDayHighlight));
DayHighlightChoices.Add(new(DayHighlightDisplay.Symbol(DayHighlightKind.Sleepy),
DayHighlightDisplay.Label(DayHighlightKind.Sleepy), "⇧2", DayHighlightKind.Sleepy, SetDayHighlight));
DayHighlightChoices.Add(new(DayHighlightDisplay.Symbol(DayHighlightKind.Rough),
DayHighlightDisplay.Label(DayHighlightKind.Rough), "⇧3", DayHighlightKind.Rough, SetDayHighlight));
DayHighlightChoices.Add(new("·", "Keine Markierung", "⇧X", null, SetDayHighlight));
}
public void SelectAspect(int index)
{
if (index < 0 || index >= AspectRows.Count) return;
@@ -827,6 +845,17 @@ public partial class SeatAssessmentViewModel : ObservableObject
if (clear || digit is 0 or 1 or 3 or 4 or 5 or 7 or 8) SetHomework(status);
}
public void ApplyDayHighlightShortcut(int? digit, bool clear)
{
if (!CanEdit) return;
var kind = clear ? null : digit switch
{
1 => DayHighlightKind.Standout, 2 => DayHighlightKind.Sleepy, 3 => DayHighlightKind.Rough,
_ => (DayHighlightKind?)null,
};
if (clear || digit is 1 or 2 or 3) SetDayHighlight(kind);
}
private void ApplyRating(string key, int? value)
{
if (!CanEdit || _entry is null) return;
@@ -857,13 +886,23 @@ public partial class SeatAssessmentViewModel : ObservableObject
RefreshStatusChoices();
}
private void SetDayHighlight(DayHighlightKind? kind)
{
if (!CanEdit || _entry is null) return;
_entry.DayHighlight = kind;
_entries.Save(_entry);
RefreshStatusChoices();
}
private void RefreshStatusChoices()
{
AttendanceLabel = AttendanceDisplay.Label(_entry?.Attendance);
HomeworkLabel = HomeworkDisplay.Label(_entry is null ? null : HomeworkDisplay.Effective(_entry));
DayHighlightLabel = DayHighlightDisplay.Label(_entry?.DayHighlight);
foreach (var choice in AttendanceChoices) choice.IsSelected = choice.Status == _entry?.Attendance;
var homework = _entry is null ? null : HomeworkDisplay.Effective(_entry);
foreach (var choice in HomeworkChoices) choice.IsSelected = choice.Status == homework;
foreach (var choice in DayHighlightChoices) choice.IsSelected = choice.Kind == _entry?.DayHighlight;
}
}
@@ -966,6 +1005,17 @@ public partial class SeatHomeworkChoice(string symbol, string label, string shor
[RelayCommand] private void Apply() => apply(Status);
}
public partial class SeatDayHighlightChoice(string symbol, string label, string shortcut,
DayHighlightKind? kind, Action<DayHighlightKind?> apply) : ObservableObject
{
public string Symbol { get; } = symbol;
public string Label { get; } = label;
public string Shortcut { get; } = shortcut;
public DayHighlightKind? Kind { get; } = kind;
[ObservableProperty] private bool _isSelected;
[RelayCommand] private void Apply() => apply(Kind);
}
public partial class SeatingPlanDialogViewModel : ObservableObject
{
private readonly ISeatingPlanRepository _plans;