From 3fc506adc17b6dbdbea46780d3428c1a6db41094 Mon Sep 17 00:00:00 2001 From: Sebastian Hedtrich Date: Wed, 12 Aug 2026 00:34:43 +0200 Subject: [PATCH] =?UTF-8?q?Notenschl=C3=BCssel-Editor=20(1.3)=20und=20Eins?= =?UTF-8?q?tellungen=20in=20Tabs=20strukturiert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Notenschlüssel-Editor im ExamDialog: Stufen (Note/Prozentgrenze) bearbeitbar, Vorbelegung passend zum GradingSystem der Gruppe, Live-Anzeige der absoluten Punktegrenze je Stufe, Validierung (lückenlos, keine Dopplungen) über GradingService.ValidateGradingKey. - Neues Modell GradingKeyTemplate + Repository: Notenschlüssel als Vorlage speichern/anwenden, direkt aus dem ExamDialog heraus. - Einstellungen: Fächer / Kompetenzen / Notenschlüssel-Vorlagen sind jetzt eigene Tabs statt einer gemeinsam wachsenden Liste. Vorlagen zeigen nur noch eine kompakte Zeile; die Stufen-Bearbeitung läuft über ein Popup-Fenster (GradingKeyTemplateDialog). Co-Authored-By: Claude Sonnet 5 --- LehrerApp.Core/Interfaces/IRepositories.cs | 8 + LehrerApp.Core/Models/Exam.cs | 8 + LehrerApp.Core/Services/GradingService.cs | 15 + LehrerApp.Data/LiteDbContext.cs | 2 + .../Repositories/AllRepositories.cs | 11 + LehrerApp.Desktop/AppBootstrapper.cs | 1 + .../ViewModels/Groups/ExamViewModels.cs | 125 ++++++- .../ViewModels/Settings/SettingsViewModel.cs | 139 ++++++- .../Views/Groups/ExamDialog.axaml | 46 +++ .../Views/Groups/GroupDetailView.axaml.cs | 3 + .../Settings/GradingKeyTemplateDialog.axaml | 46 +++ .../GradingKeyTemplateDialog.axaml.cs | 11 + .../Views/Settings/SettingsView.axaml | 346 +++++++++++------- .../Views/Settings/SettingsView.axaml.cs | 8 + TODO.md | 10 +- 15 files changed, 631 insertions(+), 148 deletions(-) create mode 100644 LehrerApp.Desktop/Views/Settings/GradingKeyTemplateDialog.axaml create mode 100644 LehrerApp.Desktop/Views/Settings/GradingKeyTemplateDialog.axaml.cs diff --git a/LehrerApp.Core/Interfaces/IRepositories.cs b/LehrerApp.Core/Interfaces/IRepositories.cs index ca88deb..5c7f886 100644 --- a/LehrerApp.Core/Interfaces/IRepositories.cs +++ b/LehrerApp.Core/Interfaces/IRepositories.cs @@ -41,6 +41,14 @@ public interface IExamResultRepository void Save(ExamResult result); void SaveMany(List results); } +public interface IGradingKeyTemplateRepository +{ + List GetAll(); + List GetByGradingSystem(GradingSystem system); + GradingKeyTemplate? GetById(Guid id); + void Save(GradingKeyTemplate template); + void Delete(Guid id); +} public interface IGradeRepository { List GetByStudentAndGroup(Guid studentId, Guid groupId); diff --git a/LehrerApp.Core/Models/Exam.cs b/LehrerApp.Core/Models/Exam.cs index 918d643..4b3f688 100644 --- a/LehrerApp.Core/Models/Exam.cs +++ b/LehrerApp.Core/Models/Exam.cs @@ -29,6 +29,14 @@ public class GradingKeyEntry public string Grade { get; set; } = ""; public double MinPercent { get; set; } } +public class GradingKeyTemplate +{ + public Guid Id { get; set; } = Guid.NewGuid(); + public string Name { get; set; } = ""; + public GradingSystem GradingSystem { get; set; } + public List Entries { get; set; } = []; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} public class ExamResult { public Guid Id { get; set; } = Guid.NewGuid(); diff --git a/LehrerApp.Core/Services/GradingService.cs b/LehrerApp.Core/Services/GradingService.cs index 1b8377f..f0e29a9 100644 --- a/LehrerApp.Core/Services/GradingService.cs +++ b/LehrerApp.Core/Services/GradingService.cs @@ -40,6 +40,21 @@ public class GradingService new() { Grade = "1", MinPercent = 20.0 }, new() { Grade = "0", MinPercent = 0.0 }, ]; + public string? ValidateGradingKey(List entries) + { + if (entries.Count < 2) return "Der Notenschlüssel braucht mindestens zwei Stufen."; + if (entries.Any(e => string.IsNullOrWhiteSpace(e.Grade))) + return "Jede Stufe braucht eine Bezeichnung."; + if (entries.Any(e => e.MinPercent < 0 || e.MinPercent > 100)) + return "Prozentgrenzen müssen zwischen 0 und 100 liegen."; + var sorted = entries.OrderByDescending(e => e.MinPercent).ToList(); + if (sorted.Select(e => e.MinPercent).Distinct().Count() != sorted.Count) + return "Prozentgrenzen dürfen sich nicht doppeln."; + if (sorted.Last().MinPercent != 0) + return "Die unterste Stufe muss bei 0 % liegen (lückenlose Abdeckung)."; + return null; + } + public double WeightedAverage(List<(string Grade, double Weight)> grades) { var numeric = grades diff --git a/LehrerApp.Data/LiteDbContext.cs b/LehrerApp.Data/LiteDbContext.cs index 2fe3c9b..0d795de 100644 --- a/LehrerApp.Data/LiteDbContext.cs +++ b/LehrerApp.Data/LiteDbContext.cs @@ -26,6 +26,7 @@ public class LiteDbContext : IDisposable public ILiteCollection Exams => _db.GetCollection("exams"); public ILiteCollection ExamResults => _db.GetCollection("exam_results"); public ILiteCollection Grades => _db.GetCollection("grades"); + public ILiteCollection GradingKeyTemplates => _db.GetCollection("grading_key_templates"); public ILiteCollection Units => _db.GetCollection("units"); public ILiteCollection Lessons => _db.GetCollection("lessons"); public ILiteCollection Documentation => _db.GetCollection("documentation"); @@ -67,6 +68,7 @@ public class LiteDbContext : IDisposable ExamResults.EnsureIndex(x => x.StudentId); Grades.EnsureIndex(x => x.StudentId); Grades.EnsureIndex(x => x.GroupId); + GradingKeyTemplates.EnsureIndex(x => x.GradingSystem); Units.EnsureIndex(x => x.GroupId); Lessons.EnsureIndex(x => x.UnitId); Lessons.EnsureIndex(x => x.GroupId); diff --git a/LehrerApp.Data/Repositories/AllRepositories.cs b/LehrerApp.Data/Repositories/AllRepositories.cs index fcd7f39..15f0f80 100644 --- a/LehrerApp.Data/Repositories/AllRepositories.cs +++ b/LehrerApp.Data/Repositories/AllRepositories.cs @@ -114,6 +114,17 @@ public class ExamResultRepository(LiteDbContext db) : IExamResultRepository } } +public class GradingKeyTemplateRepository(LiteDbContext db) : IGradingKeyTemplateRepository +{ + public List GetAll() => + db.GradingKeyTemplates.FindAll().OrderBy(t => t.Name).ToList(); + public List GetByGradingSystem(GradingSystem system) => + db.GradingKeyTemplates.Find(t => t.GradingSystem == system).OrderBy(t => t.Name).ToList(); + public GradingKeyTemplate? GetById(Guid id) => db.GradingKeyTemplates.FindById(id); + public void Save(GradingKeyTemplate t) { t.UpdatedAt = DateTime.UtcNow; db.GradingKeyTemplates.Upsert(t); } + public void Delete(Guid id) => db.GradingKeyTemplates.Delete(id); +} + public class GradeRepository(LiteDbContext db) : IGradeRepository { public List GetByStudentAndGroup(Guid sid, Guid gid) => diff --git a/LehrerApp.Desktop/AppBootstrapper.cs b/LehrerApp.Desktop/AppBootstrapper.cs index d9e587f..afe9418 100644 --- a/LehrerApp.Desktop/AppBootstrapper.cs +++ b/LehrerApp.Desktop/AppBootstrapper.cs @@ -48,6 +48,7 @@ public static class AppBootstrapper services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/LehrerApp.Desktop/ViewModels/Groups/ExamViewModels.cs b/LehrerApp.Desktop/ViewModels/Groups/ExamViewModels.cs index 2fc06ea..3d17d2a 100644 --- a/LehrerApp.Desktop/ViewModels/Groups/ExamViewModels.cs +++ b/LehrerApp.Desktop/ViewModels/Groups/ExamViewModels.cs @@ -14,11 +14,13 @@ public partial class ExamDialogViewModel : ObservableObject { private readonly IExamRepository _exams; private readonly ICompetencyDomainRepository _competencyDomains; + private readonly IGradingKeyTemplateRepository _gradingKeyTemplates; + private readonly GradingService _grading; private readonly Guid _groupId; private readonly Guid? _subjectId; private readonly int _gradeLevel; + private readonly GradingSystem _gradingSystem; private readonly Exam? _editingExam; - private readonly List _gradingKey; private readonly string _subjectName; [ObservableProperty] private string _title = ""; @@ -30,12 +32,17 @@ public partial class ExamDialogViewModel : ObservableObject [ObservableProperty] private double _totalPoints; [ObservableProperty] private bool _hasCompetencyCatalog; [ObservableProperty] private bool _useWeighting; + [ObservableProperty] private GradingKeyTemplate? _selectedTemplate; + [ObservableProperty] private string _newTemplateName = ""; + [ObservableProperty] private string _gradingKeyValidation = ""; public bool TotalPointsWarning => TotalPoints <= 0; public string TotalPointsDisplay => $"{TotalPoints.ToString("0.##", CultureInfo.InvariantCulture)} Punkte gesamt"; public ObservableCollection Tasks { get; } = []; + public ObservableCollection GradingKeyEntries { get; } = []; + public ObservableCollection AvailableTemplates { get; } = []; public Exam? Result { get; private set; } public string DialogTitle => _editingExam is null ? "Neue Klausur anlegen" : "Klausur bearbeiten"; @@ -46,17 +53,23 @@ public partial class ExamDialogViewModel : ObservableObject ? "Kein Fach hinterlegt (siehe Lerngruppe)" : $"Fach: {_subjectName}"; public ExamDialogViewModel(IExamRepository exams, ICompetencyDomainRepository competencyDomains, + IGradingKeyTemplateRepository gradingKeyTemplates, GradingService grading, Guid groupId, Guid? subjectId, int gradeLevel, GradingSystem gradingSystem, string defaultSubjectName, Exam? editingExam, Exam? duplicateSource) { _exams = exams; _competencyDomains = competencyDomains; + _gradingKeyTemplates = gradingKeyTemplates; _grading = grading; _groupId = groupId; _subjectId = subjectId; _gradeLevel = gradeLevel; + _gradingSystem = gradingSystem; _editingExam = editingExam; _subjectName = defaultSubjectName; HasCompetencyCatalog = subjectId.HasValue && _competencyDomains.GetBySubjectAndGrade(subjectId.Value, gradeLevel).Count > 0; + foreach (var t in _gradingKeyTemplates.GetByGradingSystem(gradingSystem)) + AvailableTemplates.Add(t); + var source = editingExam ?? duplicateSource; if (source is not null) { @@ -68,16 +81,18 @@ public partial class ExamDialogViewModel : ObservableObject ExamNumber = source.ExamNumber; Notes = source.Notes ?? ""; ReturnedAtText = isDuplicate ? "" : source.ReturnedAt?.ToString("dd.MM.yyyy") ?? ""; - _gradingKey = source.GradingKey - .Select(k => new GradingKeyEntry { Grade = k.Grade, MinPercent = k.MinPercent }).ToList(); foreach (var t in source.Tasks.OrderBy(t => t.Nr)) AddTaskInternal(t.Title, t.MaxPoints, t.Weight, [.. t.CompetencyCodes]); UseWeighting = Tasks.Any(t => Math.Abs(t.Weight - 1.0) > 0.0001); + foreach (var e in source.GradingKey.OrderByDescending(e => e.MinPercent)) + AddGradingKeyRowInternal(e.Grade, e.MinPercent); } else { - _gradingKey = gradingSystem == GradingSystem.Grades1To6 + var defaults = gradingSystem == GradingSystem.Grades1To6 ? GradingService.DefaultKey1To6() : GradingService.DefaultKey0To15(); + foreach (var e in defaults) + AddGradingKeyRowInternal(e.Grade, e.MinPercent); } foreach (var t in Tasks) t.ShowWeight = UseWeighting; RecomputeTotals(); @@ -158,8 +173,86 @@ public partial class ExamDialogViewModel : ObservableObject TotalPoints = Tasks.Sum(t => t.MaxPoints); OnPropertyChanged(nameof(TotalPointsWarning)); OnPropertyChanged(nameof(TotalPointsDisplay)); + RecomputeGradingKeyAbsolutes(); } + // ── Notenschlüssel (1.3) ────────────────────────────────────────────────── + + private void RecomputeGradingKeyAbsolutes() + { + foreach (var e in GradingKeyEntries) + e.AbsolutePointsDisplay = TotalPoints <= 0 + ? "–" + : $"ab {(e.MinPercent / 100.0 * TotalPoints).ToString("0.##", CultureInfo.InvariantCulture)} " + + $"von {TotalPoints.ToString("0.##", CultureInfo.InvariantCulture)} Punkten"; + } + + [RelayCommand] + private void AddGradingKeyRow() + { + AddGradingKeyRowInternal("", 0); + RecomputeGradingKeyAbsolutes(); + } + + private void AddGradingKeyRowInternal(string grade, double minPercent) + { + var item = new GradingKeyEntryEditItem + { + Grade = grade, + MinPercent = minPercent, + OnChanged = RecomputeGradingKeyAbsolutes, + OnRemove = RemoveGradingKeyRow, + }; + GradingKeyEntries.Add(item); + } + + private void RemoveGradingKeyRow(GradingKeyEntryEditItem item) + { + GradingKeyEntries.Remove(item); + RecomputeGradingKeyAbsolutes(); + } + + [RelayCommand(CanExecute = nameof(HasSelectedTemplate))] + private void ApplyTemplate() + { + if (SelectedTemplate is null) return; + GradingKeyEntries.Clear(); + foreach (var e in SelectedTemplate.Entries.OrderByDescending(x => x.MinPercent)) + AddGradingKeyRowInternal(e.Grade, e.MinPercent); + RecomputeGradingKeyAbsolutes(); + GradingKeyValidation = ""; + } + + private bool HasSelectedTemplate() => SelectedTemplate is not null; + + partial void OnSelectedTemplateChanged(GradingKeyTemplate? value) => ApplyTemplateCommand.NotifyCanExecuteChanged(); + + [RelayCommand] + private void SaveAsTemplate() + { + if (string.IsNullOrWhiteSpace(NewTemplateName)) { GradingKeyValidation = "Vorlagenname erforderlich."; return; } + + var entries = BuildGradingKeyEntries(); + var error = _grading.ValidateGradingKey(entries); + if (error is not null) { GradingKeyValidation = error; return; } + + var template = new GradingKeyTemplate + { + Name = NewTemplateName.Trim(), + GradingSystem = _gradingSystem, + Entries = entries, + }; + _gradingKeyTemplates.Save(template); + AvailableTemplates.Add(template); + NewTemplateName = ""; + GradingKeyValidation = ""; + } + + private List BuildGradingKeyEntries() => GradingKeyEntries + .Select(e => new GradingKeyEntry { Grade = e.Grade.Trim(), MinPercent = e.MinPercent }) + .OrderByDescending(e => e.MinPercent) + .ToList(); + [RelayCommand] private void Save() { @@ -180,6 +273,11 @@ public partial class ExamDialogViewModel : ObservableObject returnedAt = r; } + var gradingKey = BuildGradingKeyEntries(); + var gradingKeyError = _grading.ValidateGradingKey(gradingKey); + if (gradingKeyError is not null) { GradingKeyValidation = gradingKeyError; return; } + GradingKeyValidation = ""; + Result = _editingExam ?? new Exam { GroupId = _groupId }; Result.Title = Title.Trim(); Result.Date = date; @@ -188,11 +286,28 @@ public partial class ExamDialogViewModel : ObservableObject Result.Notes = string.IsNullOrWhiteSpace(Notes) ? null : Notes.Trim(); Result.ReturnedAt = returnedAt; Result.Tasks = Tasks.Select(t => t.ToModel()).ToList(); - Result.GradingKey = _gradingKey; + Result.GradingKey = gradingKey; _exams.Save(Result); } } +// ── Zeile im Notenschlüssel-Editor (1.3) ────────────────────────────────────── + +public partial class GradingKeyEntryEditItem : ObservableObject +{ + [ObservableProperty] private string _grade = ""; + [ObservableProperty] private double _minPercent; + [ObservableProperty] private string _absolutePointsDisplay = ""; + + public Action? OnChanged { get; set; } + public Action? OnRemove { get; set; } + + partial void OnMinPercentChanged(double value) => OnChanged?.Invoke(); + partial void OnGradeChanged(string value) => OnChanged?.Invoke(); + + [RelayCommand] private void Remove() => OnRemove?.Invoke(this); +} + // ── Zeile im Aufgaben-Editor (1.2.1 / 1.2.2 / 1.2.4) ───────────────────────── public partial class ExamTaskEditItem : ObservableObject diff --git a/LehrerApp.Desktop/ViewModels/Settings/SettingsViewModel.cs b/LehrerApp.Desktop/ViewModels/Settings/SettingsViewModel.cs index ae8a2fd..059e54f 100644 --- a/LehrerApp.Desktop/ViewModels/Settings/SettingsViewModel.cs +++ b/LehrerApp.Desktop/ViewModels/Settings/SettingsViewModel.cs @@ -2,6 +2,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using LehrerApp.Core.Interfaces; using LehrerApp.Core.Models; +using LehrerApp.Core.Services; using System.Collections.ObjectModel; using System.Text.Json; using System.Text.Json.Serialization; @@ -14,6 +15,8 @@ public partial class SettingsViewModel : ObservableObject { private readonly ISubjectRepository _subjects; private readonly ICompetencyDomainRepository _domainRepo; + private readonly IGradingKeyTemplateRepository _gradingKeyTemplates; + private readonly GradingService _grading; // ── Fächer ──────────────────────────────────────────────────────────────── @@ -33,13 +36,65 @@ public partial class SettingsViewModel : ObservableObject public ObservableCollection Domains { get; } = []; + // ── Notenschlüssel-Vorlagen (1.3.2) ────────────────────────────────────── + + [ObservableProperty] private string _newTemplateName = ""; + [ObservableProperty] private string _newTemplateGradingSystemName = "Noten 1–6"; + [ObservableProperty] private string _templateValidationMessage = ""; + + public List GradingSystemOptions { get; } = ["Noten 1–6", "Punkte 0–15"]; + public ObservableCollection GradingKeyTemplateList { get; } = []; + // ── Konstruktor ─────────────────────────────────────────────────────────── - public SettingsViewModel(ISubjectRepository subjects, ICompetencyDomainRepository domainRepo) + public SettingsViewModel(ISubjectRepository subjects, ICompetencyDomainRepository domainRepo, + IGradingKeyTemplateRepository gradingKeyTemplates, GradingService grading) { _subjects = subjects; _domainRepo = domainRepo; + _gradingKeyTemplates = gradingKeyTemplates; + _grading = grading; LoadSubjects(); + LoadGradingKeyTemplates(); + } + + // ── Notenschlüssel-Vorlagen: Laden / Hinzufügen / Löschen ──────────────── + + private void LoadGradingKeyTemplates() + { + GradingKeyTemplateList.Clear(); + foreach (var t in _gradingKeyTemplates.GetAll()) + GradingKeyTemplateList.Add(new GradingKeyTemplateEditItem(t, _gradingKeyTemplates, _grading)); + } + + [RelayCommand] + private void AddGradingKeyTemplate() + { + if (string.IsNullOrWhiteSpace(NewTemplateName)) { TemplateValidationMessage = "Vorlagenname erforderlich."; return; } + + var system = NewTemplateGradingSystemName == "Punkte 0–15" + ? GradingSystem.Points0To15 : GradingSystem.Grades1To6; + var defaults = system == GradingSystem.Grades1To6 + ? GradingService.DefaultKey1To6() : GradingService.DefaultKey0To15(); + + var template = new GradingKeyTemplate + { + Name = NewTemplateName.Trim(), + GradingSystem = system, + Entries = defaults.Select(e => new GradingKeyEntry { Grade = e.Grade, MinPercent = e.MinPercent }).ToList(), + }; + _gradingKeyTemplates.Save(template); + GradingKeyTemplateList.Add(new GradingKeyTemplateEditItem(template, _gradingKeyTemplates, _grading)); + NewTemplateName = ""; + TemplateValidationMessage = ""; + } + + [RelayCommand] + private void DeleteGradingKeyTemplate(GradingKeyTemplateEditItem? item) + { + if (item is null) return; + _gradingKeyTemplates.Delete(item.Id); + GradingKeyTemplateList.Remove(item); } // ── Fächer: Laden / Hinzufügen / Löschen ───────────────────────────────── @@ -253,6 +308,88 @@ public class CompetencyItemVm } } +// ── GradingKeyTemplateEditItem (1.3.2) ───────────────────────────────────────── + +public partial class GradingKeyTemplateEditItem : ObservableObject +{ + private readonly GradingKeyTemplate _template; + private readonly IGradingKeyTemplateRepository _repo; + private readonly GradingService _grading; + + public Guid Id { get; } + public string Name { get; } + public string GradingSystemLabel { get; } + + [ObservableProperty] private string _newGrade = ""; + [ObservableProperty] private double _newMinPercent; + [ObservableProperty] private string _validation = ""; + [ObservableProperty] private string _completenessWarning = ""; + + public ObservableCollection Entries { get; } = []; + + public GradingKeyTemplateEditItem(GradingKeyTemplate template, IGradingKeyTemplateRepository repo, + GradingService grading) + { + _template = template; _repo = repo; _grading = grading; + Id = template.Id; + Name = template.Name; + GradingSystemLabel = template.GradingSystem == GradingSystem.Grades1To6 + ? "Noten 1–6" : "Punkte 0–15"; + + foreach (var e in template.Entries.OrderByDescending(e => e.MinPercent)) + Entries.Add(new GradingKeyEntryVm(e, DeleteEntry)); + RecomputeCompleteness(); + } + + [RelayCommand] + private void AddEntry() + { + if (string.IsNullOrWhiteSpace(NewGrade)) { Validation = "Bezeichnung erforderlich."; return; } + if (NewMinPercent is < 0 or > 100) { Validation = "Prozentgrenze muss zwischen 0 und 100 liegen."; return; } + if (_template.Entries.Any(e => Math.Abs(e.MinPercent - NewMinPercent) < 0.0001)) + { Validation = "Diese Prozentgrenze existiert bereits."; return; } + + var entry = new GradingKeyEntry { Grade = NewGrade.Trim(), MinPercent = NewMinPercent }; + _template.Entries.Add(entry); + _template.Entries = _template.Entries.OrderByDescending(e => e.MinPercent).ToList(); + _repo.Save(_template); + + Entries.Clear(); + foreach (var e in _template.Entries) Entries.Add(new GradingKeyEntryVm(e, DeleteEntry)); + NewGrade = ""; NewMinPercent = 0; Validation = ""; + RecomputeCompleteness(); + } + + private void DeleteEntry(GradingKeyEntryVm vm) + { + _template.Entries.RemoveAll(e => e.Grade == vm.Grade && Math.Abs(e.MinPercent - vm.MinPercent) < 0.0001); + _repo.Save(_template); + Entries.Remove(vm); + RecomputeCompleteness(); + } + + private void RecomputeCompleteness() => + CompletenessWarning = _grading.ValidateGradingKey(_template.Entries) ?? ""; +} + +// ── GradingKeyEntryVm ───────────────────────────────────────────────────────── + +public class GradingKeyEntryVm +{ + public string Grade { get; } + public double MinPercent { get; } + public string Display { get; } + public IRelayCommand DeleteCommand { get; } + + public GradingKeyEntryVm(GradingKeyEntry e, Action onDelete) + { + Grade = e.Grade; + MinPercent = e.MinPercent; + Display = $"{Grade} — ab {MinPercent.ToString("0.##")} %"; + DeleteCommand = new RelayCommand(() => onDelete(this)); + } +} + // ── Hilfklassen ─────────────────────────────────────────────────────────────── public class SubjectListItem(Subject s) diff --git a/LehrerApp.Desktop/Views/Groups/ExamDialog.axaml b/LehrerApp.Desktop/Views/Groups/ExamDialog.axaml index f9da168..6ffcf14 100644 --- a/LehrerApp.Desktop/Views/Groups/ExamDialog.axaml +++ b/LehrerApp.Desktop/Views/Groups/ExamDialog.axaml @@ -1,6 +1,7 @@ + + + + + +