WIP (unstable): Klassenlehrer-Feature auf Einstellungen/eigenen Bereich umgestellt
Nutzer-Feedback: Klassenlehrer ist eine Rolle für die ganze Klasse, unabhängig vom eigenen Unterricht - die Anbindung an ein LearningGroup.IsClassTeacher-Flag mit Tab in der Gruppendetailansicht war deshalb der falsche Ort. Ersetzt durch: - Klassenauswahl in den WebUntis-Einstellungen (WebUntisSettingsService.HomeroomClassName), über den bestehenden WebUntis-Klassenpicker. - Eigener Sidebar-Bereich "Klassenlehrer" statt Gruppen-Tab. - Zwei Ebenen: kompakte Roster-Übersicht mit Ampel-Symbolen (Fehlzeiten heute, Klassenbuch-Badge) und Details (die bisherigen Rohdaten-Listen, jetzt gefiltert auf einzelne Schüler*innen anspringbar). Noch nicht mit echtem WebUntis-Zugang gegengeprüft (siehe TODO.md). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+27
-16
@@ -1,10 +1,9 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Desktop.Services;
|
||||
|
||||
namespace LehrerApp.Desktop.ViewModels.Groups;
|
||||
namespace LehrerApp.Desktop.ViewModels.ClassTeacher;
|
||||
|
||||
/// <summary>Eine Zeile aus dem "-alle-"-Klassenbuchbericht, die eine andere Lehrkraft angelegt hat.
|
||||
/// Rein zur Ansicht - anders als beim Dokumentations-Abgleich (eigene Einträge) gibt es hier kein
|
||||
@@ -57,19 +56,19 @@ public sealed record ClassAbsenceDaySummaryRow(DateOnly Date, string StudentName
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Klassenlehrer-Tab: zeigt Klassenbucheinträge, die andere Lehrkräfte zu Schülern dieser Klasse
|
||||
/// angelegt haben (WebUntis "-alle-"-Bericht, gefiltert auf fremde statt der eigenen
|
||||
/// Benutzerkennung wie beim bestehenden Dokumentations-Abgleich, siehe
|
||||
/// Details-Ansicht des Klassenlehrer-Bereichs: Klassenbucheinträge, die andere Lehrkräfte zu
|
||||
/// Schülern der Klasse angelegt haben (WebUntis "-alle-"-Bericht, gefiltert auf fremde statt der
|
||||
/// eigenen Benutzerkennung wie beim bestehenden Dokumentations-Abgleich, siehe
|
||||
/// <see cref="WebUntisIntegrationService.GetForeignClassRegisterEventsAsync"/>), sowie die
|
||||
/// Fehlzeiten der Klasse über alle Fächer (<see cref="WebUntisIntegrationService.GetClassAbsencesAsync"/>).
|
||||
/// Beides rein zur Ansicht, kein Übernehmen in lokale Daten. Nur für Gruppen mit
|
||||
/// <see cref="Core.Models.LearningGroup.IsClassTeacher"/> sichtbar (siehe
|
||||
/// GroupDetailViewModel/GroupDetailView).
|
||||
/// Beides rein zur Ansicht, kein Übernehmen in lokale Daten. Bewusst unabhängig von jeder
|
||||
/// <see cref="Core.Models.LearningGroup"/> — Klassenlehrer ist man für die ganze Klasse, nicht für
|
||||
/// einen einzelnen Unterricht; die Klasse kommt aus <see cref="ClassTeacherOverviewViewModel"/>
|
||||
/// (letztlich aus den WebUntis-Einstellungen), nicht aus einer Gruppen-ID.
|
||||
/// </summary>
|
||||
public partial class ClassTeacherTabViewModel : ObservableObject
|
||||
public partial class ClassTeacherDetailsViewModel : ObservableObject
|
||||
{
|
||||
private readonly WebUntisIntegrationService _untis;
|
||||
private readonly IGroupRepository _groups;
|
||||
private string _className = "";
|
||||
|
||||
public ObservableCollection<ClassTeacherClassRegisterRow> Entries { get; } = [];
|
||||
@@ -79,15 +78,17 @@ public partial class ClassTeacherTabViewModel : ObservableObject
|
||||
[ObservableProperty] private DateTimeOffset _endDate = DateTimeOffset.Now;
|
||||
[ObservableProperty] private string _status = "Zeitraum wählen und laden.";
|
||||
[ObservableProperty] private bool _busy;
|
||||
/// Von der Übersicht gesetzt (Klick auf eine Roster-Zeile) - leer zeigt alle Schüler*innen.
|
||||
[ObservableProperty] private string _studentFilter = "";
|
||||
|
||||
public ClassTeacherTabViewModel(WebUntisIntegrationService untis, IGroupRepository groups)
|
||||
public ClassTeacherDetailsViewModel(WebUntisIntegrationService untis)
|
||||
{
|
||||
_untis = untis; _groups = groups;
|
||||
_untis = untis;
|
||||
}
|
||||
|
||||
public void Initialize(Guid groupId)
|
||||
public void Initialize(string className)
|
||||
{
|
||||
_className = _groups.GetById(groupId)?.Name ?? "";
|
||||
_className = className;
|
||||
Entries.Clear();
|
||||
AbsenceEntries.Clear();
|
||||
Status = "Zeitraum wählen und laden.";
|
||||
@@ -99,7 +100,7 @@ public partial class ClassTeacherTabViewModel : ObservableObject
|
||||
var start = DateOnly.FromDateTime(StartDate.LocalDateTime);
|
||||
var end = DateOnly.FromDateTime(EndDate.LocalDateTime);
|
||||
if (end < start) { Status = "Das Enddatum darf nicht vor dem Startdatum liegen."; return; }
|
||||
if (string.IsNullOrWhiteSpace(_className)) { Status = "Dieser Gruppe fehlt ein Klassenname."; return; }
|
||||
if (string.IsNullOrWhiteSpace(_className)) { Status = "Keine Klasse ausgewählt."; return; }
|
||||
|
||||
Busy = true; Entries.Clear(); AbsenceEntries.Clear();
|
||||
try
|
||||
@@ -109,6 +110,7 @@ public partial class ClassTeacherTabViewModel : ObservableObject
|
||||
await Task.WhenAll(classRegisterTask, absencesTask);
|
||||
|
||||
var ordered = classRegisterTask.Result
|
||||
.Where(MatchesStudentFilter)
|
||||
.Select(e => (Entry: e, Date: TryDate(e.Date, out var d) ? d : (DateOnly?)null))
|
||||
.Where(x => x.Date is not null)
|
||||
.OrderByDescending(x => x.Date).ThenBy(x => x.Entry.StudentName);
|
||||
@@ -118,7 +120,8 @@ public partial class ClassTeacherTabViewModel : ObservableObject
|
||||
entry.TeacherUsername, entry.CategoryName, entry.CategoryGroup, entry.Text));
|
||||
}
|
||||
|
||||
foreach (var row in ClassAbsenceDaySummaryRow.GroupByStudentAndDay(absencesTask.Result))
|
||||
var absences = absencesTask.Result.Where(MatchesStudentFilter);
|
||||
foreach (var row in ClassAbsenceDaySummaryRow.GroupByStudentAndDay(absences))
|
||||
AbsenceEntries.Add(row);
|
||||
|
||||
Status = $"{Entries.Count} Klassenbucheinträge anderer Lehrkräfte, " +
|
||||
@@ -128,6 +131,14 @@ public partial class ClassTeacherTabViewModel : ObservableObject
|
||||
finally { Busy = false; }
|
||||
}
|
||||
|
||||
private bool MatchesStudentFilter(UntisForeignClassRegisterEventDto entry) =>
|
||||
string.IsNullOrWhiteSpace(StudentFilter) ||
|
||||
string.Equals(entry.StudentName, StudentFilter, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private bool MatchesStudentFilter(UntisClassAbsenceEntryDto entry) =>
|
||||
string.IsNullOrWhiteSpace(StudentFilter) ||
|
||||
string.Equals(entry.StudentName, StudentFilter, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static bool TryDate(int value, out DateOnly date) =>
|
||||
DateOnly.TryParseExact(value.ToString(), "yyyyMMdd", out date);
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using LehrerApp.Desktop.Services;
|
||||
|
||||
namespace LehrerApp.Desktop.ViewModels.ClassTeacher;
|
||||
|
||||
/// <summary>Eine Zeile der Klassenlehrer-Übersicht ("auf einen Blick", Nutzer-Feedback) - pro
|
||||
/// Schüler*in ein Ampel-Symbol für "heute laut WebUntis keine gemeldete Fehlzeit" und ein Badge,
|
||||
/// falls in den letzten Tagen ein Klassenbucheintrag einer anderen Lehrkraft angelegt wurde.
|
||||
/// Bewusst kein Bezug zu eigenem Unterricht/eigener Anwesenheitserfassung - genau der Fall, den es
|
||||
/// abzudecken gilt, ist der Tag ohne eigenen Unterricht mit der Klasse.</summary>
|
||||
public sealed record ClassTeacherRosterRow(string StudentName, int? ExternKey, bool HasAbsenceToday,
|
||||
string? AbsenceTooltip, bool HasRecentClassRegisterEntry)
|
||||
{
|
||||
public string StatusIcon => HasAbsenceToday ? "⚠" : "✓";
|
||||
|
||||
/// <summary>Führt die WebUntis-Schülerliste der Klasse mit den heutigen Fehlzeiten und den
|
||||
/// jüngsten Klassenbucheinträgen zusammen. Zuordnung primär über <c>ExternKey</c> (beide
|
||||
/// stammen aus WebUntis, gleiche Kennung), Fallback auf Namensgleichheit für den
|
||||
/// Klassenbuch-Bericht, der keinen ExternKey liefert (siehe UntisForeignClassRegisterEventDto).</summary>
|
||||
public static IReadOnlyList<ClassTeacherRosterRow> Build(
|
||||
IReadOnlyList<UntisStudentDto> students,
|
||||
IReadOnlyList<ClassAbsenceDaySummaryRow> todayAbsences,
|
||||
IReadOnlyList<UntisForeignClassRegisterEventDto> recentClassRegisterEntries)
|
||||
{
|
||||
var absenceByKey = todayAbsences
|
||||
.Where(a => a.ExternKey is not null)
|
||||
.GroupBy(a => a.ExternKey!.Value)
|
||||
.ToDictionary(g => g.Key, g => g.First());
|
||||
var absenceByName = todayAbsences
|
||||
.GroupBy(a => a.StudentName, StringComparer.OrdinalIgnoreCase)
|
||||
.ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);
|
||||
var recentNames = recentClassRegisterEntries
|
||||
.Select(e => e.StudentName)
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
return students
|
||||
.Select(s =>
|
||||
{
|
||||
var absence = absenceByKey.GetValueOrDefault(s.ExternKey)
|
||||
?? absenceByName.GetValueOrDefault(s.DisplayName);
|
||||
return new ClassTeacherRosterRow(s.DisplayName, s.ExternKey, absence is not null,
|
||||
absence is null ? null : $"{absence.TotalAbsentPeriods} Stunde(n) — {absence.StatusLabel}",
|
||||
recentNames.Contains(s.DisplayName));
|
||||
})
|
||||
.OrderBy(r => r.StudentName)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Top-Level-Bereich "Klassenlehrer" (eigener Sidebar-Eintrag, siehe MainWindowViewModel) - bewusst
|
||||
/// nicht an eine LearningGroup gehängt, da man Klassenlehrer für eine ganze Klasse ist, unabhängig
|
||||
/// vom eigenen Unterricht (Nutzer-Feedback zur ursprünglichen Umsetzung als Gruppen-Tab). Die
|
||||
/// Klasse selbst kommt aus den WebUntis-Einstellungen (<see cref="WebUntisSettingsService.HomeroomClassName"/>).
|
||||
/// Zwei Ebenen: Übersicht (dieses ViewModel, kompaktes Roster) und Details
|
||||
/// (<see cref="ClassTeacherDetailsViewModel"/>, die bisherigen Rohdaten-Listen) - ein Klick auf
|
||||
/// eine Roster-Zeile springt gefiltert in die Details.
|
||||
/// </summary>
|
||||
public partial class ClassTeacherOverviewViewModel : ObservableObject
|
||||
{
|
||||
private readonly WebUntisSettingsService _settings;
|
||||
private readonly WebUntisIntegrationService _untis;
|
||||
|
||||
public ClassTeacherDetailsViewModel DetailsTab { get; }
|
||||
public ObservableCollection<ClassTeacherRosterRow> Roster { get; } = [];
|
||||
|
||||
[ObservableProperty] private string? _homeroomClassName;
|
||||
[ObservableProperty] private int _activeTabIndex;
|
||||
[ObservableProperty] private string _status = "";
|
||||
[ObservableProperty] private bool _busy;
|
||||
|
||||
public bool HomeroomClassConfigured => !string.IsNullOrWhiteSpace(HomeroomClassName);
|
||||
|
||||
/// Vom Code-Behind/MainWindowViewModel gesetzt: springt in die WebUntis-Einstellungen, wenn noch
|
||||
/// keine Klasse gewählt ist.
|
||||
public Func<Task>? OnNavigateToSettings { get; set; }
|
||||
|
||||
public ClassTeacherOverviewViewModel(WebUntisSettingsService settings, WebUntisIntegrationService untis,
|
||||
ClassTeacherDetailsViewModel detailsTab)
|
||||
{
|
||||
_settings = settings; _untis = untis; DetailsTab = detailsTab;
|
||||
}
|
||||
|
||||
partial void OnHomeroomClassNameChanged(string? value) => OnPropertyChanged(nameof(HomeroomClassConfigured));
|
||||
|
||||
[RelayCommand]
|
||||
private async Task Load()
|
||||
{
|
||||
HomeroomClassName = _settings.HomeroomClassName;
|
||||
ActiveTabIndex = 0;
|
||||
Roster.Clear();
|
||||
if (!HomeroomClassConfigured)
|
||||
{
|
||||
Status = "Noch keine Klasse ausgewählt.";
|
||||
return;
|
||||
}
|
||||
|
||||
var className = HomeroomClassName!;
|
||||
DetailsTab.Initialize(className);
|
||||
Busy = true;
|
||||
try
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var studentsTask = _untis.GetStudentsAsync(className);
|
||||
var absencesTask = _untis.GetClassAbsencesAsync(className, today, today);
|
||||
var classRegisterTask = _untis.GetForeignClassRegisterEventsAsync(className, today.AddDays(-7), today);
|
||||
await Task.WhenAll(studentsTask, absencesTask, classRegisterTask);
|
||||
|
||||
var todayAbsences = ClassAbsenceDaySummaryRow.GroupByStudentAndDay(absencesTask.Result);
|
||||
foreach (var row in ClassTeacherRosterRow.Build(studentsTask.Result.Students, todayAbsences,
|
||||
classRegisterTask.Result))
|
||||
Roster.Add(row);
|
||||
Status = $"{Roster.Count} Schüler*innen · Stand heute.";
|
||||
}
|
||||
catch (WebUntisIntegrationException ex) { Status = ex.Message; }
|
||||
finally { Busy = false; }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ShowDetailsForStudent(ClassTeacherRosterRow? row)
|
||||
{
|
||||
if (row is null) return;
|
||||
DetailsTab.StudentFilter = row.StudentName;
|
||||
ActiveTabIndex = 1;
|
||||
DetailsTab.LoadCommand.Execute(null);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task GoToSettings()
|
||||
{
|
||||
if (OnNavigateToSettings is not null) await OnNavigateToSettings();
|
||||
}
|
||||
}
|
||||
@@ -251,7 +251,6 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
// bevor LoadGroup() läuft (siehe MainWindowViewModel.NavigateToGroupDetail), Group ist dann
|
||||
// kurzzeitig null — ein verschachtelter Pfad würde dafür jedes Mal einen Binding-Fehler loggen.
|
||||
public bool IsDifferentiated => Group?.IsDifferentiated ?? false;
|
||||
public bool IsClassTeacher => Group?.IsClassTeacher ?? false;
|
||||
public bool IsReadOnly => Group is { IsActive: false };
|
||||
public bool IsEditable => Group is { IsActive: true };
|
||||
public string SubjectName { get; private set; } = "";
|
||||
@@ -259,7 +258,6 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
partial void OnGroupChanged(LearningGroup? value)
|
||||
{
|
||||
OnPropertyChanged(nameof(IsDifferentiated));
|
||||
OnPropertyChanged(nameof(IsClassTeacher));
|
||||
OnPropertyChanged(nameof(IsReadOnly));
|
||||
OnPropertyChanged(nameof(IsEditable));
|
||||
NotifyWriteCommands();
|
||||
@@ -281,7 +279,6 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
public CompetencyOverviewTabViewModel CompetencyOverviewTab { get; }
|
||||
public SeatingPlanTabViewModel SeatingPlanTab { get; }
|
||||
public GroupDocumentationTabViewModel GroupDocumentationTab { get; }
|
||||
public ClassTeacherTabViewModel ClassTeacherTab { get; }
|
||||
public Func<Task<bool>>? OnAddStudent { get; set; }
|
||||
public Func<StudentSummary, Task<bool>>? OnWithdrawStudent { get; set; }
|
||||
public Func<Guid, Task<bool>>? OnAddExam { get; set; }
|
||||
@@ -298,8 +295,7 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
GroupOverviewViewModel overviewTab, ParticipationTabViewModel participationTab,
|
||||
GradeOverviewTabViewModel gradeOverviewTab,
|
||||
PlanningTabViewModel planningTab, CompetencyOverviewTabViewModel competencyOverviewTab,
|
||||
SeatingPlanTabViewModel seatingPlanTab, GroupDocumentationTabViewModel groupDocumentationTab,
|
||||
ClassTeacherTabViewModel classTeacherTab)
|
||||
SeatingPlanTabViewModel seatingPlanTab, GroupDocumentationTabViewModel groupDocumentationTab)
|
||||
{
|
||||
_groups = groups; _students = students; _memberships = memberships; _subjects = subjects;
|
||||
_exams = exams; _grades = grades; _tasks = tasks;
|
||||
@@ -310,7 +306,6 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
CompetencyOverviewTab = competencyOverviewTab;
|
||||
SeatingPlanTab = seatingPlanTab;
|
||||
GroupDocumentationTab = groupDocumentationTab;
|
||||
ClassTeacherTab = classTeacherTab;
|
||||
SeatingPlanTab.OnAssessmentChanged = () =>
|
||||
{
|
||||
ParticipationTab.LoadSessions();
|
||||
@@ -340,7 +335,6 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
CompetencyOverviewTab.Initialize(Group);
|
||||
SeatingPlanTab.Initialize(Group.Id, IsReadOnly);
|
||||
GroupDocumentationTab.Initialize(Group.Id);
|
||||
ClassTeacherTab.Initialize(Group.Id);
|
||||
}
|
||||
|
||||
private void ReloadExams()
|
||||
@@ -883,7 +877,6 @@ public partial class AddGroupDialogViewModel : ObservableObject
|
||||
[ObservableProperty] private int? _hoursPerWeek;
|
||||
[ObservableProperty] private bool _isOwnClass;
|
||||
[ObservableProperty] private bool _isDifferentiated;
|
||||
[ObservableProperty] private bool _isClassTeacher;
|
||||
[ObservableProperty] private bool _requiresLessonPlanning = true;
|
||||
[ObservableProperty] private int? _webUntisLessonId;
|
||||
[ObservableProperty] private string _nameError = "";
|
||||
@@ -924,7 +917,6 @@ public partial class AddGroupDialogViewModel : ObservableObject
|
||||
HoursPerWeek = group.HoursPerWeek;
|
||||
IsOwnClass = group.IsOwnClass;
|
||||
IsDifferentiated = group.IsDifferentiated;
|
||||
IsClassTeacher = group.IsClassTeacher;
|
||||
RequiresLessonPlanning = group.RequiresLessonPlanning;
|
||||
WebUntisLessonId = group.WebUntisLessonId;
|
||||
OnPropertyChanged(nameof(DialogTitle));
|
||||
@@ -972,7 +964,6 @@ public partial class AddGroupDialogViewModel : ObservableObject
|
||||
Result.HoursPerWeek = HoursPerWeek;
|
||||
Result.IsOwnClass = IsOwnClass;
|
||||
Result.IsDifferentiated = IsDifferentiated;
|
||||
Result.IsClassTeacher = IsClassTeacher;
|
||||
Result.RequiresLessonPlanning = RequiresLessonPlanning;
|
||||
Result.WebUntisLessonId = WebUntisLessonId;
|
||||
_groups.Save(Result);
|
||||
|
||||
@@ -3,6 +3,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.Services;
|
||||
using LehrerApp.Desktop.ViewModels.ClassTeacher;
|
||||
using LehrerApp.Desktop.ViewModels.Groups;
|
||||
using LehrerApp.Desktop.ViewModels.Planning;
|
||||
using LehrerApp.Desktop.ViewModels.Settings;
|
||||
@@ -31,6 +32,7 @@ public partial class MainWindowViewModel : ObservableObject
|
||||
public bool IsExamsActive => ActiveNavItem == NavItem.Exams;
|
||||
public bool IsPlannerActive => ActiveNavItem == NavItem.Planner;
|
||||
public bool IsWorkloadActive => ActiveNavItem == NavItem.Workload;
|
||||
public bool IsClassTeacherActive => ActiveNavItem == NavItem.ClassTeacher;
|
||||
public bool IsSettingsActive => ActiveNavItem == NavItem.Settings;
|
||||
|
||||
public MainWindowViewModel(IServiceProvider services,
|
||||
@@ -71,6 +73,7 @@ public partial class MainWindowViewModel : ObservableObject
|
||||
case TimetableViewModel vm: vm.Load(); break;
|
||||
case WorkloadViewModel vm:
|
||||
vm.Tasks.Load(); vm.TimeTracking.Load(); vm.Evaluation.Load(); break;
|
||||
case ClassTeacherOverviewViewModel vm: vm.LoadCommand.Execute(null); break;
|
||||
case GroupDetailViewModel { Group: { } group } vm: vm.LoadGroup(group.Id); break;
|
||||
// Inline-Bearbeitung (IsEditing) nicht überschreiben - anders als die Gruppenansicht
|
||||
// laufen Namens-/Geschlechtsänderungen hier nicht über einen Dialog.
|
||||
@@ -87,6 +90,7 @@ public partial class MainWindowViewModel : ObservableObject
|
||||
OnPropertyChanged(nameof(IsExamsActive));
|
||||
OnPropertyChanged(nameof(IsPlannerActive));
|
||||
OnPropertyChanged(nameof(IsWorkloadActive));
|
||||
OnPropertyChanged(nameof(IsClassTeacherActive));
|
||||
OnPropertyChanged(nameof(IsSettingsActive));
|
||||
}
|
||||
|
||||
@@ -102,6 +106,7 @@ public partial class MainWindowViewModel : ObservableObject
|
||||
NavItem.Exams => new PlaceholderViewModel { Title = "Klausuren", Icon = "📝" },
|
||||
NavItem.Planner => GetTimetable(),
|
||||
NavItem.Workload => GetWorkload(),
|
||||
NavItem.ClassTeacher => GetClassTeacherOverview(),
|
||||
NavItem.Settings => _services.GetRequiredService<SettingsViewModel>(),
|
||||
_ => CurrentPage,
|
||||
};
|
||||
@@ -141,6 +146,14 @@ public partial class MainWindowViewModel : ObservableObject
|
||||
return workload;
|
||||
}
|
||||
|
||||
private ClassTeacherOverviewViewModel GetClassTeacherOverview()
|
||||
{
|
||||
var vm = _services.GetRequiredService<ClassTeacherOverviewViewModel>();
|
||||
vm.OnNavigateToSettings = () => { NavigateToSettings(SettingsTab.WebUntis); return Task.CompletedTask; };
|
||||
vm.LoadCommand.Execute(null);
|
||||
return vm;
|
||||
}
|
||||
|
||||
public void NavigateToGroupDetail(Guid groupId, int initialTab = 0)
|
||||
{
|
||||
ActiveNavItem = NavItem.Groups;
|
||||
@@ -175,7 +188,7 @@ public partial class MainWindowViewModel : ObservableObject
|
||||
public void NavigateToWorkload() => NavigateTo(NavItem.Workload);
|
||||
}
|
||||
|
||||
public enum NavItem { Dashboard, Groups, Students, Exams, Planner, Workload, Settings }
|
||||
public enum NavItem { Dashboard, Groups, Students, Exams, Planner, Workload, ClassTeacher, Settings }
|
||||
|
||||
public partial class PlaceholderViewModel : ObservableObject
|
||||
{
|
||||
|
||||
@@ -217,7 +217,14 @@ public partial class SettingsViewModel : ObservableObject
|
||||
[ObservableProperty] private string _untisPassword = "";
|
||||
[ObservableProperty] private string _untisApiStatus = "";
|
||||
[ObservableProperty] private bool _untisApiBusy;
|
||||
[ObservableProperty] private string? _untisHomeroomClassName;
|
||||
public string UntisHomeroomClassDisplay => UntisHomeroomClassName is { Length: > 0 } name
|
||||
? $"Ausgewählt: {name}"
|
||||
: "Keine Klasse ausgewählt.";
|
||||
partial void OnUntisHomeroomClassNameChanged(string? value) => OnPropertyChanged(nameof(UntisHomeroomClassDisplay));
|
||||
public Func<Task>? OnReviewUntisMapping { get; set; }
|
||||
/// Vom Code-Behind gesetzt: öffnet den WebUntis-Klassenauswahldialog, liefert null bei Abbruch.
|
||||
public Func<Task<(int UntisId, string Name)?>>? OnPickHomeroomClass { get; set; }
|
||||
|
||||
// ── Schulweiter Jahresplan (ClassyPlan-iCal) ─────────────────────────────
|
||||
|
||||
@@ -559,6 +566,7 @@ public partial class SettingsViewModel : ObservableObject
|
||||
UntisUsername = credentials.Username;
|
||||
UntisApiStatus = $"API-Zugang für {credentials.Username} ist lokal verschlüsselt gespeichert.";
|
||||
}
|
||||
UntisHomeroomClassName = _untisSettings.HomeroomClassName;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -647,6 +655,23 @@ public partial class SettingsViewModel : ObservableObject
|
||||
if (OnReviewUntisMapping is not null) await OnReviewUntisMapping();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task UntisPickHomeroomClass()
|
||||
{
|
||||
if (OnPickHomeroomClass is null) return;
|
||||
var result = await OnPickHomeroomClass();
|
||||
if (result is null) return;
|
||||
_untisSettings.SetHomeroomClass(result.Value.UntisId, result.Value.Name);
|
||||
LoadUntisSettings();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void UntisClearHomeroomClass()
|
||||
{
|
||||
_untisSettings.SetHomeroomClass(null, null);
|
||||
LoadUntisSettings();
|
||||
}
|
||||
|
||||
// ── Schulweiter Jahresplan: Laden / Speichern / Entfernen / Jetzt abrufen ─
|
||||
|
||||
private void LoadAnnualPlanSettings()
|
||||
|
||||
Reference in New Issue
Block a user