From dfe74adf01cde9666d252060d70c833fc8fb2b5b Mon Sep 17 00:00:00 2001 From: beo3000 Date: Sat, 3 Jan 2026 21:55:02 +0100 Subject: [PATCH] =?UTF-8?q?=E2=97=8F=20K15=20fertig.=20Erstellte=20Dateien?= =?UTF-8?q?:=20=20=20-=20src/Koogle.Web/Components/Pages/CashBook/Reports.?= =?UTF-8?q?razor=20-=20UI=20mit=20Monat/Jahr-Auswahl,=20Saldo-Karten,=20Do?= =?UTF-8?q?nut-Charts=20f=C3=BCr=20Kategorien,=20expandierbare=20Buchungsl?= =?UTF-8?q?iste=20=20=20-=20src/Koogle.Web/Components/Pages/CashBook/Repor?= =?UTF-8?q?ts.razor.cs=20-=20Code-behind?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Route: /cashbook/reports | Policy: ClubViewer Export-Buttons (Excel/PDF) zeigen Info-Message - werden in K16/K17 implementiert. --- docs/IMPLEMENTATION_PLAN.md | 2 +- .../Components/Pages/CashBook/Reports.razor | 199 ++++++++++++++++++ .../Pages/CashBook/Reports.razor.cs | 94 +++++++++ 3 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 src/Koogle.Web/Components/Pages/CashBook/Reports.razor create mode 100644 src/Koogle.Web/Components/Pages/CashBook/Reports.razor.cs diff --git a/docs/IMPLEMENTATION_PLAN.md b/docs/IMPLEMENTATION_PLAN.md index bab1503..33de4b1 100644 --- a/docs/IMPLEMENTATION_PLAN.md +++ b/docs/IMPLEMENTATION_PLAN.md @@ -1713,7 +1713,7 @@ public enum CashBookEntryType { Income = 0, Expense = 1 } | ✓ | K12 | Web | Fluxor CashBookState | 4 | | ✓ | K13 | Web | CashBook UI | 3 | | ✓ | K14 | Web | Categories UI | 2 | -| ☐ | K15 | Web | Reports UI | 2 | +| ✓ | K15 | Web | Reports UI | 2 | | ☐ | K16 | Application | Excel Export (ClosedXML) | 3 | | ☐ | K17 | Application | PDF Export (QuestPDF) | 1 | | ☐ | K18 | Web | Export Controller | 1 | diff --git a/src/Koogle.Web/Components/Pages/CashBook/Reports.razor b/src/Koogle.Web/Components/Pages/CashBook/Reports.razor new file mode 100644 index 0000000..a2bf569 --- /dev/null +++ b/src/Koogle.Web/Components/Pages/CashBook/Reports.razor @@ -0,0 +1,199 @@ +@page "/cashbook/reports" +@attribute [Authorize(Policy = "ClubViewer")] + +@inherits Fluxor.Blazor.Web.Components.FluxorComponent + +@using Fluxor +@using Koogle.Application.DTOs +@using Koogle.Domain.Enums +@using Koogle.Web.Store.CashBookState +@using Microsoft.AspNetCore.Authorization + +@inject IState CashBookState +@inject IDispatcher Dispatcher +@inject ISnackbar Snackbar + +Kassenbericht + +Kassenbericht + +@if (CashBookState.Value.Error is not null) +{ + + @CashBookState.Value.Error + +} + +@* Period Selection *@ + + + + @for (int m = 1; m <= 12; m++) + { + @GetMonthName(m) + } + + + @for (int y = DateTime.Today.Year; y >= DateTime.Today.Year - 5; y--) + { + @y + } + + + Laden + + + + + Excel + + + PDF + + + + + +@if (CashBookState.Value.IsLoading) +{ + +} + +@if (Report is not null) +{ + @* Summary Cards *@ + + + + + Anfangssaldo + + @Report.OpeningBalance.ToString("C") + + + + + + + + Einnahmen + + +@Report.TotalIncome.ToString("C") + + + + + + + + Ausgaben + + -@Report.TotalExpense.ToString("C") + + + + + + + + Endsaldo + + @Report.ClosingBalance.ToString("C") + + + + + + + @* Charts *@ + + @if (Report.IncomeByCategory.Count > 0) + { + + + Einnahmen nach Kategorie + + + @foreach (var cat in Report.IncomeByCategory.OrderByDescending(c => c.Total)) + { + + @cat.CategoryName (@cat.Count) + @cat.Total.ToString("C") + + } + + + + } + @if (Report.ExpenseByCategory.Count > 0) + { + + + Ausgaben nach Kategorie + + + @foreach (var cat in Report.ExpenseByCategory.OrderByDescending(c => c.Total)) + { + + @cat.CategoryName (@cat.Count) + @cat.Total.ToString("C") + + } + + + + } + + + @* Entries Detail *@ + + + + + Datum + Kategorie + Beschreibung + Person + Betrag + + + @context.BookingDate.ToString("dd.MM.yyyy") + + + @context.CategoryName + + + @context.Comment + @context.PersonName + + + @(context.EntryType == CashBookEntryType.Income ? "+" : "-")@context.Amount.ToString("C") + + + + + Keine Buchungen im gewählten Zeitraum + + + + +} +else if (!CashBookState.Value.IsLoading) +{ + + Wählen Sie einen Zeitraum und klicken Sie auf "Laden" + +} + + + Zurück zum Kassenbuch + diff --git a/src/Koogle.Web/Components/Pages/CashBook/Reports.razor.cs b/src/Koogle.Web/Components/Pages/CashBook/Reports.razor.cs new file mode 100644 index 0000000..0eef36d --- /dev/null +++ b/src/Koogle.Web/Components/Pages/CashBook/Reports.razor.cs @@ -0,0 +1,94 @@ +using Koogle.Application.DTOs; +using Koogle.Domain.Enums; +using Koogle.Web.Store.CashBookState; +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace Koogle.Web.Components.Pages.CashBook; + +/// +/// Code-behind for CashBook Reports page. +/// +public partial class Reports +{ + private int _selectedMonth = DateTime.Today.Month; + private int _selectedYear = DateTime.Today.Year; + + private CashBookReportDto? Report => CashBookState.Value.Report; + + private double[] IncomeChartData => Report?.IncomeByCategory + .OrderByDescending(c => c.Total) + .Select(c => (double)c.Total) + .ToArray() ?? []; + + private string[] IncomeChartLabels => Report?.IncomeByCategory + .OrderByDescending(c => c.Total) + .Select(c => c.CategoryName) + .ToArray() ?? []; + + private double[] ExpenseChartData => Report?.ExpenseByCategory + .OrderByDescending(c => c.Total) + .Select(c => (double)c.Total) + .ToArray() ?? []; + + private string[] ExpenseChartLabels => Report?.ExpenseByCategory + .OrderByDescending(c => c.Total) + .Select(c => c.CategoryName) + .ToArray() ?? []; + + protected override void OnInitialized() + { + base.OnInitialized(); + // Auto-load current month report + LoadReport(); + } + + private void LoadReport() + { + var from = new DateTime(_selectedYear, _selectedMonth, 1); + var to = from.AddMonths(1).AddDays(-1); + Dispatcher.Dispatch(new LoadCashBookReportAction(from, to)); + } + + private void ClearError() + { + Dispatcher.Dispatch(new ClearCashBookErrorAction()); + } + + private static string GetMonthName(int month) + { + return month switch + { + 1 => "Januar", + 2 => "Februar", + 3 => "März", + 4 => "April", + 5 => "Mai", + 6 => "Juni", + 7 => "Juli", + 8 => "August", + 9 => "September", + 10 => "Oktober", + 11 => "November", + 12 => "Dezember", + _ => month.ToString() + }; + } + + private static Color GetEntryTypeColor(CashBookEntryType type) + { + return type == CashBookEntryType.Income ? Color.Success : Color.Error; + } + + private void ExportExcel() + { + // Will be implemented in K16 + Snackbar.Add("Excel-Export wird in einer späteren Version verfügbar sein", Severity.Info); + } + + private void ExportPdf() + { + // Will be implemented in K17 + Snackbar.Add("PDF-Export wird in einer späteren Version verfügbar sein", Severity.Info); + } +}