Files
LehrerApp/LehrerApp.Desktop/ViewModels/Groups/TeachingModeOutstandingHomeworkViewModel.cs
adminandClaude Opus 5.5 6c0a2dbd2e
CI / build-and-test (push) Canceled after 0s
Nachreichungen direkt im Unterrichtsmodus erfassen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:38:45 +02:00

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();
}
}