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
@@ -54,6 +54,7 @@ public partial class GradeOverviewTabViewModel : ObservableObject
public Func<GradeOverviewRow, Task>? OnManageStudentGrades { get; set; }
public Func<Task>? OnCollectiveGrade { get; set; }
public Func<Task>? OnReportGrades { get; set; }
public Func<GradeOverviewRow, Task>? OnShowPerformanceOverview { get; set; }
public GradeOverviewTabViewModel(IGradeRepository grades, IExamRepository exams,
IExamResultRepository results, IStudentRepository students,
@@ -107,6 +108,14 @@ public partial class GradeOverviewTabViewModel : ObservableObject
Recompute();
}
[RelayCommand]
private async Task ShowPerformanceOverview()
{
if (SelectedRow is null || OnShowPerformanceOverview is null) return;
await OnShowPerformanceOverview(SelectedRow);
Recompute();
}
[RelayCommand]
private async Task CollectiveGrade()
{
@@ -158,6 +158,7 @@ public partial class ParticipationTabViewModel : ObservableObject
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);
row.DayHighlightChangedCallback = (sid, val) => SaveDayHighlight(sessionId, sid, val);
StudentRows.Add(row);
}
QuickInputCommand.NotifyCanExecuteChanged();
@@ -212,6 +213,15 @@ public partial class ParticipationTabViewModel : ObservableObject
_entries.Save(entry);
}
private void SaveDayHighlight(Guid sessionId, Guid studentId, DayHighlightKind? value)
{
if (IsReadOnly) return;
var entry = _entries.GetBySessionAndStudent(sessionId, studentId)
?? new ParticipationEntry { SessionId = sessionId, StudentId = studentId };
entry.DayHighlight = value;
_entries.Save(entry);
}
[RelayCommand]
private void ToggleCompetencyTags() => CompetencyTagsVisible = !CompetencyTagsVisible;
@@ -389,6 +399,7 @@ public partial class ParticipationStudentRow : ObservableObject
[ObservableProperty] private HomeworkStatus? _homework;
[ObservableProperty] private AttendanceStatus? _attendance;
[ObservableProperty] private DayHighlightKind? _dayHighlight;
public string AttendanceLabel => AttendanceDisplay.ShortLabel(Attendance);
public string AttendanceTooltip => AttendanceDisplay.Label(Attendance);
@@ -405,6 +416,7 @@ public partial class ParticipationStudentRow : ObservableObject
public Action<Guid, string, int?>? OnCompetencyRatingChanged { get; set; }
public Action<Guid, HomeworkStatus?>? HomeworkChangedCallback { get; set; }
public Action<Guid, AttendanceStatus?>? AttendanceChangedCallback { get; set; }
public Action<Guid, DayHighlightKind?>? DayHighlightChangedCallback { get; set; }
public ParticipationStudentRow(Guid id, string name, ParticipationEntry entry,
List<AspectColumnDef> aspects, List<string> competencyCodes)
@@ -414,6 +426,7 @@ public partial class ParticipationStudentRow : ObservableObject
_aspectDefs = aspects;
_homework = HomeworkDisplay.Effective(entry);
_attendance = entry.Attendance;
_dayHighlight = entry.DayHighlight;
foreach (var a in aspects)
{
@@ -463,6 +476,12 @@ public partial class ParticipationStudentRow : ObservableObject
HomeworkChangedCallback?.Invoke(StudentId, value);
}
public void SetDayHighlight(DayHighlightKind? value)
{
DayHighlight = value;
DayHighlightChangedCallback?.Invoke(StudentId, value);
}
[RelayCommand]
private void CycleAttendance()
{
@@ -614,6 +633,27 @@ public static class AttendanceDisplay
};
}
// ── Tagesflagge (Nutzer-Feedback) ──────────────────────────────────────────────
public static class DayHighlightDisplay
{
public static string Symbol(DayHighlightKind? kind) => kind switch
{
DayHighlightKind.Standout => "👑",
DayHighlightKind.Sleepy => "😴",
DayHighlightKind.Rough => "⚡",
_ => "",
};
public static string Label(DayHighlightKind? kind) => kind switch
{
DayHighlightKind.Standout => "Spitzentag",
DayHighlightKind.Sleepy => "Schlaftag",
DayHighlightKind.Rough => "Schlechter Tag",
_ => "Keine Markierung",
};
}
// ── Eine Bewertungszelle ──────────────────────────────────────────────────────
public partial class RatingCell : ObservableObject
@@ -800,12 +840,31 @@ public partial class QuickInputViewModel : ObservableObject
[ObservableProperty] private string _progressText = "";
[ObservableProperty] private bool _currentStudentIsAbsent;
[ObservableProperty] private string _currentStudentAttendanceLabel = "";
[ObservableProperty] private DayHighlightKind? _currentStudentDayHighlight;
/// Dimmt Name/Aspektliste, wenn der aktuelle Schüler abwesend ist — kein Blockieren der
/// Eingabe (manche Bewertungssysteme wollen trotzdem einen Eintrag, z.B. "0 Punkte"), nur ein
/// visueller Hinweis, dass eine Bewertung hier normalerweise keinen Sinn ergibt.
public double CurrentStudentContentOpacity => CurrentStudentIsAbsent ? 0.4 : 1.0;
/// Tagesflagge (Nutzer-Feedback): Fallback für Kurse ohne Sitzplan — im Sitzplatz-Dialog gibt
/// es dieselbe Auswahl bereits über SeatAssessmentViewModel.DayHighlightChoices.
public string CurrentStudentDayHighlightSymbol => DayHighlightDisplay.Symbol(CurrentStudentDayHighlight);
public string CurrentStudentDayHighlightLabel => DayHighlightDisplay.Label(CurrentStudentDayHighlight);
public void SetDayHighlight(DayHighlightKind? value)
{
if (_rows.Count == 0) return;
_rows[StudentIndex].SetDayHighlight(value);
CurrentStudentDayHighlight = value;
}
partial void OnCurrentStudentDayHighlightChanged(DayHighlightKind? value)
{
OnPropertyChanged(nameof(CurrentStudentDayHighlightSymbol));
OnPropertyChanged(nameof(CurrentStudentDayHighlightLabel));
}
public ObservableCollection<QuickAspectRow> AspectRows { get; } = [];
/// Wechselt je nach Typ des aktuell gewählten Aspekts (3.1.3) — Scale3/Binary haben andere
@@ -824,7 +883,8 @@ public partial class QuickInputViewModel : ObservableObject
_ => "15 bewerten",
};
return $"{ratingHint} · Q/W/E/R/T Aspekt wählen · Leertaste/↓ nächster Aspekt · ↑ vorheriger Aspekt · " +
"Enter/→ nächster Schüler · Backspace/← vorheriger Schüler · +/ anpassen · Entf nicht bewertet · Esc schließen";
"Enter/→ nächster Schüler · Backspace/← vorheriger Schüler · +/ anpassen · Entf nicht bewertet · " +
"⇧1/2/3 Tagesflagge, ⇧0 löschen · Esc schließen";
}
}
@@ -852,6 +912,7 @@ public partial class QuickInputViewModel : ObservableObject
ProgressText = $"{index + 1} / {_rows.Count}";
CurrentStudentIsAbsent = row.IsAbsent;
CurrentStudentAttendanceLabel = row.AttendanceTooltip;
CurrentStudentDayHighlight = row.DayHighlight;
AspectRows.Clear();
foreach (var (a, i) in _aspects.Select((a, i) => (a, i)))
@@ -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;
@@ -0,0 +1,310 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Models;
using LehrerApp.Core.Services;
using LehrerApp.Desktop.ViewModels.Students;
using System.Collections.ObjectModel;
using System.Globalization;
namespace LehrerApp.Desktop.ViewModels.Groups;
// ── Schüler-Leistungsüberblick + Zielnoten-Rechner (Nutzer-Feedback) ─────────────
//
// Zeigt die Leistungen eines einzelnen Schülers in einem Kurs im Überblick — Klausuren,
// Mitarbeit- und sonstige Noten, die daraus berechnete Zeugnisnote sowie zwei Rechner
// ("Zielnote": was brauche ich noch? / "Was-wäre-wenn": wie wirkt sich eine zusätzliche
// Klausurnote aus?). Bewusst pro Kurs statt fächerübergreifend, da Gewichtungsschema und
// Notensystem am Kurs hängen. Zwei Modi über IsTeacherMode: im Bewertermodus zusätzlich
// Kursdurchschnitt je Klausur und Bearbeitbarkeit der Mitarbeit-/Sonstige-Noten; im
// Schülermodus ausschließlich die eigenen Werte, rein lesend — gedacht, um den Bildschirm im
// Gespräch umzudrehen. Exam-Noten bleiben bewusst nur lesbar: eine Korrektur läuft weiterhin
// über den bestehenden ExamGradingDialog, der die Punkte-Struktur konsistent hält — direktes
// Überschreiben von Exam.Grade hier könnte mit ExamResult.Points/TotalPoints auseinanderlaufen.
public partial class StudentPerformanceOverviewViewModel : ObservableObject
{
private readonly IGradeRepository _grades;
private readonly IExamRepository _exams;
private readonly IExamResultRepository _results;
private readonly IGroupMembershipRepository _memberships;
private readonly IGradingSchemeRepository _schemes;
private readonly GradingService _grading;
private readonly Guid _groupId;
private readonly Guid _studentId;
private readonly GroupType _groupType;
private readonly GradingSystem _gradingSystem;
private readonly string _schoolYear;
private GradingScheme _scheme = new();
private double? _examsAverage;
private double? _participationAverage;
private double? _otherAverage;
private List<(string Grade, double Weight)> _examGradesRaw = [];
private List<(string Grade, double Weight)> _participationGradesRaw = [];
private List<(string Grade, double Weight)> _otherGradesRaw = [];
public string StudentName { get; }
public string GroupLabel { get; }
[ObservableProperty] private ParticipationPeriodOption _selectedPeriod;
[ObservableProperty] private bool _isTeacherMode = true;
[ObservableProperty] private string _schemeSummary = "";
[ObservableProperty] private string? _examsAverageDisplay;
[ObservableProperty] private string? _participationAverageDisplay;
[ObservableProperty] private string? _otherAverageDisplay;
[ObservableProperty] private string _currentReportGradeDisplay = "";
[ObservableProperty] private string _targetGradeText = "";
[ObservableProperty] private GradeBucketKind _targetBucket = GradeBucketKind.Exams;
[ObservableProperty] private string _targetResultDisplay = "";
[ObservableProperty] private string _whatIfExamGradeText = "";
[ObservableProperty] private string _whatIfResultDisplay = "";
public List<ParticipationPeriodOption> PeriodOptions { get; } =
[
new(ParticipationPeriod.FullYear, "Gesamtes Schuljahr"),
new(ParticipationPeriod.H1, "1. Halbjahr"),
new(ParticipationPeriod.H2, "2. Halbjahr"),
];
public string[] TargetBucketOptions { get; } = ["Klausuren", "Mitarbeit", "Sonstige"];
public string TargetBucketName
{
get => TargetBucket switch
{
GradeBucketKind.Participation => "Mitarbeit",
GradeBucketKind.Other => "Sonstige",
_ => "Klausuren",
};
set => TargetBucket = value switch
{
"Mitarbeit" => GradeBucketKind.Participation,
"Sonstige" => GradeBucketKind.Other,
_ => GradeBucketKind.Exams,
};
}
public ObservableCollection<StudentExamRow> ExamRows { get; } = [];
/// Eigener Verlauf (Nutzer-Feedback): dieselbe Balken-Sparkline wie die bestehende
/// Notenentwicklung im Schülerdetail (2.5), hier nur auf die Klausuren dieses Kurses
/// beschränkt statt aller Lerngruppen — passt zum Kursdurchschnitt-Vergleich je Klausur.
public StudentGradeHistoryGroup ExamHistory { get; } = new("Klausurverlauf");
public ObservableCollection<StudentSimpleGradeRow> ParticipationRows { get; } = [];
public ObservableCollection<StudentSimpleGradeRow> OtherRows { get; } = [];
public StudentPerformanceOverviewViewModel(IGradeRepository grades, IExamRepository exams,
IExamResultRepository results, IGroupMembershipRepository memberships,
IGradingSchemeRepository schemes, GradingService grading,
Guid groupId, Guid studentId, GroupType groupType, GradingSystem gradingSystem,
string studentName, string groupLabel, string schoolYear)
{
_grades = grades; _exams = exams; _results = results; _memberships = memberships;
_schemes = schemes; _grading = grading;
_groupId = groupId; _studentId = studentId; _groupType = groupType;
_gradingSystem = gradingSystem; _schoolYear = schoolYear;
StudentName = studentName; GroupLabel = groupLabel;
_selectedPeriod = PeriodOptions[0];
Recompute();
}
partial void OnSelectedPeriodChanged(ParticipationPeriodOption value) => Recompute();
public string ModeButtonLabel => IsTeacherMode ? "Modus: Bewerter" : "Modus: Schüler";
partial void OnIsTeacherModeChanged(bool value) => OnPropertyChanged(nameof(ModeButtonLabel));
[RelayCommand]
private void ToggleMode() => IsTeacherMode = !IsTeacherMode;
private GradingScheme ResolveScheme() =>
_schemes.GetByGroup(_groupId)
?? _schemes.GetDefaultForType(_groupType)
?? new GradingScheme { ExamsPercent = 50, ParticipationPercent = 40, OtherPercent = 10 };
private void Recompute()
{
_scheme = ResolveScheme();
SchemeSummary = $"Klausuren {_scheme.ExamsPercent:0.#} % · Mitarbeit {_scheme.ParticipationPercent:0.#} % · " +
$"Sonstige {_scheme.OtherPercent:0.#} %";
var (periodFrom, periodTo) = GroupMembershipService.SchoolYearPeriod(_schoolYear, SelectedPeriod.Period switch
{
ParticipationPeriod.H1 => SchoolYearPeriodKind.H1,
ParticipationPeriod.H2 => SchoolYearPeriodKind.H2,
_ => SchoolYearPeriodKind.FullYear,
});
var membership = _memberships.GetByGroup(_groupId).FirstOrDefault(m => m.StudentId == _studentId);
// ── Klausuren ─────────────────────────────────────────────────────
ExamRows.Clear();
ExamHistory.Points.Clear();
_examGradesRaw = [];
int? previousNoteEquivalent = null;
foreach (var exam in _exams.GetByGroup(_groupId)
.Where(e => e.Date >= periodFrom && e.Date <= periodTo)
.Where(e => membership is null || GroupMembershipService.IsActiveOn(membership, e.Date))
.Where(e => !e.Niveau.HasValue || membership?.Niveau == e.Niveau)
.OrderBy(e => e.Date))
{
var allResults = _results.GetByExam(exam.Id);
var own = allResults.FirstOrDefault(r => r.StudentId == _studentId);
string? ownGrade = own is { Absent: false, Grade: not null } ? own.Grade : null;
if (ownGrade is not null) _examGradesRaw.Add((ownGrade, 1.0));
var classGrades = allResults.Where(r => !r.Absent && r.Grade is not null)
.Select(r => (r.Grade!, 1.0)).ToList();
var classAverage = classGrades.Count > 0 ? _grading.WeightedAverage(classGrades) : (double?)null;
ExamRows.Add(new StudentExamRow(exam.Title, exam.Date, ownGrade,
classAverage.HasValue ? FormatGrade(classAverage.Value) : ""));
// Gleiche Balken-Sparkline wie die bestehende Notenentwicklung (2.5): Rohwert auf die
// 1..6-Notenäquivalent-Achse abbilden, damit Grades1To6 und Points0To15 dieselbe
// Balkenhöhen-Formel nutzen können.
int? noteEquivalent = ownGrade is not null && int.TryParse(ownGrade, out var raw)
? (_gradingSystem == GradingSystem.Grades1To6 ? raw : int.Parse(PointsNoteMapping.PointsToNote(raw)))
: null;
var isFailing = noteEquivalent is >= 5;
var isDrop = noteEquivalent.HasValue && previousNoteEquivalent.HasValue
&& noteEquivalent.Value - previousNoteEquivalent.Value >= 1;
var warnings = new List<string>();
if (isDrop) warnings.Add("Abfall um ≥ 1 Note");
if (isFailing) warnings.Add("Versetzungsgefährdung");
ExamHistory.Points.Add(new GradeHistoryPoint(exam.Date, exam.Title, ownGrade ?? "",
noteEquivalent, warnings.Count > 0, string.Join(" · ", warnings)));
if (noteEquivalent.HasValue) previousNoteEquivalent = noteEquivalent;
}
_examsAverage = _examGradesRaw.Count > 0 ? _grading.WeightedAverage(_examGradesRaw) : null;
// ── Mitarbeit & Sonstige ──────────────────────────────────────────
var allGrades = _grades.GetByStudentAndGroup(_studentId, _groupId)
.Where(g => g.Date >= periodFrom && g.Date <= periodTo)
.Where(g => membership is null || GroupMembershipService.IsActiveOn(membership, g.Date))
.OrderBy(g => g.Date).ToList();
ParticipationRows.Clear();
_participationGradesRaw = [];
foreach (var g in allGrades.Where(g => g.Category == GradeCategory.Participation))
{
_participationGradesRaw.Add((g.Value, g.Weight));
ParticipationRows.Add(new StudentSimpleGradeRow(g, SaveGrade));
}
_participationAverage = _participationGradesRaw.Count > 0 ? _grading.WeightedAverage(_participationGradesRaw) : null;
OtherRows.Clear();
_otherGradesRaw = [];
foreach (var g in allGrades.Where(g => g.Category != GradeCategory.Participation))
{
_otherGradesRaw.Add((g.Value, g.Weight));
OtherRows.Add(new StudentSimpleGradeRow(g, SaveGrade));
}
_otherAverage = _otherGradesRaw.Count > 0 ? _grading.WeightedAverage(_otherGradesRaw) : null;
ExamsAverageDisplay = _examsAverage.HasValue ? FormatGrade(_examsAverage.Value) : null;
ParticipationAverageDisplay = _participationAverage.HasValue ? FormatGrade(_participationAverage.Value) : null;
OtherAverageDisplay = _otherAverage.HasValue ? FormatGrade(_otherAverage.Value) : null;
var reportGrade = _grading.CalculateReportGrade(_examGradesRaw, _participationGradesRaw, _otherGradesRaw,
_scheme, _gradingSystem, RoundingRule.Commercial);
CurrentReportGradeDisplay = reportGrade is null
? "Noch nicht berechenbar — keine Werte im gewählten Zeitraum."
: $"Note {reportGrade}";
RecomputeTarget();
RecomputeWhatIf();
}
private void SaveGrade(Grade grade)
{
_grades.Save(grade);
Recompute();
}
partial void OnTargetGradeTextChanged(string value) => RecomputeTarget();
partial void OnTargetBucketChanged(GradeBucketKind value)
{
OnPropertyChanged(nameof(TargetBucketName));
RecomputeTarget();
}
private void RecomputeTarget()
{
if (!TryParseGrade(TargetGradeText, out var target))
{
TargetResultDisplay = "";
return;
}
var result = ReportGradeTargetCalculator.SolveRequiredAverage(target, TargetBucket,
_examsAverage, _participationAverage, _otherAverage, _scheme, _gradingSystem);
TargetResultDisplay = result switch
{
null => $"{TargetBucketName} fließt laut Gewichtungsschema nicht in die Zeugnisnote ein.",
{ IsAchievable: true } r => $"Nötiger Schnitt in {TargetBucketName}: {FormatGrade(r.RequiredAverage)}",
{ IsAchievable: false } r =>
$"Mit den übrigen Bereichen rechnerisch nicht mehr erreichbar (bräuchte {FormatGrade(r.RequiredAverage)}).",
};
}
partial void OnWhatIfExamGradeTextChanged(string value) => RecomputeWhatIf();
private void RecomputeWhatIf()
{
if (string.IsNullOrWhiteSpace(WhatIfExamGradeText))
{
WhatIfResultDisplay = "";
return;
}
var hypothetical = new List<(string Grade, double Weight)>(_examGradesRaw) { (WhatIfExamGradeText.Trim(), 1.0) };
var result = _grading.CalculateReportGrade(hypothetical, _participationGradesRaw, _otherGradesRaw,
_scheme, _gradingSystem, RoundingRule.Commercial);
WhatIfResultDisplay = result is null
? "Ungültige Note."
: $"Zeugnisnote mit dieser zusätzlichen Klausurnote: {result}";
}
private static bool TryParseGrade(string text, out double value) =>
double.TryParse(text.Trim().Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out value);
private static string FormatGrade(double value) => value.ToString("0.0", CultureInfo.InvariantCulture);
}
public class StudentExamRow(string title, DateOnly date, string? ownGrade, string classAverageDisplay)
{
public string Title { get; } = title;
public string DateDisplay { get; } = date.ToString("dd.MM.yyyy");
public string OwnGradeDisplay { get; } = ownGrade ?? "";
public string ClassAverageDisplay { get; } = classAverageDisplay;
}
public partial class StudentSimpleGradeRow : ObservableObject
{
private readonly Grade _grade;
private readonly Action<Grade> _save;
public string CategoryLabel { get; }
public string DateDisplay { get; }
[ObservableProperty] private string _value;
public StudentSimpleGradeRow(Grade grade, Action<Grade> save)
{
_grade = grade;
_save = save;
CategoryLabel = GradeCategoryDisplay.Label(grade.Category);
DateDisplay = grade.Date.ToString("dd.MM.yyyy");
_value = grade.Value;
}
[RelayCommand]
private void Save()
{
_grade.Value = Value.Trim();
_save(_grade);
}
}
@@ -19,6 +19,9 @@
<Button Content="Noten verwalten" Command="{Binding ManageStudentGradesCommand}" Margin="12,0,0,0"
IsVisible="{Binding SelectedRow, Converter={x:Static ObjectConverters.IsNotNull}}"
IsEnabled="{Binding !IsReadOnly}"/>
<Button Content="Überblick" Command="{Binding ShowPerformanceOverviewCommand}" Margin="6,0,0,0"
IsVisible="{Binding SelectedRow, Converter={x:Static ObjectConverters.IsNotNull}}"
ToolTip.Tip="Leistungsüberblick mit Zielnoten-Rechner für diesen Schüler"/>
<Button Content=" Sammelnote" Command="{Binding CollectiveGradeCommand}" IsEnabled="{Binding !IsReadOnly}"/>
<Button Content="Zeugnisnoten" Command="{Binding ReportGradesCommand}" IsEnabled="{Binding !IsReadOnly}"/>
<Button Content="Als PDF" Click="OnExportPdfClick" Margin="12,0,0,0"
@@ -25,6 +25,7 @@ public partial class GradeOverviewTabView : UserControl
vm.OnManageStudentGrades = ShowStudentGradesDialog;
vm.OnCollectiveGrade = ShowCollectiveGradeDialog;
vm.OnReportGrades = ShowReportGradesDialog;
vm.OnShowPerformanceOverview = ShowPerformanceOverviewDialog;
vm.PropertyChanged += (_, pe) =>
{
if (pe.PropertyName == nameof(GradeOverviewTabViewModel.RebuildColumnsSignal))
@@ -79,6 +80,22 @@ public partial class GradeOverviewTabView : UserControl
if (owner is not null) await dialog.ShowDialog(owner);
}
private async Task ShowPerformanceOverviewDialog(GradeOverviewRow row)
{
var dialogVm = new StudentPerformanceOverviewViewModel(
App.Services.GetRequiredService<IGradeRepository>(),
App.Services.GetRequiredService<IExamRepository>(),
App.Services.GetRequiredService<IExamResultRepository>(),
App.Services.GetRequiredService<IGroupMembershipRepository>(),
App.Services.GetRequiredService<IGradingSchemeRepository>(),
App.Services.GetRequiredService<GradingService>(),
_vm!.GroupId, row.StudentId, _vm.GroupType, _vm.GradingSystem, row.Name, _vm.GroupLabel, _vm.SchoolYear);
var dialog = new StudentPerformanceOverviewDialog { DataContext = dialogVm };
var owner = TopLevel.GetTopLevel(this) as Window;
if (owner is not null) await dialog.ShowDialog(owner);
}
private async void OnExportPdfClick(object? sender, RoutedEventArgs e)
{
var topLevel = TopLevel.GetTopLevel(this);
@@ -12,8 +12,12 @@
<!-- Schülername + Fortschritt -->
<Grid Grid.Row="0" ColumnDefinitions="*,Auto" Margin="0,0,0,16">
<StackPanel Grid.Column="0">
<TextBlock Text="{Binding StudentName}" FontSize="22" FontWeight="SemiBold"
Opacity="{Binding CurrentStudentContentOpacity}"/>
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock Text="{Binding StudentName}" FontSize="22" FontWeight="SemiBold"
Opacity="{Binding CurrentStudentContentOpacity}"/>
<TextBlock Text="{Binding CurrentStudentDayHighlightSymbol}" FontSize="20"
VerticalAlignment="Center" ToolTip.Tip="{Binding CurrentStudentDayHighlightLabel}"/>
</StackPanel>
<TextBlock Text="{Binding CurrentAspectLabel}" FontSize="13" Opacity="0.5"
IsVisible="{Binding !CurrentStudentIsAbsent}"/>
<TextBlock Text="{Binding CurrentStudentAttendanceLabel, StringFormat='⚠ Abwesend — {0}'}"
@@ -1,6 +1,7 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using LehrerApp.Core.Models;
using LehrerApp.Desktop.ViewModels.Groups;
namespace LehrerApp.Desktop.Views.Groups;
@@ -19,6 +20,18 @@ public partial class ParticipationQuickInputDialog : Window
{
if (DataContext is not QuickInputViewModel vm) { base.OnKeyDown(e); return; }
if (e.KeyModifiers.HasFlag(KeyModifiers.Shift))
{
switch (e.Key)
{
case Key.D1 or Key.NumPad1: vm.SetDayHighlight(DayHighlightKind.Standout); e.Handled = true; break;
case Key.D2 or Key.NumPad2: vm.SetDayHighlight(DayHighlightKind.Sleepy); e.Handled = true; break;
case Key.D3 or Key.NumPad3: vm.SetDayHighlight(DayHighlightKind.Rough); e.Handled = true; break;
case Key.D0 or Key.NumPad0: vm.SetDayHighlight(null); e.Handled = true; break;
}
if (e.Handled) return;
}
switch (e.Key)
{
case Key.D0 or Key.NumPad0: vm.SetRatingByNumber(0); e.Handled = true; break;
@@ -128,10 +128,30 @@
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<StackPanel Spacing="7">
<TextBlock Text="TAGESFLAGGE" FontSize="11" FontWeight="Bold" Opacity="0.5"/>
<TextBlock Text="{Binding DayHighlightLabel}" FontSize="12"/>
<ItemsControl ItemsSource="{Binding DayHighlightChoices}">
<ItemsControl.ItemsPanel><ItemsPanelTemplate><WrapPanel/></ItemsPanelTemplate></ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:SeatDayHighlightChoice">
<Button Classes="choice" Classes.selected="{Binding IsSelected}"
Command="{Binding ApplyCommand}" Margin="2">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock Text="{Binding Symbol}" FontWeight="Bold"/>
<TextBlock Text="{Binding Label}"/>
<TextBlock Text="{Binding Shortcut}" FontSize="9" Opacity="0.5" VerticalAlignment="Center"/>
</StackPanel>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</StackPanel>
</ScrollViewer>
<TextBlock Grid.Row="2" Margin="0,12,0,0" FontSize="10" Opacity="0.55" TextWrapping="Wrap"
Text="Mitarbeit: Q/W/E/R/T Aspekt · 15 Bewertung · +/ anpassen · Backspace löschen · ←/→ Aspekt | Anwesenheit: Strg+1/2/5/7/9/0, Strg+X | Hausaufgaben: ⌥+1/3/4/5/7/8/0, ⌥+X | Esc schließen"/>
Text="Mitarbeit: Q/W/E/R/T Aspekt · 15 Bewertung · +/ anpassen · Backspace löschen · ←/→ Aspekt | Anwesenheit: Strg+1/2/5/7/9/0, Strg+X | Hausaufgaben: ⌥+1/3/4/5/7/8/0, ⌥+X | Tagesflagge: ⇧1/2/3, ⇧X | Esc schließen"/>
</Grid>
</Window>
@@ -36,6 +36,12 @@ public partial class SeatAssessmentDialog : Window
e.Handled = clear || digit is not null;
return;
}
if (e.KeyModifiers.HasFlag(KeyModifiers.Shift))
{
if (clear || digit is not null) vm.ApplyDayHighlightShortcut(digit, clear);
e.Handled = clear || digit is not null;
return;
}
if (digit is not null)
{
vm.SetRatingByNumber(digit.Value);
@@ -154,6 +154,9 @@
<TextBlock Text="{Binding HomeworkBadge}" Foreground="White" FontSize="10"
ToolTip.Tip="Hausaufgabe"/>
</Border>
<TextBlock Text="{Binding DayHighlightBadge}" FontSize="14"
IsVisible="{Binding HasDayHighlightBadge}"
ToolTip.Tip="Tagesflagge"/>
</StackPanel>
<!-- Strichliste Meldungen (Nutzer-Feedback): schnelles Mitzählen ohne den
vollen Bewertungsdialog zu öffnen -->
@@ -0,0 +1,184 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:LehrerApp.Desktop.ViewModels.Groups"
xmlns:students="clr-namespace:LehrerApp.Desktop.ViewModels.Students"
x:Class="LehrerApp.Desktop.Views.Groups.StudentPerformanceOverviewDialog"
x:DataType="vm:StudentPerformanceOverviewViewModel"
Title="Leistungsüberblick"
Width="920" Height="720" MinWidth="700" MinHeight="520"
CanResize="True" WindowStartupLocation="CenterOwner">
<Grid RowDefinitions="Auto,Auto,*,Auto" Margin="24">
<Grid Grid.Row="0" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" Spacing="2">
<TextBlock Text="{Binding StudentName}" Classes="dialogtitle"/>
<TextBlock Text="{Binding GroupLabel}" FontSize="12" Opacity="0.6"/>
</StackPanel>
<Button Grid.Column="1" Content="{Binding ModeButtonLabel}"
Command="{Binding ToggleModeCommand}" VerticalAlignment="Top"
ToolTip.Tip="Zwischen Bewerter- und Schülermodus umschalten"/>
</Grid>
<StackPanel Grid.Row="1" Orientation="Horizontal" Spacing="12" Margin="0,12,0,10">
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock Text="Zeitraum:" VerticalAlignment="Center"/>
<ComboBox ItemsSource="{Binding PeriodOptions}" SelectedItem="{Binding SelectedPeriod}" MinWidth="170"/>
</StackPanel>
<TextBlock Text="{Binding SchemeSummary}" FontSize="12" Opacity="0.6" VerticalAlignment="Center"
IsVisible="{Binding IsTeacherMode}"/>
</StackPanel>
<ScrollViewer Grid.Row="2">
<Grid ColumnDefinitions="*,300">
<StackPanel Grid.Column="0" Spacing="18" Margin="0,0,16,0">
<StackPanel Spacing="6">
<Grid ColumnDefinitions="*,Auto">
<TextBlock Grid.Column="0" Text="KLAUSUREN" FontSize="11" FontWeight="Bold" Opacity="0.5"/>
<TextBlock Grid.Column="1" Text="{Binding ExamsAverageDisplay, StringFormat='Ø {0}'}"
FontSize="12" Opacity="0.7" IsVisible="{Binding ExamsAverageDisplay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
</Grid>
<ItemsControl ItemsSource="{Binding ExamRows}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:StudentExamRow">
<Grid ColumnDefinitions="Auto,*,80,80" Margin="0,4">
<TextBlock Grid.Column="0" Text="{Binding DateDisplay}" FontSize="12" Opacity="0.6" Width="80"/>
<TextBlock Grid.Column="1" Text="{Binding Title}" FontSize="13"/>
<TextBlock Grid.Column="2" Text="{Binding OwnGradeDisplay}" FontWeight="SemiBold"
HorizontalAlignment="Right"/>
<TextBlock Grid.Column="3" Text="{Binding ClassAverageDisplay}" Opacity="0.55"
HorizontalAlignment="Right"
IsVisible="{Binding $parent[ItemsControl].((vm:StudentPerformanceOverviewViewModel)DataContext).IsTeacherMode}"
ToolTip.Tip="Kursdurchschnitt dieser Klausur"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="Keine Klausuren im gewählten Zeitraum." Classes="emptyhint"
IsVisible="{Binding !ExamRows.Count}"/>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled"
IsVisible="{Binding !!ExamHistory.Points.Count}">
<ItemsControl ItemsSource="{Binding ExamHistory.Points}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate><StackPanel Orientation="Horizontal" Spacing="10"/></ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="students:GradeHistoryPoint">
<StackPanel Width="46" VerticalAlignment="Bottom" ToolTip.Tip="{Binding TooltipText}">
<Border Classes="historybar" Height="{Binding BarHeight}" Width="16" CornerRadius="2"
HorizontalAlignment="Center" VerticalAlignment="Bottom"
Classes.warning="{Binding IsWarning}"/>
<TextBlock Text="{Binding Value}" FontSize="11" FontWeight="SemiBold"
HorizontalAlignment="Center" Margin="0,3,0,0"/>
<TextBlock Text="{Binding DateDisplay}" FontSize="9" Opacity="0.5"
HorizontalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Styles>
<Style Selector="Border.historybar">
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundAccentBrush}"/>
</Style>
<Style Selector="Border.historybar.warning">
<Setter Property="Background" Value="#E53935"/>
</Style>
</ItemsControl.Styles>
</ItemsControl>
</ScrollViewer>
</StackPanel>
<StackPanel Spacing="6">
<Grid ColumnDefinitions="*,Auto">
<TextBlock Grid.Column="0" Text="MITARBEIT" FontSize="11" FontWeight="Bold" Opacity="0.5"/>
<TextBlock Grid.Column="1" Text="{Binding ParticipationAverageDisplay, StringFormat='Ø {0}'}"
FontSize="12" Opacity="0.7" IsVisible="{Binding ParticipationAverageDisplay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
</Grid>
<ItemsControl ItemsSource="{Binding ParticipationRows}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:StudentSimpleGradeRow">
<Grid ColumnDefinitions="Auto,*,70,Auto" Margin="0,4">
<TextBlock Grid.Column="0" Text="{Binding DateDisplay}" FontSize="12" Opacity="0.6" Width="80"/>
<TextBlock Grid.Column="1" Text="{Binding CategoryLabel}" FontSize="13"/>
<TextBox Grid.Column="2" Text="{Binding Value}"
IsVisible="{Binding $parent[ItemsControl].((vm:StudentPerformanceOverviewViewModel)DataContext).IsTeacherMode}"/>
<TextBlock Grid.Column="2" Text="{Binding Value}" FontWeight="SemiBold" HorizontalAlignment="Right"
IsVisible="{Binding !$parent[ItemsControl].((vm:StudentPerformanceOverviewViewModel)DataContext).IsTeacherMode}"/>
<Button Grid.Column="3" Content="Speichern" Command="{Binding SaveCommand}" FontSize="11" Padding="8,3" Margin="6,0,0,0"
IsVisible="{Binding $parent[ItemsControl].((vm:StudentPerformanceOverviewViewModel)DataContext).IsTeacherMode}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="Keine Mitarbeitsnote im gewählten Zeitraum." Classes="emptyhint"
IsVisible="{Binding !ParticipationRows.Count}"/>
</StackPanel>
<StackPanel Spacing="6">
<Grid ColumnDefinitions="*,Auto">
<TextBlock Grid.Column="0" Text="SONSTIGE" FontSize="11" FontWeight="Bold" Opacity="0.5"/>
<TextBlock Grid.Column="1" Text="{Binding OtherAverageDisplay, StringFormat='Ø {0}'}"
FontSize="12" Opacity="0.7" IsVisible="{Binding OtherAverageDisplay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
</Grid>
<ItemsControl ItemsSource="{Binding OtherRows}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:StudentSimpleGradeRow">
<Grid ColumnDefinitions="Auto,*,70,Auto" Margin="0,4">
<TextBlock Grid.Column="0" Text="{Binding DateDisplay}" FontSize="12" Opacity="0.6" Width="80"/>
<TextBlock Grid.Column="1" Text="{Binding CategoryLabel}" FontSize="13"/>
<TextBox Grid.Column="2" Text="{Binding Value}"
IsVisible="{Binding $parent[ItemsControl].((vm:StudentPerformanceOverviewViewModel)DataContext).IsTeacherMode}"/>
<TextBlock Grid.Column="2" Text="{Binding Value}" FontWeight="SemiBold" HorizontalAlignment="Right"
IsVisible="{Binding !$parent[ItemsControl].((vm:StudentPerformanceOverviewViewModel)DataContext).IsTeacherMode}"/>
<Button Grid.Column="3" Content="Speichern" Command="{Binding SaveCommand}" FontSize="11" Padding="8,3" Margin="6,0,0,0"
IsVisible="{Binding $parent[ItemsControl].((vm:StudentPerformanceOverviewViewModel)DataContext).IsTeacherMode}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="Keine sonstigen Noten im gewählten Zeitraum." Classes="emptyhint"
IsVisible="{Binding !OtherRows.Count}"/>
</StackPanel>
<Border Background="{DynamicResource SystemControlBackgroundAltHighBrush}" CornerRadius="8" Padding="14">
<StackPanel Spacing="2">
<TextBlock Text="ZEUGNISNOTE (BERECHNET)" FontSize="11" FontWeight="Bold" Opacity="0.5"/>
<TextBlock Text="{Binding CurrentReportGradeDisplay}" FontSize="18" FontWeight="SemiBold"/>
</StackPanel>
</Border>
</StackPanel>
<StackPanel Grid.Column="1" Spacing="16">
<Border Background="{DynamicResource SystemControlBackgroundAltHighBrush}" CornerRadius="8" Padding="14">
<StackPanel Spacing="8">
<TextBlock Text="ZIELNOTE: WAS BRAUCHE ICH NOCH?" FontSize="11" FontWeight="Bold" Opacity="0.5"/>
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock Text="Ziel:" VerticalAlignment="Center"/>
<TextBox Text="{Binding TargetGradeText}" Width="70" PlaceholderText="z.B. 2"/>
<TextBlock Text="in" VerticalAlignment="Center"/>
<ComboBox ItemsSource="{Binding TargetBucketOptions}" SelectedItem="{Binding TargetBucketName}" MinWidth="110"/>
</StackPanel>
<TextBlock Text="{Binding TargetResultDisplay}" TextWrapping="Wrap" FontSize="13"/>
</StackPanel>
</Border>
<Border Background="{DynamicResource SystemControlBackgroundAltHighBrush}" CornerRadius="8" Padding="14">
<StackPanel Spacing="8">
<TextBlock Text="WAS-WÄRE-WENN" FontSize="11" FontWeight="Bold" Opacity="0.5"/>
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock Text="Zusätzliche Klausurnote:" VerticalAlignment="Center" TextWrapping="Wrap"/>
<TextBox Text="{Binding WhatIfExamGradeText}" Width="70" PlaceholderText="z.B. 3"/>
</StackPanel>
<TextBlock Text="{Binding WhatIfResultDisplay}" TextWrapping="Wrap" FontSize="13"/>
</StackPanel>
</Border>
</StackPanel>
</Grid>
</ScrollViewer>
<Grid Grid.Row="3" ColumnDefinitions="*,Auto" Margin="0,16,0,0">
<Button Grid.Column="1" Content="Schließen" Click="OnClose"/>
</Grid>
</Grid>
</Window>
@@ -0,0 +1,11 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
namespace LehrerApp.Desktop.Views.Groups;
public partial class StudentPerformanceOverviewDialog : Window
{
public StudentPerformanceOverviewDialog() => InitializeComponent();
private void OnClose(object? sender, RoutedEventArgs e) => Close();
}