Mitarbeits Wizzard und neue Statuslabels für Anwesenheit und Hausaufgaben

This commit is contained in:
2026-08-13 01:49:40 +02:00
parent febcb0855a
commit 741586846a
10 changed files with 1160 additions and 144 deletions
@@ -0,0 +1,225 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using LehrerApp.Core.Models;
using LehrerApp.Desktop.ViewModels.Groups;
namespace LehrerApp.Desktop.Views.Groups;
public partial class AttendanceHomeworkQuickInputDialog : Window
{
public AttendanceHomeworkQuickInputDialog()
{
InitializeComponent();
BuildColumns();
}
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
StatusGrid.Focus();
if (ViewModel?.SelectedRow is { } row)
StatusGrid.ScrollIntoView(row, null);
}
private void BuildColumns()
{
StatusGrid.Columns.Add(new DataGridTextColumn
{
Header = "Schüler",
Binding = new Binding(nameof(ParticipationStudentRow.Name)),
Width = new DataGridLength(1, DataGridLengthUnitType.Star),
});
StatusGrid.Columns.Add(new DataGridTemplateColumn
{
Header = "Anwesenheit",
Width = new DataGridLength(210, DataGridLengthUnitType.Pixel),
CellTemplate = BuildAttendanceTemplate(),
});
StatusGrid.Columns.Add(new DataGridTemplateColumn
{
Header = "Hausaufgaben",
Width = new DataGridLength(270, DataGridLengthUnitType.Pixel),
CellTemplate = BuildHomeworkTemplate(),
});
}
private IDataTemplate BuildAttendanceTemplate() =>
new FuncDataTemplate<ParticipationStudentRow>((row, _) =>
{
if (row is null) return new TextBlock();
var button = StatusButton(185);
var flyout = new MenuFlyout { Placement = PlacementMode.BottomEdgeAlignedLeft };
AddAttendanceItem(flyout, row, "· Nicht kontrolliert", null);
AddAttendanceItem(flyout, row, "✓ Anwesend", AttendanceStatus.Present);
AddAttendanceItem(flyout, row, "? Entschuldigung offen", AttendanceStatus.ExcusePending);
AddAttendanceItem(flyout, row, "⊘ Krank, entschuldigt", AttendanceStatus.Excused);
AddAttendanceItem(flyout, row, "! Unentschuldigt", AttendanceStatus.Unexcused);
AddAttendanceItem(flyout, row, "✕ Geschwänzt", AttendanceStatus.Truant);
AddAttendanceItem(flyout, row, "◇ Andere Schulveranstaltung", AttendanceStatus.OtherSchoolEvent);
button.Flyout = flyout;
void Refresh()
{
UpdateButton(button,
string.IsNullOrEmpty(row.AttendanceLabel) ? "·" : row.AttendanceLabel,
row.AttendanceTooltip, AttendanceDisplay.Color(row.Attendance));
}
Refresh();
row.PropertyChanged += (_, args) =>
{
if (args.PropertyName == nameof(ParticipationStudentRow.AttendanceLabel)) Refresh();
};
return Center(button);
});
private IDataTemplate BuildHomeworkTemplate() =>
new FuncDataTemplate<ParticipationStudentRow>((row, _) =>
{
if (row is null) return new TextBlock();
var button = StatusButton(245);
var flyout = new MenuFlyout { Placement = PlacementMode.BottomEdgeAlignedLeft };
AddHomeworkItem(flyout, row, "· Keine Hausaufgabe", null);
AddHomeworkItem(flyout, row, "✓ Gemacht", HomeworkStatus.Completed);
AddHomeworkItem(flyout, row, "◐ Teilweise, Rest offen", HomeworkStatus.PartiallyCompleted);
AddHomeworkItem(flyout, row, "◕ Rest nachgereicht", HomeworkStatus.PartialSubmittedLate);
AddHomeworkItem(flyout, row, "◒ Rest nicht nachgereicht", HomeworkStatus.PartialMissingOverdue);
AddHomeworkItem(flyout, row, "! Nicht gemacht, Nachreichen offen", HomeworkStatus.MissingOpen);
AddHomeworkItem(flyout, row, "✕ Nicht mehr nachgereicht", HomeworkStatus.MissingOverdue);
AddHomeworkItem(flyout, row, "↺ Nachgereicht", HomeworkStatus.SubmittedLate);
button.Flyout = flyout;
void Refresh() => UpdateButton(button, row.HomeworkSymbol, row.HomeworkTooltip,
HomeworkDisplay.Color(row.Homework));
Refresh();
row.PropertyChanged += (_, args) =>
{
if (args.PropertyName == nameof(ParticipationStudentRow.HomeworkSymbol)) Refresh();
};
return Center(button);
});
private void AddAttendanceItem(MenuFlyout flyout, ParticipationStudentRow row,
string header, AttendanceStatus? status)
{
var item = new MenuItem { Header = header };
item.Click += (_, _) => ViewModel?.ApplyAttendance(row, status);
flyout.Items.Add(item);
}
private void AddHomeworkItem(MenuFlyout flyout, ParticipationStudentRow row,
string header, HomeworkStatus? status)
{
var item = new MenuItem { Header = header };
item.Click += (_, _) => ViewModel?.ApplyHomework(row, status);
flyout.Items.Add(item);
}
private static Button StatusButton(double width) => new()
{
Width = width,
Height = 29,
Padding = new Thickness(8, 2),
FontSize = 12,
HorizontalContentAlignment = Avalonia.Layout.HorizontalAlignment.Center,
};
private static Control Center(Control child) => new Grid
{
Margin = new Thickness(0, 2),
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
Children = { child },
};
private static void UpdateButton(Button button, string symbol, string tooltip, string color)
{
button.Content = $"{symbol} {tooltip}";
ToolTip.SetTip(button, tooltip + " klicken, um Status auszuwählen");
if (Color.TryParse(color, out var parsed))
{
button.Background = new SolidColorBrush(parsed);
button.Foreground = Brushes.White;
button.Opacity = 1;
}
else
{
button.ClearValue(Button.BackgroundProperty);
button.ClearValue(Button.ForegroundProperty);
button.Opacity = 0.48;
}
}
protected override void OnKeyDown(KeyEventArgs e)
{
var vm = ViewModel;
var row = vm?.SelectedRow;
if (vm is null || row is null) { base.OnKeyDown(e); return; }
if (e.KeyModifiers.HasFlag(KeyModifiers.Alt))
{
var homework = e.Key switch
{
Key.D0 or Key.NumPad0 => (HomeworkStatus?)null,
Key.M => HomeworkStatus.Completed,
Key.T => HomeworkStatus.PartiallyCompleted,
Key.R => HomeworkStatus.PartialSubmittedLate,
Key.X => HomeworkStatus.PartialMissingOverdue,
Key.O => HomeworkStatus.MissingOpen,
Key.F => HomeworkStatus.MissingOverdue,
Key.S => HomeworkStatus.SubmittedLate,
_ => null,
};
var recognized = e.Key is Key.D0 or Key.NumPad0 or Key.M or Key.T or Key.R or Key.X or Key.O or Key.F or Key.S;
if (recognized)
{
vm.ApplyHomework(row, homework);
BringSelectionIntoView();
e.Handled = true;
return;
}
}
AttendanceStatus? attendance = e.Key switch
{
Key.A => AttendanceStatus.Present,
Key.K => AttendanceStatus.ExcusePending,
Key.E => AttendanceStatus.Excused,
Key.U => AttendanceStatus.Unexcused,
Key.G => AttendanceStatus.Truant,
Key.V => AttendanceStatus.OtherSchoolEvent,
_ => null,
};
var attendanceRecognized = e.Key is Key.D0 or Key.NumPad0 or Key.A or Key.K or Key.E or Key.U or Key.G or Key.V;
if (attendanceRecognized)
{
vm.ApplyAttendance(row, attendance);
BringSelectionIntoView();
e.Handled = true;
return;
}
switch (e.Key)
{
case Key.Up: vm.MoveSelection(-1); BringSelectionIntoView(); e.Handled = true; break;
case Key.Down or Key.Enter: vm.MoveSelection(1); BringSelectionIntoView(); e.Handled = true; break;
case Key.Escape: Close(); e.Handled = true; break;
default: base.OnKeyDown(e); break;
}
}
private AttendanceHomeworkQuickInputViewModel? ViewModel =>
DataContext as AttendanceHomeworkQuickInputViewModel;
private void BringSelectionIntoView()
{
if (ViewModel?.SelectedRow is { } row)
StatusGrid.ScrollIntoView(row, null);
}
private void OnClose(object? sender, RoutedEventArgs e) => Close();
}