Wetterdienst
This commit is contained in:
@@ -36,6 +36,7 @@ public partial class DashboardViewModel : ObservableObject
|
||||
private readonly SchoolCalendarSettingsService _calendarSettings;
|
||||
private readonly ISubstitutionEntryRepository _substitutions;
|
||||
private readonly IAnnualPlanEventRepository? _annualPlanEvents;
|
||||
private readonly SchoolWeatherService? _schoolWeather;
|
||||
|
||||
private const int OpenExcuseMaxAgeDays = 21;
|
||||
private const int SupportPlanDueWithinDays = 14;
|
||||
@@ -56,6 +57,11 @@ public partial class DashboardViewModel : ObservableObject
|
||||
[ObservableProperty] private DateOnly _calendarMonth = FirstOfMonth(DateTime.Today);
|
||||
[ObservableProperty] private string _selectedDayLabel = "";
|
||||
[ObservableProperty] private bool _isDashboardSettingsOpen;
|
||||
[ObservableProperty] private bool _isWeatherPanelVisible;
|
||||
[ObservableProperty] private string _weatherSummary = "";
|
||||
[ObservableProperty] private string _weatherDetails = "";
|
||||
[ObservableProperty] private string _weatherStatus = "";
|
||||
[ObservableProperty] private bool _hasWeatherWarnings;
|
||||
|
||||
public string CalendarMonthLabel => CalendarMonth.ToString("MMMM yyyy", De);
|
||||
|
||||
@@ -72,6 +78,7 @@ public partial class DashboardViewModel : ObservableObject
|
||||
public ObservableCollection<DashboardAlertItem> Alerts { get; } = [];
|
||||
public ObservableCollection<CalendarEventItem> SelectedDayEvents { get; } = [];
|
||||
public ObservableCollection<DashboardCardOption> DashboardCards { get; } = [];
|
||||
public ObservableCollection<DashboardWeatherWarningItem> WeatherWarnings { get; } = [];
|
||||
public static string[] CalendarWeekdayHeaders { get; } = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
|
||||
|
||||
// Navigation-Callback – wird von App.axaml.cs verdrahtet
|
||||
@@ -111,7 +118,7 @@ public partial class DashboardViewModel : ObservableObject
|
||||
DashboardSettingsService dashboardSettings, ISchoolHolidayRepository schoolHolidays,
|
||||
PublicHolidayService publicHolidays, SchoolCalendarSettingsService calendarSettings,
|
||||
ISubstitutionEntryRepository substitutions, IAnnualPlanEventRepository? annualPlanEvents = null,
|
||||
AnnualPlanSyncService? annualPlanSync = null)
|
||||
AnnualPlanSyncService? annualPlanSync = null, SchoolWeatherService? schoolWeather = null)
|
||||
{
|
||||
_groups = groups; _subjects = subjects; _lessons = lessons; _exams = exams; _tasks = tasks;
|
||||
_examResults = examResults; _grades = grades; _reportGrades = reportGrades; _memberships = memberships;
|
||||
@@ -122,6 +129,7 @@ public partial class DashboardViewModel : ObservableObject
|
||||
_schoolHolidays = schoolHolidays; _publicHolidays = publicHolidays; _calendarSettings = calendarSettings;
|
||||
_substitutions = substitutions;
|
||||
_annualPlanEvents = annualPlanEvents;
|
||||
_schoolWeather = schoolWeather;
|
||||
if (annualPlanSync is not null)
|
||||
{
|
||||
annualPlanSync.DataChanged += () => Avalonia.Threading.Dispatcher.UIThread.Post(LoadCalendar);
|
||||
@@ -142,6 +150,7 @@ public partial class DashboardViewModel : ObservableObject
|
||||
CurrentDate = now.ToString("dddd, d. MMMM yyyy", De);
|
||||
CurrentSchoolYear = _sy.CurrentSchoolYear();
|
||||
Greeting = now.Hour < 12 ? "Guten Morgen" : now.Hour < 18 ? "Guten Tag" : "Guten Abend";
|
||||
_ = LoadWeatherAsync();
|
||||
|
||||
TodaysLessons.Clear();
|
||||
var groups = _groups.GetBySchoolYear(_sy.CurrentSchoolYear()).ToDictionary(g => g.Id);
|
||||
@@ -197,6 +206,57 @@ public partial class DashboardViewModel : ObservableObject
|
||||
LoadAlerts(groups, today);
|
||||
}
|
||||
|
||||
private async Task LoadWeatherAsync()
|
||||
{
|
||||
if (_schoolWeather?.IsAvailable != true) return;
|
||||
try
|
||||
{
|
||||
var snapshot = await _schoolWeather.GetWeatherAsync();
|
||||
if (snapshot is null) return;
|
||||
var current = snapshot.Forecast
|
||||
.Where(x => x.ValidAt.ToUniversalTime() >= DateTime.UtcNow.AddHours(-1))
|
||||
.MinBy(x => Math.Abs((x.ValidAt.ToUniversalTime() - DateTime.UtcNow).TotalMinutes));
|
||||
WeatherWarnings.Clear();
|
||||
foreach (var warning in snapshot.Warnings)
|
||||
WeatherWarnings.Add(new DashboardWeatherWarningItem(warning));
|
||||
HasWeatherWarnings = WeatherWarnings.Count > 0;
|
||||
|
||||
if (current is not null)
|
||||
{
|
||||
var temperature = current.TemperatureC is { } t ? $"{t:0.#} °C" : "Temperatur unbekannt";
|
||||
WeatherSummary = $"{temperature} · {WeatherDescription(current.WeatherCode)}";
|
||||
var details = new List<string>();
|
||||
if (current.WindSpeedKmh is { } wind) details.Add($"Wind {wind:0.#} km/h");
|
||||
if (current.WindGustKmh is { } gust) details.Add($"Böen {gust:0.#} km/h");
|
||||
if (current.PrecipitationMm is > 0) details.Add($"Niederschlag {current.PrecipitationMm:0.#} mm");
|
||||
details.Add($"DWD-Station {snapshot.StationName} ({snapshot.StationDistanceKm:0.#} km)");
|
||||
WeatherDetails = string.Join(" · ", details);
|
||||
}
|
||||
WeatherStatus = snapshot.IsStale
|
||||
? $"Letzter verfügbarer Stand vom {snapshot.RetrievedAt.ToLocalTime():dd.MM., HH:mm} Uhr"
|
||||
: "";
|
||||
IsWeatherPanelVisible = current is not null || HasWeatherWarnings;
|
||||
}
|
||||
catch (SchoolWeatherException ex)
|
||||
{
|
||||
WeatherStatus = ex.Message;
|
||||
IsWeatherPanelVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static string WeatherDescription(int? code) => code switch
|
||||
{
|
||||
0 => "klar",
|
||||
>= 1 and <= 3 => "bewölkt",
|
||||
45 or 48 => "Nebel",
|
||||
>= 51 and <= 67 => "Regen",
|
||||
>= 71 and <= 77 => "Schnee",
|
||||
>= 80 and <= 82 => "Regenschauer",
|
||||
85 or 86 => "Schneeschauer",
|
||||
>= 95 and <= 99 => "Gewitter",
|
||||
_ => "Wettervorhersage",
|
||||
};
|
||||
|
||||
// ── Fehlzeiten-Warnung (5.2.3) ────────────────────────────────────────────
|
||||
|
||||
private void LoadAttendanceWarnings(DateOnly today)
|
||||
@@ -761,6 +821,32 @@ public partial class DashboardViewModel : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DashboardWeatherWarningItem
|
||||
{
|
||||
public string Headline { get; }
|
||||
public string Description { get; }
|
||||
public string Instruction { get; }
|
||||
public string PeriodDisplay { get; }
|
||||
public string SeverityColor { get; }
|
||||
|
||||
public DashboardWeatherWarningItem(WeatherWarning warning)
|
||||
{
|
||||
Headline = string.IsNullOrWhiteSpace(warning.Headline) ? warning.Event : warning.Headline;
|
||||
Description = warning.Description;
|
||||
Instruction = warning.Instruction;
|
||||
PeriodDisplay = (warning.Onset, warning.Expires) switch
|
||||
{
|
||||
({ } onset, { } expires) => $"{onset.ToLocalTime():dd.MM., HH:mm}–{expires.ToLocalTime():HH:mm} Uhr",
|
||||
({ } onset, null) => $"ab {onset.ToLocalTime():dd.MM., HH:mm} Uhr",
|
||||
_ => "",
|
||||
};
|
||||
SeverityColor = warning.Severity switch
|
||||
{
|
||||
"Extreme" => "#7E0023", "Severe" => "#D32F2F", "Moderate" => "#F59E0B", _ => "#FDD835",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class LessonItem
|
||||
{
|
||||
public Guid LessonId { get; set; }
|
||||
|
||||
@@ -162,6 +162,16 @@ public partial class SettingsViewModel : ObservableObject
|
||||
[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; } = [];
|
||||
@@ -283,6 +293,7 @@ public partial class SettingsViewModel : ObservableObject
|
||||
|
||||
private readonly ISchoolHolidayRepository _schoolHolidays;
|
||||
private readonly SchoolCalendarSettingsService _calendarSettings;
|
||||
private readonly SchoolWeatherService? _schoolWeather;
|
||||
private readonly PeriodScheduleService _periodSchedule;
|
||||
private readonly ISupervisionDutyRepository _supervisionDuties;
|
||||
private readonly AiSettingsService _aiSettings;
|
||||
@@ -318,7 +329,8 @@ public partial class SettingsViewModel : ObservableObject
|
||||
AppLogger logger, SyncKeyStatus syncKeyStatus, SyncKeyRecoveryService syncKeyRecovery,
|
||||
AppearanceSettingsService appearance, TrashViewModel trashTab,
|
||||
SnapshotService? snapshotService = null, SyncEngine? syncEngine = null,
|
||||
UntisSyncService? untisSync = null, AnnualPlanSyncService? annualPlanSync = null)
|
||||
UntisSyncService? untisSync = null, AnnualPlanSyncService? annualPlanSync = null,
|
||||
SchoolWeatherService? schoolWeather = null)
|
||||
{
|
||||
_logger = logger;
|
||||
_syncKeyRecovery = syncKeyRecovery;
|
||||
@@ -341,6 +353,7 @@ public partial class SettingsViewModel : ObservableObject
|
||||
_shorthandCodes = shorthandCodes;
|
||||
_schoolHolidays = schoolHolidays;
|
||||
_calendarSettings = calendarSettings;
|
||||
_schoolWeather = schoolWeather;
|
||||
_periodSchedule = periodSchedule;
|
||||
_supervisionDuties = supervisionDuties;
|
||||
_letterTemplates = letterTemplates;
|
||||
@@ -930,6 +943,60 @@ public partial class SettingsViewModel : ObservableObject
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user