feat(ui): constrain decimal input and add stacked timed toasts with uniform actions

This commit is contained in:
2026-11-02 19:19:07 +01:00
parent 22e509d982
commit 86a73b862a
24 changed files with 315 additions and 103 deletions
@@ -1,3 +1,4 @@
@inject ToastService Toasts
@inject IEntryService EntryService
<div class="dialog-backdrop">
@@ -36,8 +37,8 @@
<label class="form-label" for="entry-amount">Betrag (€)</label>
<div>
<input id="entry-amount" type="text" inputmode="decimal" class="form-control" @bind="amountText" @bind:event="oninput" placeholder="12,50" aria-describedby="entry-amount-help" aria-invalid="@(!ValidAmount ? "true" : "false")" />
<p id="entry-amount-help" class="@(!ValidAmount ? "text-danger" : "text-muted")">Betrag ohne Minuszeichen, größer als 0. Komma oder Punkt, höchstens zwei Nachkommastellen. Keine Tausendertrenner oder Währungszeichen, auch beim Einfügen. Es wird nicht gerundet.</p>
<AmountInput Id="entry-amount" @bind-Value="amountText" />
<p class="text-muted">Betrag ohne Minuszeichen.</p>
</div>
</div>
@@ -50,13 +51,6 @@
}
</fieldset>
@if (isSaving) { <p role="status" aria-live="polite">Wird gespeichert …</p> }
@if (saveError != null)
{
<div class="alert alert-danger" role="alert">@saveError
<details><summary>Technische Details</summary>@saveDetails</details>
</div>
}
<div class="d-flex justify-content-end gap-2">
<button type="submit" class="btn btn-success" disabled="@(isSaving || completed || (committedResult == null && !CanSave))">
@(isSaving ? "Wird gespeichert …" : committedResult != null ? "Ansicht aktualisieren" : "Speichern")
@@ -73,6 +73,7 @@ public partial class AddEntryDialog
private async Task Save()
{
if (isSaving || completed || (committedResult == null && !CanSave)) return;
var busyToast = Toasts.Show("Wird gespeichert …", persistent: true);
isSaving = true;
saveError = null;
saveDetails = null;
@@ -98,8 +99,9 @@ public partial class AddEntryDialog
? "Speichern fehlgeschlagen. Die Eingaben bleiben erhalten. Bitte erneut versuchen."
: "Die Buchung ist gespeichert. Die Ansicht konnte nicht aktualisiert werden. Bitte Ansicht aktualisieren; es wird keine weitere Buchung erzeugt.";
saveDetails = ex.Message;
Toasts.Show(saveError, Duempelkas.App.Services.ToastKind.Error, saveDetails);
}
finally { isSaving = false; }
finally { isSaving = false; Toasts.Dismiss(busyToast); }
}
private async Task Cancel()
@@ -1,3 +1,4 @@
@inject ToastService Toasts
@inject IEntryService EntryService
@inject IAccountService AccountService
@@ -26,8 +27,8 @@
<input id="transfer-title" type="text" class="form-control" @bind="title" @bind:event="oninput" placeholder="Beschreibung der Umbuchung" />
<label class="form-label">Betrag (€)</label>
<div><input id="transfer-amount" type="text" inputmode="decimal" class="form-control" @bind="amountText" @bind:event="oninput" aria-describedby="transfer-amount-help" />
<p id="transfer-amount-help" class="@(!ValidAmount ? "text-danger" : "text-muted")">Betrag ohne Minuszeichen, größer als 0; Komma oder Punkt, höchstens zwei Nachkommastellen. Keine Tausendertrenner oder Währungszeichen; keine Rundung.</p></div>
<div><AmountInput Id="transfer-amount" @bind-Value="amountText" />
<p class="text-muted">Betrag ohne Minuszeichen.</p></div>
</div>
@if (OutsideYear)
@@ -39,13 +40,6 @@
}
</fieldset>
@if (isSaving) { <p role="status" aria-live="polite">Wird gespeichert …</p> }
@if (saveError != null)
{
<div class="alert alert-danger" role="alert">@saveError
<details><summary>Technische Details</summary>@saveDetails</details>
</div>
}
<div class="d-flex justify-content-end gap-2">
<button type="submit" class="btn btn-success" disabled="@(isSaving || completed || (committedResult == null && !CanSave))">
@(isSaving ? "Wird gespeichert …" : committedResult != null ? "Ansicht aktualisieren" : "Speichern")
@@ -81,6 +81,7 @@ public partial class AddTransferDialog
private async Task Save()
{
if (isSaving || completed || (committedResult == null && !CanSave)) return;
var busyToast = Toasts.Show("Wird gespeichert …", persistent: true);
isSaving = true;
saveError = null;
saveDetails = null;
@@ -106,8 +107,9 @@ public partial class AddTransferDialog
? "Speichern fehlgeschlagen. Die Eingaben bleiben erhalten. Bitte erneut versuchen."
: "Die Buchung ist gespeichert. Die Ansicht konnte nicht aktualisiert werden. Bitte Ansicht aktualisieren; es wird keine weitere Buchung erzeugt.";
saveDetails = ex.Message;
Toasts.Show(saveError, Duempelkas.App.Services.ToastKind.Error, saveDetails);
}
finally { isSaving = false; }
finally { isSaving = false; Toasts.Dismiss(busyToast); }
}
private async Task Cancel()