Mitarbeit bewerten begonnen, Schülerdaten, Gruppen
This commit is contained in:
@@ -40,8 +40,17 @@ public partial class StudentListViewModel : ObservableObject
|
||||
foreach (var s in f) Students.Add(new StudentListItem(s));
|
||||
}
|
||||
|
||||
[RelayCommand] private void AddStudent() { /* TODO */ }
|
||||
[RelayCommand] private void Refresh() => LoadStudents();
|
||||
public Func<Task>? OnAddStudent { get; set; }
|
||||
|
||||
[RelayCommand]
|
||||
private async Task AddStudent()
|
||||
{
|
||||
if (OnAddStudent is null) return;
|
||||
await OnAddStudent();
|
||||
LoadStudents();
|
||||
}
|
||||
|
||||
[RelayCommand] private void Refresh() => LoadStudents();
|
||||
}
|
||||
|
||||
public class StudentListItem
|
||||
@@ -68,9 +77,14 @@ public partial class StudentDetailViewModel : ObservableObject
|
||||
[ObservableProperty] private bool _isEditing;
|
||||
[ObservableProperty] private string _editFirstName = "";
|
||||
[ObservableProperty] private string _editLastName = "";
|
||||
[ObservableProperty] private ContactItem? _selectedContact;
|
||||
|
||||
public ObservableCollection<EnrollmentEntry> Enrollments { get; } = [];
|
||||
public ObservableCollection<DocEntry> Documentation { get; } = [];
|
||||
public ObservableCollection<EnrollmentEntry> Enrollments { get; } = [];
|
||||
public ObservableCollection<DocEntry> Documentation { get; } = [];
|
||||
public ObservableCollection<ContactItem> Contacts { get; } = [];
|
||||
public bool HasNoContacts => Contacts.Count == 0;
|
||||
public Func<Contact?, Task<Contact?>>? OnEditContact { get; set; }
|
||||
public Action<ContactItem>? OnViewAddress { get; set; }
|
||||
|
||||
public StudentDetailViewModel(IStudentRepository students,
|
||||
IEnrollmentRepository enrollments, IGroupRepository groups,
|
||||
@@ -96,6 +110,8 @@ public partial class StudentDetailViewModel : ObservableObject
|
||||
Enrollments.Add(new() { SchoolYear = e.SchoolYear, GroupName = g.Name, Subject = g.Subject ?? "" });
|
||||
}
|
||||
|
||||
LoadContacts();
|
||||
|
||||
Documentation.Clear();
|
||||
foreach (var d in _docs.GetByStudent(Student.Id))
|
||||
Documentation.Add(new() { Date = d.Date.ToString("dd.MM.yyyy"), Title = d.Title,
|
||||
@@ -125,7 +141,309 @@ public partial class StudentDetailViewModel : ObservableObject
|
||||
StudentTitle = Student.FullName;
|
||||
IsEditing = false;
|
||||
}
|
||||
|
||||
private void LoadContacts(Guid? selectedId = null)
|
||||
{
|
||||
Contacts.Clear();
|
||||
if (Student is null) return;
|
||||
foreach (var c in Student.Contacts
|
||||
.OrderBy(c => c.InvalidSince.HasValue)
|
||||
.ThenBy(c => c.Name))
|
||||
Contacts.Add(new ContactItem(c));
|
||||
OnPropertyChanged(nameof(HasNoContacts));
|
||||
SelectedContact = selectedId.HasValue
|
||||
? Contacts.FirstOrDefault(c => c.Id == selectedId.Value)
|
||||
: null;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task AddContact()
|
||||
{
|
||||
if (Student is null || OnEditContact is null) return;
|
||||
var contact = await OnEditContact(null);
|
||||
if (contact is null) return;
|
||||
Student.Contacts.Add(contact);
|
||||
_students.Save(Student);
|
||||
LoadContacts(contact.Id);
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(HasSelectedContact))]
|
||||
private async Task EditContact()
|
||||
{
|
||||
if (Student is null || SelectedContact is null || OnEditContact is null) return;
|
||||
var edited = await OnEditContact(SelectedContact.Model);
|
||||
if (edited is null) return;
|
||||
var index = Student.Contacts.FindIndex(c => c.Id == edited.Id);
|
||||
if (index >= 0) Student.Contacts[index] = edited;
|
||||
_students.Save(Student);
|
||||
LoadContacts(edited.Id);
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanViewSelectedAddress))]
|
||||
private void ViewSelectedAddress()
|
||||
{
|
||||
if (SelectedContact is not null) OnViewAddress?.Invoke(SelectedContact);
|
||||
}
|
||||
|
||||
partial void OnSelectedContactChanged(ContactItem? value)
|
||||
{
|
||||
EditContactCommand.NotifyCanExecuteChanged();
|
||||
ViewSelectedAddressCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
private bool HasSelectedContact() => SelectedContact is not null;
|
||||
private bool CanViewSelectedAddress() => SelectedContact?.HasAddress == true;
|
||||
}
|
||||
|
||||
public class EnrollmentEntry { public string SchoolYear { get; set; } = ""; public string GroupName { get; set; } = ""; public string Subject { get; set; } = ""; }
|
||||
public class DocEntry { public string Date { get; set; } = ""; public string Title { get; set; } = ""; public string TypeLabel { get; set; } = ""; public bool IsConfidential { get; set; } }
|
||||
|
||||
public class ContactItem
|
||||
{
|
||||
public Contact Model { get; }
|
||||
public Guid Id => Model.Id;
|
||||
public string Name { get; }
|
||||
public string Relation { get; }
|
||||
public string? Phone { get; }
|
||||
public string? Email { get; }
|
||||
public string Address { get; }
|
||||
public bool HasPhone => !string.IsNullOrEmpty(Phone);
|
||||
public bool HasEmail => !string.IsNullOrEmpty(Email);
|
||||
public bool HasAddress => !string.IsNullOrEmpty(Address);
|
||||
public bool IsInvalid => Model.InvalidSince.HasValue;
|
||||
public bool IsValid => !IsInvalid;
|
||||
public string PhoneDisplay => Phone ?? "";
|
||||
public string EmailDisplay => Email ?? "";
|
||||
public string StatusText => IsInvalid
|
||||
? $"Ungültig seit {Model.InvalidSince:dd.MM.yyyy} · {InvalidReasonText(Model.InvalidReason)}"
|
||||
: "Aktuell";
|
||||
|
||||
public IRelayCommand CallCommand { get; }
|
||||
public IRelayCommand MailCommand { get; }
|
||||
|
||||
public ContactItem(Contact c)
|
||||
{
|
||||
Model = c;
|
||||
Name = c.Name;
|
||||
Relation = c.Relation;
|
||||
Phone = c.Phone;
|
||||
Email = c.Email;
|
||||
Address = FormatAddress(c);
|
||||
CallCommand = new RelayCommand(() => OpenUri($"tel:{Phone}"), () => HasPhone);
|
||||
MailCommand = new RelayCommand(() => OpenUri($"mailto:{Email}"), () => HasEmail);
|
||||
}
|
||||
|
||||
public static string InvalidReasonText(ContactInvalidReason? reason) => reason switch
|
||||
{
|
||||
ContactInvalidReason.Moved => "Umzug",
|
||||
ContactInvalidReason.NewPhoneNumber => "Telefonnummer neu",
|
||||
ContactInvalidReason.LostCustody => "Sorgerecht verloren",
|
||||
ContactInvalidReason.NewEmailAddress => "E-Mail-Adresse neu",
|
||||
ContactInvalidReason.NoLongerResponsible => "Nicht mehr zuständig",
|
||||
ContactInvalidReason.Other => "Sonstiges",
|
||||
_ => "Kein Grund angegeben",
|
||||
};
|
||||
|
||||
private static string FormatAddress(Contact c)
|
||||
{
|
||||
var cityLine = string.Join(" ", new[] { c.PostalCode, c.City }
|
||||
.Where(value => !string.IsNullOrWhiteSpace(value)));
|
||||
return string.Join(Environment.NewLine, new[] { c.Street, cityLine }
|
||||
.Where(value => !string.IsNullOrWhiteSpace(value)));
|
||||
}
|
||||
|
||||
private static void OpenUri(string uri) =>
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(uri) { UseShellExecute = true });
|
||||
}
|
||||
|
||||
// ── Dialog: Schüler anlegen ───────────────────────────────────────────────────
|
||||
|
||||
public partial class AddStudentDialogViewModel : ObservableObject
|
||||
{
|
||||
private readonly IStudentRepository _students;
|
||||
|
||||
[ObservableProperty] private string _firstName = "";
|
||||
[ObservableProperty] private string _lastName = "";
|
||||
[ObservableProperty] private string _dateOfBirthText = "";
|
||||
[ObservableProperty] private string _selectedGender = "";
|
||||
[ObservableProperty] private string _notes = "";
|
||||
[ObservableProperty] private string _validationMessage = "";
|
||||
|
||||
public List<string> GenderOptions { get; } = ["", "M – männlich", "W – weiblich", "D – divers"];
|
||||
public List<string> RelationPresets { get; } = ["Schüler/in", "Mutter", "Vater", "Elternteil", "Erziehungsberechtigte/r", "Sonstige"];
|
||||
|
||||
public ObservableCollection<ContactEntryViewModel> Contacts { get; } = [];
|
||||
public Student? Result { get; private set; }
|
||||
|
||||
public AddStudentDialogViewModel(IStudentRepository students) => _students = students;
|
||||
|
||||
[RelayCommand]
|
||||
private void AddContact() =>
|
||||
Contacts.Add(new ContactEntryViewModel(this) { Relation = "Elternteil" });
|
||||
|
||||
public void RemoveContact(ContactEntryViewModel c) => Contacts.Remove(c);
|
||||
|
||||
[RelayCommand]
|
||||
private void Save()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(LastName)) { ValidationMessage = "Nachname erforderlich."; return; }
|
||||
if (string.IsNullOrWhiteSpace(FirstName)) { ValidationMessage = "Vorname erforderlich."; return; }
|
||||
|
||||
DateOnly? dob = null;
|
||||
if (!string.IsNullOrWhiteSpace(DateOfBirthText))
|
||||
{
|
||||
if (!DateOnly.TryParseExact(DateOfBirthText, "dd.MM.yyyy", null,
|
||||
System.Globalization.DateTimeStyles.None, out var d))
|
||||
{
|
||||
ValidationMessage = "Geburtsdatum im Format TT.MM.JJJJ."; return;
|
||||
}
|
||||
dob = d;
|
||||
}
|
||||
|
||||
Result = new Student
|
||||
{
|
||||
FirstName = FirstName.Trim(),
|
||||
LastName = LastName.Trim(),
|
||||
DateOfBirth = dob,
|
||||
Gender = SelectedGender switch
|
||||
{
|
||||
"M – männlich" => Gender.M,
|
||||
"W – weiblich" => Gender.W,
|
||||
"D – divers" => Gender.D,
|
||||
_ => (Gender?)null,
|
||||
},
|
||||
Notes = string.IsNullOrWhiteSpace(Notes) ? null : Notes.Trim(),
|
||||
Contacts = Contacts.Select(c => c.ToModel()).Where(c => !string.IsNullOrWhiteSpace(c.Name)).ToList(),
|
||||
};
|
||||
_students.Save(Result);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class ContactEntryViewModel : ObservableObject
|
||||
{
|
||||
private readonly AddStudentDialogViewModel _parent;
|
||||
|
||||
[ObservableProperty] private string _name = "";
|
||||
[ObservableProperty] private string _relation = "";
|
||||
[ObservableProperty] private string _phone = "";
|
||||
[ObservableProperty] private string _email = "";
|
||||
[ObservableProperty] private string _street = "";
|
||||
[ObservableProperty] private string _postalCode = "";
|
||||
[ObservableProperty] private string _city = "";
|
||||
|
||||
public ContactEntryViewModel(AddStudentDialogViewModel parent) => _parent = parent;
|
||||
|
||||
[RelayCommand] private void Remove() => _parent.RemoveContact(this);
|
||||
|
||||
public Contact ToModel() => new()
|
||||
{
|
||||
Name = Name.Trim(),
|
||||
Relation = Relation.Trim(),
|
||||
Phone = string.IsNullOrWhiteSpace(Phone) ? null : Phone.Trim(),
|
||||
Email = string.IsNullOrWhiteSpace(Email) ? null : Email.Trim(),
|
||||
Street = string.IsNullOrWhiteSpace(Street) ? null : Street.Trim(),
|
||||
PostalCode = string.IsNullOrWhiteSpace(PostalCode) ? null : PostalCode.Trim(),
|
||||
City = string.IsNullOrWhiteSpace(City) ? null : City.Trim(),
|
||||
};
|
||||
}
|
||||
|
||||
public partial class ContactEditDialogViewModel : ObservableObject
|
||||
{
|
||||
private readonly Contact? _source;
|
||||
|
||||
[ObservableProperty] private string _name = "";
|
||||
[ObservableProperty] private string _relation = "Elternteil";
|
||||
[ObservableProperty] private string _phone = "";
|
||||
[ObservableProperty] private string _email = "";
|
||||
[ObservableProperty] private string _street = "";
|
||||
[ObservableProperty] private string _postalCode = "";
|
||||
[ObservableProperty] private string _city = "";
|
||||
[ObservableProperty] private bool _isInvalid;
|
||||
[ObservableProperty] private string _invalidSinceText = "";
|
||||
[ObservableProperty] private string _selectedInvalidReason = "";
|
||||
[ObservableProperty] private string _invalidReasonDetails = "";
|
||||
[ObservableProperty] private string _validationMessage = "";
|
||||
|
||||
public string DialogTitle => _source is null ? "Kontakt anlegen" : "Kontakt bearbeiten";
|
||||
public List<string> RelationPresets { get; } =
|
||||
["Schüler/in", "Mutter", "Vater", "Elternteil", "Erziehungsberechtigte/r", "Sonstige"];
|
||||
public List<string> InvalidReasonOptions { get; } =
|
||||
["Umzug", "Telefonnummer neu", "Sorgerecht verloren", "E-Mail-Adresse neu",
|
||||
"Nicht mehr zuständig", "Sonstiges"];
|
||||
public Contact? Result { get; private set; }
|
||||
|
||||
public ContactEditDialogViewModel(Contact? source)
|
||||
{
|
||||
_source = source;
|
||||
if (source is null) return;
|
||||
Name = source.Name;
|
||||
Relation = source.Relation;
|
||||
Phone = source.Phone ?? "";
|
||||
Email = source.Email ?? "";
|
||||
Street = source.Street ?? "";
|
||||
PostalCode = source.PostalCode ?? "";
|
||||
City = source.City ?? "";
|
||||
IsInvalid = source.InvalidSince.HasValue;
|
||||
InvalidSinceText = source.InvalidSince?.ToString("dd.MM.yyyy") ?? "";
|
||||
SelectedInvalidReason = ContactItem.InvalidReasonText(source.InvalidReason);
|
||||
InvalidReasonDetails = source.InvalidReasonDetails ?? "";
|
||||
}
|
||||
|
||||
partial void OnIsInvalidChanged(bool value)
|
||||
{
|
||||
if (value && string.IsNullOrWhiteSpace(InvalidSinceText))
|
||||
InvalidSinceText = DateTime.Today.ToString("dd.MM.yyyy");
|
||||
if (value && string.IsNullOrWhiteSpace(SelectedInvalidReason))
|
||||
SelectedInvalidReason = InvalidReasonOptions[0];
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Save()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Name))
|
||||
{
|
||||
ValidationMessage = "Name erforderlich.";
|
||||
return;
|
||||
}
|
||||
|
||||
DateOnly? invalidSince = null;
|
||||
if (IsInvalid)
|
||||
{
|
||||
if (!DateOnly.TryParseExact(InvalidSinceText, "dd.MM.yyyy", null,
|
||||
System.Globalization.DateTimeStyles.None, out var parsedDate))
|
||||
{
|
||||
ValidationMessage = "Ungültig seit im Format TT.MM.JJJJ angeben.";
|
||||
return;
|
||||
}
|
||||
invalidSince = parsedDate;
|
||||
}
|
||||
|
||||
Result = new Contact
|
||||
{
|
||||
Id = _source?.Id ?? Guid.NewGuid(),
|
||||
Name = Name.Trim(),
|
||||
Relation = Relation.Trim(),
|
||||
Phone = NullIfEmpty(Phone),
|
||||
Email = NullIfEmpty(Email),
|
||||
Street = NullIfEmpty(Street),
|
||||
PostalCode = NullIfEmpty(PostalCode),
|
||||
City = NullIfEmpty(City),
|
||||
InvalidSince = invalidSince,
|
||||
InvalidReason = IsInvalid ? ParseInvalidReason(SelectedInvalidReason) : null,
|
||||
InvalidReasonDetails = IsInvalid ? NullIfEmpty(InvalidReasonDetails) : null,
|
||||
};
|
||||
}
|
||||
|
||||
private static string? NullIfEmpty(string value) =>
|
||||
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
||||
|
||||
private static ContactInvalidReason ParseInvalidReason(string value) => value switch
|
||||
{
|
||||
"Umzug" => ContactInvalidReason.Moved,
|
||||
"Telefonnummer neu" => ContactInvalidReason.NewPhoneNumber,
|
||||
"Sorgerecht verloren" => ContactInvalidReason.LostCustody,
|
||||
"E-Mail-Adresse neu" => ContactInvalidReason.NewEmailAddress,
|
||||
"Nicht mehr zuständig" => ContactInvalidReason.NoLongerResponsible,
|
||||
_ => ContactInvalidReason.Other,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user