From 05d1622bd43a27a65b319f702933fc1c4b965b0c Mon Sep 17 00:00:00 2001 From: beo3000 Date: Sat, 3 Jan 2026 21:49:11 +0100 Subject: [PATCH] K14: add Categories UI page + dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../Pages/CashBook/Categories.razor | 199 ++++++++++++++++++ .../Pages/CashBook/CategoryDialog.razor | 187 ++++++++++++++++ 2 files changed, 386 insertions(+) create mode 100644 src/Koogle.Web/Components/Pages/CashBook/Categories.razor create mode 100644 src/Koogle.Web/Components/Pages/CashBook/CategoryDialog.razor diff --git a/src/Koogle.Web/Components/Pages/CashBook/Categories.razor b/src/Koogle.Web/Components/Pages/CashBook/Categories.razor new file mode 100644 index 0000000..8655b3e --- /dev/null +++ b/src/Koogle.Web/Components/Pages/CashBook/Categories.razor @@ -0,0 +1,199 @@ +@page "/cashbook/categories" +@attribute [Authorize(Policy = "ClubTreasurer")] + +@inherits Fluxor.Blazor.Web.Components.FluxorComponent + +@using Fluxor +@using Koogle.Application.DTOs +@using Koogle.Domain.Enums +@using Koogle.Web.Store.CategoryState +@using Microsoft.AspNetCore.Authorization + +@inject IState CategoryState +@inject IDispatcher Dispatcher +@inject ISnackbar Snackbar +@inject IDialogService DialogService + +Buchungskategorien + + + Buchungskategorien + + Zurück zum Kassenbuch + + + +@if (CategoryState.Value.Error is not null) +{ + + @CategoryState.Value.Error + +} + + + + + + + Neue Kategorie + + + + Farbe + Name + Beschreibung + Typ + Status + Aktionen + + + + @if (!string.IsNullOrWhiteSpace(context.Color)) + { + + } + + + @if (!string.IsNullOrWhiteSpace(context.Icon)) + { + + } + @context.Name + @if (context.IsSystemCategory) + { + + + + } + + + @context.Description + + + + @GetTypeLabel(context.CategoryType) + + + + @if (context.IsActive) + { + Aktiv + } + else + { + Inaktiv + } + + + + + + @if (!context.IsSystemCategory) + { + + + + } + + + + + + + Keine Kategorien gefunden + + + +@code { + private bool _showInactive; + + protected override void OnInitialized() + { + base.OnInitialized(); + LoadCategories(); + } + + private void LoadCategories() + { + Dispatcher.Dispatch(new LoadCategoriesAction(_showInactive)); + } + + protected override void OnParametersSet() + { + base.OnParametersSet(); + } + + private void ClearError() + { + Dispatcher.Dispatch(new ClearCategoryErrorAction()); + } + + private static string GetTypeLabel(BookingCategoryType type) => type switch + { + BookingCategoryType.Income => "Einnahme", + BookingCategoryType.Expense => "Ausgabe", + _ => type.ToString() + }; + + private static Color GetTypeColor(BookingCategoryType type) => type switch + { + BookingCategoryType.Income => Color.Success, + BookingCategoryType.Expense => Color.Error, + _ => Color.Default + }; + + private async Task OpenCreateDialog() + { + var dialog = await DialogService.ShowAsync("Neue Kategorie"); + var result = await dialog.Result; + + if (result != null && !result.Canceled && result.Data is CreateBookingCategoryDto dto) + { + Dispatcher.Dispatch(new CreateCategoryAction(dto)); + Snackbar.Add("Kategorie wird erstellt...", Severity.Info); + } + } + + private async Task OpenEditDialog(BookingCategoryDto category) + { + var parameters = new DialogParameters + { + { "Category", category } + }; + + var dialog = await DialogService.ShowAsync("Kategorie bearbeiten", parameters); + var result = await dialog.Result; + + if (result != null && !result.Canceled && result.Data is UpdateBookingCategoryDto dto) + { + Dispatcher.Dispatch(new UpdateCategoryAction(dto)); + Snackbar.Add("Kategorie wird aktualisiert...", Severity.Info); + } + } + + private async Task ConfirmDelete(BookingCategoryDto category) + { + var parameters = new DialogParameters + { + { "ContentText", $"Möchtest du \"{category.Name}\" wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden." }, + { "ButtonText", "Löschen" }, + { "Color", Color.Error } + }; + + var dialog = await DialogService.ShowAsync("Kategorie löschen", parameters); + var result = await dialog.Result; + + if (result != null && !result.Canceled) + { + Dispatcher.Dispatch(new DeleteCategoryAction(category.Id)); + Snackbar.Add("Kategorie wird gelöscht...", Severity.Info); + } + } +} diff --git a/src/Koogle.Web/Components/Pages/CashBook/CategoryDialog.razor b/src/Koogle.Web/Components/Pages/CashBook/CategoryDialog.razor new file mode 100644 index 0000000..9f3fe67 --- /dev/null +++ b/src/Koogle.Web/Components/Pages/CashBook/CategoryDialog.razor @@ -0,0 +1,187 @@ +@using Koogle.Application.DTOs +@using Koogle.Domain.Enums +@using MudBlazor.Utilities + + + + + @(IsEditMode ? "Kategorie bearbeiten" : "Neue Kategorie") + + + + + + + + + @if (!IsEditMode) + { + + + + + Einnahme + + + + + + Ausgabe + + + + } + else + { + + Typ: @GetTypeLabel(Category!.CategoryType) + + } + + + Darstellung + + + + @if (!string.IsNullOrWhiteSpace(_color?.Value)) + { + + } + + + + @foreach (var iconOption in IconOptions) + { + + + + @iconOption.Label + + + } + + + @if (IsEditMode && !Category!.IsSystemCategory) + { + + + } + + + + Abbrechen + + @(IsEditMode ? "Speichern" : "Erstellen") + + + + +@code { + [CascadingParameter] + private IMudDialogInstance? MudDialog { get; set; } + + [Parameter] + public BookingCategoryDto? Category { get; set; } + + private MudForm? _form; + private bool _isValid; + private string _name = ""; + private string? _description; + private BookingCategoryType _categoryType = BookingCategoryType.Income; + private MudColor? _color; + private string? _icon; + private bool _isActive = true; + + private bool IsEditMode => Category is not null; + + private static readonly List<(string Label, string Value)> IconOptions = + [ + ("Geld", Icons.Material.Filled.AttachMoney), + ("Strafe", Icons.Material.Filled.Gavel), + ("Mitglied", Icons.Material.Filled.CardMembership), + ("Korrektur", Icons.Material.Filled.Build), + ("Saldo", Icons.Material.Filled.Balance), + ("Kasse", Icons.Material.Filled.PointOfSale), + ("Geschenk", Icons.Material.Filled.CardGiftcard), + ("Einkauf", Icons.Material.Filled.ShoppingCart), + ("Essen", Icons.Material.Filled.Restaurant), + ("Getränk", Icons.Material.Filled.LocalBar), + ("Miete", Icons.Material.Filled.Home), + ("Sport", Icons.Material.Filled.SportsScore), + ("Veranstaltung", Icons.Material.Filled.Event), + ("Sonstiges", Icons.Material.Filled.MoreHoriz) + ]; + + protected override void OnInitialized() + { + if (Category is not null) + { + _name = Category.Name; + _description = Category.Description; + _categoryType = Category.CategoryType; + _color = string.IsNullOrWhiteSpace(Category.Color) ? null : new MudColor(Category.Color); + _icon = Category.Icon; + _isActive = Category.IsActive; + } + } + + private static string GetTypeLabel(BookingCategoryType type) => type switch + { + BookingCategoryType.Income => "Einnahme", + BookingCategoryType.Expense => "Ausgabe", + _ => type.ToString() + }; + + private void Cancel() => MudDialog?.Cancel(); + + private void Submit() + { + if (!_isValid) return; + + if (IsEditMode) + { + var dto = new UpdateBookingCategoryDto + { + Id = Category!.Id, + Name = _name, + Description = _description, + Color = _color?.Value, + Icon = _icon, + IsActive = Category.IsSystemCategory ? true : _isActive + }; + MudDialog?.Close(DialogResult.Ok(dto)); + } + else + { + var dto = new CreateBookingCategoryDto + { + Name = _name, + Description = _description, + CategoryType = _categoryType, + Color = _color?.Value, + Icon = _icon + }; + MudDialog?.Close(DialogResult.Ok(dto)); + } + } +}