feat: Pädagogische Klassen-Aufgaben, Prioritäten/Checklisten, Dashboard-Schnellzugriffe

Neue Aufgabenart über bestehende Felder (WorkTask.Kind=Reminder + GroupId) statt Vererbung,
mit Priorität (TaskPriority) und optionaler Abhak-Liste (ChecklistItems, wahlweise Kurs-
Roster oder Freitext). Kurs-Dashboard zeigt jetzt eine "Anstehende Aufgaben"-Karte samt
direktem Anlege-Button; derselbe Anlege-Einstieg wurde auch ins Hauptdashboard und (als
Schnellüberblick samt fehlender Übersicht/Mitarbeit-Buttons) in die Gruppenliste gezogen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-21 22:24:38 +02:00
co-authored by Claude Sonnet 5
parent e7c3fdd4ec
commit e65a729f97
25 changed files with 878 additions and 39 deletions
@@ -57,17 +57,51 @@
</StackPanel>
</Grid>
<StackPanel Spacing="4">
<TextBlock Text="Wiederholung" FontSize="12" Opacity="0.7"
ToolTip.Tip="Beim Abschließen wird automatisch die nächste Instanz mit neuem Fälligkeitsdatum angelegt. Braucht ein Fälligkeitsdatum als Ausgangspunkt."/>
<ComboBox ItemsSource="{Binding RecurrenceOptions}" SelectedItem="{Binding SelectedRecurrence}"
HorizontalAlignment="Stretch"/>
</StackPanel>
<Grid ColumnDefinitions="*,8,*">
<StackPanel Grid.Column="0" Spacing="4">
<TextBlock Text="Wiederholung" FontSize="12" Opacity="0.7"
ToolTip.Tip="Beim Abschließen wird automatisch die nächste Instanz mit neuem Fälligkeitsdatum angelegt. Braucht ein Fälligkeitsdatum als Ausgangspunkt."/>
<ComboBox ItemsSource="{Binding RecurrenceOptions}" SelectedItem="{Binding SelectedRecurrence}"
HorizontalAlignment="Stretch"/>
</StackPanel>
<StackPanel Grid.Column="2" Spacing="4">
<TextBlock Text="Priorität" FontSize="12" Opacity="0.7"/>
<ComboBox ItemsSource="{Binding PriorityOptions}" SelectedItem="{Binding SelectedPriority}"
HorizontalAlignment="Stretch"/>
</StackPanel>
</Grid>
<StackPanel Spacing="4">
<TextBlock Text="Notizen (optional)" FontSize="12" Opacity="0.7"/>
<TextBox Text="{Binding Notes}" AcceptsReturn="True" TextWrapping="Wrap" Height="80"/>
</StackPanel>
<StackPanel Spacing="4">
<Grid ColumnDefinitions="*,Auto">
<TextBlock Grid.Column="0" Text="Abhak-Liste (optional)" FontSize="12" Opacity="0.7"
VerticalAlignment="Center"/>
<Button Grid.Column="1" Content="Aus Kursliste befüllen" FontSize="11" Padding="7,3"
Command="{Binding FillFromRosterCommand}" IsVisible="{Binding CanFillFromRoster}"/>
</Grid>
<ItemsControl ItemsSource="{Binding ChecklistItems}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:ChecklistItemRow">
<Grid ColumnDefinitions="Auto,*,Auto" Margin="0,2">
<CheckBox Grid.Column="0" IsChecked="{Binding IsDone}"/>
<TextBlock Grid.Column="1" Text="{Binding Label}" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"/>
<Button Grid.Column="2" Content="✕" FontSize="11" Padding="6,2"
Command="{Binding $parent[ItemsControl].((vm:AddEditWorkTaskDialogViewModel)DataContext).RemoveChecklistItemCommand}"
CommandParameter="{Binding}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Grid ColumnDefinitions="*,8,Auto">
<TextBox Grid.Column="0" Text="{Binding NewChecklistItemText}" PlaceholderText="Eigener Punkt..."/>
<Button Grid.Column="2" Content="+" Command="{Binding AddChecklistItemCommand}"/>
</Grid>
</StackPanel>
</StackPanel>
</ScrollViewer>
@@ -0,0 +1,41 @@
using Avalonia.Controls;
using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Models;
using LehrerApp.Core.Services;
using LehrerApp.Desktop.ViewModels.Workload;
using Microsoft.Extensions.DependencyInjection;
namespace LehrerApp.Desktop.Views.Workload;
/// Gemeinsamer Aufruf des Aufgaben-Dialogs für alle drei Entry-Points (Aufgabenliste,
/// Kurs-Dashboard, Hauptdashboard) — vermeidet drei fast identische Kopien der Gruppen-/
/// Kursliste-Ladelogik.
public static class WorkTaskDialogHelper
{
public static async Task<WorkTask?> ShowDialog(Window owner, WorkTask? source = null,
bool startAsReminder = false, Guid? preselectedGroupId = null)
{
var groups = App.Services.GetRequiredService<IGroupRepository>().GetAll(includeInactive: true);
var vm = new AddEditWorkTaskDialogViewModel(source, groups, startAsReminder, LoadActiveRoster);
if (source is null && preselectedGroupId is { } groupId)
vm.SelectedGroup = groups.FirstOrDefault(g => g.Id == groupId);
var dialog = new AddEditWorkTaskDialog { DataContext = vm };
await dialog.ShowDialog<bool>(owner);
return vm.Result;
}
// "Aus Kursliste befüllen" (Nutzer-Feedback): nur aktuell aktive Mitglieder, wie überall sonst
// im Dashboard/Übersicht-Tab (GroupMembershipService.IsActiveOn).
private static List<(Guid StudentId, string Name)> LoadActiveRoster(Guid groupId)
{
var memberships = App.Services.GetRequiredService<IGroupMembershipRepository>();
var students = App.Services.GetRequiredService<IStudentRepository>();
var today = DateOnly.FromDateTime(DateTime.Today);
return memberships.GetByGroup(groupId)
.Where(m => GroupMembershipService.IsActiveOn(m, today))
.Select(m => students.GetById(m.StudentId))
.Where(s => s is not null)
.Select(s => (s!.Id, s.FullName))
.ToList();
}
}
@@ -25,15 +25,18 @@
<DataTemplate x:DataType="vm:WorkTaskListItem">
<Border Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
CornerRadius="6" Padding="12,10" Margin="0,0,0,8">
<Grid ColumnDefinitions="Auto,*,Auto,Auto,Auto,Auto">
<Button Grid.Column="0" Background="{Binding StatusColorHex}" Padding="8,4"
<Grid ColumnDefinitions="4,Auto,*,Auto,Auto,Auto,Auto">
<Border Grid.Column="0" Background="{Binding PriorityColorHex}" CornerRadius="2"
Margin="0,0,8,0" IsVisible="{Binding ShowPriority}"
ToolTip.Tip="{Binding PriorityLabel}"/>
<Button Grid.Column="1" Background="{Binding StatusColorHex}" Padding="8,4"
CornerRadius="4" VerticalAlignment="Center"
Command="{Binding $parent[ItemsControl].((vm:WorkTaskListViewModel)DataContext).CycleStatusCommand}"
CommandParameter="{Binding}"
ToolTip.Tip="Klicken, um den Status zu wechseln">
<TextBlock Text="{Binding StatusLabel}" FontSize="12" Foreground="White"/>
</Button>
<StackPanel Grid.Column="1" Margin="10,0" VerticalAlignment="Center">
<StackPanel Grid.Column="2" Margin="10,0" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBlock Text="🔔" FontSize="12" IsVisible="{Binding IsReminder}"
ToolTip.Tip="Erinnerung — kein Zeitbezug, nicht Teil der Auswertung"/>
@@ -50,16 +53,19 @@
<!-- Ist-Zeit vs. Schätzung (6.2.4) -->
<TextBlock Text="{Binding ActualVsEstimateDisplay}" FontSize="11" Opacity="0.6"
IsVisible="{Binding HasActualTime}"/>
<!-- Abhak-Liste -->
<TextBlock Text="{Binding ChecklistProgressDisplay}" FontSize="11" Opacity="0.6"
IsVisible="{Binding HasChecklist}"/>
</StackPanel>
<TextBlock Grid.Column="2" Text="{Binding DueDateDisplay}" VerticalAlignment="Center"
<TextBlock Grid.Column="3" Text="{Binding DueDateDisplay}" VerticalAlignment="Center"
Margin="0,0,10,0" FontSize="12" Foreground="{Binding DueDateColorHex}"/>
<TextBlock Grid.Column="3" Text="{Binding EstimatedMinutesDisplay}"
<TextBlock Grid.Column="4" Text="{Binding EstimatedMinutesDisplay}"
VerticalAlignment="Center" Opacity="0.6" FontSize="12" Margin="0,0,10,0"
IsVisible="{Binding !HasActualTime}"/>
<Button Grid.Column="4" Content="Bearbeiten" FontSize="11" Padding="8,3" Margin="0,0,4,0"
<Button Grid.Column="5" Content="Bearbeiten" FontSize="11" Padding="8,3" Margin="0,0,4,0"
Command="{Binding $parent[ItemsControl].((vm:WorkTaskListViewModel)DataContext).EditTaskCommand}"
CommandParameter="{Binding}"/>
<Button Grid.Column="5" Content="Löschen" FontSize="11" Padding="8,3"
<Button Grid.Column="6" Content="Löschen" FontSize="11" Padding="8,3"
Command="{Binding $parent[ItemsControl].((vm:WorkTaskListViewModel)DataContext).DeleteTaskCommand}"
CommandParameter="{Binding}"/>
</Grid>
@@ -1,8 +1,6 @@
using Avalonia.Controls;
using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Models;
using LehrerApp.Desktop.ViewModels.Workload;
using Microsoft.Extensions.DependencyInjection;
namespace LehrerApp.Desktop.Views.Workload;
@@ -21,11 +19,6 @@ public partial class WorkTaskListView : UserControl
{
var owner = TopLevel.GetTopLevel(this) as Window;
if (owner is null) return null;
var groups = App.Services.GetRequiredService<IGroupRepository>().GetAll(includeInactive: true);
var vm = new AddEditWorkTaskDialogViewModel(source, groups, startAsReminder);
var dialog = new AddEditWorkTaskDialog { DataContext = vm };
await dialog.ShowDialog<bool>(owner);
return vm.Result;
return await WorkTaskDialogHelper.ShowDialog(owner, source, startAsReminder);
}
}