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? OnNavigateToDetail { get; set; } public Func? OnAddGroup { get; set; } [ObservableProperty] private string _selectedSchoolYear = ""; [ObservableProperty] private string _searchText = ""; [ObservableProperty] private GroupListItem? _selectedGroup; public string SelectedGroupDisplayName => SelectedGroup?.DisplayName ?? ""; public string SelectedGroupSubtitle => SelectedGroup?.Subtitle ?? ""; public ObservableCollection SchoolYears { get; } = []; public ObservableCollection 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 OnSelectedGroupChanged(GroupListItem? value) { OnPropertyChanged(nameof(SelectedGroupDisplayName)); OnPropertyChanged(nameof(SelectedGroupSubtitle)); NavigateToSectionCommand.NotifyCanExecuteChanged(); } public void LoadGroups() { Groups.Clear(); var all = _groups.GetBySchoolYear(SelectedSchoolYear); 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)); } [RelayCommand] private async Task AddGroup() { if (OnAddGroup is null) return; await OnAddGroup(); LoadGroups(); } [RelayCommand] private void Refresh() => 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 GroupListItem(LearningGroup g) { Id = g.Id; 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; public ObservableCollection Students { get; } = []; public ObservableCollection Exams { get; } = []; public ParticipationTabViewModel ParticipationTab { get; } public Func>? OnAddStudent { 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(); Exams.Clear(); foreach (var e in _exams.GetByGroup(Group.Id)) Exams.Add(new ExamSummary(e)); ParticipationTab.Initialize(Group.Id, Group.SchoolYear); } 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 void AddExam() { /* TODO */ } [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 string StatusLabel { get; } public ExamSummary(Core.Models.Exam e) { Id = e.Id; Title = e.Title; Date = e.Date.ToString("dd.MM.yyyy"); StatusLabel = e.Status switch { ExamStatus.Planned => "Geplant", ExamStatus.Conducted => "Durchgeführt", ExamStatus.Graded => "Korrigiert", ExamStatus.Returned => "Zurückgegeben", _ => "", }; } } // ── 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 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 List _allSubjects = []; public List 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 GradingSystem _gradingSystem = GradingSystem.Grades1To6; [ObservableProperty] private string _selectedSchoolYear = ""; [ObservableProperty] private string _validationMessage = ""; public List SchoolYears { get; } public List KnownSubjectNames { get; private set; } = []; public LearningGroup? Result { get; private set; } public AddGroupDialogViewModel(IGroupRepository groups, ISubjectRepository subjects, SchoolYearService sy) { _groups = groups; _subjects = subjects; _sy = sy; _allSubjects = subjects.GetAll(); KnownSubjectNames = _allSubjects.Select(s => s.Name).ToList(); SchoolYears = sy.RecentSchoolYears(3); SelectedSchoolYear = sy.CurrentSchoolYear(); PropertyChanged += (_, e) => { if (e.PropertyName == nameof(GradeLevel)) GradingSystem = GradeLevel >= 11 ? GradingSystem.Points0To15 : GradingSystem.Grades1To6; }; } [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 = new LearningGroup { Name = Name.Trim(), Subject = subjectName, SubjectId = subjectId, Type = IsKurs ? GroupType.Course : GroupType.Class, GradeLevel = GradeLevel, GradingSystem = GradingSystem, SchoolYear = SelectedSchoolYear, }; _groups.Save(Result); } }