using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using LehrerApp.Core.Interfaces; using LehrerApp.Core.Models; using System.Collections.ObjectModel; namespace LehrerApp.Desktop.ViewModels; /// /// Globale Suche und Schnellerfassung (14.2). Die Projektion bleibt bewusst klein und lokal: /// durchsucht werden die wichtigsten täglichen Ziele, ohne dafür einen zusätzlichen Suchindex /// oder eine Netzwerkabhängigkeit einzuführen. /// public partial class GlobalSearchViewModel : ObservableObject { private const int MaxDataResults = 12; private readonly IStudentRepository _students; private readonly IGroupRepository _groups; private readonly IExamRepository _exams; private readonly IWorkTaskRepository _tasks; [ObservableProperty] private string _query = ""; [ObservableProperty] private GlobalSearchResult? _selectedResult; public ObservableCollection Results { get; } = []; public bool HasResults => Results.Count > 0; public bool ShowNoResults => !string.IsNullOrWhiteSpace(Query) && Results.Count == 0; public Action? OnNavigate { get; set; } public Func? OnQuickAddTask { get; set; } public Func? OnQuickAddStudent { get; set; } public Action? OnClose { get; set; } public GlobalSearchViewModel(IStudentRepository students, IGroupRepository groups, IExamRepository exams, IWorkTaskRepository tasks) { _students = students; _groups = groups; _exams = exams; _tasks = tasks; RefreshResults(); } partial void OnQueryChanged(string value) => RefreshResults(); public void Reset() { Query = ""; RefreshResults(); } private void RefreshResults() { Results.Clear(); var query = Query.Trim(); AddQuickActions(query); if (query.Length > 0) { var groups = _groups.GetAll(includeInactive: true); var groupNames = groups.ToDictionary(g => g.Id, g => g.Name); var candidates = new List(); candidates.AddRange(_students.GetAll(includeInactive: true) .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)); candidates.AddRange(_exams.GetAll() .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 => Matches($"{t.Title} {t.Notes} {groupNames.GetValueOrDefault(t.GroupId ?? Guid.Empty)}", query)) .Select(t => GlobalSearchResult.ForTask(t, groupNames.GetValueOrDefault(t.GroupId ?? Guid.Empty) ?? ""))); foreach (var item in candidates .OrderByDescending(x => x.Title.StartsWith(query, StringComparison.CurrentCultureIgnoreCase)) .ThenBy(x => x.KindSortOrder) .ThenBy(x => x.Title) .Take(MaxDataResults)) Results.Add(item); } SelectedResult = Results.FirstOrDefault(); OnPropertyChanged(nameof(HasResults)); OnPropertyChanged(nameof(ShowNoResults)); } private void AddQuickActions(string query) { var actions = new[] { 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", "+"), }; foreach (var action in actions.Where(a => query.Length == 0 || Matches(a.Title, query))) Results.Add(action); } private static bool Matches(string? value, string query) => value?.Contains(query, StringComparison.CurrentCultureIgnoreCase) == true; [RelayCommand] private async Task Execute(GlobalSearchResult? result) { if (result is null) return; switch (result.Action) { case GlobalSearchAction.NewTask: if (OnQuickAddTask is not null) await OnQuickAddTask(false); break; case GlobalSearchAction.NewReminder: if (OnQuickAddTask is not null) await OnQuickAddTask(true); break; case GlobalSearchAction.NewStudent: if (OnQuickAddStudent is not null) await OnQuickAddStudent(); break; default: OnNavigate?.Invoke(result); break; } OnClose?.Invoke(); } } public enum GlobalSearchResultKind { Action, Student, Group, Exam, Task } public enum GlobalSearchAction { None, NewTask, NewReminder, NewStudent } public sealed class GlobalSearchResult { public GlobalSearchResultKind Kind { get; private init; } public GlobalSearchAction Action { get; private init; } public Guid? EntityId { get; private init; } public Guid? GroupId { get; private init; } public string Title { get; private init; } = ""; public string Subtitle { get; private init; } = ""; public string Icon { get; private init; } = ""; public int KindSortOrder => Kind switch { GlobalSearchResultKind.Student => 0, GlobalSearchResultKind.Group => 1, GlobalSearchResultKind.Exam => 2, GlobalSearchResultKind.Task => 3, _ => -1, }; public string KindLabel => Kind switch { GlobalSearchResultKind.Student => "Schüler", GlobalSearchResultKind.Group => "Lerngruppe", GlobalSearchResultKind.Exam => "Klausur", GlobalSearchResultKind.Task => "Aufgabe", _ => "Schnellaktion", }; public static GlobalSearchResult ForAction(GlobalSearchAction action, string title, string subtitle, string icon) => new() { Kind = GlobalSearchResultKind.Action, Action = action, Title = title, Subtitle = subtitle, Icon = icon }; public static GlobalSearchResult ForStudent(Student student) => new() { Kind = GlobalSearchResultKind.Student, EntityId = student.Id, Title = student.FullName, Subtitle = student.IsActive ? "Aktiv" : "Inaktiv", Icon = "P", }; public static GlobalSearchResult ForGroup(LearningGroup group) => new() { Kind = GlobalSearchResultKind.Group, EntityId = group.Id, GroupId = group.Id, Title = group.Name, Subtitle = $"{group.SchoolYear} · Stufe {group.GradeLevel}", Icon = "G", }; public static GlobalSearchResult ForExam(Exam exam, string groupName) => new() { Kind = GlobalSearchResultKind.Exam, EntityId = exam.Id, GroupId = exam.GroupId, Title = exam.Title, Subtitle = $"{groupName} · {exam.Date:dd.MM.yyyy}", Icon = "K", }; public static GlobalSearchResult ForTask(WorkTask task, string groupName) => new() { Kind = GlobalSearchResultKind.Task, EntityId = task.Id, GroupId = task.GroupId, Title = task.Title, Subtitle = string.Join(" · ", new[] { groupName, task.DueDate?.ToString("dd.MM.yyyy") ?? "" } .Where(x => !string.IsNullOrWhiteSpace(x))), Icon = task.Kind == TaskKind.Reminder ? "E" : "A", }; }