175 lines
6.7 KiB
C#
175 lines
6.7 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;
|
||
|
||
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<CompetencyStudentOption> StudentOptions { get; } = [];
|
||
public ObservableCollection<CompetencyOverviewRow> 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}";
|
||
}
|
||
}
|