CI / build-and-test (push) Canceled after 0s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
167 lines
7.4 KiB
C#
167 lines
7.4 KiB
C#
using System.Collections.ObjectModel;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using LehrerApp.Core.Interfaces;
|
|
using LehrerApp.Core.Models;
|
|
using LehrerApp.Core.Services;
|
|
|
|
namespace LehrerApp.Desktop.ViewModels.Groups;
|
|
|
|
/// <summary>Eine offene Nachreichung. Die Aktionen setzen nur die Endzustände, die beim Nachreichen
|
|
/// sinnvoll sind: nachgereicht, endgültig nicht nachgereicht oder (bei ganz fehlender Hausaufgabe)
|
|
/// teilweise angefertigt mit endgültig fehlendem Rest.</summary>
|
|
public sealed class OutstandingHomeworkRow
|
|
{
|
|
public OutstandingHomeworkRow(Guid sessionId, Guid studentId, string studentName, DateOnly date,
|
|
HomeworkStatus status, bool canEdit, Action<OutstandingHomeworkRow, HomeworkStatus> apply)
|
|
{
|
|
SessionId = sessionId;
|
|
StudentId = studentId;
|
|
StudentName = studentName;
|
|
Date = date;
|
|
Status = status;
|
|
CanEdit = canEdit;
|
|
MarkSubmittedCommand = new RelayCommand(() => apply(this, SubmittedStatus));
|
|
MarkFinalCommand = new RelayCommand(() => apply(this, FinalStatus));
|
|
MarkPartialCommand = new RelayCommand(() => apply(this, HomeworkStatus.PartialMissingOverdue));
|
|
}
|
|
|
|
public Guid SessionId { get; }
|
|
public Guid StudentId { get; }
|
|
public string StudentName { get; }
|
|
public DateOnly Date { get; }
|
|
public HomeworkStatus Status { get; }
|
|
public string DateLabel => Date.ToString("dd.MM.yyyy");
|
|
public string StatusLabel => HomeworkDisplay.Label(Status);
|
|
|
|
private bool IsPartial => Status is HomeworkStatus.PartiallyCompleted or HomeworkStatus.PartialMissingOverdue;
|
|
private bool IsFinal => Status is HomeworkStatus.MissingOverdue or HomeworkStatus.PartialMissingOverdue;
|
|
|
|
public HomeworkStatus SubmittedStatus =>
|
|
IsPartial ? HomeworkStatus.PartialSubmittedLate : HomeworkStatus.SubmittedLate;
|
|
public HomeworkStatus FinalStatus =>
|
|
IsPartial ? HomeworkStatus.PartialMissingOverdue : HomeworkStatus.MissingOverdue;
|
|
|
|
public string SubmittedLabel => IsPartial ? "◕ Rest nachgereicht" : "↺ Nachgereicht";
|
|
public string FinalLabel => IsPartial ? "✕ Rest endgültig nicht" : "✕ Endgültig nicht";
|
|
|
|
public bool CanEdit { get; }
|
|
public bool CanMarkFinal => CanEdit && !IsFinal;
|
|
public bool CanMarkPartial => CanEdit && !IsPartial;
|
|
|
|
public IRelayCommand MarkSubmittedCommand { get; }
|
|
public IRelayCommand MarkFinalCommand { get; }
|
|
public IRelayCommand MarkPartialCommand { get; }
|
|
}
|
|
|
|
/// <summary>Jüngste noch fehlende Hausaufgaben der aktuellen Lerngruppe. Liest bei jedem Öffnen
|
|
/// und auf Wunsch erneut, damit die Anwesenheit der laufenden Stunde berücksichtigt wird.
|
|
/// Nachreichungen lassen sich direkt in der Liste abschließen, ohne die alte Sitzung zu öffnen;
|
|
/// die jeweils letzte Änderung kann rückgängig gemacht werden.</summary>
|
|
public partial class TeachingModeOutstandingHomeworkViewModel(
|
|
Guid groupId, DateOnly lessonDate,
|
|
IStudentRepository students, IGroupMembershipRepository memberships,
|
|
IParticipationSessionRepository sessions, IParticipationRepository participation,
|
|
bool isReadOnly = false) : ObservableObject
|
|
{
|
|
private const int LookbackDays = 120;
|
|
private const int MaxRows = 20;
|
|
private Guid? _currentSessionId;
|
|
private (Guid EntryId, Guid SessionId, Guid StudentId, HomeworkStatus? Homework, bool Missing)? _undo;
|
|
|
|
public ObservableCollection<OutstandingHomeworkRow> Rows { get; } = [];
|
|
public string Header => $"Offene Nachreichungen ({Rows.Count})";
|
|
public bool HasRows => Rows.Count > 0;
|
|
|
|
[ObservableProperty] private bool _isExpanded;
|
|
[ObservableProperty] private bool _includeAbsentStudents;
|
|
[ObservableProperty] private string? _lastChangeLabel;
|
|
|
|
/// <summary>Wird nach jeder Statusänderung aufgerufen, damit andere Ansichten neu laden können.</summary>
|
|
public Action? OnHomeworkChanged { get; set; }
|
|
|
|
partial void OnIsExpandedChanged(bool value)
|
|
{
|
|
if (value) Refresh();
|
|
}
|
|
|
|
partial void OnIncludeAbsentStudentsChanged(bool value) => Refresh();
|
|
|
|
public void SetCurrentSessionId(Guid? sessionId) => _currentSessionId = sessionId;
|
|
|
|
[RelayCommand]
|
|
public void Refresh()
|
|
{
|
|
Rows.Clear();
|
|
var groupMemberships = memberships.GetByGroup(groupId);
|
|
var roster = students.GetByGroup(groupId)
|
|
.Where(s => groupMemberships.FirstOrDefault(m => m.StudentId == s.Id) is not { } membership ||
|
|
GroupMembershipService.IsActiveOn(membership, lessonDate))
|
|
.ToDictionary(s => s.Id);
|
|
|
|
var absentIds = _currentSessionId is { } sessionId
|
|
? participation.GetBySession(sessionId)
|
|
.Where(e => e.Attendance is not null and not AttendanceStatus.Present
|
|
and not AttendanceStatus.Late and not AttendanceStatus.SignificantlyLate)
|
|
.Select(e => e.StudentId).ToHashSet()
|
|
: [];
|
|
|
|
var rows = sessions.GetByGroup(groupId)
|
|
.Where(s => s.Date < lessonDate && s.Date >= lessonDate.AddDays(-LookbackDays))
|
|
.SelectMany(session => participation.GetBySession(session.Id)
|
|
.Select(entry => (session.Date, Entry: entry)))
|
|
.Where(x => roster.ContainsKey(x.Entry.StudentId) &&
|
|
(IncludeAbsentStudents || !absentIds.Contains(x.Entry.StudentId)))
|
|
.Select(x => (x.Date, x.Entry.SessionId, x.Entry.StudentId, Status: HomeworkDisplay.Effective(x.Entry)))
|
|
.Where(x => x.Status is { } status && HomeworkDisplay.CountsAsMissing(status))
|
|
.OrderByDescending(x => x.Date)
|
|
.ThenBy(x => roster[x.StudentId].FullName)
|
|
.Take(MaxRows);
|
|
|
|
foreach (var row in rows)
|
|
Rows.Add(new OutstandingHomeworkRow(row.SessionId, row.StudentId, roster[row.StudentId].FullName,
|
|
row.Date, row.Status!.Value, !isReadOnly, Apply));
|
|
OnPropertyChanged(nameof(Header));
|
|
OnPropertyChanged(nameof(HasRows));
|
|
}
|
|
|
|
public bool CanUndo => _undo is not null;
|
|
|
|
public void Apply(OutstandingHomeworkRow row, HomeworkStatus status)
|
|
{
|
|
if (isReadOnly) return;
|
|
var entry = participation.GetBySessionAndStudent(row.SessionId, row.StudentId);
|
|
if (entry is null) return;
|
|
_undo = (entry.Id, entry.SessionId, entry.StudentId, entry.Homework, entry.HomeworkMissing);
|
|
entry.Homework = status;
|
|
entry.HomeworkMissing = HomeworkDisplay.CountsAsMissing(status);
|
|
entry.UpdatedAt = DateTime.UtcNow;
|
|
participation.Save(entry);
|
|
LastChangeLabel = $"{row.StudentName} ({row.DateLabel}): {HomeworkDisplay.Label(status)}";
|
|
OnPropertyChanged(nameof(CanUndo));
|
|
UndoCommand.NotifyCanExecuteChanged();
|
|
Refresh();
|
|
OnHomeworkChanged?.Invoke();
|
|
}
|
|
|
|
[RelayCommand(CanExecute = nameof(CanUndo))]
|
|
private void Undo()
|
|
{
|
|
if (_undo is not { } undo) return;
|
|
_undo = null;
|
|
var entry = participation.GetBySessionAndStudent(undo.SessionId, undo.StudentId);
|
|
if (entry is not null && entry.Id == undo.EntryId)
|
|
{
|
|
entry.Homework = undo.Homework;
|
|
entry.HomeworkMissing = undo.Missing;
|
|
entry.UpdatedAt = DateTime.UtcNow;
|
|
participation.Save(entry);
|
|
}
|
|
LastChangeLabel = null;
|
|
OnPropertyChanged(nameof(CanUndo));
|
|
UndoCommand.NotifyCanExecuteChanged();
|
|
Refresh();
|
|
OnHomeworkChanged?.Invoke();
|
|
}
|
|
}
|