Implementiert TODO 1.1 (Klausur anlegen/verwalten) und die dazu passenden Teile von 1.2 (Aufgabenstruktur), da beides im selben Dialog verwaltet wird: - ExamDialog: Klausur anlegen/bearbeiten/duplizieren mit Aufgaben-Editor (Nr./Titel/Maximalpunkte/Gewichtung, sortierbar), Gesamtpunkte-Anzeige mit Warnung bei 0 Punkten, und Kompetenz-Zuordnung je Aufgabe. - Klausuren-Tab: Kontextmenü und Toolbar für Bearbeiten/Duplizieren/ Löschen (mit Rückfrage), farbcodierte Status-Spalte, SplitButton für Statuswechsel (Klick = nächster Status, Dropdown = manuelles Setzen). - ExamRepository.Delete löscht zugehörige ExamResults kaskadierend. - Gewichtung ist standardmäßig ausgeblendet (Checkbox zum Einblenden), NumericUpDown-Felder ohne Spinner-Buttons wegen eines Layout-Bugs im Avalonia-Standardtemplate, der das interne Textfeld auf wenige Pixel schrumpfen ließ. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
613 lines
24 KiB
C#
613 lines
24 KiB
C#
using CommunityToolkit.Mvvm.ComponentModel;
|
||
using CommunityToolkit.Mvvm.Input;
|
||
using LehrerApp.Core.Interfaces;
|
||
using LehrerApp.Core.Models;
|
||
using LehrerApp.Core.Services;
|
||
using System.Collections.ObjectModel;
|
||
|
||
namespace LehrerApp.Desktop.ViewModels.Groups;
|
||
|
||
// ── Gruppenliste ──────────────────────────────────────────────────────────────
|
||
|
||
public partial class GroupListViewModel : ObservableObject
|
||
{
|
||
private readonly IGroupRepository _groups;
|
||
private readonly SchoolYearService _sy;
|
||
|
||
public Action<Guid, int>? OnNavigateToDetail { get; set; }
|
||
public Func<Task>? OnAddGroup { get; set; }
|
||
public Func<Guid, Task>? OnEditGroup { get; set; }
|
||
public Func<GroupListItem, Task<bool>>? OnConfirmDelete { get; set; }
|
||
|
||
[ObservableProperty] private string _selectedSchoolYear = "";
|
||
[ObservableProperty] private string _searchText = "";
|
||
[ObservableProperty] private GroupListItem? _selectedGroup;
|
||
[ObservableProperty] private bool _showArchived;
|
||
|
||
public string SelectedGroupDisplayName => SelectedGroup?.DisplayName ?? "";
|
||
public string SelectedGroupSubtitle => SelectedGroup?.Subtitle ?? "";
|
||
public string ListSummary => ShowArchived
|
||
? $"{Groups.Count} archivierte Gruppen · {SelectedSchoolYear}"
|
||
: $"{Groups.Count} aktive Gruppen · {SelectedSchoolYear}";
|
||
public bool HasNoGroups => Groups.Count == 0;
|
||
public string EmptyListMessage => ShowArchived
|
||
? "Keine archivierten Lerngruppen in diesem Schuljahr."
|
||
: "Noch keine aktiven Lerngruppen in diesem Schuljahr.";
|
||
|
||
public ObservableCollection<string> SchoolYears { get; } = [];
|
||
public ObservableCollection<GroupListItem> Groups { get; } = [];
|
||
|
||
public GroupListViewModel(IGroupRepository groups, SchoolYearService sy)
|
||
{
|
||
_groups = groups; _sy = sy;
|
||
foreach (var y in sy.RecentSchoolYears()) SchoolYears.Add(y);
|
||
SelectedSchoolYear = sy.CurrentSchoolYear();
|
||
}
|
||
|
||
partial void OnSelectedSchoolYearChanged(string value) => LoadGroups();
|
||
partial void OnSearchTextChanged(string value) => LoadGroups();
|
||
partial void OnShowArchivedChanged(bool value) => LoadGroups();
|
||
partial void OnSelectedGroupChanged(GroupListItem? value)
|
||
{
|
||
OnPropertyChanged(nameof(SelectedGroupDisplayName));
|
||
OnPropertyChanged(nameof(SelectedGroupSubtitle));
|
||
NavigateToSectionCommand.NotifyCanExecuteChanged();
|
||
EditGroupCommand.NotifyCanExecuteChanged();
|
||
ToggleArchiveCommand.NotifyCanExecuteChanged();
|
||
DeleteGroupCommand.NotifyCanExecuteChanged();
|
||
}
|
||
|
||
public void LoadGroups()
|
||
{
|
||
var selectedId = SelectedGroup?.Id;
|
||
Groups.Clear();
|
||
var all = _groups.GetBySchoolYear(SelectedSchoolYear, includeInactive: ShowArchived)
|
||
.Where(g => g.IsActive != ShowArchived);
|
||
var filtered = string.IsNullOrWhiteSpace(SearchText) ? all
|
||
: all.Where(g => g.Name.Contains(SearchText, StringComparison.OrdinalIgnoreCase)
|
||
|| (g.Subject?.Contains(SearchText, StringComparison.OrdinalIgnoreCase) ?? false));
|
||
foreach (var g in filtered.OrderBy(g => g.Name))
|
||
Groups.Add(new GroupListItem(g));
|
||
SelectedGroup = Groups.FirstOrDefault(g => g.Id == selectedId);
|
||
OnPropertyChanged(nameof(ListSummary));
|
||
OnPropertyChanged(nameof(HasNoGroups));
|
||
OnPropertyChanged(nameof(EmptyListMessage));
|
||
}
|
||
|
||
[RelayCommand]
|
||
private async Task AddGroup()
|
||
{
|
||
if (OnAddGroup is null) return;
|
||
await OnAddGroup();
|
||
LoadGroups();
|
||
}
|
||
[RelayCommand] private void Refresh() => LoadGroups();
|
||
|
||
[RelayCommand(CanExecute = nameof(HasSelectedGroup))]
|
||
private async Task EditGroup()
|
||
{
|
||
if (SelectedGroup is null || OnEditGroup is null) return;
|
||
var id = SelectedGroup.Id;
|
||
await OnEditGroup(id);
|
||
LoadGroups();
|
||
SelectedGroup = Groups.FirstOrDefault(g => g.Id == id);
|
||
}
|
||
|
||
[RelayCommand(CanExecute = nameof(HasSelectedGroup))]
|
||
private void ToggleArchive()
|
||
{
|
||
if (SelectedGroup is null) return;
|
||
var group = _groups.GetById(SelectedGroup.Id);
|
||
if (group is null) return;
|
||
group.IsActive = !group.IsActive;
|
||
_groups.Save(group);
|
||
LoadGroups();
|
||
}
|
||
|
||
[RelayCommand(CanExecute = nameof(HasSelectedGroup))]
|
||
private async Task DeleteGroup()
|
||
{
|
||
if (SelectedGroup is null || OnConfirmDelete is null) return;
|
||
var selected = SelectedGroup;
|
||
if (!await OnConfirmDelete(selected)) return;
|
||
_groups.Delete(selected.Id);
|
||
LoadGroups();
|
||
}
|
||
|
||
[RelayCommand(CanExecute = nameof(HasSelectedGroup))]
|
||
private void NavigateToSection(string? tabIndex)
|
||
{
|
||
if (SelectedGroup is null || !int.TryParse(tabIndex, out var tab)) return;
|
||
OnNavigateToDetail?.Invoke(SelectedGroup.Id, tab);
|
||
}
|
||
|
||
private bool HasSelectedGroup() => SelectedGroup is not null;
|
||
}
|
||
|
||
public class GroupListItem
|
||
{
|
||
public Guid Id { get; }
|
||
public string Name { get; }
|
||
public string Subject { get; }
|
||
public string DisplayName { get; }
|
||
public string TypeLabel { get; }
|
||
public string GradingLabel { get; }
|
||
public string Subtitle { get; }
|
||
public bool IsActive { get; }
|
||
public string ArchiveActionLabel => IsActive ? "Archivieren" : "Wieder aktivieren";
|
||
|
||
public GroupListItem(LearningGroup g)
|
||
{
|
||
Id = g.Id;
|
||
IsActive = g.IsActive;
|
||
Name = g.Name;
|
||
Subject = g.Subject ?? "";
|
||
TypeLabel = g.Type == GroupType.Class ? "Klasse" : "Kurs";
|
||
GradingLabel = g.GradingSystem == GradingSystem.Grades1To6 ? "1–6" : "0–15";
|
||
DisplayName = string.IsNullOrEmpty(g.Subject) ? g.Name : $"{g.Name} · {g.Subject}";
|
||
Subtitle = $"{TypeLabel} · Stufe {g.GradeLevel} · Noten {GradingLabel} · {g.SchoolYear}";
|
||
}
|
||
}
|
||
|
||
// ── Gruppendetail ─────────────────────────────────────────────────────────────
|
||
|
||
public partial class GroupDetailViewModel : ObservableObject
|
||
{
|
||
private readonly IGroupRepository _groups;
|
||
private readonly IStudentRepository _students;
|
||
private readonly IEnrollmentRepository _enrollments;
|
||
private readonly IExamRepository _exams;
|
||
private readonly IGradeRepository _grades;
|
||
|
||
[ObservableProperty] private LearningGroup? _group;
|
||
[ObservableProperty] private string _groupTitle = "";
|
||
[ObservableProperty] private string _groupSubtitle = "";
|
||
[ObservableProperty] private int _studentCount;
|
||
[ObservableProperty] private int _activeTabIndex = 0;
|
||
[ObservableProperty] private StudentSummary? _selectedStudent;
|
||
[ObservableProperty] private ExamSummary? _selectedExam;
|
||
|
||
public ObservableCollection<StudentSummary> Students { get; } = [];
|
||
public ObservableCollection<ExamSummary> Exams { get; } = [];
|
||
|
||
public ParticipationTabViewModel ParticipationTab { get; }
|
||
public Func<Task<bool>>? OnAddStudent { get; set; }
|
||
public Func<Guid, Task<bool>>? OnAddExam { get; set; }
|
||
public Func<Exam, Task<bool>>? OnEditExam { get; set; }
|
||
public Func<Exam, Task<bool>>? OnDuplicateExam { get; set; }
|
||
public Func<ExamSummary, Task<bool>>? OnConfirmDeleteExam { get; set; }
|
||
|
||
public GroupDetailViewModel(IGroupRepository groups, IStudentRepository students,
|
||
IEnrollmentRepository enrollments, IExamRepository exams, IGradeRepository grades,
|
||
ParticipationTabViewModel participationTab)
|
||
{
|
||
_groups = groups; _students = students; _enrollments = enrollments;
|
||
_exams = exams; _grades = grades;
|
||
ParticipationTab = participationTab;
|
||
}
|
||
|
||
public void LoadGroup(Guid id)
|
||
{
|
||
Group = _groups.GetById(id);
|
||
if (Group is null) return;
|
||
GroupTitle = Group.Name;
|
||
GroupSubtitle = $"{Group.SchoolYear} · " +
|
||
$"{(Group.Type == GroupType.Class ? "Klasse" : "Kurs")} · " +
|
||
$"Stufe {Group.GradeLevel} · Noten {(Group.GradingSystem == GradingSystem.Grades1To6 ? "1–6" : "0–15")}";
|
||
LoadStudents();
|
||
ReloadExams();
|
||
ParticipationTab.Initialize(Group.Id, Group.SchoolYear);
|
||
}
|
||
|
||
private void ReloadExams()
|
||
{
|
||
if (Group is null) return;
|
||
var selectedId = SelectedExam?.Id;
|
||
Exams.Clear();
|
||
foreach (var e in _exams.GetByGroup(Group.Id)) Exams.Add(new ExamSummary(e));
|
||
SelectedExam = Exams.FirstOrDefault(e => e.Id == selectedId);
|
||
}
|
||
|
||
public void LoadStudents()
|
||
{
|
||
if (Group is null) return;
|
||
Students.Clear();
|
||
var enrolled = _students.GetByGroup(Group.Id, Group.SchoolYear);
|
||
var enrollments = _enrollments.GetByGroupAndYear(Group.Id, Group.SchoolYear)
|
||
.ToDictionary(e => e.StudentId);
|
||
StudentCount = enrolled.Count;
|
||
foreach (var s in enrolled)
|
||
{
|
||
enrollments.TryGetValue(s.Id, out var enr);
|
||
Students.Add(new StudentSummary(s, enr));
|
||
}
|
||
}
|
||
|
||
[RelayCommand]
|
||
private async Task AddStudent()
|
||
{
|
||
if (OnAddStudent is null) return;
|
||
var confirmed = await OnAddStudent();
|
||
if (confirmed)
|
||
{
|
||
LoadStudents();
|
||
ParticipationTab.RefreshCurrentGrid();
|
||
}
|
||
}
|
||
|
||
[RelayCommand(CanExecute = nameof(HasSelectedStudent))]
|
||
private void RemoveStudent()
|
||
{
|
||
if (Group is null || SelectedStudent is null) return;
|
||
var enrollment = _enrollments.GetByGroupAndYear(Group.Id, Group.SchoolYear)
|
||
.FirstOrDefault(e => e.StudentId == SelectedStudent.Id);
|
||
if (enrollment is null) return;
|
||
_enrollments.Delete(enrollment.Id);
|
||
LoadStudents();
|
||
SelectedStudent = null;
|
||
ParticipationTab.RefreshCurrentGrid();
|
||
}
|
||
|
||
partial void OnSelectedStudentChanged(StudentSummary? value) =>
|
||
RemoveStudentCommand.NotifyCanExecuteChanged();
|
||
|
||
private bool HasSelectedStudent() => SelectedStudent is not null;
|
||
|
||
[RelayCommand]
|
||
private async Task AddExam()
|
||
{
|
||
if (Group is null || OnAddExam is null) return;
|
||
var saved = await OnAddExam(Group.Id);
|
||
if (saved) ReloadExams();
|
||
}
|
||
|
||
[RelayCommand(CanExecute = nameof(HasSelectedExam))]
|
||
private async Task EditExam()
|
||
{
|
||
if (SelectedExam is null || OnEditExam is null) return;
|
||
var exam = _exams.GetById(SelectedExam.Id);
|
||
if (exam is null) return;
|
||
var id = exam.Id;
|
||
var saved = await OnEditExam(exam);
|
||
if (saved)
|
||
{
|
||
ReloadExams();
|
||
SelectedExam = Exams.FirstOrDefault(e => e.Id == id);
|
||
}
|
||
}
|
||
|
||
[RelayCommand(CanExecute = nameof(HasSelectedExam))]
|
||
private async Task DuplicateExam()
|
||
{
|
||
if (SelectedExam is null || OnDuplicateExam is null) return;
|
||
var exam = _exams.GetById(SelectedExam.Id);
|
||
if (exam is null) return;
|
||
var saved = await OnDuplicateExam(exam);
|
||
if (saved) ReloadExams();
|
||
}
|
||
|
||
[RelayCommand(CanExecute = nameof(HasSelectedExam))]
|
||
private async Task DeleteExam()
|
||
{
|
||
if (SelectedExam is null || OnConfirmDeleteExam is null) return;
|
||
var selected = SelectedExam;
|
||
if (!await OnConfirmDeleteExam(selected)) return;
|
||
_exams.Delete(selected.Id);
|
||
SelectedExam = null;
|
||
ReloadExams();
|
||
}
|
||
|
||
[RelayCommand(CanExecute = nameof(HasSelectedExam))]
|
||
private void AdvanceExamStatus()
|
||
{
|
||
if (SelectedExam is null) return;
|
||
SetExamStatus(SelectedExam.Status switch
|
||
{
|
||
ExamStatus.Planned => ExamStatus.Conducted,
|
||
ExamStatus.Conducted => ExamStatus.Graded,
|
||
ExamStatus.Graded => ExamStatus.Returned,
|
||
_ => SelectedExam.Status,
|
||
});
|
||
}
|
||
|
||
[RelayCommand(CanExecute = nameof(HasSelectedExam))]
|
||
private void SetExamStatus(ExamStatus status)
|
||
{
|
||
if (SelectedExam is null) return;
|
||
var exam = _exams.GetById(SelectedExam.Id);
|
||
if (exam is null) return;
|
||
exam.Status = status;
|
||
_exams.Save(exam);
|
||
var id = exam.Id;
|
||
ReloadExams();
|
||
SelectedExam = Exams.FirstOrDefault(e => e.Id == id);
|
||
}
|
||
|
||
partial void OnSelectedExamChanged(ExamSummary? value)
|
||
{
|
||
EditExamCommand.NotifyCanExecuteChanged();
|
||
DuplicateExamCommand.NotifyCanExecuteChanged();
|
||
DeleteExamCommand.NotifyCanExecuteChanged();
|
||
AdvanceExamStatusCommand.NotifyCanExecuteChanged();
|
||
SetExamStatusCommand.NotifyCanExecuteChanged();
|
||
}
|
||
|
||
private bool HasSelectedExam() => SelectedExam is not null;
|
||
|
||
[RelayCommand] private void Refresh() { if (Group is not null) LoadGroup(Group.Id); }
|
||
}
|
||
|
||
public class StudentSummary
|
||
{
|
||
public Guid Id { get; }
|
||
public string FullName { get; }
|
||
public string PeriodLabel { get; }
|
||
|
||
public StudentSummary(Core.Models.Student s, Enrollment? e)
|
||
{
|
||
Id = s.Id;
|
||
FullName = s.FullName;
|
||
PeriodLabel = e?.Period switch
|
||
{
|
||
EnrollmentPeriod.H1Only => "H1",
|
||
EnrollmentPeriod.H2Only => "H2",
|
||
EnrollmentPeriod.Custom => BuildCustomLabel(e),
|
||
_ => "",
|
||
};
|
||
}
|
||
|
||
private static string BuildCustomLabel(Enrollment e)
|
||
{
|
||
if (e.JoinedAt.HasValue && e.LeftAt.HasValue) return $"{e.JoinedAt:dd.MM.}–{e.LeftAt:dd.MM.}";
|
||
if (e.JoinedAt.HasValue) return $"ab {e.JoinedAt:dd.MM.}";
|
||
if (e.LeftAt.HasValue) return $"bis {e.LeftAt:dd.MM.}";
|
||
return "Datum";
|
||
}
|
||
}
|
||
|
||
public class ExamSummary
|
||
{
|
||
public Guid Id { get; }
|
||
public string Title { get; }
|
||
public string Date { get; }
|
||
public ExamStatus Status { get; }
|
||
public string StatusLabel { get; }
|
||
public string StatusColorHex { get; }
|
||
|
||
public ExamSummary(Core.Models.Exam e)
|
||
{
|
||
Id = e.Id; Title = e.Title; Date = e.Date.ToString("dd.MM.yyyy"); Status = e.Status;
|
||
StatusLabel = e.Status switch
|
||
{
|
||
ExamStatus.Planned => "Geplant",
|
||
ExamStatus.Conducted => "Durchgeführt",
|
||
ExamStatus.Graded => "Korrigiert",
|
||
ExamStatus.Returned => "Zurückgegeben",
|
||
_ => "",
|
||
};
|
||
StatusColorHex = e.Status switch
|
||
{
|
||
ExamStatus.Planned => "#9E9E9E",
|
||
ExamStatus.Conducted => "#FB8C00",
|
||
ExamStatus.Graded => "#43A047",
|
||
ExamStatus.Returned => "#1E88E5",
|
||
_ => "#9E9E9E",
|
||
};
|
||
}
|
||
}
|
||
|
||
// ── Dialog: Schüler zur Gruppe hinzufügen ─────────────────────────────────────
|
||
|
||
public partial class AddStudentToGroupDialogViewModel : ObservableObject
|
||
{
|
||
private readonly IStudentRepository _students;
|
||
private readonly IEnrollmentRepository _enrollments;
|
||
private readonly Guid _groupId;
|
||
private readonly string _schoolYear;
|
||
|
||
[ObservableProperty] private string _searchText = "";
|
||
[ObservableProperty] private StudentPickerItem? _selectedStudent;
|
||
[ObservableProperty] private string _validationMessage = "";
|
||
[ObservableProperty] private EnrollmentPeriod _period = EnrollmentPeriod.FullYear;
|
||
[ObservableProperty] private string _joinedAtText = "";
|
||
[ObservableProperty] private string _leftAtText = "";
|
||
|
||
public bool IsFullYear { get => Period == EnrollmentPeriod.FullYear; set { if (value) Period = EnrollmentPeriod.FullYear; } }
|
||
public bool IsH1Only { get => Period == EnrollmentPeriod.H1Only; set { if (value) Period = EnrollmentPeriod.H1Only; } }
|
||
public bool IsH2Only { get => Period == EnrollmentPeriod.H2Only; set { if (value) Period = EnrollmentPeriod.H2Only; } }
|
||
public bool IsCustom { get => Period == EnrollmentPeriod.Custom; set { if (value) Period = EnrollmentPeriod.Custom; } }
|
||
public bool IsCustomPeriod => Period == EnrollmentPeriod.Custom;
|
||
|
||
public ObservableCollection<StudentPickerItem> AvailableStudents { get; } = [];
|
||
public Enrollment? Result { get; private set; }
|
||
|
||
public AddStudentToGroupDialogViewModel(IStudentRepository students,
|
||
IEnrollmentRepository enrollments, Guid groupId, string schoolYear)
|
||
{
|
||
_students = students; _enrollments = enrollments;
|
||
_groupId = groupId; _schoolYear = schoolYear;
|
||
LoadAvailableStudents();
|
||
}
|
||
|
||
partial void OnPeriodChanged(EnrollmentPeriod value)
|
||
{
|
||
OnPropertyChanged(nameof(IsFullYear));
|
||
OnPropertyChanged(nameof(IsH1Only));
|
||
OnPropertyChanged(nameof(IsH2Only));
|
||
OnPropertyChanged(nameof(IsCustom));
|
||
OnPropertyChanged(nameof(IsCustomPeriod));
|
||
}
|
||
|
||
partial void OnSearchTextChanged(string value) => LoadAvailableStudents();
|
||
|
||
private void LoadAvailableStudents()
|
||
{
|
||
var alreadyEnrolled = _enrollments.GetByGroupAndYear(_groupId, _schoolYear)
|
||
.Select(e => e.StudentId).ToHashSet();
|
||
var all = _students.GetAll();
|
||
var available = all
|
||
.Where(s => !alreadyEnrolled.Contains(s.Id))
|
||
.Where(s => string.IsNullOrWhiteSpace(SearchText) ||
|
||
s.FullName.Contains(SearchText, StringComparison.OrdinalIgnoreCase));
|
||
AvailableStudents.Clear();
|
||
foreach (var s in available) AvailableStudents.Add(new StudentPickerItem(s));
|
||
}
|
||
|
||
[RelayCommand]
|
||
private void Save()
|
||
{
|
||
if (SelectedStudent is null) { ValidationMessage = "Bitte einen Schüler auswählen."; return; }
|
||
|
||
DateOnly? joinedAt = null, leftAt = null;
|
||
if (Period == EnrollmentPeriod.Custom)
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(JoinedAtText) &&
|
||
DateOnly.TryParseExact(JoinedAtText, "dd.MM.yyyy",
|
||
null, System.Globalization.DateTimeStyles.None, out var j))
|
||
joinedAt = j;
|
||
if (!string.IsNullOrWhiteSpace(LeftAtText) &&
|
||
DateOnly.TryParseExact(LeftAtText, "dd.MM.yyyy",
|
||
null, System.Globalization.DateTimeStyles.None, out var l))
|
||
leftAt = l;
|
||
}
|
||
|
||
Result = new Enrollment
|
||
{
|
||
StudentId = SelectedStudent.Id,
|
||
GroupId = _groupId,
|
||
SchoolYear = _schoolYear,
|
||
Period = Period,
|
||
JoinedAt = joinedAt,
|
||
LeftAt = leftAt,
|
||
};
|
||
_enrollments.Save(Result);
|
||
}
|
||
}
|
||
|
||
public class StudentPickerItem
|
||
{
|
||
public Guid Id { get; }
|
||
public string FullName { get; }
|
||
public StudentPickerItem(Student s) { Id = s.Id; FullName = s.FullName; }
|
||
}
|
||
|
||
// ── Dialog: Neue Lerngruppe anlegen ──────────────────────────────────────────
|
||
|
||
public partial class AddGroupDialogViewModel : ObservableObject
|
||
{
|
||
private readonly IGroupRepository _groups;
|
||
private readonly ISubjectRepository _subjects;
|
||
private readonly SchoolYearService _sy;
|
||
private readonly IEnrollmentRepository _enrollments;
|
||
private List<Subject> _allSubjects = [];
|
||
private LearningGroup? _editingGroup;
|
||
private string? _originalSchoolYear;
|
||
|
||
public List<string> TypeOptions { get; } = ["Klasse", "Kurs"];
|
||
[ObservableProperty] private string _selectedTypeName = "Kurs";
|
||
|
||
public bool IsKurs => SelectedTypeName == "Kurs";
|
||
public string NameLabel => IsKurs ? "Kursname *" : "Klassenname *";
|
||
public string NameHint => IsKurs ? "z.B. Q1, LK, GK" : "z.B. 10E, 5a, 11b";
|
||
|
||
partial void OnSelectedTypeNameChanged(string value)
|
||
{
|
||
OnPropertyChanged(nameof(IsKurs));
|
||
OnPropertyChanged(nameof(NameLabel));
|
||
OnPropertyChanged(nameof(NameHint));
|
||
}
|
||
|
||
[ObservableProperty] private string _name = "";
|
||
[ObservableProperty] private string _subject = "";
|
||
[ObservableProperty] private int _gradeLevel = 10;
|
||
[ObservableProperty] private string _selectedGradingName = "Noten 1–6";
|
||
[ObservableProperty] private string _selectedSchoolYear = "";
|
||
[ObservableProperty] private int? _hoursPerWeek;
|
||
[ObservableProperty] private string _validationMessage = "";
|
||
|
||
public List<string> SchoolYears { get; }
|
||
public List<string> GradingOptions { get; } = ["Noten 1–6", "Punkte 0–15"];
|
||
public List<string> KnownSubjectNames { get; private set; } = [];
|
||
public LearningGroup? Result { get; private set; }
|
||
public string DialogTitle => _editingGroup is null ? "Neue Lerngruppe anlegen" : "Lerngruppe bearbeiten";
|
||
public string SaveButtonText => _editingGroup is null ? "Anlegen" : "Speichern";
|
||
|
||
public AddGroupDialogViewModel(IGroupRepository groups, ISubjectRepository subjects,
|
||
SchoolYearService sy, IEnrollmentRepository enrollments)
|
||
{
|
||
_groups = groups; _subjects = subjects; _sy = sy; _enrollments = enrollments;
|
||
_allSubjects = subjects.GetAll();
|
||
KnownSubjectNames = _allSubjects.Select(s => s.Name).ToList();
|
||
SchoolYears = sy.RecentSchoolYears(3);
|
||
SelectedSchoolYear = sy.CurrentSchoolYear();
|
||
PropertyChanged += (_, e) =>
|
||
{
|
||
if (e.PropertyName == nameof(GradeLevel))
|
||
SelectedGradingName = GradeLevel >= 11 ? "Punkte 0–15" : "Noten 1–6";
|
||
};
|
||
}
|
||
|
||
public void LoadForEdit(LearningGroup group)
|
||
{
|
||
_editingGroup = group;
|
||
_originalSchoolYear = group.SchoolYear;
|
||
SelectedTypeName = group.Type == GroupType.Class ? "Klasse" : "Kurs";
|
||
Name = group.Name;
|
||
Subject = group.Subject ?? "";
|
||
GradeLevel = group.GradeLevel;
|
||
SelectedGradingName = group.GradingSystem == GradingSystem.Grades1To6
|
||
? "Noten 1–6" : "Punkte 0–15";
|
||
SelectedSchoolYear = group.SchoolYear;
|
||
HoursPerWeek = group.HoursPerWeek;
|
||
OnPropertyChanged(nameof(DialogTitle));
|
||
OnPropertyChanged(nameof(SaveButtonText));
|
||
}
|
||
|
||
[RelayCommand]
|
||
private void Save()
|
||
{
|
||
if (string.IsNullOrWhiteSpace(Name)) { ValidationMessage = "Bezeichnung erforderlich."; return; }
|
||
if (GradeLevel is < 1 or > 13) { ValidationMessage = "Klassenstufe 1–13."; return; }
|
||
|
||
string? subjectName = IsKurs && !string.IsNullOrWhiteSpace(Subject) ? Subject.Trim() : null;
|
||
Guid? subjectId = null;
|
||
|
||
if (subjectName is not null)
|
||
{
|
||
var existing = _allSubjects.FirstOrDefault(
|
||
s => s.Name.Equals(subjectName, StringComparison.OrdinalIgnoreCase));
|
||
if (existing is not null)
|
||
{
|
||
subjectId = existing.Id;
|
||
}
|
||
else
|
||
{
|
||
var newSubject = new Subject { Name = subjectName };
|
||
_subjects.Save(newSubject);
|
||
subjectId = newSubject.Id;
|
||
}
|
||
}
|
||
|
||
Result = _editingGroup ?? new LearningGroup();
|
||
Result.Name = Name.Trim();
|
||
Result.Subject = subjectName;
|
||
Result.SubjectId = subjectId;
|
||
Result.Type = IsKurs ? GroupType.Course : GroupType.Class;
|
||
Result.GradeLevel = GradeLevel;
|
||
Result.GradingSystem = SelectedGradingName == "Punkte 0–15"
|
||
? GradingSystem.Points0To15 : GradingSystem.Grades1To6;
|
||
Result.SchoolYear = SelectedSchoolYear;
|
||
Result.HoursPerWeek = HoursPerWeek;
|
||
_groups.Save(Result);
|
||
|
||
if (_editingGroup is not null && _originalSchoolYear != SelectedSchoolYear)
|
||
{
|
||
foreach (var enrollment in _enrollments.GetByGroup(Result.Id))
|
||
{
|
||
enrollment.SchoolYear = SelectedSchoolYear;
|
||
_enrollments.Save(enrollment);
|
||
}
|
||
}
|
||
}
|
||
}
|