diff --git a/docs/usability-umsetzung.md b/docs/usability-umsetzung.md index 7d1388c..0119cd8 100644 --- a/docs/usability-umsetzung.md +++ b/docs/usability-umsetzung.md @@ -27,3 +27,7 @@ Strukturierte Ergebnisse und gemeinsame Statusanzeige mit optionalen technischen ## U03 – Gleiche Bedeutung in PDF und Oberfläche Einzelkonto und Übersicht enthalten nur wirksame Buchungen. Zeitraum und Bedeutung des Endstands sind ausdrücklich benannt, auch bei „Alle Jahre“ mit Anfangsbestand. Im Einzelkonto zählen Umbuchungen als Zu-/Abgang; die Übersicht zeigt beide Konten in einer Zeile und erklärt die unveränderte Vereinssumme. Prüfung: 19/19 Debug-Tests. PDF-Textextraktion prüft 58,30 € Kontostand, 108,30 € Vereinssumme, ausgeschlossene gelöschte/spätere Buchungen und Richtung. Einzelkonto, gemeinsame Übersicht und Seiten 2/3 eines dreiseitigen Auszugs gerendert und visuell auf Lesbarkeit, wiederholte Kopfzeilen und Abschluss geprüft. + +## U04 – Jahreskontext + +Gemeinsame explizite Jahresauswahl bleibt beim Wechsel Übersicht/Konto/Zurück erhalten, auch „Alle Jahre“. Auswahl enthält aktuelles und gewähltes Jahr auch ohne Buchungen. Liste und Summen benennen den Zeitraum; Leerzustände unterscheiden fehlende Buchungen und leeren Zeitraum. Abweichendes Datum bleibt unverändert, erfordert eine gezielte Bestätigung im Formular und bietet nach Erfolg „Buchung im Jahr … anzeigen“. Prüfung: 21/21 Debug-Tests, einschließlich gerenderter Navigation 2025 → Konto → Zurück, Buchung 2026 bei Filter 2025 und anschließendem Aufruf des gespeicherten Jahres. Dabei bestehende doppelte DbContext-Registrierung korrigiert, da die Singleton-Factory sonst scoped Optionen konsumiert. diff --git a/src/Duempelkas.App/Components/Accounts/EntryTable.razor b/src/Duempelkas.App/Components/Accounts/EntryTable.razor index 0ad0a17..ed26cd5 100644 --- a/src/Duempelkas.App/Components/Accounts/EntryTable.razor +++ b/src/Duempelkas.App/Components/Accounts/EntryTable.razor @@ -20,6 +20,6 @@ @if (!Entries.Any()) { -
Keine Buchungen vorhanden.
+
@EmptyMessage
} diff --git a/src/Duempelkas.App/Components/Accounts/EntryTable.razor.cs b/src/Duempelkas.App/Components/Accounts/EntryTable.razor.cs index 87236e0..cd18e92 100644 --- a/src/Duempelkas.App/Components/Accounts/EntryTable.razor.cs +++ b/src/Duempelkas.App/Components/Accounts/EntryTable.razor.cs @@ -5,6 +5,7 @@ namespace Duempelkas.App.Components.Accounts; public partial class EntryTable { + [Parameter] public string EmptyMessage { get; set; } = "Keine Buchungen vorhanden."; #region Parameters [Parameter] diff --git a/src/Duempelkas.App/Components/Dialogs/AddEntryDialog.razor b/src/Duempelkas.App/Components/Dialogs/AddEntryDialog.razor index bfda88b..fcb637f 100644 --- a/src/Duempelkas.App/Components/Dialogs/AddEntryDialog.razor +++ b/src/Duempelkas.App/Components/Dialogs/AddEntryDialog.razor @@ -24,9 +24,17 @@ + @if (OutsideYear) + { +
+ Datum @date.ToString("dd.MM.yyyy"): Diese Buchung liegt außerhalb von Jahr @ContextYear und wird dort nicht angezeigt. + +
+ } +
diff --git a/src/Duempelkas.App/Components/Dialogs/AddEntryDialog.razor.cs b/src/Duempelkas.App/Components/Dialogs/AddEntryDialog.razor.cs index 327dbbd..e7bfc0f 100644 --- a/src/Duempelkas.App/Components/Dialogs/AddEntryDialog.razor.cs +++ b/src/Duempelkas.App/Components/Dialogs/AddEntryDialog.razor.cs @@ -15,13 +15,18 @@ public partial class AddEntryDialog public EntryDto? EditEntry { get; set; } [Parameter] - public EventCallback OnSave { get; set; } + public EventCallback OnSave { get; set; } [Parameter] public EventCallback OnCancel { get; set; } #endregion + [Parameter] public int? ContextYear { get; set; } + private bool confirmDifferentYear; + private int? confirmedYear; + private bool OutsideYear => ContextYear.HasValue && date.Year != ContextYear.Value; + #region Fields private EntryType entryType = EntryType.Income; @@ -50,6 +55,7 @@ public partial class AddEntryDialog private async Task Save() { + if (OutsideYear && (!confirmDifferentYear || confirmedYear != date.Year)) return; if (EditEntry != null) { await EntryService.UpdateEntryAsync(EditEntry.Id, date, title.Trim(), amount); @@ -59,7 +65,7 @@ public partial class AddEntryDialog await EntryService.CreateEntryAsync(AccountId, entryType, date, title.Trim(), amount); } - await OnSave.InvokeAsync(); + await OnSave.InvokeAsync(date); } private async Task Cancel() => await OnCancel.InvokeAsync(); diff --git a/src/Duempelkas.App/Components/Dialogs/AddTransferDialog.razor b/src/Duempelkas.App/Components/Dialogs/AddTransferDialog.razor index 827651a..807e8c5 100644 --- a/src/Duempelkas.App/Components/Dialogs/AddTransferDialog.razor +++ b/src/Duempelkas.App/Components/Dialogs/AddTransferDialog.razor @@ -25,9 +25,17 @@
+ @if (OutsideYear) + { +
+ Datum @date.ToString("dd.MM.yyyy"): Diese Buchung liegt außerhalb von Jahr @ContextYear und wird dort nicht angezeigt. + +
+ } +
+ disabled="@(!CanSave || (OutsideYear && (!confirmDifferentYear || confirmedYear != date.Year)))"> @(EditEntry != null ? "Speichern" : "Hinzufügen")
diff --git a/src/Duempelkas.App/Components/Dialogs/AddTransferDialog.razor.cs b/src/Duempelkas.App/Components/Dialogs/AddTransferDialog.razor.cs index 28bb846..6e1541c 100644 --- a/src/Duempelkas.App/Components/Dialogs/AddTransferDialog.razor.cs +++ b/src/Duempelkas.App/Components/Dialogs/AddTransferDialog.razor.cs @@ -14,13 +14,18 @@ public partial class AddTransferDialog public EntryDto? EditEntry { get; set; } [Parameter] - public EventCallback OnSave { get; set; } + public EventCallback OnSave { get; set; } [Parameter] public EventCallback OnCancel { get; set; } #endregion + [Parameter] public int? ContextYear { get; set; } + private bool confirmDifferentYear; + private int? confirmedYear; + private bool OutsideYear => ContextYear.HasValue && date.Year != ContextYear.Value; + #region Fields private List accounts = new(); @@ -61,6 +66,7 @@ public partial class AddTransferDialog private async Task Save() { + if (OutsideYear && (!confirmDifferentYear || confirmedYear != date.Year)) return; if (EditEntry != null) { await EntryService.UpdateTransferAsync(EditEntry.Id, targetAccountId, date, title.Trim(), amount); @@ -70,7 +76,7 @@ public partial class AddTransferDialog await EntryService.CreateTransferAsync(SourceAccountId, targetAccountId, date, title.Trim(), amount); } - await OnSave.InvokeAsync(); + await OnSave.InvokeAsync(date); } private async Task Cancel() => await OnCancel.InvokeAsync(); diff --git a/src/Duempelkas.App/Pages/Accounts/AccountDetail.razor b/src/Duempelkas.App/Pages/Accounts/AccountDetail.razor index 7d836fb..966d038 100644 --- a/src/Duempelkas.App/Pages/Accounts/AccountDetail.razor +++ b/src/Duempelkas.App/Pages/Accounts/AccountDetail.razor @@ -1,3 +1,4 @@ +@inject ReportingPeriod Period @page "/accounts/{AccountId:int}" @inject IAccountService AccountService @inject IEntryService EntryService @@ -10,6 +11,13 @@ @if (fileBusy) {

