Niveaudifferenzierung (E/G/Förder) für Klassen und Kurse
Für Fächer mit Binnendifferenzierung (z.B. Mathematik E/G-Niveau, Förderniveau) innerhalb derselben Lerngruppe: - LearningGroup.IsDifferentiated: neue Checkbox in den Stammdaten, aktiviert die Niveau-Funktionen optional für Klassen und Kurse. - Enrollment.Niveau: Niveau-Zuordnung je Schüler innerhalb der Gruppe, editierbar über eine neue Spalte im Schüler-Tab (nur sichtbar bei differenzierter Gruppe). - Exam.Niveau: Klausuren können optional einem Niveau zugeordnet werden. Workflow nutzt die bestehende Duplizieren-Funktion (1.1.4): G-Klausur anlegen, für die E-Variante duplizieren und eigene Aufgaben/Punkte vergeben — Niveau wird beim Duplizieren bewusst zurückgesetzt, damit nicht versehentlich zwei gleiche entstehen. - Punkteeingabe und Auswertung filtern automatisch auf die Schüler mit passendem Niveau; Klausuren ohne Niveau-Zuordnung verhalten sich unverändert (gesamte Gruppe). Beide Dialoge zeigen das Niveau als Badge im Titel, die Klausurenliste als eigene Spalte. - Individuelle Förderklausuren (nur für einen Schüler) laufen pragmatisch über die bestehende Abwesend-Markierung der übrigen Schüler statt über eine eigene Pro-Schüler-Zuordnung. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ public class Exam
|
|||||||
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 DateOnly? ReturnedAt { get; set; }
|
||||||
|
public Niveau? Niveau { 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;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ public class LearningGroup
|
|||||||
public int? HoursPerWeek { get; set; }
|
public int? HoursPerWeek { get; set; }
|
||||||
public bool IsActive { get; set; } = true;
|
public bool IsActive { get; set; } = true;
|
||||||
public bool IsOwnClass { get; set; }
|
public bool IsOwnClass { get; set; }
|
||||||
|
public bool IsDifferentiated { 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;
|
||||||
}
|
}
|
||||||
@@ -26,7 +27,9 @@ public class Enrollment
|
|||||||
public EnrollmentPeriod Period { get; set; } = EnrollmentPeriod.FullYear;
|
public EnrollmentPeriod Period { get; set; } = EnrollmentPeriod.FullYear;
|
||||||
public DateOnly? JoinedAt { get; set; }
|
public DateOnly? JoinedAt { get; set; }
|
||||||
public DateOnly? LeftAt { get; set; }
|
public DateOnly? LeftAt { get; set; }
|
||||||
|
public Niveau? Niveau { get; set; }
|
||||||
}
|
}
|
||||||
public enum EnrollmentPeriod { FullYear, H1Only, H2Only, Custom }
|
public enum EnrollmentPeriod { FullYear, H1Only, H2Only, Custom }
|
||||||
public enum GroupType { Class, Course }
|
public enum GroupType { Class, Course }
|
||||||
public enum GradingSystem { Grades1To6, Points0To15 }
|
public enum GradingSystem { Grades1To6, Points0To15 }
|
||||||
|
public enum Niveau { E, G, Foerder }
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public partial class ExamEvaluationDialogViewModel : ObservableObject
|
|||||||
|
|
||||||
public string ExamTitle => _exam.Title;
|
public string ExamTitle => _exam.Title;
|
||||||
public string ExamDateLabel => _exam.Date.ToString("dd.MM.yyyy");
|
public string ExamDateLabel => _exam.Date.ToString("dd.MM.yyyy");
|
||||||
|
public string NiveauLabel => _exam.Niveau.HasValue ? NiveauDisplay.ToName(_exam.Niveau) : "";
|
||||||
|
|
||||||
[ObservableProperty] private int _gradedCount;
|
[ObservableProperty] private int _gradedCount;
|
||||||
[ObservableProperty] private int _absentCount;
|
[ObservableProperty] private int _absentCount;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ public partial class ExamGradingDialogViewModel : ObservableObject
|
|||||||
|
|
||||||
public string ExamTitle => _exam.Title;
|
public string ExamTitle => _exam.Title;
|
||||||
public string ExamDateLabel => _exam.Date.ToString("dd.MM.yyyy");
|
public string ExamDateLabel => _exam.Date.ToString("dd.MM.yyyy");
|
||||||
|
public string NiveauLabel => _exam.Niveau.HasValue ? NiveauDisplay.ToName(_exam.Niveau) : "";
|
||||||
public List<ExamTask> Tasks { get; }
|
public List<ExamTask> Tasks { get; }
|
||||||
|
|
||||||
public ObservableCollection<ExamResultRow> Rows { get; } = [];
|
public ObservableCollection<ExamResultRow> Rows { get; } = [];
|
||||||
@@ -38,6 +39,10 @@ public partial class ExamGradingDialogViewModel : ObservableObject
|
|||||||
var enrollment = enrollmentList.FirstOrDefault(e => e.StudentId == s.Id);
|
var enrollment = enrollmentList.FirstOrDefault(e => e.StudentId == s.Id);
|
||||||
if (enrollment is not null && !IsEnrolledAtDate(enrollment, exam.Date)) continue;
|
if (enrollment is not null && !IsEnrolledAtDate(enrollment, exam.Date)) continue;
|
||||||
|
|
||||||
|
// Niveau-Klausur: nur Schüler mit passendem Niveau zeigen. Klausuren ohne
|
||||||
|
// Niveau-Zuordnung gelten weiterhin für die ganze Gruppe.
|
||||||
|
if (exam.Niveau.HasValue && enrollment?.Niveau != exam.Niveau) continue;
|
||||||
|
|
||||||
existing.TryGetValue(s.Id, out var result);
|
existing.TryGetValue(s.Id, out var result);
|
||||||
var row = new ExamResultRow(s.Id, s.FullName, Tasks, result, _exam.GradingKey, _examMaxPoints, _grading);
|
var row = new ExamResultRow(s.Id, s.FullName, Tasks, result, _exam.GradingKey, _examMaxPoints, _grading);
|
||||||
row.OnChanged = SaveRow;
|
row.OnChanged = SaveRow;
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ public partial class ExamDialogViewModel : ObservableObject
|
|||||||
[ObservableProperty] private GradingKeyTemplate? _selectedTemplate;
|
[ObservableProperty] private GradingKeyTemplate? _selectedTemplate;
|
||||||
[ObservableProperty] private string _newTemplateName = "";
|
[ObservableProperty] private string _newTemplateName = "";
|
||||||
[ObservableProperty] private string _gradingKeyValidation = "";
|
[ObservableProperty] private string _gradingKeyValidation = "";
|
||||||
|
[ObservableProperty] private string _selectedNiveauName = "–";
|
||||||
|
|
||||||
|
public bool IsDifferentiated { get; }
|
||||||
|
public string[] NiveauOptions => NiveauDisplay.Options;
|
||||||
|
|
||||||
public bool TotalPointsWarning => TotalPoints <= 0;
|
public bool TotalPointsWarning => TotalPoints <= 0;
|
||||||
public string TotalPointsDisplay =>
|
public string TotalPointsDisplay =>
|
||||||
@@ -55,7 +59,7 @@ public partial class ExamDialogViewModel : ObservableObject
|
|||||||
public ExamDialogViewModel(IExamRepository exams, ICompetencyDomainRepository competencyDomains,
|
public ExamDialogViewModel(IExamRepository exams, ICompetencyDomainRepository competencyDomains,
|
||||||
IGradingKeyTemplateRepository gradingKeyTemplates, GradingService grading,
|
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, bool isDifferentiated, Exam? editingExam, Exam? duplicateSource)
|
||||||
{
|
{
|
||||||
_exams = exams; _competencyDomains = competencyDomains;
|
_exams = exams; _competencyDomains = competencyDomains;
|
||||||
_gradingKeyTemplates = gradingKeyTemplates; _grading = grading;
|
_gradingKeyTemplates = gradingKeyTemplates; _grading = grading;
|
||||||
@@ -63,6 +67,7 @@ public partial class ExamDialogViewModel : ObservableObject
|
|||||||
_gradingSystem = gradingSystem;
|
_gradingSystem = gradingSystem;
|
||||||
_editingExam = editingExam;
|
_editingExam = editingExam;
|
||||||
_subjectName = defaultSubjectName;
|
_subjectName = defaultSubjectName;
|
||||||
|
IsDifferentiated = isDifferentiated;
|
||||||
|
|
||||||
HasCompetencyCatalog = subjectId.HasValue
|
HasCompetencyCatalog = subjectId.HasValue
|
||||||
&& _competencyDomains.GetBySubjectAndGrade(subjectId.Value, gradeLevel).Count > 0;
|
&& _competencyDomains.GetBySubjectAndGrade(subjectId.Value, gradeLevel).Count > 0;
|
||||||
@@ -81,6 +86,9 @@ 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") ?? "";
|
||||||
|
// Beim Duplizieren bewusst kein Niveau übernehmen — meist wird dupliziert, um
|
||||||
|
// gerade eine andere Niveau-Variante (E/G/Förder) derselben Klausur anzulegen.
|
||||||
|
SelectedNiveauName = isDuplicate ? "–" : NiveauDisplay.ToName(source.Niveau);
|
||||||
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);
|
||||||
@@ -285,6 +293,7 @@ public partial class ExamDialogViewModel : ObservableObject
|
|||||||
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.ReturnedAt = returnedAt;
|
||||||
|
Result.Niveau = NiveauDisplay.FromName(SelectedNiveauName);
|
||||||
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);
|
||||||
|
|||||||
@@ -221,10 +221,21 @@ public partial class GroupDetailViewModel : ObservableObject
|
|||||||
foreach (var s in enrolled)
|
foreach (var s in enrolled)
|
||||||
{
|
{
|
||||||
enrollments.TryGetValue(s.Id, out var enr);
|
enrollments.TryGetValue(s.Id, out var enr);
|
||||||
Students.Add(new StudentSummary(s, enr));
|
var summary = new StudentSummary(s, enr) { OnChanged = SaveStudentNiveau };
|
||||||
|
Students.Add(summary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SaveStudentNiveau(StudentSummary summary)
|
||||||
|
{
|
||||||
|
if (Group is null) return;
|
||||||
|
var enrollment = _enrollments.GetByGroupAndYear(Group.Id, Group.SchoolYear)
|
||||||
|
.FirstOrDefault(e => e.StudentId == summary.Id);
|
||||||
|
if (enrollment is null) return;
|
||||||
|
enrollment.Niveau = summary.Niveau;
|
||||||
|
_enrollments.Save(enrollment);
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task AddStudent()
|
private async Task AddStudent()
|
||||||
{
|
{
|
||||||
@@ -363,12 +374,47 @@ public partial class GroupDetailViewModel : ObservableObject
|
|||||||
[RelayCommand] private void Refresh() { if (Group is not null) LoadGroup(Group.Id); }
|
[RelayCommand] private void Refresh() { if (Group is not null) LoadGroup(Group.Id); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class StudentSummary
|
// ── Niveau-Anzeige (Niveaudifferenzierung) ────────────────────────────────────
|
||||||
|
|
||||||
|
public static class NiveauDisplay
|
||||||
{
|
{
|
||||||
|
public static string[] Options { get; } = ["–", "E-Niveau", "G-Niveau", "Förderniveau"];
|
||||||
|
|
||||||
|
public static string ToName(Niveau? n) => n switch
|
||||||
|
{
|
||||||
|
Core.Models.Niveau.E => "E-Niveau",
|
||||||
|
Core.Models.Niveau.G => "G-Niveau",
|
||||||
|
Core.Models.Niveau.Foerder => "Förderniveau",
|
||||||
|
_ => "–",
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Niveau? FromName(string? name) => name switch
|
||||||
|
{
|
||||||
|
"E-Niveau" => Core.Models.Niveau.E,
|
||||||
|
"G-Niveau" => Core.Models.Niveau.G,
|
||||||
|
"Förderniveau" => Core.Models.Niveau.Foerder,
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class StudentSummary : ObservableObject
|
||||||
|
{
|
||||||
|
public static string[] NiveauOptions => NiveauDisplay.Options;
|
||||||
|
|
||||||
public Guid Id { get; }
|
public Guid Id { get; }
|
||||||
public string FullName { get; }
|
public string FullName { get; }
|
||||||
public string PeriodLabel { get; }
|
public string PeriodLabel { get; }
|
||||||
|
|
||||||
|
[ObservableProperty] private Niveau? _niveau;
|
||||||
|
|
||||||
|
public string NiveauName
|
||||||
|
{
|
||||||
|
get => NiveauDisplay.ToName(Niveau);
|
||||||
|
set => Niveau = NiveauDisplay.FromName(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action<StudentSummary>? OnChanged { get; set; }
|
||||||
|
|
||||||
public StudentSummary(Core.Models.Student s, Enrollment? e)
|
public StudentSummary(Core.Models.Student s, Enrollment? e)
|
||||||
{
|
{
|
||||||
Id = s.Id;
|
Id = s.Id;
|
||||||
@@ -380,6 +426,13 @@ public class StudentSummary
|
|||||||
EnrollmentPeriod.Custom => BuildCustomLabel(e),
|
EnrollmentPeriod.Custom => BuildCustomLabel(e),
|
||||||
_ => "",
|
_ => "",
|
||||||
};
|
};
|
||||||
|
_niveau = e?.Niveau;
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnNiveauChanged(Niveau? value)
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(NiveauName));
|
||||||
|
OnChanged?.Invoke(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string BuildCustomLabel(Enrollment e)
|
private static string BuildCustomLabel(Enrollment e)
|
||||||
@@ -400,11 +453,13 @@ public class ExamSummary
|
|||||||
public string StatusLabel { get; }
|
public string StatusLabel { get; }
|
||||||
public string StatusColorHex { get; }
|
public string StatusColorHex { get; }
|
||||||
public string ReturnedAtDisplay { get; }
|
public string ReturnedAtDisplay { get; }
|
||||||
|
public string NiveauLabel { 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") ?? "";
|
ReturnedAtDisplay = e.ReturnedAt?.ToString("dd.MM.yyyy") ?? "";
|
||||||
|
NiveauLabel = e.Niveau.HasValue ? NiveauDisplay.ToName(e.Niveau) : "alle";
|
||||||
StatusLabel = e.Status switch
|
StatusLabel = e.Status switch
|
||||||
{
|
{
|
||||||
ExamStatus.Planned => "Geplant",
|
ExamStatus.Planned => "Geplant",
|
||||||
@@ -552,6 +607,7 @@ public partial class AddGroupDialogViewModel : ObservableObject
|
|||||||
[ObservableProperty] private string _selectedSchoolYear = "";
|
[ObservableProperty] private string _selectedSchoolYear = "";
|
||||||
[ObservableProperty] private int? _hoursPerWeek;
|
[ObservableProperty] private int? _hoursPerWeek;
|
||||||
[ObservableProperty] private bool _isOwnClass;
|
[ObservableProperty] private bool _isOwnClass;
|
||||||
|
[ObservableProperty] private bool _isDifferentiated;
|
||||||
[ObservableProperty] private string _validationMessage = "";
|
[ObservableProperty] private string _validationMessage = "";
|
||||||
|
|
||||||
public List<string> SchoolYears { get; }
|
public List<string> SchoolYears { get; }
|
||||||
@@ -589,6 +645,7 @@ public partial class AddGroupDialogViewModel : ObservableObject
|
|||||||
SelectedSchoolYear = group.SchoolYear;
|
SelectedSchoolYear = group.SchoolYear;
|
||||||
HoursPerWeek = group.HoursPerWeek;
|
HoursPerWeek = group.HoursPerWeek;
|
||||||
IsOwnClass = group.IsOwnClass;
|
IsOwnClass = group.IsOwnClass;
|
||||||
|
IsDifferentiated = group.IsDifferentiated;
|
||||||
OnPropertyChanged(nameof(DialogTitle));
|
OnPropertyChanged(nameof(DialogTitle));
|
||||||
OnPropertyChanged(nameof(SaveButtonText));
|
OnPropertyChanged(nameof(SaveButtonText));
|
||||||
}
|
}
|
||||||
@@ -629,6 +686,7 @@ public partial class AddGroupDialogViewModel : ObservableObject
|
|||||||
Result.SchoolYear = SelectedSchoolYear;
|
Result.SchoolYear = SelectedSchoolYear;
|
||||||
Result.HoursPerWeek = HoursPerWeek;
|
Result.HoursPerWeek = HoursPerWeek;
|
||||||
Result.IsOwnClass = IsOwnClass;
|
Result.IsOwnClass = IsOwnClass;
|
||||||
|
Result.IsDifferentiated = IsDifferentiated;
|
||||||
_groups.Save(Result);
|
_groups.Save(Result);
|
||||||
|
|
||||||
if (_editingGroup is not null && _originalSchoolYear != SelectedSchoolYear)
|
if (_editingGroup is not null && _originalSchoolYear != SelectedSchoolYear)
|
||||||
|
|||||||
@@ -67,6 +67,9 @@
|
|||||||
<CheckBox Content="Gehört zu meiner Klasse" IsChecked="{Binding IsOwnClass}"
|
<CheckBox Content="Gehört zu meiner Klasse" IsChecked="{Binding IsOwnClass}"
|
||||||
ToolTip.Tip="Für die farbliche Hervorhebung im Dashboard-Kalender. Mehrere Gruppen (Klasse + zugehörige Kurse) können markiert werden."/>
|
ToolTip.Tip="Für die farbliche Hervorhebung im Dashboard-Kalender. Mehrere Gruppen (Klasse + zugehörige Kurse) können markiert werden."/>
|
||||||
|
|
||||||
|
<CheckBox Content="Niveaudifferenzierung (E/G/Förder)" IsChecked="{Binding IsDifferentiated}"
|
||||||
|
ToolTip.Tip="Blendet im Schüler-Tab eine Niveau-Zuordnung je Schüler ein und erlaubt in Klausuren die Zuordnung zu einem Niveau."/>
|
||||||
|
|
||||||
<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}}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -21,6 +21,13 @@
|
|||||||
<TextBox Text="{Binding Title}" PlaceholderText="z.B. 1. Klausur Kinetik"/>
|
<TextBox Text="{Binding Title}" PlaceholderText="z.B. 1. Klausur Kinetik"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Spacing="4" IsVisible="{Binding IsDifferentiated}">
|
||||||
|
<TextBlock Text="Niveau" FontSize="12" Opacity="0.7"/>
|
||||||
|
<ComboBox ItemsSource="{Binding NiveauOptions}" SelectedItem="{Binding SelectedNiveauName}"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
ToolTip.Tip="Strich = für alle Schüler der Gruppe. Bei E/G/Förder zeigt die Punkteeingabe nur die Schüler mit passendem Niveau."/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<Grid ColumnDefinitions="*,12,*,12,*">
|
<Grid ColumnDefinitions="*,12,*,12,*">
|
||||||
<StackPanel Grid.Column="0" Spacing="4">
|
<StackPanel Grid.Column="0" Spacing="4">
|
||||||
<TextBlock Text="Datum *" FontSize="12" Opacity="0.7"/>
|
<TextBlock Text="Datum *" FontSize="12" Opacity="0.7"/>
|
||||||
|
|||||||
@@ -12,7 +12,13 @@
|
|||||||
<StackPanel Spacing="16" Margin="0,0,12,0">
|
<StackPanel Spacing="16" Margin="0,0,12,0">
|
||||||
|
|
||||||
<StackPanel Spacing="2">
|
<StackPanel Spacing="2">
|
||||||
<TextBlock Text="{Binding ExamTitle}" FontSize="18" FontWeight="SemiBold"/>
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<TextBlock Text="{Binding ExamTitle}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||||||
|
<Border Background="{DynamicResource SystemControlBackgroundAccentBrush}" CornerRadius="4" Padding="8,2"
|
||||||
|
IsVisible="{Binding NiveauLabel, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
||||||
|
<TextBlock Text="{Binding NiveauLabel}" FontSize="12" Foreground="White"/>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
<TextBlock Text="{Binding ExamDateLabel}" FontSize="12" Opacity="0.6"/>
|
<TextBlock Text="{Binding ExamDateLabel}" FontSize="12" Opacity="0.6"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Spacing="10" Margin="0,0,0,10">
|
<StackPanel Grid.Row="0" Orientation="Horizontal" Spacing="10" Margin="0,0,0,10">
|
||||||
<TextBlock Text="{Binding ExamTitle}" FontSize="16" FontWeight="SemiBold" VerticalAlignment="Center"/>
|
<TextBlock Text="{Binding ExamTitle}" FontSize="16" FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||||||
<TextBlock Text="{Binding ExamDateLabel}" FontSize="13" Opacity="0.6" VerticalAlignment="Center"/>
|
<TextBlock Text="{Binding ExamDateLabel}" FontSize="13" Opacity="0.6" VerticalAlignment="Center"/>
|
||||||
|
<Border Background="{DynamicResource SystemControlBackgroundAccentBrush}" CornerRadius="4" Padding="8,2"
|
||||||
|
IsVisible="{Binding NiveauLabel, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
||||||
|
<TextBlock Text="{Binding NiveauLabel}" FontSize="12" Foreground="White"/>
|
||||||
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<DataGrid Grid.Row="1" Name="ResultGrid"
|
<DataGrid Grid.Row="1" Name="ResultGrid"
|
||||||
|
|||||||
@@ -56,6 +56,16 @@
|
|||||||
<DataGrid.Columns>
|
<DataGrid.Columns>
|
||||||
<DataGridTextColumn Header="Name" Binding="{Binding FullName}" Width="*"/>
|
<DataGridTextColumn Header="Name" Binding="{Binding FullName}" Width="*"/>
|
||||||
<DataGridTextColumn Header="Zeitraum" Binding="{Binding PeriodLabel}" Width="90"/>
|
<DataGridTextColumn Header="Zeitraum" Binding="{Binding PeriodLabel}" Width="90"/>
|
||||||
|
<DataGridTemplateColumn Header="Niveau" Width="130"
|
||||||
|
IsVisible="{Binding Group.IsDifferentiated}">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate x:DataType="vm:StudentSummary">
|
||||||
|
<ComboBox ItemsSource="{x:Static vm:StudentSummary.NiveauOptions}"
|
||||||
|
SelectedItem="{Binding NiveauName}"
|
||||||
|
HorizontalAlignment="Stretch" Margin="2"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
</DataGrid.Columns>
|
</DataGrid.Columns>
|
||||||
</DataGrid>
|
</DataGrid>
|
||||||
</ContentPage>
|
</ContentPage>
|
||||||
@@ -99,6 +109,8 @@
|
|||||||
<DataGrid.Columns>
|
<DataGrid.Columns>
|
||||||
<DataGridTextColumn Header="Datum" Binding="{Binding Date}" Width="110"/>
|
<DataGridTextColumn Header="Datum" Binding="{Binding Date}" Width="110"/>
|
||||||
<DataGridTextColumn Header="Titel" Binding="{Binding Title}" Width="*"/>
|
<DataGridTextColumn Header="Titel" Binding="{Binding Title}" Width="*"/>
|
||||||
|
<DataGridTextColumn Header="Niveau" Binding="{Binding NiveauLabel}" Width="80"
|
||||||
|
IsVisible="{Binding Group.IsDifferentiated}"/>
|
||||||
<DataGridTemplateColumn Header="Status" Width="150">
|
<DataGridTemplateColumn Header="Status" Width="150">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate x:DataType="vm:ExamSummary">
|
<DataTemplate x:DataType="vm:ExamSummary">
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public partial class GroupDetailView : UserControl
|
|||||||
App.Services.GetRequiredService<IGradingKeyTemplateRepository>(),
|
App.Services.GetRequiredService<IGradingKeyTemplateRepository>(),
|
||||||
App.Services.GetRequiredService<GradingService>(),
|
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 ?? "", vm.Group.IsDifferentiated, editingExam, duplicateSource);
|
||||||
|
|
||||||
var dialog = new ExamDialog { DataContext = dialogVm };
|
var dialog = new ExamDialog { DataContext = dialogVm };
|
||||||
var owner = TopLevel.GetTopLevel(this) as Window;
|
var owner = TopLevel.GetTopLevel(this) as Window;
|
||||||
|
|||||||
@@ -31,6 +31,15 @@ Diese Regeln gelten für jede Aufgabe unten und müssen nicht wiederholt werden.
|
|||||||
Modelle `Exam`, `ExamTask`, `GradingKeyEntry`, `ExamResult` existieren bereits in
|
Modelle `Exam`, `ExamTask`, `GradingKeyEntry`, `ExamResult` existieren bereits in
|
||||||
[Exam.cs](LehrerApp.Core/Models/Exam.cs), Repositories ebenfalls. Die komplette UI fehlt.
|
[Exam.cs](LehrerApp.Core/Models/Exam.cs), Repositories ebenfalls. Die komplette UI fehlt.
|
||||||
|
|
||||||
|
**Niveaudifferenzierung (E/G/Förder) ist bereits umgesetzt** — nicht aus dieser Liste, sondern
|
||||||
|
ein separater Bedarf (Nachteilsausgleich/Binnendifferenzierung): `LearningGroup.IsDifferentiated`
|
||||||
|
(Checkbox in den Stammdaten) blendet eine Niveau-Zuordnung je Schüler ein (`Enrollment.Niveau`,
|
||||||
|
Schüler-Tab). Klausuren bekommen optional ein `Niveau`; Punkteeingabe und Auswertung filtern dann
|
||||||
|
automatisch auf die passenden Schüler. Workflow: G-Klausur normal anlegen, per "Duplizieren"
|
||||||
|
(1.1.4) die E-Variante mit eigenen Aufgaben/Punkten ableiten (Niveau wird beim Duplizieren bewusst
|
||||||
|
zurückgesetzt). Vollständig individuelle Förderklausuren (nur für einen einzelnen Schüler) laufen
|
||||||
|
pragmatisch über "Abwesend" bei den übrigen Schülern statt über eine eigene Schüler-Zuordnung.
|
||||||
|
|
||||||
### 1.1 Klausur anlegen und verwalten
|
### 1.1 Klausur anlegen und verwalten
|
||||||
- [x] **1.1.1** Dialog `AddExamDialog` — Titel, Datum, Fach, Klausurnummer, Notizen.
|
- [x] **1.1.1** Dialog `AddExamDialog` — Titel, Datum, Fach, Klausurnummer, Notizen.
|
||||||
Ersetzt den TODO-Stub `AddExam()` in [GroupViewModels.cs](LehrerApp.Desktop/ViewModels/Groups/GroupViewModels.cs).
|
Ersetzt den TODO-Stub `AddExam()` in [GroupViewModels.cs](LehrerApp.Desktop/ViewModels/Groups/GroupViewModels.cs).
|
||||||
@@ -68,7 +77,7 @@ Modelle `Exam`, `ExamTask`, `GradingKeyEntry`, `ExamResult` existieren bereits i
|
|||||||
Pfeil-Runter springen zur gleichen Spalte in der Nachbarzeile, direkte Zifferneingabe
|
Pfeil-Runter springen zur gleichen Spalte in der Nachbarzeile, direkte Zifferneingabe
|
||||||
und halbe Punkte (Komma oder Punkt) erlaubt.
|
und halbe Punkte (Komma oder Punkt) erlaubt.
|
||||||
- [x] **1.4.3** Kennzeichnung "Abwesend" (`ExamResult.Absent`) per Checkbox — Note zeigt "abwesend"
|
- [x] **1.4.3** Kennzeichnung "Abwesend" (`ExamResult.Absent`) per Checkbox — Note zeigt "abwesend"
|
||||||
statt einer berechneten Note. Ausschluss aus Statistiken folgt mit 1.5 (noch nicht umgesetzt).
|
statt einer berechneten Note. Ausschluss aus Statistiken über 1.5 umgesetzt.
|
||||||
- [x] **1.4.4** Kommentarfeld pro Schüler (`ExamResult.Comment`).
|
- [x] **1.4.4** Kommentarfeld pro Schüler (`ExamResult.Comment`).
|
||||||
- [x] **1.4.5** Validierung: Punkte < 0 oder > Maximalpunkte der Aufgabe wird rot markiert und nicht
|
- [x] **1.4.5** Validierung: Punkte < 0 oder > Maximalpunkte der Aufgabe wird rot markiert und nicht
|
||||||
gespeichert (`PointsCell.TrySetValue`).
|
gespeichert (`PointsCell.TrySetValue`).
|
||||||
@@ -258,6 +267,8 @@ Navigationspunkt "Arbeitszeit" ist ein `PlaceholderViewModel`.
|
|||||||
|
|
||||||
Grundfunktionen sind vorhanden ([StudentViewModels.cs](LehrerApp.Desktop/ViewModels/Students/StudentViewModels.cs),
|
Grundfunktionen sind vorhanden ([StudentViewModels.cs](LehrerApp.Desktop/ViewModels/Students/StudentViewModels.cs),
|
||||||
[GroupViewModels.cs](LehrerApp.Desktop/ViewModels/Groups/GroupViewModels.cs)).
|
[GroupViewModels.cs](LehrerApp.Desktop/ViewModels/Groups/GroupViewModels.cs)).
|
||||||
|
Niveau-Zuordnung je Schüler (E/G/Förder, `Enrollment.Niveau`) ist bereits umgesetzt, siehe
|
||||||
|
Hinweis in Kapitel 1 — betrifft auch Kurse, nicht nur Klassen.
|
||||||
|
|
||||||
### 7.1 Schüler
|
### 7.1 Schüler
|
||||||
- [ ] **7.1.1** Schüler löschen / auf inaktiv setzen inkl. Hinweis auf verknüpfte Daten.
|
- [ ] **7.1.1** Schüler löschen / auf inaktiv setzen inkl. Hinweis auf verknüpfte Daten.
|
||||||
@@ -267,7 +278,9 @@ Grundfunktionen sind vorhanden ([StudentViewModels.cs](LehrerApp.Desktop/ViewMod
|
|||||||
- [ ] **7.1.5** Sitzplan je Gruppe (Raster mit Drag & Drop), Sprung von Sitzplatz zur Bewertung.
|
- [ ] **7.1.5** Sitzplan je Gruppe (Raster mit Drag & Drop), Sprung von Sitzplatz zur Bewertung.
|
||||||
|
|
||||||
### 7.2 Gruppen
|
### 7.2 Gruppen
|
||||||
- [ ] **7.2.1** Gruppe bearbeiten und löschen (aktuell nur Anlegen möglich).
|
- [x] **7.2.1** Gruppe bearbeiten und löschen — bereits vorhanden (`EditGroupCommand`/`DeleteGroupCommand`/
|
||||||
|
`ToggleArchiveCommand` in `GroupListViewModel`), nicht Teil der aktuellen Klausuren-Arbeit,
|
||||||
|
beim Review aber bestätigt.
|
||||||
- [ ] **7.2.2** Gruppe ins neue Schuljahr übernehmen: Kopie mit gleicher Schülerschaft,
|
- [ ] **7.2.2** Gruppe ins neue Schuljahr übernehmen: Kopie mit gleicher Schülerschaft,
|
||||||
neues `SchoolYear`, neue `Enrollment`-Einträge.
|
neues `SchoolYear`, neue `Enrollment`-Einträge.
|
||||||
- [ ] **7.2.3** Schüler aus einer Gruppe entfernen (`Enrollment.LeftAt` setzen statt löschen).
|
- [ ] **7.2.3** Schüler aus einer Gruppe entfernen (`Enrollment.LeftAt` setzen statt löschen).
|
||||||
@@ -296,7 +309,7 @@ Format dokumentiert in [Kompetenzkatalog-KI-Prompt.md](docs/Kompetenzkatalog-KI-
|
|||||||
|
|
||||||
### 8.2 Verwendung im Unterricht
|
### 8.2 Verwendung im Unterricht
|
||||||
- [ ] **8.2.1** Kompetenzen einer Unterrichtseinheit zuordnen (siehe 4.1.3).
|
- [ ] **8.2.1** Kompetenzen einer Unterrichtseinheit zuordnen (siehe 4.1.3).
|
||||||
- [ ] **8.2.2** Kompetenzen einzelnen Klausuraufgaben zuordnen (siehe 1.2.4).
|
- [x] **8.2.2** Kompetenzen einzelnen Klausuraufgaben zuordnen — umgesetzt mit 1.2.4.
|
||||||
- [ ] **8.2.3** Abdeckungsübersicht: welche Kompetenzen wurden im Schuljahr behandelt/geprüft?
|
- [ ] **8.2.3** Abdeckungsübersicht: welche Kompetenzen wurden im Schuljahr behandelt/geprüft?
|
||||||
|
|
||||||
### 8.3 Kompetenzorientierte Auswertung
|
### 8.3 Kompetenzorientierte Auswertung
|
||||||
|
|||||||
Reference in New Issue
Block a user