Planung: Stunde einer anderen Einheit zuweisen ("Einheit wechseln")
Bisher war die Einheit einer Stunde nach dem Anlegen fix - bei falscher Auswahl im Erstellen-Dialog blieb nur Löschen+Neuanlegen. Neuer Button öffnet einen Dialog zur Auswahl der Ziel-Einheit; die Ansicht springt danach automatisch dorthin mit der Stunde vorausgewählt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -93,6 +93,30 @@ public class PlanningTabViewModelTests
|
|||||||
Assert.Equal(new DateOnly(2025, 9, 8), byUnit[l2.Id].Date); // unverändert
|
Assert.Equal(new DateOnly(2025, 9, 8), byUnit[l2.Id].Date); // unverändert
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ChangeLessonUnit_OrdnetStundeDerZieleinheitZuUndWaehltSieAus()
|
||||||
|
{
|
||||||
|
var (vm, units, lessons, groupId) = BuildScenario();
|
||||||
|
var unitA = new Unit { GroupId = groupId, Title = "Mechanik" };
|
||||||
|
var unitB = new Unit { GroupId = groupId, Title = "Optik" };
|
||||||
|
units.Add(unitA); units.Add(unitB);
|
||||||
|
var l1 = new Lesson { UnitId = unitA.Id, GroupId = groupId, Date = new DateOnly(2025, 9, 1), Status = LessonStatus.Planned, Topic = "1" };
|
||||||
|
lessons.Add(l1);
|
||||||
|
|
||||||
|
vm.Initialize(groupId);
|
||||||
|
vm.SelectedUnit = vm.Units.Single(u => u.Id == unitA.Id);
|
||||||
|
vm.SelectedLesson = vm.Lessons.Single(l => l.Id == l1.Id);
|
||||||
|
vm.OnPickUnitChange = _ => Task.FromResult<Unit?>(unitB);
|
||||||
|
|
||||||
|
await vm.ChangeLessonUnitCommand.ExecuteAsync(null);
|
||||||
|
|
||||||
|
Assert.Empty(lessons.GetByUnit(unitA.Id));
|
||||||
|
var moved = Assert.Single(lessons.GetByUnit(unitB.Id));
|
||||||
|
Assert.Equal(l1.Id, moved.Id);
|
||||||
|
Assert.Equal(unitB.Id, vm.SelectedUnit?.Id);
|
||||||
|
Assert.Equal(l1.Id, vm.SelectedLesson?.Id);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CopyUnit_ErhaeltAbstaendeUndSetztGroupIdAufZielgruppe()
|
public async Task CopyUnit_ErhaeltAbstaendeUndSetztGroupIdAufZielgruppe()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using LehrerApp.Core.Interfaces;
|
|||||||
using LehrerApp.Core.Models;
|
using LehrerApp.Core.Models;
|
||||||
using LehrerApp.Core.Services;
|
using LehrerApp.Core.Services;
|
||||||
using LehrerApp.Desktop.Services;
|
using LehrerApp.Desktop.Services;
|
||||||
|
using LehrerApp.Desktop.ViewModels.Planning;
|
||||||
using LehrerApp.Desktop.ViewModels.Students;
|
using LehrerApp.Desktop.ViewModels.Students;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@@ -75,6 +76,7 @@ public partial class PlanningTabViewModel : ObservableObject
|
|||||||
public Func<Lesson, List<string>, List<string>, Task<bool>>? OnEditLesson { get; set; }
|
public Func<Lesson, List<string>, List<string>, Task<bool>>? OnEditLesson { get; set; }
|
||||||
public Func<LessonSummary, Task<bool>>? OnConfirmDeleteLesson { get; set; }
|
public Func<LessonSummary, Task<bool>>? OnConfirmDeleteLesson { get; set; }
|
||||||
public Func<Lesson, Task<MoveLessonTarget?>>? OnPickMoveTarget { get; set; }
|
public Func<Lesson, Task<MoveLessonTarget?>>? OnPickMoveTarget { get; set; }
|
||||||
|
public Func<Lesson, Task<Unit?>>? OnPickUnitChange { get; set; }
|
||||||
public Func<Lesson, Task>? OnShowLesson { get; set; }
|
public Func<Lesson, Task>? OnShowLesson { get; set; }
|
||||||
public Func<Unit, Task<LessonSeriesResult?>>? OnGenerateLessonSeries { get; set; }
|
public Func<Unit, Task<LessonSeriesResult?>>? OnGenerateLessonSeries { get; set; }
|
||||||
public Func<Unit, Task<bool>>? OnAiAssist { get; set; }
|
public Func<Unit, Task<bool>>? OnAiAssist { get; set; }
|
||||||
@@ -160,6 +162,7 @@ public partial class PlanningTabViewModel : ObservableObject
|
|||||||
EditLessonCommand.NotifyCanExecuteChanged();
|
EditLessonCommand.NotifyCanExecuteChanged();
|
||||||
DeleteLessonCommand.NotifyCanExecuteChanged();
|
DeleteLessonCommand.NotifyCanExecuteChanged();
|
||||||
MoveLessonCommand.NotifyCanExecuteChanged();
|
MoveLessonCommand.NotifyCanExecuteChanged();
|
||||||
|
ChangeLessonUnitCommand.NotifyCanExecuteChanged();
|
||||||
AdvanceLessonStatusCommand.NotifyCanExecuteChanged();
|
AdvanceLessonStatusCommand.NotifyCanExecuteChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,6 +352,23 @@ public partial class PlanningTabViewModel : ObservableObject
|
|||||||
new LessonSchedulingService(_lessons).Move(moved, newDate, newPeriod, shiftFollowing);
|
new LessonSchedulingService(_lessons).Move(moved, newDate, newPeriod, shiftFollowing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ordnet eine bereits angelegte Stunde einer anderen Einheit derselben Gruppe zu (Korrektur
|
||||||
|
/// einer im Erstellen-Dialog falsch gewählten Einheit, Nutzer-Feedback) — bisher war das nur
|
||||||
|
/// über Löschen + Neuanlegen möglich. Datum/Stundennummer bleiben unverändert, nur UnitId ändert
|
||||||
|
/// sich. Nach dem Speichern springt die Ansicht direkt zur Ziel-Einheit mit der Stunde
|
||||||
|
/// vorausgewählt, damit sichtbar bleibt, wohin sie verschoben wurde.
|
||||||
|
[RelayCommand(CanExecute = nameof(HasSelectedLesson))]
|
||||||
|
private async Task ChangeLessonUnit()
|
||||||
|
{
|
||||||
|
if (OnPickUnitChange is null || SelectedLesson is null) return;
|
||||||
|
var lesson = SelectedLesson.Model;
|
||||||
|
var targetUnit = await OnPickUnitChange(lesson);
|
||||||
|
if (targetUnit is null) return;
|
||||||
|
lesson.UnitId = targetUnit.Id;
|
||||||
|
_lessons.Save(lesson);
|
||||||
|
RefreshPlanning(selectUnitId: targetUnit.Id, selectLessonId: lesson.Id);
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand(CanExecute = nameof(HasSelectedLesson))]
|
[RelayCommand(CanExecute = nameof(HasSelectedLesson))]
|
||||||
private void AdvanceLessonStatus()
|
private void AdvanceLessonStatus()
|
||||||
{
|
{
|
||||||
@@ -1206,6 +1226,38 @@ public partial class MoveLessonDialogViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Dialog: Stunde einer anderen Einheit zuweisen (Korrektur der Einheit) ────
|
||||||
|
|
||||||
|
public partial class ChangeLessonUnitDialogViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
[ObservableProperty] private TimetableUnitOption? _selectedUnit;
|
||||||
|
[ObservableProperty] private string _error = "";
|
||||||
|
|
||||||
|
public string CurrentUnitLabel { get; }
|
||||||
|
public ObservableCollection<TimetableUnitOption> Units { get; } = [];
|
||||||
|
public bool HasUnits => Units.Count > 0;
|
||||||
|
public Unit? Result { get; private set; }
|
||||||
|
|
||||||
|
public ChangeLessonUnitDialogViewModel(IUnitRepository units, Guid groupId, Guid currentUnitId,
|
||||||
|
string currentUnitTitle)
|
||||||
|
{
|
||||||
|
CurrentUnitLabel = currentUnitTitle;
|
||||||
|
foreach (var unit in units.GetByGroup(groupId).Where(u => u.Id != currentUnitId)
|
||||||
|
.OrderBy(u => u.Status == UnitStatus.Active ? 0 : u.Status == UnitStatus.Planned ? 1 : 2)
|
||||||
|
.ThenByDescending(u => u.StartDate)
|
||||||
|
.ThenBy(u => u.Title, StringComparer.CurrentCultureIgnoreCase))
|
||||||
|
Units.Add(new TimetableUnitOption(unit));
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void Save()
|
||||||
|
{
|
||||||
|
Error = "";
|
||||||
|
if (SelectedUnit is null) { Error = "Bitte eine Ziel-Einheit auswählen."; return; }
|
||||||
|
Result = SelectedUnit.Model;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Dialog: Stunden serienweise aus dem Stundenplan erzeugen (4.2.5) ────────
|
// ── Dialog: Stunden serienweise aus dem Stundenplan erzeugen (4.2.5) ────────
|
||||||
|
|
||||||
public partial class GenerateLessonSeriesDialogViewModel : ObservableObject
|
public partial class GenerateLessonSeriesDialogViewModel : ObservableObject
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="clr-namespace:LehrerApp.Desktop.ViewModels.Groups"
|
||||||
|
xmlns:vmp="clr-namespace:LehrerApp.Desktop.ViewModels.Planning"
|
||||||
|
x:Class="LehrerApp.Desktop.Views.Groups.ChangeLessonUnitDialog"
|
||||||
|
x:DataType="vm:ChangeLessonUnitDialogViewModel"
|
||||||
|
Title="Einheit wechseln"
|
||||||
|
Width="440" Height="360" MinWidth="400" MinHeight="320"
|
||||||
|
CanResize="False" WindowStartupLocation="CenterOwner">
|
||||||
|
|
||||||
|
<Grid RowDefinitions="*,Auto" Margin="24">
|
||||||
|
<StackPanel Grid.Row="0" Spacing="14">
|
||||||
|
<TextBlock Text="Einheit wechseln" Classes="dialogtitle"/>
|
||||||
|
<TextBlock FontSize="12" Opacity="0.6">
|
||||||
|
<Run Text="Bisherige Einheit: "/>
|
||||||
|
<Run Text="{Binding CurrentUnitLabel}"/>
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<StackPanel Spacing="5" IsVisible="{Binding HasUnits}">
|
||||||
|
<TextBlock Text="Neue Einheit" FontSize="12" Opacity="0.7"/>
|
||||||
|
<ComboBox ItemsSource="{Binding Units}" SelectedItem="{Binding SelectedUnit}"
|
||||||
|
HorizontalAlignment="Stretch">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="vmp:TimetableUnitOption">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="{Binding Label}" FontWeight="SemiBold"/>
|
||||||
|
<TextBlock Text="{Binding Detail}" FontSize="11" Opacity="0.6"/>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<TextBlock Text="Keine andere Einheit in dieser Gruppe vorhanden."
|
||||||
|
FontSize="12" Opacity="0.7" TextWrapping="Wrap"
|
||||||
|
IsVisible="{Binding !HasUnits}"/>
|
||||||
|
|
||||||
|
<TextBlock Text="{Binding Error}" Foreground="Red" FontSize="11"
|
||||||
|
IsVisible="{Binding Error, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<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="Verschieben" HorizontalAlignment="Stretch" Click="OnSave"
|
||||||
|
IsEnabled="{Binding HasUnits}"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using LehrerApp.Desktop.ViewModels.Groups;
|
||||||
|
|
||||||
|
namespace LehrerApp.Desktop.Views.Groups;
|
||||||
|
|
||||||
|
public partial class ChangeLessonUnitDialog : Window
|
||||||
|
{
|
||||||
|
public ChangeLessonUnitDialog() => InitializeComponent();
|
||||||
|
|
||||||
|
private void OnSave(object? s, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is ChangeLessonUnitDialogViewModel vm && vm.SaveCommand.CanExecute(null))
|
||||||
|
{
|
||||||
|
vm.SaveCommand.Execute(null);
|
||||||
|
if (vm.Result is not null) Close(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCancel(object? s, RoutedEventArgs e) => Close(false);
|
||||||
|
}
|
||||||
@@ -91,6 +91,8 @@
|
|||||||
ToolTip.Tip="Verlaufsplan schreibgeschützt und größer anzeigen — zum Mitnehmen in den Unterricht."/>
|
ToolTip.Tip="Verlaufsplan schreibgeschützt und größer anzeigen — zum Mitnehmen in den Unterricht."/>
|
||||||
<Button Content="Bearbeiten" Command="{Binding EditLessonCommand}" IsEnabled="{Binding !IsReadOnly}"/>
|
<Button Content="Bearbeiten" Command="{Binding EditLessonCommand}" IsEnabled="{Binding !IsReadOnly}"/>
|
||||||
<Button Content="Verschieben" Command="{Binding MoveLessonCommand}" IsEnabled="{Binding !IsReadOnly}"/>
|
<Button Content="Verschieben" Command="{Binding MoveLessonCommand}" IsEnabled="{Binding !IsReadOnly}"/>
|
||||||
|
<Button Content="Einheit wechseln" Command="{Binding ChangeLessonUnitCommand}" IsEnabled="{Binding !IsReadOnly}"
|
||||||
|
ToolTip.Tip="Ordnet die ausgewählte Stunde einer anderen Einheit derselben Gruppe zu."/>
|
||||||
<Button Content="Status weiter" Command="{Binding AdvanceLessonStatusCommand}" IsEnabled="{Binding !IsReadOnly}"
|
<Button Content="Status weiter" Command="{Binding AdvanceLessonStatusCommand}" IsEnabled="{Binding !IsReadOnly}"
|
||||||
ToolTip.Tip="Entwurf/Geplant → Bereit → Durchgeführt"/>
|
ToolTip.Tip="Entwurf/Geplant → Bereit → Durchgeführt"/>
|
||||||
<Button Content="Sitzung erzeugen" Command="{Binding CreateParticipationSessionCommand}"
|
<Button Content="Sitzung erzeugen" Command="{Binding CreateParticipationSessionCommand}"
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ public partial class PlanningTabView : UserControl
|
|||||||
ShowLessonDialog(lesson.UnitId, lesson.GroupId, materials, shorthands, editingLesson: lesson);
|
ShowLessonDialog(lesson.UnitId, lesson.GroupId, materials, shorthands, editingLesson: lesson);
|
||||||
vm.OnConfirmDeleteLesson = ShowDeleteLessonDialog;
|
vm.OnConfirmDeleteLesson = ShowDeleteLessonDialog;
|
||||||
vm.OnPickMoveTarget = ShowMoveLessonDialog;
|
vm.OnPickMoveTarget = ShowMoveLessonDialog;
|
||||||
|
vm.OnPickUnitChange = ShowChangeLessonUnitDialog;
|
||||||
vm.OnShowLesson = ShowLessonViewerDialog;
|
vm.OnShowLesson = ShowLessonViewerDialog;
|
||||||
vm.OnGenerateLessonSeries = ShowGenerateLessonSeriesDialog;
|
vm.OnGenerateLessonSeries = ShowGenerateLessonSeriesDialog;
|
||||||
vm.OnAiAssist = ShowAiAssistDialog;
|
vm.OnAiAssist = ShowAiAssistDialog;
|
||||||
@@ -136,6 +137,20 @@ public partial class PlanningTabView : UserControl
|
|||||||
return ok ? dialogVm.Result : null;
|
return ok ? dialogVm.Result : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<Unit?> ShowChangeLessonUnitDialog(Lesson lesson)
|
||||||
|
{
|
||||||
|
var dialogVm = new ChangeLessonUnitDialogViewModel(
|
||||||
|
App.Services.GetRequiredService<IUnitRepository>(),
|
||||||
|
lesson.GroupId, lesson.UnitId,
|
||||||
|
(DataContext as PlanningTabViewModel)?.SelectedUnit?.Title ?? "");
|
||||||
|
var dialog = new ChangeLessonUnitDialog { DataContext = dialogVm };
|
||||||
|
var owner = TopLevel.GetTopLevel(this) as Window;
|
||||||
|
if (owner is null) return null;
|
||||||
|
|
||||||
|
var ok = await dialog.ShowDialog<bool>(owner);
|
||||||
|
return ok ? dialogVm.Result : null;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ShowLessonViewerDialog(Lesson lesson)
|
private async Task ShowLessonViewerDialog(Lesson lesson)
|
||||||
{
|
{
|
||||||
var viewerVm = new LessonViewerViewModel(lesson,
|
var viewerVm = new LessonViewerViewModel(lesson,
|
||||||
|
|||||||
Reference in New Issue
Block a user