WIP (unstable): WebUntis-iCal-Abgleich für Vertretungen/Ausfälle
Erkennt Vertretungen, Ausfälle und Zusatzaufsichten aus dem persönlichen WebUntis-iCal-Feed und schreibt sie automatisch als SubstitutionEntry. Bekannter offener Bug: es tauchen weiterhin falsche Vertretungen für Stunden auf, die real unverändert sind — wird in einem Folge-Commit untersucht, deshalb vorerst auf diesem Branch statt main. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,11 +23,22 @@
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<Grid RowDefinitions="Auto,Auto,*">
|
||||
<shared:PageHeader Grid.Row="0" Margin="32,28,32,0" Title="Stundenplan"
|
||||
Subtitle="Wiederkehrendes wöchentliches Muster, keine konkreten Termine"/>
|
||||
|
||||
<TabbedPage Grid.Row="1" TabPlacement="Top" SelectedIndex="{Binding ActiveTabIndex}">
|
||||
<!-- WebUntis-Abweichung (Nutzer-Feedback: "oder der Stundenplan gar nicht mehr passt") -->
|
||||
<Border Grid.Row="1" Margin="32,12,32,0" Padding="12,8" CornerRadius="6"
|
||||
Background="#332196F3" IsVisible="{Binding HasUntisMismatch}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Text="⚠" FontSize="14" Margin="0,0,8,0" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding UntisMismatchLabel}" FontSize="12" VerticalAlignment="Center"/>
|
||||
<Button Grid.Column="2" Content="Prüfen…" FontSize="11" Padding="8,3"
|
||||
Command="{Binding ReviewUntisMismatchCommand}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<TabbedPage Grid.Row="2" TabPlacement="Top" SelectedIndex="{Binding ActiveTabIndex}">
|
||||
|
||||
<!-- Tab: Heute (Standardansicht) -->
|
||||
<ContentPage Header="Heute">
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="clr-namespace:LehrerApp.Desktop.ViewModels.Planning"
|
||||
x:Class="LehrerApp.Desktop.Views.Planning.UntisMappingReviewDialog"
|
||||
x:DataType="vm:UntisMappingReviewDialogViewModel"
|
||||
Title="WebUntis-Zuordnung prüfen"
|
||||
Width="560" Height="620" MinWidth="480" MinHeight="440"
|
||||
CanResize="True" WindowStartupLocation="CenterOwner">
|
||||
|
||||
<Window.Styles>
|
||||
<Style Selector="TextBlock.hint">
|
||||
<Setter Property="FontSize" Value="11"/>
|
||||
<Setter Property="Opacity" Value="0.6"/>
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
|
||||
<Grid RowDefinitions="*,Auto" Margin="24">
|
||||
<ScrollViewer Grid.Row="0">
|
||||
<StackPanel Spacing="14" Margin="0,0,12,0">
|
||||
<TextBlock Text="WebUntis-Zuordnung prüfen" Classes="dialogtitle"/>
|
||||
<TextBlock FontSize="12" Opacity="0.6" TextWrapping="Wrap"
|
||||
Text="Erkannte reguläre Wochenmuster aus dem iCal-Feed — nur bestätigte Zeilen (mit ausgewählter Gruppe) lösen künftig automatisch erkannte Vertretungen/Ausfälle aus."/>
|
||||
|
||||
<TextBlock Text="Abgleich läuft…" IsVisible="{Binding IsLoading}" FontSize="13"/>
|
||||
<TextBlock Text="{Binding ErrorMessage}" Foreground="Red" FontSize="12"
|
||||
IsVisible="{Binding ErrorMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding Rows}" IsVisible="{Binding !IsLoading}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:UntisMappingRow">
|
||||
<Border Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
||||
CornerRadius="6" Padding="10,8" Margin="0,0,0,6">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<StackPanel Grid.Column="0" Width="60">
|
||||
<TextBlock Text="{Binding WeekdayLabel}" FontWeight="SemiBold" FontSize="13"/>
|
||||
<TextBlock Text="{Binding TimeLabel}" Classes="hint" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Margin="8,0" VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding PatternLabel}" FontSize="13"/>
|
||||
<TextBlock Text="Mehrdeutig — bitte Gruppe wählen oder ignorieren" Classes="hint"
|
||||
Foreground="#F59E0B"
|
||||
IsVisible="{Binding !IsConfident}"/>
|
||||
</StackPanel>
|
||||
<ComboBox Grid.Column="2" Width="190" VerticalAlignment="Center"
|
||||
IsVisible="{Binding !IsSupervisionCandidate}"
|
||||
ItemsSource="{Binding $parent[ItemsControl].((vm:UntisMappingReviewDialogViewModel)DataContext).Groups}"
|
||||
SelectedItem="{Binding SelectedGroup}"
|
||||
IsEnabled="{Binding CanResolve}"
|
||||
PlaceholderText="Ignorieren">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:UntisGroupOption">
|
||||
<TextBlock Text="{Binding DisplayLabel}"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<CheckBox Grid.Column="2" Content="Als Aufsicht bestätigen" VerticalAlignment="Center"
|
||||
IsVisible="{Binding IsSupervisionCandidate}"
|
||||
IsChecked="{Binding ConfirmAsSupervision}"
|
||||
IsEnabled="{Binding CanResolve}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<TextBlock Text="Keine regulären Wochenmuster im Feed gefunden." Classes="emptyhint"
|
||||
IsVisible="{Binding HasNoRows}"/>
|
||||
|
||||
<StackPanel Spacing="4" IsVisible="{Binding HasUnmatchedSlots}">
|
||||
<Separator Margin="0,4"/>
|
||||
<TextBlock Text="Stundenplan-Einträge ohne passendes WebUntis-Muster:" FontSize="12" FontWeight="SemiBold"/>
|
||||
<ItemsControl ItemsSource="{Binding UnmatchedSlotLabels}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}" FontSize="12" Opacity="0.75" Margin="0,2"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<Grid Grid.Row="1" ColumnDefinitions="*,8,*" Margin="0,20,0,0">
|
||||
<Button Grid.Column="0" Content="Abbrechen" HorizontalAlignment="Stretch" Click="OnCancel"/>
|
||||
<Button Grid.Column="2" Content="Speichern" HorizontalAlignment="Stretch" Click="OnSave"
|
||||
IsEnabled="{Binding !IsLoading}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,27 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using LehrerApp.Desktop.ViewModels.Planning;
|
||||
|
||||
namespace LehrerApp.Desktop.Views.Planning;
|
||||
|
||||
public partial class UntisMappingReviewDialog : Window
|
||||
{
|
||||
public UntisMappingReviewDialog() => InitializeComponent();
|
||||
|
||||
protected override void OnDataContextChanged(EventArgs e)
|
||||
{
|
||||
base.OnDataContextChanged(e);
|
||||
if (DataContext is UntisMappingReviewDialogViewModel vm) _ = vm.LoadAsync();
|
||||
}
|
||||
|
||||
private void OnSave(object? s, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is UntisMappingReviewDialogViewModel vm && vm.SaveCommand.CanExecute(null))
|
||||
{
|
||||
vm.SaveCommand.Execute(null);
|
||||
Close(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCancel(object? s, RoutedEventArgs e) => Close(false);
|
||||
}
|
||||
@@ -921,6 +921,41 @@
|
||||
</ScrollViewer>
|
||||
</ContentPage>
|
||||
|
||||
<!-- Tab: WebUntis-iCal-Abgleich (Nutzer-Feedback) -->
|
||||
<ContentPage Header="Stundenplan-Abgleich">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="32,20,32,28" Spacing="14" MaxWidth="420">
|
||||
|
||||
<TextBlock Text="WebUntis-Stundenplan-Abgleich" FontSize="16" FontWeight="SemiBold"/>
|
||||
<TextBlock FontSize="12" Opacity="0.6" TextWrapping="Wrap"
|
||||
Text="Ruft periodisch den persönlichen iCal-Export von WebUntis ab, um Vertretungen, Ausfälle und Raumänderungen automatisch zu erkennen und in den Stundenplan zu übernehmen. Der Link enthält ein Zugangs-Token und wird verschlüsselt gespeichert."/>
|
||||
|
||||
<StackPanel Spacing="8" IsVisible="{Binding !UntisIsConfigured}">
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="iCal-URL" FontSize="12" Opacity="0.7"/>
|
||||
<TextBox Text="{Binding UntisIcalUrlInput}" PasswordChar="●"
|
||||
PlaceholderText="https://…/WebUntis/ical_export?..."/>
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding UntisUrlError}" Foreground="Red" FontSize="12"
|
||||
IsVisible="{Binding UntisUrlError, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
<Button Content="Speichern und aktivieren" Command="{Binding UntisSaveUrlCommand}" HorizontalAlignment="Left"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Spacing="8" IsVisible="{Binding UntisIsConfigured}">
|
||||
<TextBlock Text="iCal-URL hinterlegt (verschlüsselt gespeichert)." FontSize="13" FontWeight="SemiBold"/>
|
||||
<TextBlock Text="{Binding UntisStatusDisplay}" FontSize="12" Opacity="0.7"/>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Button Content="Jetzt abrufen" Command="{Binding UntisFetchNowCommand}"
|
||||
IsEnabled="{Binding !UntisFetchBusy}"/>
|
||||
<Button Content="Zuordnung prüfen…" Command="{Binding UntisReviewMappingCommand}"/>
|
||||
<Button Content="Entfernen" Command="{Binding UntisRemoveCommand}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</ContentPage>
|
||||
|
||||
<!-- Tab: Darstellung (12.4) -->
|
||||
<ContentPage Header="Darstellung">
|
||||
<ScrollViewer>
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Platform.Storage;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Desktop.Services;
|
||||
using LehrerApp.Desktop.ViewModels;
|
||||
using LehrerApp.Desktop.ViewModels.Planning;
|
||||
using LehrerApp.Desktop.ViewModels.Settings;
|
||||
using LehrerApp.Desktop.Views.Planning;
|
||||
using LehrerApp.Desktop.Views.Shared;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
@@ -26,9 +30,26 @@ public partial class SettingsView : UserControl
|
||||
vm.OnPickRecoveryFile = PickRecoveryFile;
|
||||
vm.OnConfirmRecoveryRestore = ShowRecoveryRestoreConfirmDialog;
|
||||
vm.OnThemeChanged = App.ApplyTheme;
|
||||
vm.OnReviewUntisMapping = ShowUntisMappingReviewDialog;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowUntisMappingReviewDialog()
|
||||
{
|
||||
var owner = TopLevel.GetTopLevel(this) as Window;
|
||||
var untisSync = App.Services.GetService<UntisSyncService>();
|
||||
if (owner is null || untisSync is null) return;
|
||||
|
||||
var groups = App.Services.GetRequiredService<IGroupRepository>().GetAll();
|
||||
var mappings = App.Services.GetRequiredService<LehrerApp.Core.Interfaces.IUntisSlotMappingRepository>();
|
||||
var subjects = App.Services.GetRequiredService<LehrerApp.Core.Interfaces.ISubjectRepository>();
|
||||
var dialog = new UntisMappingReviewDialog
|
||||
{
|
||||
DataContext = new UntisMappingReviewDialogViewModel(untisSync, mappings, groups, subjects),
|
||||
};
|
||||
await dialog.ShowDialog<bool>(owner);
|
||||
}
|
||||
|
||||
private async Task<bool> SaveRecoveryFile(string content)
|
||||
{
|
||||
var topLevel = TopLevel.GetTopLevel(this);
|
||||
|
||||
Reference in New Issue
Block a user