371 lines
14 KiB
C#
371 lines
14 KiB
C#
using Avalonia.Controls;
|
||
using Avalonia.Controls.Templates;
|
||
using Avalonia.Data;
|
||
using LehrerApp.Core.Interfaces;
|
||
using LehrerApp.Core.Models;
|
||
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.OnEditSession = ShowEditSessionDialog;
|
||
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();
|
||
|
||
// Punkte-Aspekte (3.1.3) sind eine freie Zahl 0..MaxPoints statt einer festen
|
||
// Stufenauswahl — dafür ein NumericUpDown statt der Stufen-Buttons unten.
|
||
return cell.Type == AspectValueType.Points
|
||
? BuildPointsCell(cell, isReadOnly)
|
||
: BuildStepButtonsCell(cell, isReadOnly);
|
||
});
|
||
}
|
||
|
||
private static Control BuildStepButtonsCell(RatingCell cell, bool isReadOnly)
|
||
{
|
||
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 (val, label) in ParticipationRatingScale.Steps(cell.Type))
|
||
{
|
||
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 Control BuildPointsCell(RatingCell cell, bool isReadOnly)
|
||
{
|
||
var updown = new NumericUpDown
|
||
{
|
||
Minimum = 0,
|
||
Maximum = cell.MaxPoints,
|
||
Increment = 1,
|
||
FormatString = "0",
|
||
Value = cell.Value,
|
||
IsEnabled = !isReadOnly,
|
||
Width = 72,
|
||
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
|
||
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
|
||
};
|
||
updown.ValueChanged += (_, e) =>
|
||
{
|
||
var newVal = e.NewValue.HasValue ? (int?)e.NewValue.Value : null;
|
||
if (newVal != cell.Value) cell.SetValue(newVal);
|
||
};
|
||
cell.PropertyChanged += (_, pe) =>
|
||
{
|
||
if (pe.PropertyName != nameof(RatingCell.Value)) return;
|
||
var current = cell.Value.HasValue ? (decimal?)cell.Value.Value : null;
|
||
if (updown.Value != current) updown.Value = current;
|
||
};
|
||
return updown;
|
||
}
|
||
|
||
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<LehrerApp.Core.Models.ParticipationSession?> ShowEditSessionDialog(
|
||
LehrerApp.Core.Models.ParticipationSession session)
|
||
{
|
||
var vm = new AddSessionDialogViewModel(session);
|
||
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);
|
||
}
|
||
}
|