Files
LehrerApp/LehrerApp.Desktop/Views/Groups/ParticipationTabView.axaml.cs
T
adminandClaude Sonnet 5 e40585801c Kapitel 3.1: Mitarbeit-Aspekte pro Gruppe verwalten (3.1.1/3.1.2/3.1.4)
Neuer Button "Aspekte verwalten" im Mitarbeit-Tab öffnet
ParticipationAspectsDialog: Anlegen, Bezeichnung/Typ/Gewichtung/Aktiv-Status
bearbeiten (speichert sofort, gleiches Muster wie die bestehende
Gewichtungs-Bearbeitung in 3.2.1), Hoch/Runter-Reihenfolge, Löschen mit
Rückfrage. Verwaltet bewusst nur die gruppenspezifischen Aspekte, nicht den
bislang nirgends befüllten globalen Standardkatalog.

Schlüssel ist nur beim Neuanlegen editierbar - er verknüpft AspectRating mit
dem Aspekt per Key, ein nachträgliches Umbenennen würde historische
Bewertungen unauffindbar machen.

IParticipationAspectRepository.Save validiert jetzt Pflichtfelder und
Schlüssel-Eindeutigkeit gegen globale Standards UND eigene Gruppen-Aspekte
zusammen, da beide im Bewertungsraster kombiniert verwendet werden. Neue
GetAllByGroup-Methode (inkl. inaktiver) für die Verwaltungsansicht.

3.1.3 (Scale3/Binary/Points im Bewertungsraster selbst) bewusst nicht
angefasst - eigener, größerer Eingriff in die Bewertungs-UI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 23:39:44 +02:00

