diff --git a/LehrerApp.Core/Models/Workload.cs b/LehrerApp.Core/Models/Workload.cs index 3093c5f..2a2d3f1 100644 --- a/LehrerApp.Core/Models/Workload.cs +++ b/LehrerApp.Core/Models/Workload.cs @@ -93,7 +93,7 @@ public interface IHasAttachments List Attachments { get; } } // Hinten angefügt, damit die numerischen Werte bereits gespeicherter LiteDB-Einträge stabil bleiben. -public enum DocumentationType { Conversation, Incident, SupportPlan, Absence, ParentCall, ParentLetter } +public enum DocumentationType { Conversation, Incident, SupportPlan, Absence, ParentCall, ParentLetter, Planning } public enum SupportStatus { Active, Completed, Paused } public class WorkTask diff --git a/LehrerApp.Desktop.Tests/DashboardViewModelTests.cs b/LehrerApp.Desktop.Tests/DashboardViewModelTests.cs index dbedb49..1fcf3f1 100644 --- a/LehrerApp.Desktop.Tests/DashboardViewModelTests.cs +++ b/LehrerApp.Desktop.Tests/DashboardViewModelTests.cs @@ -59,12 +59,12 @@ public sealed class DashboardViewModelTests DashboardSettingsService? dashboardSettings = null, FakeSchoolHolidays? schoolHolidays = null, FakeLessons? lessons = null, FakeSubstitutionEntries? substitutions = null, FakeSessions? sessions = null, FakeEntries? entries = null, - FakeAnnualPlanEvents? annualPlanEvents = null) + FakeAnnualPlanEvents? annualPlanEvents = null, List? allGroups = null) { lessons ??= new FakeLessons(); lessons.Add(lesson); return new DashboardViewModel( - new FakeGroups([group]), new FakeSubjects([]), lessons, + new FakeGroups(allGroups ?? [group]), new FakeSubjects([]), lessons, exams ?? new FakeExams([]), results ?? new FakeResults(), grades ?? new FakeGrades(), reportGrades ?? new FakeReportGrades(), memberships ?? new FakeMemberships([]), tasks ?? new FakeWorkTasks(), sessions ?? new FakeSessions([]), entries ?? new FakeEntries(), @@ -242,6 +242,26 @@ public sealed class DashboardViewModelTests Assert.Contains(vm.SelectedDayEvents, e => e.Kind == CalendarEventKind.Exam && e.Title == "Test"); } + [Fact] + public void Kalenderauswahl_SortiertKurseDesTagesVorDenAlphabetischenRest() + { + var today = DateOnly.FromDateTime(DateTime.Today); + var selectedDate = today.AddDays(1); + var alphabeticallyFirst = new LearningGroup { Name = "10a" }; + var selectedDayGroup = new LearningGroup { Name = "WAT 10c" }; + var lessons = new FakeLessons(); + lessons.Add(new Lesson { GroupId = selectedDayGroup.Id, Date = selectedDate }); + var vm = BuildVm(alphabeticallyFirst, + new Lesson { GroupId = alphabeticallyFirst.Id, Date = today }, lessons: lessons, + allGroups: [alphabeticallyFirst, selectedDayGroup]); + + vm.SelectCalendarDayCommand.Execute(vm.CalendarDays.Single(d => d.Date == selectedDate)); + + Assert.Equal(selectedDayGroup.Id, vm.CurrentGroups[0].GroupId); + Assert.True(vm.CurrentGroups[0].IsOnSelectedDay); + Assert.Equal(alphabeticallyFirst.Id, vm.CurrentGroups[1].GroupId); + } + [Fact] public void Kalender_ZeigtMitarbeitssitzungenAlsEigenenTermintyp() { diff --git a/LehrerApp.Desktop.Tests/DocumentationDialogViewModelTests.cs b/LehrerApp.Desktop.Tests/DocumentationDialogViewModelTests.cs index 676ae0e..faf0cc1 100644 --- a/LehrerApp.Desktop.Tests/DocumentationDialogViewModelTests.cs +++ b/LehrerApp.Desktop.Tests/DocumentationDialogViewModelTests.cs @@ -247,6 +247,25 @@ public sealed class DocumentationDialogViewModelTests Assert.Equal(groupId, vm.Result.GroupId); } + [Fact] + public void GesamteLerngruppe_SaveSpeichertOhneSchuelerbezug() + { + var groupId = Guid.NewGuid(); + var wholeGroup = new StudentOption(Guid.Empty, "Gesamte Lerngruppe"); + var vm = new DocumentationDialogViewModel(Guid.Empty, null, new FakeAttachmentStorage(), + [wholeGroup], groupId) + { + SelectedStudent = wholeGroup, Title = "Klausur planen", TypeName = "Planung / Erinnerung", + }; + + vm.SaveCommand.Execute(null); + + Assert.NotNull(vm.Result); + Assert.Equal(Guid.Empty, vm.Result!.StudentId); + Assert.Equal(groupId, vm.Result.GroupId); + Assert.Equal(DocumentationType.Planning, vm.Result.Type); + } + [Fact] public void MitStudentOptions_Bearbeiten_SchuelerIstVorausgewaehlt() { diff --git a/LehrerApp.Desktop.Tests/GlobalSearchViewModelTests.cs b/LehrerApp.Desktop.Tests/GlobalSearchViewModelTests.cs index d215a94..3c3ef4c 100644 --- a/LehrerApp.Desktop.Tests/GlobalSearchViewModelTests.cs +++ b/LehrerApp.Desktop.Tests/GlobalSearchViewModelTests.cs @@ -14,7 +14,8 @@ public sealed class GlobalSearchViewModelTests Assert.Collection(vm.Results, item => Assert.Equal(GlobalSearchAction.NewTask, item.Action), item => Assert.Equal(GlobalSearchAction.NewReminder, item.Action), - item => Assert.Equal(GlobalSearchAction.NewStudent, item.Action)); + item => Assert.Equal(GlobalSearchAction.NewStudent, item.Action), + item => Assert.Equal(GlobalSearchAction.NewGroupDocumentation, item.Action)); Assert.Same(vm.Results[0], vm.SelectedResult); } @@ -67,8 +68,36 @@ public sealed class GlobalSearchViewModelTests Assert.True(reminder); } + [Fact] + public void Suche_FindetAktiveGruppeUeberFachUndZeigtFachImUntertitel() + { + var subject = new Subject { Name = "Wirtschaft-Arbeit-Technik", ShortName = "WAT" }; + var group = new LearningGroup { Name = "10c", SubjectId = subject.Id, SchoolYear = "2026/27", GradeLevel = 10 }; + var vm = BuildVm(groups: [group], subjects: [subject]); + + vm.Query = "wat"; + + var result = Assert.Single(vm.Results, x => x.Kind == GlobalSearchResultKind.Group); + Assert.Contains("WAT", result.Subtitle); + } + + [Fact] + public void Suche_UeberspringtArchivierteSchuelerUndGruppenSamtDerenKlausuren() + { + var archivedGroup = new LearningGroup { Name = "Testkurs Vorjahr", IsActive = false }; + var archivedStudent = new Student { FirstName = "Test", LastName = "Archiv", IsActive = false }; + var exam = new Exam { GroupId = archivedGroup.Id, Title = "Testklausur" }; + var vm = BuildVm([archivedStudent], [archivedGroup], [exam]); + + vm.Query = "Test"; + + Assert.DoesNotContain(vm.Results, x => x.Kind is GlobalSearchResultKind.Student + or GlobalSearchResultKind.Group or GlobalSearchResultKind.Exam); + } + private static GlobalSearchViewModel BuildVm(List? students = null, - List? groups = null, List? exams = null, FakeWorkTasks? tasks = null) => + List? groups = null, List? exams = null, FakeWorkTasks? tasks = null, + List? subjects = null) => new(new FakeStudents(students ?? []), new FakeGroups(groups ?? []), - new FakeExams(exams ?? []), tasks ?? new FakeWorkTasks()); + new FakeSubjects(subjects ?? []), new FakeExams(exams ?? []), tasks ?? new FakeWorkTasks()); } diff --git a/LehrerApp.Desktop.Tests/GroupDocumentationQuickViewModelTests.cs b/LehrerApp.Desktop.Tests/GroupDocumentationQuickViewModelTests.cs new file mode 100644 index 0000000..e5e6984 --- /dev/null +++ b/LehrerApp.Desktop.Tests/GroupDocumentationQuickViewModelTests.cs @@ -0,0 +1,26 @@ +using LehrerApp.Core.Models; +using LehrerApp.Desktop.ViewModels.Students; +using Xunit; + +namespace LehrerApp.Desktop.Tests; + +public sealed class GroupDocumentationQuickViewModelTests +{ + [Fact] + public void Save_ErstelltPlanungFuerGesamteLerngruppe() + { + var group = new GroupDocumentationOption(Guid.NewGuid(), "10c · WAT"); + var vm = new GroupDocumentationQuickViewModel([group]) + { + SelectedGroup = group, Title = "Klausur ankündigen", Content = "Termin abstimmen", + }; + + vm.SaveCommand.Execute(null); + + Assert.NotNull(vm.Result); + Assert.Equal(Guid.Empty, vm.Result!.StudentId); + Assert.Equal(group.Id, vm.Result.GroupId); + Assert.Equal(DocumentationType.Planning, vm.Result.Type); + Assert.True(vm.Result.ExcludeFromWebUntisSync); + } +} diff --git a/LehrerApp.Desktop.Tests/GroupDocumentationTabViewModelTests.cs b/LehrerApp.Desktop.Tests/GroupDocumentationTabViewModelTests.cs index cf507ee..2c9c07a 100644 --- a/LehrerApp.Desktop.Tests/GroupDocumentationTabViewModelTests.cs +++ b/LehrerApp.Desktop.Tests/GroupDocumentationTabViewModelTests.cs @@ -36,6 +36,25 @@ public sealed class GroupDocumentationTabViewModelTests Assert.Equal("Alt", vm.Entries[1].Model.Title); } + [Fact] + public void Initialize_ZeigtGruppenweitenEintragAuchOhneSchueler() + { + var group = new LearningGroup { Name = "10c" }; + var docs = new FakeDocumentation(); + docs.Add(new Documentation + { + StudentId = Guid.Empty, GroupId = group.Id, Type = DocumentationType.Planning, + Title = "Klausur planen", Date = new DateOnly(2026, 9, 1), + }); + + var vm = BuildVm([], [group], docs); + vm.Initialize(group.Id); + + var entry = Assert.Single(vm.Entries); + Assert.Equal("Gesamte Lerngruppe", entry.StudentName); + Assert.Equal("Planung / Erinnerung", entry.TypeLabel); + } + [Fact] public void Initialize_EintragAusAndererGruppe_WirdMitangezeigtAberAlsFremdMarkiert() { diff --git a/LehrerApp.Desktop/App.axaml.cs b/LehrerApp.Desktop/App.axaml.cs index 1c34c86..2e4a200 100644 --- a/LehrerApp.Desktop/App.axaml.cs +++ b/LehrerApp.Desktop/App.axaml.cs @@ -170,6 +170,7 @@ public class App : Application }; search.OnQuickAddTask = startAsReminder => ShowQuickTaskDialog(startAsReminder, dash); search.OnQuickAddStudent = ShowAddStudentDialog; + search.OnQuickAddGroupDocumentation = () => ShowQuickGroupDocumentationDialog(dash); // StudentList → StudentDetail + Anlegen var sl = Services.GetRequiredService(); @@ -203,4 +204,26 @@ public class App : Application dashboard.RefreshCommand.Execute(null); Services.GetRequiredService().Load(); } + + private static async Task ShowQuickGroupDocumentationDialog(DashboardViewModel dashboard) + { + if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime + { MainWindow: { } owner }) return; + + var subjects = Services.GetRequiredService().GetAll() + .ToDictionary(s => s.Id); + var options = Services.GetRequiredService().GetAll() + .Where(g => g.IsActive) + .OrderBy(g => g.Name) + .Select(g => new GroupDocumentationOption(g.Id, + g.SubjectId is { } subjectId && subjects.TryGetValue(subjectId, out var subject) + ? $"{g.Name} · {(string.IsNullOrWhiteSpace(subject.ShortName) ? subject.Name : subject.ShortName)}" + : g.Name)); + var vm = new GroupDocumentationQuickViewModel(options); + var dialog = new Views.Students.GroupDocumentationQuickDialog { DataContext = vm }; + if (!await dialog.ShowDialog(owner) || vm.Result is null) return; + + Services.GetRequiredService().Save(vm.Result); + dashboard.RefreshCommand.Execute(null); + } } diff --git a/LehrerApp.Desktop/ViewModels/DashboardViewModel.cs b/LehrerApp.Desktop/ViewModels/DashboardViewModel.cs index 916ca29..7812450 100644 --- a/LehrerApp.Desktop/ViewModels/DashboardViewModel.cs +++ b/LehrerApp.Desktop/ViewModels/DashboardViewModel.cs @@ -56,6 +56,7 @@ public partial class DashboardViewModel : ObservableObject [ObservableProperty] private string _currentSchoolYear = ""; [ObservableProperty] private DateOnly _calendarMonth = FirstOfMonth(DateTime.Today); [ObservableProperty] private string _selectedDayLabel = ""; + [ObservableProperty] private DateOnly _selectedCalendarDate = DateOnly.FromDateTime(DateTime.Today); [ObservableProperty] private bool _isDashboardSettingsOpen; [ObservableProperty] private bool _isWeatherPanelVisible; [ObservableProperty] private string _weatherSummary = ""; @@ -196,7 +197,7 @@ public partial class DashboardViewModel : ObservableObject IsHighPriority = t.Priority == TaskPriority.High }); CurrentGroups.Clear(); - foreach (var g in groups.Values.OrderBy(g => g.Name)) + foreach (var g in groups.Values) CurrentGroups.Add(new() { GroupId = g.Id, @@ -749,9 +750,35 @@ public partial class DashboardViewModel : ObservableObject { if (day is null) return; foreach (var cell in CalendarDays) cell.IsSelected = cell == day; + SelectedCalendarDate = day.Date; SelectedDayLabel = day.Date.ToString("dddd, d. MMMM", De); SelectedDayEvents.Clear(); foreach (var item in day.Events) SelectedDayEvents.Add(item); + SortCurrentGroups(day.Date); + } + + private void SortCurrentGroups(DateOnly date) + { + var schoolHolidays = _schoolHolidays.GetAll(); + var publicHolidays = _publicHolidays.GetHolidays(date.Year, _calendarSettings.State) + .Select(h => h.Date).ToHashSet(); + var isFreeDay = IsFreeDay(date, schoolHolidays, publicHolidays); + var cancelledPeriods = _substitutions.GetByDate(date) + .Where(s => s.Kind == SubstitutionKind.Cancelled) + .Select(s => s.PeriodNumber).ToHashSet(); + + foreach (var chip in CurrentGroups) + { + var hasLesson = _lessons.GetByGroupAndDate(chip.GroupId, date).Count > 0; + var hasActiveSlot = !isFreeDay && _timetableSlots.GetByGroup(chip.GroupId) + .Any(s => s.Weekday == date.DayOfWeek && !cancelledPeriods.Contains(s.PeriodNumber)); + chip.IsOnSelectedDay = hasLesson || hasActiveSlot; + } + + var sorted = CurrentGroups.OrderByDescending(g => g.IsOnSelectedDay) + .ThenBy(g => g.Name, StringComparer.CurrentCultureIgnoreCase).ToList(); + CurrentGroups.Clear(); + foreach (var chip in sorted) CurrentGroups.Add(chip); } [RelayCommand] @@ -891,7 +918,13 @@ public class LessonItem public bool HasRoom => !string.IsNullOrWhiteSpace(Room); } public class TaskItem { public string Title { get; set; } = ""; public string DueDate { get; set; } = ""; public bool IsOverdue { get; set; } public bool IsReminder { get; set; } public bool IsHighPriority { get; set; } } -public class GroupChip { public Guid GroupId { get; set; } public string Name { get; set; } = ""; public string Subject { get; set; } = ""; } +public class GroupChip +{ + public Guid GroupId { get; set; } + public string Name { get; set; } = ""; + public string Subject { get; set; } = ""; + public bool IsOnSelectedDay { get; set; } +} // ── Offene Entschuldigungen (aus Mitarbeit-Fehltagen) ──────────────────────── diff --git a/LehrerApp.Desktop/ViewModels/GlobalSearchViewModel.cs b/LehrerApp.Desktop/ViewModels/GlobalSearchViewModel.cs index 21a5350..a99fdb1 100644 --- a/LehrerApp.Desktop/ViewModels/GlobalSearchViewModel.cs +++ b/LehrerApp.Desktop/ViewModels/GlobalSearchViewModel.cs @@ -17,6 +17,7 @@ public partial class GlobalSearchViewModel : ObservableObject private readonly IStudentRepository _students; private readonly IGroupRepository _groups; + private readonly ISubjectRepository _subjects; private readonly IExamRepository _exams; private readonly IWorkTaskRepository _tasks; @@ -30,13 +31,15 @@ public partial class GlobalSearchViewModel : ObservableObject public Action? OnNavigate { get; set; } public Func? OnQuickAddTask { get; set; } public Func? OnQuickAddStudent { get; set; } + public Func? OnQuickAddGroupDocumentation { get; set; } public Action? OnClose { get; set; } public GlobalSearchViewModel(IStudentRepository students, IGroupRepository groups, - IExamRepository exams, IWorkTaskRepository tasks) + ISubjectRepository subjects, IExamRepository exams, IWorkTaskRepository tasks) { _students = students; _groups = groups; + _subjects = subjects; _exams = exams; _tasks = tasks; RefreshResults(); @@ -59,23 +62,34 @@ public partial class GlobalSearchViewModel : ObservableObject if (query.Length > 0) { - var groups = _groups.GetAll(includeInactive: true); + var groups = _groups.GetAll().Where(g => g.IsActive).ToList(); var groupNames = groups.ToDictionary(g => g.Id, g => g.Name); + var subjects = _subjects.GetAll().ToDictionary(s => s.Id); + Subject? GroupSubject(LearningGroup group) => group.SubjectId is { } subjectId + ? subjects.GetValueOrDefault(subjectId) + : null; + static string SubjectLabel(Subject? subject) => subject is null ? "" + : string.IsNullOrWhiteSpace(subject.ShortName) + || subject.Name.Equals(subject.ShortName, StringComparison.CurrentCultureIgnoreCase) + ? subject.Name + : $"{subject.ShortName} – {subject.Name}"; var candidates = new List(); - candidates.AddRange(_students.GetAll(includeInactive: true) + candidates.AddRange(_students.GetAll().Where(s => s.IsActive) .Where(s => Matches(s.FullName, query)) .Select(s => GlobalSearchResult.ForStudent(s))); candidates.AddRange(groups - .Where(g => Matches($"{g.Name} {g.SchoolYear} {g.GradeLevel}", query)) - .Select(GlobalSearchResult.ForGroup)); + .Where(g => Matches($"{g.Name} {GroupSubject(g)?.Name} {GroupSubject(g)?.ShortName} {g.SchoolYear} {g.GradeLevel}", query)) + .Select(g => GlobalSearchResult.ForGroup(g, SubjectLabel(GroupSubject(g))))); candidates.AddRange(_exams.GetAll() + .Where(e => groupNames.ContainsKey(e.GroupId)) .Where(e => Matches($"{e.Title} {groupNames.GetValueOrDefault(e.GroupId)}", query)) .Select(e => GlobalSearchResult.ForExam(e, groupNames.GetValueOrDefault(e.GroupId) ?? ""))); candidates.AddRange(_tasks.GetAll() + .Where(t => t.GroupId is null || groupNames.ContainsKey(t.GroupId.Value)) .Where(t => Matches($"{t.Title} {t.Notes} {groupNames.GetValueOrDefault(t.GroupId ?? Guid.Empty)}", query)) .Select(t => GlobalSearchResult.ForTask(t, groupNames.GetValueOrDefault(t.GroupId ?? Guid.Empty) ?? ""))); @@ -99,6 +113,7 @@ public partial class GlobalSearchViewModel : ObservableObject GlobalSearchResult.ForAction(GlobalSearchAction.NewTask, "Aufgabe anlegen", "Mit Fälligkeit, Gruppe und Priorität", "+"), GlobalSearchResult.ForAction(GlobalSearchAction.NewReminder, "Erinnerung anlegen", "Kurze Notiz ohne Zeiterfassung", "◷"), GlobalSearchResult.ForAction(GlobalSearchAction.NewStudent, "Schüler anlegen", "Neue Stammdaten erfassen", "+"), + GlobalSearchResult.ForAction(GlobalSearchAction.NewGroupDocumentation, "Lerngruppen-Eintrag", "Planung, Klausur oder Erinnerung dokumentieren", "N"), }; foreach (var action in actions.Where(a => query.Length == 0 || Matches(a.Title, query))) @@ -124,6 +139,9 @@ public partial class GlobalSearchViewModel : ObservableObject case GlobalSearchAction.NewStudent: if (OnQuickAddStudent is not null) await OnQuickAddStudent(); break; + case GlobalSearchAction.NewGroupDocumentation: + if (OnQuickAddGroupDocumentation is not null) await OnQuickAddGroupDocumentation(); + break; default: OnNavigate?.Invoke(result); break; @@ -134,7 +152,7 @@ public partial class GlobalSearchViewModel : ObservableObject } public enum GlobalSearchResultKind { Action, Student, Group, Exam, Task } -public enum GlobalSearchAction { None, NewTask, NewReminder, NewStudent } +public enum GlobalSearchAction { None, NewTask, NewReminder, NewStudent, NewGroupDocumentation } public sealed class GlobalSearchResult { @@ -171,10 +189,13 @@ public sealed class GlobalSearchResult Title = student.FullName, Subtitle = student.IsActive ? "Aktiv" : "Inaktiv", Icon = "P", }; - public static GlobalSearchResult ForGroup(LearningGroup group) => new() + public static GlobalSearchResult ForGroup(LearningGroup group, string subjectName) => new() { Kind = GlobalSearchResultKind.Group, EntityId = group.Id, GroupId = group.Id, - Title = group.Name, Subtitle = $"{group.SchoolYear} · Stufe {group.GradeLevel}", Icon = "G", + Title = group.Name, + Subtitle = string.Join(" · ", new[] { subjectName, group.SchoolYear, $"Stufe {group.GradeLevel}" } + .Where(x => !string.IsNullOrWhiteSpace(x))), + Icon = "G", }; public static GlobalSearchResult ForExam(Exam exam, string groupName) => new() diff --git a/LehrerApp.Desktop/ViewModels/Groups/GroupDocumentationViewModels.cs b/LehrerApp.Desktop/ViewModels/Groups/GroupDocumentationViewModels.cs index 9dac71e..1e1a1c3 100644 --- a/LehrerApp.Desktop/ViewModels/Groups/GroupDocumentationViewModels.cs +++ b/LehrerApp.Desktop/ViewModels/Groups/GroupDocumentationViewModels.cs @@ -25,6 +25,7 @@ public partial class GroupDocumentationTabViewModel : ObservableObject private List _groupStudents = []; public static readonly StudentOption AllStudentsOption = new(Guid.Empty, "Alle Schüler"); + public static readonly StudentOption WholeGroupOption = new(Guid.Empty, "Gesamte Lerngruppe"); public ObservableCollection Entries { get; } = []; public ObservableCollection StudentFilterOptions { get; } = [AllStudentsOption]; @@ -62,8 +63,6 @@ public partial class GroupDocumentationTabViewModel : ObservableObject private void Load() { Entries.Clear(); - if (_groupStudents.Count == 0) return; - var studentNameById = _groupStudents.ToDictionary(s => s.Id, s => s.Name); var groupNameCache = new Dictionary(); string GroupLabel(Guid id) @@ -80,6 +79,10 @@ public partial class GroupDocumentationTabViewModel : ObservableObject var all = relevantStudentIds .SelectMany(id => _docs.GetByStudent(id)) + .Concat(SelectedStudentFilter.Id == Guid.Empty + ? _docs.GetAll().Where(d => d.StudentId == Guid.Empty && d.GroupId == _groupId) + : []) + .DistinctBy(d => d.Id) .Where(d => !OnlyThisGroup || d.GroupId == _groupId) .OrderByDescending(d => d.IsDraft) .ThenByDescending(d => d.Date) @@ -90,7 +93,10 @@ public partial class GroupDocumentationTabViewModel : ObservableObject { var isOwnGroup = d.GroupId is null || d.GroupId == _groupId; var otherGroupLabel = isOwnGroup ? "" : GroupLabel(d.GroupId!.Value); - Entries.Add(new DocumentationItem(d, studentNameById.GetValueOrDefault(d.StudentId, ""), + var studentName = d.StudentId == Guid.Empty + ? WholeGroupOption.Name + : studentNameById.GetValueOrDefault(d.StudentId, ""); + Entries.Add(new DocumentationItem(d, studentName, isOwnGroup, otherGroupLabel)); } } @@ -100,7 +106,7 @@ public partial class GroupDocumentationTabViewModel : ObservableObject [RelayCommand] private async Task AddDocumentation() { - if (OnEditDocumentation is null || _groupStudents.Count == 0) return; + if (OnEditDocumentation is null) return; var result = await OnEditDocumentation(_groupId, _groupStudents, null); if (result is null) return; _docs.Save(result); diff --git a/LehrerApp.Desktop/ViewModels/Students/DocumentationViewModels.cs b/LehrerApp.Desktop/ViewModels/Students/DocumentationViewModels.cs index ab95edf..047391e 100644 --- a/LehrerApp.Desktop/ViewModels/Students/DocumentationViewModels.cs +++ b/LehrerApp.Desktop/ViewModels/Students/DocumentationViewModels.cs @@ -17,7 +17,7 @@ public record StudentOption(Guid Id, string Name); public static class DocumentationTypeDisplay { public static string[] Options { get; } = - ["Gespräch", "Vorkommnis", "Förderplan", "Fehlzeit", "Elternanruf", "Elternbrief"]; + ["Gespräch", "Vorkommnis", "Förderplan", "Fehlzeit", "Elternanruf", "Elternbrief", "Planung / Erinnerung"]; public static string Label(DocumentationType t) => t switch { @@ -27,6 +27,7 @@ public static class DocumentationTypeDisplay DocumentationType.Absence => "Fehlzeit", DocumentationType.ParentCall => "Elternanruf", DocumentationType.ParentLetter => "Elternbrief", + DocumentationType.Planning => "Planung / Erinnerung", _ => "", }; @@ -37,6 +38,7 @@ public static class DocumentationTypeDisplay "Fehlzeit" => DocumentationType.Absence, "Elternanruf" => DocumentationType.ParentCall, "Elternbrief" => DocumentationType.ParentLetter, + "Planung / Erinnerung" => DocumentationType.Planning, _ => DocumentationType.Conversation, }; } @@ -301,7 +303,7 @@ public partial class DocumentationDialogViewModel : ObservableObject LetterSentDateError = ""; LetterResponseDateError = ""; var valid = true; - if (CanPickStudent && SelectedStudent is null) { StudentError = "Schüler auswählen."; valid = false; } + if (CanPickStudent && SelectedStudent is null) { StudentError = "Bezug auswählen."; valid = false; } if (string.IsNullOrWhiteSpace(Title)) { TitleError = "Titel erforderlich."; valid = false; } diff --git a/LehrerApp.Desktop/ViewModels/Students/GroupDocumentationQuickViewModel.cs b/LehrerApp.Desktop/ViewModels/Students/GroupDocumentationQuickViewModel.cs new file mode 100644 index 0000000..8188550 --- /dev/null +++ b/LehrerApp.Desktop/ViewModels/Students/GroupDocumentationQuickViewModel.cs @@ -0,0 +1,46 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using LehrerApp.Core.Models; +using System.Globalization; + +namespace LehrerApp.Desktop.ViewModels.Students; + +public sealed record GroupDocumentationOption(Guid Id, string DisplayName); + +/// Kompakte Erfassung einer gruppenweiten Planungsnotiz aus der globalen Befehlspalette. +public partial class GroupDocumentationQuickViewModel(IEnumerable groups) + : ObservableObject +{ + public List Groups { get; } = groups.ToList(); + [ObservableProperty] private GroupDocumentationOption? _selectedGroup; + [ObservableProperty] private string _title = ""; + [ObservableProperty] private string _content = ""; + [ObservableProperty] private string _dateText = DateOnly.FromDateTime(DateTime.Today).ToString("dd.MM.yyyy"); + [ObservableProperty] private string _groupError = ""; + [ObservableProperty] private string _titleError = ""; + [ObservableProperty] private string _dateError = ""; + + public Documentation? Result { get; private set; } + + [RelayCommand] + private void Save() + { + GroupError = TitleError = DateError = ""; + if (SelectedGroup is null) GroupError = "Lerngruppe auswählen."; + if (string.IsNullOrWhiteSpace(Title)) TitleError = "Titel erforderlich."; + if (!DateOnly.TryParseExact(DateText, "dd.MM.yyyy", null, DateTimeStyles.None, out var date)) + DateError = "Format TT.MM.JJJJ."; + if (GroupError.Length > 0 || TitleError.Length > 0 || DateError.Length > 0) return; + + Result = new Documentation + { + StudentId = Guid.Empty, + GroupId = SelectedGroup!.Id, + Type = DocumentationType.Planning, + Date = date, + Title = Title.Trim(), + Content = Content.Trim(), + ExcludeFromWebUntisSync = true, + }; + } +} diff --git a/LehrerApp.Desktop/Views/Dashboard/DashboardView.axaml b/LehrerApp.Desktop/Views/Dashboard/DashboardView.axaml index 3e52dd2..3db8572 100644 --- a/LehrerApp.Desktop/Views/Dashboard/DashboardView.axaml +++ b/LehrerApp.Desktop/Views/Dashboard/DashboardView.axaml @@ -586,10 +586,10 @@ - + - + @@ -597,9 +597,15 @@