- Dashboard: Fehlzeiten-Warnung lädt Mitarbeitssitzungen einmal vorab statt pro Schüler/Eintrag einzeln nachzuschlagen (N+1 vermieden); neues IParticipationSessionRepository.GetAll() dafür. - CI: .gitea/workflows/ci.yml baut und testet bei jedem Push/PR. Dabei fehlende Release|Any CPU-Konfiguration für 6 Projekte in der .sln behoben (LehrerApp.Data.Tests wurde bei Release-Builds der Solution bislang stillschweigend übersprungen). TreatWarningsAsErrors jetzt aktiv. - SettingsViewModel (1986 Zeilen) als partial class auf 20 Themen- dateien aufgeteilt, Verhalten unverändert. - Backup: optionaler zweiter Sicherungsordner (USB-Stick/Netzlaufwerk, best-effort) und Integritätsprüfung nach jedem Backup (DatabaseEncryptionService.CanOpenAndRead). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
147 lines
6.1 KiB
C#
147 lines
6.1 KiB
C#
using CommunityToolkit.Mvvm.ComponentModel;
|
||
using CommunityToolkit.Mvvm.Input;
|
||
using LehrerApp.Core.Interfaces;
|
||
using LehrerApp.Core.Models;
|
||
using LehrerApp.Core.Services;
|
||
using LehrerApp.Data;
|
||
using LehrerApp.Desktop.Services;
|
||
using LehrerApp.Desktop.ViewModels.Planning;
|
||
using LehrerApp.Sync;
|
||
using LehrerApp.Sync.Crypto;
|
||
using System.Collections.ObjectModel;
|
||
using System.Globalization;
|
||
using System.Security.Cryptography;
|
||
using System.Text.Json;
|
||
using System.Text.Json.Serialization;
|
||
|
||
namespace LehrerApp.Desktop.ViewModels.Settings;
|
||
|
||
public partial class SettingsViewModel
|
||
{
|
||
// ── Ferien & Feiertage (4.3.5, aus dem Stundenplan hierher verschoben) ───
|
||
|
||
[ObservableProperty] private string _selectedStateName = "";
|
||
[ObservableProperty] private string _newHolidayName = "";
|
||
[ObservableProperty] private string _newHolidayStartText = "";
|
||
[ObservableProperty] private string _newHolidayEndText = "";
|
||
[ObservableProperty] private string _holidayNameError = "";
|
||
[ObservableProperty] private string _holidayDateError = "";
|
||
[ObservableProperty] private string _schoolName = "";
|
||
[ObservableProperty] private string _schoolStreet = "";
|
||
[ObservableProperty] private string _schoolPostalCode = "";
|
||
[ObservableProperty] private string _schoolCity = "";
|
||
[ObservableProperty] private string _schoolNameError = "";
|
||
[ObservableProperty] private string _schoolStreetError = "";
|
||
[ObservableProperty] private string _schoolPostalCodeError = "";
|
||
[ObservableProperty] private string _schoolCityError = "";
|
||
[ObservableProperty] private string _schoolLocationStatus = "";
|
||
[ObservableProperty] private string _resolvedSchoolAddress = "";
|
||
|
||
public List<string> StateOptions { get; } = GermanStateDisplay.Options.ToList();
|
||
public ObservableCollection<SchoolHolidayItem> SchoolHolidayEntries { get; } = [];
|
||
|
||
// ── Ferien & Feiertage: Bundesland / Schulferien pflegen ─────────────────
|
||
|
||
partial void OnSelectedStateNameChanged(string value) =>
|
||
_calendarSettings.SetState(GermanStateDisplay.FromLabel(value));
|
||
|
||
[RelayCommand]
|
||
private async Task LoadSchoolLocation()
|
||
{
|
||
if (_schoolWeather is null) return;
|
||
try
|
||
{
|
||
var location = await _schoolWeather.GetLocationAsync();
|
||
if (location is null) return;
|
||
SchoolName = location.SchoolName;
|
||
SchoolStreet = location.Street;
|
||
SchoolPostalCode = location.PostalCode;
|
||
SchoolCity = location.City;
|
||
SelectedStateName = GermanStateDisplay.Label(location.State);
|
||
ResolvedSchoolAddress = location.ResolvedAddress;
|
||
}
|
||
catch (SchoolWeatherException ex) { SchoolLocationStatus = ex.Message; }
|
||
}
|
||
|
||
[RelayCommand]
|
||
private async Task SaveSchoolLocation()
|
||
{
|
||
SchoolNameError = ""; SchoolStreetError = ""; SchoolPostalCodeError = "";
|
||
SchoolCityError = ""; SchoolLocationStatus = "";
|
||
var valid = true;
|
||
if (string.IsNullOrWhiteSpace(SchoolName))
|
||
{ SchoolNameError = "Name der Schule erforderlich."; valid = false; }
|
||
if (string.IsNullOrWhiteSpace(SchoolStreet))
|
||
{ SchoolStreetError = "Straße und Hausnummer erforderlich."; valid = false; }
|
||
if (!System.Text.RegularExpressions.Regex.IsMatch(SchoolPostalCode.Trim(), @"^\d{5}$"))
|
||
{ SchoolPostalCodeError = "Bitte eine fünfstellige PLZ angeben."; valid = false; }
|
||
if (string.IsNullOrWhiteSpace(SchoolCity))
|
||
{ SchoolCityError = "Ort erforderlich."; valid = false; }
|
||
if (!valid) return;
|
||
if (_schoolWeather is null)
|
||
{
|
||
SchoolLocationStatus = "Der Wetterdienst ist nicht verfügbar.";
|
||
return;
|
||
}
|
||
|
||
SchoolLocationStatus = "Adresse wird geprüft …";
|
||
try
|
||
{
|
||
var profile = await _schoolWeather.SaveLocationAsync(new SchoolLocationRequest
|
||
{
|
||
SchoolName = SchoolName.Trim(), Street = SchoolStreet.Trim(),
|
||
PostalCode = SchoolPostalCode.Trim(), City = SchoolCity.Trim(),
|
||
State = GermanStateDisplay.FromLabel(SelectedStateName),
|
||
});
|
||
ResolvedSchoolAddress = profile.ResolvedAddress;
|
||
SchoolLocationStatus = "Schulstandort gespeichert. Wetterdaten werden serverseitig abgerufen.";
|
||
}
|
||
catch (SchoolWeatherException ex) { SchoolLocationStatus = ex.Message; }
|
||
}
|
||
|
||
private void LoadSchoolHolidays()
|
||
{
|
||
SchoolHolidayEntries.Clear();
|
||
foreach (var h in _schoolHolidays.GetAll().OrderBy(h => h.StartDate))
|
||
SchoolHolidayEntries.Add(new SchoolHolidayItem(h));
|
||
}
|
||
|
||
[RelayCommand]
|
||
private void AddSchoolHoliday()
|
||
{
|
||
HolidayNameError = ""; HolidayDateError = "";
|
||
var valid = true;
|
||
|
||
if (string.IsNullOrWhiteSpace(NewHolidayName)) { HolidayNameError = "Name erforderlich."; valid = false; }
|
||
|
||
var hasStart = DateOnly.TryParseExact(NewHolidayStartText, "dd.MM.yyyy", CultureInfo.InvariantCulture,
|
||
System.Globalization.DateTimeStyles.None, out var start);
|
||
var hasEnd = DateOnly.TryParseExact(NewHolidayEndText, "dd.MM.yyyy", CultureInfo.InvariantCulture,
|
||
System.Globalization.DateTimeStyles.None, out var end);
|
||
|
||
if (!hasStart || !hasEnd) { HolidayDateError = "Bitte Beginn und Ende im Format TT.MM.JJJJ angeben."; valid = false; }
|
||
else if (end < start) { HolidayDateError = "Das Ende darf nicht vor dem Beginn liegen."; valid = false; }
|
||
|
||
if (!valid) return;
|
||
|
||
_schoolHolidays.Save(new SchoolHoliday { Name = NewHolidayName.Trim(), StartDate = start, EndDate = end });
|
||
NewHolidayName = ""; NewHolidayStartText = ""; NewHolidayEndText = "";
|
||
LoadSchoolHolidays();
|
||
}
|
||
|
||
[RelayCommand]
|
||
private void RemoveSchoolHoliday(SchoolHolidayItem? item)
|
||
{
|
||
if (item is null) return;
|
||
_schoolHolidays.Delete(item.Id);
|
||
SchoolHolidayEntries.Remove(item);
|
||
}
|
||
}
|
||
|
||
public class SchoolHolidayItem(SchoolHoliday h)
|
||
{
|
||
public Guid Id { get; } = h.Id;
|
||
public string Name { get; } = h.Name;
|
||
public string RangeDisplay { get; } = $"{h.StartDate:dd.MM.yyyy} – {h.EndDate:dd.MM.yyyy}";
|
||
}
|