Klausur: Fach aus Lerngruppe übernehmen, Rückgabedatum erfassen
- Fach kommt jetzt aus der Lerngruppe statt manuell pro Klausur eingegeben zu werden: Fach-Feld im Anlegen-Dialog der Lerngruppe ist nicht mehr auf Typ "Kurs" beschränkt, da eine Gruppe genau ein Fach unterrichtet (bei zwei Fächern legt man zwei Gruppen an). Im Klausur-Dialog ist das Fach dadurch nur noch read-only Kontext statt eines redundanten Eingabefelds. - Neues Feld Exam.ReturnedAt: wird beim Statuswechsel auf "Zurück- gegeben" automatisch mit dem heutigen Datum belegt (und beim Zurücknehmen des Status wieder geleert), ist im Klausur-Dialog aber auch manuell nachträglich korrigierbar. Sichtbar als eigene Spalte im Klausuren-Tab. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ public class Exam
|
|||||||
public List<ExamTask> Tasks { get; set; } = [];
|
public List<ExamTask> Tasks { get; set; } = [];
|
||||||
public List<GradingKeyEntry> GradingKey { get; set; } = [];
|
public List<GradingKeyEntry> GradingKey { get; set; } = [];
|
||||||
public ExamStatus Status { get; set; } = ExamStatus.Planned;
|
public ExamStatus Status { get; set; } = ExamStatus.Planned;
|
||||||
|
public DateOnly? ReturnedAt { get; set; }
|
||||||
public string? Notes { get; set; }
|
public string? Notes { get; set; }
|
||||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|||||||
@@ -19,12 +19,13 @@ public partial class ExamDialogViewModel : ObservableObject
|
|||||||
private readonly int _gradeLevel;
|
private readonly int _gradeLevel;
|
||||||
private readonly Exam? _editingExam;
|
private readonly Exam? _editingExam;
|
||||||
private readonly List<GradingKeyEntry> _gradingKey;
|
private readonly List<GradingKeyEntry> _gradingKey;
|
||||||
|
private readonly string _subjectName;
|
||||||
|
|
||||||
[ObservableProperty] private string _title = "";
|
[ObservableProperty] private string _title = "";
|
||||||
[ObservableProperty] private string _dateText = DateOnly.FromDateTime(DateTime.Today).ToString("dd.MM.yyyy");
|
[ObservableProperty] private string _dateText = DateOnly.FromDateTime(DateTime.Today).ToString("dd.MM.yyyy");
|
||||||
[ObservableProperty] private string _subject = "";
|
|
||||||
[ObservableProperty] private int? _examNumber;
|
[ObservableProperty] private int? _examNumber;
|
||||||
[ObservableProperty] private string _notes = "";
|
[ObservableProperty] private string _notes = "";
|
||||||
|
[ObservableProperty] private string _returnedAtText = "";
|
||||||
[ObservableProperty] private string _validationMessage = "";
|
[ObservableProperty] private string _validationMessage = "";
|
||||||
[ObservableProperty] private double _totalPoints;
|
[ObservableProperty] private double _totalPoints;
|
||||||
[ObservableProperty] private bool _hasCompetencyCatalog;
|
[ObservableProperty] private bool _hasCompetencyCatalog;
|
||||||
@@ -40,6 +41,10 @@ public partial class ExamDialogViewModel : ObservableObject
|
|||||||
public string DialogTitle => _editingExam is null ? "Neue Klausur anlegen" : "Klausur bearbeiten";
|
public string DialogTitle => _editingExam is null ? "Neue Klausur anlegen" : "Klausur bearbeiten";
|
||||||
public string SaveButtonText => _editingExam is null ? "Anlegen" : "Speichern";
|
public string SaveButtonText => _editingExam is null ? "Anlegen" : "Speichern";
|
||||||
|
|
||||||
|
/// Fach kommt von der Lerngruppe, nicht editierbar (jede Gruppe unterrichtet ein Fach).
|
||||||
|
public string SubjectDisplay => string.IsNullOrWhiteSpace(_subjectName)
|
||||||
|
? "Kein Fach hinterlegt (siehe Lerngruppe)" : $"Fach: {_subjectName}";
|
||||||
|
|
||||||
public ExamDialogViewModel(IExamRepository exams, ICompetencyDomainRepository competencyDomains,
|
public ExamDialogViewModel(IExamRepository exams, ICompetencyDomainRepository competencyDomains,
|
||||||
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)
|
||||||
@@ -47,6 +52,7 @@ public partial class ExamDialogViewModel : ObservableObject
|
|||||||
_exams = exams; _competencyDomains = competencyDomains;
|
_exams = exams; _competencyDomains = competencyDomains;
|
||||||
_groupId = groupId; _subjectId = subjectId; _gradeLevel = gradeLevel;
|
_groupId = groupId; _subjectId = subjectId; _gradeLevel = gradeLevel;
|
||||||
_editingExam = editingExam;
|
_editingExam = editingExam;
|
||||||
|
_subjectName = defaultSubjectName;
|
||||||
|
|
||||||
HasCompetencyCatalog = subjectId.HasValue
|
HasCompetencyCatalog = subjectId.HasValue
|
||||||
&& _competencyDomains.GetBySubjectAndGrade(subjectId.Value, gradeLevel).Count > 0;
|
&& _competencyDomains.GetBySubjectAndGrade(subjectId.Value, gradeLevel).Count > 0;
|
||||||
@@ -59,9 +65,9 @@ public partial class ExamDialogViewModel : ObservableObject
|
|||||||
DateText = isDuplicate
|
DateText = isDuplicate
|
||||||
? DateOnly.FromDateTime(DateTime.Today).ToString("dd.MM.yyyy")
|
? DateOnly.FromDateTime(DateTime.Today).ToString("dd.MM.yyyy")
|
||||||
: source.Date.ToString("dd.MM.yyyy");
|
: source.Date.ToString("dd.MM.yyyy");
|
||||||
Subject = source.Subject;
|
|
||||||
ExamNumber = source.ExamNumber;
|
ExamNumber = source.ExamNumber;
|
||||||
Notes = source.Notes ?? "";
|
Notes = source.Notes ?? "";
|
||||||
|
ReturnedAtText = isDuplicate ? "" : source.ReturnedAt?.ToString("dd.MM.yyyy") ?? "";
|
||||||
_gradingKey = source.GradingKey
|
_gradingKey = source.GradingKey
|
||||||
.Select(k => new GradingKeyEntry { Grade = k.Grade, MinPercent = k.MinPercent }).ToList();
|
.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))
|
||||||
@@ -70,7 +76,6 @@ public partial class ExamDialogViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Subject = defaultSubjectName;
|
|
||||||
_gradingKey = gradingSystem == GradingSystem.Grades1To6
|
_gradingKey = gradingSystem == GradingSystem.Grades1To6
|
||||||
? GradingService.DefaultKey1To6() : GradingService.DefaultKey0To15();
|
? GradingService.DefaultKey1To6() : GradingService.DefaultKey0To15();
|
||||||
}
|
}
|
||||||
@@ -164,13 +169,24 @@ public partial class ExamDialogViewModel : ObservableObject
|
|||||||
ValidationMessage = "Datum im Format TT.MM.JJJJ eingeben.";
|
ValidationMessage = "Datum im Format TT.MM.JJJJ eingeben.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
DateOnly? returnedAt = null;
|
||||||
|
if (!string.IsNullOrWhiteSpace(ReturnedAtText))
|
||||||
|
{
|
||||||
|
if (!DateOnly.TryParseExact(ReturnedAtText, "dd.MM.yyyy", null, DateTimeStyles.None, out var r))
|
||||||
|
{
|
||||||
|
ValidationMessage = "Rückgabedatum im Format TT.MM.JJJJ eingeben.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
returnedAt = r;
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
Result.Subject = Subject.Trim();
|
Result.Subject = _subjectName.Trim();
|
||||||
Result.ExamNumber = ExamNumber;
|
Result.ExamNumber = ExamNumber;
|
||||||
Result.Notes = string.IsNullOrWhiteSpace(Notes) ? null : Notes.Trim();
|
Result.Notes = string.IsNullOrWhiteSpace(Notes) ? null : Notes.Trim();
|
||||||
|
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);
|
||||||
|
|||||||
@@ -317,6 +317,10 @@ public partial class GroupDetailViewModel : ObservableObject
|
|||||||
var exam = _exams.GetById(SelectedExam.Id);
|
var exam = _exams.GetById(SelectedExam.Id);
|
||||||
if (exam is null) return;
|
if (exam is null) return;
|
||||||
exam.Status = status;
|
exam.Status = status;
|
||||||
|
if (status == ExamStatus.Returned)
|
||||||
|
exam.ReturnedAt ??= DateOnly.FromDateTime(DateTime.Today);
|
||||||
|
else
|
||||||
|
exam.ReturnedAt = null;
|
||||||
_exams.Save(exam);
|
_exams.Save(exam);
|
||||||
var id = exam.Id;
|
var id = exam.Id;
|
||||||
ReloadExams();
|
ReloadExams();
|
||||||
@@ -373,10 +377,12 @@ public class ExamSummary
|
|||||||
public ExamStatus Status { get; }
|
public ExamStatus Status { get; }
|
||||||
public string StatusLabel { get; }
|
public string StatusLabel { get; }
|
||||||
public string StatusColorHex { get; }
|
public string StatusColorHex { get; }
|
||||||
|
public string ReturnedAtDisplay { get; }
|
||||||
|
|
||||||
public ExamSummary(Core.Models.Exam e)
|
public ExamSummary(Core.Models.Exam e)
|
||||||
{
|
{
|
||||||
Id = e.Id; Title = e.Title; Date = e.Date.ToString("dd.MM.yyyy"); Status = e.Status;
|
Id = e.Id; Title = e.Title; Date = e.Date.ToString("dd.MM.yyyy"); Status = e.Status;
|
||||||
|
ReturnedAtDisplay = e.ReturnedAt?.ToString("dd.MM.yyyy") ?? "";
|
||||||
StatusLabel = e.Status switch
|
StatusLabel = e.Status switch
|
||||||
{
|
{
|
||||||
ExamStatus.Planned => "Geplant",
|
ExamStatus.Planned => "Geplant",
|
||||||
@@ -569,7 +575,7 @@ public partial class AddGroupDialogViewModel : ObservableObject
|
|||||||
if (string.IsNullOrWhiteSpace(Name)) { ValidationMessage = "Bezeichnung erforderlich."; return; }
|
if (string.IsNullOrWhiteSpace(Name)) { ValidationMessage = "Bezeichnung erforderlich."; return; }
|
||||||
if (GradeLevel is < 1 or > 13) { ValidationMessage = "Klassenstufe 1–13."; return; }
|
if (GradeLevel is < 1 or > 13) { ValidationMessage = "Klassenstufe 1–13."; return; }
|
||||||
|
|
||||||
string? subjectName = IsKurs && !string.IsNullOrWhiteSpace(Subject) ? Subject.Trim() : null;
|
string? subjectName = !string.IsNullOrWhiteSpace(Subject) ? Subject.Trim() : null;
|
||||||
Guid? subjectId = null;
|
Guid? subjectId = null;
|
||||||
|
|
||||||
if (subjectName is not null)
|
if (subjectName is not null)
|
||||||
|
|||||||
@@ -25,8 +25,9 @@
|
|||||||
<TextBox Text="{Binding Name}" PlaceholderText="{Binding NameHint}"/>
|
<TextBox Text="{Binding Name}" PlaceholderText="{Binding NameHint}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Fach – nur bei Kurs, AutoComplete aus bekannten Fächern -->
|
<!-- Fach – AutoComplete aus bekannten Fächern; auch bei Klasse, da eine
|
||||||
<StackPanel Spacing="4" IsVisible="{Binding IsKurs}">
|
Lerngruppe genau ein Fach unterrichtet (bei zwei Fächern: zwei Gruppen anlegen) -->
|
||||||
|
<StackPanel Spacing="4">
|
||||||
<TextBlock Text="Fach (optional)" FontSize="12" Opacity="0.7"/>
|
<TextBlock Text="Fach (optional)" FontSize="12" Opacity="0.7"/>
|
||||||
<AutoCompleteBox Text="{Binding Subject}"
|
<AutoCompleteBox Text="{Binding Subject}"
|
||||||
ItemsSource="{Binding KnownSubjectNames}"
|
ItemsSource="{Binding KnownSubjectNames}"
|
||||||
|
|||||||
@@ -10,7 +10,10 @@
|
|||||||
<Grid RowDefinitions="*,Auto" Margin="24">
|
<Grid RowDefinitions="*,Auto" Margin="24">
|
||||||
<ScrollViewer Grid.Row="0">
|
<ScrollViewer Grid.Row="0">
|
||||||
<StackPanel Spacing="14" Margin="0,0,12,0">
|
<StackPanel Spacing="14" Margin="0,0,12,0">
|
||||||
<TextBlock Text="{Binding DialogTitle}" FontSize="18" FontWeight="SemiBold"/>
|
<StackPanel Spacing="2">
|
||||||
|
<TextBlock Text="{Binding DialogTitle}" FontSize="18" FontWeight="SemiBold"/>
|
||||||
|
<TextBlock Text="{Binding SubjectDisplay}" FontSize="12" Opacity="0.6"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Spacing="4">
|
<StackPanel Spacing="4">
|
||||||
<TextBlock Text="Titel *" FontSize="12" Opacity="0.7"/>
|
<TextBlock Text="Titel *" FontSize="12" Opacity="0.7"/>
|
||||||
@@ -23,14 +26,14 @@
|
|||||||
<TextBox Text="{Binding DateText}" PlaceholderText="TT.MM.JJJJ"/>
|
<TextBox Text="{Binding DateText}" PlaceholderText="TT.MM.JJJJ"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Grid.Column="2" Spacing="4">
|
<StackPanel Grid.Column="2" Spacing="4">
|
||||||
<TextBlock Text="Fach" FontSize="12" Opacity="0.7"/>
|
|
||||||
<TextBox Text="{Binding Subject}"/>
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Grid.Column="4" Spacing="4">
|
|
||||||
<TextBlock Text="Klausurnummer" FontSize="12" Opacity="0.7"/>
|
<TextBlock Text="Klausurnummer" FontSize="12" Opacity="0.7"/>
|
||||||
<NumericUpDown Value="{Binding ExamNumber}" Minimum="1" Maximum="20" FormatString="0"
|
<NumericUpDown Value="{Binding ExamNumber}" Minimum="1" Maximum="20" FormatString="0"
|
||||||
ShowButtonSpinner="False"/>
|
ShowButtonSpinner="False"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Column="4" Spacing="4">
|
||||||
|
<TextBlock Text="Rückgabedatum" FontSize="12" Opacity="0.7"/>
|
||||||
|
<TextBox Text="{Binding ReturnedAtText}" PlaceholderText="TT.MM.JJJJ"/>
|
||||||
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<StackPanel Spacing="4">
|
<StackPanel Spacing="4">
|
||||||
|
|||||||
@@ -107,6 +107,7 @@
|
|||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
|
<DataGridTextColumn Header="Rückgabe" Binding="{Binding ReturnedAtDisplay}" Width="100"/>
|
||||||
</DataGrid.Columns>
|
</DataGrid.Columns>
|
||||||
<DataGrid.ContextMenu>
|
<DataGrid.ContextMenu>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
|
|||||||
Reference in New Issue
Block a user