Files
LehrerApp/LehrerApp.Desktop/ViewModels/Groups/ParticipationViewModels.cs
T

464 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Models;
using System.Collections.ObjectModel;
namespace LehrerApp.Desktop.ViewModels.Groups;
// ── Tab-ViewModel ─────────────────────────────────────────────────────────────
public partial class ParticipationTabViewModel : ObservableObject
{
private readonly IParticipationSessionRepository _sessions;
private readonly IParticipationRepository _entries;
private readonly IParticipationAspectRepository _aspects;
private readonly IStudentRepository _students;
private Guid _groupId;
private string _schoolYear = "";
[ObservableProperty] private ParticipationSessionItem? _selectedSession;
[ObservableProperty] private string _noDataText = "Keine Bewertungssitzung ausgewählt.";
public string SelectedSessionDisplay => SelectedSession?.Display ?? "";
public ObservableCollection<ParticipationSessionItem> Sessions { get; } = [];
public ObservableCollection<ParticipationStudentRow> StudentRows { get; } = [];
public ObservableCollection<AspectColumnDef> Aspects { get; } = [];
public Func<Task<ParticipationSession?>>? OnAddSession { get; set; }
public Func<ParticipationTabViewModel, Task>? OnQuickInput { get; set; }
public ParticipationTabViewModel(
IParticipationSessionRepository sessions,
IParticipationRepository entries,
IParticipationAspectRepository aspects,
IStudentRepository students)
{
_sessions = sessions; _entries = entries;
_aspects = aspects; _students = students;
}
public void Initialize(Guid groupId, string schoolYear)
{
_groupId = groupId;
_schoolYear = schoolYear;
LoadAspects();
LoadSessions();
}
private void LoadAspects()
{
Aspects.Clear();
var defaults = _aspects.GetDefaults();
var specific = _aspects.GetByGroup(_groupId);
var all = defaults.Concat(specific).ToList();
if (!all.Any())
{
foreach (var a in DefaultParticipationAspects.All)
Aspects.Add(new AspectColumnDef(a));
}
else
{
foreach (var a in all) Aspects.Add(new AspectColumnDef(a));
}
}
public void LoadSessions()
{
Sessions.Clear();
foreach (var s in _sessions.GetByGroup(_groupId))
Sessions.Add(new ParticipationSessionItem(s));
if (SelectedSession is null && Sessions.Any())
SelectedSession = Sessions[0];
}
partial void OnSelectedSessionChanged(ParticipationSessionItem? value)
{
OnPropertyChanged(nameof(SelectedSessionDisplay));
if (value is null) { StudentRows.Clear(); QuickInputCommand.NotifyCanExecuteChanged(); return; }
LoadGrid(value.Id);
}
private void LoadGrid(Guid sessionId)
{
StudentRows.Clear();
var students = _students.GetByGroup(_groupId, _schoolYear);
var entries = _entries.GetBySession(sessionId);
foreach (var s in students)
{
var entry = entries.FirstOrDefault(e => e.StudentId == s.Id)
?? new ParticipationEntry { SessionId = sessionId, GroupId = _groupId, StudentId = s.Id };
var row = new ParticipationStudentRow(s.Id, s.FullName, entry, Aspects.ToList());
row.OnRatingChanged = (studentId, key, val) => SaveRating(sessionId, studentId, key, val);
StudentRows.Add(row);
}
QuickInputCommand.NotifyCanExecuteChanged();
}
private void SaveRating(Guid sessionId, Guid studentId, string key, int? value)
{
var session = _sessions.GetById(sessionId);
var entry = _entries.GetBySessionAndStudent(sessionId, studentId)
?? new ParticipationEntry
{
SessionId = sessionId,
GroupId = _groupId,
StudentId = studentId,
Date = session?.Date ?? DateOnly.FromDateTime(DateTime.Today),
};
var existing = entry.Ratings.FirstOrDefault(r => r.Key == key);
if (value is null)
{
if (existing is not null) entry.Ratings.Remove(existing);
}
else
{
if (existing is null) entry.Ratings.Add(new AspectRating { Key = key, Value = value.Value });
else existing.Value = value.Value;
}
_entries.Save(entry);
}
[RelayCommand]
private async Task AddSession()
{
if (OnAddSession is null) return;
var session = await OnAddSession();
if (session is null) return;
session.GroupId = _groupId;
_sessions.Save(session);
LoadSessions();
SelectedSession = Sessions.FirstOrDefault(s => s.Id == session.Id);
}
[RelayCommand(CanExecute = nameof(CanQuickInput))]
private async Task QuickInput()
{
if (OnQuickInput is null) return;
await OnQuickInput(this);
if (SelectedSession is not null) LoadGrid(SelectedSession.Id);
}
private bool CanQuickInput() => SelectedSession is not null && StudentRows.Count > 0;
[RelayCommand]
private void DeleteSession()
{
if (SelectedSession is null) return;
_sessions.Delete(SelectedSession.Id);
LoadSessions();
}
public void SaveNote(Guid sessionId, Guid studentId, string note)
{
var entry = _entries.GetBySessionAndStudent(sessionId, studentId);
if (entry is null) return;
entry.Note = note;
_entries.Save(entry);
}
}
// ── Zeilendaten für das Bewertungsraster ─────────────────────────────────────
public partial class ParticipationStudentRow : ObservableObject
{
public Guid StudentId { get; }
public string Name { get; }
private readonly ParticipationEntry _entry;
private readonly IReadOnlyList<AspectColumnDef> _aspectDefs;
public ObservableCollection<RatingCell> Cells { get; } = [];
public Action<Guid, string, int?>? OnRatingChanged { get; set; }
public ParticipationStudentRow(Guid id, string name, ParticipationEntry entry, List<AspectColumnDef> aspects)
{
StudentId = id;
Name = name;
_entry = entry;
_aspectDefs = aspects;
foreach (var a in aspects)
{
var existing = entry.Ratings.FirstOrDefault(r => r.Key == a.Key);
var cell = new RatingCell(id, a.Key, existing?.Value);
cell.OnChanged = (sid, key, val) => OnRatingChanged?.Invoke(sid, key, val);
Cells.Add(cell);
}
}
public int? GetRating(string key) =>
_entry.Ratings.FirstOrDefault(r => r.Key == key)?.Value;
public void SetRating(string key, int? value)
{
var cell = Cells.FirstOrDefault(c => c.AspectKey == key);
cell?.SetValue(value);
OnRatingChanged?.Invoke(StudentId, key, value);
}
}
// ── Eine Bewertungszelle ──────────────────────────────────────────────────────
public partial class RatingCell : ObservableObject
{
public Guid StudentId { get; }
public string AspectKey { get; }
[ObservableProperty] private int? _value;
[ObservableProperty] private string _displayLabel = "";
public Action<Guid, string, int?>? OnChanged { get; set; }
public RatingCell(Guid studentId, string key, int? value)
{
StudentId = studentId;
AspectKey = key;
_value = value;
UpdateLabel();
}
public void SetValue(int? value)
{
Value = value;
UpdateLabel();
OnChanged?.Invoke(StudentId, AspectKey, value);
}
[RelayCommand]
private void CycleUp()
{
var next = Value is null ? -2 : Math.Min(2, Value.Value + 1);
SetValue(next);
}
[RelayCommand]
private void CycleDown()
{
var next = Value is null ? 2 : Math.Max(-2, Value.Value - 1);
SetValue(next);
}
[RelayCommand]
private void Clear() => SetValue(null);
private void UpdateLabel() => DisplayLabel = Value switch
{
2 => "++",
1 => "+",
0 => "~",
-1 => "",
-2 => "−−",
_ => "",
};
}
// ── Hilfsklassen ──────────────────────────────────────────────────────────────
public class AspectColumnDef
{
public string Key { get; }
public string Label { get; }
public AspectColumnDef(ParticipationAspect a) { Key = a.Key; Label = a.Label; }
}
public class ParticipationSessionItem
{
public Guid Id { get; }
public string Display { get; }
public string Comment { get; }
public DateOnly Date { get; }
public ParticipationSessionItem(ParticipationSession s)
{
Id = s.Id;
Date = s.Date;
Comment = s.Comment ?? "";
Display = s.Comment is { Length: > 0 }
? $"{s.Date:dd.MM.yyyy} {s.Comment}"
: s.Date.ToString("dd.MM.yyyy");
}
}
// ── Dialog: Sitzung anlegen ───────────────────────────────────────────────────
public partial class AddSessionDialogViewModel : ObservableObject
{
[ObservableProperty] private DateOnly _date = DateOnly.FromDateTime(DateTime.Today);
[ObservableProperty] private string _comment = "";
[ObservableProperty] private string _dateText = DateOnly.FromDateTime(DateTime.Today).ToString("dd.MM.yyyy");
[ObservableProperty] private string _validationMessage = "";
public ParticipationSession? Result { get; private set; }
[RelayCommand]
private void Save()
{
if (!DateOnly.TryParseExact(DateText, "dd.MM.yyyy", null,
System.Globalization.DateTimeStyles.None, out var date))
{
ValidationMessage = "Datum im Format TT.MM.JJJJ eingeben.";
return;
}
Result = new ParticipationSession { Date = date, Comment = Comment.Trim() };
}
}
// ── Dialog: Schnelleingabe ────────────────────────────────────────────────────
public partial class QuickInputViewModel : ObservableObject
{
private readonly List<ParticipationStudentRow> _rows;
private readonly List<AspectColumnDef> _aspects;
[ObservableProperty] private int _studentIndex;
[ObservableProperty] private int _aspectIndex;
[ObservableProperty] private string _studentName = "";
[ObservableProperty] private string _currentAspectLabel = "";
[ObservableProperty] private string _currentValueLabel = "";
[ObservableProperty] private string _progressText = "";
public ObservableCollection<QuickAspectRow> AspectRows { get; } = [];
public QuickInputViewModel(List<ParticipationStudentRow> rows, List<AspectColumnDef> aspects)
{
_rows = rows;
_aspects = aspects;
if (rows.Any()) ShowStudent(0);
}
private void ShowStudent(int index)
{
if (index < 0 || index >= _rows.Count) return;
StudentIndex = index;
var row = _rows[index];
StudentName = row.Name;
ProgressText = $"{index + 1} / {_rows.Count}";
AspectRows.Clear();
foreach (var (a, i) in _aspects.Select((a, i) => (a, i)))
{
var val = row.GetRating(a.Key);
AspectRows.Add(new QuickAspectRow(i, a.Label, val, i == AspectIndex));
}
UpdateCurrentAspect();
}
private void UpdateCurrentAspect()
{
if (!_aspects.Any()) return;
var safeIdx = Math.Clamp(AspectIndex, 0, _aspects.Count - 1);
CurrentAspectLabel = _aspects[safeIdx].Label;
var row = AspectRows.ElementAtOrDefault(safeIdx);
CurrentValueLabel = row?.DisplayLabel ?? "";
foreach (var r in AspectRows) r.IsActive = r.Index == safeIdx;
}
public void SetRatingByNumber(int num)
{
// 1=−−, 2=, 3=~, 4=+, 5=++
var val = num switch { 1 => -2, 2 => -1, 3 => 0, 4 => 1, 5 => 2, _ => (int?)null };
if (val is null) return;
ApplyRating(val.Value);
}
public void IncrementRating()
{
var cell = GetCurrentCell();
if (cell is null) return;
var next = cell.Value is null ? -2 : Math.Min(2, cell.Value.Value + 1);
ApplyRating(next);
}
public void DecrementRating()
{
var cell = GetCurrentCell();
if (cell is null) return;
var next = cell.Value is null ? 2 : Math.Max(-2, cell.Value.Value - 1);
ApplyRating(next);
}
private void ApplyRating(int val)
{
if (_rows.Count == 0 || !_aspects.Any()) return;
var key = _aspects[Math.Clamp(AspectIndex, 0, _aspects.Count - 1)].Key;
_rows[StudentIndex].SetRating(key, val);
var row = AspectRows.ElementAtOrDefault(AspectIndex);
if (row is not null)
{
row.Value = val;
row.UpdateLabel();
}
CurrentValueLabel = RatingLabel(val);
}
public void SelectAspect(int index)
{
if (index < 0 || index >= _aspects.Count) return;
AspectIndex = index;
UpdateCurrentAspect();
}
public void NextAspect()
{
AspectIndex = (AspectIndex + 1) % _aspects.Count;
UpdateCurrentAspect();
}
public void NextStudent()
{
if (StudentIndex >= _rows.Count - 1) return;
AspectIndex = 0;
ShowStudent(StudentIndex + 1);
}
public void PreviousStudent()
{
if (StudentIndex <= 0) return;
AspectIndex = 0;
ShowStudent(StudentIndex - 1);
}
public bool IsLastStudent => StudentIndex >= _rows.Count - 1;
private RatingCell? GetCurrentCell()
{
if (_rows.Count == 0 || !_aspects.Any()) return null;
var key = _aspects[Math.Clamp(AspectIndex, 0, _aspects.Count - 1)].Key;
return _rows[StudentIndex].Cells.FirstOrDefault(c => c.AspectKey == key);
}
private static string RatingLabel(int? v) => v switch
{
2 => "++", 1 => "+", 0 => "~", -1 => "", -2 => "−−", _ => "",
};
}
public partial class QuickAspectRow : ObservableObject
{
public int Index { get; }
public string Label { get; }
[ObservableProperty] private bool _isActive;
[ObservableProperty] private string _displayLabel = "";
public int? Value { get; set; }
public QuickAspectRow(int index, string label, int? value, bool isActive)
{
Index = index;
Label = label;
Value = value;
IsActive = isActive;
UpdateLabel();
}
public void UpdateLabel() => DisplayLabel = Value switch
{
2 => "++", 1 => "+", 0 => "~", -1 => "", -2 => "−−", _ => "·",
};
}