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>
This commit is contained in:
@@ -185,6 +185,7 @@ public static class AppBootstrapper
|
||||
services.AddSingleton<IUntisClassRegisterCacheRepository, UntisClassRegisterCacheRepository>();
|
||||
services.AddSingleton<IUntisCacheFetchStateRepository, UntisCacheFetchStateRepository>();
|
||||
services.AddSingleton<IUntisStudentRosterCacheRepository, UntisStudentRosterCacheRepository>();
|
||||
services.AddSingleton<IUntisHubJobStateRepository, UntisHubJobStateRepository>();
|
||||
|
||||
// ── Services ──────────────────────────────────────────────────────────
|
||||
services.AddSingleton<GradingService>();
|
||||
@@ -248,6 +249,7 @@ public static class AppBootstrapper
|
||||
services.AddSingleton(sp => new SchoolWeatherService(new HttpClient(), syncSettings));
|
||||
services.AddSingleton(sp => new WebUntisIntegrationService(new HttpClient(), untisSettings));
|
||||
services.AddSingleton<UntisReportCacheService>();
|
||||
services.AddSingleton<UntisHubService>();
|
||||
// War dieses Gerät schon eingeloggt, aber sync.key fehlt(e), wurde gerade eben (unten)
|
||||
// stillschweigend ein neuer, unabhängiger Schlüssel erzeugt - bisher unter dem ALTEN
|
||||
// Schlüssel synchronisierte Server-Daten sind für dieses Gerät dann nicht mehr lesbar.
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
using Avalonia.Controls;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.ViewModels.Groups;
|
||||
using LehrerApp.Desktop.ViewModels.Students;
|
||||
using LehrerApp.Desktop.Views;
|
||||
using LehrerApp.Desktop.Views.Groups;
|
||||
using LehrerApp.Desktop.Views.Students;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace LehrerApp.Desktop.Services;
|
||||
|
||||
/// <summary>Führt einen der vier bestehenden WebUntis-Abgleiche (unverändert, über ihre bestehenden
|
||||
/// Dialoge) aus und vermerkt das Ergebnis im <see cref="UntisHubService"/> - genutzt sowohl vom
|
||||
/// Untis-Hub-Dialog ("Jetzt prüfen" je Zeile) als auch von den WebUntis-Menüpunkten in
|
||||
/// <c>MainWindow</c>, damit beide Einstiegspunkte denselben Fälligkeitsstand pflegen.</summary>
|
||||
public static class UntisHubActions
|
||||
{
|
||||
public static async Task RunFehlzeitenAsync(Window owner, LearningGroup group, UntisHubJobKind kind,
|
||||
DateOnly start, DateOnly end, UntisHubService hub)
|
||||
{
|
||||
var vm = new WebUntisLessonAbsenceComparisonViewModel(group,
|
||||
App.Services.GetRequiredService<WebUntisIntegrationService>(),
|
||||
App.Services.GetRequiredService<IStudentRepository>(),
|
||||
App.Services.GetRequiredService<IParticipationSessionRepository>(),
|
||||
App.Services.GetRequiredService<IParticipationRepository>())
|
||||
{ StartDate = start.ToDateTime(TimeOnly.MinValue), EndDate = end.ToDateTime(TimeOnly.MinValue) };
|
||||
var loaded = TrackLoad(vm, v => v.Busy);
|
||||
await new WebUntisLessonAbsenceComparisonDialog { DataContext = vm }.ShowDialog(owner);
|
||||
if (loaded()) hub.RecordRun(kind, group.Id, vm.Status);
|
||||
}
|
||||
|
||||
public static async Task RunKlassenbuchAsync(Window owner, UntisHubService hub)
|
||||
{
|
||||
var vm = new WebUntisDocumentationComparisonViewModel(
|
||||
App.Services.GetRequiredService<WebUntisIntegrationService>(),
|
||||
App.Services.GetRequiredService<IStudentRepository>(),
|
||||
App.Services.GetRequiredService<IGroupRepository>(),
|
||||
App.Services.GetRequiredService<IDocumentationRepository>(),
|
||||
App.Services.GetRequiredService<SchoolYearService>());
|
||||
var loaded = TrackLoad(vm, v => v.Busy);
|
||||
await new WebUntisDocumentationComparisonDialog { DataContext = vm }.ShowDialog(owner);
|
||||
if (loaded()) hub.RecordRun(UntisHubJobKind.Klassenbuchabgleich, null, vm.Status);
|
||||
}
|
||||
|
||||
public static async Task RunHausaufgabenAsync(Window owner, UntisHubService hub)
|
||||
{
|
||||
var vm = new WebUntisHomeworkComparisonViewModel(
|
||||
App.Services.GetRequiredService<WebUntisIntegrationService>(),
|
||||
App.Services.GetRequiredService<IStudentRepository>(),
|
||||
App.Services.GetRequiredService<IGroupRepository>(),
|
||||
App.Services.GetRequiredService<ISubjectRepository>(),
|
||||
App.Services.GetRequiredService<IGroupMembershipRepository>(),
|
||||
App.Services.GetRequiredService<IParticipationSessionRepository>(),
|
||||
App.Services.GetRequiredService<IParticipationRepository>(),
|
||||
App.Services.GetRequiredService<SchoolYearService>());
|
||||
var loaded = TrackLoad(vm, v => v.Busy);
|
||||
await new WebUntisHomeworkComparisonDialog { DataContext = vm }.ShowDialog(owner);
|
||||
if (loaded()) hub.RecordRun(UntisHubJobKind.Hausaufgabenabgleich, null, vm.Status);
|
||||
}
|
||||
|
||||
public static async Task RunOffenePeriodsAsync(Window owner, UntisHubService hub)
|
||||
{
|
||||
var dialog = new OpenUntisPeriodsDialog();
|
||||
await dialog.ShowDialog(owner);
|
||||
hub.RecordRun(UntisHubJobKind.OffenePeriods, null, dialog.LastStatus);
|
||||
}
|
||||
|
||||
/// <summary>Erkennt nicht-invasiv (ohne die Vergleichs-ViewModels zu ändern), ob im Dialog
|
||||
/// tatsächlich ein Ladeversuch stattfand: <c>Busy</c> wechselt in <c>Load()</c> immer erst auf
|
||||
/// true und im <c>finally</c>-Block zurück auf false, egal ob erfolgreich oder mit Fehler
|
||||
/// abgebrochen - genau dieser Übergang wird hier beobachtet.</summary>
|
||||
private static Func<bool> TrackLoad<T>(T vm, Func<T, bool> isBusy) where T : ObservableObject
|
||||
{
|
||||
var loaded = false;
|
||||
var wasBusy = false;
|
||||
vm.PropertyChanged += (_, e) =>
|
||||
{
|
||||
if (e.PropertyName != "Busy") return;
|
||||
var busy = isBusy(vm);
|
||||
if (wasBusy && !busy) loaded = true;
|
||||
wasBusy = busy;
|
||||
};
|
||||
return () => loaded;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
|
||||
namespace LehrerApp.Desktop.Services;
|
||||
|
||||
public enum UntisHubDueState { Ok, Due, Overdue }
|
||||
|
||||
/// <summary>Eine Zeile im Untis-Hub: eine Job-Instanz (Fehlzeiten-Kadenz je Lerngruppe, oder einer
|
||||
/// der drei dashboard-weiten Jobs) mit ihrer aktuellen Fälligkeit.</summary>
|
||||
public sealed record UntisHubJobRow(
|
||||
UntisHubJobKind Kind, Guid? GroupId, string GroupName,
|
||||
DateTime? LastRunAt, string? LastResultSummary, UntisHubDueState DueState, string DueLabel);
|
||||
|
||||
/// <summary>
|
||||
/// Zeigt, welche der bestehenden, rein manuell ausgelösten WebUntis-Abgleiche (Fehlzeiten pro
|
||||
/// Lerngruppe, offene Stunden, Klassenbuch-/Hausaufgabenabgleich) fällig sind - ohne selbst
|
||||
/// WebUntis anzufragen (Nutzer-Feedback: "nicht bei WebUntis auffallen", siehe
|
||||
/// <see cref="UntisReportCacheService"/>). Die eigentlichen Abgleiche laufen weiterhin über die
|
||||
/// bestehenden Vergleichsdialoge (siehe <see cref="UntisHubActions"/>); dieser Service verwaltet
|
||||
/// nur die Fälligkeits-Zeitstempel dazu.
|
||||
/// </summary>
|
||||
public sealed class UntisHubService(
|
||||
IGroupRepository groups, IUntisHubJobStateRepository jobStates, SchoolYearService schoolYears)
|
||||
{
|
||||
private static readonly TimeSpan FehlzeitenKurzDue = TimeSpan.FromDays(14);
|
||||
private static readonly TimeSpan FehlzeitenKurzOverdue = TimeSpan.FromDays(21);
|
||||
private static readonly TimeSpan FehlzeitenLangDue = TimeSpan.FromDays(60);
|
||||
private static readonly TimeSpan FehlzeitenLangOverdue = TimeSpan.FromDays(90);
|
||||
private static readonly TimeSpan GlobalDue = TimeSpan.FromDays(14);
|
||||
private static readonly TimeSpan GlobalOverdue = TimeSpan.FromDays(21);
|
||||
|
||||
private static readonly (UntisHubJobKind Kind, string Label)[] GlobalJobs =
|
||||
[
|
||||
(UntisHubJobKind.OffenePeriods, "Offene Stunden"),
|
||||
(UntisHubJobKind.Klassenbuchabgleich, "Klassenbuchabgleich"),
|
||||
(UntisHubJobKind.Hausaufgabenabgleich, "Hausaufgabenabgleich"),
|
||||
];
|
||||
|
||||
public List<UntisHubJobRow> GetRows()
|
||||
{
|
||||
var eligibleGroups = groups.GetBySchoolYear(schoolYears.CurrentSchoolYear())
|
||||
.Where(g => g.WebUntisLessonId is not null)
|
||||
.OrderBy(g => g.Name)
|
||||
.ToList();
|
||||
return BuildRows(eligibleGroups, jobStates.GetAll(), DateTime.UtcNow);
|
||||
}
|
||||
|
||||
public void RecordRun(UntisHubJobKind kind, Guid? groupId, string? summary) =>
|
||||
jobStates.Save(new UntisHubJobState
|
||||
{
|
||||
Id = jobStates.Get(kind, groupId)?.Id ?? Guid.NewGuid(),
|
||||
Kind = kind, GroupId = groupId, LastRunAt = DateTime.UtcNow, LastResultSummary = summary,
|
||||
});
|
||||
|
||||
/// Reine Entscheidungslogik ohne Repository-Zugriff (gleiches Muster wie
|
||||
/// <see cref="UntisReportCacheService.Plan"/>): aus den fälligkeitsrelevanten Lerngruppen und den
|
||||
/// zuletzt gespeicherten Job-Zuständen wird die vollständige Hub-Zeilenliste gebaut - zwei
|
||||
/// Fehlzeiten-Zeilen je Gruppe plus die drei dashboard-weiten Zeilen.
|
||||
public static List<UntisHubJobRow> BuildRows(
|
||||
IReadOnlyList<LearningGroup> eligibleGroups, IReadOnlyList<UntisHubJobState> states, DateTime utcNow)
|
||||
{
|
||||
UntisHubJobState? State(UntisHubJobKind kind, Guid? groupId) =>
|
||||
states.FirstOrDefault(s => s.Kind == kind && s.GroupId == groupId);
|
||||
|
||||
var rows = new List<UntisHubJobRow>();
|
||||
foreach (var group in eligibleGroups)
|
||||
{
|
||||
rows.Add(Row(UntisHubJobKind.FehlzeitenKurz, group.Id, group.Name,
|
||||
State(UntisHubJobKind.FehlzeitenKurz, group.Id), FehlzeitenKurzDue, FehlzeitenKurzOverdue, utcNow));
|
||||
rows.Add(Row(UntisHubJobKind.FehlzeitenLang, group.Id, group.Name,
|
||||
State(UntisHubJobKind.FehlzeitenLang, group.Id), FehlzeitenLangDue, FehlzeitenLangOverdue, utcNow));
|
||||
}
|
||||
foreach (var (kind, label) in GlobalJobs)
|
||||
rows.Add(Row(kind, null, label, State(kind, null), GlobalDue, GlobalOverdue, utcNow));
|
||||
return rows;
|
||||
}
|
||||
|
||||
private static UntisHubJobRow Row(UntisHubJobKind kind, Guid? groupId, string groupName,
|
||||
UntisHubJobState? state, TimeSpan due, TimeSpan overdue, DateTime utcNow)
|
||||
{
|
||||
var (dueState, dueLabel) = DueStatus(state?.LastRunAt, due, overdue, utcNow);
|
||||
return new UntisHubJobRow(kind, groupId, groupName, state?.LastRunAt, state?.LastResultSummary,
|
||||
dueState, dueLabel);
|
||||
}
|
||||
|
||||
private static (UntisHubDueState, string) DueStatus(
|
||||
DateTime? lastRunAt, TimeSpan due, TimeSpan overdue, DateTime utcNow)
|
||||
{
|
||||
if (lastRunAt is null) return (UntisHubDueState.Overdue, "noch nie geprüft");
|
||||
var age = utcNow - lastRunAt.Value;
|
||||
var days = (int)age.TotalDays;
|
||||
if (age >= overdue) return (UntisHubDueState.Overdue, $"fällig seit {days} Tagen");
|
||||
if (age >= due) return (UntisHubDueState.Due, $"fällig seit {days} Tagen");
|
||||
return (UntisHubDueState.Ok, days == 0 ? "gerade eben geprüft" : $"vor {days} Tag(en) geprüft");
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,8 @@ public partial class DashboardViewModel : ObservableObject
|
||||
private readonly ITimeEntryRepository _timeEntries;
|
||||
private readonly IAnnualPlanEventRepository? _annualPlanEvents;
|
||||
private readonly SchoolWeatherService? _schoolWeather;
|
||||
private readonly UntisHubService _untisHub;
|
||||
private readonly WebUntisIntegrationService _webUntis;
|
||||
|
||||
private const int OpenExcuseMaxAgeDays = 21;
|
||||
private const int SupportPlanDueWithinDays = 14;
|
||||
@@ -143,6 +145,10 @@ public partial class DashboardViewModel : ObservableObject
|
||||
public string AttentionSummary => AttentionCount == 1 ? "1 offener Punkt" : $"{AttentionCount} offene Punkte";
|
||||
public string UpcomingSummary => UpcomingCount == 1 ? "1 Termin" : $"{UpcomingCount} Termine";
|
||||
|
||||
[ObservableProperty] private string _webUntisHealthLabel = "";
|
||||
[ObservableProperty] private bool _isWebUntisHealthWarning;
|
||||
[ObservableProperty] private bool _isWebUntisHealthVisible;
|
||||
|
||||
public DashboardViewModel(IGroupRepository groups, ISubjectRepository subjects, ILessonRepository lessons,
|
||||
IExamRepository exams, IExamResultRepository examResults, IGradeRepository grades,
|
||||
IReportGradeRepository reportGrades, IGroupMembershipRepository memberships,
|
||||
@@ -153,6 +159,7 @@ public partial class DashboardViewModel : ObservableObject
|
||||
DashboardSettingsService dashboardSettings, ISchoolHolidayRepository schoolHolidays,
|
||||
PublicHolidayService publicHolidays, SchoolCalendarSettingsService calendarSettings,
|
||||
ISubstitutionEntryRepository substitutions, ITimeEntryRepository timeEntries,
|
||||
UntisHubService untisHub, WebUntisIntegrationService webUntis,
|
||||
IAnnualPlanEventRepository? annualPlanEvents = null,
|
||||
AnnualPlanSyncService? annualPlanSync = null, SchoolWeatherService? schoolWeather = null)
|
||||
{
|
||||
@@ -167,12 +174,28 @@ public partial class DashboardViewModel : ObservableObject
|
||||
_substitutions = substitutions;
|
||||
_annualPlanEvents = annualPlanEvents;
|
||||
_schoolWeather = schoolWeather;
|
||||
_untisHub = untisHub;
|
||||
_webUntis = webUntis;
|
||||
if (annualPlanSync is not null)
|
||||
{
|
||||
annualPlanSync.DataChanged += () => Avalonia.Threading.Dispatcher.UIThread.Post(LoadCalendar);
|
||||
}
|
||||
LoadDashboardCards();
|
||||
Load();
|
||||
RefreshWebUntisHealth();
|
||||
}
|
||||
|
||||
/// <summary>Liest nur den gespeicherten Fälligkeitsstand der Untis-Hub-Jobs (kein
|
||||
/// WebUntis-Zugriff, siehe <see cref="UntisHubService.GetRows"/>) - aufgerufen bei jedem
|
||||
/// Dashboard-Refresh und erneut, nachdem der Nutzer den Hub geöffnet/einen Abgleich gemacht hat.</summary>
|
||||
public void RefreshWebUntisHealth()
|
||||
{
|
||||
IsWebUntisHealthVisible = _webUntis.IsAvailable;
|
||||
if (!IsWebUntisHealthVisible) return;
|
||||
var rows = _untisHub.GetRows();
|
||||
var due = rows.Count(r => r.DueState != UntisHubDueState.Ok);
|
||||
IsWebUntisHealthWarning = due > 0;
|
||||
WebUntisHealthLabel = due > 0 ? $"WebUntis ⚠ {due} fällig" : "WebUntis ✓";
|
||||
}
|
||||
|
||||
private DashboardCardOption Card(string key) => DashboardCards.First(c => c.Key == key);
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Desktop.Services;
|
||||
|
||||
namespace LehrerApp.Desktop.ViewModels.UntisHub;
|
||||
|
||||
/// <summary>Eine Zeile im Untis-Hub-Fenster - reine Anzeige-Projektion von
|
||||
/// <see cref="UntisHubJobRow"/>, neu aufgebaut bei jedem <see cref="UntisHubViewModel.Load"/>.</summary>
|
||||
public sealed class UntisHubRowViewModel
|
||||
{
|
||||
public UntisHubJobKind Kind { get; }
|
||||
public Guid? GroupId { get; }
|
||||
public string GroupName { get; }
|
||||
public string JobLabel { get; }
|
||||
public string DueLabel { get; }
|
||||
public string? LastResultSummary { get; }
|
||||
public bool IsWarning { get; }
|
||||
public bool IsDanger { get; }
|
||||
|
||||
public UntisHubRowViewModel(UntisHubJobRow row)
|
||||
{
|
||||
Kind = row.Kind; GroupId = row.GroupId; GroupName = row.GroupName;
|
||||
JobLabel = Label(row.Kind); DueLabel = row.DueLabel; LastResultSummary = row.LastResultSummary;
|
||||
IsWarning = row.DueState == UntisHubDueState.Due;
|
||||
IsDanger = row.DueState == UntisHubDueState.Overdue;
|
||||
}
|
||||
|
||||
private static string Label(UntisHubJobKind kind) => kind switch
|
||||
{
|
||||
UntisHubJobKind.FehlzeitenKurz => "Fehlzeiten (kurzfristig)",
|
||||
UntisHubJobKind.FehlzeitenLang => "Fehlzeiten (seit Schuljahresbeginn)",
|
||||
UntisHubJobKind.OffenePeriods => "Offene Stunden",
|
||||
UntisHubJobKind.Klassenbuchabgleich => "Klassenbuchabgleich",
|
||||
UntisHubJobKind.Hausaufgabenabgleich => "Hausaufgabenabgleich",
|
||||
_ => kind.ToString(),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>ViewModel des Untis-Hub-Fensters (siehe TODO.md) - zeigt nur den gespeicherten
|
||||
/// Fälligkeitsstand an (<see cref="UntisHubService.GetRows"/>, rein lesend aus LiteDB). Das
|
||||
/// tatsächliche Ausführen eines Jobs (inkl. WebUntis-Anfrage) übernimmt die Code-Behind-Klasse über
|
||||
/// <see cref="UntisHubActions"/>, weil dafür ein Fenster-Owner für <c>ShowDialog</c> gebraucht wird.</summary>
|
||||
public partial class UntisHubViewModel : ObservableObject
|
||||
{
|
||||
private readonly UntisHubService _hub;
|
||||
private readonly IGroupRepository _groups;
|
||||
|
||||
public ObservableCollection<UntisHubRowViewModel> Rows { get; } = [];
|
||||
[ObservableProperty] private bool _isAvailable;
|
||||
[ObservableProperty] private string _status = "";
|
||||
|
||||
public UntisHubViewModel(UntisHubService hub, IGroupRepository groups, WebUntisIntegrationService untis)
|
||||
{
|
||||
_hub = hub; _groups = groups;
|
||||
IsAvailable = untis.IsAvailable;
|
||||
Load();
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
Rows.Clear();
|
||||
if (!IsAvailable)
|
||||
{
|
||||
Status = "WebUntis ist nicht konfiguriert (siehe Einstellungen).";
|
||||
return;
|
||||
}
|
||||
foreach (var row in _hub.GetRows()) Rows.Add(new UntisHubRowViewModel(row));
|
||||
var overdue = Rows.Count(r => r.IsDanger);
|
||||
var due = Rows.Count(r => r.IsWarning);
|
||||
Status = overdue > 0 || due > 0
|
||||
? $"{overdue + due} von {Rows.Count} Prüfungen fällig ({overdue} überfällig)."
|
||||
: $"Alle {Rows.Count} Prüfungen aktuell.";
|
||||
}
|
||||
|
||||
public LearningGroup? FindGroup(Guid id) => _groups.GetById(id);
|
||||
}
|
||||
@@ -9,6 +9,12 @@
|
||||
<Setter Property="Foreground" Value="Red"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
</Style>
|
||||
<Style Selector="Button.webuntishealth">
|
||||
<Setter Property="Foreground" Value="{DynamicResource AppStatusOkBrush}"/>
|
||||
</Style>
|
||||
<Style Selector="Button.webuntishealth.warning">
|
||||
<Setter Property="Foreground" Value="{DynamicResource AppStatusWarningBrush}"/>
|
||||
</Style>
|
||||
<Style Selector="Border.daycell.selected">
|
||||
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundAccentBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="2"/>
|
||||
@@ -26,9 +32,11 @@
|
||||
<TextBlock Text="{Binding CurrentDate}" FontSize="24" FontWeight="SemiBold"/>
|
||||
</StackPanel>
|
||||
<WrapPanel Grid.Row="1" Orientation="Horizontal" ItemSpacing="8" LineSpacing="8" Margin="0,10,0,0">
|
||||
<Button Content="Klassenbuch abgleichen…" Click="OnCompareWebUntisDocumentationClick"
|
||||
VerticalAlignment="Center"/>
|
||||
<Button Content="Fehlende Hausaufgaben abgleichen…" Click="OnCompareWebUntisHomeworkClick"
|
||||
<!-- Fasst die vier WebUntis-Abgleiche zusammen, die jetzt nur noch im Menü "WebUntis"
|
||||
erreichbar sind (siehe TODO.md, Untis-Hub) - Klick öffnet den Hub. -->
|
||||
<Button Content="{Binding WebUntisHealthLabel}" Click="OnOpenUntisHubClick"
|
||||
IsVisible="{Binding IsWebUntisHealthVisible}"
|
||||
Classes="webuntishealth" Classes.warning="{Binding IsWebUntisHealthWarning}"
|
||||
VerticalAlignment="Center"/>
|
||||
<Button Content="Bereiche anpassen" Command="{Binding ToggleDashboardSettingsCommand}"
|
||||
VerticalAlignment="Center"/>
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.Services;
|
||||
using LehrerApp.Desktop.ViewModels;
|
||||
using LehrerApp.Desktop.ViewModels.Groups;
|
||||
using LehrerApp.Desktop.ViewModels.Students;
|
||||
using LehrerApp.Desktop.Views.Groups;
|
||||
using LehrerApp.Desktop.Views.Students;
|
||||
using LehrerApp.Desktop.Views.UntisHub;
|
||||
using LehrerApp.Desktop.Views.Workload;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
@@ -32,32 +27,11 @@ public partial class DashboardView : UserControl
|
||||
return await WorkTaskDialogHelper.ShowDialog(owner, startAsReminder: startAsReminder);
|
||||
}
|
||||
|
||||
private async void OnCompareWebUntisDocumentationClick(object? sender, RoutedEventArgs e)
|
||||
private async void OnOpenUntisHubClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var owner = TopLevel.GetTopLevel(this) as Window;
|
||||
if (owner is null) return;
|
||||
var dialogVm = new WebUntisDocumentationComparisonViewModel(
|
||||
App.Services.GetRequiredService<WebUntisIntegrationService>(),
|
||||
App.Services.GetRequiredService<IStudentRepository>(),
|
||||
App.Services.GetRequiredService<IGroupRepository>(),
|
||||
App.Services.GetRequiredService<IDocumentationRepository>(),
|
||||
App.Services.GetRequiredService<SchoolYearService>());
|
||||
await new WebUntisDocumentationComparisonDialog { DataContext = dialogVm }.ShowDialog(owner);
|
||||
}
|
||||
|
||||
private async void OnCompareWebUntisHomeworkClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var owner = TopLevel.GetTopLevel(this) as Window;
|
||||
if (owner is null) return;
|
||||
var dialogVm = new WebUntisHomeworkComparisonViewModel(
|
||||
App.Services.GetRequiredService<WebUntisIntegrationService>(),
|
||||
App.Services.GetRequiredService<IStudentRepository>(),
|
||||
App.Services.GetRequiredService<IGroupRepository>(),
|
||||
App.Services.GetRequiredService<ISubjectRepository>(),
|
||||
App.Services.GetRequiredService<IGroupMembershipRepository>(),
|
||||
App.Services.GetRequiredService<IParticipationSessionRepository>(),
|
||||
App.Services.GetRequiredService<IParticipationRepository>(),
|
||||
App.Services.GetRequiredService<SchoolYearService>());
|
||||
await new WebUntisHomeworkComparisonDialog { DataContext = dialogVm }.ShowDialog(owner);
|
||||
await new UntisHubDialog().ShowDialog(owner);
|
||||
if (DataContext is DashboardViewModel vm) vm.RefreshWebUntisHealth();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,16 @@
|
||||
darunter als Overlay-Drawer mit Hamburger-Button (automatisch).
|
||||
Kein eigener Code nötig.
|
||||
-->
|
||||
<DockPanel>
|
||||
<Menu DockPanel.Dock="Top">
|
||||
<MenuItem Header="WebUntis">
|
||||
<MenuItem Header="Untis-Hub…" Click="OnOpenUntisHub"/>
|
||||
<Separator/>
|
||||
<MenuItem Header="Offene Stunden…" Click="OnOpenUntisPeriods"/>
|
||||
<MenuItem Header="Klassenbuchabgleich…" Click="OnCompareUntisKlassenbuch"/>
|
||||
<MenuItem Header="Hausaufgabenabgleich…" Click="OnCompareUntisHausaufgaben"/>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<DrawerPage x:Name="RootDrawer"
|
||||
DrawerLength="220"
|
||||
DrawerBehavior="Auto"
|
||||
@@ -250,15 +260,6 @@
|
||||
<TextBlock Classes="navlabel" Text="Klassenlehrer"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Classes="navitem" HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left" CornerRadius="6"
|
||||
Click="OnOpenUntisPeriods"
|
||||
ToolTip.Tip="Offene Untis-Stunden" AutomationProperties.Name="Offene Untis-Stunden">
|
||||
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
|
||||
<PathIcon Classes="navicon" Data="{StaticResource IconOpenPeriods}"/>
|
||||
<TextBlock Classes="navlabel" Text="Offene Untis-Stunden"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Classes="navitem" Classes.active="{Binding IsSettingsActive}" HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
CornerRadius="6"
|
||||
@@ -276,6 +277,7 @@
|
||||
</DrawerPage.Drawer>
|
||||
|
||||
</DrawerPage>
|
||||
</DockPanel>
|
||||
|
||||
<!-- Globale Suche und Schnellerfassung (14.2). Bewusst als Overlay auf der aktuellen Seite:
|
||||
Der Nutzer behält den Kontext und kann mit Escape ohne Navigation zurückkehren. -->
|
||||
|
||||
@@ -3,7 +3,9 @@ using Avalonia.Input;
|
||||
using Avalonia.Threading;
|
||||
using LehrerApp.Desktop.Services;
|
||||
using LehrerApp.Desktop.ViewModels;
|
||||
using LehrerApp.Desktop.Views.UntisHub;
|
||||
using LehrerApp.Sync;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace LehrerApp.Desktop.Views;
|
||||
|
||||
@@ -23,9 +25,24 @@ public partial class MainWindow : Window
|
||||
KeyDown += OnWindowKeyDown;
|
||||
}
|
||||
|
||||
private async void OnOpenUntisHub(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
await new UntisHubDialog().ShowDialog(this);
|
||||
}
|
||||
|
||||
private async void OnOpenUntisPeriods(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
await new OpenUntisPeriodsDialog().ShowDialog(this);
|
||||
await UntisHubActions.RunOffenePeriodsAsync(this, App.Services.GetRequiredService<UntisHubService>());
|
||||
}
|
||||
|
||||
private async void OnCompareUntisKlassenbuch(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
await UntisHubActions.RunKlassenbuchAsync(this, App.Services.GetRequiredService<UntisHubService>());
|
||||
}
|
||||
|
||||
private async void OnCompareUntisHausaufgaben(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
await UntisHubActions.RunHausaufgabenAsync(this, App.Services.GetRequiredService<UntisHubService>());
|
||||
}
|
||||
|
||||
private void OnWindowKeyDown(object? sender, KeyEventArgs e)
|
||||
|
||||
@@ -23,6 +23,10 @@ public sealed class OpenUntisPeriodsDialog : Window
|
||||
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";
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="clr-namespace:LehrerApp.Desktop.ViewModels.UntisHub"
|
||||
x:Class="LehrerApp.Desktop.Views.UntisHub.UntisHubDialog"
|
||||
x:DataType="vm:UntisHubViewModel"
|
||||
Title="Untis-Hub"
|
||||
Width="900" Height="600" MinWidth="640" MinHeight="400"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
|
||||
<Window.Styles>
|
||||
<Style Selector="TextBlock.duestatus">
|
||||
<Setter Property="Foreground" Value="{DynamicResource AppStatusOkBrush}"/>
|
||||
</Style>
|
||||
<Style Selector="TextBlock.duestatus.warning">
|
||||
<Setter Property="Foreground" Value="{DynamicResource AppStatusWarningBrush}"/>
|
||||
</Style>
|
||||
<Style Selector="TextBlock.duestatus.danger">
|
||||
<Setter Property="Foreground" Value="{DynamicResource AppStatusDangerBrush}"/>
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
|
||||
<DockPanel Margin="20">
|
||||
<StackPanel DockPanel.Dock="Top" Spacing="10" Margin="0,0,0,14">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Text="Untis-Hub" FontSize="22" FontWeight="SemiBold"/>
|
||||
<Button Grid.Column="1" Content="Aktualisieren" Click="OnRefreshClick"/>
|
||||
</Grid>
|
||||
<TextBlock Text="{Binding Status}" FontSize="12" Opacity="0.7" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
|
||||
<DataGrid ItemsSource="{Binding Rows}" AutoGenerateColumns="False" IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal" BorderBrush="{DynamicResource AppCardBorderBrush}"
|
||||
BorderThickness="1" CanUserResizeColumns="True" CanUserSortColumns="True" RowHeight="46"
|
||||
IsVisible="{Binding IsAvailable}">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Bereich" Binding="{Binding GroupName}" Width="1.3*"/>
|
||||
<DataGridTextColumn Header="Prüfung" Binding="{Binding JobLabel}" Width="1.3*"/>
|
||||
<DataGridTemplateColumn Header="Status" Width="1.1*">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="vm:UntisHubRowViewModel">
|
||||
<TextBlock Text="{Binding DueLabel}" Classes="duestatus"
|
||||
Classes.warning="{Binding IsWarning}" Classes.danger="{Binding IsDanger}"
|
||||
VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTextColumn Header="Letztes Ergebnis" Binding="{Binding LastResultSummary}" Width="2*"/>
|
||||
<DataGridTemplateColumn Header="" Width="130">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="vm:UntisHubRowViewModel">
|
||||
<Button Content="Jetzt prüfen" Click="OnCheckClick" DataContext="{Binding}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<TextBlock IsVisible="{Binding !IsAvailable}" Text="WebUntis ist nicht konfiguriert. Bitte zuerst in den Einstellungen hinterlegen."
|
||||
FontSize="14" Opacity="0.7" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
@@ -0,0 +1,58 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.Services;
|
||||
using LehrerApp.Desktop.ViewModels.UntisHub;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace LehrerApp.Desktop.Views.UntisHub;
|
||||
|
||||
public partial class UntisHubDialog : Window
|
||||
{
|
||||
public UntisHubDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = new UntisHubViewModel(
|
||||
App.Services.GetRequiredService<UntisHubService>(),
|
||||
App.Services.GetRequiredService<IGroupRepository>(),
|
||||
App.Services.GetRequiredService<WebUntisIntegrationService>());
|
||||
}
|
||||
|
||||
private void OnRefreshClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is UntisHubViewModel vm) vm.Load();
|
||||
}
|
||||
|
||||
private async void OnCheckClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is not UntisHubViewModel vm || sender is not Button { DataContext: UntisHubRowViewModel row })
|
||||
return;
|
||||
var hub = App.Services.GetRequiredService<UntisHubService>();
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
|
||||
switch (row.Kind)
|
||||
{
|
||||
case UntisHubJobKind.FehlzeitenKurz or UntisHubJobKind.FehlzeitenLang:
|
||||
var group = row.GroupId is { } id ? vm.FindGroup(id) : null;
|
||||
if (group is null) return;
|
||||
var schoolYears = App.Services.GetRequiredService<SchoolYearService>();
|
||||
var start = row.Kind == UntisHubJobKind.FehlzeitenKurz
|
||||
? today.AddDays(-30)
|
||||
: schoolYears.SchoolYearStart(schoolYears.CurrentSchoolYear());
|
||||
await UntisHubActions.RunFehlzeitenAsync(this, group, row.Kind, start, today, hub);
|
||||
break;
|
||||
case UntisHubJobKind.OffenePeriods:
|
||||
await UntisHubActions.RunOffenePeriodsAsync(this, hub);
|
||||
break;
|
||||
case UntisHubJobKind.Klassenbuchabgleich:
|
||||
await UntisHubActions.RunKlassenbuchAsync(this, hub);
|
||||
break;
|
||||
case UntisHubJobKind.Hausaufgabenabgleich:
|
||||
await UntisHubActions.RunHausaufgabenAsync(this, hub);
|
||||
break;
|
||||
}
|
||||
vm.Load();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user