fix: Schnellbewerten-Dialog - Datenverlust, Pfeiltasten, Shortcuts
- GetRating las aus einem nie aktualisierten ParticipationEntry- Snapshot statt aus der laufend gepflegten Cells-Liste. Beim Zurückwechseln zu einem vorherigen Schüler zeigte der Dialog dadurch fälschlich keine Bewertung, obwohl sie tatsächlich gespeichert war. Jetzt liest GetRating aus Cells, das ungenutzte _entry-Feld entfernt. - Pfeiltasten waren unbehandelt und fielen auf Avalonias Standard-Fokusnavigation durch: der Fokus sprang auf den Schließen-Button, ein Enter danach schloss den Dialog statt zum nächsten Schüler zu springen. Jetzt echte Navigation: ↑/↓ voriger/ nächster Aspekt (neue PreviousAspect-Methode), ←/→ voriger/ nächster Schüler - zugleich rechtsseitiges Pendant zu Q/W/E/R/T links. Hotkey-Legende im Dialog aktualisiert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,48 @@
|
|||||||
|
using LehrerApp.Core.Models;
|
||||||
|
using LehrerApp.Desktop.ViewModels.Groups;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace LehrerApp.Desktop.Tests;
|
||||||
|
|
||||||
|
public sealed class QuickInputViewModelTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ZurueckZuVorherigemSchueler_ZeigtBereitsGesetzteBewertung()
|
||||||
|
{
|
||||||
|
var aspects = new List<AspectColumnDef>
|
||||||
|
{
|
||||||
|
new(new ParticipationAspect { Key = "mitarbeit", Label = "Mitarbeit" }),
|
||||||
|
};
|
||||||
|
var rows = new List<ParticipationStudentRow>
|
||||||
|
{
|
||||||
|
new(Guid.NewGuid(), "Anna", new ParticipationEntry(), aspects, []),
|
||||||
|
new(Guid.NewGuid(), "Ben", new ParticipationEntry(), aspects, []),
|
||||||
|
};
|
||||||
|
var vm = new QuickInputViewModel(rows, aspects);
|
||||||
|
|
||||||
|
vm.SetRatingByNumber(4); // Scale5, 4. Stufe -> Rohwert 1 ("+"), Anna bewerten
|
||||||
|
vm.NextStudent(); // zu Ben
|
||||||
|
vm.PreviousStudent(); // zurück zu Anna
|
||||||
|
|
||||||
|
Assert.Equal(1, vm.AspectRows[0].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void VorherigerAspekt_SpringtRueckwaertsMitUmlauf()
|
||||||
|
{
|
||||||
|
var aspects = new List<AspectColumnDef>
|
||||||
|
{
|
||||||
|
new(new ParticipationAspect { Key = "a", Label = "A" }),
|
||||||
|
new(new ParticipationAspect { Key = "b", Label = "B" }),
|
||||||
|
};
|
||||||
|
var rows = new List<ParticipationStudentRow>
|
||||||
|
{
|
||||||
|
new(Guid.NewGuid(), "Anna", new ParticipationEntry(), aspects, []),
|
||||||
|
};
|
||||||
|
var vm = new QuickInputViewModel(rows, aspects);
|
||||||
|
|
||||||
|
vm.PreviousAspect(); // von Index 0 rückwärts -> letzter Aspekt (Umlauf)
|
||||||
|
|
||||||
|
Assert.Equal(1, vm.AspectIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -351,7 +351,6 @@ public partial class ParticipationStudentRow : ObservableObject
|
|||||||
public Guid StudentId { get; }
|
public Guid StudentId { get; }
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
private readonly ParticipationEntry _entry;
|
|
||||||
private readonly IReadOnlyList<AspectColumnDef> _aspectDefs;
|
private readonly IReadOnlyList<AspectColumnDef> _aspectDefs;
|
||||||
|
|
||||||
public ObservableCollection<RatingCell> Cells { get; } = [];
|
public ObservableCollection<RatingCell> Cells { get; } = [];
|
||||||
@@ -375,7 +374,6 @@ public partial class ParticipationStudentRow : ObservableObject
|
|||||||
{
|
{
|
||||||
StudentId = id;
|
StudentId = id;
|
||||||
Name = name;
|
Name = name;
|
||||||
_entry = entry;
|
|
||||||
_aspectDefs = aspects;
|
_aspectDefs = aspects;
|
||||||
_homework = HomeworkDisplay.Effective(entry);
|
_homework = HomeworkDisplay.Effective(entry);
|
||||||
_attendance = entry.Attendance;
|
_attendance = entry.Attendance;
|
||||||
@@ -397,8 +395,12 @@ public partial class ParticipationStudentRow : ObservableObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Liest aus Cells (per SetValue laufend aktuell gehalten), NICHT aus dem ursprünglichen
|
||||||
|
// ParticipationEntry-Snapshot: SetRating() schreibt nur in Cells + feuert OnRatingChanged
|
||||||
|
// (der Callback speichert über eine EIGENE, neu aus dem Repository geladene Entry-Instanz —
|
||||||
|
// der hier ursprünglich referenzierte Entry-Snapshot bekommt diese Änderung nie zu sehen).
|
||||||
public int? GetRating(string key) =>
|
public int? GetRating(string key) =>
|
||||||
_entry.Ratings.FirstOrDefault(r => r.Key == key)?.Value;
|
Cells.FirstOrDefault(c => c.AspectKey == key)?.Value;
|
||||||
|
|
||||||
public void SetRating(string key, int? value)
|
public void SetRating(string key, int? value)
|
||||||
{
|
{
|
||||||
@@ -743,8 +745,8 @@ public partial class QuickInputViewModel : ObservableObject
|
|||||||
AspectValueType.Points => $"0–9 Punkte eingeben (bis {CurrentAspectMaxPoints()})",
|
AspectValueType.Points => $"0–9 Punkte eingeben (bis {CurrentAspectMaxPoints()})",
|
||||||
_ => "1–5 bewerten",
|
_ => "1–5 bewerten",
|
||||||
};
|
};
|
||||||
return $"{ratingHint} · Q/W/E/R/T Aspekt wählen · Leertaste nächster Aspekt · " +
|
return $"{ratingHint} · Q/W/E/R/T Aspekt wählen · Leertaste/↓ nächster Aspekt · ↑ vorheriger Aspekt · " +
|
||||||
"Enter nächster Schüler · Backspace vorheriger · +/− anpassen · Esc schließen";
|
"Enter/→ nächster Schüler · Backspace/← vorheriger Schüler · +/− anpassen · Esc schließen";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -862,6 +864,12 @@ public partial class QuickInputViewModel : ObservableObject
|
|||||||
UpdateCurrentAspect();
|
UpdateCurrentAspect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void PreviousAspect()
|
||||||
|
{
|
||||||
|
AspectIndex = (AspectIndex - 1 + _aspects.Count) % _aspects.Count;
|
||||||
|
UpdateCurrentAspect();
|
||||||
|
}
|
||||||
|
|
||||||
public void NextStudent()
|
public void NextStudent()
|
||||||
{
|
{
|
||||||
if (StudentIndex >= _rows.Count - 1) return;
|
if (StudentIndex >= _rows.Count - 1) return;
|
||||||
|
|||||||
@@ -47,6 +47,15 @@ public partial class ParticipationQuickInputDialog : Window
|
|||||||
break;
|
break;
|
||||||
case Key.Back: vm.PreviousStudent(); e.Handled = true; break;
|
case Key.Back: vm.PreviousStudent(); e.Handled = true; break;
|
||||||
|
|
||||||
|
// Rechtsseitiges Pendant zu Q/W/E/R/T (links)/Enter/Backspace/Leertaste: ohne diese
|
||||||
|
// Fälle fallen Pfeiltasten auf Avalonias Standard-Fokusnavigation durch und der Fokus
|
||||||
|
// springt auf den Schließen-Button — ein Enter danach schließt dann den Dialog statt
|
||||||
|
// zum nächsten Schüler zu springen.
|
||||||
|
case Key.Up: vm.PreviousAspect(); e.Handled = true; break;
|
||||||
|
case Key.Down: vm.NextAspect(); e.Handled = true; break;
|
||||||
|
case Key.Right: vm.NextStudent(); e.Handled = true; break;
|
||||||
|
case Key.Left: vm.PreviousStudent(); e.Handled = true; break;
|
||||||
|
|
||||||
case Key.OemPlus or Key.Add: vm.IncrementRating(); e.Handled = true; break;
|
case Key.OemPlus or Key.Add: vm.IncrementRating(); e.Handled = true; break;
|
||||||
case Key.OemMinus or Key.Subtract: vm.DecrementRating(); e.Handled = true; break;
|
case Key.OemMinus or Key.Subtract: vm.DecrementRating(); e.Handled = true; break;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user