Dateiaktion läuft … Bitte warten.

} + @if (savedOutsideYear.HasValue) + { +
+ Die Buchung wurde im Jahr @savedOutsideYear gespeichert und liegt außerhalb der aktuellen Ansicht. + +
+ } @if (account == null) {
@@ -43,10 +51,11 @@ |
+

Zeitraum für Liste und Summen: @Period.Label

@if (balance != null) {
@@ -96,7 +105,7 @@ @if (entries != null) {
- +
} } @@ -104,14 +113,14 @@ @if (showAddEntry) { - } @if (showAddTransfer) { - } @@ -168,7 +177,7 @@ @if (editingEntry != null) { - @@ -176,7 +185,7 @@ @if (editingTransferEntry != null) { - diff --git a/src/Duempelkas.App/Pages/Accounts/AccountDetail.razor.cs b/src/Duempelkas.App/Pages/Accounts/AccountDetail.razor.cs index 80139e9..e28db1f 100644 --- a/src/Duempelkas.App/Pages/Accounts/AccountDetail.razor.cs +++ b/src/Duempelkas.App/Pages/Accounts/AccountDetail.razor.cs @@ -22,7 +22,7 @@ public partial class AccountDetail private bool showEditName; private bool showEditCarryover; private bool showYearFilterDialog; - private int? selectedYear = DateTime.Now.Year; + private int? selectedYear { get => Period.Year; set => Period.Year = value; } private List availableYears = []; private int? confirmDeleteEntryId; @@ -35,6 +35,8 @@ public partial class AccountDetail private FileOperationResult? fileResult; private bool fileBusy; + private int? savedOutsideYear; + private string emptyEntriesMessage = "Noch keine Buchungen vorhanden."; private EntryDto? editingEntry; private EntryDto? editingTransferEntry; @@ -62,7 +64,9 @@ public partial class AccountDetail account = await AccountService.GetAccountAsync(AccountId); balance = await BalanceQueryService.GetAccountBalanceAsync(AccountId, selectedYear); entries = await EntryService.GetEntriesAsync(AccountId, selectedYear); - availableYears = await EntryService.GetEntryYearsAsync(AccountId); + var entryYears = await EntryService.GetEntryYearsAsync(AccountId); + availableYears = Period.Choices(entryYears); + emptyEntriesMessage = entryYears.Count == 0 ? "Noch keine Buchungen vorhanden." : "Keine Buchungen in diesem Zeitraum. Wähle ein anderes Jahr oder Alle Jahre."; } private void OpenYearFilterDialog() @@ -78,6 +82,7 @@ public partial class AccountDetail private async Task SelectFilterYear(int? year) { selectedYear = year; + savedOutsideYear = null; showYearFilterDialog = false; await LoadAll(); } @@ -100,18 +105,22 @@ public partial class AccountDetail await LoadAll(); } - private async Task HandleEntryCreated() + private async Task HandleEntryCreated(DateTime savedDate) { + savedOutsideYear = selectedYear.HasValue && selectedYear.Value != savedDate.Year ? savedDate.Year : null; showAddEntry = false; await LoadAll(); } - private async Task HandleTransferCreated() + private async Task HandleTransferCreated(DateTime savedDate) { + savedOutsideYear = selectedYear.HasValue && selectedYear.Value != savedDate.Year ? savedDate.Year : null; showAddTransfer = false; await LoadAll(); } + private Task ShowSavedYear() => SelectFilterYear(savedOutsideYear); + private void RequestDeleteEntry(int entryId) { confirmDeleteEntryId = entryId; @@ -171,8 +180,9 @@ public partial class AccountDetail } } - private async Task HandleEntryEdited() + private async Task HandleEntryEdited(DateTime savedDate) { + savedOutsideYear = selectedYear.HasValue && selectedYear.Value != savedDate.Year ? savedDate.Year : null; editingEntry = null; editingTransferEntry = null; await LoadAll(); @@ -226,7 +236,7 @@ public partial class AccountDetail private static string FormatAmount(decimal amount) => $"{amount:N2} €"; - private string GetFilterLabel() => selectedYear?.ToString() ?? "Alle"; + private string GetFilterLabel() => Period.Label; #endregion } diff --git a/src/Duempelkas.App/Pages/Dashboard.razor b/src/Duempelkas.App/Pages/Dashboard.razor index 638eda9..9154fee 100644 --- a/src/Duempelkas.App/Pages/Dashboard.razor +++ b/src/Duempelkas.App/Pages/Dashboard.razor @@ -1,3 +1,4 @@ +@inject ReportingPeriod Period @page "/" @inject IAccountService AccountService @inject IBackupService BackupService @@ -40,7 +41,7 @@ |