Persönliche Klausurlast pro Woche, Mitarbeit-Reset und Sitzplan-Strichliste
CI / build-and-test (push) Canceled after 0s
CI / build-and-test (push) Canceled after 0s
- Klausuren: neuer ExamWeekLoadService zählt eigene Klausuren je ISO-Woche über alle Kurse hinweg (Klassen-Kollisionen deckt bereits der externe Klausurplaner ab). Hinweis direkt im ExamDialog beim Datum, zusätzlich neue Dashboard-Karte "Klausurwochen" für die 60-Tage-Vorausschau. - Mitarbeit: Schnelleingabe-Dialog kann eine Bewertung jetzt per Entf auf "nicht bewertet" zurücksetzen (bisher nur im Sitzplatz-Dialog möglich). - Sitzplan: Strichliste "gemeldet"/"gemeldet und drangekommen" direkt auf der Sitzplatz-Kachel (ParticipationEntry.RaisedHandCount/CalledOnCount); schlägt im Sitzplatz-Dialog eine Quantitätsbewertung vor (ein Klick zum Übernehmen, nie automatisch). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -227,7 +227,7 @@ public partial class SeatingPlanTabViewModel : ObservableObject
|
||||
?? StudentSeatOption.Empty;
|
||||
var isHidden = hiddenSeats.Any(h => h.Row == row && h.Column == column);
|
||||
Seats.Add(new SeatCellViewModel(row, column, StudentOptions, option, OnSeatChanged,
|
||||
CanEditLayout, ToggleSituationTag, IsEditable, isHidden, ToggleSeatHidden));
|
||||
CanEditLayout, ToggleSituationTag, IsEditable, isHidden, ToggleSeatHidden, TallyParticipation));
|
||||
}
|
||||
UpdateAssignmentSummary();
|
||||
RefreshSeatLessonData();
|
||||
@@ -266,6 +266,22 @@ public partial class SeatingPlanTabViewModel : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
/// Strichliste im Sitzplan (Nutzer-Feedback): "drangekommen" ist immer auch eine Meldung und
|
||||
/// erhöht daher beide Zähler — es gibt kein "drangekommen, ohne sich gemeldet zu haben".
|
||||
private void TallyParticipation(SeatCellViewModel seat, bool calledOn)
|
||||
{
|
||||
if (!IsEditable || seat.SelectedOption.StudentId is not Guid studentId) return;
|
||||
var session = EnsureTodaySession();
|
||||
if (session is null) return;
|
||||
|
||||
var entry = _participation.GetBySessionAndStudent(session.Id, studentId)
|
||||
?? new ParticipationEntry { SessionId = session.Id, StudentId = studentId };
|
||||
entry.RaisedHandCount++;
|
||||
if (calledOn) entry.CalledOnCount++;
|
||||
_participation.Save(entry);
|
||||
RefreshSeatLessonData();
|
||||
}
|
||||
|
||||
private void ToggleSituationTag(SeatCellViewModel seat, string tag)
|
||||
{
|
||||
if (!IsEditable || _documentation is null ||
|
||||
@@ -531,6 +547,8 @@ public partial class SeatCellViewModel : ObservableObject
|
||||
[ObservableProperty] private double _lessonOpacity = 1.0;
|
||||
[ObservableProperty] private string _attendanceBadge = "";
|
||||
[ObservableProperty] private string _homeworkBadge = "";
|
||||
[ObservableProperty] private int _raisedHandCount;
|
||||
[ObservableProperty] private int _calledOnCount;
|
||||
public ObservableCollection<SituationTagChoice> SituationTags { get; } = [];
|
||||
public bool HasAttendanceBadge => AttendanceBadge.Length > 0;
|
||||
public bool HasHomeworkBadge => HomeworkBadge.Length > 0;
|
||||
@@ -555,10 +573,13 @@ public partial class SeatCellViewModel : ObservableObject
|
||||
/// die Abblendung für ausgeblendete Plätze hier statt per CSS-Klasse erfolgen.</summary>
|
||||
public double DisplayOpacity => IsHidden ? 0.4 : LessonOpacity;
|
||||
|
||||
private readonly Action<SeatCellViewModel, bool> _tally;
|
||||
|
||||
public SeatCellViewModel(int row, int column, ObservableCollection<StudentSeatOption> options,
|
||||
StudentSeatOption selectedOption, Action<SeatCellViewModel> onChanged, bool canEdit,
|
||||
Action<SeatCellViewModel, string>? toggleSituationTag = null, bool canRecordLesson = false,
|
||||
bool isHidden = false, Action<SeatCellViewModel>? toggleHidden = null)
|
||||
bool isHidden = false, Action<SeatCellViewModel>? toggleHidden = null,
|
||||
Action<SeatCellViewModel, bool>? tally = null)
|
||||
{
|
||||
Row = row;
|
||||
Column = column;
|
||||
@@ -570,10 +591,18 @@ public partial class SeatCellViewModel : ObservableObject
|
||||
_canRecordLesson = canRecordLesson;
|
||||
_isHidden = isHidden;
|
||||
_toggleHidden = toggleHidden ?? (_ => { });
|
||||
_tally = tally ?? ((_, _) => { });
|
||||
foreach (var tag in SituationTagChoice.DefaultTags)
|
||||
SituationTags.Add(new SituationTagChoice(tag, false, value => _toggleSituationTag(this, value)));
|
||||
}
|
||||
|
||||
/// Strichliste im Sitzplan (Nutzer-Feedback): schnelles Mitzählen während des Unterrichts,
|
||||
/// ohne dafür den vollen Sitzplatz-Bewertungsdialog öffnen zu müssen — bewusst nur ein
|
||||
/// Hochzählen ohne Korrekturmöglichkeit hier direkt auf der Kachel; die Rohwerte bleiben über
|
||||
/// den Bewertungsdialog einsehbar und fließen dort als Quantitäts-Vorschlag ein.
|
||||
[RelayCommand] private void TallyRaisedHand() => _tally(this, false);
|
||||
[RelayCommand] private void TallyCalledOn() => _tally(this, true);
|
||||
|
||||
partial void OnSelectedOptionChanged(StudentSeatOption value)
|
||||
{
|
||||
OnPropertyChanged(nameof(IsOccupied));
|
||||
@@ -615,6 +644,8 @@ public partial class SeatCellViewModel : ObservableObject
|
||||
var attendance = entry?.Attendance;
|
||||
AttendanceBadge = attendance is null ? "" : AttendanceDisplay.ShortLabel(attendance);
|
||||
HomeworkBadge = entry is null ? "" : HomeworkDisplay.Symbol(HomeworkDisplay.Effective(entry));
|
||||
RaisedHandCount = entry?.RaisedHandCount ?? 0;
|
||||
CalledOnCount = entry?.CalledOnCount ?? 0;
|
||||
LessonOpacity = attendance is not null and not AttendanceStatus.Present
|
||||
and not AttendanceStatus.Late and not AttendanceStatus.SignificantlyLate ? 0.42 : 1.0;
|
||||
var selected = tags.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -690,7 +721,10 @@ public partial class SeatAssessmentViewModel : ObservableObject
|
||||
foreach (var (aspect, index) in aspectDefinitions.Select((a, i) => (a, i)))
|
||||
{
|
||||
var value = _entry?.Ratings.FirstOrDefault(r => r.Key == aspect.Key)?.Value;
|
||||
AspectRows.Add(new SeatAssessmentAspectRow(index, aspect, value, ApplyRating));
|
||||
// Quantitäts-Vorschlag aus der Sitzplan-Strichliste (Nutzer-Feedback): nur für den
|
||||
// eingebauten "quantity"-Aspekt, siehe ParticipationCountSuggestion.
|
||||
var raisedHandCount = aspect.Key == "quantity" ? _entry?.RaisedHandCount : null;
|
||||
AspectRows.Add(new SeatAssessmentAspectRow(index, aspect, value, ApplyRating, raisedHandCount));
|
||||
}
|
||||
|
||||
BuildAttendanceChoices();
|
||||
@@ -847,11 +881,22 @@ public partial class SeatAssessmentAspectRow : ObservableObject
|
||||
public string DisplayValue => ParticipationRatingScale.DisplayLabel(ValueType, Value);
|
||||
public ObservableCollection<SeatRatingChoice> Choices { get; } = [];
|
||||
|
||||
/// Quantitäts-Vorschlag aus der Sitzplan-Strichliste (Nutzer-Feedback), siehe
|
||||
/// ParticipationCountSuggestion — null, wenn keine Strichliste zu diesem Aspekt gehört.
|
||||
private readonly int? _raisedHandCount;
|
||||
public int? SuggestedValue { get; }
|
||||
public bool HasSuggestion => SuggestedValue.HasValue && SuggestedValue != Value;
|
||||
public string SuggestionLabel =>
|
||||
$"{_raisedHandCount}× gemeldet → Vorschlag: {ParticipationRatingScale.DisplayLabel(ValueType, SuggestedValue)}";
|
||||
|
||||
public SeatAssessmentAspectRow(int index, ParticipationAspect aspect, int? value,
|
||||
Action<string, int?> apply)
|
||||
Action<string, int?> apply, int? raisedHandCount = null)
|
||||
{
|
||||
Index = index; Key = aspect.Key; Label = aspect.Label; ValueType = aspect.ValueType;
|
||||
MaxPoints = aspect.MaxPoints; _value = value; _apply = apply;
|
||||
_raisedHandCount = raisedHandCount;
|
||||
SuggestedValue = raisedHandCount is int rhc
|
||||
? ParticipationCountSuggestion.SuggestQuantity(ValueType, rhc) : null;
|
||||
var steps = ValueType == AspectValueType.Points
|
||||
? Enumerable.Range(0, Math.Min(MaxPoints, 9) + 1).Select(v => (v, v.ToString())).ToList()
|
||||
: ParticipationRatingScale.Steps(ValueType).ToList();
|
||||
@@ -864,10 +909,13 @@ public partial class SeatAssessmentAspectRow : ObservableObject
|
||||
{
|
||||
Value = value;
|
||||
OnPropertyChanged(nameof(DisplayValue));
|
||||
OnPropertyChanged(nameof(HasSuggestion));
|
||||
foreach (var choice in Choices) choice.IsSelected = choice.Value == value;
|
||||
_apply(Key, value);
|
||||
}
|
||||
|
||||
[RelayCommand] private void ApplySuggestion() => ApplyValue(SuggestedValue);
|
||||
|
||||
public void Adjust(int delta)
|
||||
{
|
||||
if (ValueType == AspectValueType.Points)
|
||||
|
||||
Reference in New Issue
Block a user