feat: expand teaching mode with live timeline and seating quick checks
CI / build-and-test (push) Canceled after 0s
CI / build-and-test (push) Canceled after 0s
This commit is contained in:
@@ -55,6 +55,7 @@ public class Lesson : IHasAttachments
|
||||
/// gepflegt. Dient nur der abgeleiteten Uhrzeit-Anzeige je Phase in <see cref="Phases"/>.
|
||||
public TimeOnly? StartTime { get; set; }
|
||||
public List<LessonPhaseStep> Phases { get; set; } = [];
|
||||
public TeachingTimelineState? TeachingTimeline { get; set; }
|
||||
public string? Homework { get; set; }
|
||||
/// Markiert, dass die hier eingetragene Hausaufgabe in einer Folgestunde besprochen/kontrolliert
|
||||
/// wurde — treibt das Stundenplan-Badge "Hausaufgabe kontrollieren" (4.5.4).
|
||||
@@ -244,3 +245,21 @@ public class ReportGrade
|
||||
public bool IsLocked { get; set; }
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>Live timing is separate from the original lesson plan.</summary>
|
||||
public class TeachingTimelineState
|
||||
{
|
||||
public DateTime StartUtc { get; set; }
|
||||
public DateTime EndUtc { get; set; }
|
||||
public DateTime? HeldSinceUtc { get; set; }
|
||||
public Guid? HeldPhaseId { get; set; }
|
||||
public List<TeachingPhaseTiming> Phases { get; set; } = [];
|
||||
public List<Guid> TransferredPhaseIds { get; set; } = [];
|
||||
}
|
||||
|
||||
public class TeachingPhaseTiming
|
||||
{
|
||||
public Guid PhaseId { get; set; }
|
||||
public double Minutes { get; set; }
|
||||
public bool ExplicitlyStarted { get; set; }
|
||||
}
|
||||
|
||||
@@ -10,6 +10,33 @@ namespace LehrerApp.Desktop.Tests;
|
||||
/// zusammen, was Planung/Klausuren/Mitarbeit/Dokumentation ohnehin schon verwalten.
|
||||
public sealed class GroupOverviewViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void UnterrichtHeute_BietetNurHeutigeNichtAusgefalleneStundenUndOeffnetAuswahl()
|
||||
{
|
||||
var group = new LearningGroup { IsActive = true };
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var lessons = new FakeLessons();
|
||||
var first = new Lesson { GroupId = group.Id, Date = today, Topic = "Erste Stunde" };
|
||||
var second = new Lesson { GroupId = group.Id, Date = today, Topic = "Zweite Stunde" };
|
||||
lessons.Add(first);
|
||||
lessons.Add(second);
|
||||
lessons.Add(new Lesson { GroupId = group.Id, Date = today, Status = LessonStatus.Cancelled });
|
||||
lessons.Add(new Lesson { GroupId = group.Id, Date = today.AddDays(1) });
|
||||
lessons.Add(new Lesson { GroupId = Guid.NewGuid(), Date = today });
|
||||
var vm = NewVm(lessons: lessons, groups: new FakeGroups([group]));
|
||||
vm.Initialize(group.Id, group.Name);
|
||||
Assert.True(vm.HasTodayLessons);
|
||||
Assert.Equal(2, vm.TodayLessons.Count);
|
||||
Lesson? opened = null;
|
||||
vm.OnOpenTeachingMode = lesson => opened = lesson;
|
||||
vm.SelectedTeachingLesson = second;
|
||||
vm.StartTeachingModeCommand.Execute(null);
|
||||
Assert.Same(second, opened);
|
||||
group.IsActive = false;
|
||||
vm.Refresh();
|
||||
Assert.False(vm.HasTodayLessons);
|
||||
}
|
||||
|
||||
private static GroupOverviewViewModel NewVm(FakeLessons? lessons = null, FakeExams? exams = null,
|
||||
FakeSessions? sessions = null, FakeEntries? entries = null, FakeStudents? students = null,
|
||||
FakeDocumentation? documentation = null, FakeWorkTasks? tasks = null,
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Desktop.ViewModels.Groups;
|
||||
using Xunit;
|
||||
|
||||
namespace LehrerApp.Desktop.Tests;
|
||||
|
||||
public sealed class SeatingQuickCheckTests
|
||||
{
|
||||
private static (SeatingPlanTabViewModel Vm, FakeEntries Entries, ParticipationSession Session, Student Student) Build(bool readOnly = false)
|
||||
{
|
||||
var group = Guid.NewGuid();
|
||||
var student = new Student { FirstName = "Anna", LastName = "Beispiel" };
|
||||
var session = new ParticipationSession { GroupId = group, Date = DateOnly.FromDateTime(DateTime.Today) };
|
||||
var entries = new FakeEntries();
|
||||
var plan = new SeatingPlan { GroupId = group, Rows = 1, Columns = 2,
|
||||
Assignments = [new SeatAssignment { Row = 0, Column = 0, StudentId = student.Id }] };
|
||||
var vm = new SeatingPlanTabViewModel(new FakeSeatingPlans([plan]), new FakeStudents([student]),
|
||||
new FakeMemberships([new GroupMembership { GroupId = group, StudentId = student.Id }]),
|
||||
new FakeSessions([session]), entries, new FakeAspects());
|
||||
vm.Initialize(group, readOnly);
|
||||
return (vm, entries, session, student);
|
||||
}
|
||||
|
||||
[Fact] public void Attendance_UsesSeatAndPreservesExistingHomeworkAndCounters()
|
||||
{
|
||||
var (vm, entries, session, student) = Build();
|
||||
entries.Save(new ParticipationEntry { SessionId = session.Id, StudentId = student.Id,
|
||||
Homework = HomeworkStatus.Completed, RaisedHandCount = 3, CalledOnCount = 2 });
|
||||
vm.CheckAttendanceCommand.Execute(null);
|
||||
var seat = vm.Seats[0];
|
||||
Assert.True(seat.ShowQuickCheck);
|
||||
Assert.False(vm.Seats[1].ShowQuickCheck);
|
||||
seat.QuickNegativeCommand.Execute(null);
|
||||
var entry = entries.GetBySessionAndStudent(session.Id, student.Id)!;
|
||||
Assert.Equal(AttendanceStatus.ExcusePending, entry.Attendance);
|
||||
Assert.Equal(HomeworkStatus.Completed, entry.Homework);
|
||||
Assert.Equal(3, entry.RaisedHandCount);
|
||||
Assert.Equal(2, entry.CalledOnCount);
|
||||
Assert.Equal(1, seat.DisplayOpacity);
|
||||
seat.QuickPositiveCommand.Execute(null);
|
||||
Assert.Equal(AttendanceStatus.Present, entry.Attendance);
|
||||
}
|
||||
|
||||
[Fact] public void Homework_UpdatesLegacyFlagAndPreservesAttendance()
|
||||
{
|
||||
var (vm, entries, session, student) = Build();
|
||||
entries.Save(new ParticipationEntry { SessionId = session.Id, StudentId = student.Id, Attendance = AttendanceStatus.Late });
|
||||
vm.CheckHomeworkCommand.Execute(null);
|
||||
vm.Seats[0].QuickNegativeCommand.Execute(null);
|
||||
var entry = entries.GetBySessionAndStudent(session.Id, student.Id)!;
|
||||
Assert.Equal(HomeworkStatus.MissingOpen, entry.Homework);
|
||||
Assert.True(entry.HomeworkMissing);
|
||||
Assert.Equal(AttendanceStatus.Late, entry.Attendance);
|
||||
vm.Seats[0].QuickPositiveCommand.Execute(null);
|
||||
Assert.Equal(HomeworkStatus.Completed, entry.Homework);
|
||||
Assert.False(entry.HomeworkMissing);
|
||||
vm.EndQuickCheckCommand.Execute(null);
|
||||
Assert.False(vm.Seats[0].ShowQuickCheck);
|
||||
Assert.True(vm.Seats[0].ShowNormalActions);
|
||||
}
|
||||
|
||||
[Fact] public async Task SpecialCases_OpenAssessmentForClickedStudentAndSession()
|
||||
{
|
||||
var (vm, _, _, _) = Build();
|
||||
var calls = 0;
|
||||
vm.OnAssessStudent = _ => { calls++; return Task.CompletedTask; };
|
||||
vm.CheckAttendanceCommand.Execute(null);
|
||||
await vm.Seats[0].QuickSpecialCommand.ExecuteAsync(null);
|
||||
Assert.Equal(1, calls);
|
||||
}
|
||||
|
||||
[Fact] public void ReadOnlyAndEmptySeats_CannotWriteQuickChecks()
|
||||
{
|
||||
var (vm, entries, session, _) = Build(readOnly: true);
|
||||
vm.CheckHomeworkCommand.Execute(null);
|
||||
vm.Seats[0].QuickNegativeCommand.Execute(null);
|
||||
vm.Seats[1].QuickPositiveCommand.Execute(null);
|
||||
Assert.False(vm.Seats[0].ShowQuickCheck);
|
||||
Assert.Empty(entries.GetBySession(session.Id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Desktop.ViewModels.Groups;
|
||||
using Xunit;
|
||||
|
||||
namespace LehrerApp.Desktop.Tests;
|
||||
|
||||
public sealed class TeachingTimelineViewModelTests
|
||||
{
|
||||
private readonly DateTime _start = new(2026, 9, 14, 8, 0, 0, DateTimeKind.Utc);
|
||||
private DateTime _now;
|
||||
private readonly FakeLessons _lessons = new();
|
||||
|
||||
private (Lesson Lesson, TeachingTimelineViewModel Vm) Build(bool scheduled = true)
|
||||
{
|
||||
_now = _start.AddMinutes(5);
|
||||
var local = _start.ToLocalTime();
|
||||
var lesson = new Lesson
|
||||
{
|
||||
Date = DateOnly.FromDateTime(local),
|
||||
StartTime = scheduled ? TimeOnly.FromDateTime(local) : null,
|
||||
Phases = [new() { Name = "Einstieg", DurationMinutes = 10 },
|
||||
new() { Name = "Arbeit", DurationMinutes = 10 },
|
||||
new() { Name = "Sicherung", DurationMinutes = 10 }]
|
||||
};
|
||||
_lessons.Add(lesson);
|
||||
return (lesson, new TeachingTimelineViewModel(lesson, _lessons, utcNow: () => _now));
|
||||
}
|
||||
|
||||
[Fact] public void Clock_HighlightsExactlyOnePhaseAtBoundary()
|
||||
{
|
||||
var (_, vm) = Build();
|
||||
Assert.True(vm.Phases[0].IsActive);
|
||||
Assert.Equal(50, vm.Phases[0].Progress);
|
||||
_now = _start.AddMinutes(10);
|
||||
vm.Refresh();
|
||||
Assert.True(vm.Phases[0].IsCompleted);
|
||||
Assert.True(vm.Phases[1].IsActive);
|
||||
Assert.Single(vm.Phases, p => p.IsActive);
|
||||
}
|
||||
|
||||
[Fact] public void NoStartTime_RequiresExplicitStart()
|
||||
{
|
||||
var (_, vm) = Build(scheduled: false);
|
||||
Assert.True(vm.NeedsStart);
|
||||
Assert.DoesNotContain(vm.Phases, p => p.IsActive);
|
||||
vm.StartNowCommand.Execute(null);
|
||||
Assert.False(vm.NeedsStart);
|
||||
Assert.True(vm.Phases[0].IsActive);
|
||||
Assert.Equal(_now, vm.Phases[0].StartUtc);
|
||||
}
|
||||
|
||||
[Fact] public void Extend_ShiftsLaterPhasesAndPreservesOriginalPlan()
|
||||
{
|
||||
var (lesson, vm) = Build();
|
||||
vm.Phases[0].ExtendTenCommand.Execute(null);
|
||||
Assert.Equal(_start.AddMinutes(20), vm.Phases[1].StartUtc);
|
||||
Assert.True(vm.Phases[2].IsOverflow);
|
||||
Assert.Equal(10, lesson.Phases[0].DurationMinutes);
|
||||
Assert.NotNull(_lessons.GetById(lesson.Id)!.TeachingTimeline);
|
||||
}
|
||||
|
||||
[Fact] public void FinishEarly_StartsNextPhaseNow()
|
||||
{
|
||||
var (_, vm) = Build();
|
||||
vm.Phases[0].FinishCommand.Execute(null);
|
||||
Assert.True(vm.Phases[0].IsCompleted);
|
||||
Assert.True(vm.Phases[1].IsActive);
|
||||
Assert.Equal(_now, vm.Phases[1].StartUtc);
|
||||
}
|
||||
|
||||
[Fact] public void Hold_SurvivesReopenAndContinuesOnlyOnNext()
|
||||
{
|
||||
var (lesson, vm) = Build();
|
||||
vm.Phases[0].HoldCommand.Execute(null);
|
||||
_now = _start.AddMinutes(65);
|
||||
var reopened = new TeachingTimelineViewModel(lesson, _lessons, utcNow: () => _now);
|
||||
Assert.True(reopened.Phases[0].IsHeld);
|
||||
Assert.True(reopened.Phases[0].IsActive);
|
||||
Assert.Single(reopened.Phases, p => p.IsActive);
|
||||
reopened.Phases[0].FinishCommand.Execute(null);
|
||||
Assert.True(reopened.Phases[1].IsActive);
|
||||
Assert.False(reopened.Phases[0].IsHeld);
|
||||
Assert.Equal(_now, reopened.Phases[1].StartUtc);
|
||||
}
|
||||
|
||||
[Fact] public void BringForward_KeepsSkippedPendingPhasesBelowChosenPhase()
|
||||
{
|
||||
var (_, vm) = Build();
|
||||
var chosen = vm.Phases[2];
|
||||
chosen.BringForwardCommand.Execute(null);
|
||||
Assert.Equal(new[] { "Einstieg", "Sicherung", "Arbeit" }, vm.Phases.Select(p => p.Source.Name));
|
||||
Assert.True(chosen.IsActive);
|
||||
Assert.Equal(_now, chosen.StartUtc);
|
||||
Assert.False(vm.Phases[2].IsCompleted);
|
||||
}
|
||||
|
||||
[Fact] public void Overflow_RemainsPendingTheNextDayAndCanBeStartedNow()
|
||||
{
|
||||
var (lesson, vm) = Build();
|
||||
vm.Phases[0].ExtendTenCommand.Execute(null);
|
||||
_now = _start.AddDays(1);
|
||||
var reopened = new TeachingTimelineViewModel(lesson, _lessons, utcNow: () => _now);
|
||||
var remainder = reopened.Phases[2];
|
||||
Assert.True(remainder.IsOverflow);
|
||||
Assert.False(remainder.IsCompleted);
|
||||
Assert.False(remainder.IsActive);
|
||||
Assert.True(reopened.HasRemainder);
|
||||
remainder.BringForwardCommand.Execute(null);
|
||||
Assert.True(remainder.IsActive);
|
||||
Assert.Equal(_now, remainder.StartUtc);
|
||||
}
|
||||
|
||||
[Fact] public void Transfer_CopiesIntoSelectedLessonOnlyOnceAndKeepsSource()
|
||||
{
|
||||
var (lesson, _) = Build();
|
||||
var target = new Lesson { GroupId = lesson.GroupId, Date = lesson.Date.AddDays(1), Topic = "Folgestunde" };
|
||||
_lessons.Add(target);
|
||||
var vm = new TeachingTimelineViewModel(lesson, _lessons, utcNow: () => _now);
|
||||
vm.Phases[0].ExtendTenCommand.Execute(null);
|
||||
vm.TransferRemainderCommand.Execute(null);
|
||||
vm.TransferRemainderCommand.Execute(null);
|
||||
var copy = Assert.Single(target.Phases);
|
||||
Assert.Equal("Sicherung", copy.Name);
|
||||
Assert.NotEqual(lesson.Phases[2].Id, copy.Id);
|
||||
Assert.Equal(3, lesson.Phases.Count);
|
||||
Assert.True(vm.Phases[2].IsTransferred);
|
||||
}
|
||||
|
||||
[Fact] public void PartiallyOverflowingPhase_PreservesOnlyUnfinishedMinutesAfterLessonEnds()
|
||||
{
|
||||
var (lesson, vm) = Build();
|
||||
vm.Phases[0].ExtendFiveCommand.Execute(null);
|
||||
_now = _start.AddMinutes(28);
|
||||
vm.Refresh();
|
||||
Assert.True(vm.Phases[2].IsActive);
|
||||
_now = _start.AddDays(1);
|
||||
var reopened = new TeachingTimelineViewModel(lesson, _lessons, utcNow: () => _now);
|
||||
var remainder = reopened.Phases[2];
|
||||
Assert.False(remainder.IsCompleted);
|
||||
Assert.False(remainder.IsActive);
|
||||
Assert.True(reopened.HasRemainder);
|
||||
Assert.Equal(5, remainder.RemainingMinutes);
|
||||
remainder.BringForwardCommand.Execute(null);
|
||||
Assert.True(remainder.IsActive);
|
||||
Assert.Equal(_now.AddMinutes(5), remainder.EndUtc);
|
||||
}
|
||||
|
||||
[Fact] public void ReadOnly_DoesNotChangeTimingOrStartClock()
|
||||
{
|
||||
var (lesson, _) = Build(scheduled: false);
|
||||
var vm = new TeachingTimelineViewModel(lesson, _lessons, readOnly: true, utcNow: () => _now);
|
||||
vm.StartNowCommand.Execute(null);
|
||||
Assert.Null(lesson.TeachingTimeline);
|
||||
Assert.True(vm.NeedsStart);
|
||||
}
|
||||
|
||||
[Fact] public void AlternativePhases_AreNotRunAlongsideMainPath()
|
||||
{
|
||||
var (lesson, _) = Build();
|
||||
lesson.Phases.Add(new LessonPhaseStep { AlternativePathId = Guid.NewGuid(), DurationMinutes = 30 });
|
||||
var vm = new TeachingTimelineViewModel(lesson, _lessons, utcNow: () => _now);
|
||||
Assert.Equal(3, vm.Phases.Count);
|
||||
Assert.Equal(_start.AddMinutes(30), vm.Phases[^1].EndUtc);
|
||||
}
|
||||
|
||||
[Fact] public void HeldTiming_RoundTripsThroughLiteDbWithoutTimezoneShift()
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
using var db = new LehrerApp.Data.LiteDbContext(stream);
|
||||
var repository = new LehrerApp.Data.Repositories.LessonRepository(db);
|
||||
var (lesson, _) = Build();
|
||||
repository.Save(lesson);
|
||||
var vm = new TeachingTimelineViewModel(lesson, repository, utcNow: () => _now);
|
||||
vm.Phases[0].HoldCommand.Execute(null);
|
||||
_now = _start.AddMinutes(40);
|
||||
var loaded = repository.GetById(lesson.Id)!;
|
||||
var reopened = new TeachingTimelineViewModel(loaded, repository, utcNow: () => _now);
|
||||
Assert.True(reopened.Phases[0].IsActive);
|
||||
Assert.True(reopened.Phases[0].IsHeld);
|
||||
Assert.Equal(_start, reopened.Phases[0].StartUtc);
|
||||
Assert.Equal(_start.AddMinutes(45), reopened.Phases[0].EndUtc);
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,15 @@ public partial class GroupOverviewViewModel : ObservableObject
|
||||
private readonly GradingService _grading;
|
||||
private readonly SchoolYearService _schoolYear;
|
||||
|
||||
public ObservableCollection<Lesson> TodayLessons { get; } = [];
|
||||
[ObservableProperty] private Lesson? _selectedTeachingLesson;
|
||||
public bool HasTodayLessons => TodayLessons.Count > 0;
|
||||
public Action<Lesson>? OnOpenTeachingMode { get; set; }
|
||||
[RelayCommand] private void StartTeachingMode()
|
||||
{
|
||||
if (SelectedTeachingLesson is { } lesson) OnOpenTeachingMode?.Invoke(lesson);
|
||||
}
|
||||
|
||||
private Guid _groupId;
|
||||
private string _groupName = "";
|
||||
|
||||
@@ -130,6 +139,16 @@ public partial class GroupOverviewViewModel : ObservableObject
|
||||
public void Refresh()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
TodayLessons.Clear();
|
||||
if (_groups.GetById(_groupId)?.IsActive == true)
|
||||
foreach (var lesson in _lessons.GetByGroupAndRange(_groupId, today, today)
|
||||
.Where(l => l.Status != LessonStatus.Cancelled)
|
||||
.OrderBy(l => l.StartTime).ThenBy(l => l.LessonNumber))
|
||||
TodayLessons.Add(lesson);
|
||||
var now = TimeOnly.FromDateTime(DateTime.Now);
|
||||
SelectedTeachingLesson = TodayLessons.LastOrDefault(l => l.StartTime <= now)
|
||||
?? TodayLessons.FirstOrDefault();
|
||||
OnPropertyChanged(nameof(HasTodayLessons));
|
||||
LoadNextLesson(today);
|
||||
LoadNextExam(today);
|
||||
LoadYearComparison();
|
||||
|
||||
@@ -16,6 +16,45 @@ public partial class SeatingPlanTabViewModel : ObservableObject
|
||||
private readonly IParticipationRepository _participation;
|
||||
private readonly IParticipationAspectRepository _aspects;
|
||||
private readonly IDocumentationRepository? _documentation;
|
||||
[ObservableProperty] private bool _isTeachingMode;
|
||||
[ObservableProperty] private string _quickMode = "";
|
||||
public bool IsQuickMode => QuickMode.Length > 0;
|
||||
public string QuickModeDisplay => QuickMode switch
|
||||
{
|
||||
"Attendance" => "Anwesenheit kontrollieren · Fehlend = Entschuldigung offen",
|
||||
"Homework" => "Hausaufgaben kontrollieren",
|
||||
_ => "Klicken: bewerten"
|
||||
};
|
||||
[RelayCommand] private void CheckAttendance() => QuickMode = "Attendance";
|
||||
[RelayCommand] private void CheckHomework() => QuickMode = "Homework";
|
||||
[RelayCommand] private void EndQuickCheck() => QuickMode = "";
|
||||
partial void OnQuickModeChanged(string value)
|
||||
{
|
||||
if (value.Length > 0) IsEditMode = false;
|
||||
foreach (var seat in Seats) seat.QuickMode = value;
|
||||
OnPropertyChanged(nameof(IsQuickMode));
|
||||
OnPropertyChanged(nameof(QuickModeDisplay));
|
||||
}
|
||||
|
||||
private void SaveQuickStatus(SeatCellViewModel seat, bool positive)
|
||||
{
|
||||
if (!IsEditable || !IsQuickMode || seat.SelectedOption.StudentId is not Guid studentId) return;
|
||||
var session = EnsureTodaySession();
|
||||
if (session is null) return;
|
||||
// Always merge with the latest entry, so quick checks preserve ratings and counters.
|
||||
var entry = _participation.GetBySessionAndStudent(session.Id, studentId)
|
||||
?? new ParticipationEntry { SessionId = session.Id, StudentId = studentId };
|
||||
if (QuickMode == "Attendance") entry.Attendance = positive ? AttendanceStatus.Present : AttendanceStatus.ExcusePending;
|
||||
else
|
||||
{
|
||||
entry.Homework = positive ? HomeworkStatus.Completed : HomeworkStatus.MissingOpen;
|
||||
entry.HomeworkMissing = HomeworkDisplay.CountsAsMissing(entry.Homework);
|
||||
}
|
||||
_participation.Save(entry);
|
||||
RefreshSeatLessonData();
|
||||
OnAssessmentChanged?.Invoke();
|
||||
}
|
||||
|
||||
private Guid _groupId;
|
||||
private SeatingPlan? _currentPlan;
|
||||
private bool _isReadOnly;
|
||||
@@ -235,7 +274,12 @@ public partial class SeatingPlanTabViewModel : ObservableObject
|
||||
?? StudentSeatOption.Empty;
|
||||
var isHidden = hiddenSeats.Any(h => h.Row == row && h.Column == column);
|
||||
Seats.Add(new SeatCellViewModel(row, column, StudentOptions, option, OnSeatChanged,
|
||||
CanEditLayout, ToggleSituationTag, IsEditable, isHidden, ToggleSeatHidden, TallyParticipation));
|
||||
CanEditLayout, ToggleSituationTag, IsEditable, isHidden, ToggleSeatHidden, TallyParticipation)
|
||||
{
|
||||
QuickMode = QuickMode,
|
||||
OnQuickStatus = SaveQuickStatus,
|
||||
OnQuickSpecial = AssessStudent
|
||||
});
|
||||
}
|
||||
UpdateAssignmentSummary();
|
||||
RefreshSeatLessonData();
|
||||
@@ -488,6 +532,7 @@ public partial class SeatingPlanTabViewModel : ObservableObject
|
||||
|
||||
partial void OnIsEditModeChanged(bool value)
|
||||
{
|
||||
if (value) QuickMode = "";
|
||||
OnPropertyChanged(nameof(CanEditLayout));
|
||||
foreach (var seat in Seats)
|
||||
{
|
||||
@@ -540,6 +585,32 @@ public sealed class ParticipationSessionOption(ParticipationSession session)
|
||||
|
||||
public partial class SeatCellViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty] private string _quickMode = "";
|
||||
public bool ShowQuickCheck => QuickMode.Length > 0 && IsOccupied && CanRecordLesson;
|
||||
public bool ShowNormalActions => ShowLessonOverview && QuickMode.Length == 0;
|
||||
public bool ShowSituationActions => ShowNormalActions && CanRecordLesson;
|
||||
public string QuickPositiveLabel => QuickMode == "Attendance" ? "Anwesend" : "Gemacht";
|
||||
public string QuickNegativeLabel => QuickMode == "Attendance" ? "Fehlend" : "Fehlt";
|
||||
public Action<SeatCellViewModel, bool>? OnQuickStatus { get; init; }
|
||||
public Func<SeatCellViewModel, Task>? OnQuickSpecial { get; init; }
|
||||
[RelayCommand] private void QuickPositive() => OnQuickStatus?.Invoke(this, true);
|
||||
[RelayCommand] private void QuickNegative() => OnQuickStatus?.Invoke(this, false);
|
||||
[RelayCommand] private Task QuickSpecial() => OnQuickSpecial?.Invoke(this) ?? Task.CompletedTask;
|
||||
partial void OnQuickModeChanged(string value)
|
||||
{
|
||||
OnPropertyChanged(nameof(ShowQuickCheck));
|
||||
OnPropertyChanged(nameof(ShowNormalActions));
|
||||
OnPropertyChanged(nameof(ShowSituationActions));
|
||||
OnPropertyChanged(nameof(QuickPositiveLabel));
|
||||
OnPropertyChanged(nameof(QuickNegativeLabel));
|
||||
OnPropertyChanged(nameof(DisplayOpacity));
|
||||
}
|
||||
partial void OnCanRecordLessonChanged(bool value)
|
||||
{
|
||||
OnPropertyChanged(nameof(ShowQuickCheck));
|
||||
OnPropertyChanged(nameof(ShowSituationActions));
|
||||
}
|
||||
|
||||
private readonly Action<SeatCellViewModel> _onChanged;
|
||||
private bool _suppressChange;
|
||||
private readonly Action<SeatCellViewModel, string> _toggleSituationTag;
|
||||
@@ -581,7 +652,7 @@ public partial class SeatCellViewModel : ObservableObject
|
||||
/// Opacity ist im DataTemplate bereits lokal an LessonOpacity gebunden gewesen; ein lokal
|
||||
/// gebundener Wert überschreibt aber jeden Style-Setter für dieselbe Eigenschaft, daher muss
|
||||
/// die Abblendung für ausgeblendete Plätze hier statt per CSS-Klasse erfolgen.</summary>
|
||||
public double DisplayOpacity => IsHidden ? 0.4 : LessonOpacity;
|
||||
public double DisplayOpacity => IsHidden ? 0.4 : ShowQuickCheck ? 1 : LessonOpacity;
|
||||
|
||||
private readonly Action<SeatCellViewModel, bool> _tally;
|
||||
|
||||
@@ -615,6 +686,9 @@ public partial class SeatCellViewModel : ObservableObject
|
||||
|
||||
partial void OnSelectedOptionChanged(StudentSeatOption value)
|
||||
{
|
||||
OnPropertyChanged(nameof(ShowQuickCheck));
|
||||
OnPropertyChanged(nameof(ShowNormalActions));
|
||||
OnPropertyChanged(nameof(ShowSituationActions));
|
||||
OnPropertyChanged(nameof(IsOccupied));
|
||||
OnPropertyChanged(nameof(StudentName));
|
||||
OnPropertyChanged(nameof(ShowLessonOverview));
|
||||
@@ -624,6 +698,8 @@ public partial class SeatCellViewModel : ObservableObject
|
||||
|
||||
partial void OnCanEditChanged(bool value)
|
||||
{
|
||||
OnPropertyChanged(nameof(ShowNormalActions));
|
||||
OnPropertyChanged(nameof(ShowSituationActions));
|
||||
OnPropertyChanged(nameof(ShowLessonOverview));
|
||||
OnPropertyChanged(nameof(ShowSeat));
|
||||
OnPropertyChanged(nameof(CanToggleHidden));
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace LehrerApp.Desktop.ViewModels.Groups;
|
||||
/// </summary>
|
||||
public class TeachingModeViewModel
|
||||
{
|
||||
public TeachingTimelineViewModel Timeline { get; }
|
||||
public string GroupName { get; }
|
||||
public LessonViewerViewModel LessonInfo { get; }
|
||||
public SeatingPlanTabViewModel SeatingPlan { get; }
|
||||
@@ -38,9 +39,11 @@ public class TeachingModeViewModel
|
||||
SeatingPlanTabViewModel seatingPlan, ParticipationTabViewModel participation)
|
||||
{
|
||||
GroupName = group.Name;
|
||||
Timeline = new TeachingTimelineViewModel(lesson, lessons, !group.IsActive);
|
||||
LessonInfo = new LessonViewerViewModel(lesson, alternativePaths);
|
||||
|
||||
SeatingPlan = seatingPlan;
|
||||
SeatingPlan.IsTeachingMode = true;
|
||||
SeatingPlan.Initialize(group.Id, !group.IsActive);
|
||||
SeatingPlan.SelectOrCreateSessionForLesson(lesson);
|
||||
|
||||
@@ -102,14 +105,19 @@ public partial class TeachingModeHomeworkViewModel : ObservableObject
|
||||
if (_previousLesson is null) return;
|
||||
_previousLesson.HomeworkChecked = value;
|
||||
if (value) _previousLesson.HomeworkCheckDismissed = false;
|
||||
_lessons.Save(_previousLesson);
|
||||
var latest = _lessons.GetById(_previousLesson.Id) ?? _previousLesson;
|
||||
latest.HomeworkChecked = value;
|
||||
if (value) latest.HomeworkCheckDismissed = false;
|
||||
_lessons.Save(latest);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SaveCurrentHomework()
|
||||
{
|
||||
_lesson.Homework = CurrentHomework;
|
||||
_lessons.Save(_lesson);
|
||||
var latest = _lessons.GetById(_lesson.Id) ?? _lesson;
|
||||
latest.Homework = CurrentHomework;
|
||||
_lessons.Save(latest);
|
||||
SaveStatus = "Gespeichert.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Core.Models;
|
||||
|
||||
namespace LehrerApp.Desktop.ViewModels.Groups;
|
||||
|
||||
public partial class TeachingTimelineViewModel : ObservableObject
|
||||
{
|
||||
private readonly Lesson _lesson;
|
||||
private readonly ILessonRepository _lessons;
|
||||
private readonly Func<DateTime> _utcNow;
|
||||
private TeachingTimelineState? _state;
|
||||
public bool IsEditable { get; }
|
||||
public ObservableCollection<TeachingPhaseViewModel> Phases { get; } = [];
|
||||
public ObservableCollection<Lesson> TransferTargets { get; } = [];
|
||||
[ObservableProperty] private Lesson? _transferTarget;
|
||||
[ObservableProperty] private string _status = "";
|
||||
[ObservableProperty] private bool _needsStart;
|
||||
public bool HasPhases => Phases.Count > 0;
|
||||
public bool HasTransferTargets => TransferTargets.Count > 0;
|
||||
public bool HasRemainder => Phases.Any(p => p.IsOverflow && !p.IsCompleted && !p.IsTransferred);
|
||||
|
||||
public TeachingTimelineViewModel(Lesson lesson, ILessonRepository lessons, bool readOnly = false,
|
||||
Func<DateTime>? utcNow = null)
|
||||
{
|
||||
_lesson = lesson;
|
||||
_lessons = lessons;
|
||||
_utcNow = utcNow ?? (() => DateTime.UtcNow);
|
||||
IsEditable = !readOnly;
|
||||
_state = lesson.TeachingTimeline;
|
||||
if (_state is not null)
|
||||
{
|
||||
// LiteDB returns local DateTimes by default; arithmetic below uses UTC.
|
||||
_state.StartUtc = _state.StartUtc.ToUniversalTime();
|
||||
_state.EndUtc = _state.EndUtc.ToUniversalTime();
|
||||
_state.HeldSinceUtc = _state.HeldSinceUtc?.ToUniversalTime();
|
||||
}
|
||||
foreach (var phase in lesson.Phases.Where(p => p.AlternativePathId is null))
|
||||
Phases.Add(new TeachingPhaseViewModel(phase, this));
|
||||
if (_state is null && lesson.StartTime is { } start)
|
||||
CreateState(lesson.Date.ToDateTime(start).ToUniversalTime());
|
||||
foreach (var target in lessons.GetByGroupAndRange(lesson.GroupId, lesson.Date, lesson.Date.AddDays(120))
|
||||
.Where(l => l.Id != lesson.Id && l.Status is not (LessonStatus.Cancelled or LessonStatus.Conducted)
|
||||
&& (l.Date > lesson.Date || l.StartTime > lesson.StartTime || l.LessonNumber > lesson.LessonNumber))
|
||||
.OrderBy(l => l.Date).ThenBy(l => l.StartTime).ThenBy(l => l.LessonNumber))
|
||||
TransferTargets.Add(target);
|
||||
TransferTarget = TransferTargets.FirstOrDefault();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void CreateState(DateTime start)
|
||||
{
|
||||
_state = new TeachingTimelineState
|
||||
{
|
||||
StartUtc = start,
|
||||
EndUtc = start.AddMinutes(Phases.Sum(p => Math.Max(0, p.Source.DurationMinutes))),
|
||||
Phases = Phases.Select(p => new TeachingPhaseTiming
|
||||
{ PhaseId = p.Source.Id, Minutes = Math.Max(0, p.Source.DurationMinutes) }).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
[RelayCommand] private void StartNow()
|
||||
{
|
||||
if (!IsEditable || _state is not null) return;
|
||||
CreateState(_utcNow());
|
||||
Save();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
NeedsStart = _state is null && HasPhases;
|
||||
if (_state is null) return;
|
||||
var now = _utcNow();
|
||||
var cursor = _state.StartUtc;
|
||||
_state.Phases.RemoveAll(t => !Phases.Any(p => p.Source.Id == t.PhaseId));
|
||||
var ordered = _state.Phases.Select(t => Phases.FirstOrDefault(p => p.Source.Id == t.PhaseId))
|
||||
.OfType<TeachingPhaseViewModel>().ToList();
|
||||
// Keep surviving IDs in their live order when a plan was edited between openings.
|
||||
foreach (var phase in Phases.Where(p => !ordered.Contains(p)).ToList())
|
||||
{
|
||||
_state.Phases.Add(new TeachingPhaseTiming { PhaseId = phase.Source.Id, Minutes = Math.Max(0, phase.Source.DurationMinutes) });
|
||||
ordered.Add(phase);
|
||||
}
|
||||
for (var i = 0; i < ordered.Count; i++)
|
||||
if (Phases.IndexOf(ordered[i]) != i) Phases.Move(Phases.IndexOf(ordered[i]), i);
|
||||
foreach (var phase in Phases)
|
||||
{
|
||||
var timing = _state.Phases.First(t => t.PhaseId == phase.Source.Id);
|
||||
var held = _state.HeldPhaseId == phase.Source.Id && _state.HeldSinceUtc.HasValue;
|
||||
var extension = held ? Math.Max(0, (now - _state.HeldSinceUtc!.Value).TotalMinutes) : 0;
|
||||
var end = cursor.AddMinutes(timing.Minutes + extension);
|
||||
// Phases pushed entirely out of the lesson remain pending, even after closing
|
||||
// the window overnight. They run only if explicitly brought forward.
|
||||
var canRun = cursor < _state.EndUtc || timing.ExplicitlyStarted || held;
|
||||
phase.StartUtc = cursor;
|
||||
phase.EndUtc = end;
|
||||
phase.IsActive = canRun && cursor <= now && (now < end || held)
|
||||
&& (now < _state.EndUtc || timing.ExplicitlyStarted || held);
|
||||
phase.IsCompleted = canRun && !held && end <= now
|
||||
&& (end <= _state.EndUtc || timing.ExplicitlyStarted);
|
||||
phase.IsOverflow = end > _state.EndUtc && !phase.IsCompleted;
|
||||
phase.IsTransferred = _state.TransferredPhaseIds.Contains(phase.Source.Id);
|
||||
phase.IsHeld = held;
|
||||
var effectiveNow = timing.ExplicitlyStarted || held || now < _state.EndUtc ? now : _state.EndUtc;
|
||||
var elapsed = Math.Clamp((effectiveNow - cursor).TotalMinutes, 0, timing.Minutes + extension);
|
||||
phase.RemainingMinutes = phase.IsCompleted ? 0 : timing.Minutes + extension - elapsed;
|
||||
phase.Progress = !canRun ? 0 : phase.IsCompleted ? 100 : elapsed / Math.Max(0.01, timing.Minutes + extension) * 100;
|
||||
phase.TimeDisplay = $"{cursor.ToLocalTime():HH:mm}–{end.ToLocalTime():HH:mm}";
|
||||
phase.CanStartNow = IsEditable && !phase.IsCompleted && !phase.IsActive && !phase.IsTransferred;
|
||||
cursor = end;
|
||||
}
|
||||
OnPropertyChanged(nameof(HasRemainder));
|
||||
}
|
||||
|
||||
public void Extend(TeachingPhaseViewModel phase, int minutes)
|
||||
{
|
||||
Refresh();
|
||||
if (!IsEditable || !phase.IsActive || _state is null) return;
|
||||
var timing = _state.Phases.First(p => p.PhaseId == phase.Source.Id);
|
||||
timing.Minutes += minutes;
|
||||
timing.ExplicitlyStarted = true;
|
||||
Save(); Refresh();
|
||||
}
|
||||
|
||||
public void Hold(TeachingPhaseViewModel phase)
|
||||
{
|
||||
Refresh();
|
||||
if (!IsEditable || !phase.IsActive || phase.IsHeld || _state is null) return;
|
||||
_state.HeldPhaseId = phase.Source.Id;
|
||||
_state.HeldSinceUtc = _utcNow();
|
||||
_state.Phases.First(p => p.PhaseId == phase.Source.Id).ExplicitlyStarted = true;
|
||||
Save(); Refresh();
|
||||
}
|
||||
|
||||
public void Finish(TeachingPhaseViewModel phase, bool advanceNext = true)
|
||||
{
|
||||
Refresh();
|
||||
if (!IsEditable || !phase.IsActive || _state is null) return;
|
||||
_state.Phases.First(p => p.PhaseId == phase.Source.Id).Minutes = Math.Max(0, (_utcNow() - phase.StartUtc).TotalMinutes);
|
||||
_state.HeldPhaseId = null;
|
||||
_state.HeldSinceUtc = null;
|
||||
if (advanceNext)
|
||||
{
|
||||
var nextIndex = _state.Phases.FindIndex(p => p.PhaseId == phase.Source.Id) + 1;
|
||||
if (nextIndex < _state.Phases.Count) _state.Phases[nextIndex].ExplicitlyStarted = true;
|
||||
}
|
||||
Save(); Refresh();
|
||||
}
|
||||
|
||||
public void BringForward(TeachingPhaseViewModel phase)
|
||||
{
|
||||
Refresh();
|
||||
if (!phase.CanStartNow || _state is null) return;
|
||||
var active = Phases.FirstOrDefault(p => p.IsActive);
|
||||
if (active is not null) Finish(active, advanceNext: false);
|
||||
var timing = _state.Phases.First(p => p.PhaseId == phase.Source.Id);
|
||||
timing.Minutes = phase.RemainingMinutes;
|
||||
_state.Phases.Remove(timing);
|
||||
var completed = Phases.TakeWhile(p => p.IsCompleted).Count();
|
||||
_state.Phases.Insert(Math.Min(completed, _state.Phases.Count), timing);
|
||||
timing.ExplicitlyStarted = true;
|
||||
var now = _utcNow();
|
||||
// A pending phase may be selected long after the scheduled end. Anchor it to
|
||||
// now instead of letting yesterday's timestamps immediately complete it.
|
||||
var prefix = _state.Phases.Take(completed).Sum(p => p.Minutes);
|
||||
var delay = (now - _state.StartUtc.AddMinutes(prefix)).TotalMinutes;
|
||||
if (completed == 0) _state.StartUtc = now;
|
||||
else if (delay > 0) _state.Phases[completed - 1].Minutes += delay;
|
||||
Save(); Refresh();
|
||||
}
|
||||
|
||||
[RelayCommand] private void TransferRemainder()
|
||||
{
|
||||
Refresh();
|
||||
if (!IsEditable || _state is null || TransferTarget is null) return;
|
||||
var target = _lessons.GetById(TransferTarget.Id);
|
||||
if (target is null || target.Status is LessonStatus.Cancelled or LessonStatus.Conducted) return;
|
||||
var remainder = Phases.Where(p => p.IsOverflow && !p.IsCompleted && !p.IsTransferred).ToList();
|
||||
if (remainder.Count == 0) return;
|
||||
foreach (var phase in remainder)
|
||||
{
|
||||
var source = phase.Source;
|
||||
target.Phases.Add(new LessonPhaseStep { Name = source.Name, DurationMinutes = (int)Math.Ceiling(phase.RemainingMinutes),
|
||||
Activity = source.Activity, Material = source.Material, Shorthand = source.Shorthand });
|
||||
}
|
||||
_lessons.Save(target);
|
||||
_state.TransferredPhaseIds.AddRange(remainder.Select(p => p.Source.Id));
|
||||
Save(); Refresh();
|
||||
Status = $"{remainder.Count} Phase(n) nach {target.Date:dd.MM.yyyy} · {target.Topic} kopiert.";
|
||||
}
|
||||
|
||||
private void Save()
|
||||
{
|
||||
var latest = _lessons.GetById(_lesson.Id) ?? _lesson;
|
||||
latest.TeachingTimeline = _state;
|
||||
_lesson.TeachingTimeline = _state;
|
||||
_lessons.Save(latest);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class TeachingPhaseViewModel(LessonPhaseStep source, TeachingTimelineViewModel owner) : ObservableObject
|
||||
{
|
||||
public LessonPhaseStep Source { get; } = source;
|
||||
public DateTime StartUtc { get; set; }
|
||||
public DateTime EndUtc { get; set; }
|
||||
public double RemainingMinutes { get; set; }
|
||||
[ObservableProperty] private bool _isActive;
|
||||
[ObservableProperty] private bool _isCompleted;
|
||||
[ObservableProperty] private bool _isOverflow;
|
||||
[ObservableProperty] private bool _isHeld;
|
||||
[ObservableProperty] private bool _isTransferred;
|
||||
[ObservableProperty] private bool _canStartNow;
|
||||
[ObservableProperty] private double _progress;
|
||||
[ObservableProperty] private string _timeDisplay = "";
|
||||
public bool IsEditable => owner.IsEditable;
|
||||
[RelayCommand] private void ExtendFive() => owner.Extend(this, 5);
|
||||
[RelayCommand] private void ExtendTen() => owner.Extend(this, 10);
|
||||
[RelayCommand] private void Hold() => owner.Hold(this);
|
||||
[RelayCommand] private void Finish() => owner.Finish(this);
|
||||
[RelayCommand] private void BringForward() => owner.BringForward(this);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:models="clr-namespace:LehrerApp.Core.Models;assembly=LehrerApp.Core"
|
||||
xmlns:vm="clr-namespace:LehrerApp.Desktop.ViewModels.Groups"
|
||||
xmlns:vmRoot="clr-namespace:LehrerApp.Desktop.ViewModels"
|
||||
x:Class="LehrerApp.Desktop.Views.Groups.GroupOverviewTabView"
|
||||
@@ -42,6 +43,19 @@
|
||||
<StackPanel Spacing="0">
|
||||
<WrapPanel>
|
||||
|
||||
<Border Classes="card" IsVisible="{Binding HasTodayLessons}">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="UNTERRICHT HEUTE" Classes="cardTitle"/>
|
||||
<ComboBox ItemsSource="{Binding TodayLessons}" SelectedItem="{Binding SelectedTeachingLesson}" HorizontalAlignment="Stretch">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:Lesson">
|
||||
<TextBlock><Run Text="{Binding StartTime}"/><Run Text=" · "/><Run Text="{Binding Topic}"/></TextBlock>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<Button Content="Unterrichtsansicht öffnen" Command="{Binding StartTeachingModeCommand}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<!-- Nächste Stunde -->
|
||||
<Border Classes="card">
|
||||
<StackPanel>
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
using Avalonia.Controls;
|
||||
using LehrerApp.Desktop.ViewModels.Groups;
|
||||
|
||||
namespace LehrerApp.Desktop.Views.Groups;
|
||||
|
||||
public partial class GroupOverviewTabView : UserControl
|
||||
{
|
||||
public GroupOverviewTabView() => InitializeComponent();
|
||||
protected override void OnDataContextChanged(EventArgs e)
|
||||
{
|
||||
base.OnDataContextChanged(e);
|
||||
if (DataContext is GroupOverviewViewModel vm) vm.OnOpenTeachingMode = TeachingModeWindow.Open;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
<Grid ColumnDefinitions="260,*">
|
||||
<Border Grid.Column="0" BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
|
||||
<Grid ColumnDefinitions="Auto,*">
|
||||
<Border Grid.Column="0" Width="260" IsVisible="{Binding !IsTeachingMode}" BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
|
||||
BorderThickness="0,0,1,0" Padding="16">
|
||||
<Grid RowDefinitions="Auto,*,Auto">
|
||||
<StackPanel Grid.Row="0" Spacing="4" Margin="0,0,0,12">
|
||||
@@ -72,26 +72,35 @@
|
||||
<Grid RowDefinitions="Auto,*" ColumnDefinitions="*,Auto" IsVisible="{Binding HasSelectedPlan}" Margin="24">
|
||||
<Grid Grid.Row="0" Grid.ColumnSpan="2" ColumnDefinitions="*,Auto" Margin="0,0,0,18">
|
||||
<StackPanel Spacing="3">
|
||||
<TextBlock Text="{Binding PlanTitle}" FontSize="22" FontWeight="SemiBold"/>
|
||||
<ComboBox ItemsSource="{Binding Plans}" SelectedItem="{Binding SelectedPlan}" DisplayMemberBinding="{Binding Name}"
|
||||
IsVisible="{Binding IsTeachingMode}" MinWidth="180"/>
|
||||
<TextBlock Text="{Binding PlanTitle}" FontSize="22" FontWeight="SemiBold" IsVisible="{Binding !IsTeachingMode}"/>
|
||||
<TextBlock Text="{Binding PlanSubtitle}" Opacity="0.65"/>
|
||||
<TextBlock Text="{Binding QuickModeDisplay}" FontSize="12" TextWrapping="Wrap" IsVisible="{Binding IsTeachingMode}"/>
|
||||
<Border IsVisible="{Binding !IsTeachingMode}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8" Margin="0,6,0,0"
|
||||
IsVisible="{Binding IsEditMode, Converter={x:Static BoolConverters.Not}}">
|
||||
<TextBlock Text="Unterricht:" VerticalAlignment="Center" FontSize="12" Opacity="0.65"/>
|
||||
<ComboBox ItemsSource="{Binding TodaySessions}" SelectedItem="{Binding SelectedSession}"
|
||||
DisplayMemberBinding="{Binding DisplayName}" MinWidth="210"/>
|
||||
DisplayMemberBinding="{Binding DisplayName}" MinWidth="210" IsEnabled="{Binding !IsTeachingMode}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Bottom" Spacing="4">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10" HorizontalAlignment="Right">
|
||||
<Button Content="Als PDF" Click="OnExportPdfClick" VerticalAlignment="Center"/>
|
||||
<ToggleSwitch Content="Bearbeitungsmodus" IsChecked="{Binding IsEditMode}"
|
||||
IsVisible="{Binding IsEditable}"/>
|
||||
<Border IsVisible="{Binding !IsTeachingMode}">
|
||||
<ToggleSwitch Content="Bearbeitungsmodus" IsChecked="{Binding IsEditMode}"
|
||||
IsVisible="{Binding IsEditable}"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding AssignmentSummary}" HorizontalAlignment="Right" FontSize="12" Opacity="0.6"/>
|
||||
<TextBlock Text="Ziehen: Platz ändern · Klicken: bewerten" HorizontalAlignment="Right"
|
||||
FontSize="11" Opacity="0.5" IsVisible="{Binding IsEditMode}"/>
|
||||
<TextBlock Text="Klicken: bewerten" HorizontalAlignment="Right"
|
||||
FontSize="11" Opacity="0.5" IsVisible="{Binding IsEditMode, Converter={x:Static BoolConverters.Not}}"/>
|
||||
<Border IsVisible="{Binding !IsTeachingMode}">
|
||||
<TextBlock Text="{Binding QuickModeDisplay}" HorizontalAlignment="Right"
|
||||
FontSize="11" Opacity="0.5" IsVisible="{Binding IsEditMode, Converter={x:Static BoolConverters.Not}}"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -158,10 +167,20 @@
|
||||
IsVisible="{Binding HasDayHighlightBadge}"
|
||||
ToolTip.Tip="Tagesflagge"/>
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" IsVisible="{Binding ShowQuickCheck}">
|
||||
<Grid ColumnDefinitions="*,*">
|
||||
<Button Content="{Binding QuickPositiveLabel}" Command="{Binding QuickPositiveCommand}"
|
||||
FontSize="11" Padding="5,5" HorizontalAlignment="Stretch" Margin="0,0,3,0"/>
|
||||
<Button Grid.Column="1" Content="{Binding QuickNegativeLabel}" Command="{Binding QuickNegativeCommand}"
|
||||
FontSize="11" Padding="5,5" HorizontalAlignment="Stretch"/>
|
||||
</Grid>
|
||||
<Button Content="Sonderfälle …" Command="{Binding QuickSpecialCommand}"
|
||||
FontSize="10" Padding="5,3" HorizontalAlignment="Stretch"/>
|
||||
</StackPanel>
|
||||
<!-- Strichliste Meldungen (Nutzer-Feedback): schnelles Mitzählen ohne den
|
||||
vollen Bewertungsdialog zu öffnen -->
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="5"
|
||||
IsVisible="{Binding ShowLessonOverview}">
|
||||
IsVisible="{Binding ShowNormalActions}">
|
||||
<Button Padding="6,2" FontSize="10"
|
||||
Command="{Binding TallyRaisedHandCommand}"
|
||||
ToolTip.Tip="Meldung zählen">
|
||||
@@ -174,7 +193,7 @@
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<Expander Header="+ Situation" FontSize="10"
|
||||
IsVisible="{Binding CanRecordLesson}">
|
||||
IsVisible="{Binding ShowSituationActions}">
|
||||
<ItemsControl ItemsSource="{Binding SituationTags}" Margin="0,4,0,0">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate><WrapPanel ItemSpacing="3" LineSpacing="3"/></ItemsPanelTemplate>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:models="clr-namespace:LehrerApp.Core.Models;assembly=LehrerApp.Core"
|
||||
xmlns:vm="clr-namespace:LehrerApp.Desktop.ViewModels.Groups"
|
||||
xmlns:views="clr-namespace:LehrerApp.Desktop.Views.Groups"
|
||||
x:Class="LehrerApp.Desktop.Views.Groups.TeachingModeWindow"
|
||||
@@ -8,8 +9,23 @@
|
||||
Width="1400" Height="850" MinWidth="1000" MinHeight="600"
|
||||
WindowState="Maximized" CanResize="True" WindowStartupLocation="CenterScreen">
|
||||
|
||||
<Window.Styles>
|
||||
<Style Selector="Border.phase">
|
||||
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundAltHighBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="2"/>
|
||||
</Style>
|
||||
<Style Selector="Border.phase.overflow">
|
||||
<Setter Property="Background" Value="#22E57373"/>
|
||||
<Setter Property="BorderBrush" Value="#99E57373"/>
|
||||
</Style>
|
||||
<Style Selector="Border.phase.active">
|
||||
<Setter Property="Background" Value="#223BA6C8"/>
|
||||
<Setter Property="BorderBrush" Value="#3BA6C8"/>
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
<Grid RowDefinitions="Auto,*" Margin="20">
|
||||
<Grid Grid.Row="0" ColumnDefinitions="*,Auto" Margin="0,0,0,14">
|
||||
<Grid Grid.Row="0" RowDefinitions="Auto,Auto" Margin="0,0,0,14">
|
||||
<StackPanel Grid.Column="0" Spacing="3">
|
||||
<TextBlock FontSize="20" FontWeight="SemiBold">
|
||||
<Run Text="{Binding GroupName}"/><Run Text=" · "/><Run Text="{Binding LessonInfo.Topic}"/>
|
||||
@@ -26,19 +42,22 @@
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
|
||||
<WrapPanel Grid.Row="1" ItemSpacing="8" LineSpacing="6" Margin="0,10,0,0">
|
||||
<!-- Nutzer-Feedback: die Schnellbewertungs-Dialoge gab es bisher nur über den
|
||||
Mitarbeit-Tab der Gruppe — hier direkt auf die Sitzung dieser Stunde vorselektiert
|
||||
(siehe TeachingModeViewModel), kein Umweg mehr über "Zur Mitarbeit". -->
|
||||
<Button Content="⚡ Mitarbeit" Command="{Binding Participation.QuickInputCommand}"
|
||||
ToolTip.Tip="Mitarbeit dieser Stunde schnell bewerten."/>
|
||||
<Button Content="⚡ Anwesenheit/Hausaufgabe" Command="{Binding Participation.StatusQuickInputCommand}"
|
||||
ToolTip.Tip="Anwesenheit und Hausaufgabenstatus dieser Stunde schnell erfassen."/>
|
||||
<Button Content="Anwesenheit kontrollieren" Command="{Binding SeatingPlan.CheckAttendanceCommand}" IsEnabled="{Binding SeatingPlan.IsEditable}"/>
|
||||
<Button Content="Hausaufgaben kontrollieren" Command="{Binding SeatingPlan.CheckHomeworkCommand}" IsEnabled="{Binding SeatingPlan.IsEditable}"/>
|
||||
<Button Content="Kontrolle beenden" Command="{Binding SeatingPlan.EndQuickCheckCommand}" IsVisible="{Binding SeatingPlan.IsQuickMode}"/>
|
||||
<Button Content="Listenansicht …" Command="{Binding Participation.StatusQuickInputCommand}" ToolTip.Tip="Auch Schüler ohne Sitzplatz erfassen."/>
|
||||
<Button Content="Vollbild ↔" Click="OnFullScreen" ToolTip.Tip="Vollbild umschalten (F11); mit Escape verlassen."/>
|
||||
<Button Content="Zur Mitarbeit" Command="{Binding LessonInfo.NavigateToParticipationCommand}"
|
||||
ToolTip.Tip="Schließt den Unterrichtsmodus und springt zum Tab 'Mitarbeit' der Lerngruppe."/>
|
||||
<Button Content="Zu den Noten" Command="{Binding LessonInfo.NavigateToGradesCommand}"/>
|
||||
<Button Content="Unterrichtsmodus beenden" Click="OnClose" Margin="16,0,0,0"/>
|
||||
</StackPanel>
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1" ColumnDefinitions="360,16,*">
|
||||
@@ -48,44 +67,78 @@
|
||||
<StackPanel Spacing="14">
|
||||
<TextBlock Text="Verlaufsplan" FontSize="14" FontWeight="SemiBold"/>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding LessonInfo.PhaseGroups}">
|
||||
<TextBlock Text="Hauptweg · Live-Verlauf" FontSize="12" Opacity="0.65"/>
|
||||
<TextBlock Text="Noch keine Phasen geplant." IsVisible="{Binding !Timeline.HasPhases}"/>
|
||||
<Button Content="Zeitmessung jetzt starten" Command="{Binding Timeline.StartNowCommand}" IsVisible="{Binding Timeline.NeedsStart}" IsEnabled="{Binding Timeline.IsEditable}"/>
|
||||
<ItemsControl ItemsSource="{Binding Timeline.Phases}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:PhaseGroupViewItem">
|
||||
<StackPanel Margin="0,0,0,10">
|
||||
<StackPanel IsVisible="{Binding $parent[ItemsControl].((vm:LessonViewerViewModel)DataContext).HasAlternatives}">
|
||||
<TextBlock Text="{Binding Label}" FontSize="12" FontWeight="SemiBold" Opacity="0.75" Margin="0,6,0,2"/>
|
||||
<TextBlock Text="{Binding Description}" FontSize="11" Opacity="0.55" TextWrapping="Wrap" Margin="0,0,0,6"
|
||||
IsVisible="{Binding Description, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
<DataTemplate x:DataType="vm:TeachingPhaseViewModel">
|
||||
<Border Classes="phase" Classes.active="{Binding IsActive}" Classes.overflow="{Binding IsOverflow}" CornerRadius="6" Padding="10,8" Margin="0,0,0,8">
|
||||
<StackPanel Spacing="5">
|
||||
<TextBlock Text="{Binding Source.Name}" FontWeight="SemiBold" TextWrapping="Wrap"/>
|
||||
<TextBlock Text="{Binding TimeDisplay}" FontSize="12"/>
|
||||
<TextBlock Text="{Binding Source.DurationMinutes, StringFormat='Geplant: {0} Min.'}" FontSize="11" Opacity="0.7"/>
|
||||
<TextBlock Text="{Binding Source.Activity}" TextWrapping="Wrap" FontSize="12"
|
||||
IsVisible="{Binding Source.Activity, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
<TextBlock Text="{Binding Source.Material}" TextWrapping="Wrap" FontSize="11" Opacity="0.7"
|
||||
IsVisible="{Binding Source.Material, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
<TextBlock Text="{Binding Source.Shorthand}" FontSize="11" Opacity="0.7"
|
||||
IsVisible="{Binding Source.Shorthand, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
<ProgressBar Minimum="0" Maximum="100" Value="{Binding Progress}" Height="3" IsVisible="{Binding IsActive}"/>
|
||||
<TextBlock Text="Über dem geplanten Stundenende" FontSize="11" IsVisible="{Binding IsOverflow}"/>
|
||||
<TextBlock Text="Erledigt" FontSize="11" Opacity="0.6" IsVisible="{Binding IsCompleted}"/>
|
||||
<TextBlock Text="In Folgestunde kopiert" FontSize="11" IsVisible="{Binding IsTransferred}"/>
|
||||
<StackPanel IsVisible="{Binding IsActive}" IsEnabled="{Binding IsEditable}" Spacing="4">
|
||||
<WrapPanel ItemSpacing="4" LineSpacing="4">
|
||||
<Button Content="Weiter" Command="{Binding FinishCommand}" FontSize="11" Padding="6,4"/>
|
||||
<Button Content="+5 Min." Command="{Binding ExtendFiveCommand}" FontSize="11" Padding="6,4"/>
|
||||
<Button Content="+10 Min." Command="{Binding ExtendTenCommand}" FontSize="11" Padding="6,4"/>
|
||||
<Button Content="Halten bis Weiter" Command="{Binding HoldCommand}" FontSize="11" Padding="6,4" IsEnabled="{Binding !IsHeld}"/>
|
||||
</WrapPanel>
|
||||
<TextBlock Text="Gehalten – mit Weiter nächste Phase starten" FontSize="11" TextWrapping="Wrap" IsVisible="{Binding IsHeld}"/>
|
||||
</StackPanel>
|
||||
<Button Content="Jetzt vorziehen" Command="{Binding BringForwardCommand}" IsVisible="{Binding CanStartNow}" FontSize="11"/>
|
||||
</StackPanel>
|
||||
<ItemsControl ItemsSource="{Binding Phases}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:PhaseViewItem">
|
||||
<Border Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
||||
CornerRadius="6" Padding="10,8" Margin="0,0,0,6">
|
||||
<StackPanel Spacing="2">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Text="{Binding Name}" FontSize="13" FontWeight="SemiBold"
|
||||
TextWrapping="Wrap"/>
|
||||
<TextBlock Grid.Column="1" FontSize="11" Opacity="0.6">
|
||||
<Run Text="{Binding TimeDisplay}"/><Run Text=" · "/>
|
||||
<Run Text="{Binding DurationMinutes, StringFormat='{}{0} Min.'}"/>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<TextBlock Text="{Binding Activity}" FontSize="12" TextWrapping="Wrap"
|
||||
IsVisible="{Binding Activity, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
<TextBlock FontSize="11" Opacity="0.55" TextWrapping="Wrap"
|
||||
IsVisible="{Binding Material, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
||||
<Run Text="Material: "/><Run Text="{Binding Material}"/>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<StackPanel Spacing="6" IsVisible="{Binding Timeline.HasRemainder}" IsEnabled="{Binding Timeline.IsEditable}">
|
||||
<TextBlock Text="Rest für eine Folgestunde" FontWeight="SemiBold"/>
|
||||
<ComboBox ItemsSource="{Binding Timeline.TransferTargets}" SelectedItem="{Binding Timeline.TransferTarget}" HorizontalAlignment="Stretch">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:Lesson">
|
||||
<TextBlock><Run Text="{Binding Date, StringFormat='{}{0:dd.MM.yyyy}'}"/><Run Text=" · "/><Run Text="{Binding Topic}"/></TextBlock>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<Button Content="Rötlich markierte Phasen kopieren" Command="{Binding Timeline.TransferRemainderCommand}" IsEnabled="{Binding Timeline.HasTransferTargets}"/>
|
||||
<TextBlock Text="Zuerst eine Folgestunde in der Planung anlegen." IsVisible="{Binding !Timeline.HasTransferTargets}" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding Timeline.Status}" TextWrapping="Wrap" FontSize="11"/>
|
||||
<Expander Header="Alternative Abläufe" IsVisible="{Binding LessonInfo.HasAlternatives}">
|
||||
<ItemsControl ItemsSource="{Binding LessonInfo.PhaseGroups}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:PhaseGroupViewItem">
|
||||
<StackPanel IsVisible="{Binding !IsMainPath}" Spacing="4" Margin="0,4">
|
||||
<TextBlock Text="{Binding Label}" FontWeight="SemiBold"/>
|
||||
<TextBlock Text="{Binding Description}" TextWrapping="Wrap"/>
|
||||
<ItemsControl ItemsSource="{Binding Phases}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:PhaseViewItem">
|
||||
<StackPanel Margin="0,4">
|
||||
<TextBlock><Run Text="{Binding Name}"/><Run Text="{Binding DurationMinutes, StringFormat=' · {0} Min.'}"/></TextBlock>
|
||||
<TextBlock Text="{Binding Activity}" TextWrapping="Wrap" FontSize="12"/>
|
||||
<TextBlock Text="{Binding Material}" TextWrapping="Wrap" FontSize="11"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Expander>
|
||||
|
||||
<!-- Nutzer-Feedback: Hausaufgabe der letzten Stunde ansehen/als kontrolliert abhaken
|
||||
und die Hausaufgabe DIESER Stunde einsehen/ändern, ohne den vollen
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Threading;
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Desktop.ViewModels;
|
||||
using LehrerApp.Desktop.ViewModels.Groups;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -8,7 +12,53 @@ namespace LehrerApp.Desktop.Views.Groups;
|
||||
|
||||
public partial class TeachingModeWindow : Window
|
||||
{
|
||||
public TeachingModeWindow() => InitializeComponent();
|
||||
private static readonly Dictionary<Guid, TeachingModeWindow> OpenWindows = [];
|
||||
private readonly DispatcherTimer _clock = new() { Interval = TimeSpan.FromSeconds(1) };
|
||||
private WindowState _previousState = WindowState.Maximized;
|
||||
|
||||
public static void Open(Lesson lesson)
|
||||
{
|
||||
if (OpenWindows.TryGetValue(lesson.Id, out var existing))
|
||||
{
|
||||
if (existing.WindowState == WindowState.Minimized) existing.WindowState = WindowState.Normal;
|
||||
existing.Activate();
|
||||
return;
|
||||
}
|
||||
var group = App.Services.GetRequiredService<IGroupRepository>().GetById(lesson.GroupId);
|
||||
if (group is null) return;
|
||||
var window = new TeachingModeWindow
|
||||
{
|
||||
DataContext = new TeachingModeViewModel(lesson, group,
|
||||
App.Services.GetRequiredService<IAlternativeLessonPathRepository>(),
|
||||
App.Services.GetRequiredService<ILessonRepository>(),
|
||||
App.Services.GetRequiredService<SeatingPlanTabViewModel>(),
|
||||
App.Services.GetRequiredService<ParticipationTabViewModel>())
|
||||
};
|
||||
OpenWindows.Add(lesson.Id, window);
|
||||
window.Closed += (_, _) => OpenWindows.Remove(lesson.Id);
|
||||
window.Show();
|
||||
}
|
||||
|
||||
public TeachingModeWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
_clock.Tick += (_, _) => (DataContext as TeachingModeViewModel)?.Timeline.Refresh();
|
||||
Opened += (_, _) => _clock.Start();
|
||||
Closed += (_, _) => _clock.Stop();
|
||||
KeyDown += (_, e) =>
|
||||
{
|
||||
if (e.Key == Key.F11) { ToggleFullScreen(); e.Handled = true; }
|
||||
else if (e.Key == Key.Escape && WindowState == WindowState.FullScreen)
|
||||
{ WindowState = _previousState; e.Handled = true; }
|
||||
};
|
||||
}
|
||||
|
||||
private void ToggleFullScreen()
|
||||
{
|
||||
if (WindowState == WindowState.FullScreen) WindowState = _previousState;
|
||||
else { _previousState = WindowState; WindowState = WindowState.FullScreen; }
|
||||
}
|
||||
private void OnFullScreen(object? sender, RoutedEventArgs e) => ToggleFullScreen();
|
||||
|
||||
protected override void OnDataContextChanged(EventArgs e)
|
||||
{
|
||||
@@ -35,6 +85,7 @@ public partial class TeachingModeWindow : Window
|
||||
private async Task ShowQuickInputDialog(ParticipationTabViewModel tabVm)
|
||||
{
|
||||
if (tabVm.StudentRows.Count == 0) return;
|
||||
tabVm.RefreshCurrentGrid();
|
||||
var quickVm = new QuickInputViewModel(tabVm.StudentRows.ToList(), tabVm.Aspects.ToList());
|
||||
var dialog = new ParticipationQuickInputDialog { DataContext = quickVm };
|
||||
await dialog.ShowDialog(this);
|
||||
@@ -44,6 +95,7 @@ public partial class TeachingModeWindow : Window
|
||||
private async Task ShowStatusQuickInputDialog(ParticipationTabViewModel tabVm)
|
||||
{
|
||||
if (tabVm.StudentRows.Count == 0 || tabVm.SelectedSession is null) return;
|
||||
tabVm.RefreshCurrentGrid();
|
||||
var quickVm = new AttendanceHomeworkQuickInputViewModel(tabVm.StudentRows, tabVm.SelectedSessionDisplay);
|
||||
var dialog = new AttendanceHomeworkQuickInputDialog { DataContext = quickVm };
|
||||
await dialog.ShowDialog(this);
|
||||
|
||||
@@ -185,19 +185,10 @@ public partial class TimetableView : UserControl
|
||||
await dialog.ShowDialog<bool>(owner);
|
||||
}
|
||||
|
||||
private async Task ShowTeachingMode(Lesson lesson)
|
||||
private Task ShowTeachingMode(Lesson lesson)
|
||||
{
|
||||
var owner = TopLevel.GetTopLevel(this) as Window;
|
||||
var group = App.Services.GetRequiredService<IGroupRepository>().GetById(lesson.GroupId);
|
||||
if (owner is null || group is null) return;
|
||||
|
||||
var teachingModeVm = new TeachingModeViewModel(lesson, group,
|
||||
App.Services.GetRequiredService<IAlternativeLessonPathRepository>(),
|
||||
App.Services.GetRequiredService<ILessonRepository>(),
|
||||
App.Services.GetRequiredService<SeatingPlanTabViewModel>(),
|
||||
App.Services.GetRequiredService<ParticipationTabViewModel>());
|
||||
var window = new TeachingModeWindow { DataContext = teachingModeVm };
|
||||
await window.ShowDialog(owner);
|
||||
TeachingModeWindow.Open(lesson);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task ShowLessonViewerDialog(Lesson lesson)
|
||||
|
||||
Reference in New Issue
Block a user