CI / build-and-test (push) Canceled after 0s
Zeigt die Fälligkeit der vier bestehenden, manuell ausgelösten WebUntis-Abgleiche (Fehlzeiten je Lerngruppe, offene Stunden, Klassenbuch-, Hausaufgabenabgleich) in einem neuen Hub-Fenster, ohne selbst WebUntis anzufragen - nur gespeicherte Zeitstempel werden ausgewertet. Konsolidiert die bisher verstreuten Einstiegspunkte (Sidebar-Button, Dashboard-Buttons) in ein neues WebUntis-Menü plus einen kompakten Gesundheits-Indikator auf dem Dashboard. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
130 lines
7.1 KiB
C#
130 lines
7.1 KiB
C#
using Avalonia;
|
|
using Avalonia.Controls;
|
|
using LehrerApp.Desktop.Services;
|
|
using LehrerApp.WebUntis;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace LehrerApp.Desktop.Views;
|
|
|
|
public sealed class OpenUntisPeriodsDialog : Window
|
|
{
|
|
private readonly WebUntisIntegrationService _service = App.Services.GetRequiredService<WebUntisIntegrationService>();
|
|
private readonly WebUntisSettingsService _settings = App.Services.GetRequiredService<WebUntisSettingsService>();
|
|
private readonly CancellationTokenSource _closed = new();
|
|
private readonly ComboBox _mode = new() { ItemsSource = new[] { "Mein Unterricht", "Meine Klasse" }, SelectedIndex = 0 };
|
|
private readonly ComboBox _element = new() { MinWidth = 150 };
|
|
private readonly DatePicker _start = new();
|
|
private readonly DatePicker _end = new() { SelectedDate = DateTimeOffset.Now };
|
|
private readonly Button _load = new() { Content = "Abrufen" };
|
|
private readonly Button _schoolYear = new() { Content = "Seit Schuljahresbeginn" };
|
|
private readonly TextBlock _status = new() { TextWrapping = Avalonia.Media.TextWrapping.Wrap };
|
|
private readonly StackPanel _rows = new() { Spacing = 12 };
|
|
private readonly StackPanel _filters = new() { Spacing = 10, IsEnabled = false };
|
|
private UntisOpenPeriodsMeta? _meta;
|
|
private int _yearId;
|
|
|
|
/// <summary>Für den Untis-Hub (<see cref="UntisHubActions.RunOffenePeriodsAsync"/>): letzter
|
|
/// Status-Text nach dem automatischen Laden beim Öffnen.</summary>
|
|
public string? LastStatus => _status.Text;
|
|
|
|
public OpenUntisPeriodsDialog()
|
|
{
|
|
Title = "Offene WebUntis-Stunden";
|
|
Width = 850; Height = 650; MinWidth = 600; MinHeight = 450;
|
|
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
|
var root = new DockPanel { Margin = new Thickness(20) };
|
|
var header = new StackPanel { Spacing = 12 };
|
|
header.Children.Add(new TextBlock { Text = Title, FontSize = 22 });
|
|
var selection = new StackPanel { Orientation = Avalonia.Layout.Orientation.Horizontal, Spacing = 12 };
|
|
selection.Children.Add(_mode); selection.Children.Add(_element);
|
|
_filters.Children.Add(selection);
|
|
var dates = new WrapPanel { Orientation = Avalonia.Layout.Orientation.Horizontal };
|
|
dates.Children.Add(new TextBlock { Text = "Von ", VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center });
|
|
dates.Children.Add(_start);
|
|
dates.Children.Add(new TextBlock { Text = " bis ", VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center });
|
|
dates.Children.Add(_end);
|
|
_filters.Children.Add(dates);
|
|
var actions = new StackPanel { Orientation = Avalonia.Layout.Orientation.Horizontal, Spacing = 12 };
|
|
actions.Children.Add(_schoolYear); actions.Children.Add(_load);
|
|
_filters.Children.Add(actions);
|
|
header.Children.Add(_filters); header.Children.Add(_status);
|
|
DockPanel.SetDock(header, Dock.Top); root.Children.Add(header);
|
|
root.Children.Add(new ScrollViewer { Content = _rows, Margin = new Thickness(0, 20, 0, 0) });
|
|
Content = root;
|
|
_mode.SelectionChanged += (_, _) => SelectMode();
|
|
_element.SelectionChanged += (_, _) => ClearResults();
|
|
_start.SelectedDateChanged += (_, _) => ClearResults();
|
|
_end.SelectedDateChanged += (_, _) => ClearResults();
|
|
_schoolYear.Click += (_, _) => { if (_meta is not null) _start.SelectedDate = new DateTimeOffset(_meta.SchoolYearStart.ToDateTime(TimeOnly.MinValue)); };
|
|
_load.Click += async (_, _) => await LoadAsync();
|
|
Opened += async (_, _) => await InitializeAsync();
|
|
Closed += (_, _) => _closed.Cancel();
|
|
}
|
|
|
|
private void ClearResults() { _rows.Children.Clear(); _status.Text = "Auswahl festlegen und Abrufen wählen."; }
|
|
|
|
private void SelectMode()
|
|
{
|
|
if (_meta is null) return;
|
|
var own = _mode.SelectedIndex == 0;
|
|
var elements = own ? _meta.Teachers : _meta.Classes;
|
|
_element.ItemsSource = elements;
|
|
var preferred = own ? _settings.TeacherUntisId ?? _meta.OwnTeacherId
|
|
: _settings.HomeroomClassUntisId ?? _meta.MyClassIds.Cast<int?>().FirstOrDefault();
|
|
_element.SelectedItem = elements.FirstOrDefault(x => x.Id == preferred)
|
|
?? (!own ? elements.FirstOrDefault(x => _meta.MyClassIds.Contains(x.Id)) : null);
|
|
ClearResults();
|
|
}
|
|
|
|
private async Task InitializeAsync()
|
|
{
|
|
_status.Text = "WebUntis-Auswahl wird geladen …";
|
|
try
|
|
{
|
|
var today = DateOnly.FromDateTime(DateTime.Today);
|
|
var date = today.Year * 10000 + today.Month * 100 + today.Day;
|
|
var years = await _service.GetSchoolYearsAsync(_closed.Token);
|
|
var year = years.FirstOrDefault(x => x.StartDate <= date && date <= x.EndDate)
|
|
?? throw new InvalidDataException("Kein aktuelles WebUntis-Schuljahr gefunden.");
|
|
_yearId = year.UntisId;
|
|
_meta = await _service.GetOpenPeriodsMetaAsync(_yearId, _closed.Token);
|
|
_start.SelectedDate = new DateTimeOffset(_meta.SchoolYearStart.ToDateTime(TimeOnly.MinValue));
|
|
SelectMode();
|
|
_filters.IsEnabled = true;
|
|
if (_element.SelectedItem is not null) await LoadAsync();
|
|
}
|
|
catch (OperationCanceledException) when (_closed.IsCancellationRequested) { }
|
|
catch (Exception ex) { _status.Text = $"Abruf fehlgeschlagen: {ex.Message}"; }
|
|
}
|
|
|
|
private async Task LoadAsync()
|
|
{
|
|
_rows.Children.Clear();
|
|
if (_element.SelectedItem is not UntisOpenElement element || _start.SelectedDate is null || _end.SelectedDate is null)
|
|
{ _status.Text = "Bitte Lehrkraft oder Klasse und Zeitraum auswählen."; return; }
|
|
var start = DateOnly.FromDateTime(_start.SelectedDate.Value.DateTime);
|
|
var end = DateOnly.FromDateTime(_end.SelectedDate.Value.DateTime);
|
|
if (start > end || start < _meta!.SchoolYearStart || end > _meta.SchoolYearEnd)
|
|
{ _status.Text = "Bitte einen gültigen Zeitraum innerhalb des Schuljahres auswählen."; return; }
|
|
_filters.IsEnabled = false;
|
|
_status.Text = "Offene Stunden werden geladen …";
|
|
try
|
|
{
|
|
var own = _mode.SelectedIndex == 0;
|
|
var periods = await _service.GetOpenPeriodsAsync(_yearId, own ? element.Id : null,
|
|
own ? null : element.Id, start, end, _closed.Token);
|
|
foreach (var period in periods)
|
|
_rows.Children.Add(new TextBlock
|
|
{
|
|
Text = $"{period.When}\n{period.Classes} · {period.Subject} · {period.Teachers}\n{period.Missing}",
|
|
TextWrapping = Avalonia.Media.TextWrapping.Wrap
|
|
});
|
|
_status.Text = periods.Count == 0 ? "Keine offenen Stunden im gewählten Zeitraum."
|
|
: $"{periods.Count} offene Stunde(n) · Stand {DateTime.Now:HH:mm}";
|
|
}
|
|
catch (OperationCanceledException) when (_closed.IsCancellationRequested) { }
|
|
catch (Exception ex) { _status.Text = $"Abruf fehlgeschlagen: {ex.Message}"; }
|
|
finally { _filters.IsEnabled = true; }
|
|
}
|
|
}
|