Kapitel 7 abgeschlossen.
This commit is contained in:
@@ -37,6 +37,7 @@ public partial class GradeOverviewTabViewModel : ObservableObject
|
||||
[ObservableProperty] private bool _showAsPoints = true;
|
||||
[ObservableProperty] private GradeOverviewRow? _selectedRow;
|
||||
[ObservableProperty] private int _rebuildColumnsSignal;
|
||||
[ObservableProperty] private bool _isReadOnly;
|
||||
|
||||
public bool CanTogglePointsView => _gradingSystem == GradingSystem.Points0To15;
|
||||
|
||||
@@ -64,13 +65,14 @@ public partial class GradeOverviewTabViewModel : ObservableObject
|
||||
}
|
||||
|
||||
public void Initialize(Guid groupId, GradingSystem gradingSystem, GroupType groupType,
|
||||
string groupLabel, string schoolYear)
|
||||
string groupLabel, string schoolYear, bool isReadOnly = false)
|
||||
{
|
||||
_groupId = groupId;
|
||||
_gradingSystem = gradingSystem;
|
||||
_groupType = groupType;
|
||||
_groupLabel = groupLabel;
|
||||
_schoolYear = schoolYear;
|
||||
IsReadOnly = isReadOnly;
|
||||
ShowAsPoints = gradingSystem == GradingSystem.Points0To15;
|
||||
OnPropertyChanged(nameof(CanTogglePointsView));
|
||||
Recompute();
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace LehrerApp.Desktop.ViewModels.Groups;
|
||||
|
||||
public partial class GroupRolloverDialogViewModel : ObservableObject
|
||||
{
|
||||
private readonly LearningGroup _source;
|
||||
private readonly GroupRolloverService _rollover;
|
||||
private readonly SchoolYearService _schoolYears;
|
||||
|
||||
[ObservableProperty] private string _name;
|
||||
[ObservableProperty] private string _targetSchoolYear;
|
||||
[ObservableProperty] private int _gradeLevel;
|
||||
[ObservableProperty] private bool _copyGradingScheme;
|
||||
[ObservableProperty] private bool _archiveSource = true;
|
||||
[ObservableProperty] private string _nameError = "";
|
||||
[ObservableProperty] private string _schoolYearError = "";
|
||||
[ObservableProperty] private string _selectionError = "";
|
||||
[ObservableProperty] private string _generalError = "";
|
||||
|
||||
public string SourceSummary => $"{_source.Name} · {_source.SchoolYear} · Stufe {_source.GradeLevel}";
|
||||
public bool HasGradingScheme { get; }
|
||||
public int SelectedCount => Students.Count(s => s.IsSelected);
|
||||
public string SelectedCountText => $"{SelectedCount} von {Students.Count} Schülern ausgewählt";
|
||||
public ObservableCollection<GroupRolloverStudentItem> Students { get; } = [];
|
||||
public LearningGroup? Result { get; private set; }
|
||||
|
||||
public GroupRolloverDialogViewModel(LearningGroup source,
|
||||
IStudentRepository students, IGroupMembershipRepository memberships,
|
||||
IGradingSchemeRepository gradingSchemes, SchoolYearService schoolYears,
|
||||
GroupRolloverService rollover)
|
||||
{
|
||||
_source = source;
|
||||
_rollover = rollover;
|
||||
_schoolYears = schoolYears;
|
||||
_name = source.Name;
|
||||
_targetSchoolYear = NextSchoolYear(source.SchoolYear, schoolYears);
|
||||
_gradeLevel = Math.Min(13, source.GradeLevel + 1);
|
||||
HasGradingScheme = gradingSchemes.GetByGroup(source.Id) is not null;
|
||||
_copyGradingScheme = HasGradingScheme;
|
||||
|
||||
var sourceEnd = schoolYears.SchoolYearEnd(source.SchoolYear);
|
||||
foreach (var membership in memberships.GetByGroup(source.Id)
|
||||
.Select(m => (Membership: m, Student: students.GetById(m.StudentId)))
|
||||
.Where(x => x.Student is not null)
|
||||
.OrderBy(x => x.Student!.LastName).ThenBy(x => x.Student!.FirstName))
|
||||
{
|
||||
var canSelect = membership.Student!.IsActive;
|
||||
var isPreselected = canSelect
|
||||
&& GroupMembershipService.IsActiveOn(membership.Membership, sourceEnd);
|
||||
var item = new GroupRolloverStudentItem(
|
||||
membership.Student, membership.Membership, isPreselected, canSelect);
|
||||
item.PropertyChanged += (_, e) =>
|
||||
{
|
||||
if (e.PropertyName != nameof(GroupRolloverStudentItem.IsSelected)) return;
|
||||
SelectionError = "";
|
||||
OnPropertyChanged(nameof(SelectedCount));
|
||||
OnPropertyChanged(nameof(SelectedCountText));
|
||||
};
|
||||
Students.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Save()
|
||||
{
|
||||
NameError = "";
|
||||
SchoolYearError = "";
|
||||
SelectionError = "";
|
||||
GeneralError = "";
|
||||
var valid = true;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Name))
|
||||
{
|
||||
NameError = "Gruppenname erforderlich.";
|
||||
valid = false;
|
||||
}
|
||||
try
|
||||
{
|
||||
var targetText = TargetSchoolYear.Trim();
|
||||
var targetStart = _schoolYears.SchoolYearStart(targetText);
|
||||
if (targetText != _schoolYears.FormatSchoolYear(targetStart.Year))
|
||||
{
|
||||
SchoolYearError = "Format JJJJ/JJ, z.B. 2027/28.";
|
||||
valid = false;
|
||||
}
|
||||
else if (targetStart <= _schoolYears.SchoolYearStart(_source.SchoolYear))
|
||||
{
|
||||
SchoolYearError = "Das Zielschuljahr muss nach dem bisherigen liegen.";
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is FormatException or ArgumentOutOfRangeException or IndexOutOfRangeException)
|
||||
{
|
||||
SchoolYearError = "Format JJJJ/JJ, z.B. 2027/28.";
|
||||
valid = false;
|
||||
}
|
||||
if (SelectedCount == 0)
|
||||
{
|
||||
SelectionError = "Bitte mindestens einen Schüler auswählen.";
|
||||
valid = false;
|
||||
}
|
||||
if (!valid) return;
|
||||
|
||||
try
|
||||
{
|
||||
Result = _rollover.RollOver(_source, new GroupRolloverRequest(
|
||||
Name, TargetSchoolYear.Trim(), GradeLevel,
|
||||
Students.Where(s => s.IsSelected).Select(s => s.MembershipId).ToList(),
|
||||
CopyGradingScheme, ArchiveSource));
|
||||
}
|
||||
catch (Exception ex) when (ex is InvalidOperationException or FormatException or ArgumentOutOfRangeException)
|
||||
{
|
||||
GeneralError = ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
private static string NextSchoolYear(string source, SchoolYearService schoolYears)
|
||||
{
|
||||
var start = schoolYears.SchoolYearStart(source);
|
||||
return schoolYears.FormatSchoolYear(start.Year + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class GroupRolloverStudentItem : ObservableObject
|
||||
{
|
||||
[ObservableProperty] private bool _isSelected;
|
||||
|
||||
public Guid MembershipId { get; }
|
||||
public string FullName { get; }
|
||||
public string NiveauText { get; }
|
||||
public string MembershipStatus { get; }
|
||||
public bool CanSelect { get; }
|
||||
|
||||
public GroupRolloverStudentItem(Student student, GroupMembership membership,
|
||||
bool isSelected, bool canSelect)
|
||||
{
|
||||
MembershipId = membership.Id;
|
||||
FullName = student.FullName;
|
||||
NiveauText = membership.Niveau?.ToString() ?? "–";
|
||||
CanSelect = canSelect;
|
||||
_isSelected = isSelected;
|
||||
MembershipStatus = BuildStatus(student, membership, isSelected);
|
||||
}
|
||||
|
||||
partial void OnIsSelectedChanged(bool value)
|
||||
{
|
||||
if (!CanSelect && value) IsSelected = false;
|
||||
}
|
||||
|
||||
private static string BuildStatus(Student student, GroupMembership membership, bool isPreselected)
|
||||
{
|
||||
if (!student.IsActive) return "Schüler inaktiv · nicht übernehmbar";
|
||||
if (isPreselected) return "Bis Schuljahresende aktiv · vorausgewählt";
|
||||
if (membership.LeftAt is { } leftAt) return $"Ausgetreten am {leftAt:dd.MM.yyyy} · nicht vorausgewählt";
|
||||
if (membership.Period == MembershipPeriod.H1Only) return "Nur 1. Halbjahr · nicht vorausgewählt";
|
||||
return "Am Schuljahresende nicht aktiv · nicht vorausgewählt";
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -37,6 +37,7 @@ public partial class ParticipationTabViewModel : ObservableObject
|
||||
[ObservableProperty] private bool _hasCompetencyCatalog;
|
||||
[ObservableProperty] private bool _studentCompetencyRatingsVisible;
|
||||
[ObservableProperty] private int _rebuildColumnsSignal;
|
||||
[ObservableProperty] private bool _isReadOnly;
|
||||
|
||||
public string SelectedSessionDisplay => SelectedSession?.Display ?? "";
|
||||
public List<string> ActiveCompetencyCodes { get; private set; } = [];
|
||||
@@ -67,10 +68,11 @@ public partial class ParticipationTabViewModel : ObservableObject
|
||||
_competencyDomains = competencyDomains;
|
||||
}
|
||||
|
||||
public void Initialize(Guid groupId, string schoolYear)
|
||||
public void Initialize(Guid groupId, string schoolYear, bool isReadOnly = false)
|
||||
{
|
||||
_groupId = groupId;
|
||||
_schoolYear = schoolYear;
|
||||
IsReadOnly = isReadOnly;
|
||||
|
||||
var group = _groups.GetById(groupId);
|
||||
_subjectId = group?.SubjectId;
|
||||
@@ -160,6 +162,7 @@ public partial class ParticipationTabViewModel : ObservableObject
|
||||
|
||||
private void SaveRating(Guid sessionId, Guid studentId, string key, int? value)
|
||||
{
|
||||
if (IsReadOnly) return;
|
||||
var entry = _entries.GetBySessionAndStudent(sessionId, studentId)
|
||||
?? new ParticipationEntry
|
||||
{
|
||||
@@ -187,6 +190,7 @@ public partial class ParticipationTabViewModel : ObservableObject
|
||||
|
||||
private void SaveHomework(Guid sessionId, Guid studentId, HomeworkStatus? value)
|
||||
{
|
||||
if (IsReadOnly) return;
|
||||
var entry = _entries.GetBySessionAndStudent(sessionId, studentId)
|
||||
?? new ParticipationEntry { SessionId = sessionId, StudentId = studentId };
|
||||
entry.Homework = value;
|
||||
@@ -196,6 +200,7 @@ public partial class ParticipationTabViewModel : ObservableObject
|
||||
|
||||
private void SaveAttendance(Guid sessionId, Guid studentId, AttendanceStatus? value)
|
||||
{
|
||||
if (IsReadOnly) return;
|
||||
var entry = _entries.GetBySessionAndStudent(sessionId, studentId)
|
||||
?? new ParticipationEntry { SessionId = sessionId, StudentId = studentId };
|
||||
entry.Attendance = value;
|
||||
|
||||
@@ -39,6 +39,7 @@ public partial class PlanningTabViewModel : ObservableObject
|
||||
|
||||
[ObservableProperty] private UnitSummary? _selectedUnit;
|
||||
[ObservableProperty] private LessonSummary? _selectedLesson;
|
||||
[ObservableProperty] private bool _isReadOnly;
|
||||
|
||||
// Eigene Property statt "SelectedUnit.Title" im Binding-Pfad: SelectedUnit ist zwischen
|
||||
// Gruppenwechsel/Laden kurzzeitig null — ein verschachtelter Pfad würde dafür jedes Mal
|
||||
@@ -74,9 +75,10 @@ public partial class PlanningTabViewModel : ObservableObject
|
||||
_subjects = subjects; _competencyDomains = competencyDomains;
|
||||
}
|
||||
|
||||
public void Initialize(Guid groupId)
|
||||
public void Initialize(Guid groupId, bool isReadOnly = false)
|
||||
{
|
||||
_groupId = groupId;
|
||||
IsReadOnly = isReadOnly;
|
||||
var group = _groups.GetById(groupId);
|
||||
SubjectId = group?.SubjectId;
|
||||
GradeLevel = group?.GradeLevel ?? 0;
|
||||
|
||||
Reference in New Issue
Block a user