Kurs-Übersicht gefüllt: nächste Termine, Mitarbeitshinweis, offene Kontrollen/Dokumentationen, auffällige Fehlzeiten
Der bislang leere "Übersicht"-Tab je Kurs zeigt jetzt einen kompakten "Was steht an"-Überblick aus bereits vorhandenen Daten mit Klick-Durchsprung in den jeweiligen Tab: nächste Stunde/Klausur, Hinweis auf die letzte Mitarbeitssitzung, offene Hausaufgaben-Kontrolle, offene Entschuldigungen, offene Dokumentations-Entwürfe und auffällige Fehlzeiten. Die Fehlzeiten-Karte ist bewusst kein 1:1-Abbild der Dashboard-Warnung: erst ab einer Mindeststichprobe von 8 erfassten Terminen im Schuljahr gemeldet, damit ein einzelner Fehltag zu Schuljahresbeginn nicht sofort als auffällig gilt. Das Hauptdashboard hat dasselbe Problem noch, wird dort separat nachgezogen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -249,6 +249,7 @@ public static class AppBootstrapper
|
||||
// Transient: neue Instanz pro Navigation (für Detailseiten)
|
||||
services.AddTransient<GroupDetailViewModel>();
|
||||
services.AddTransient<StudentDetailViewModel>();
|
||||
services.AddTransient<GroupOverviewViewModel>();
|
||||
services.AddTransient<ParticipationTabViewModel>();
|
||||
services.AddTransient<GradeOverviewTabViewModel>();
|
||||
services.AddTransient<PlanningTabViewModel>();
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.ViewModels;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace LehrerApp.Desktop.ViewModels.Groups;
|
||||
|
||||
// ── Tab: Übersicht — kompakte "Was steht an"-Zusammenfassung eines Kurses ────
|
||||
|
||||
/// <summary>
|
||||
/// Zieht keine eigenen neuen Daten — jede Karte liest nur, was die anderen Tabs (Planung,
|
||||
/// Klausuren, Mitarbeit) ohnehin schon verwalten, und verweist per Klick dorthin. Bewusst kein
|
||||
/// Ersatz für die Detail-Tabs, sondern ein schneller Überblick "was ist als nächstes dran /
|
||||
/// was wurde vergessen".
|
||||
/// </summary>
|
||||
public partial class GroupOverviewViewModel : ObservableObject
|
||||
{
|
||||
private const int OpenExcuseMaxAgeDays = 21;
|
||||
private const int HomeworkCheckLookbackDays = 120;
|
||||
private const int NextLessonLookaheadDays = 90;
|
||||
|
||||
/// Unterhalb dieser Anzahl erfasster Anwesenheits-Einträge im laufenden Schuljahr wird eine
|
||||
/// hohe Fehlquote bewusst NICHT gemeldet — wer zu Schuljahresbeginn zweimal fehlt, hat rein
|
||||
/// rechnerisch schon 100 %, das ist noch kein auffälliges Muster, nur eine zu kleine
|
||||
/// Stichprobe. Dasselbe Problem hat aktuell noch die Fehlzeiten-Warnung im Hauptdashboard
|
||||
/// (AttendanceBalanceService/AttendanceWarningItem) — bewusst unangetastet, wird dort separat
|
||||
/// nachgezogen.
|
||||
private const int AttendanceMinSampleSize = 8;
|
||||
|
||||
private readonly ILessonRepository _lessons;
|
||||
private readonly IExamRepository _exams;
|
||||
private readonly IParticipationSessionRepository _sessions;
|
||||
private readonly IParticipationRepository _entries;
|
||||
private readonly IStudentRepository _students;
|
||||
private readonly IDocumentationRepository _documentation;
|
||||
private readonly AttendanceBalanceService _attendanceBalance;
|
||||
private readonly SchoolYearService _schoolYear;
|
||||
|
||||
private Guid _groupId;
|
||||
private string _groupName = "";
|
||||
|
||||
[ObservableProperty] private bool _hasNextLesson;
|
||||
[ObservableProperty] private string _nextLessonLabel = "";
|
||||
[ObservableProperty] private bool _hasNextExam;
|
||||
[ObservableProperty] private string _nextExamLabel = "";
|
||||
[ObservableProperty] private string _participationHintLabel = "";
|
||||
[ObservableProperty] private bool _participationHintIsStale;
|
||||
[ObservableProperty] private bool _hasOpenHomeworkCheck;
|
||||
[ObservableProperty] private string _openHomeworkCheckLabel = "";
|
||||
[ObservableProperty] private int _draftDocumentationCount;
|
||||
|
||||
public ObservableCollection<OpenExcuseItem> OpenExcuses { get; } = [];
|
||||
public bool HasOpenExcuses => OpenExcuses.Count > 0;
|
||||
|
||||
/// Absichtlich zurückhaltender formuliert/gestylt als das Hauptdashboard (kein "Warnfall") —
|
||||
/// siehe <see cref="AttendanceMinSampleSize"/>.
|
||||
public ObservableCollection<AttendanceWarningItem> AttendanceNotices { get; } = [];
|
||||
public bool HasAttendanceNotices => AttendanceNotices.Count > 0;
|
||||
|
||||
/// Ein Delegate statt einem pro Karte, mit dem Ziel-Tab-Index von GroupDetailView.axaml als
|
||||
/// Parameter (0 Übersicht, 1 Schüler, 2 Sitzpläne, 3 Mitarbeit, 4 Klausuren, 5 Noten,
|
||||
/// 6 Planung, 7 Kompetenzen, 8 Dokumentation).
|
||||
public Action<int>? OnNavigateToTab { get; set; }
|
||||
|
||||
[RelayCommand] private void NavigateToPlanning() => OnNavigateToTab?.Invoke(6);
|
||||
[RelayCommand] private void NavigateToExams() => OnNavigateToTab?.Invoke(4);
|
||||
[RelayCommand] private void NavigateToParticipation() => OnNavigateToTab?.Invoke(3);
|
||||
[RelayCommand] private void NavigateToDocumentation() => OnNavigateToTab?.Invoke(8);
|
||||
|
||||
public GroupOverviewViewModel(ILessonRepository lessons, IExamRepository exams,
|
||||
IParticipationSessionRepository sessions, IParticipationRepository entries,
|
||||
IStudentRepository students, IDocumentationRepository documentation,
|
||||
AttendanceBalanceService attendanceBalance, SchoolYearService schoolYear)
|
||||
{
|
||||
_lessons = lessons; _exams = exams; _sessions = sessions;
|
||||
_entries = entries; _students = students; _documentation = documentation;
|
||||
_attendanceBalance = attendanceBalance; _schoolYear = schoolYear;
|
||||
}
|
||||
|
||||
public void Initialize(Guid groupId, string groupName)
|
||||
{
|
||||
_groupId = groupId;
|
||||
_groupName = groupName;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
LoadNextLesson(today);
|
||||
LoadNextExam(today);
|
||||
LoadParticipationHint(today);
|
||||
LoadOpenHomeworkCheck(today);
|
||||
LoadOpenExcuses(today);
|
||||
LoadDraftDocumentationCount();
|
||||
LoadAttendanceNotices(today);
|
||||
}
|
||||
|
||||
private void LoadNextLesson(DateOnly today)
|
||||
{
|
||||
var next = _lessons.GetByGroupAndRange(_groupId, today, today.AddDays(NextLessonLookaheadDays))
|
||||
.Where(l => l.Status == LessonStatus.Planned)
|
||||
.OrderBy(l => l.Date).ThenBy(l => l.LessonNumber ?? 0)
|
||||
.FirstOrDefault();
|
||||
|
||||
HasNextLesson = next is not null;
|
||||
NextLessonLabel = next is null ? ""
|
||||
: string.IsNullOrWhiteSpace(next.Topic)
|
||||
? next.Date.ToString("dd.MM.yyyy")
|
||||
: $"{next.Date:dd.MM.yyyy} — {next.Topic}";
|
||||
}
|
||||
|
||||
private void LoadNextExam(DateOnly today)
|
||||
{
|
||||
var next = _exams.GetByGroup(_groupId).Where(e => e.Date >= today).MinBy(e => e.Date);
|
||||
HasNextExam = next is not null;
|
||||
NextExamLabel = next is null ? "" : $"{next.Date:dd.MM.yyyy} — {next.Title}";
|
||||
}
|
||||
|
||||
/// Erinnert nicht an eine Note, sondern schlicht daran, überhaupt wieder eine Sitzung
|
||||
/// anzulegen — genau das vergisst man in Kursen, die man seltener unterrichtet, zuerst.
|
||||
private void LoadParticipationHint(DateOnly today)
|
||||
{
|
||||
var last = _sessions.GetByGroup(_groupId).OrderByDescending(s => s.Date).FirstOrDefault();
|
||||
if (last is null)
|
||||
{
|
||||
ParticipationHintLabel = "Noch keine Mitarbeitssitzung angelegt.";
|
||||
ParticipationHintIsStale = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var daysAgo = today.DayNumber - last.Date.DayNumber;
|
||||
ParticipationHintLabel = daysAgo <= 0
|
||||
? "Letzte Sitzung: heute."
|
||||
: $"Letzte Sitzung: {last.Date:dd.MM.yyyy} (vor {daysAgo} Tag(en)).";
|
||||
ParticipationHintIsStale = daysAgo > 14;
|
||||
}
|
||||
|
||||
/// Dieselbe Erkennung wie das Stundenplan-Badge "Hausaufgabe kontrollieren"
|
||||
/// (TimetableViewModel.HasUnhandledHomework) — bewusst nur die unmittelbar letzte Lesson,
|
||||
/// nicht die gesamte Historie.
|
||||
private void LoadOpenHomeworkCheck(DateOnly today)
|
||||
{
|
||||
var previous = _lessons.GetByGroupAndRange(_groupId, today.AddDays(-HomeworkCheckLookbackDays), today.AddDays(-1))
|
||||
.OrderByDescending(l => l.Date).ThenByDescending(l => l.LessonNumber ?? 0)
|
||||
.FirstOrDefault();
|
||||
|
||||
var open = previous is not null && !string.IsNullOrWhiteSpace(previous.Homework)
|
||||
&& !previous.HomeworkChecked && !previous.HomeworkCheckDismissed;
|
||||
|
||||
HasOpenHomeworkCheck = open;
|
||||
OpenHomeworkCheckLabel = open
|
||||
? $"Aus der Stunde vom {previous!.Date:dd.MM.yyyy}: {previous.Homework}"
|
||||
: "";
|
||||
}
|
||||
|
||||
private void LoadOpenExcuses(DateOnly today)
|
||||
{
|
||||
OpenExcuses.Clear();
|
||||
var cutoff = today.AddDays(-OpenExcuseMaxAgeDays);
|
||||
|
||||
var items = new List<OpenExcuseItem>();
|
||||
foreach (var session in _sessions.GetByGroup(_groupId).Where(s => s.Date >= cutoff && s.Date <= today))
|
||||
{
|
||||
foreach (var entry in _entries.GetBySession(session.Id)
|
||||
.Where(e => e.Attendance == AttendanceStatus.ExcusePending))
|
||||
{
|
||||
var student = _students.GetById(entry.StudentId);
|
||||
if (student is null) continue;
|
||||
var item = new OpenExcuseItem(session.Id, entry.StudentId, student.FullName, _groupName, session.Date);
|
||||
item.OnResolve = ResolveExcuse;
|
||||
items.Add(item);
|
||||
}
|
||||
}
|
||||
foreach (var item in items.OrderBy(i => i.Date))
|
||||
OpenExcuses.Add(item);
|
||||
OnPropertyChanged(nameof(HasOpenExcuses));
|
||||
}
|
||||
|
||||
private void ResolveExcuse(OpenExcuseItem item, AttendanceStatus status)
|
||||
{
|
||||
var entry = _entries.GetBySessionAndStudent(item.SessionId, item.StudentId);
|
||||
if (entry is null) return;
|
||||
entry.Attendance = status;
|
||||
_entries.Save(entry);
|
||||
OpenExcuses.Remove(item);
|
||||
OnPropertyChanged(nameof(HasOpenExcuses));
|
||||
}
|
||||
|
||||
/// Entwürfe (5.1.1: schnelle Notiz beim Sitzplan, noch nicht zu einem echten Dokumentations-
|
||||
/// eintrag ausformuliert) dieser Gruppe — dieselbe Zählung wie das Badge in
|
||||
/// GroupDocumentationTabViewModel.Load, hier nur ohne die vollständige Entwurfsliste.
|
||||
private void LoadDraftDocumentationCount()
|
||||
{
|
||||
DraftDocumentationCount = _students.GetByGroup(_groupId)
|
||||
.SelectMany(s => _documentation.GetByStudent(s.Id))
|
||||
.Count(d => d.IsDraft && (d.GroupId is null || d.GroupId == _groupId));
|
||||
}
|
||||
|
||||
/// Bewusst kein 1:1-Abbild der Dashboard-Fehlzeiten-Warnung: dieselbe Quote
|
||||
/// (AttendanceBalanceService.WarningThresholdPercent), aber erst ab
|
||||
/// <see cref="AttendanceMinSampleSize"/> erfassten Terminen, damit ein Fehltag in der ersten
|
||||
/// Schulwoche nicht sofort als auffällig gilt.
|
||||
private void LoadAttendanceNotices(DateOnly today)
|
||||
{
|
||||
AttendanceNotices.Clear();
|
||||
var schoolYear = _schoolYear.CurrentSchoolYear(today);
|
||||
var from = _schoolYear.SchoolYearStart(schoolYear);
|
||||
var to = _schoolYear.SchoolYearEnd(schoolYear);
|
||||
|
||||
var entriesByStudent = new Dictionary<Guid, List<(DateOnly Date, AttendanceStatus? Status)>>();
|
||||
foreach (var session in _sessions.GetByGroup(_groupId).Where(s => s.Date >= from && s.Date <= to))
|
||||
{
|
||||
foreach (var entry in _entries.GetBySession(session.Id))
|
||||
{
|
||||
if (!entriesByStudent.TryGetValue(entry.StudentId, out var list))
|
||||
entriesByStudent[entry.StudentId] = list = [];
|
||||
list.Add((session.Date, entry.Attendance));
|
||||
}
|
||||
}
|
||||
|
||||
var items = new List<AttendanceWarningItem>();
|
||||
foreach (var student in _students.GetByGroup(_groupId))
|
||||
{
|
||||
if (!entriesByStudent.TryGetValue(student.Id, out var entries) || entries.Count < AttendanceMinSampleSize)
|
||||
continue;
|
||||
|
||||
var balance = _attendanceBalance.Calculate(entries, from, to);
|
||||
if (balance.ExceedsThreshold)
|
||||
items.Add(new AttendanceWarningItem(student.Id, student.FullName, balance.AbsenceRatePercent));
|
||||
}
|
||||
foreach (var item in items.OrderByDescending(i => i.AbsenceRatePercent))
|
||||
AttendanceNotices.Add(item);
|
||||
OnPropertyChanged(nameof(HasAttendanceNotices));
|
||||
}
|
||||
}
|
||||
@@ -208,12 +208,14 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
partial void OnShowFormerStudentsChanged(bool value) => LoadStudents();
|
||||
partial void OnActiveTabIndexChanged(int value)
|
||||
{
|
||||
if (value == 0) OverviewTab.Refresh();
|
||||
if (value == 7) CompetencyOverviewTab.Refresh();
|
||||
}
|
||||
|
||||
public ObservableCollection<StudentSummary> Students { get; } = [];
|
||||
public ObservableCollection<ExamSummary> Exams { get; } = [];
|
||||
|
||||
public GroupOverviewViewModel OverviewTab { get; }
|
||||
public ParticipationTabViewModel ParticipationTab { get; }
|
||||
public GradeOverviewTabViewModel GradeOverviewTab { get; }
|
||||
public PlanningTabViewModel PlanningTab { get; }
|
||||
@@ -233,12 +235,14 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
public GroupDetailViewModel(IGroupRepository groups, IStudentRepository students,
|
||||
IGroupMembershipRepository memberships, ISubjectRepository subjects,
|
||||
IExamRepository exams, IGradeRepository grades, IWorkTaskRepository tasks,
|
||||
ParticipationTabViewModel participationTab, GradeOverviewTabViewModel gradeOverviewTab,
|
||||
GroupOverviewViewModel overviewTab, ParticipationTabViewModel participationTab,
|
||||
GradeOverviewTabViewModel gradeOverviewTab,
|
||||
PlanningTabViewModel planningTab, CompetencyOverviewTabViewModel competencyOverviewTab,
|
||||
SeatingPlanTabViewModel seatingPlanTab, GroupDocumentationTabViewModel groupDocumentationTab)
|
||||
{
|
||||
_groups = groups; _students = students; _memberships = memberships; _subjects = subjects;
|
||||
_exams = exams; _grades = grades; _tasks = tasks;
|
||||
OverviewTab = overviewTab;
|
||||
ParticipationTab = participationTab;
|
||||
GradeOverviewTab = gradeOverviewTab;
|
||||
PlanningTab = planningTab;
|
||||
@@ -251,6 +255,7 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
ParticipationTab.RefreshCurrentGrid();
|
||||
};
|
||||
SeatingPlanTab.OnDocumentationChanged = GroupDocumentationTab.Refresh;
|
||||
OverviewTab.OnNavigateToTab = idx => ActiveTabIndex = idx;
|
||||
}
|
||||
|
||||
public void LoadGroup(Guid id)
|
||||
@@ -266,6 +271,7 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
$"Stufe {Group.GradeLevel} · Noten {(Group.GradingSystem == GradingSystem.Grades1To6 ? "1–6" : "0–15")}";
|
||||
LoadStudents();
|
||||
ReloadExams();
|
||||
OverviewTab.Initialize(Group.Id, GroupTitle);
|
||||
ParticipationTab.Initialize(Group.Id, Group.SchoolYear, IsReadOnly);
|
||||
GradeOverviewTab.Initialize(Group.Id, Group.GradingSystem, Group.Type, GroupTitle, Group.SchoolYear, IsReadOnly);
|
||||
PlanningTab.Initialize(Group.Id, IsReadOnly);
|
||||
|
||||
@@ -53,13 +53,7 @@
|
||||
|
||||
<!-- Tab: Übersicht -->
|
||||
<ContentPage Header="Übersicht">
|
||||
<ScrollViewer Padding="20">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="{Binding GroupSubtitle}" Opacity="0.7" FontSize="14"/>
|
||||
<TextBlock Text="Hier erscheint später eine Zusammenfassung der Lerngruppe."
|
||||
Opacity="0.4"/>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
<views:GroupOverviewTabView DataContext="{Binding OverviewTab}"/>
|
||||
</ContentPage>
|
||||
|
||||
<!-- Tab: Schüler -->
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="clr-namespace:LehrerApp.Desktop.ViewModels.Groups"
|
||||
xmlns:vmRoot="clr-namespace:LehrerApp.Desktop.ViewModels"
|
||||
x:Class="LehrerApp.Desktop.Views.Groups.GroupOverviewTabView"
|
||||
x:DataType="vm:GroupOverviewViewModel">
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Border.card">
|
||||
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundAltHighBrush}"/>
|
||||
<Setter Property="CornerRadius" Value="8"/>
|
||||
<Setter Property="Padding" Value="16"/>
|
||||
<Setter Property="Margin" Value="0,0,12,12"/>
|
||||
<Setter Property="MinWidth" Value="260"/>
|
||||
<Setter Property="MaxWidth" Value="340"/>
|
||||
</Style>
|
||||
<Style Selector="TextBlock.cardTitle">
|
||||
<Setter Property="FontSize" Value="11"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="Opacity" Value="0.5"/>
|
||||
<Setter Property="Margin" Value="0,0,0,10"/>
|
||||
</Style>
|
||||
<Style Selector="TextBlock.cardBody.stale">
|
||||
<Setter Property="Foreground" Value="#D97706"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
</Style>
|
||||
<Style Selector="Button.cardLink">
|
||||
<Setter Property="FontSize" Value="11"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Margin" Value="0,10,0,0"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Left"/>
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<ScrollViewer Padding="20">
|
||||
<StackPanel Spacing="0">
|
||||
<WrapPanel>
|
||||
|
||||
<!-- Nächste Stunde -->
|
||||
<Border Classes="card">
|
||||
<StackPanel>
|
||||
<TextBlock Text="NÄCHSTE STUNDE" Classes="cardTitle"/>
|
||||
<TextBlock Text="{Binding NextLessonLabel}" TextWrapping="Wrap" Classes="cardBody"
|
||||
IsVisible="{Binding HasNextLesson}"/>
|
||||
<TextBlock Text="Keine geplante Stunde in Sicht." Classes="emptyhint"
|
||||
IsVisible="{Binding !HasNextLesson}"/>
|
||||
<Button Content="Zur Planung ›" Classes="cardLink" Command="{Binding NavigateToPlanningCommand}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Nächste Klausur -->
|
||||
<Border Classes="card">
|
||||
<StackPanel>
|
||||
<TextBlock Text="NÄCHSTE KLAUSUR" Classes="cardTitle"/>
|
||||
<TextBlock Text="{Binding NextExamLabel}" TextWrapping="Wrap" Classes="cardBody"
|
||||
IsVisible="{Binding HasNextExam}"/>
|
||||
<TextBlock Text="Keine Klausur geplant." Classes="emptyhint"
|
||||
IsVisible="{Binding !HasNextExam}"/>
|
||||
<Button Content="Zu den Klausuren ›" Classes="cardLink" Command="{Binding NavigateToExamsCommand}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Mitarbeit -->
|
||||
<Border Classes="card">
|
||||
<StackPanel>
|
||||
<TextBlock Text="MITARBEIT" Classes="cardTitle"/>
|
||||
<TextBlock Text="{Binding ParticipationHintLabel}" TextWrapping="Wrap"
|
||||
Classes="cardBody" Classes.stale="{Binding ParticipationHintIsStale}"/>
|
||||
<Button Content="Zur Mitarbeit ›" Classes="cardLink" Command="{Binding NavigateToParticipationCommand}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Offene Hausaufgaben-Kontrolle -->
|
||||
<Border Classes="card" IsVisible="{Binding HasOpenHomeworkCheck}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="OFFENE HAUSAUFGABEN-KONTROLLE" Classes="cardTitle"/>
|
||||
<TextBlock Text="{Binding OpenHomeworkCheckLabel}" TextWrapping="Wrap" Classes="cardBody stale"/>
|
||||
<Button Content="Zur Planung ›" Classes="cardLink" Command="{Binding NavigateToPlanningCommand}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Offene Dokumentationen -->
|
||||
<Border Classes="card" IsVisible="{Binding !!DraftDocumentationCount}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="OFFENE DOKUMENTATIONEN" Classes="cardTitle"/>
|
||||
<TextBlock TextWrapping="Wrap" Classes="cardBody">
|
||||
<Run Text="{Binding DraftDocumentationCount}"/>
|
||||
<Run Text=" Entwurf/Entwürfe noch nicht ausformuliert."/>
|
||||
</TextBlock>
|
||||
<Button Content="Zur Dokumentation ›" Classes="cardLink" Command="{Binding NavigateToDocumentationCommand}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</WrapPanel>
|
||||
|
||||
<WrapPanel>
|
||||
<!-- Offene Entschuldigungen -->
|
||||
<Border Classes="card" MinWidth="340" MaxWidth="420"
|
||||
IsVisible="{Binding HasOpenExcuses}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="OFFENE ENTSCHULDIGUNGEN" Classes="cardTitle"/>
|
||||
<ItemsControl ItemsSource="{Binding OpenExcuses}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vmRoot:OpenExcuseItem">
|
||||
<Grid ColumnDefinitions="*,Auto,Auto" Margin="0,4">
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="{Binding StudentName}" FontSize="13" FontWeight="SemiBold"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
<TextBlock Text="{Binding DateDisplay}" FontSize="11" Opacity="0.6"/>
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Content="Entschuldigt" FontSize="11" Padding="7,3"
|
||||
Command="{Binding MarkExcusedCommand}" Margin="0,0,4,0"/>
|
||||
<Button Grid.Column="2" Content="Unentschuldigt" FontSize="11" Padding="7,3"
|
||||
Command="{Binding MarkUnexcusedCommand}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Auffällige Fehlzeiten: bewusst zurückhaltender formuliert/gestylt als die
|
||||
Fehlzeiten-Warnung im Hauptdashboard, siehe GroupOverviewViewModel.AttendanceMinSampleSize. -->
|
||||
<Border Classes="card" MinWidth="340" MaxWidth="420"
|
||||
IsVisible="{Binding HasAttendanceNotices}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="AUFFÄLLIGE FEHLZEITEN" Classes="cardTitle"/>
|
||||
<ItemsControl ItemsSource="{Binding AttendanceNotices}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vmRoot:AttendanceWarningItem">
|
||||
<Grid ColumnDefinitions="*,Auto" Margin="0,4">
|
||||
<TextBlock Grid.Column="0" Text="{Binding StudentName}" FontSize="13"
|
||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="1" FontSize="12" Foreground="#D97706" FontWeight="SemiBold"
|
||||
VerticalAlignment="Center">
|
||||
<Run Text="{Binding AbsenceRatePercent, StringFormat={}{0:0.#}}"/>
|
||||
<Run Text=" %"/>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,8 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace LehrerApp.Desktop.Views.Groups;
|
||||
|
||||
public partial class GroupOverviewTabView : UserControl
|
||||
{
|
||||
public GroupOverviewTabView() => InitializeComponent();
|
||||
}
|
||||
Reference in New Issue
Block a user