641 lines
23 KiB
C#
641 lines
23 KiB
C#
using CommunityToolkit.Mvvm.ComponentModel;
|
||
using CommunityToolkit.Mvvm.Input;
|
||
using LehrerApp.Core.Interfaces;
|
||
using LehrerApp.Core.Models;
|
||
using System.Collections.ObjectModel;
|
||
using System.Collections.Generic;
|
||
|
||
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 readonly IGroupMembershipRepository _memberships;
|
||
private readonly IGroupRepository _groups;
|
||
private readonly ICompetencyDomainRepository _competencyDomains;
|
||
|
||
private Guid _groupId;
|
||
private string _schoolYear = "";
|
||
private Guid? _subjectId;
|
||
private int _gradeLevel;
|
||
private GradingSystem _gradingSystem;
|
||
|
||
public Guid GroupId => _groupId;
|
||
public string SchoolYear => _schoolYear;
|
||
public GradingSystem GradingSystem => _gradingSystem;
|
||
|
||
[ObservableProperty] private ParticipationSessionItem? _selectedSession;
|
||
[ObservableProperty] private string _noDataText = "Keine Bewertungssitzung ausgewählt.";
|
||
[ObservableProperty] private bool _competencyTagsVisible;
|
||
[ObservableProperty] private bool _hasCompetencyCatalog;
|
||
[ObservableProperty] private bool _studentCompetencyRatingsVisible;
|
||
[ObservableProperty] private int _rebuildColumnsSignal;
|
||
|
||
public string SelectedSessionDisplay => SelectedSession?.Display ?? "";
|
||
public List<string> ActiveCompetencyCodes { get; private set; } = [];
|
||
|
||
public ObservableCollection<ParticipationSessionItem> Sessions { get; } = [];
|
||
public ObservableCollection<ParticipationStudentRow> StudentRows { get; } = [];
|
||
public ObservableCollection<AspectColumnDef> Aspects { get; } = [];
|
||
public ObservableCollection<CompetencyTagGroup> CompetencyTagGroups { get; } = [];
|
||
|
||
public Func<Task<ParticipationSession?>>? OnAddSession { get; set; }
|
||
public Func<ParticipationTabViewModel, Task>? OnQuickInput { get; set; }
|
||
public Func<ParticipationTabViewModel, Task>? OnComputeGrade { get; set; }
|
||
|
||
public ParticipationTabViewModel(
|
||
IParticipationSessionRepository sessions,
|
||
IParticipationRepository entries,
|
||
IParticipationAspectRepository aspects,
|
||
IStudentRepository students,
|
||
IGroupMembershipRepository memberships,
|
||
IGroupRepository groups,
|
||
ICompetencyDomainRepository competencyDomains)
|
||
{
|
||
_sessions = sessions; _entries = entries;
|
||
_aspects = aspects; _students = students;
|
||
_memberships = memberships; _groups = groups;
|
||
_competencyDomains = competencyDomains;
|
||
}
|
||
|
||
public void Initialize(Guid groupId, string schoolYear)
|
||
{
|
||
_groupId = groupId;
|
||
_schoolYear = schoolYear;
|
||
|
||
var group = _groups.GetById(groupId);
|
||
_subjectId = group?.SubjectId;
|
||
_gradeLevel = group?.GradeLevel ?? 0;
|
||
_gradingSystem = group?.GradingSystem ?? GradingSystem.Grades1To6;
|
||
|
||
HasCompetencyCatalog = _subjectId.HasValue
|
||
&& _competencyDomains.GetBySubjectAndGrade(_subjectId.Value, _gradeLevel).Count > 0;
|
||
|
||
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();
|
||
CompetencyTagGroups.Clear();
|
||
ActiveCompetencyCodes = [];
|
||
QuickInputCommand.NotifyCanExecuteChanged();
|
||
RebuildColumnsSignal++;
|
||
return;
|
||
}
|
||
LoadCompetencyTags(value.Id); // sets ActiveCompetencyCodes first
|
||
LoadGrid(value.Id); // uses ActiveCompetencyCodes, fires RebuildColumnsSignal++
|
||
}
|
||
|
||
private void LoadGrid(Guid sessionId)
|
||
{
|
||
StudentRows.Clear();
|
||
var session = _sessions.GetById(sessionId);
|
||
var sessionDate = session?.Date ?? DateOnly.FromDateTime(DateTime.Today);
|
||
var students = _students.GetByGroup(_groupId);
|
||
var memberships = _memberships.GetByGroup(_groupId);
|
||
var entries = _entries.GetBySession(sessionId);
|
||
|
||
foreach (var s in students)
|
||
{
|
||
var membership = memberships.FirstOrDefault(e => e.StudentId == s.Id);
|
||
if (membership is not null && !IsMemberAtDate(membership, sessionDate))
|
||
continue;
|
||
|
||
var entry = entries.FirstOrDefault(e => e.StudentId == s.Id)
|
||
?? new ParticipationEntry { SessionId = sessionId, StudentId = s.Id };
|
||
var row = new ParticipationStudentRow(s.Id, s.FullName, entry, Aspects.ToList(), ActiveCompetencyCodes);
|
||
row.OnRatingChanged = (sid, key, val) => SaveRating(sessionId, sid, key, val);
|
||
row.OnCompetencyRatingChanged = (sid, code, val) => SaveCompetencyRating(sessionId, sid, code, val);
|
||
StudentRows.Add(row);
|
||
}
|
||
QuickInputCommand.NotifyCanExecuteChanged();
|
||
RebuildColumnsSignal++;
|
||
}
|
||
|
||
private static bool IsMemberAtDate(GroupMembership membership, DateOnly date) => membership.Period switch
|
||
{
|
||
MembershipPeriod.H1Only => date.Month >= 8 || date.Month <= 1,
|
||
MembershipPeriod.H2Only => date.Month >= 2 && date.Month <= 7,
|
||
MembershipPeriod.Custom => (membership.JoinedAt is null || date >= membership.JoinedAt.Value)
|
||
&& (membership.LeftAt is null || date <= membership.LeftAt.Value),
|
||
_ => true,
|
||
};
|
||
|
||
private void SaveRating(Guid sessionId, Guid studentId, string key, int? value)
|
||
{
|
||
var entry = _entries.GetBySessionAndStudent(sessionId, studentId)
|
||
?? new ParticipationEntry
|
||
{
|
||
SessionId = sessionId,
|
||
StudentId = studentId,
|
||
};
|
||
|
||
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);
|
||
}
|
||
|
||
public void RefreshCurrentGrid()
|
||
{
|
||
if (SelectedSession is not null) LoadGrid(SelectedSession.Id);
|
||
}
|
||
|
||
[RelayCommand]
|
||
private void ToggleCompetencyTags() => CompetencyTagsVisible = !CompetencyTagsVisible;
|
||
|
||
partial void OnStudentCompetencyRatingsVisibleChanged(bool value) => RebuildColumnsSignal++;
|
||
|
||
private void LoadCompetencyTags(Guid sessionId)
|
||
{
|
||
CompetencyTagGroups.Clear();
|
||
var session = _sessions.GetById(sessionId);
|
||
ActiveCompetencyCodes = session?.CompetencyCodes?.ToList() ?? [];
|
||
|
||
if (!_subjectId.HasValue) return;
|
||
var active = ActiveCompetencyCodes.ToHashSet();
|
||
|
||
foreach (var domain in _competencyDomains.GetBySubjectAndGrade(_subjectId.Value, _gradeLevel))
|
||
{
|
||
var group = new CompetencyTagGroup(domain.Name, domain.Code);
|
||
foreach (var item in domain.Items.OrderBy(i => i.SortOrder))
|
||
{
|
||
var tag = new CompetencyTag(item.Code, item.Description, active.Contains(item.Code));
|
||
tag.OnChanged = (code, sel) => OnTagToggled(code, sel);
|
||
group.Items.Add(tag);
|
||
}
|
||
if (group.Items.Count > 0)
|
||
CompetencyTagGroups.Add(group);
|
||
}
|
||
}
|
||
|
||
private void OnTagToggled(string code, bool selected)
|
||
{
|
||
if (SelectedSession is null) return;
|
||
var session = _sessions.GetById(SelectedSession.Id);
|
||
if (session is null) return;
|
||
if (selected) { if (!session.CompetencyCodes.Contains(code)) session.CompetencyCodes.Add(code); }
|
||
else { session.CompetencyCodes.Remove(code); }
|
||
_sessions.Save(session);
|
||
|
||
ActiveCompetencyCodes = session.CompetencyCodes.ToList();
|
||
if (StudentCompetencyRatingsVisible)
|
||
LoadGrid(SelectedSession.Id); // reloads rows with updated cells, fires RebuildColumnsSignal++
|
||
}
|
||
|
||
[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);
|
||
|
||
Sessions.Clear();
|
||
foreach (var s in _sessions.GetByGroup(_groupId))
|
||
Sessions.Add(new ParticipationSessionItem(s));
|
||
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 async Task ComputeGrade()
|
||
{
|
||
if (OnComputeGrade is null) return;
|
||
await OnComputeGrade(this);
|
||
}
|
||
|
||
[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);
|
||
}
|
||
|
||
private void SaveCompetencyRating(Guid sessionId, Guid studentId, string code, int? value)
|
||
{
|
||
var entry = _entries.GetBySessionAndStudent(sessionId, studentId)
|
||
?? new ParticipationEntry
|
||
{
|
||
SessionId = sessionId,
|
||
StudentId = studentId,
|
||
};
|
||
var existing = entry.CompetencyRatings.FirstOrDefault(r => r.Code == code);
|
||
if (value is null)
|
||
{
|
||
if (existing is not null) entry.CompetencyRatings.Remove(existing);
|
||
}
|
||
else
|
||
{
|
||
if (existing is null) entry.CompetencyRatings.Add(new CompetencyRating { Code = code, Value = value.Value });
|
||
else existing.Value = value.Value;
|
||
}
|
||
_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 ObservableCollection<RatingCell> CompetencyCells { get; } = [];
|
||
|
||
public Action<Guid, string, int?>? OnRatingChanged { get; set; }
|
||
public Action<Guid, string, int?>? OnCompetencyRatingChanged { get; set; }
|
||
|
||
public ParticipationStudentRow(Guid id, string name, ParticipationEntry entry,
|
||
List<AspectColumnDef> aspects, List<string> competencyCodes)
|
||
{
|
||
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);
|
||
}
|
||
|
||
foreach (var code in competencyCodes)
|
||
{
|
||
var existing = entry.CompetencyRatings.FirstOrDefault(r => r.Code == code);
|
||
var cell = new RatingCell(id, code, existing?.Value);
|
||
cell.OnChanged = (sid, key, val) => OnCompetencyRatingChanged?.Invoke(sid, key, val);
|
||
CompetencyCells.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 => "−−",
|
||
_ => "",
|
||
};
|
||
}
|
||
|
||
// ── Kompetenz-Tags ────────────────────────────────────────────────────────────
|
||
|
||
public class CompetencyTagGroup(string name, string code)
|
||
{
|
||
public string Name { get; } = name;
|
||
public string Code { get; } = code;
|
||
public string DisplayName { get; } = string.IsNullOrEmpty(code) ? name : $"{name} ({code})";
|
||
public List<CompetencyTag> Items { get; } = [];
|
||
}
|
||
|
||
public partial class CompetencyTag : ObservableObject
|
||
{
|
||
public string Code { get; }
|
||
public string Description { get; }
|
||
public string Display { get; }
|
||
|
||
[ObservableProperty] private bool _isSelected;
|
||
|
||
public Action<string, bool>? OnChanged { get; set; }
|
||
|
||
public CompetencyTag(string code, string description, bool isSelected)
|
||
{
|
||
Code = code;
|
||
Description = description;
|
||
Display = string.IsNullOrEmpty(code) ? description : $"[{code}] {description}";
|
||
_isSelected = isSelected;
|
||
}
|
||
|
||
partial void OnIsSelectedChanged(bool value) => OnChanged?.Invoke(Code, value);
|
||
}
|
||
|
||
// ── 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 => "−−", _ => "·",
|
||
};
|
||
}
|