feat(usability): confirm saves and guard booking submissions against duplicates (U07)

This commit is contained in:
2026-11-02 06:38:42 +01:00
parent 76ba79b63e
commit 22e509d982
17 changed files with 373 additions and 118 deletions
@@ -11,6 +11,12 @@
<OperationNotice Result="fileResult" />
@if (fileBusy) { <p role="status" aria-live="polite">Dateiaktion läuft … Bitte warten.</p> }
@if (lastSaved != null)
{
<div class="alert alert-success" role="status" aria-live="polite">
@lastSaved.Kind über @FormatAmount(lastSaved.Amount) @(lastSaved.Edited ? "aktualisiert" : "gespeichert").
</div>
}
@if (savedOutsideYear.HasValue)
{
<div class="alert alert-info" role="status">
@@ -39,7 +45,7 @@
<button class="btn btn-secondary btn-nav" @onclick="NavigateBack">
<i class="bi bi-arrow-left"></i> Zurück
</button>
<button class="btn btn-success btn-nav" @onclick="() => showAddEntry = true">
<button class="btn btn-success btn-nav" @onclick="() => showAddEntry = true">
<i class="bi bi-plus-lg"></i> Einnahme oder Ausgabe erfassen
</button>
<button class="btn btn-info btn-nav" @onclick="() => showAddTransfer = true">
@@ -48,7 +54,7 @@
<button class="btn btn-dark btn-nav" @onclick="HandleExport" disabled="@fileBusy">
<i class="bi bi-file-earmark-pdf"></i> Als PDF speichern
</button>
<button class="btn btn-nav btn-primary" @onclick="OpenYearFilterDialog">
<button class="btn btn-nav btn-primary" @onclick="OpenYearFilterDialog">
<i class="bi bi-funnel"></i> @GetFilterLabel()
</button>
</div>
@@ -103,7 +109,7 @@
@if (entries != null)
{
<div class="entries-scroll-wrapper">
<EntryTable Entries="entries" EmptyMessage="emptyEntriesMessage" OnDeleteEntry="RequestDeleteEntry" OnRestoreEntry="RequestRestoreEntry" OnEditEntry="RequestEditEntry" />
<EntryTable HighlightedEntryId="lastSaved?.EntryId" Entries="entries" EmptyMessage="emptyEntriesMessage" OnDeleteEntry="RequestDeleteEntry" OnRestoreEntry="RequestRestoreEntry" OnEditEntry="RequestEditEntry" />
</div>
}
}
@@ -36,6 +36,7 @@ public partial class AccountDetail
private bool fileBusy;
private int? savedOutsideYear;
private BookingSaved? lastSaved;
private string emptyEntriesMessage = "Noch keine Buchungen vorhanden.";
private EntryDto? editingEntry;
private EntryDto? editingTransferEntry;
@@ -105,18 +106,19 @@ public partial class AccountDetail
await LoadAll();
}
private async Task HandleEntryCreated(DateTime savedDate)
{
savedOutsideYear = selectedYear.HasValue && selectedYear.Value != savedDate.Year ? savedDate.Year : null;
showAddEntry = false;
await LoadAll();
}
private Task HandleEntryCreated(BookingSaved saved) => HandleBookingSaved(saved);
private async Task HandleTransferCreated(DateTime savedDate)
private Task HandleTransferCreated(BookingSaved saved) => HandleBookingSaved(saved);
private async Task HandleBookingSaved(BookingSaved saved)
{
savedOutsideYear = selectedYear.HasValue && selectedYear.Value != savedDate.Year ? savedDate.Year : null;
showAddTransfer = false;
await LoadAll();
lastSaved = saved;
savedOutsideYear = selectedYear.HasValue && selectedYear.Value != saved.Date.Year ? saved.Date.Year : null;
showAddEntry = false;
showAddTransfer = false;
editingEntry = null;
editingTransferEntry = null;
}
private Task ShowSavedYear() => SelectFilterYear(savedOutsideYear);
@@ -180,13 +182,7 @@ public partial class AccountDetail
}
}
private async Task HandleEntryEdited(DateTime savedDate)
{
savedOutsideYear = selectedYear.HasValue && selectedYear.Value != savedDate.Year ? savedDate.Year : null;
editingEntry = null;
editingTransferEntry = null;
await LoadAll();
}
private Task HandleEntryEdited(BookingSaved saved) => HandleBookingSaved(saved);
private async Task HandleExport()
{
@@ -234,7 +230,7 @@ public partial class AccountDetail
#region Helpers
private static string FormatAmount(decimal amount) => $"{amount:N2} €";
private static string FormatAmount(decimal amount) => amount.ToString("N2", System.Globalization.CultureInfo.GetCultureInfo("de-DE")) + " €";
private string GetFilterLabel() => Period.Label;