321 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Services;
using LehrerApp.Desktop.ViewModels.Groups;
using Microsoft.Extensions.DependencyInjection;
namespace LehrerApp.Desktop.Views.Groups;
public partial class ParticipationTabView : UserControl
{
private ParticipationTabViewModel? _vm;
public ParticipationTabView() => InitializeComponent();
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
if (DataContext is ParticipationTabViewModel vm)
{
_vm = vm;
vm.OnAddSession = ShowAddSessionDialog;
vm.OnQuickInput = ShowQuickInputDialog;
vm.OnStatusQuickInput = ShowStatusQuickInputDialog;
vm.OnComputeGrade = ShowComputeGradeDialog;
vm.OnOpenWizard = ShowWizardDialog;
vm.OnManageAspects = ShowManageAspectsDialog;
vm.Aspects.CollectionChanged += (_, _) => BuildColumns();
vm.PropertyChanged += (_, pe) =>
{
if (pe.PropertyName == nameof(ParticipationTabViewModel.RebuildColumnsSignal))
BuildColumns();
};
BuildColumns();
}
}
private void BuildColumns()
{
var grid = this.FindControl<DataGrid>("RatingGrid");
if (grid is null || _vm is null) return;
grid.Columns.Clear();
grid.Columns.Add(new DataGridTextColumn
{
Header = "Schüler",
Binding = new Binding("Name"),
Width = new DataGridLength(160, DataGridLengthUnitType.Pixel),
});
// Aspekt-Spalten
foreach (var (aspect, i) in _vm.Aspects.Select((a, i) => (a, i)))
{
var idx = i;
grid.Columns.Add(new DataGridTemplateColumn
{
Header = $"{aspect.Label} [{AspectShortcut(i)}]",
Width = new DataGridLength(1, DataGridLengthUnitType.Star),
CellTemplate = BuildCellTemplate(idx, forCompetency: false, _vm.IsReadOnly),
});
}
grid.Columns.Add(new DataGridTemplateColumn
{
Header = "HA",
Width = new DataGridLength(50, DataGridLengthUnitType.Pixel),
CellTemplate = BuildHomeworkCellTemplate(_vm.IsReadOnly),
});
grid.Columns.Add(new DataGridTemplateColumn
{
Header = "Anwesenheit",
Width = new DataGridLength(80, DataGridLengthUnitType.Pixel),
CellTemplate = BuildAttendanceCellTemplate(_vm.IsReadOnly),
});
// Kompetenz-Spalten (opt-in)
if (_vm.StudentCompetencyRatingsVisible && _vm.ActiveCompetencyCodes.Count > 0)
{
foreach (var (code, i) in _vm.ActiveCompetencyCodes.Select((c, i) => (c, i)))
{
var idx = i;
grid.Columns.Add(new DataGridTemplateColumn
{
Header = code,
Width = new DataGridLength(80, DataGridLengthUnitType.Pixel),
CellTemplate = BuildCellTemplate(idx, forCompetency: true, _vm.IsReadOnly),
});
}
}
}
private static IDataTemplate BuildCellTemplate(int cellIndex, bool forCompetency, bool isReadOnly)
{
return new FuncDataTemplate<ParticipationStudentRow>((row, _) =>
{
if (row is null) return new TextBlock();
var cell = forCompetency
? row.CompetencyCells.ElementAtOrDefault(cellIndex)
: row.Cells.ElementAtOrDefault(cellIndex);
if (cell is null) return new TextBlock();
var panel = new StackPanel
{
Orientation = Avalonia.Layout.Orientation.Horizontal,
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
Spacing = 2,
Margin = new Avalonia.Thickness(0, 2),
};
foreach (var (label, val) in new[] { ("−−", -2), ("", -1), ("", 0), ("+", 1), ("++", 2) })
{
var btn = new Button
{
Content = label,
Padding = new Avalonia.Thickness(5, 1),
FontSize = 11,
Opacity = cell.Value == val ? 1.0 : 0.3,
IsEnabled = !isReadOnly,
};
var capturedVal = val;
btn.Click += (_, _) => cell.SetValue(capturedVal);
cell.PropertyChanged += (_, pe) =>
{
if (pe.PropertyName == nameof(RatingCell.Value))
btn.Opacity = cell.Value == capturedVal ? 1.0 : 0.3;
};
panel.Children.Add(btn);
}
return panel;
});
}
private static IDataTemplate BuildHomeworkCellTemplate(bool isReadOnly)
{
return new FuncDataTemplate<ParticipationStudentRow>((row, _) =>
{
if (row is null) return new TextBlock();
var btn = new Button
{
FontSize = 12,
Width = 30,
Height = 26,
Padding = new Avalonia.Thickness(3, 1),
Command = row.ToggleHomeworkCommand,
IsEnabled = !isReadOnly,
};
void RefreshHomework()
{
btn.Content = row.HomeworkSymbol;
ToolTip.SetTip(btn, row.HomeworkTooltip + " klicken für nächsten Status");
if (row.Homework is null)
{
btn.ClearValue(Button.BackgroundProperty);
btn.ClearValue(Button.ForegroundProperty);
btn.Opacity = 0.38;
}
else
{
btn.Background = new Avalonia.Media.SolidColorBrush(Avalonia.Media.Color.Parse(HomeworkDisplay.Color(row.Homework)));
btn.Foreground = Avalonia.Media.Brushes.White;
btn.Opacity = 1;
}
}
RefreshHomework();
row.PropertyChanged += (_, pe) =>
{
if (pe.PropertyName == nameof(ParticipationStudentRow.HomeworkSymbol))
RefreshHomework();
};
return new StackPanel
{
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
Children = { btn },
};
});
}
private static IDataTemplate BuildAttendanceCellTemplate(bool isReadOnly)
{
return new FuncDataTemplate<ParticipationStudentRow>((row, _) =>
{
if (row is null) return new TextBlock();
var btn = new Button
{
FontSize = 12,
Width = 30,
Height = 26,
Padding = new Avalonia.Thickness(3, 1),
Command = row.CycleAttendanceCommand,
IsEnabled = !isReadOnly,
};
void RefreshAttendance()
{
btn.Content = string.IsNullOrEmpty(row.AttendanceLabel) ? "·" : row.AttendanceLabel;
ToolTip.SetTip(btn, row.AttendanceTooltip + " klicken für nächsten Status");
if (row.Attendance is null)
{
btn.ClearValue(Button.BackgroundProperty);
btn.ClearValue(Button.ForegroundProperty);
btn.Opacity = 0.38;
}
else
{
btn.Background = new Avalonia.Media.SolidColorBrush(Avalonia.Media.Color.Parse(AttendanceDisplay.Color(row.Attendance)));
btn.Foreground = Avalonia.Media.Brushes.White;
btn.Opacity = 1;
}
}
RefreshAttendance();
row.PropertyChanged += (_, pe) =>
{
if (pe.PropertyName == nameof(ParticipationStudentRow.AttendanceLabel))
RefreshAttendance();
};
return new StackPanel
{
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
Children = { btn },
};
});
}
private static string AspectShortcut(int i) => i switch
{
0 => "Q", 1 => "W", 2 => "E", 3 => "R", 4 => "T", _ => ""
};
private async Task<LehrerApp.Core.Models.ParticipationSession?> ShowAddSessionDialog()
{
var vm = new AddSessionDialogViewModel();
var dialog = new AddSessionDialog { DataContext = vm };
var owner = TopLevel.GetTopLevel(this) as Window;
if (owner is null) return null;
var ok = await dialog.ShowDialog<bool>(owner);
return ok ? vm.Result : null;
}
private async Task ShowQuickInputDialog(ParticipationTabViewModel tabVm)
{
if (tabVm.StudentRows.Count == 0) return;
var quickVm = new QuickInputViewModel(
tabVm.StudentRows.ToList(),
tabVm.Aspects.ToList());
var dialog = new ParticipationQuickInputDialog { DataContext = quickVm };
var owner = TopLevel.GetTopLevel(this) as Window;
if (owner is not null)
await dialog.ShowDialog(owner);
}
private async Task ShowStatusQuickInputDialog(ParticipationTabViewModel tabVm)
{
if (tabVm.StudentRows.Count == 0 || tabVm.SelectedSession is null) return;
var quickVm = new AttendanceHomeworkQuickInputViewModel(
tabVm.StudentRows, tabVm.SelectedSessionDisplay);
var dialog = new AttendanceHomeworkQuickInputDialog { DataContext = quickVm };
var owner = TopLevel.GetTopLevel(this) as Window;
if (owner is not null)
await dialog.ShowDialog(owner);
}
private async Task ShowComputeGradeDialog(ParticipationTabViewModel tabVm)
{
var dialogVm = new ParticipationGradeDialogViewModel(
App.Services.GetRequiredService<IParticipationSessionRepository>(),
App.Services.GetRequiredService<IParticipationRepository>(),
App.Services.GetRequiredService<IParticipationAspectRepository>(),
App.Services.GetRequiredService<IStudentRepository>(),
App.Services.GetRequiredService<IGroupMembershipRepository>(),
App.Services.GetRequiredService<IGradeRepository>(),
App.Services.GetRequiredService<GradingService>(),
tabVm.GroupId, tabVm.SchoolYear, tabVm.GradingSystem);
var dialog = new ParticipationGradeDialog { DataContext = dialogVm };
var owner = TopLevel.GetTopLevel(this) as Window;
if (owner is not null)
await dialog.ShowDialog(owner);
}
private async Task ShowWizardDialog(ParticipationTabViewModel tabVm)
{
var dialogVm = new ParticipationWizardDialogViewModel(
App.Services.GetRequiredService<IParticipationSessionRepository>(),
App.Services.GetRequiredService<IParticipationRepository>(),
App.Services.GetRequiredService<IParticipationAspectRepository>(),
App.Services.GetRequiredService<IParticipationSectionRepository>(),
App.Services.GetRequiredService<IStudentRepository>(),
App.Services.GetRequiredService<IGroupMembershipRepository>(),
App.Services.GetRequiredService<IExamRepository>(),
App.Services.GetRequiredService<IExamResultRepository>(),
App.Services.GetRequiredService<IGradeRepository>(),
App.Services.GetRequiredService<GradingService>(),
tabVm.GroupId, tabVm.SchoolYear, tabVm.GradingSystem, tabVm.GroupLabel);
var dialog = new ParticipationWizardDialog { DataContext = dialogVm };
var owner = TopLevel.GetTopLevel(this) as Window;
if (owner is not null)
await dialog.ShowDialog(owner);
}
private async Task ShowManageAspectsDialog(ParticipationTabViewModel tabVm)
{
var dialogVm = new ParticipationAspectsDialogViewModel(
App.Services.GetRequiredService<IParticipationAspectRepository>(), tabVm.GroupId);
var dialog = new ParticipationAspectsDialog { DataContext = dialogVm };
var owner = TopLevel.GetTopLevel(this) as Window;
if (owner is not null)
await dialog.ShowDialog(owner);
}
}