Mitarbeitsnote-Aggregation (3.2)
Neuer Dialog berechnet aus den Sitzungs-Bewertungen je Schüler eine Mitarbeitsnote (H1/H2/Gesamtjahr), mit editierbarer Aspekt-Gewichtung, Trendanzeige und Übernahme als Grade (Category=Participation).
This commit is contained in:
@@ -44,6 +44,7 @@ public class ParticipationAspect
|
|||||||
public string Key { get; set; } = "";
|
public string Key { get; set; } = "";
|
||||||
public string Label { get; set; } = "";
|
public string Label { get; set; } = "";
|
||||||
public AspectValueType ValueType { get; set; } = AspectValueType.Scale5;
|
public AspectValueType ValueType { get; set; } = AspectValueType.Scale5;
|
||||||
|
public double Weight { get; set; } = 1.0;
|
||||||
public bool IsActive { get; set; } = true;
|
public bool IsActive { get; set; } = true;
|
||||||
public int SortOrder { get; set; }
|
public int SortOrder { get; set; }
|
||||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|||||||
@@ -55,6 +55,15 @@ public class GradingService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wandelt eine Mitarbeit-Bewertung im Bereich -2..+2 in eine Note/Punktzahl
|
||||||
|
/// nach dem jeweiligen Notensystem der Gruppe um (0 % = -2, 100 % = +2).
|
||||||
|
public string ParticipationGrade(double averageRating, GradingSystem system)
|
||||||
|
{
|
||||||
|
var percent = Math.Clamp((averageRating + 2.0) / 4.0 * 100.0, 0, 100);
|
||||||
|
var key = system == GradingSystem.Points0To15 ? DefaultKey0To15() : DefaultKey1To6();
|
||||||
|
return CalculateGrade(percent, 100, key);
|
||||||
|
}
|
||||||
|
|
||||||
public double WeightedAverage(List<(string Grade, double Weight)> grades)
|
public double WeightedAverage(List<(string Grade, double Weight)> grades)
|
||||||
{
|
{
|
||||||
var numeric = grades
|
var numeric = grades
|
||||||
|
|||||||
@@ -0,0 +1,252 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using LehrerApp.Core.Interfaces;
|
||||||
|
using LehrerApp.Core.Models;
|
||||||
|
using LehrerApp.Core.Services;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace LehrerApp.Desktop.ViewModels.Groups;
|
||||||
|
|
||||||
|
// ── Aggregation zur Mitarbeitsnote (3.2) ─────────────────────────────────────
|
||||||
|
|
||||||
|
public partial class ParticipationGradeDialogViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
private readonly IParticipationSessionRepository _sessions;
|
||||||
|
private readonly IParticipationRepository _entries;
|
||||||
|
private readonly IParticipationAspectRepository _aspects;
|
||||||
|
private readonly IStudentRepository _students;
|
||||||
|
private readonly IEnrollmentRepository _enrollments;
|
||||||
|
private readonly IGradeRepository _grades;
|
||||||
|
private readonly GradingService _grading;
|
||||||
|
private readonly Guid _groupId;
|
||||||
|
private readonly string _schoolYear;
|
||||||
|
private readonly GradingSystem _gradingSystem;
|
||||||
|
|
||||||
|
[ObservableProperty] private ParticipationPeriodOption _selectedPeriod;
|
||||||
|
[ObservableProperty] private string _statusMessage = "";
|
||||||
|
|
||||||
|
public List<ParticipationPeriodOption> PeriodOptions { get; } =
|
||||||
|
[
|
||||||
|
new(ParticipationPeriod.FullYear, "Gesamtes Schuljahr"),
|
||||||
|
new(ParticipationPeriod.H1, "1. Halbjahr"),
|
||||||
|
new(ParticipationPeriod.H2, "2. Halbjahr"),
|
||||||
|
];
|
||||||
|
|
||||||
|
public ObservableCollection<ParticipationGradeRow> Rows { get; } = [];
|
||||||
|
public ObservableCollection<AspectWeightItem> AspectWeights { get; } = [];
|
||||||
|
|
||||||
|
public ParticipationGradeDialogViewModel(
|
||||||
|
IParticipationSessionRepository sessions, IParticipationRepository entries,
|
||||||
|
IParticipationAspectRepository aspects, IStudentRepository students,
|
||||||
|
IEnrollmentRepository enrollments, IGradeRepository grades, GradingService grading,
|
||||||
|
Guid groupId, string schoolYear, GradingSystem gradingSystem)
|
||||||
|
{
|
||||||
|
_sessions = sessions; _entries = entries; _aspects = aspects;
|
||||||
|
_students = students; _enrollments = enrollments; _grades = grades;
|
||||||
|
_grading = grading; _groupId = groupId; _schoolYear = schoolYear;
|
||||||
|
_gradingSystem = gradingSystem;
|
||||||
|
|
||||||
|
_selectedPeriod = PeriodOptions[0];
|
||||||
|
LoadAspectWeights();
|
||||||
|
Recompute();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.2.1: Gewichtung je Aspekt konfigurierbar — direkt hier, da es noch keine eigene
|
||||||
|
// Aspekt-Verwaltung (3.1) gibt. Änderungen fließen sofort in die Vorschau ein.
|
||||||
|
private void LoadAspectWeights()
|
||||||
|
{
|
||||||
|
var defaults = _aspects.GetDefaults();
|
||||||
|
var specific = _aspects.GetByGroup(_groupId);
|
||||||
|
var all = defaults.Concat(specific).ToList();
|
||||||
|
if (all.Count == 0) all = DefaultParticipationAspects.All.ToList();
|
||||||
|
|
||||||
|
foreach (var a in all)
|
||||||
|
{
|
||||||
|
var item = new AspectWeightItem(a, _aspects);
|
||||||
|
item.OnChanged = Recompute;
|
||||||
|
AspectWeights.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnSelectedPeriodChanged(ParticipationPeriodOption value) => Recompute();
|
||||||
|
|
||||||
|
private void Recompute()
|
||||||
|
{
|
||||||
|
Rows.Clear();
|
||||||
|
StatusMessage = "";
|
||||||
|
|
||||||
|
var aspectWeights = AspectWeights.ToDictionary(a => a.Key, a => a.Weight);
|
||||||
|
|
||||||
|
var sessions = _sessions.GetByGroup(_groupId)
|
||||||
|
.Where(s => InPeriod(s.Date, SelectedPeriod.Period))
|
||||||
|
.OrderBy(s => s.Date)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var studentList = _students.GetByGroup(_groupId, _schoolYear);
|
||||||
|
var enrollmentList = _enrollments.GetByGroupAndYear(_groupId, _schoolYear);
|
||||||
|
|
||||||
|
foreach (var student in studentList.OrderBy(s => s.LastName).ThenBy(s => s.FirstName))
|
||||||
|
{
|
||||||
|
var enrollment = enrollmentList.FirstOrDefault(e => e.StudentId == student.Id);
|
||||||
|
var relevantSessions = sessions
|
||||||
|
.Where(s => enrollment is null || IsEnrolledAtDate(enrollment, s.Date))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var points = new List<(DateOnly Date, double Rating)>();
|
||||||
|
foreach (var session in relevantSessions)
|
||||||
|
{
|
||||||
|
var entry = _entries.GetBySessionAndStudent(session.Id, student.Id);
|
||||||
|
if (entry is null || entry.Ratings.Count == 0) continue;
|
||||||
|
|
||||||
|
var weightSum = 0.0;
|
||||||
|
var valueSum = 0.0;
|
||||||
|
foreach (var r in entry.Ratings)
|
||||||
|
{
|
||||||
|
var w = aspectWeights.TryGetValue(r.Key, out var aw) ? aw : 1.0;
|
||||||
|
if (w <= 0) continue;
|
||||||
|
valueSum += r.Value * w;
|
||||||
|
weightSum += w;
|
||||||
|
}
|
||||||
|
if (weightSum > 0) points.Add((session.Date, valueSum / weightSum));
|
||||||
|
}
|
||||||
|
|
||||||
|
Rows.Add(new ParticipationGradeRow(student.Id, student.FullName, points, _grading, _gradingSystem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.2.4: Übernahme als Grade (Category = Participation). Eine bereits übernommene
|
||||||
|
// Note für denselben Zeitraum wird aktualisiert statt dupliziert (erkannt am Note-Tag).
|
||||||
|
[RelayCommand]
|
||||||
|
private void Apply()
|
||||||
|
{
|
||||||
|
var noteTag = $"Mitarbeit {SelectedPeriod.Label} {_schoolYear}";
|
||||||
|
var applied = 0;
|
||||||
|
foreach (var row in Rows)
|
||||||
|
{
|
||||||
|
if (row.Grade is null) continue;
|
||||||
|
|
||||||
|
var grade = _grades.GetByStudentAndGroup(row.StudentId, _groupId)
|
||||||
|
.FirstOrDefault(g => g.Category == GradeCategory.Participation && g.Note == noteTag)
|
||||||
|
?? new Grade
|
||||||
|
{
|
||||||
|
StudentId = row.StudentId,
|
||||||
|
GroupId = _groupId,
|
||||||
|
SchoolYear = _schoolYear,
|
||||||
|
Category = GradeCategory.Participation,
|
||||||
|
Note = noteTag,
|
||||||
|
};
|
||||||
|
grade.Value = row.Grade;
|
||||||
|
grade.Date = DateOnly.FromDateTime(DateTime.Today);
|
||||||
|
_grades.Save(grade);
|
||||||
|
applied++;
|
||||||
|
}
|
||||||
|
StatusMessage = applied == 0
|
||||||
|
? "Keine Schüler mit Bewertungen im gewählten Zeitraum."
|
||||||
|
: $"{applied} Mitarbeitsnote(n) übernommen.";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool InPeriod(DateOnly date, ParticipationPeriod period) => period switch
|
||||||
|
{
|
||||||
|
ParticipationPeriod.H1 => date.Month >= 8 || date.Month <= 1,
|
||||||
|
ParticipationPeriod.H2 => date.Month >= 2 && date.Month <= 7,
|
||||||
|
_ => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static bool IsEnrolledAtDate(Enrollment e, DateOnly date) => e.Period switch
|
||||||
|
{
|
||||||
|
EnrollmentPeriod.H1Only => date.Month >= 8 || date.Month <= 1,
|
||||||
|
EnrollmentPeriod.H2Only => date.Month >= 2 && date.Month <= 7,
|
||||||
|
EnrollmentPeriod.Custom => (e.JoinedAt is null || date >= e.JoinedAt.Value)
|
||||||
|
&& (e.LeftAt is null || date <= e.LeftAt.Value),
|
||||||
|
_ => true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Gewichtung eines Aspekts (3.2.1) ─────────────────────────────────────────
|
||||||
|
|
||||||
|
public partial class AspectWeightItem : ObservableObject
|
||||||
|
{
|
||||||
|
private readonly ParticipationAspect _aspect;
|
||||||
|
private readonly IParticipationAspectRepository _repo;
|
||||||
|
|
||||||
|
public string Key => _aspect.Key;
|
||||||
|
public string Label => _aspect.Label;
|
||||||
|
|
||||||
|
[ObservableProperty] private double _weight;
|
||||||
|
|
||||||
|
public Action? OnChanged { get; set; }
|
||||||
|
|
||||||
|
public AspectWeightItem(ParticipationAspect aspect, IParticipationAspectRepository repo)
|
||||||
|
{
|
||||||
|
_aspect = aspect;
|
||||||
|
_repo = repo;
|
||||||
|
_weight = aspect.Weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnWeightChanged(double value)
|
||||||
|
{
|
||||||
|
_aspect.Weight = value;
|
||||||
|
_repo.Save(_aspect);
|
||||||
|
OnChanged?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ParticipationPeriod { FullYear, H1, H2 }
|
||||||
|
|
||||||
|
public class ParticipationPeriodOption(ParticipationPeriod period, string label)
|
||||||
|
{
|
||||||
|
public ParticipationPeriod Period { get; } = period;
|
||||||
|
public string Label { get; } = label;
|
||||||
|
public override string ToString() => Label;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Zeile: berechnete Mitarbeitsnote pro Schüler ─────────────────────────────
|
||||||
|
|
||||||
|
public class ParticipationGradeRow
|
||||||
|
{
|
||||||
|
public Guid StudentId { get; }
|
||||||
|
public string Name { get; }
|
||||||
|
public string AverageDisplay { get; }
|
||||||
|
public string? Grade { get; }
|
||||||
|
public string GradeDisplay { get; }
|
||||||
|
public string TrendSymbol { get; }
|
||||||
|
public int SessionCount { get; }
|
||||||
|
|
||||||
|
public ParticipationGradeRow(Guid studentId, string name, List<(DateOnly Date, double Rating)> points,
|
||||||
|
GradingService grading, GradingSystem system)
|
||||||
|
{
|
||||||
|
StudentId = studentId;
|
||||||
|
Name = name;
|
||||||
|
SessionCount = points.Count;
|
||||||
|
|
||||||
|
if (points.Count == 0)
|
||||||
|
{
|
||||||
|
// 3.2.3: nicht bewertet ≠ schlecht bewertet — kein Grade-Wert, klar erkennbar.
|
||||||
|
AverageDisplay = "–";
|
||||||
|
Grade = null;
|
||||||
|
GradeDisplay = "nicht bewertet";
|
||||||
|
TrendSymbol = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var average = points.Average(p => p.Rating);
|
||||||
|
AverageDisplay = average.ToString("0.00", CultureInfo.InvariantCulture);
|
||||||
|
Grade = grading.ParticipationGrade(average, system);
|
||||||
|
GradeDisplay = Grade;
|
||||||
|
TrendSymbol = ComputeTrend(points);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.2.5: einfache Trendanzeige — Vergleich erste vs. zweite Hälfte der Sitzungen.
|
||||||
|
private static string ComputeTrend(List<(DateOnly Date, double Rating)> points)
|
||||||
|
{
|
||||||
|
if (points.Count < 2) return "";
|
||||||
|
var mid = points.Count / 2;
|
||||||
|
var first = points.Take(mid).Average(p => p.Rating);
|
||||||
|
var second = points.Skip(mid).Average(p => p.Rating);
|
||||||
|
var diff = second - first;
|
||||||
|
if (diff >= 0.4) return "↑";
|
||||||
|
if (diff <= -0.4) return "↓";
|
||||||
|
return "→";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,11 @@ public partial class ParticipationTabViewModel : ObservableObject
|
|||||||
private string _schoolYear = "";
|
private string _schoolYear = "";
|
||||||
private Guid? _subjectId;
|
private Guid? _subjectId;
|
||||||
private int _gradeLevel;
|
private int _gradeLevel;
|
||||||
|
private GradingSystem _gradingSystem;
|
||||||
|
|
||||||
|
public Guid GroupId => _groupId;
|
||||||
|
public string SchoolYear => _schoolYear;
|
||||||
|
public GradingSystem GradingSystem => _gradingSystem;
|
||||||
|
|
||||||
[ObservableProperty] private ParticipationSessionItem? _selectedSession;
|
[ObservableProperty] private ParticipationSessionItem? _selectedSession;
|
||||||
[ObservableProperty] private string _noDataText = "Keine Bewertungssitzung ausgewählt.";
|
[ObservableProperty] private string _noDataText = "Keine Bewertungssitzung ausgewählt.";
|
||||||
@@ -41,6 +46,7 @@ public partial class ParticipationTabViewModel : ObservableObject
|
|||||||
|
|
||||||
public Func<Task<ParticipationSession?>>? OnAddSession { get; set; }
|
public Func<Task<ParticipationSession?>>? OnAddSession { get; set; }
|
||||||
public Func<ParticipationTabViewModel, Task>? OnQuickInput { get; set; }
|
public Func<ParticipationTabViewModel, Task>? OnQuickInput { get; set; }
|
||||||
|
public Func<ParticipationTabViewModel, Task>? OnComputeGrade { get; set; }
|
||||||
|
|
||||||
public ParticipationTabViewModel(
|
public ParticipationTabViewModel(
|
||||||
IParticipationSessionRepository sessions,
|
IParticipationSessionRepository sessions,
|
||||||
@@ -65,6 +71,7 @@ public partial class ParticipationTabViewModel : ObservableObject
|
|||||||
var group = _groups.GetById(groupId);
|
var group = _groups.GetById(groupId);
|
||||||
_subjectId = group?.SubjectId;
|
_subjectId = group?.SubjectId;
|
||||||
_gradeLevel = group?.GradeLevel ?? 0;
|
_gradeLevel = group?.GradeLevel ?? 0;
|
||||||
|
_gradingSystem = group?.GradingSystem ?? GradingSystem.Grades1To6;
|
||||||
|
|
||||||
HasCompetencyCatalog = _subjectId.HasValue
|
HasCompetencyCatalog = _subjectId.HasValue
|
||||||
&& _competencyDomains.GetBySubjectAndGrade(_subjectId.Value, _gradeLevel).Count > 0;
|
&& _competencyDomains.GetBySubjectAndGrade(_subjectId.Value, _gradeLevel).Count > 0;
|
||||||
@@ -248,6 +255,13 @@ public partial class ParticipationTabViewModel : ObservableObject
|
|||||||
|
|
||||||
private bool CanQuickInput() => SelectedSession is not null && StudentRows.Count > 0;
|
private bool CanQuickInput() => SelectedSession is not null && StudentRows.Count > 0;
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task ComputeGrade()
|
||||||
|
{
|
||||||
|
if (OnComputeGrade is null) return;
|
||||||
|
await OnComputeGrade(this);
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void DeleteSession()
|
private void DeleteSession()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="clr-namespace:LehrerApp.Desktop.ViewModels.Groups"
|
||||||
|
x:Class="LehrerApp.Desktop.Views.Groups.ParticipationGradeDialog"
|
||||||
|
x:DataType="vm:ParticipationGradeDialogViewModel"
|
||||||
|
Title="Mitarbeitsnote berechnen"
|
||||||
|
Width="620" Height="620" MinWidth="480" MinHeight="360"
|
||||||
|
CanResize="True" WindowStartupLocation="CenterOwner">
|
||||||
|
|
||||||
|
<Grid RowDefinitions="Auto,Auto,Auto,*,Auto" Margin="24">
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="0" Text="Mitarbeitsnote berechnen" FontSize="18" FontWeight="SemiBold"/>
|
||||||
|
|
||||||
|
<Grid Grid.Row="1" ColumnDefinitions="Auto,Auto,*" Margin="0,12,0,10">
|
||||||
|
<TextBlock Grid.Column="0" Text="Zeitraum:" VerticalAlignment="Center" Margin="0,0,8,0"/>
|
||||||
|
<ComboBox Grid.Column="1" ItemsSource="{Binding PeriodOptions}"
|
||||||
|
SelectedItem="{Binding SelectedPeriod}" MinWidth="180"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="2" Spacing="4" Margin="0,0,0,12">
|
||||||
|
<TextBlock Text="Gewichtung je Aspekt" FontSize="12" FontWeight="SemiBold" Opacity="0.6"/>
|
||||||
|
<ItemsControl ItemsSource="{Binding AspectWeights}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<WrapPanel Orientation="Horizontal"/>
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="vm:AspectWeightItem">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="6" Margin="0,0,16,4" VerticalAlignment="Center">
|
||||||
|
<TextBlock Text="{Binding Label}" VerticalAlignment="Center" FontSize="12"/>
|
||||||
|
<NumericUpDown Value="{Binding Weight}" Minimum="0" Maximum="10" Increment="0.1"
|
||||||
|
FormatString="0.#" Width="80" ShowButtonSpinner="False"/>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<DataGrid Grid.Row="3"
|
||||||
|
ItemsSource="{Binding Rows}"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
IsReadOnly="True"
|
||||||
|
GridLinesVisibility="Horizontal"
|
||||||
|
CanUserReorderColumns="False"
|
||||||
|
CanUserResizeColumns="True">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="Schüler" Binding="{Binding Name}" Width="2*"/>
|
||||||
|
<DataGridTextColumn Header="Sitzungen" Binding="{Binding SessionCount}" Width="Auto"/>
|
||||||
|
<DataGridTextColumn Header="Ø Bewertung" Binding="{Binding AverageDisplay}" Width="Auto"/>
|
||||||
|
<DataGridTextColumn Header="Trend" Binding="{Binding TrendSymbol}" Width="Auto"/>
|
||||||
|
<DataGridTextColumn Header="Note" Binding="{Binding GradeDisplay}" Width="Auto"/>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
|
||||||
|
<Grid Grid.Row="4" ColumnDefinitions="*,Auto,Auto" Margin="0,16,0,0">
|
||||||
|
<TextBlock Grid.Column="0" Text="{Binding StatusMessage}" Foreground="Green" FontSize="12"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
IsVisible="{Binding StatusMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||||
|
<Button Grid.Column="1" Content="Als Noten übernehmen" Command="{Binding ApplyCommand}" Margin="0,0,8,0"/>
|
||||||
|
<Button Grid.Column="2" Content="Schließen" Click="OnClose"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
|
||||||
|
namespace LehrerApp.Desktop.Views.Groups;
|
||||||
|
|
||||||
|
public partial class ParticipationGradeDialog : Window
|
||||||
|
{
|
||||||
|
public ParticipationGradeDialog() => InitializeComponent();
|
||||||
|
|
||||||
|
private void OnClose(object? sender, RoutedEventArgs e) => Close();
|
||||||
|
}
|
||||||
@@ -10,14 +10,17 @@
|
|||||||
<Border Grid.Column="0"
|
<Border Grid.Column="0"
|
||||||
BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
|
BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
|
||||||
BorderThickness="0,0,1,0">
|
BorderThickness="0,0,1,0">
|
||||||
<Grid RowDefinitions="Auto,*">
|
<Grid RowDefinitions="Auto,Auto,*">
|
||||||
|
|
||||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Spacing="6" Margin="10,10,10,6">
|
<StackPanel Grid.Row="0" Orientation="Horizontal" Spacing="6" Margin="10,10,10,6">
|
||||||
<Button Content="+ Sitzung" Command="{Binding AddSessionCommand}" HorizontalAlignment="Stretch"/>
|
<Button Content="+ Sitzung" Command="{Binding AddSessionCommand}" HorizontalAlignment="Stretch"/>
|
||||||
<Button Content="Schnell" Command="{Binding QuickInputCommand}"/>
|
<Button Content="Schnell" Command="{Binding QuickInputCommand}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<ListBox Grid.Row="1"
|
<Button Grid.Row="1" Content="Ø Mitarbeitsnote" Command="{Binding ComputeGradeCommand}"
|
||||||
|
HorizontalAlignment="Stretch" Margin="10,0,10,6"/>
|
||||||
|
|
||||||
|
<ListBox Grid.Row="2"
|
||||||
ItemsSource="{Binding Sessions}"
|
ItemsSource="{Binding Sessions}"
|
||||||
SelectedItem="{Binding SelectedSession}"
|
SelectedItem="{Binding SelectedSession}"
|
||||||
BorderThickness="0">
|
BorderThickness="0">
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Templates;
|
using Avalonia.Controls.Templates;
|
||||||
using Avalonia.Data;
|
using Avalonia.Data;
|
||||||
|
using LehrerApp.Core.Interfaces;
|
||||||
|
using LehrerApp.Core.Services;
|
||||||
using LehrerApp.Desktop.ViewModels.Groups;
|
using LehrerApp.Desktop.ViewModels.Groups;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace LehrerApp.Desktop.Views.Groups;
|
namespace LehrerApp.Desktop.Views.Groups;
|
||||||
|
|
||||||
@@ -20,6 +23,7 @@ public partial class ParticipationTabView : UserControl
|
|||||||
_vm = vm;
|
_vm = vm;
|
||||||
vm.OnAddSession = ShowAddSessionDialog;
|
vm.OnAddSession = ShowAddSessionDialog;
|
||||||
vm.OnQuickInput = ShowQuickInputDialog;
|
vm.OnQuickInput = ShowQuickInputDialog;
|
||||||
|
vm.OnComputeGrade = ShowComputeGradeDialog;
|
||||||
vm.Aspects.CollectionChanged += (_, _) => BuildColumns();
|
vm.Aspects.CollectionChanged += (_, _) => BuildColumns();
|
||||||
vm.PropertyChanged += (_, pe) =>
|
vm.PropertyChanged += (_, pe) =>
|
||||||
{
|
{
|
||||||
@@ -141,4 +145,22 @@ public partial class ParticipationTabView : UserControl
|
|||||||
if (owner is not null)
|
if (owner is not null)
|
||||||
await dialog.ShowDialog(owner);
|
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<IEnrollmentRepository>(),
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,13 +150,22 @@ Siehe [ParticipationViewModels.cs](LehrerApp.Desktop/ViewModels/Groups/Participa
|
|||||||
- [ ] **3.1.4** Aspekt deaktivieren statt löschen, damit alte Einträge gültig bleiben.
|
- [ ] **3.1.4** Aspekt deaktivieren statt löschen, damit alte Einträge gültig bleiben.
|
||||||
|
|
||||||
### 3.2 Aggregation zur Mitarbeitsnote
|
### 3.2 Aggregation zur Mitarbeitsnote
|
||||||
- [ ] **3.2.1** Gewichtung je Aspekt konfigurierbar (z.B. Qualität 50 %, Quantität 30 %, Experiment 20 %).
|
- [x] **3.2.1** Gewichtung je Aspekt konfigurierbar (z.B. Qualität 50 %, Quantität 30 %, Experiment 20 %).
|
||||||
- [ ] **3.2.2** Berechnung einer Mitarbeitsnote je Halbjahr aus allen Sitzungen,
|
- [x] **3.2.2** Berechnung einer Mitarbeitsnote je Halbjahr aus allen Sitzungen,
|
||||||
Ausgabe als Note bzw. Punkte je nach `GradingSystem` der Gruppe.
|
Ausgabe als Note bzw. Punkte je nach `GradingSystem` der Gruppe.
|
||||||
- [ ] **3.2.3** Umgang mit fehlenden Werten festlegen (nicht bewertet ≠ schlecht bewertet).
|
- [x] **3.2.3** Umgang mit fehlenden Werten festlegen (nicht bewertet ≠ schlecht bewertet).
|
||||||
- [ ] **3.2.4** Übernahme der berechneten Mitarbeitsnote als `Grade` mit
|
- [x] **3.2.4** Übernahme der berechneten Mitarbeitsnote als `Grade` mit
|
||||||
`Category = Participation` (Anbindung an 2.1).
|
`Category = Participation` (Anbindung an 2.1).
|
||||||
- [ ] **3.2.5** Trendanzeige pro Schüler (Entwicklung über die Sitzungen hinweg).
|
- [x] **3.2.5** Trendanzeige pro Schüler (Entwicklung über die Sitzungen hinweg).
|
||||||
|
|
||||||
|
Umgesetzt über den neuen Dialog "Ø Mitarbeitsnote" im Mitarbeit-Tab
|
||||||
|
([ParticipationGradeViewModels.cs](LehrerApp.Desktop/ViewModels/Groups/ParticipationGradeViewModels.cs),
|
||||||
|
[ParticipationGradeDialog.axaml](LehrerApp.Desktop/Views/Groups/ParticipationGradeDialog.axaml)):
|
||||||
|
Zeitraum wählbar (Gesamtjahr/H1/H2), Aspekt-Gewichtung live editierbar (`ParticipationAspect.Weight`,
|
||||||
|
da die eigentliche Aspekt-Verwaltung aus 3.1 noch fehlt), Mapping der Bewertungsskala (-2..+2) auf
|
||||||
|
Note/Punkte über `GradingService.ParticipationGrade()`, einfache Trendanzeige (↑/↓/→) durch Vergleich
|
||||||
|
der ersten mit der zweiten Hälfte der Sitzungen. "Übernehmen" aktualisiert eine bestehende
|
||||||
|
Mitarbeit-Note für denselben Zeitraum statt sie zu duplizieren (erkannt über den `Grade.Note`-Tag).
|
||||||
|
|
||||||
### 3.3 Sitzungen
|
### 3.3 Sitzungen
|
||||||
- [ ] **3.3.1** Sitzung automatisch aus einer geplanten `Lesson` erzeugen
|
- [ ] **3.3.1** Sitzung automatisch aus einer geplanten `Lesson` erzeugen
|
||||||
|
|||||||
Reference in New Issue
Block a user