Vernetzung Stundenplan ↔ Unterrichtsplanung: Terminvorschlag, Lesson-Viewer-Sprung, Verzweigung (4.5.1–4.5.3)
4.5.1: Terminvorschlag für neue Stunden schlägt jetzt auch die Stundennummer
vor (bei Doppelstunden die erste Periode), nicht nur den Wochentag —
zieht über den bestehenden LessonNumber-Changed-Hook automatisch auch den
Stundenbeginn nach.
4.5.2 (teilweise): Klick auf eine Stunde im Stundenplan ("Heute"-Liste und
read-only Wochenraster) springt bei bereits vorhandener Lesson direkt in
den Verlaufsplan-Viewer statt nur grob zum Planung-Tab. Das Anlegen einer
neuen Lesson direkt aus dem Stundenplan bleibt bewusst offen (ungeklärte
Unit-Zuordnung, siehe TODO.md).
4.5.3: Der Lesson-Viewer hat jetzt Buttons "Zur Mitarbeit"/"Zu den Noten"
zum Weiterverzweigen in die Lerngruppe.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -470,6 +470,7 @@ public sealed class LessonDialogViewModelTests
|
||||
|
||||
// Nächster Dienstag nach dem 06.01.2099 (einem Dienstag) ist der 13.01.2099.
|
||||
Assert.Equal("13.01.2099", vm.DateText);
|
||||
Assert.Equal(3, vm.LessonNumber);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -489,5 +490,42 @@ public sealed class LessonDialogViewModelTests
|
||||
var suggested = DateOnly.ParseExact(vm.DateText, "dd.MM.yyyy");
|
||||
Assert.True(suggested >= DateOnly.FromDateTime(DateTime.Today));
|
||||
Assert.Equal(weekday, suggested.DayOfWeek);
|
||||
Assert.Equal(3, vm.LessonNumber);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NeueStunde_Doppelstunde_SchlaegtDieErstePeriodeAlsStundennummerVor()
|
||||
{
|
||||
var unitId = Guid.NewGuid();
|
||||
var groupId = Guid.NewGuid();
|
||||
var futureLessonDate = new DateOnly(2099, 1, 6); // Dienstag
|
||||
var lessons = new FakeLessons();
|
||||
lessons.Add(new Lesson { UnitId = unitId, GroupId = groupId, Date = futureLessonDate, LessonNumber = 3 });
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = groupId, Weekday = DayOfWeek.Tuesday, PeriodNumber = 4 });
|
||||
slots.Add(new TimetableSlot { GroupId = groupId, Weekday = DayOfWeek.Tuesday, PeriodNumber = 3 });
|
||||
|
||||
var vm = new LessonDialogViewModel(lessons, new FakeShorthandCodes([]), new FakeAlternativeLessonPaths([]),
|
||||
slots, NewPeriodSchedule(), unitId, groupId, "10c", "Chemie", [], [], null);
|
||||
|
||||
Assert.Equal(3, vm.LessonNumber); // frühere Periode, nicht 4
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NeueStunde_MitStundenvorschlag_UebernimmtBeginnAusStundenraster()
|
||||
{
|
||||
var unitId = Guid.NewGuid();
|
||||
var groupId = Guid.NewGuid();
|
||||
var weekday = DateTime.Today.DayOfWeek;
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = groupId, Weekday = weekday, PeriodNumber = 2 });
|
||||
var periodSchedule = NewPeriodSchedule();
|
||||
periodSchedule.SetPeriods([new PeriodTimeEntry { PeriodNumber = 2, Start = new TimeOnly(8, 50), End = new TimeOnly(9, 35) }]);
|
||||
|
||||
var vm = new LessonDialogViewModel(new FakeLessons(), new FakeShorthandCodes([]), new FakeAlternativeLessonPaths([]),
|
||||
slots, periodSchedule, unitId, groupId, "10c", "Chemie", [], [], null);
|
||||
|
||||
Assert.Equal(2, vm.LessonNumber);
|
||||
Assert.Equal("08:50", vm.StartTimeText);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,4 +97,34 @@ public sealed class LessonViewerViewModelTests
|
||||
Assert.Equal("ab 12:00", vm.PhaseGroups[0].Phases[0].TimeDisplay);
|
||||
Assert.Equal("ab 12:00", vm.PhaseGroups[1].Phases[0].TimeDisplay);
|
||||
}
|
||||
|
||||
// ── Verzweigung aus dem Viewer (4.5.3) ────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void NavigateToParticipation_RuftDelegatMitTabIndexDreiAuf()
|
||||
{
|
||||
var groupId = Guid.NewGuid();
|
||||
var lesson = new Lesson { GroupId = groupId, Topic = "Brechung", Date = new DateOnly(2025, 9, 1) };
|
||||
var vm = new LessonViewerViewModel(lesson, new FakeAlternativeLessonPaths([]));
|
||||
int? navigatedTab = null;
|
||||
vm.OnNavigateToTab = idx => navigatedTab = idx;
|
||||
|
||||
vm.NavigateToParticipationCommand.Execute(null);
|
||||
|
||||
Assert.Equal(groupId, vm.GroupId);
|
||||
Assert.Equal(3, navigatedTab);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NavigateToGrades_RuftDelegatMitTabIndexFuenfAuf()
|
||||
{
|
||||
var lesson = new Lesson { GroupId = Guid.NewGuid(), Topic = "Brechung", Date = new DateOnly(2025, 9, 1) };
|
||||
var vm = new LessonViewerViewModel(lesson, new FakeAlternativeLessonPaths([]));
|
||||
int? navigatedTab = null;
|
||||
vm.OnNavigateToTab = idx => navigatedTab = idx;
|
||||
|
||||
vm.NavigateToGradesCommand.Execute(null);
|
||||
|
||||
Assert.Equal(5, navigatedTab);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,6 +230,71 @@ public sealed class TimetableViewModelTests
|
||||
Assert.Equal(group.Id, navigatedTo);
|
||||
}
|
||||
|
||||
// ── Direktsprung in den Verlaufsplan-Viewer (4.5.2) ──────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task OpenTodayLesson_MitVorhandenerLesson_OeffnetDenViewerStattZurGruppeZuNavigieren()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var group = new LearningGroup { Name = "Q1 Chemie" };
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 });
|
||||
var lesson = new Lesson { GroupId = group.Id, Date = today, LessonNumber = 1, Topic = "Redox" };
|
||||
var lessons = new FakeLessons();
|
||||
lessons.Add(lesson);
|
||||
var vm = BuildViewModel(slots, new FakeGroups([group]), lessons: lessons);
|
||||
Lesson? openedLesson = null;
|
||||
Guid? navigatedTo = null;
|
||||
vm.OnOpenLessonViewer = l => { openedLesson = l; return Task.CompletedTask; };
|
||||
vm.OnNavigateToGroup = id => navigatedTo = id;
|
||||
|
||||
await vm.OpenTodayLessonCommand.ExecuteAsync(vm.TodayItems[0]);
|
||||
|
||||
Assert.Equal(lesson.Id, openedLesson?.Id);
|
||||
Assert.Null(navigatedTo);
|
||||
Assert.Equal("Verlaufsplan ansehen", vm.TodayItems[0].OpenButtonLabel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OpenTodayLesson_OhneVorhandeneLesson_NavigiertWieBisherZurGruppe()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var group = new LearningGroup { Name = "Q1 Chemie" };
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 });
|
||||
var vm = BuildViewModel(slots, new FakeGroups([group]));
|
||||
Guid? navigatedTo = null;
|
||||
var viewerOpened = false;
|
||||
vm.OnOpenLessonViewer = _ => { viewerOpened = true; return Task.CompletedTask; };
|
||||
vm.OnNavigateToGroup = id => navigatedTo = id;
|
||||
|
||||
await vm.OpenTodayLessonCommand.ExecuteAsync(vm.TodayItems[0]);
|
||||
|
||||
Assert.Equal(group.Id, navigatedTo);
|
||||
Assert.False(viewerOpened);
|
||||
Assert.Equal("Zur Lerngruppe", vm.TodayItems[0].OpenButtonLabel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OpenWeekCell_MitVorhandenerLesson_OeffnetDenViewer()
|
||||
{
|
||||
var group = new LearningGroup { Name = "Q1 Chemie" };
|
||||
var date = DateInCurrentWeek(DayOfWeek.Monday);
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
|
||||
var lesson = new Lesson { GroupId = group.Id, Date = date, LessonNumber = 1, Topic = "Redox" };
|
||||
var lessons = new FakeLessons();
|
||||
lessons.Add(lesson);
|
||||
var vm = BuildViewModel(slots, new FakeGroups([group]), lessons: lessons);
|
||||
Lesson? openedLesson = null;
|
||||
vm.OnOpenLessonViewer = l => { openedLesson = l; return Task.CompletedTask; };
|
||||
|
||||
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 1);
|
||||
await vm.OpenWeekCellCommand.ExecuteAsync(cell);
|
||||
|
||||
Assert.Equal(lesson.Id, openedLesson?.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OpenSettings_RuftOnNavigateToSettingsAuf()
|
||||
{
|
||||
|
||||
@@ -749,31 +749,39 @@ public partial class LessonDialogViewModel : ObservableObject
|
||||
}
|
||||
else
|
||||
{
|
||||
DateText = SuggestNextLessonDate().ToString("dd.MM.yyyy");
|
||||
var (date, lessonNumber) = SuggestNextLesson();
|
||||
DateText = date.ToString("dd.MM.yyyy");
|
||||
LessonNumber = lessonNumber;
|
||||
}
|
||||
RecomputeTimes();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Vorbelegung für eine neue Stunde: statt des sonst über den Feld-Default eingesetzten
|
||||
/// heutigen Datums (das an einem beliebigen Wochentag steht und die Doppelstunden-Erkennung
|
||||
/// in <see cref="RecomputeTimeBudget"/> stillschweigend auf eine Einzelperiode zurückfallen
|
||||
/// lässt, wenn der Wochentag nicht zufällig passt) der nächste laut Stundenplan (4.3) für
|
||||
/// diese Gruppe passende Wochentag — ab der letzten Stunde dieser Einheit, oder ab heute, wenn
|
||||
/// noch keine Stunde in dieser Einheit existiert oder die letzte in der Vergangenheit liegt.
|
||||
/// Ohne Stundenplan-Einträge für die Gruppe (z.B. Klassenrat) bleibt es beim heutigen Datum.
|
||||
/// Terminvorschlag für eine neue Stunde (4.5.1): statt des sonst über den Feld-Default
|
||||
/// eingesetzten heutigen Datums (das an einem beliebigen Wochentag steht und die
|
||||
/// Doppelstunden-Erkennung in <see cref="RecomputeTimeBudget"/> stillschweigend auf eine
|
||||
/// Einzelperiode zurückfallen lässt, wenn der Wochentag nicht zufällig passt) der nächste laut
|
||||
/// Stundenplan (4.3) für diese Gruppe passende Wochentag — ab der letzten Stunde dieser
|
||||
/// Einheit, oder ab heute, wenn noch keine Stunde in dieser Einheit existiert oder die letzte
|
||||
/// in der Vergangenheit liegt. Die Stundennummer wird passend dazu aus dem frühesten
|
||||
/// `TimetableSlot` dieses Wochentags vorbelegt (bei einer Doppelstunde also die erste Periode,
|
||||
/// dieselbe Ankerkonvention wie bei der Serienerzeugung, 4.2.5). Ohne Stundenplan-Einträge für
|
||||
/// die Gruppe (z.B. Klassenrat) bleibt es beim heutigen Datum ohne Stundenvorschlag.
|
||||
/// </summary>
|
||||
private DateOnly SuggestNextLessonDate()
|
||||
private (DateOnly Date, int? LessonNumber) SuggestNextLesson()
|
||||
{
|
||||
var weekdays = _timetableSlots.GetByGroup(_groupId).Select(s => s.Weekday).ToHashSet();
|
||||
var slots = _timetableSlots.GetByGroup(_groupId);
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
if (weekdays.Count == 0) return today;
|
||||
if (slots.Count == 0) return (today, null);
|
||||
|
||||
var weekdays = slots.Select(s => s.Weekday).ToHashSet();
|
||||
var lastLessonDate = _lessons.GetByUnit(_unitId).Select(l => l.Date)
|
||||
.DefaultIfEmpty(today.AddDays(-1)).Max();
|
||||
var candidate = lastLessonDate >= today ? lastLessonDate.AddDays(1) : today;
|
||||
while (!weekdays.Contains(candidate.DayOfWeek)) candidate = candidate.AddDays(1);
|
||||
return candidate;
|
||||
|
||||
var lessonNumber = slots.Where(s => s.Weekday == candidate.DayOfWeek).Min(s => (int?)s.PeriodNumber);
|
||||
return (candidate, lessonNumber);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -1432,10 +1440,11 @@ public record PhaseViewItem(string Name, int DurationMinutes, string TimeDisplay
|
||||
/// von Stundenbeginn an nimmt", nicht ab einer gemeinsamen Verzweigungsstelle im Hauptweg.
|
||||
public record PhaseGroupViewItem(string Label, bool IsMainPath, string? Description, List<PhaseViewItem> Phases);
|
||||
|
||||
public class LessonViewerViewModel
|
||||
public partial class LessonViewerViewModel : ObservableObject
|
||||
{
|
||||
private const string MainPathLabel = "Hauptweg";
|
||||
|
||||
public Guid GroupId { get; }
|
||||
public string DateDisplay { get; }
|
||||
public string Topic { get; }
|
||||
public string StatusLabel { get; }
|
||||
@@ -1445,8 +1454,16 @@ public class LessonViewerViewModel
|
||||
public List<PhaseGroupViewItem> PhaseGroups { get; }
|
||||
public bool HasAlternatives { get; }
|
||||
|
||||
/// Verzweigung aus dem Viewer heraus (4.5.3): Ziel-Tab-Index von GroupDetailView.axaml
|
||||
/// (3 Mitarbeit, 5 Noten) — das Code-Behind schließt den Viewer und navigiert dorthin.
|
||||
public Action<int>? OnNavigateToTab { get; set; }
|
||||
|
||||
[RelayCommand] private void NavigateToParticipation() => OnNavigateToTab?.Invoke(3);
|
||||
[RelayCommand] private void NavigateToGrades() => OnNavigateToTab?.Invoke(5);
|
||||
|
||||
public LessonViewerViewModel(Lesson lesson, IAlternativeLessonPathRepository alternativePaths)
|
||||
{
|
||||
GroupId = lesson.GroupId;
|
||||
DateDisplay = lesson.Date.ToString("dd.MM.yyyy");
|
||||
Topic = lesson.Topic;
|
||||
StatusLabel = LessonStatusDisplay.ToName(lesson.Status);
|
||||
|
||||
@@ -90,6 +90,7 @@ public partial class TimetableViewModel : ObservableObject
|
||||
public Action<Guid>? OnNavigateToGroup { get; set; }
|
||||
public Func<Task>? OnAddSubstitution { get; set; }
|
||||
public Action<SettingsTab>? OnNavigateToSettings { get; set; }
|
||||
public Func<Lesson, Task>? OnOpenLessonViewer { get; set; }
|
||||
|
||||
public TimetableViewModel(ITimetableSlotRepository slots, IGroupRepository groups,
|
||||
ISubjectRepository subjects, ILessonRepository lessons, IExamRepository exams,
|
||||
@@ -198,7 +199,7 @@ public partial class TimetableViewModel : ObservableObject
|
||||
var exam = _exams.GetByGroup(slot.GroupId).FirstOrDefault(e => e.Date == today);
|
||||
items.Add(new TodayLessonItem(slot.GroupId, slot.PeriodNumber, group.Name,
|
||||
slot.Room ?? "", ColorFor(group.Name), lesson?.Topic, exam?.Title,
|
||||
HasUnhandledHomework(slot.GroupId, today)));
|
||||
HasUnhandledHomework(slot.GroupId, today), lesson));
|
||||
}
|
||||
|
||||
foreach (var sub in substitutionsToday.Where(s => s.Kind == SubstitutionKind.Lesson &&
|
||||
@@ -245,6 +246,27 @@ public partial class TimetableViewModel : ObservableObject
|
||||
OnNavigateToGroup?.Invoke(groupId);
|
||||
}
|
||||
|
||||
/// Springt aus dem Stundenplan direkt in den Verlaufsplan-Viewer der zugehörigen Lesson (4.5.2)
|
||||
/// — sofern für den Slot schon eine Lesson existiert. Ohne Lesson (Slot laut Stundenplan belegt,
|
||||
/// aber noch keine konkrete Stunde geplant) bleibt es bei der bisherigen, gröberen Navigation
|
||||
/// zum Planung-Tab der Gruppe: eine neue Lesson direkt von hier aus anzulegen bräuchte eine
|
||||
/// Antwort auf "welcher Unit wird sie zugeordnet", die bewusst noch offen ist (siehe TODO 4.5.2).
|
||||
[RelayCommand]
|
||||
private async Task OpenTodayLesson(TodayLessonItem? item)
|
||||
{
|
||||
if (item is null || item.GroupId == Guid.Empty) return;
|
||||
if (item.Lesson is { } lesson && OnOpenLessonViewer is not null) await OnOpenLessonViewer(lesson);
|
||||
else OnNavigateToGroup?.Invoke(item.GroupId);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task OpenWeekCell(WeekCellItem? item)
|
||||
{
|
||||
if (item is null || item.GroupId == Guid.Empty) return;
|
||||
if (item.Lesson is { } lesson && OnOpenLessonViewer is not null) await OnOpenLessonViewer(lesson);
|
||||
else OnNavigateToGroup?.Invoke(item.GroupId);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenSettings() => OnNavigateToSettings?.Invoke(SettingsTab.Holidays);
|
||||
|
||||
@@ -333,7 +355,7 @@ public partial class TimetableViewModel : ObservableObject
|
||||
group?.Name ?? "?", slot.Room ?? "", lesson?.Topic ?? "",
|
||||
colorHex, holidayBadge, hasExam, isLastBeforeExam,
|
||||
MentionsExperiment(lesson), slot.GroupId, isHoliday,
|
||||
HasUnhandledHomework(slot.GroupId, date)));
|
||||
HasUnhandledHomework(slot.GroupId, date), lesson));
|
||||
}
|
||||
AddWeekSupervisionRowIfAny(period, dutiesByPeriod, substitutionsThisWeek, dateByWeekday);
|
||||
}
|
||||
@@ -652,6 +674,10 @@ public class WeekCellItem
|
||||
public bool HasSupervision => SupervisionLocation.Length > 0;
|
||||
public bool HasRoom => !string.IsNullOrWhiteSpace(Room);
|
||||
public bool HasTopic => !string.IsNullOrWhiteSpace(Topic);
|
||||
/// Nur bei einer regulären, zugewiesenen Zelle mit bereits existierender Lesson gesetzt —
|
||||
/// Grundlage für den Direktsprung in den Verlaufsplan-Viewer (4.5.2).
|
||||
public Lesson? Lesson { get; private init; }
|
||||
public string OpenButtonLabel => Lesson is not null ? "Verlaufsplan ansehen" : "Zur Lerngruppe";
|
||||
|
||||
public static WeekCellItem Corner() => new() { IsHeader = true };
|
||||
|
||||
@@ -686,13 +712,13 @@ public class WeekCellItem
|
||||
public static WeekCellItem ForSlot(DayOfWeek day, int period, bool isToday, string subjectLabel,
|
||||
string groupName, string room, string topic, string colorHex, string holidayBadge,
|
||||
bool hasExam, bool isLastBeforeExam, bool hasExperiment, Guid groupId, bool isHoliday,
|
||||
bool hasUnhandledHomework = false) => new()
|
||||
bool hasUnhandledHomework = false, Lesson? lesson = null) => new()
|
||||
{
|
||||
Weekday = day, PeriodNumber = period, IsAssigned = true, IsToday = isToday,
|
||||
SubjectLabel = subjectLabel, GroupName = groupName, Room = room, Topic = topic,
|
||||
ColorHex = colorHex, HolidayBadge = holidayBadge, HasExam = hasExam,
|
||||
IsLastBeforeExam = isLastBeforeExam, HasExperiment = hasExperiment, GroupId = groupId,
|
||||
IsHoliday = isHoliday, HasUnhandledHomework = hasUnhandledHomework,
|
||||
IsHoliday = isHoliday, HasUnhandledHomework = hasUnhandledHomework, Lesson = lesson,
|
||||
};
|
||||
|
||||
public static WeekCellItem ForSubstitutionLesson(DayOfWeek day, int period, bool isToday, SubstitutionEntry entry) => new()
|
||||
@@ -757,13 +783,18 @@ public class TodayLessonItem
|
||||
public bool HasExam => ExamTitle is not null;
|
||||
public bool HasGroupId => GroupId != Guid.Empty;
|
||||
public bool HasUnhandledHomework { get; private init; }
|
||||
/// Nur gesetzt, wenn für diesen Slot/Tag bereits eine Lesson existiert — Grundlage für den
|
||||
/// Direktsprung in den Verlaufsplan-Viewer (4.5.2).
|
||||
public Lesson? Lesson { get; private init; }
|
||||
public string OpenButtonLabel => Lesson is not null ? "Verlaufsplan ansehen" : "Zur Lerngruppe";
|
||||
|
||||
public TodayLessonItem(Guid groupId, int periodNumber, string groupName, string room,
|
||||
string colorHex, string? lessonTopic, string? examTitle, bool hasUnhandledHomework = false)
|
||||
string colorHex, string? lessonTopic, string? examTitle, bool hasUnhandledHomework = false,
|
||||
Lesson? lesson = null)
|
||||
{
|
||||
GroupId = groupId; PeriodNumber = periodNumber; GroupName = groupName; Room = room;
|
||||
ColorHex = colorHex; LessonTopic = lessonTopic; ExamTitle = examTitle;
|
||||
HasUnhandledHomework = hasUnhandledHomework;
|
||||
HasUnhandledHomework = hasUnhandledHomework; Lesson = lesson;
|
||||
}
|
||||
|
||||
public static TodayLessonItem ForSubstitution(int periodNumber, SubstitutionEntry entry) => new(
|
||||
|
||||
@@ -85,6 +85,13 @@
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<Button Grid.Row="2" Content="Schließen" HorizontalAlignment="Right" Margin="0,16,0,0" Click="OnClose"/>
|
||||
<Grid Grid.Row="2" ColumnDefinitions="*,Auto" Margin="0,16,0,0">
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="8">
|
||||
<Button Content="Zur Mitarbeit" Command="{Binding NavigateToParticipationCommand}"
|
||||
ToolTip.Tip="Springt zum Tab 'Mitarbeit' der Lerngruppe — dort auch Anwesenheit/Hausaufgaben."/>
|
||||
<Button Content="Zu den Noten" Command="{Binding NavigateToGradesCommand}"/>
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Content="Schließen" Click="OnClose"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using LehrerApp.Desktop.ViewModels;
|
||||
using LehrerApp.Desktop.ViewModels.Groups;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace LehrerApp.Desktop.Views.Groups;
|
||||
|
||||
@@ -7,5 +10,16 @@ public partial class LessonViewerDialog : Window
|
||||
{
|
||||
public LessonViewerDialog() => InitializeComponent();
|
||||
|
||||
protected override void OnDataContextChanged(EventArgs e)
|
||||
{
|
||||
base.OnDataContextChanged(e);
|
||||
if (DataContext is LessonViewerViewModel vm)
|
||||
vm.OnNavigateToTab = tabIndex =>
|
||||
{
|
||||
Close();
|
||||
App.Services.GetRequiredService<MainWindowViewModel>().NavigateToGroupDetail(vm.GroupId, tabIndex);
|
||||
};
|
||||
}
|
||||
|
||||
private void OnClose(object? s, RoutedEventArgs e) => Close();
|
||||
}
|
||||
|
||||
@@ -100,10 +100,10 @@
|
||||
Foreground="#8E6C00" FontWeight="SemiBold"
|
||||
IsVisible="{Binding HasUnhandledHomework}"/>
|
||||
</StackPanel>
|
||||
<Button Grid.Column="3" Content="Zur Lerngruppe" FontSize="11" Padding="9,4"
|
||||
<Button Grid.Column="3" Content="{Binding OpenButtonLabel}" FontSize="11" Padding="9,4"
|
||||
VerticalAlignment="Center" IsVisible="{Binding HasGroupId}"
|
||||
Command="{Binding $parent[ItemsControl].((vm:TimetableViewModel)DataContext).OpenGroupCommand}"
|
||||
CommandParameter="{Binding GroupId}"/>
|
||||
Command="{Binding $parent[ItemsControl].((vm:TimetableViewModel)DataContext).OpenTodayLessonCommand}"
|
||||
CommandParameter="{Binding}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
@@ -177,8 +177,8 @@
|
||||
<Button Background="{Binding ColorHex}" IsVisible="{Binding IsAssigned}"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch" CornerRadius="6" Padding="6"
|
||||
Command="{Binding $parent[ItemsControl;1].((vm:TimetableViewModel)DataContext).OpenGroupCommand}"
|
||||
CommandParameter="{Binding GroupId}">
|
||||
Command="{Binding $parent[ItemsControl;1].((vm:TimetableViewModel)DataContext).OpenWeekCellCommand}"
|
||||
CommandParameter="{Binding}">
|
||||
<StackPanel Spacing="1">
|
||||
<TextBlock FontSize="11" FontWeight="Bold" Foreground="White" TextWrapping="Wrap">
|
||||
<Run Text="{Binding SubjectLabel}"/><Run Text=" · "/><Run Text="{Binding GroupName}"/>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using Avalonia.Controls;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.ViewModels.Planning;
|
||||
using LehrerApp.Desktop.ViewModels.Groups;
|
||||
using LehrerApp.Desktop.Views.Groups;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace LehrerApp.Desktop.Views.Planning;
|
||||
@@ -17,9 +20,21 @@ public partial class TimetableView : UserControl
|
||||
{
|
||||
vm.OnEditSlot = ShowSlotDialog;
|
||||
vm.OnAddSubstitution = ShowSubstitutionDialog;
|
||||
vm.OnOpenLessonViewer = ShowLessonViewerDialog;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowLessonViewerDialog(Lesson lesson)
|
||||
{
|
||||
var owner = TopLevel.GetTopLevel(this) as Window;
|
||||
if (owner is null) return;
|
||||
|
||||
var viewerVm = new LessonViewerViewModel(lesson,
|
||||
App.Services.GetRequiredService<IAlternativeLessonPathRepository>());
|
||||
var dialog = new LessonViewerDialog { DataContext = viewerVm };
|
||||
await dialog.ShowDialog(owner);
|
||||
}
|
||||
|
||||
private async Task ShowSlotDialog(TimetableCellItem cell)
|
||||
{
|
||||
var owner = TopLevel.GetTopLevel(this) as Window;
|
||||
|
||||
@@ -733,23 +733,55 @@ war zudem redundant (das Bearbeiten-Tab ist direkt anklickbar) und fühlte sich
|
||||
("ggf. einem neuen Termine-Modell"). Kein Aufschub aus Unklarheit, sondern weil das über den
|
||||
angefragten Umfang hinausginge.
|
||||
|
||||
### 4.5 Vernetzung Stundenplan ↔ Unterrichtsplanung (zurückgestellt)
|
||||
### 4.5 Vernetzung Stundenplan ↔ Unterrichtsplanung (zurückgestellt, 4.5.1–4.5.3 auf Nutzerwunsch vorgezogen)
|
||||
|
||||
Nutzer-Feedback nach der 4.3-Iterationen: Stundenplan (4.3, wiederkehrendes Muster) und
|
||||
Unterrichtsplanung (4.1/4.2, `Unit`/`Lesson` je Gruppe) laufen bisher zu getrennt nebeneinander.
|
||||
Ausdrücklich als Idee für später festgehalten, **nicht** jetzt umsetzen:
|
||||
Ausdrücklich als Idee für später festgehalten, **nicht** jetzt umsetzen — mit Ausnahme von
|
||||
4.5.1–4.5.3, die auf ausdrücklichen Nutzerwunsch vorgezogen wurden (Rest von 4.5 bleibt
|
||||
zurückgestellt):
|
||||
|
||||
- [ ] **4.5.1** Terminvorschlag beim Anlegen einer `Lesson`: Tag/Stunde anhand des Stundenplans
|
||||
- [x] **4.5.1** Terminvorschlag beim Anlegen einer `Lesson`: Tag/Stunde anhand des Stundenplans
|
||||
vorschlagen, an dem die betroffene Gruppe laut `TimetableSlot` tatsächlich Unterricht hat,
|
||||
statt Datum/Stunde komplett frei einzutragen.
|
||||
statt Datum/Stunde komplett frei einzutragen. **Vorgefunden (Nebeneffekt der
|
||||
Doppelstunden-Erkennung, 4.2.2-Nachtrag):** `LessonDialogViewModel` schlug für eine neue
|
||||
Stunde bereits den nächsten passenden Wochentag vor (`SuggestNextLessonDate`) — nötig, damit
|
||||
die Zeitbudget-Doppelstunden-Erkennung nicht auf "heute zufällig kein Unterrichtstag" ins
|
||||
Leere läuft. Die Stundennummer selbst blieb dabei aber leer. **Ergänzt:** `SuggestNextLesson`
|
||||
schlägt jetzt zusätzlich die früheste `TimetableSlot`-Periode dieses Wochentags vor (bei
|
||||
einer Doppelstunde also die erste Periode, dieselbe Ankerkonvention wie bei 4.2.5) — über
|
||||
den bereits bestehenden `OnLessonNumberChanged`-Hook wird dadurch automatisch auch der
|
||||
Stundenbeginn aus dem Stundenraster übernommen, ganz ohne zusätzlichen Code.
|
||||
- [ ] **4.5.2** Vom Stundenplan (Wochenraster oder Bearbeiten-Raster) direkt in einen
|
||||
"Viewer" der zugehörigen `Lesson` springen können — und von dort auch eine neue `Lesson`
|
||||
anlegen können. **Offene Frage, vor Umsetzung zu klären:** `Lesson` hängt an einer
|
||||
übergeordneten `Unit` (4.1) — beim Anlegen direkt aus dem Stundenplan heraus ist unklar,
|
||||
welcher `Unit` die neue Lesson zugeordnet werden soll (aktuellste offene Einheit der Gruppe?
|
||||
Rückfrage an die Lehrkraft?). Muss überdacht werden, bevor das gebaut wird.
|
||||
- [ ] **4.5.3** Aus diesem Lesson-Viewer heraus weiter verzweigen können: in die Zeugnisnote/
|
||||
Rückfrage an die Lehrkraft?). Muss überdacht werden, bevor das gebaut wird — deshalb weiterhin
|
||||
unimplementiert, auch nach diesem Durchgang.
|
||||
|
||||
**Teilweise umgesetzt:** Der erste Teil ("direkt in den Viewer springen") ist jetzt fertig,
|
||||
sofern für den angeklickten Slot schon eine `Lesson` existiert — genau der unstrittige Teil
|
||||
ohne die offene Unit-Frage. Sowohl die "Heute"-Tagesliste als auch das schreibgeschützte
|
||||
Wochenraster im Stundenplan-Tab "Heute" (bewusst NICHT das Bearbeiten-Raster aus 4.3.2/4.3.3
|
||||
— dessen Klick öffnet weiterhin `TimetableSlotDialog` zur Slot-Zuweisung, das ist ein anderer,
|
||||
etablierter Zweck) öffnen bei vorhandener Lesson jetzt direkt `LessonViewerDialog` statt nur
|
||||
grob zum Planung-Tab der Gruppe zu springen (`TimetableViewModel.OnOpenLessonViewer`,
|
||||
`TodayLessonItem`/`WeekCellItem.Lesson`). Ohne vorhandene Lesson bleibt es unverändert bei der
|
||||
Navigation zum Planung-Tab — dort lässt sich über "+ Stunde" weiterhin manuell mit expliziter
|
||||
Unit-Auswahl eine neue Lesson anlegen.
|
||||
- [x] **4.5.3** Aus diesem Lesson-Viewer heraus weiter verzweigen können: in die Zeugnisnote/
|
||||
Bewertung der Gruppe, und in die Schnelldialoge für Mitarbeit sowie Anwesenheit/Hausaufgaben.
|
||||
**Umsetzung:** Zwei neue Buttons im Footer von `LessonViewerDialog` ("Zur Mitarbeit"/
|
||||
"Zu den Noten", `LessonViewerViewModel.NavigateToParticipationCommand`/`NavigateToGradesCommand`)
|
||||
schließen den Viewer und springen in den jeweiligen Tab der Gruppe (3 Mitarbeit, 5 Noten) —
|
||||
dieselbe `MainWindowViewModel`-Singleton-Navigation wie überall sonst im Code-Behind. Bewusst
|
||||
**kein** automatisches Öffnen der Schnelldialoge ("Schnell Mitarbeit"/"Anw.-HA") selbst: die
|
||||
brauchen eine ausgewählte Mitarbeitssitzung, die für das Datum dieser Lesson noch gar nicht
|
||||
existieren muss — automatisch eine anzulegen hätte dieselbe Geistereintrag-Falle wie der
|
||||
kürzlich behobene Sitzplan-Bug (14.12/7.1.5-Nachtrag) reproduziert. Im Mitarbeit-Tab
|
||||
angekommen ist "Sitzung erzeugen" (3.3.1, aus der Planung) bzw. der bestehende
|
||||
"+ Sitzung"-Button ohnehin nur einen Klick entfernt.
|
||||
- [x] **4.5.4** Badges am Stundenplan, die anzeigen, ob eine Hausaufgabe aus der letzten Stunde
|
||||
noch nicht kontrolliert ist.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user