Kapitel 7 abgeschlossen.
This commit is contained in:
@@ -3,6 +3,7 @@ using CommunityToolkit.Mvvm.Input;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.ViewModels.Students;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace LehrerApp.Desktop.ViewModels.Groups;
|
||||
@@ -17,6 +18,7 @@ public partial class GroupListViewModel : ObservableObject
|
||||
public Action<Guid, int>? OnNavigateToDetail { get; set; }
|
||||
public Func<Task>? OnAddGroup { get; set; }
|
||||
public Func<Guid, Task>? OnEditGroup { get; set; }
|
||||
public Func<Guid, Task<Guid?>>? OnRollOverGroup { get; set; }
|
||||
public Func<GroupListItem, Task<bool>>? OnConfirmDelete { get; set; }
|
||||
|
||||
[ObservableProperty] private string _selectedSchoolYear = "";
|
||||
@@ -53,6 +55,7 @@ public partial class GroupListViewModel : ObservableObject
|
||||
OnPropertyChanged(nameof(SelectedGroupSubtitle));
|
||||
NavigateToSectionCommand.NotifyCanExecuteChanged();
|
||||
EditGroupCommand.NotifyCanExecuteChanged();
|
||||
RollOverGroupCommand.NotifyCanExecuteChanged();
|
||||
ToggleArchiveCommand.NotifyCanExecuteChanged();
|
||||
DeleteGroupCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
@@ -85,7 +88,7 @@ public partial class GroupListViewModel : ObservableObject
|
||||
}
|
||||
[RelayCommand] private void Refresh() => LoadGroups();
|
||||
|
||||
[RelayCommand(CanExecute = nameof(HasSelectedGroup))]
|
||||
[RelayCommand(CanExecute = nameof(CanEditSelectedGroup))]
|
||||
private async Task EditGroup()
|
||||
{
|
||||
if (SelectedGroup is null || OnEditGroup is null) return;
|
||||
@@ -95,6 +98,20 @@ public partial class GroupListViewModel : ObservableObject
|
||||
SelectedGroup = Groups.FirstOrDefault(g => g.Id == id);
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(HasSelectedGroup))]
|
||||
private async Task RollOverGroup()
|
||||
{
|
||||
if (SelectedGroup is null || OnRollOverGroup is null) return;
|
||||
var targetId = await OnRollOverGroup(SelectedGroup.Id);
|
||||
if (targetId is null) return;
|
||||
var target = _groups.GetById(targetId.Value);
|
||||
if (target is null) return;
|
||||
if (!SchoolYears.Contains(target.SchoolYear)) SchoolYears.Insert(0, target.SchoolYear);
|
||||
SelectedSchoolYear = target.SchoolYear;
|
||||
LoadGroups();
|
||||
SelectedGroup = Groups.FirstOrDefault(g => g.Id == target.Id);
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(HasSelectedGroup))]
|
||||
private void ToggleArchive()
|
||||
{
|
||||
@@ -106,7 +123,7 @@ public partial class GroupListViewModel : ObservableObject
|
||||
LoadGroups();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(HasSelectedGroup))]
|
||||
[RelayCommand(CanExecute = nameof(CanEditSelectedGroup))]
|
||||
private async Task DeleteGroup()
|
||||
{
|
||||
if (SelectedGroup is null || OnConfirmDelete is null) return;
|
||||
@@ -124,6 +141,7 @@ public partial class GroupListViewModel : ObservableObject
|
||||
}
|
||||
|
||||
private bool HasSelectedGroup() => SelectedGroup is not null;
|
||||
private bool CanEditSelectedGroup() => SelectedGroup?.IsActive == true;
|
||||
}
|
||||
|
||||
public class GroupListItem
|
||||
@@ -175,9 +193,17 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
// bevor LoadGroup() läuft (siehe MainWindowViewModel.NavigateToGroupDetail), Group ist dann
|
||||
// kurzzeitig null — ein verschachtelter Pfad würde dafür jedes Mal einen Binding-Fehler loggen.
|
||||
public bool IsDifferentiated => Group?.IsDifferentiated ?? false;
|
||||
public bool IsReadOnly => Group is { IsActive: false };
|
||||
public bool IsEditable => Group is { IsActive: true };
|
||||
public string SubjectName { get; private set; } = "";
|
||||
|
||||
partial void OnGroupChanged(LearningGroup? value) => OnPropertyChanged(nameof(IsDifferentiated));
|
||||
partial void OnGroupChanged(LearningGroup? value)
|
||||
{
|
||||
OnPropertyChanged(nameof(IsDifferentiated));
|
||||
OnPropertyChanged(nameof(IsReadOnly));
|
||||
OnPropertyChanged(nameof(IsEditable));
|
||||
NotifyWriteCommands();
|
||||
}
|
||||
partial void OnShowFormerStudentsChanged(bool value) => LoadStudents();
|
||||
|
||||
public ObservableCollection<StudentSummary> Students { get; } = [];
|
||||
@@ -194,6 +220,7 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
public Func<ExamSummary, Task<bool>>? OnConfirmDeleteExam { get; set; }
|
||||
public Func<Exam, Task>? OnGradeExam { get; set; }
|
||||
public Func<Exam, Task>? OnEvaluateExam { get; set; }
|
||||
public Func<Task<bool>>? OnConfirmReactivate { get; set; }
|
||||
|
||||
public GroupDetailViewModel(IGroupRepository groups, IStudentRepository students,
|
||||
IGroupMembershipRepository memberships, ISubjectRepository subjects,
|
||||
@@ -221,9 +248,9 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
$"Stufe {Group.GradeLevel} · Noten {(Group.GradingSystem == GradingSystem.Grades1To6 ? "1–6" : "0–15")}";
|
||||
LoadStudents();
|
||||
ReloadExams();
|
||||
ParticipationTab.Initialize(Group.Id, Group.SchoolYear);
|
||||
GradeOverviewTab.Initialize(Group.Id, Group.GradingSystem, Group.Type, GroupTitle, Group.SchoolYear);
|
||||
PlanningTab.Initialize(Group.Id);
|
||||
ParticipationTab.Initialize(Group.Id, Group.SchoolYear, IsReadOnly);
|
||||
GradeOverviewTab.Initialize(Group.Id, Group.GradingSystem, Group.Type, GroupTitle, Group.SchoolYear, IsReadOnly);
|
||||
PlanningTab.Initialize(Group.Id, IsReadOnly);
|
||||
}
|
||||
|
||||
private void ReloadExams()
|
||||
@@ -247,7 +274,11 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
{
|
||||
memberships.TryGetValue(s.Id, out var membership);
|
||||
if (membership is null) continue;
|
||||
var summary = new StudentSummary(s, membership, today) { OnChanged = SaveStudentNiveau };
|
||||
var summary = new StudentSummary(s, membership, today)
|
||||
{
|
||||
OnChanged = SaveStudentNiveau,
|
||||
CanEdit = IsEditable,
|
||||
};
|
||||
if (summary.IsFormer && !ShowFormerStudents) continue;
|
||||
Students.Add(summary);
|
||||
}
|
||||
@@ -255,14 +286,14 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
|
||||
private void SaveStudentNiveau(StudentSummary summary)
|
||||
{
|
||||
if (Group is null) return;
|
||||
if (!IsEditable || Group is null) return;
|
||||
var membership = _memberships.GetByStudentAndGroup(summary.Id, Group.Id);
|
||||
if (membership is null) return;
|
||||
membership.Niveau = summary.Niveau;
|
||||
_memberships.Save(membership);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
[RelayCommand(CanExecute = nameof(IsEditableGroup))]
|
||||
private async Task AddStudent()
|
||||
{
|
||||
if (OnAddStudent is null) return;
|
||||
@@ -274,7 +305,7 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(HasSelectedStudent))]
|
||||
[RelayCommand(CanExecute = nameof(CanEditSelectedStudent))]
|
||||
private async Task WithdrawStudent()
|
||||
{
|
||||
if (SelectedStudent is null || OnWithdrawStudent is null) return;
|
||||
@@ -284,7 +315,7 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
ParticipationTab.RefreshCurrentGrid();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanReinstateSelectedStudent))]
|
||||
[RelayCommand(CanExecute = nameof(CanEditAndReinstateSelectedStudent))]
|
||||
private void ReinstateStudent()
|
||||
{
|
||||
if (Group is null || SelectedStudent is null) return;
|
||||
@@ -303,10 +334,10 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
ReinstateStudentCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
private bool HasSelectedStudent() => SelectedStudent is not null;
|
||||
private bool CanReinstateSelectedStudent() => SelectedStudent?.HasExitDate == true;
|
||||
private bool CanEditSelectedStudent() => IsEditable && SelectedStudent is not null;
|
||||
private bool CanEditAndReinstateSelectedStudent() => IsEditable && SelectedStudent?.HasExitDate == true;
|
||||
|
||||
[RelayCommand]
|
||||
[RelayCommand(CanExecute = nameof(IsEditableGroup))]
|
||||
private async Task AddExam()
|
||||
{
|
||||
if (Group is null || OnAddExam is null) return;
|
||||
@@ -314,7 +345,7 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
if (saved) ReloadExams();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(HasSelectedExam))]
|
||||
[RelayCommand(CanExecute = nameof(CanEditSelectedExam))]
|
||||
private async Task EditExam()
|
||||
{
|
||||
if (SelectedExam is null || OnEditExam is null) return;
|
||||
@@ -329,7 +360,7 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(HasSelectedExam))]
|
||||
[RelayCommand(CanExecute = nameof(CanEditSelectedExam))]
|
||||
private async Task GradeExam()
|
||||
{
|
||||
if (SelectedExam is null || OnGradeExam is null) return;
|
||||
@@ -338,7 +369,7 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
await OnGradeExam(exam);
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(HasSelectedExam))]
|
||||
[RelayCommand(CanExecute = nameof(CanEditSelectedExam))]
|
||||
private async Task EvaluateExam()
|
||||
{
|
||||
if (SelectedExam is null || OnEvaluateExam is null) return;
|
||||
@@ -347,7 +378,7 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
await OnEvaluateExam(exam);
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(HasSelectedExam))]
|
||||
[RelayCommand(CanExecute = nameof(CanEditSelectedExam))]
|
||||
private async Task DuplicateExam()
|
||||
{
|
||||
if (SelectedExam is null || OnDuplicateExam is null) return;
|
||||
@@ -357,7 +388,7 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
if (saved) ReloadExams();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(HasSelectedExam))]
|
||||
[RelayCommand(CanExecute = nameof(CanEditSelectedExam))]
|
||||
private async Task DeleteExam()
|
||||
{
|
||||
if (SelectedExam is null || OnConfirmDeleteExam is null) return;
|
||||
@@ -368,7 +399,7 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
ReloadExams();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(HasSelectedExam))]
|
||||
[RelayCommand(CanExecute = nameof(CanEditSelectedExam))]
|
||||
private void AdvanceExamStatus()
|
||||
{
|
||||
if (SelectedExam is null) return;
|
||||
@@ -381,7 +412,7 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
});
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(HasSelectedExam))]
|
||||
[RelayCommand(CanExecute = nameof(CanEditSelectedExam))]
|
||||
private void SetExamStatus(ExamStatus status)
|
||||
{
|
||||
if (SelectedExam is null) return;
|
||||
@@ -409,7 +440,35 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
SetExamStatusCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
private bool HasSelectedExam() => SelectedExam is not null;
|
||||
private bool CanEditSelectedExam() => IsEditable && SelectedExam is not null;
|
||||
private bool IsEditableGroup() => IsEditable;
|
||||
|
||||
[RelayCommand(CanExecute = nameof(IsReadOnlyGroup))]
|
||||
private async Task ReactivateGroup()
|
||||
{
|
||||
if (Group is null || OnConfirmReactivate is null || !await OnConfirmReactivate()) return;
|
||||
Group.IsActive = true;
|
||||
_groups.Save(Group);
|
||||
LoadGroup(Group.Id);
|
||||
}
|
||||
|
||||
private bool IsReadOnlyGroup() => IsReadOnly;
|
||||
|
||||
private void NotifyWriteCommands()
|
||||
{
|
||||
AddStudentCommand.NotifyCanExecuteChanged();
|
||||
WithdrawStudentCommand.NotifyCanExecuteChanged();
|
||||
ReinstateStudentCommand.NotifyCanExecuteChanged();
|
||||
AddExamCommand.NotifyCanExecuteChanged();
|
||||
EditExamCommand.NotifyCanExecuteChanged();
|
||||
GradeExamCommand.NotifyCanExecuteChanged();
|
||||
EvaluateExamCommand.NotifyCanExecuteChanged();
|
||||
DuplicateExamCommand.NotifyCanExecuteChanged();
|
||||
DeleteExamCommand.NotifyCanExecuteChanged();
|
||||
AdvanceExamStatusCommand.NotifyCanExecuteChanged();
|
||||
SetExamStatusCommand.NotifyCanExecuteChanged();
|
||||
ReactivateGroupCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
[RelayCommand] private void Refresh() { if (Group is not null) LoadGroup(Group.Id); }
|
||||
}
|
||||
@@ -448,6 +507,9 @@ public partial class StudentSummary : ObservableObject
|
||||
public bool HasExitDate { get; }
|
||||
public string MembershipStatus { get; }
|
||||
public string WithdrawActionLabel => HasExitDate ? "Austrittsdatum ändern" : "Austragen";
|
||||
public string AvatarLabel { get; }
|
||||
public string AvatarColor { get; }
|
||||
public string AvatarTooltip { get; }
|
||||
|
||||
[ObservableProperty] private Niveau? _niveau;
|
||||
|
||||
@@ -458,11 +520,15 @@ public partial class StudentSummary : ObservableObject
|
||||
}
|
||||
|
||||
public Action<StudentSummary>? OnChanged { get; set; }
|
||||
public bool CanEdit { get; init; } = true;
|
||||
|
||||
public StudentSummary(Core.Models.Student s, GroupMembership? membership, DateOnly? today = null)
|
||||
{
|
||||
Id = s.Id;
|
||||
FullName = s.FullName;
|
||||
AvatarLabel = GenderAvatarDisplay.Label(s.Gender);
|
||||
AvatarColor = GenderAvatarDisplay.Color(s.Gender);
|
||||
AvatarTooltip = GenderAvatarDisplay.Tooltip(s.Gender);
|
||||
PeriodLabel = membership is null ? "" : BuildPeriodLabel(membership);
|
||||
HasExitDate = membership?.LeftAt.HasValue == true;
|
||||
IsFormer = membership?.LeftAt is { } leftAt
|
||||
|
||||
Reference in New Issue
Block a user