Notenschlüssel-Editor (1.3) und Einstellungen in Tabs strukturiert
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,14 @@ public interface IExamResultRepository
|
|||||||
void Save(ExamResult result);
|
void Save(ExamResult result);
|
||||||
void SaveMany(List<ExamResult> results);
|
void SaveMany(List<ExamResult> results);
|
||||||
}
|
}
|
||||||
|
public interface IGradingKeyTemplateRepository
|
||||||
|
{
|
||||||
|
List<GradingKeyTemplate> GetAll();
|
||||||
|
List<GradingKeyTemplate> GetByGradingSystem(GradingSystem system);
|
||||||
|
GradingKeyTemplate? GetById(Guid id);
|
||||||
|
void Save(GradingKeyTemplate template);
|
||||||
|
void Delete(Guid id);
|
||||||
|
}
|
||||||
public interface IGradeRepository
|
public interface IGradeRepository
|
||||||
{
|
{
|
||||||
List<Grade> GetByStudentAndGroup(Guid studentId, Guid groupId);
|
List<Grade> GetByStudentAndGroup(Guid studentId, Guid groupId);
|
||||||
|
|||||||
@@ -29,6 +29,14 @@ public class GradingKeyEntry
|
|||||||
public string Grade { get; set; } = "";
|
public string Grade { get; set; } = "";
|
||||||
public double MinPercent { 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<GradingKeyEntry> Entries { get; set; } = [];
|
||||||
|
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
}
|
||||||
public class ExamResult
|
public class ExamResult
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; } = Guid.NewGuid();
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|||||||
@@ -40,6 +40,21 @@ public class GradingService
|
|||||||
new() { Grade = "1", MinPercent = 20.0 },
|
new() { Grade = "1", MinPercent = 20.0 },
|
||||||
new() { Grade = "0", MinPercent = 0.0 },
|
new() { Grade = "0", MinPercent = 0.0 },
|
||||||
];
|
];
|
||||||
|
public string? ValidateGradingKey(List<GradingKeyEntry> 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)
|
public double WeightedAverage(List<(string Grade, double Weight)> grades)
|
||||||
{
|
{
|
||||||
var numeric = grades
|
var numeric = grades
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public class LiteDbContext : IDisposable
|
|||||||
public ILiteCollection<Exam> Exams => _db.GetCollection<Exam>("exams");
|
public ILiteCollection<Exam> Exams => _db.GetCollection<Exam>("exams");
|
||||||
public ILiteCollection<ExamResult> ExamResults => _db.GetCollection<ExamResult>("exam_results");
|
public ILiteCollection<ExamResult> ExamResults => _db.GetCollection<ExamResult>("exam_results");
|
||||||
public ILiteCollection<Grade> Grades => _db.GetCollection<Grade>("grades");
|
public ILiteCollection<Grade> Grades => _db.GetCollection<Grade>("grades");
|
||||||
|
public ILiteCollection<GradingKeyTemplate> GradingKeyTemplates => _db.GetCollection<GradingKeyTemplate>("grading_key_templates");
|
||||||
public ILiteCollection<Unit> Units => _db.GetCollection<Unit>("units");
|
public ILiteCollection<Unit> Units => _db.GetCollection<Unit>("units");
|
||||||
public ILiteCollection<Lesson> Lessons => _db.GetCollection<Lesson>("lessons");
|
public ILiteCollection<Lesson> Lessons => _db.GetCollection<Lesson>("lessons");
|
||||||
public ILiteCollection<Documentation> Documentation => _db.GetCollection<Documentation>("documentation");
|
public ILiteCollection<Documentation> Documentation => _db.GetCollection<Documentation>("documentation");
|
||||||
@@ -67,6 +68,7 @@ public class LiteDbContext : IDisposable
|
|||||||
ExamResults.EnsureIndex(x => x.StudentId);
|
ExamResults.EnsureIndex(x => x.StudentId);
|
||||||
Grades.EnsureIndex(x => x.StudentId);
|
Grades.EnsureIndex(x => x.StudentId);
|
||||||
Grades.EnsureIndex(x => x.GroupId);
|
Grades.EnsureIndex(x => x.GroupId);
|
||||||
|
GradingKeyTemplates.EnsureIndex(x => x.GradingSystem);
|
||||||
Units.EnsureIndex(x => x.GroupId);
|
Units.EnsureIndex(x => x.GroupId);
|
||||||
Lessons.EnsureIndex(x => x.UnitId);
|
Lessons.EnsureIndex(x => x.UnitId);
|
||||||
Lessons.EnsureIndex(x => x.GroupId);
|
Lessons.EnsureIndex(x => x.GroupId);
|
||||||
|
|||||||
@@ -114,6 +114,17 @@ public class ExamResultRepository(LiteDbContext db) : IExamResultRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class GradingKeyTemplateRepository(LiteDbContext db) : IGradingKeyTemplateRepository
|
||||||
|
{
|
||||||
|
public List<GradingKeyTemplate> GetAll() =>
|
||||||
|
db.GradingKeyTemplates.FindAll().OrderBy(t => t.Name).ToList();
|
||||||
|
public List<GradingKeyTemplate> 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 class GradeRepository(LiteDbContext db) : IGradeRepository
|
||||||
{
|
{
|
||||||
public List<Grade> GetByStudentAndGroup(Guid sid, Guid gid) =>
|
public List<Grade> GetByStudentAndGroup(Guid sid, Guid gid) =>
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ public static class AppBootstrapper
|
|||||||
services.AddSingleton<IExamRepository, ExamRepository>();
|
services.AddSingleton<IExamRepository, ExamRepository>();
|
||||||
services.AddSingleton<IExamResultRepository, ExamResultRepository>();
|
services.AddSingleton<IExamResultRepository, ExamResultRepository>();
|
||||||
services.AddSingleton<IGradeRepository, GradeRepository>();
|
services.AddSingleton<IGradeRepository, GradeRepository>();
|
||||||
|
services.AddSingleton<IGradingKeyTemplateRepository, GradingKeyTemplateRepository>();
|
||||||
services.AddSingleton<IUnitRepository, UnitRepository>();
|
services.AddSingleton<IUnitRepository, UnitRepository>();
|
||||||
services.AddSingleton<ILessonRepository, LessonRepository>();
|
services.AddSingleton<ILessonRepository, LessonRepository>();
|
||||||
services.AddSingleton<IDocumentationRepository, DocumentationRepository>();
|
services.AddSingleton<IDocumentationRepository, DocumentationRepository>();
|
||||||
|
|||||||
@@ -14,11 +14,13 @@ public partial class ExamDialogViewModel : ObservableObject
|
|||||||
{
|
{
|
||||||
private readonly IExamRepository _exams;
|
private readonly IExamRepository _exams;
|
||||||
private readonly ICompetencyDomainRepository _competencyDomains;
|
private readonly ICompetencyDomainRepository _competencyDomains;
|
||||||
|
private readonly IGradingKeyTemplateRepository _gradingKeyTemplates;
|
||||||
|
private readonly GradingService _grading;
|
||||||
private readonly Guid _groupId;
|
private readonly Guid _groupId;
|
||||||
private readonly Guid? _subjectId;
|
private readonly Guid? _subjectId;
|
||||||
private readonly int _gradeLevel;
|
private readonly int _gradeLevel;
|
||||||
|
private readonly GradingSystem _gradingSystem;
|
||||||
private readonly Exam? _editingExam;
|
private readonly Exam? _editingExam;
|
||||||
private readonly List<GradingKeyEntry> _gradingKey;
|
|
||||||
private readonly string _subjectName;
|
private readonly string _subjectName;
|
||||||
|
|
||||||
[ObservableProperty] private string _title = "";
|
[ObservableProperty] private string _title = "";
|
||||||
@@ -30,12 +32,17 @@ public partial class ExamDialogViewModel : ObservableObject
|
|||||||
[ObservableProperty] private double _totalPoints;
|
[ObservableProperty] private double _totalPoints;
|
||||||
[ObservableProperty] private bool _hasCompetencyCatalog;
|
[ObservableProperty] private bool _hasCompetencyCatalog;
|
||||||
[ObservableProperty] private bool _useWeighting;
|
[ObservableProperty] private bool _useWeighting;
|
||||||
|
[ObservableProperty] private GradingKeyTemplate? _selectedTemplate;
|
||||||
|
[ObservableProperty] private string _newTemplateName = "";
|
||||||
|
[ObservableProperty] private string _gradingKeyValidation = "";
|
||||||
|
|
||||||
public bool TotalPointsWarning => TotalPoints <= 0;
|
public bool TotalPointsWarning => TotalPoints <= 0;
|
||||||
public string TotalPointsDisplay =>
|
public string TotalPointsDisplay =>
|
||||||
$"{TotalPoints.ToString("0.##", CultureInfo.InvariantCulture)} Punkte gesamt";
|
$"{TotalPoints.ToString("0.##", CultureInfo.InvariantCulture)} Punkte gesamt";
|
||||||
|
|
||||||
public ObservableCollection<ExamTaskEditItem> Tasks { get; } = [];
|
public ObservableCollection<ExamTaskEditItem> Tasks { get; } = [];
|
||||||
|
public ObservableCollection<GradingKeyEntryEditItem> GradingKeyEntries { get; } = [];
|
||||||
|
public ObservableCollection<GradingKeyTemplate> AvailableTemplates { get; } = [];
|
||||||
|
|
||||||
public Exam? Result { get; private set; }
|
public Exam? Result { get; private set; }
|
||||||
public string DialogTitle => _editingExam is null ? "Neue Klausur anlegen" : "Klausur bearbeiten";
|
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}";
|
? "Kein Fach hinterlegt (siehe Lerngruppe)" : $"Fach: {_subjectName}";
|
||||||
|
|
||||||
public ExamDialogViewModel(IExamRepository exams, ICompetencyDomainRepository competencyDomains,
|
public ExamDialogViewModel(IExamRepository exams, ICompetencyDomainRepository competencyDomains,
|
||||||
|
IGradingKeyTemplateRepository gradingKeyTemplates, GradingService grading,
|
||||||
Guid groupId, Guid? subjectId, int gradeLevel, GradingSystem gradingSystem,
|
Guid groupId, Guid? subjectId, int gradeLevel, GradingSystem gradingSystem,
|
||||||
string defaultSubjectName, Exam? editingExam, Exam? duplicateSource)
|
string defaultSubjectName, Exam? editingExam, Exam? duplicateSource)
|
||||||
{
|
{
|
||||||
_exams = exams; _competencyDomains = competencyDomains;
|
_exams = exams; _competencyDomains = competencyDomains;
|
||||||
|
_gradingKeyTemplates = gradingKeyTemplates; _grading = grading;
|
||||||
_groupId = groupId; _subjectId = subjectId; _gradeLevel = gradeLevel;
|
_groupId = groupId; _subjectId = subjectId; _gradeLevel = gradeLevel;
|
||||||
|
_gradingSystem = gradingSystem;
|
||||||
_editingExam = editingExam;
|
_editingExam = editingExam;
|
||||||
_subjectName = defaultSubjectName;
|
_subjectName = defaultSubjectName;
|
||||||
|
|
||||||
HasCompetencyCatalog = subjectId.HasValue
|
HasCompetencyCatalog = subjectId.HasValue
|
||||||
&& _competencyDomains.GetBySubjectAndGrade(subjectId.Value, gradeLevel).Count > 0;
|
&& _competencyDomains.GetBySubjectAndGrade(subjectId.Value, gradeLevel).Count > 0;
|
||||||
|
|
||||||
|
foreach (var t in _gradingKeyTemplates.GetByGradingSystem(gradingSystem))
|
||||||
|
AvailableTemplates.Add(t);
|
||||||
|
|
||||||
var source = editingExam ?? duplicateSource;
|
var source = editingExam ?? duplicateSource;
|
||||||
if (source is not null)
|
if (source is not null)
|
||||||
{
|
{
|
||||||
@@ -68,16 +81,18 @@ public partial class ExamDialogViewModel : ObservableObject
|
|||||||
ExamNumber = source.ExamNumber;
|
ExamNumber = source.ExamNumber;
|
||||||
Notes = source.Notes ?? "";
|
Notes = source.Notes ?? "";
|
||||||
ReturnedAtText = isDuplicate ? "" : source.ReturnedAt?.ToString("dd.MM.yyyy") ?? "";
|
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))
|
foreach (var t in source.Tasks.OrderBy(t => t.Nr))
|
||||||
AddTaskInternal(t.Title, t.MaxPoints, t.Weight, [.. t.CompetencyCodes]);
|
AddTaskInternal(t.Title, t.MaxPoints, t.Weight, [.. t.CompetencyCodes]);
|
||||||
UseWeighting = Tasks.Any(t => Math.Abs(t.Weight - 1.0) > 0.0001);
|
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
|
else
|
||||||
{
|
{
|
||||||
_gradingKey = gradingSystem == GradingSystem.Grades1To6
|
var defaults = gradingSystem == GradingSystem.Grades1To6
|
||||||
? GradingService.DefaultKey1To6() : GradingService.DefaultKey0To15();
|
? GradingService.DefaultKey1To6() : GradingService.DefaultKey0To15();
|
||||||
|
foreach (var e in defaults)
|
||||||
|
AddGradingKeyRowInternal(e.Grade, e.MinPercent);
|
||||||
}
|
}
|
||||||
foreach (var t in Tasks) t.ShowWeight = UseWeighting;
|
foreach (var t in Tasks) t.ShowWeight = UseWeighting;
|
||||||
RecomputeTotals();
|
RecomputeTotals();
|
||||||
@@ -158,8 +173,86 @@ public partial class ExamDialogViewModel : ObservableObject
|
|||||||
TotalPoints = Tasks.Sum(t => t.MaxPoints);
|
TotalPoints = Tasks.Sum(t => t.MaxPoints);
|
||||||
OnPropertyChanged(nameof(TotalPointsWarning));
|
OnPropertyChanged(nameof(TotalPointsWarning));
|
||||||
OnPropertyChanged(nameof(TotalPointsDisplay));
|
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<GradingKeyEntry> BuildGradingKeyEntries() => GradingKeyEntries
|
||||||
|
.Select(e => new GradingKeyEntry { Grade = e.Grade.Trim(), MinPercent = e.MinPercent })
|
||||||
|
.OrderByDescending(e => e.MinPercent)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void Save()
|
private void Save()
|
||||||
{
|
{
|
||||||
@@ -180,6 +273,11 @@ public partial class ExamDialogViewModel : ObservableObject
|
|||||||
returnedAt = r;
|
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 = _editingExam ?? new Exam { GroupId = _groupId };
|
||||||
Result.Title = Title.Trim();
|
Result.Title = Title.Trim();
|
||||||
Result.Date = date;
|
Result.Date = date;
|
||||||
@@ -188,11 +286,28 @@ public partial class ExamDialogViewModel : ObservableObject
|
|||||||
Result.Notes = string.IsNullOrWhiteSpace(Notes) ? null : Notes.Trim();
|
Result.Notes = string.IsNullOrWhiteSpace(Notes) ? null : Notes.Trim();
|
||||||
Result.ReturnedAt = returnedAt;
|
Result.ReturnedAt = returnedAt;
|
||||||
Result.Tasks = Tasks.Select(t => t.ToModel()).ToList();
|
Result.Tasks = Tasks.Select(t => t.ToModel()).ToList();
|
||||||
Result.GradingKey = _gradingKey;
|
Result.GradingKey = gradingKey;
|
||||||
_exams.Save(Result);
|
_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<GradingKeyEntryEditItem>? 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) ─────────────────────────
|
// ── Zeile im Aufgaben-Editor (1.2.1 / 1.2.2 / 1.2.4) ─────────────────────────
|
||||||
|
|
||||||
public partial class ExamTaskEditItem : ObservableObject
|
public partial class ExamTaskEditItem : ObservableObject
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
|||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using LehrerApp.Core.Interfaces;
|
using LehrerApp.Core.Interfaces;
|
||||||
using LehrerApp.Core.Models;
|
using LehrerApp.Core.Models;
|
||||||
|
using LehrerApp.Core.Services;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
@@ -14,6 +15,8 @@ public partial class SettingsViewModel : ObservableObject
|
|||||||
{
|
{
|
||||||
private readonly ISubjectRepository _subjects;
|
private readonly ISubjectRepository _subjects;
|
||||||
private readonly ICompetencyDomainRepository _domainRepo;
|
private readonly ICompetencyDomainRepository _domainRepo;
|
||||||
|
private readonly IGradingKeyTemplateRepository _gradingKeyTemplates;
|
||||||
|
private readonly GradingService _grading;
|
||||||
|
|
||||||
// ── Fächer ────────────────────────────────────────────────────────────────
|
// ── Fächer ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -33,13 +36,65 @@ public partial class SettingsViewModel : ObservableObject
|
|||||||
|
|
||||||
public ObservableCollection<DomainEditItem> Domains { get; } = [];
|
public ObservableCollection<DomainEditItem> 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<string> GradingSystemOptions { get; } = ["Noten 1–6", "Punkte 0–15"];
|
||||||
|
public ObservableCollection<GradingKeyTemplateEditItem> GradingKeyTemplateList { get; } = [];
|
||||||
|
|
||||||
// ── Konstruktor ───────────────────────────────────────────────────────────
|
// ── Konstruktor ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
public SettingsViewModel(ISubjectRepository subjects, ICompetencyDomainRepository domainRepo)
|
public SettingsViewModel(ISubjectRepository subjects, ICompetencyDomainRepository domainRepo,
|
||||||
|
IGradingKeyTemplateRepository gradingKeyTemplates, GradingService grading)
|
||||||
{
|
{
|
||||||
_subjects = subjects;
|
_subjects = subjects;
|
||||||
_domainRepo = domainRepo;
|
_domainRepo = domainRepo;
|
||||||
|
_gradingKeyTemplates = gradingKeyTemplates;
|
||||||
|
_grading = grading;
|
||||||
LoadSubjects();
|
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 ─────────────────────────────────
|
// ── 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<GradingKeyEntryVm> 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<GradingKeyEntryVm> onDelete)
|
||||||
|
{
|
||||||
|
Grade = e.Grade;
|
||||||
|
MinPercent = e.MinPercent;
|
||||||
|
Display = $"{Grade} — ab {MinPercent.ToString("0.##")} %";
|
||||||
|
DeleteCommand = new RelayCommand(() => onDelete(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Hilfklassen ───────────────────────────────────────────────────────────────
|
// ── Hilfklassen ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
public class SubjectListItem(Subject s)
|
public class SubjectListItem(Subject s)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<Window xmlns="https://github.com/avaloniaui"
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:vm="clr-namespace:LehrerApp.Desktop.ViewModels.Groups"
|
xmlns:vm="clr-namespace:LehrerApp.Desktop.ViewModels.Groups"
|
||||||
|
xmlns:models="clr-namespace:LehrerApp.Core.Models;assembly=LehrerApp.Core"
|
||||||
x:Class="LehrerApp.Desktop.Views.Groups.ExamDialog"
|
x:Class="LehrerApp.Desktop.Views.Groups.ExamDialog"
|
||||||
x:DataType="vm:ExamDialogViewModel"
|
x:DataType="vm:ExamDialogViewModel"
|
||||||
Title="{Binding DialogTitle}"
|
Title="{Binding DialogTitle}"
|
||||||
@@ -112,6 +113,51 @@
|
|||||||
|
|
||||||
<TextBlock Text="{Binding ValidationMessage}" Foreground="Red" FontSize="12"
|
<TextBlock Text="{Binding ValidationMessage}" Foreground="Red" FontSize="12"
|
||||||
IsVisible="{Binding ValidationMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
IsVisible="{Binding ValidationMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||||
|
|
||||||
|
<Separator Margin="0,4"/>
|
||||||
|
|
||||||
|
<Grid ColumnDefinitions="*,Auto">
|
||||||
|
<TextBlock Grid.Column="0" Text="Notenschlüssel" FontSize="14" FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||||||
|
<Button Grid.Column="1" Content="+ Stufe" Command="{Binding AddGradingKeyRowCommand}"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid ColumnDefinitions="*,8,Auto">
|
||||||
|
<ComboBox Grid.Column="0" ItemsSource="{Binding AvailableTemplates}"
|
||||||
|
SelectedItem="{Binding SelectedTemplate}"
|
||||||
|
PlaceholderText="Vorlage auswählen" HorizontalAlignment="Stretch">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="models:GradingKeyTemplate">
|
||||||
|
<TextBlock Text="{Binding Name}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
<Button Grid.Column="2" Content="Anwenden" Command="{Binding ApplyTemplateCommand}"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<ItemsControl ItemsSource="{Binding GradingKeyEntries}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="vm:GradingKeyEntryEditItem">
|
||||||
|
<Grid ColumnDefinitions="90,90,*,Auto" Margin="0,0,0,6">
|
||||||
|
<TextBox Grid.Column="0" Text="{Binding Grade}" PlaceholderText="Note" Margin="0,0,6,0"/>
|
||||||
|
<NumericUpDown Grid.Column="1" Value="{Binding MinPercent}" Minimum="0" Maximum="100"
|
||||||
|
FormatString="0.##" Width="90" ShowButtonSpinner="False" Margin="0,0,6,0"
|
||||||
|
ToolTip.Tip="Mindest-Prozentgrenze"/>
|
||||||
|
<TextBlock Grid.Column="2" Text="{Binding AbsolutePointsDisplay}" FontSize="12" Opacity="0.65"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<Button Grid.Column="3" Content="✕" Command="{Binding RemoveCommand}" Padding="6,2"
|
||||||
|
ToolTip.Tip="Entfernen"/>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
|
||||||
|
<TextBlock Text="{Binding GradingKeyValidation}" Foreground="Red" FontSize="12"
|
||||||
|
IsVisible="{Binding GradingKeyValidation, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||||
|
|
||||||
|
<Grid ColumnDefinitions="*,8,Auto">
|
||||||
|
<TextBox Grid.Column="0" Text="{Binding NewTemplateName}" PlaceholderText="Name für neue Vorlage"/>
|
||||||
|
<Button Grid.Column="2" Content="Als Vorlage speichern" Command="{Binding SaveAsTemplateCommand}"/>
|
||||||
|
</Grid>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using LehrerApp.Core.Interfaces;
|
using LehrerApp.Core.Interfaces;
|
||||||
using LehrerApp.Core.Models;
|
using LehrerApp.Core.Models;
|
||||||
|
using LehrerApp.Core.Services;
|
||||||
using LehrerApp.Desktop.ViewModels.Groups;
|
using LehrerApp.Desktop.ViewModels.Groups;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
@@ -56,6 +57,8 @@ public partial class GroupDetailView : UserControl
|
|||||||
var dialogVm = new ExamDialogViewModel(
|
var dialogVm = new ExamDialogViewModel(
|
||||||
App.Services.GetRequiredService<IExamRepository>(),
|
App.Services.GetRequiredService<IExamRepository>(),
|
||||||
App.Services.GetRequiredService<ICompetencyDomainRepository>(),
|
App.Services.GetRequiredService<ICompetencyDomainRepository>(),
|
||||||
|
App.Services.GetRequiredService<IGradingKeyTemplateRepository>(),
|
||||||
|
App.Services.GetRequiredService<GradingService>(),
|
||||||
groupId, vm.Group.SubjectId, vm.Group.GradeLevel, vm.Group.GradingSystem,
|
groupId, vm.Group.SubjectId, vm.Group.GradeLevel, vm.Group.GradingSystem,
|
||||||
vm.Group.Subject ?? "", editingExam, duplicateSource);
|
vm.Group.Subject ?? "", editingExam, duplicateSource);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="clr-namespace:LehrerApp.Desktop.ViewModels.Settings"
|
||||||
|
x:Class="LehrerApp.Desktop.Views.Settings.GradingKeyTemplateDialog"
|
||||||
|
x:DataType="vm:GradingKeyTemplateEditItem"
|
||||||
|
Title="{Binding Name}"
|
||||||
|
Width="420" SizeToContent="Height" MinHeight="300"
|
||||||
|
CanResize="False" WindowStartupLocation="CenterOwner">
|
||||||
|
|
||||||
|
<Grid RowDefinitions="*,Auto" Margin="24">
|
||||||
|
<StackPanel Grid.Row="0" Spacing="10">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="{Binding Name}" FontSize="18" FontWeight="SemiBold"/>
|
||||||
|
<TextBlock Text="{Binding GradingSystemLabel}" FontSize="12" Opacity="0.5"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<TextBlock Text="{Binding CompletenessWarning}" Foreground="Red" FontSize="12" TextWrapping="Wrap"
|
||||||
|
IsVisible="{Binding CompletenessWarning, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||||
|
|
||||||
|
<ItemsControl ItemsSource="{Binding Entries}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="vm:GradingKeyEntryVm">
|
||||||
|
<Grid ColumnDefinitions="*,Auto" Margin="0,3">
|
||||||
|
<TextBlock Grid.Column="0" Text="{Binding Display}" VerticalAlignment="Center" FontSize="13"/>
|
||||||
|
<Button Grid.Column="1" Content="×" Padding="7,2" FontSize="13" Command="{Binding DeleteCommand}"/>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
|
||||||
|
<TextBlock Text="{Binding Validation}" Foreground="Red" FontSize="11"
|
||||||
|
IsVisible="{Binding Validation, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||||
|
|
||||||
|
<Grid ColumnDefinitions="*,8,90,8,Auto">
|
||||||
|
<TextBox Grid.Column="0" Text="{Binding NewGrade}"
|
||||||
|
PlaceholderText="Note (z.B. 2 oder 11)" FontSize="12"/>
|
||||||
|
<NumericUpDown Grid.Column="2" Value="{Binding NewMinPercent}" Minimum="0" Maximum="100"
|
||||||
|
FormatString="0.##" ShowButtonSpinner="False" FontSize="12"
|
||||||
|
ToolTip.Tip="Mindest-Prozentgrenze"/>
|
||||||
|
<Button Grid.Column="4" Content="+" Command="{Binding AddEntryCommand}"/>
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Button Grid.Row="1" Content="Fertig" HorizontalAlignment="Stretch" Margin="0,20,0,0" Click="OnClose"/>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
|
||||||
|
namespace LehrerApp.Desktop.Views.Settings;
|
||||||
|
|
||||||
|
public partial class GradingKeyTemplateDialog : Window
|
||||||
|
{
|
||||||
|
public GradingKeyTemplateDialog() => InitializeComponent();
|
||||||
|
|
||||||
|
private void OnClose(object? sender, RoutedEventArgs e) => Close();
|
||||||
|
}
|
||||||
@@ -4,14 +4,17 @@
|
|||||||
x:Class="LehrerApp.Desktop.Views.Settings.SettingsView"
|
x:Class="LehrerApp.Desktop.Views.Settings.SettingsView"
|
||||||
x:DataType="vm:SettingsViewModel">
|
x:DataType="vm:SettingsViewModel">
|
||||||
|
|
||||||
|
<Grid RowDefinitions="Auto,*">
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="0" Text="Einstellungen" FontSize="22" FontWeight="SemiBold"
|
||||||
|
Margin="32,28,32,0"/>
|
||||||
|
|
||||||
|
<TabbedPage Grid.Row="1" TabPlacement="Top">
|
||||||
|
|
||||||
|
<!-- Tab: Fächer -->
|
||||||
|
<ContentPage Header="Fächer">
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
<StackPanel Margin="32,28" Spacing="32" MaxWidth="640">
|
<StackPanel Margin="32,20,32,28" Spacing="14" MaxWidth="640">
|
||||||
|
|
||||||
<TextBlock Text="Einstellungen" FontSize="22" FontWeight="SemiBold"/>
|
|
||||||
|
|
||||||
<!-- ── FÄCHER ────────────────────────────────────────────────── -->
|
|
||||||
<StackPanel Spacing="10">
|
|
||||||
<TextBlock Text="FÄCHER" FontSize="10" FontWeight="Bold" Opacity="0.4"/>
|
|
||||||
|
|
||||||
<ItemsControl ItemsSource="{Binding Subjects}">
|
<ItemsControl ItemsSource="{Binding Subjects}">
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
@@ -48,11 +51,15 @@
|
|||||||
IsVisible="{Binding ValidationMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
IsVisible="{Binding ValidationMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<!-- ── KOMPETENZKATALOGE ─────────────────────────────────────── -->
|
</StackPanel>
|
||||||
<StackPanel Spacing="10">
|
</ScrollViewer>
|
||||||
<TextBlock Text="KOMPETENZKATALOGE" FontSize="10" FontWeight="Bold" Opacity="0.4"/>
|
</ContentPage>
|
||||||
|
|
||||||
|
<!-- Tab: Kompetenzen -->
|
||||||
|
<ContentPage Header="Kompetenzen">
|
||||||
|
<ScrollViewer>
|
||||||
|
<StackPanel Margin="32,20,32,28" Spacing="14" MaxWidth="640">
|
||||||
|
|
||||||
<!-- Fach + Klassenstufe Auswahl + Import/Export -->
|
<!-- Fach + Klassenstufe Auswahl + Import/Export -->
|
||||||
<Grid ColumnDefinitions="*,12,120,12,Auto,8,Auto">
|
<Grid ColumnDefinitions="*,12,120,12,Auto,8,Auto">
|
||||||
@@ -151,7 +158,70 @@
|
|||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
</ContentPage>
|
||||||
|
|
||||||
|
<!-- Tab: Notenschlüssel-Vorlagen -->
|
||||||
|
<ContentPage Header="Notenschlüssel-Vorlagen">
|
||||||
|
<ScrollViewer>
|
||||||
|
<StackPanel Margin="32,20,32,28" Spacing="14" MaxWidth="640">
|
||||||
|
|
||||||
|
<TextBlock Text="Noch keine Vorlagen angelegt." Opacity="0.4" FontSize="13"
|
||||||
|
IsVisible="{Binding !GradingKeyTemplateList.Count}"/>
|
||||||
|
|
||||||
|
<ItemsControl ItemsSource="{Binding GradingKeyTemplateList}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate DataType="vm:GradingKeyTemplateEditItem">
|
||||||
|
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
|
||||||
|
BorderThickness="0,0,0,1" Padding="0,9">
|
||||||
|
<Grid ColumnDefinitions="*,Auto,Auto">
|
||||||
|
<StackPanel Grid.Column="0">
|
||||||
|
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" FontSize="14"/>
|
||||||
|
<TextBlock FontSize="12" Opacity="0.5">
|
||||||
|
<Run Text="{Binding GradingSystemLabel}"/>
|
||||||
|
<Run Text=" · "/>
|
||||||
|
<Run Text="{Binding Entries.Count}"/>
|
||||||
|
<Run Text=" Stufen"/>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock Text="{Binding CompletenessWarning}" Foreground="Red" FontSize="11"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
IsVisible="{Binding CompletenessWarning, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<Button Grid.Column="1" Content="Bearbeiten" FontSize="12" Padding="10,4"
|
||||||
|
Margin="8,0,0,0" Tag="{Binding}" Click="OnEditGradingKeyTemplateClick"/>
|
||||||
|
<Button Grid.Column="2" Content="Löschen" FontSize="12" Padding="10,4"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Command="{Binding $parent[ItemsControl].((vm:SettingsViewModel)DataContext).DeleteGradingKeyTemplateCommand}"
|
||||||
|
CommandParameter="{Binding}"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
|
||||||
|
<!-- Neue Vorlage anlegen -->
|
||||||
|
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
|
||||||
|
BorderThickness="1" CornerRadius="6" Padding="14,12">
|
||||||
|
<StackPanel Spacing="10">
|
||||||
|
<TextBlock Text="Neue Vorlage" FontSize="12" FontWeight="SemiBold" Opacity="0.7"/>
|
||||||
|
<Grid ColumnDefinitions="*,12,140,12,Auto">
|
||||||
|
<TextBox Grid.Column="0" Text="{Binding NewTemplateName}"
|
||||||
|
PlaceholderText="Vorlagenname (z.B. Standard Oberstufe)"/>
|
||||||
|
<ComboBox Grid.Column="2" ItemsSource="{Binding GradingSystemOptions}"
|
||||||
|
SelectedItem="{Binding NewTemplateGradingSystemName}"
|
||||||
|
HorizontalAlignment="Stretch"/>
|
||||||
|
<Button Grid.Column="4" Content="Anlegen"
|
||||||
|
Command="{Binding AddGradingKeyTemplateCommand}"/>
|
||||||
|
</Grid>
|
||||||
|
<TextBlock Text="{Binding TemplateValidationMessage}" Foreground="Red" FontSize="12"
|
||||||
|
IsVisible="{Binding TemplateValidationMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
</ContentPage>
|
||||||
|
|
||||||
|
</TabbedPage>
|
||||||
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -42,4 +42,12 @@ public partial class SettingsView : UserControl
|
|||||||
var json = vm.ExportCatalog();
|
var json = vm.ExportCatalog();
|
||||||
await File.WriteAllTextAsync(file.Path.LocalPath, json);
|
await File.WriteAllTextAsync(file.Path.LocalPath, json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void OnEditGradingKeyTemplateClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not Button { Tag: GradingKeyTemplateEditItem item }) return;
|
||||||
|
var dialog = new GradingKeyTemplateDialog { DataContext = item };
|
||||||
|
var owner = TopLevel.GetTopLevel(this) as Window;
|
||||||
|
if (owner is not null) await dialog.ShowDialog(owner);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,12 +50,14 @@ Modelle `Exam`, `ExamTask`, `GradingKeyEntry`, `ExamResult` existieren bereits i
|
|||||||
Voraussetzung für die Kompetenzauswertung in 8.3.
|
Voraussetzung für die Kompetenzauswertung in 8.3.
|
||||||
|
|
||||||
### 1.3 Notenschlüssel
|
### 1.3 Notenschlüssel
|
||||||
- [ ] **1.3.1** Editor für `GradingKey` mit Vorbelegung aus
|
- [x] **1.3.1** Editor für `GradingKey` mit Vorbelegung aus
|
||||||
`GradingService.DefaultKey1To6()` / `DefaultKey0To15()` je nach `GradingSystem` der Gruppe.
|
`GradingService.DefaultKey1To6()` / `DefaultKey0To15()` je nach `GradingSystem` der Gruppe.
|
||||||
- [ ] **1.3.2** Notenschlüssel als wiederverwendbare Vorlage speichern (neues Modell
|
Umgesetzt im `ExamDialog` (Teil von 1.1.1), analog zum Aufgaben-Editor.
|
||||||
|
- [x] **1.3.2** Notenschlüssel als wiederverwendbare Vorlage speichern (neues Modell
|
||||||
`GradingKeyTemplate` + Repository) und in Einstellungen verwalten.
|
`GradingKeyTemplate` + Repository) und in Einstellungen verwalten.
|
||||||
- [ ] **1.3.3** Live-Vorschau: Punktegrenzen absolut anzeigen (z.B. "Note 2 ab 45 von 60 P.").
|
- [x] **1.3.3** Live-Vorschau: Punktegrenzen absolut anzeigen (z.B. "Note 2 ab 45 von 60 P.").
|
||||||
- [ ] **1.3.4** Validierung: lückenlose, absteigende Prozentgrenzen, keine Dopplungen.
|
- [x] **1.3.4** Validierung: lückenlose, absteigende Prozentgrenzen, keine Dopplungen
|
||||||
|
(`GradingService.ValidateGradingKey`).
|
||||||
|
|
||||||
### 1.4 Punkteeingabe & Korrektur
|
### 1.4 Punkteeingabe & Korrektur
|
||||||
- [ ] **1.4.1** Eingaberaster: Zeilen = Schüler, Spalten = Aufgaben, letzte Spalte Summe + Note.
|
- [ ] **1.4.1** Eingaberaster: Zeilen = Schüler, Spalten = Aufgaben, letzte Spalte Summe + Note.
|
||||||
|
|||||||
Reference in New Issue
Block a user