Neue Funktion Schüler austragen statt aus Kurs löschen.
This commit is contained in:
@@ -169,6 +169,7 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
[ObservableProperty] private int _activeTabIndex = 0;
|
||||
[ObservableProperty] private StudentSummary? _selectedStudent;
|
||||
[ObservableProperty] private ExamSummary? _selectedExam;
|
||||
[ObservableProperty] private bool _showFormerStudents;
|
||||
|
||||
// Eigene Property statt "Group.IsDifferentiated" im Binding-Pfad: die View bindet bereits,
|
||||
// bevor LoadGroup() läuft (siehe MainWindowViewModel.NavigateToGroupDetail), Group ist dann
|
||||
@@ -177,6 +178,7 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
public string SubjectName { get; private set; } = "";
|
||||
|
||||
partial void OnGroupChanged(LearningGroup? value) => OnPropertyChanged(nameof(IsDifferentiated));
|
||||
partial void OnShowFormerStudentsChanged(bool value) => LoadStudents();
|
||||
|
||||
public ObservableCollection<StudentSummary> Students { get; } = [];
|
||||
public ObservableCollection<ExamSummary> Exams { get; } = [];
|
||||
@@ -185,6 +187,7 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
public GradeOverviewTabViewModel GradeOverviewTab { get; }
|
||||
public PlanningTabViewModel PlanningTab { get; }
|
||||
public Func<Task<bool>>? OnAddStudent { get; set; }
|
||||
public Func<StudentSummary, Task<bool>>? OnWithdrawStudent { get; set; }
|
||||
public Func<Guid, Task<bool>>? OnAddExam { get; set; }
|
||||
public Func<Exam, Task<bool>>? OnEditExam { get; set; }
|
||||
public Func<Exam, Task<bool>>? OnDuplicateExam { get; set; }
|
||||
@@ -219,7 +222,7 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
LoadStudents();
|
||||
ReloadExams();
|
||||
ParticipationTab.Initialize(Group.Id, Group.SchoolYear);
|
||||
GradeOverviewTab.Initialize(Group.Id, Group.GradingSystem, Group.Type, GroupTitle);
|
||||
GradeOverviewTab.Initialize(Group.Id, Group.GradingSystem, Group.Type, GroupTitle, Group.SchoolYear);
|
||||
PlanningTab.Initialize(Group.Id);
|
||||
}
|
||||
|
||||
@@ -238,11 +241,14 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
Students.Clear();
|
||||
var enrolled = _students.GetByGroup(Group.Id);
|
||||
var memberships = _memberships.GetByGroup(Group.Id).ToDictionary(e => e.StudentId);
|
||||
StudentCount = enrolled.Count;
|
||||
foreach (var s in enrolled)
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
StudentCount = memberships.Values.Count(m => GroupMembershipService.IsActiveOn(m, today));
|
||||
foreach (var s in enrolled.OrderBy(s => s.LastName).ThenBy(s => s.FirstName))
|
||||
{
|
||||
memberships.TryGetValue(s.Id, out var membership);
|
||||
var summary = new StudentSummary(s, membership) { OnChanged = SaveStudentNiveau };
|
||||
if (membership is null) continue;
|
||||
var summary = new StudentSummary(s, membership, today) { OnChanged = SaveStudentNiveau };
|
||||
if (summary.IsFormer && !ShowFormerStudents) continue;
|
||||
Students.Add(summary);
|
||||
}
|
||||
}
|
||||
@@ -269,21 +275,36 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(HasSelectedStudent))]
|
||||
private void RemoveStudent()
|
||||
private async Task WithdrawStudent()
|
||||
{
|
||||
if (Group is null || SelectedStudent is null) return;
|
||||
var membership = _memberships.GetByStudentAndGroup(SelectedStudent.Id, Group.Id);
|
||||
if (membership is null) return;
|
||||
_memberships.Delete(membership.Id);
|
||||
if (SelectedStudent is null || OnWithdrawStudent is null) return;
|
||||
if (!await OnWithdrawStudent(SelectedStudent)) return;
|
||||
LoadStudents();
|
||||
SelectedStudent = null;
|
||||
ParticipationTab.RefreshCurrentGrid();
|
||||
}
|
||||
|
||||
partial void OnSelectedStudentChanged(StudentSummary? value) =>
|
||||
RemoveStudentCommand.NotifyCanExecuteChanged();
|
||||
[RelayCommand(CanExecute = nameof(CanReinstateSelectedStudent))]
|
||||
private void ReinstateStudent()
|
||||
{
|
||||
if (Group is null || SelectedStudent is null) return;
|
||||
var membership = _memberships.GetByStudentAndGroup(SelectedStudent.Id, Group.Id);
|
||||
if (membership is null) return;
|
||||
membership.LeftAt = null;
|
||||
_memberships.Save(membership);
|
||||
LoadStudents();
|
||||
SelectedStudent = null;
|
||||
ParticipationTab.RefreshCurrentGrid();
|
||||
}
|
||||
|
||||
partial void OnSelectedStudentChanged(StudentSummary? value)
|
||||
{
|
||||
WithdrawStudentCommand.NotifyCanExecuteChanged();
|
||||
ReinstateStudentCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
private bool HasSelectedStudent() => SelectedStudent is not null;
|
||||
private bool CanReinstateSelectedStudent() => SelectedStudent?.HasExitDate == true;
|
||||
|
||||
[RelayCommand]
|
||||
private async Task AddExam()
|
||||
@@ -423,6 +444,10 @@ public partial class StudentSummary : ObservableObject
|
||||
public Guid Id { get; }
|
||||
public string FullName { get; }
|
||||
public string PeriodLabel { get; }
|
||||
public bool IsFormer { get; }
|
||||
public bool HasExitDate { get; }
|
||||
public string MembershipStatus { get; }
|
||||
public string WithdrawActionLabel => HasExitDate ? "Austrittsdatum ändern" : "Austragen";
|
||||
|
||||
[ObservableProperty] private Niveau? _niveau;
|
||||
|
||||
@@ -434,17 +459,17 @@ public partial class StudentSummary : ObservableObject
|
||||
|
||||
public Action<StudentSummary>? OnChanged { get; set; }
|
||||
|
||||
public StudentSummary(Core.Models.Student s, GroupMembership? membership)
|
||||
public StudentSummary(Core.Models.Student s, GroupMembership? membership, DateOnly? today = null)
|
||||
{
|
||||
Id = s.Id;
|
||||
FullName = s.FullName;
|
||||
PeriodLabel = membership?.Period switch
|
||||
{
|
||||
MembershipPeriod.H1Only => "H1",
|
||||
MembershipPeriod.H2Only => "H2",
|
||||
MembershipPeriod.Custom => BuildCustomLabel(membership),
|
||||
_ => "",
|
||||
};
|
||||
PeriodLabel = membership is null ? "" : BuildPeriodLabel(membership);
|
||||
HasExitDate = membership?.LeftAt.HasValue == true;
|
||||
IsFormer = membership?.LeftAt is { } leftAt
|
||||
&& leftAt < (today ?? DateOnly.FromDateTime(DateTime.Today));
|
||||
MembershipStatus = membership?.LeftAt is { } exitDate
|
||||
? IsFormer ? $"Ausgetreten am {exitDate:dd.MM.yyyy}" : $"Austritt am {exitDate:dd.MM.yyyy}"
|
||||
: "Aktiv";
|
||||
_niveau = membership?.Niveau;
|
||||
}
|
||||
|
||||
@@ -454,12 +479,20 @@ public partial class StudentSummary : ObservableObject
|
||||
OnChanged?.Invoke(this);
|
||||
}
|
||||
|
||||
private static string BuildCustomLabel(GroupMembership membership)
|
||||
private static string BuildPeriodLabel(GroupMembership membership)
|
||||
{
|
||||
if (membership.JoinedAt.HasValue && membership.LeftAt.HasValue) return $"{membership.JoinedAt:dd.MM.}–{membership.LeftAt:dd.MM.}";
|
||||
if (membership.JoinedAt.HasValue) return $"ab {membership.JoinedAt:dd.MM.}";
|
||||
if (membership.LeftAt.HasValue) return $"bis {membership.LeftAt:dd.MM.}";
|
||||
return "Datum";
|
||||
var period = membership.Period switch
|
||||
{
|
||||
MembershipPeriod.H1Only => "H1",
|
||||
MembershipPeriod.H2Only => "H2",
|
||||
MembershipPeriod.Custom => "Datum",
|
||||
_ => "Ganzes Jahr",
|
||||
};
|
||||
if (membership.JoinedAt.HasValue && membership.LeftAt.HasValue)
|
||||
return $"{period} · {membership.JoinedAt:dd.MM.}–{membership.LeftAt:dd.MM.}";
|
||||
if (membership.JoinedAt.HasValue) return $"{period} · ab {membership.JoinedAt:dd.MM.}";
|
||||
if (membership.LeftAt.HasValue) return $"{period} · bis {membership.LeftAt:dd.MM.}";
|
||||
return period;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,6 +624,63 @@ public class StudentPickerItem
|
||||
public StudentPickerItem(Student s) { Id = s.Id; FullName = s.FullName; }
|
||||
}
|
||||
|
||||
// ── Dialog: Schüler aus einer Lerngruppe austragen ───────────────────────────
|
||||
|
||||
public partial class WithdrawStudentDialogViewModel : ObservableObject
|
||||
{
|
||||
private readonly IGroupMembershipRepository _memberships;
|
||||
private readonly GroupMembership _membership;
|
||||
|
||||
[ObservableProperty] private DateTimeOffset? _selectedDate;
|
||||
[ObservableProperty] private string _dateError = "";
|
||||
|
||||
public string StudentName { get; }
|
||||
public string GroupName { get; }
|
||||
public DateTimeOffset? EarliestDate { get; }
|
||||
public bool WasSaved { get; private set; }
|
||||
|
||||
public WithdrawStudentDialogViewModel(IGroupMembershipRepository memberships,
|
||||
GroupMembership membership, string studentName, string groupName, DateOnly? today = null)
|
||||
{
|
||||
_memberships = memberships;
|
||||
_membership = membership;
|
||||
StudentName = studentName;
|
||||
GroupName = groupName;
|
||||
|
||||
var initialDate = membership.LeftAt ?? today ?? DateOnly.FromDateTime(DateTime.Today);
|
||||
_selectedDate = ToDateTimeOffset(initialDate);
|
||||
EarliestDate = membership.JoinedAt is { } joinedAt ? ToDateTimeOffset(joinedAt) : null;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Save()
|
||||
{
|
||||
DateError = "";
|
||||
if (SelectedDate is null)
|
||||
{
|
||||
DateError = "Bitte ein Austrittsdatum auswählen.";
|
||||
return;
|
||||
}
|
||||
|
||||
var exitDate = DateOnly.FromDateTime(SelectedDate.Value.LocalDateTime);
|
||||
if (_membership.JoinedAt is { } joinedAt && exitDate < joinedAt)
|
||||
{
|
||||
DateError = $"Das Austrittsdatum darf nicht vor dem Eintritt am {joinedAt:dd.MM.yyyy} liegen.";
|
||||
return;
|
||||
}
|
||||
|
||||
_membership.LeftAt = exitDate;
|
||||
_memberships.Save(_membership);
|
||||
WasSaved = true;
|
||||
}
|
||||
|
||||
private static DateTimeOffset ToDateTimeOffset(DateOnly date)
|
||||
{
|
||||
var localDateTime = date.ToDateTime(TimeOnly.MinValue);
|
||||
return new DateTimeOffset(localDateTime, TimeZoneInfo.Local.GetUtcOffset(localDateTime));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Dialog: Neue Lerngruppe anlegen ──────────────────────────────────────────
|
||||
|
||||
public partial class AddGroupDialogViewModel : ObservableObject
|
||||
|
||||
Reference in New Issue
Block a user