Redesign Klassenlehrer Tab.
This commit is contained in:
@@ -12,6 +12,15 @@ public sealed record ClassTeacherClassRegisterRow(DateOnly Date, string? Subject
|
||||
string? TeacherUsername, string? CategoryName, string? CategoryGroup, string? Text)
|
||||
{
|
||||
public string DateLabel => Date.ToString("dd.MM.yyyy");
|
||||
public string StudentDisplayName => DisplayName(StudentName);
|
||||
public string CategoryColor => CategoryGroup?.Contains("Negativ", StringComparison.OrdinalIgnoreCase) == true
|
||||
? "#FF5A67" : "#4C8DFF";
|
||||
|
||||
private static string DisplayName(string value)
|
||||
{
|
||||
var parts = value.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
return parts.Length < 2 ? value : string.Join(" ", parts.Skip(1).Append(parts[0]));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Fehlzeiten eines/einer Schüler*in an einem Tag, über alle Fächer zusammengefasst -
|
||||
@@ -25,6 +34,14 @@ public sealed record ClassAbsenceDaySummaryRow(DateOnly Date, string StudentName
|
||||
string? Note, string? ExcuseNote, bool CountsAsFullDay)
|
||||
{
|
||||
public string DateLabel => Date.ToString("dd.MM.yyyy");
|
||||
public string StudentDisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
var parts = StudentName.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
return parts.Length < 2 ? StudentName : string.Join(" ", parts.Skip(1).Append(parts[0]));
|
||||
}
|
||||
}
|
||||
public string SubjectsLabel => string.Join(", ", Subjects);
|
||||
public string PeriodsLabel => string.Join(", ", PeriodNumbers.Order());
|
||||
public string StatusLabel => string.Join(", ", Statuses);
|
||||
@@ -33,6 +50,17 @@ public sealed record ClassAbsenceDaySummaryRow(DateOnly Date, string StudentName
|
||||
/// dezente Hervorhebung statt einer weiteren Text-/Icon-Spalte.
|
||||
public string RowBackground => CountsAsFullDay ? "#1FFFA000" : "Transparent";
|
||||
public string? FullDayTooltip => CountsAsFullDay ? "Ganzer Fehltag" : null;
|
||||
public bool IsLate => TotalAbsentPeriods == 0 || AbsenceReasons.Any(r =>
|
||||
r.Contains("verspät", StringComparison.OrdinalIgnoreCase));
|
||||
public bool IsUnexcused => Statuses.Any(s =>
|
||||
s.Contains("nicht entsch", StringComparison.OrdinalIgnoreCase));
|
||||
public string KindLabel => IsLate ? $"{TotalAbsentMinutes} Min. verspätet" :
|
||||
CountsAsFullDay ? "Ganzer Fehltag" : $"{TotalAbsentPeriods} Fehlstunde{(TotalAbsentPeriods == 1 ? "" : "n")}";
|
||||
public string FriendlyStatusLabel => IsUnexcused ? "Unentschuldigt" :
|
||||
Statuses.Any(s => s.Contains("entsch", StringComparison.OrdinalIgnoreCase)) ? "Entschuldigt" : StatusLabel;
|
||||
public string StatusColor => IsUnexcused ? "#FF5A67" : IsLate ? "#F59E0B" : "#73C45A";
|
||||
public string DetailLabel => string.Join(" · ", new[] { ReasonLabel, Note, ExcuseNote }
|
||||
.Where(value => !string.IsNullOrWhiteSpace(value)));
|
||||
|
||||
public static IReadOnlyList<ClassAbsenceDaySummaryRow> GroupByStudentAndDay(
|
||||
IEnumerable<UntisClassAbsenceEntryDto> entries) => entries
|
||||
@@ -78,12 +106,18 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
|
||||
public ObservableCollection<ClassTeacherClassRegisterRow> Entries { get; } = [];
|
||||
public ObservableCollection<ClassAbsenceDaySummaryRow> AbsenceEntries { get; } = [];
|
||||
|
||||
[ObservableProperty] private DateTimeOffset _startDate = DateTimeOffset.Now.AddDays(-7);
|
||||
[ObservableProperty] private DateTimeOffset _startDate = DateTimeOffset.Now.AddDays(-6);
|
||||
[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 = "";
|
||||
[ObservableProperty] private int _quickRangeIndex = 1;
|
||||
|
||||
public bool HasEntries => Entries.Count > 0;
|
||||
public bool HasAbsenceEntries => AbsenceEntries.Count > 0;
|
||||
public string ActiveFilterLabel => string.IsNullOrWhiteSpace(StudentFilter)
|
||||
? "Alle Schüler*innen" : StudentFilter;
|
||||
|
||||
public ClassTeacherDetailsViewModel(UntisReportCacheService cache)
|
||||
{
|
||||
@@ -93,9 +127,20 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
|
||||
public void Initialize(string className)
|
||||
{
|
||||
_className = className;
|
||||
StudentFilter = "";
|
||||
Entries.Clear();
|
||||
AbsenceEntries.Clear();
|
||||
Status = "Zeitraum wählen und laden.";
|
||||
NotifyListState();
|
||||
}
|
||||
|
||||
partial void OnStudentFilterChanged(string value) => OnPropertyChanged(nameof(ActiveFilterLabel));
|
||||
|
||||
partial void OnQuickRangeIndexChanged(int value)
|
||||
{
|
||||
var days = value switch { 0 => 0, 1 => 6, 2 => 29, _ => 6 };
|
||||
EndDate = DateTimeOffset.Now;
|
||||
StartDate = DateTimeOffset.Now.AddDays(-days);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -106,6 +151,16 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
|
||||
[RelayCommand]
|
||||
private Task Refresh() => LoadInternal(forceRefresh: true);
|
||||
|
||||
[RelayCommand]
|
||||
private Task ApplyFilter() => LoadInternal(forceRefresh: false);
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ClearStudentFilter()
|
||||
{
|
||||
StudentFilter = "";
|
||||
await LoadInternal(forceRefresh: false);
|
||||
}
|
||||
|
||||
private async Task LoadInternal(bool forceRefresh)
|
||||
{
|
||||
var start = DateOnly.FromDateTime(StartDate.LocalDateTime);
|
||||
@@ -113,7 +168,7 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
|
||||
if (end < start) { Status = "Das Enddatum darf nicht vor dem Startdatum liegen."; return; }
|
||||
if (string.IsNullOrWhiteSpace(_className)) { Status = "Keine Klasse ausgewählt."; return; }
|
||||
|
||||
Busy = true; Entries.Clear(); AbsenceEntries.Clear();
|
||||
Busy = true; Entries.Clear(); AbsenceEntries.Clear(); NotifyListState();
|
||||
try
|
||||
{
|
||||
var classRegisterTask = _cache.GetClassRegisterEventsAsync(_className, start, end, forceRefresh);
|
||||
@@ -137,9 +192,10 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
|
||||
|
||||
Status = $"{Entries.Count} Klassenbucheinträge anderer Lehrkräfte, " +
|
||||
$"{AbsenceEntries.Count} Fehlzeiten-Tage im Zeitraum.";
|
||||
NotifyListState();
|
||||
}
|
||||
catch (WebUntisIntegrationException ex) { Status = ex.Message; }
|
||||
finally { Busy = false; }
|
||||
finally { Busy = false; NotifyListState(); }
|
||||
}
|
||||
|
||||
// WebUntis liefert Namen je nach Bericht in anderer Reihenfolge als der Schülerreport, aus dem
|
||||
@@ -153,4 +209,10 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
|
||||
|
||||
private static bool TryDate(int value, out DateOnly date) =>
|
||||
DateOnly.TryParseExact(value.ToString(), "yyyyMMdd", out date);
|
||||
|
||||
private void NotifyListState()
|
||||
{
|
||||
OnPropertyChanged(nameof(HasEntries));
|
||||
OnPropertyChanged(nameof(HasAbsenceEntries));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user