● K15 fertig. Erstellte Dateien:
- src/Koogle.Web/Components/Pages/CashBook/Reports.razor - UI mit Monat/Jahr-Auswahl, Saldo-Karten, Donut-Charts für Kategorien, expandierbare Buchungsliste - src/Koogle.Web/Components/Pages/CashBook/Reports.razor.cs - Code-behind Route: /cashbook/reports | Policy: ClubViewer Export-Buttons (Excel/PDF) zeigen Info-Message - werden in K16/K17 implementiert.
This commit is contained in:
parent
e1f73691c4
commit
dfe74adf01
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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> CashBookState
|
||||
@inject IDispatcher Dispatcher
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<PageTitle>Kassenbericht</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-4">Kassenbericht</MudText>
|
||||
|
||||
@if (CashBookState.Value.Error is not null)
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Class="mb-4" ShowCloseIcon="true" CloseIconClicked="ClearError">
|
||||
@CashBookState.Value.Error
|
||||
</MudAlert>
|
||||
}
|
||||
|
||||
@* Period Selection *@
|
||||
<MudPaper Class="pa-4 mb-4" Elevation="1">
|
||||
<MudStack Row="true" Spacing="4" AlignItems="AlignItems.Center">
|
||||
<MudSelect T="int" @bind-Value="_selectedMonth" Label="Monat" Style="width: 150px;">
|
||||
@for (int m = 1; m <= 12; m++)
|
||||
{
|
||||
<MudSelectItem Value="@m">@GetMonthName(m)</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudSelect T="int" @bind-Value="_selectedYear" Label="Jahr" Style="width: 120px;">
|
||||
@for (int y = DateTime.Today.Year; y >= DateTime.Today.Year - 5; y--)
|
||||
{
|
||||
<MudSelectItem Value="@y">@y</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Refresh"
|
||||
OnClick="LoadReport" Disabled="CashBookState.Value.IsLoading">
|
||||
Laden
|
||||
</MudButton>
|
||||
<MudSpacer />
|
||||
<MudButtonGroup Variant="Variant.Outlined" Color="Color.Secondary">
|
||||
<MudButton StartIcon="@Icons.Material.Filled.TableChart" OnClick="ExportExcel" Disabled="Report is null">
|
||||
Excel
|
||||
</MudButton>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.PictureAsPdf" OnClick="ExportPdf" Disabled="Report is null">
|
||||
PDF
|
||||
</MudButton>
|
||||
</MudButtonGroup>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
@if (CashBookState.Value.IsLoading)
|
||||
{
|
||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Class="mb-4" />
|
||||
}
|
||||
|
||||
@if (Report is not null)
|
||||
{
|
||||
@* Summary Cards *@
|
||||
<MudGrid Class="mb-4">
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudPaper Class="pa-4" Elevation="2">
|
||||
<MudStack>
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Secondary">Anfangssaldo</MudText>
|
||||
<MudText Typo="Typo.h5" Color="@(Report.OpeningBalance >= 0 ? Color.Success : Color.Error)">
|
||||
@Report.OpeningBalance.ToString("C")
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudPaper Class="pa-4" Elevation="2">
|
||||
<MudStack>
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Secondary">Einnahmen</MudText>
|
||||
<MudText Typo="Typo.h5" Color="Color.Success">
|
||||
+@Report.TotalIncome.ToString("C")
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudPaper Class="pa-4" Elevation="2">
|
||||
<MudStack>
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Secondary">Ausgaben</MudText>
|
||||
<MudText Typo="Typo.h5" Color="Color.Error">
|
||||
-@Report.TotalExpense.ToString("C")
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudPaper Class="pa-4" Elevation="2">
|
||||
<MudStack>
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Secondary">Endsaldo</MudText>
|
||||
<MudText Typo="Typo.h5" Color="@(Report.ClosingBalance >= 0 ? Color.Success : Color.Error)">
|
||||
@Report.ClosingBalance.ToString("C")
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
@* Charts *@
|
||||
<MudGrid Class="mb-4">
|
||||
@if (Report.IncomeByCategory.Count > 0)
|
||||
{
|
||||
<MudItem xs="12" md="6">
|
||||
<MudPaper Class="pa-4" Elevation="2">
|
||||
<MudText Typo="Typo.h6" Class="mb-2">Einnahmen nach Kategorie</MudText>
|
||||
<MudChart ChartType="ChartType.Donut"
|
||||
InputData="@IncomeChartData"
|
||||
InputLabels="@IncomeChartLabels"
|
||||
Width="100%"
|
||||
Height="250px" />
|
||||
<MudStack Class="mt-2">
|
||||
@foreach (var cat in Report.IncomeByCategory.OrderByDescending(c => c.Total))
|
||||
{
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.body2">@cat.CategoryName (@cat.Count)</MudText>
|
||||
<MudText Typo="Typo.body2" Color="Color.Success">@cat.Total.ToString("C")</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
}
|
||||
@if (Report.ExpenseByCategory.Count > 0)
|
||||
{
|
||||
<MudItem xs="12" md="6">
|
||||
<MudPaper Class="pa-4" Elevation="2">
|
||||
<MudText Typo="Typo.h6" Class="mb-2">Ausgaben nach Kategorie</MudText>
|
||||
<MudChart ChartType="ChartType.Donut"
|
||||
InputData="@ExpenseChartData"
|
||||
InputLabels="@ExpenseChartLabels"
|
||||
Width="100%"
|
||||
Height="250px" />
|
||||
<MudStack Class="mt-2">
|
||||
@foreach (var cat in Report.ExpenseByCategory.OrderByDescending(c => c.Total))
|
||||
{
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.body2">@cat.CategoryName (@cat.Count)</MudText>
|
||||
<MudText Typo="Typo.body2" Color="Color.Error">@cat.Total.ToString("C")</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
|
||||
@* Entries Detail *@
|
||||
<MudExpansionPanels Class="mb-4">
|
||||
<MudExpansionPanel Text="@($"Buchungen ({Report.Entries.Count})")" IsInitiallyExpanded="false">
|
||||
<MudTable Items="Report.Entries" Dense="true" Hover="true">
|
||||
<HeaderContent>
|
||||
<MudTh>Datum</MudTh>
|
||||
<MudTh>Kategorie</MudTh>
|
||||
<MudTh>Beschreibung</MudTh>
|
||||
<MudTh>Person</MudTh>
|
||||
<MudTh Style="text-align: right;">Betrag</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Datum">@context.BookingDate.ToString("dd.MM.yyyy")</MudTd>
|
||||
<MudTd DataLabel="Kategorie">
|
||||
<MudChip T="string" Size="Size.Small" Color="@GetEntryTypeColor(context.EntryType)">
|
||||
@context.CategoryName
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Beschreibung">@context.Comment</MudTd>
|
||||
<MudTd DataLabel="Person">@context.PersonName</MudTd>
|
||||
<MudTd DataLabel="Betrag" Style="text-align: right;">
|
||||
<MudText Color="@GetEntryTypeColor(context.EntryType)" Typo="Typo.body2">
|
||||
@(context.EntryType == CashBookEntryType.Income ? "+" : "-")@context.Amount.ToString("C")
|
||||
</MudText>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
<NoRecordsContent>
|
||||
<MudText>Keine Buchungen im gewählten Zeitraum</MudText>
|
||||
</NoRecordsContent>
|
||||
</MudTable>
|
||||
</MudExpansionPanel>
|
||||
</MudExpansionPanels>
|
||||
}
|
||||
else if (!CashBookState.Value.IsLoading)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">
|
||||
Wählen Sie einen Zeitraum und klicken Sie auf "Laden"
|
||||
</MudAlert>
|
||||
}
|
||||
|
||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="/cashbook" Class="mt-4">
|
||||
Zurück zum Kassenbuch
|
||||
</MudButton>
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Code-behind for CashBook Reports page.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue