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; public partial class CompetencyOverviewTabViewModel : ObservableObject { private readonly IUnitRepository _units; private readonly IExamRepository _exams; private readonly IExamResultRepository _results; private readonly ICompetencyDomainRepository _catalog; private readonly IStudentRepository _students; private readonly CompetencyAnalysisService _analysis; private LearningGroup? _group; [ObservableProperty] private CompetencyStudentOption? _selectedStudent; [ObservableProperty] private double _reviewThreshold = 60; [ObservableProperty] private string _summary = ""; [ObservableProperty] private string _emptyMessage = ""; public ObservableCollection StudentOptions { get; } = []; public ObservableCollection Rows { get; } = []; public bool HasRows => Rows.Count > 0; public bool HasStudents => StudentOptions.Count > 0; public string ThresholdLabel => $"Wiederholungsbedarf unter {ReviewThreshold:0} %"; public CompetencyOverviewTabViewModel(IUnitRepository units, IExamRepository exams, IExamResultRepository results, ICompetencyDomainRepository catalog, IStudentRepository students, CompetencyAnalysisService analysis) { _units = units; _exams = exams; _results = results; _catalog = catalog; _students = students; _analysis = analysis; } public void Initialize(LearningGroup group) { _group = group; Refresh(); } [RelayCommand] public void Refresh() { if (_group is null) return; var selectedStudentId = SelectedStudent?.Id; var domains = _group.SubjectId is Guid subjectId ? _catalog.GetBySubjectAndGrade(subjectId, _group.GradeLevel) : []; var units = _units.GetByGroup(_group.Id); var exams = _exams.GetByGroup(_group.Id); var results = exams.SelectMany(x => _results.GetByExam(x.Id)).ToList(); var analysis = _analysis.Analyze(domains, units, exams, results); StudentOptions.Clear(); foreach (var student in _students.GetByGroup(_group.Id) .OrderBy(x => x.LastName).ThenBy(x => x.FirstName)) StudentOptions.Add(new CompetencyStudentOption(student.Id, student.FullName)); SelectedStudent = StudentOptions.FirstOrDefault(x => x.Id == selectedStudentId) ?? StudentOptions.FirstOrDefault(); Rows.Clear(); foreach (var item in analysis) Rows.Add(new CompetencyOverviewRow(item, SelectedStudent?.Id, ReviewThreshold)); var treated = analysis.Count(x => x.TaughtUnitCount > 0); var assessed = analysis.Count(x => x.AssessedExamCount > 0); var evaluated = analysis.Count(x => x.GroupScore is not null); Summary = $"{treated} von {analysis.Count} behandelt · {assessed} geprüft · {evaluated} mit Ergebnisdaten"; EmptyMessage = analysis.Count == 0 ? "Noch keine Kompetenzen im Katalog oder in Unterrichtseinheiten und Klausuren zugeordnet." : ""; OnPropertyChanged(nameof(HasRows)); OnPropertyChanged(nameof(HasStudents)); } partial void OnSelectedStudentChanged(CompetencyStudentOption? value) { foreach (var row in Rows) row.SetStudent(value?.Id); } partial void OnReviewThresholdChanged(double value) { OnPropertyChanged(nameof(ThresholdLabel)); foreach (var row in Rows) row.SetThreshold(value); } } public sealed record CompetencyStudentOption(Guid Id, string DisplayName); public partial class CompetencyOverviewRow : ObservableObject { private readonly CompetencyAnalysisItem _item; private double _threshold; [ObservableProperty] private string _studentResult = "–"; [ObservableProperty] private string _recommendation = "Noch keine Bewertung"; [ObservableProperty] private string _recommendationColor = "#6B7280"; public string Domain => string.IsNullOrWhiteSpace(_item.DomainCode) ? _item.DomainName : $"{_item.DomainName} ({_item.DomainCode})"; public string Code => string.IsNullOrWhiteSpace(_item.Code) ? "–" : _item.Code; public string Description => _item.Description; public string InstructionCoverage => CoverageText( _item.TaughtUnitCount, "behandelt", _item.PlannedUnitCount, "geplant"); public string ExamCoverage => CoverageText( _item.AssessedExamCount, "geprüft", _item.PlannedExamCount, "geplant"); public string GroupResult => ScoreText(_item.GroupScore, includeParticipants: true); public CompetencyOverviewRow(CompetencyAnalysisItem item, Guid? studentId, double threshold) { _item = item; _threshold = threshold; SetStudent(studentId); UpdateRecommendation(); } public void SetStudent(Guid? studentId) { StudentResult = studentId is Guid id && _item.StudentScores.TryGetValue(id, out var score) ? ScoreText(score, includeParticipants: false) : "–"; } public void SetThreshold(double threshold) { _threshold = threshold; UpdateRecommendation(); } private void UpdateRecommendation() { if (_item.GroupScore is null) { Recommendation = "Noch keine Bewertung"; RecommendationColor = "#6B7280"; } else if (_item.GroupScore.Percent < _threshold) { Recommendation = "Wiederholungsbedarf"; RecommendationColor = "#C62828"; } else { Recommendation = "Stand solide"; RecommendationColor = "#2E7D32"; } } private static string CoverageText(int completed, string completedLabel, int planned, string plannedLabel) { if (completed == 0 && planned == 0) return "–"; if (completed == 0) return $"{planned}× {plannedLabel}"; if (planned == 0) return $"{completed}× {completedLabel}"; return $"{completed}× {completedLabel} · {planned}× {plannedLabel}"; } private static string ScoreText(CompetencyScore? score, bool includeParticipants) { if (score is null) return "–"; var evidence = score.EvidenceCount == 1 ? "1 Aufgabenwert" : $"{score.EvidenceCount} Aufgabenwerte"; return includeParticipants ? $"{score.Percent:0.#} % · {score.ParticipantCount} Schüler · {evidence}" : $"{score.Percent:0.#} % · {evidence}"; } }