using Duempelkas.App.Services; using Duempelkas.App.Services.Models; using Microsoft.AspNetCore.Components; namespace Duempelkas.App.Pages.Accounts; public partial class AccountDetail { #region Parameters [Parameter] public int AccountId { get; set; } [SupplyParameterFromQuery(Name = "jahr")] public int? QueryYear { get; set; } private int? previousQueryYear; #endregion #region Fields private AccountSummaryDto? account; private AccountBalanceDto? balance; private List? entries; private bool showAddEntry; private bool showAddTransfer; private bool showEditName; private bool showEditCarryover; private bool showYearFilterDialog; private int? selectedYear { get => Period.Year; set => Period.Year = value; } private List availableYears = []; private bool entryActionBusy; private Guid? undoToastId; private int? confirmDeleteEntryId; private string? confirmDeleteEntryTitle; private int? confirmRestoreEntryId; private string? confirmRestoreEntryTitle; private string? savedPdfPath; private FileOperationResult? fileResult; private bool fileBusy; private int? savedOutsideYear; private BookingSaved? lastSaved; private string emptyEntriesMessage = "Noch keine Buchungen vorhanden."; private EntryDto? editingEntry; private EntryDto? editingTransferEntry; #endregion #region Navigation private void NavigateBack() => Navigation.NavigateTo("/"); #endregion #region Lifecycle protected override async Task OnParametersSetAsync() { if (QueryYear.HasValue && QueryYear != previousQueryYear) selectedYear = QueryYear; previousQueryYear = QueryYear; await LoadAll(); } #endregion #region Data Loading private async Task LoadAll() { account = await AccountService.GetAccountAsync(AccountId); balance = await BalanceQueryService.GetAccountBalanceAsync(AccountId, selectedYear); entries = await EntryService.GetEntriesAsync(AccountId, selectedYear); 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() { showYearFilterDialog = true; } private void CloseYearFilterDialog() { showYearFilterDialog = false; } private async Task SelectFilterYear(int? year) { selectedYear = year; savedOutsideYear = null; showYearFilterDialog = false; await LoadAll(); } #endregion #region Actions private async Task HandleSaveName(string newName) { await AccountService.RenameAccountAsync(AccountId, newName); showEditName = false; await LoadAll(); } private async Task HandleSaveCarryover(decimal newAmount) { await AccountService.CorrectCarryoverAsync(AccountId, newAmount, selectedYear, balance!.CarryoverBalance, account!.CarryoverBalance); await LoadAll(); showEditCarryover = false; Toasts.Show("Bestandskorrektur gespeichert. Die Änderung gilt für alle Jahre.", ToastKind.Success); } private Task HandleEntryCreated(BookingSaved saved) => HandleBookingSaved(saved); private Task HandleTransferCreated(BookingSaved saved) => HandleBookingSaved(saved); private async Task HandleBookingSaved(BookingSaved saved) { await LoadAll(); lastSaved = saved; savedOutsideYear = selectedYear.HasValue && selectedYear.Value != saved.Date.Year ? saved.Date.Year : null; var nav = Navigation; var period = Period; var accountId = AccountId; Toasts.Show($"{saved.Kind} über {FormatAmount(saved.Amount)} {(saved.Edited ? "aktualisiert" : "gespeichert")}.", ToastKind.Success, actionLabel: savedOutsideYear.HasValue ? $"Buchung im Jahr {saved.Date.Year} anzeigen" : null, action: savedOutsideYear.HasValue ? () => { period.Year = saved.Date.Year; nav.NavigateTo($"/accounts/{accountId}?jahr={saved.Date.Year}"); return Task.CompletedTask; } : null); showAddEntry = false; showAddTransfer = false; editingEntry = null; editingTransferEntry = null; } private Task ShowSavedYear() => SelectFilterYear(savedOutsideYear); private void RequestDeleteEntry(int entryId) { confirmDeleteEntryId = entryId; confirmDeleteEntryTitle = entries?.FirstOrDefault(e => e.Id == entryId)?.Title; } private void CancelDeleteConfirm() { if (entryActionBusy) return; confirmDeleteEntryId = null; confirmDeleteEntryTitle = null; } private async Task HandleConfirmDelete() { if (entryActionBusy || !confirmDeleteEntryId.HasValue) return; entryActionBusy = true; try { var id = confirmDeleteEntryId.Value; await EntryService.DeleteEntryAsync(id); confirmDeleteEntryId = null; await LoadAll(); if (undoToastId.HasValue) Toasts.Dismiss(undoToastId.Value); undoToastId = Toasts.Show("Buchung gelöscht.", ToastKind.Success, actionLabel: "Rückgängig", action: () => UndoEntryChange(id, true)); } catch (Exception ex) { Toasts.Show("Die Buchung konnte nicht geändert werden. Bitte erneut versuchen.", ToastKind.Error, ex.Message); } finally { entryActionBusy = false; } } private void RequestRestoreEntry(int entryId) { confirmRestoreEntryId = entryId; confirmRestoreEntryTitle = entries?.FirstOrDefault(e => e.Id == entryId)?.Title; } private void CancelRestoreConfirm() { if (entryActionBusy) return; confirmRestoreEntryId = null; confirmRestoreEntryTitle = null; } private async Task HandleConfirmRestore() { if (entryActionBusy || !confirmRestoreEntryId.HasValue) return; entryActionBusy = true; try { var id = confirmRestoreEntryId.Value; await EntryService.RestoreEntryAsync(id); confirmRestoreEntryId = null; await LoadAll(); if (undoToastId.HasValue) Toasts.Dismiss(undoToastId.Value); undoToastId = Toasts.Show("Buchung wiederhergestellt.", ToastKind.Success, actionLabel: "Rückgängig", action: () => UndoEntryChange(id, false)); } catch (Exception ex) { Toasts.Show("Die Buchung konnte nicht geändert werden. Bitte erneut versuchen.", ToastKind.Error, ex.Message); } finally { entryActionBusy = false; } } private async Task UndoEntryChange(int id, bool restore) { if (entryActionBusy) return; entryActionBusy = true; try { var current = (await EntryService.GetEntriesAsync(AccountId, (int?)null)).FirstOrDefault(e => e.Id == id); if (current == null || current.IsDeleted != restore) { Toasts.Show("Die Buchung wurde inzwischen geändert. Bitte die aktuelle Liste prüfen."); return; } if (restore) await EntryService.RestoreEntryAsync(id); else await EntryService.DeleteEntryAsync(id); if (undoToastId.HasValue) Toasts.Dismiss(undoToastId.Value); await LoadAll(); Toasts.Show("Änderung rückgängig gemacht.", ToastKind.Success); } finally { entryActionBusy = false; } } private string EntryChangeMessage(int? id, bool restore) { var entry = entries?.FirstOrDefault(e => e.Id == id); if (entry == null) return "Bitte die aktuelle Buchung prüfen."; var description = $"{entry.Date:dd.MM.yyyy}, {entry.Title}, {FormatAmount(entry.Amount)}, Konto {account?.Name}."; return description + (entry.IsTransfer ? $" Diese Umbuchung betrifft {account?.Name} und {entry.LinkedAccountName}. Die Buchungen auf beiden Konten werden gemeinsam {(restore ? "wiederhergestellt und wieder mitgerechnet" : "gelöscht und nicht mehr mitgerechnet")}." : $" Die Buchung wird {(restore ? "wiederhergestellt und wieder mitgerechnet" : "gelöscht und nicht mehr mitgerechnet") }."); } private void RequestEditEntry(int entryId) { var entry = entries?.FirstOrDefault(e => e.Id == entryId); if (entry?.IsTransfer == true) { editingTransferEntry = entry; } else { editingEntry = entry; } } private Task HandleEntryEdited(BookingSaved saved) => HandleBookingSaved(saved); private async Task HandleExport() { if (fileBusy) return; var busyToast = Toasts.Show("Dateiaktion läuft … Bitte warten.", persistent: true); fileBusy = true; fileResult = null; try { var pdf = await PdfStatementService.GenerateStatementAsync(AccountId, selectedYear); var suffix = selectedYear.HasValue ? $"_{selectedYear.Value}" : "_Gesamt"; fileResult = await FileSaveService.SaveFileAsync(pdf, $"{account?.Name}{suffix}.pdf"); savedPdfPath = fileResult.Status == FileOperationStatus.Success ? fileResult.Path : null; } catch (Exception ex) { fileResult = FileOperationResult.Error(ex); } finally { fileBusy = false; Toasts.Dismiss(busyToast); } } private async Task HandleOpenSavedPdf() { if (fileBusy) return; var busyToast = Toasts.Show("Dateiaktion läuft … Bitte warten.", persistent: true); fileBusy = true; fileResult = null; try { if (!string.IsNullOrWhiteSpace(savedPdfPath)) { fileResult = await FileSaveService.OpenFileAsync(savedPdfPath); } savedPdfPath = null; } catch (Exception ex) { fileResult = FileOperationResult.Error(ex); } finally { fileBusy = false; Toasts.Dismiss(busyToast); } } private void CancelOpenSavedPdf() { savedPdfPath = null; } #endregion #region Helpers private static string FormatAmount(decimal amount) => amount.ToString("N2", System.Globalization.CultureInfo.GetCultureInfo("de-DE")) + " €"; private string GetFilterLabel() => Period.Label; #endregion }