From 2d36ab0b88de660e7428e6c0c1e0785230bc424e Mon Sep 17 00:00:00 2001 From: troogs Date: Mon, 2 Nov 2026 19:25:34 +0100 Subject: [PATCH] feat(usability): label booking actions and enlarge row targets (U09) --- docs/usability-umsetzung.md | 4 +++ .../Components/Accounts/EntryRow.razor | 27 ++++++++++--------- .../Components/Accounts/EntryTable.razor | 4 +-- src/Duempelkas.Desktop/wwwroot/css/app.css | 13 +++++++++ .../EntryPresentationTests.cs | 24 +++++++++++++++++ 5 files changed, 57 insertions(+), 15 deletions(-) create mode 100644 tests/Duempelkas.Tests/EntryPresentationTests.cs diff --git a/docs/usability-umsetzung.md b/docs/usability-umsetzung.md index 5df1311..c3c8396 100644 --- a/docs/usability-umsetzung.md +++ b/docs/usability-umsetzung.md @@ -63,3 +63,7 @@ U08: gemeinsame native Modaldialoge mit Fokus/Escape/Verlustschutz, semantische ## U08 – Tastatur und Modaldialoge Gemeinsame native dialog-Komponente für Erfassung, Bestätigungen, Konten, Namen, Übertrag, Jahr und Über. showModal sperrt den Hintergrund und hält den Tastaturfokus im Dialog; Fokus geht ins sinnvolle Feld und nach Schließen zum Auslöser zurück. Escape schließt unveränderte Dialoge; geänderte Eingaben bieten Weiterbearbeiten/Verwerfen. Während Speichern kein Schließen. Unsichere Bestätigungen fokussieren Abbrechen und verwenden keine implizite Formularbestätigung. Kontokarten sind semantische Links. Labels sind technisch verbunden. Toasts bleiben auch innerhalb nativer Modaldialoge zugänglich. Prüfung: 44/44 Debug-Tests, warnungsfreier Build; Computer Use beobachtete Fokus im Beschreibungsfeld, ausschließlich modalen Accessibility-Baum und Escape-Schließen. Kein vollständiger Screenreader-Nutzertest. + +## U09 – Zeilenaktionen + +Beschriftetes Aktionsmenü je Buchung, sichtbares Bearbeiten/Löschen und Wiederherstellen als Wort. Zugängliche Namen nennen Nummer und Beschreibung, Trefferbereiche mindestens 44 CSS-Pixel. Auf schmalen Fenstern wechseln Zeilen in beschriftete Karten, damit die Aktionsspalte nicht die Beschreibung verdrängt. Gerenderter Test prüft Zuordnung und Aktion. diff --git a/src/Duempelkas.App/Components/Accounts/EntryRow.razor b/src/Duempelkas.App/Components/Accounts/EntryRow.razor index 4138c7b..24b5848 100644 --- a/src/Duempelkas.App/Components/Accounts/EntryRow.razor +++ b/src/Duempelkas.App/Components/Accounts/EntryRow.razor @@ -1,7 +1,7 @@ - @Entry.DisplayId - @Entry.Date.ToString("dd.MM.yyyy") - + @Entry.DisplayId + @Entry.Date.ToString("dd.MM.yyyy") + @Entry.Title @if (Entry.IsTransfer && !string.IsNullOrEmpty(Entry.LinkedAccountName)) { @@ -10,26 +10,27 @@ } - + @(Entry.Type == EntryType.Income ? "+" : "\u2212")@($"{Entry.Amount:N2} €") - + @if (Entry.IsDeleted) { - } else { -
- - -
+ } diff --git a/src/Duempelkas.App/Components/Accounts/EntryTable.razor b/src/Duempelkas.App/Components/Accounts/EntryTable.razor index d8b71f0..8cc0a66 100644 --- a/src/Duempelkas.App/Components/Accounts/EntryTable.razor +++ b/src/Duempelkas.App/Components/Accounts/EntryTable.razor @@ -1,4 +1,4 @@ -
+
@@ -6,7 +6,7 @@ - + diff --git a/src/Duempelkas.Desktop/wwwroot/css/app.css b/src/Duempelkas.Desktop/wwwroot/css/app.css index c84e7ed..8d068be 100644 --- a/src/Duempelkas.Desktop/wwwroot/css/app.css +++ b/src/Duempelkas.Desktop/wwwroot/css/app.css @@ -641,3 +641,16 @@ fieldset { border: 0; margin: 0; padding: 0; } body:has(dialog[open]) .toast-stack:not(.modal-toasts) { display: none; } .visually-hidden { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0,0,0,0) !important; white-space: nowrap !important; border: 0 !important; } + +.entry-actions button, .entry-action-menu summary { min-height: 44px; padding: .6rem; } +.entry-action-menu summary { cursor: pointer; border: 1px solid var(--color-border); border-radius: .4rem; } +.entry-action-menu button { display: block; width: 100%; margin-top: .5rem; } +.entry-description { overflow-wrap: anywhere; } +@media (max-width: 900px) { + .entry-list thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); } + .entry-list table, .entry-list tbody { display: block; width: 100%; } + .entry-list tbody tr { display: grid; grid-template-columns: 1fr 1fr; border: 1px solid var(--color-border); border-radius: .5rem; padding: .6rem; margin-bottom: .75rem; } + .entry-list td { display: block; border: 0; min-width: 0; padding: .3rem; text-align: left !important; } + .entry-list td::before { content: attr(data-label); display: block; color: var(--color-text-muted); font-size: .85rem; } + .entry-list .entry-description, .entry-list .entry-actions { grid-column: 1 / -1; } +} diff --git a/tests/Duempelkas.Tests/EntryPresentationTests.cs b/tests/Duempelkas.Tests/EntryPresentationTests.cs new file mode 100644 index 0000000..b530547 --- /dev/null +++ b/tests/Duempelkas.Tests/EntryPresentationTests.cs @@ -0,0 +1,24 @@ +using Bunit; +using Duempelkas.App.Components.Accounts; +using Duempelkas.App.Services.Models; +using Duempelkas.Domain.Enums; +using Xunit; + +namespace Duempelkas.Tests; + +public class EntryPresentationTests +{ + [Fact] + public void Actions_HaveVisibleTextAndBookingSpecificNames() + { + using var context = new BunitContext(); + var entry = new EntryDto(1, 1, "2026-001", EntryType.Expense, DateTime.Today, "Porto", 4.20m, false, false, null, null, null); + var edited = 0; + var table = context.Render(p => p.Add(x => x.Entries, [entry]).Add(x => x.OnEditEntry, id => edited = id)); + Assert.Contains("2026-001 Porto", table.Find("summary").GetAttribute("aria-label")); + var edit = table.FindAll("button").Single(b => b.TextContent.Contains("Bearbeiten")); + Assert.Contains("Porto", edit.GetAttribute("aria-label")); + edit.Click(); + Assert.Equal(1, edited); + } +}
Datum Bezeichnung BetragAktionen