K14: add Categories UI page + dialog
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d0beb200b3
commit
05d1622bd4
|
|
@ -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> CategoryState
|
||||
@inject IDispatcher Dispatcher
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IDialogService DialogService
|
||||
|
||||
<PageTitle>Buchungskategorien</PageTitle>
|
||||
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center" Class="mb-4">
|
||||
<MudText Typo="Typo.h4">Buchungskategorien</MudText>
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Secondary" StartIcon="@Icons.Material.Filled.ArrowBack"
|
||||
Href="/cashbook">
|
||||
Zurück zum Kassenbuch
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
|
||||
@if (CategoryState.Value.Error is not null)
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Class="mb-4" ShowCloseIcon="true" CloseIconClicked="ClearError">
|
||||
@CategoryState.Value.Error
|
||||
</MudAlert>
|
||||
}
|
||||
|
||||
<MudTable Items="CategoryState.Value.Categories" Dense="true" Hover="true" Loading="CategoryState.Value.IsLoading">
|
||||
<ToolBarContent>
|
||||
<MudSwitch @bind-Value="_showInactive" Label="Inaktive anzeigen" Color="Color.Primary" Class="mr-4" />
|
||||
<MudSpacer />
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Add"
|
||||
OnClick="OpenCreateDialog">
|
||||
Neue Kategorie
|
||||
</MudButton>
|
||||
</ToolBarContent>
|
||||
<HeaderContent>
|
||||
<MudTh>Farbe</MudTh>
|
||||
<MudTh>Name</MudTh>
|
||||
<MudTh>Beschreibung</MudTh>
|
||||
<MudTh>Typ</MudTh>
|
||||
<MudTh>Status</MudTh>
|
||||
<MudTh>Aktionen</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Farbe">
|
||||
@if (!string.IsNullOrWhiteSpace(context.Color))
|
||||
{
|
||||
<MudIcon Icon="@Icons.Material.Filled.Circle" Style="@($"color: {context.Color}")" Size="Size.Small" />
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Name" Style="@(context.IsActive ? "" : "opacity: 0.5;")">
|
||||
@if (!string.IsNullOrWhiteSpace(context.Icon))
|
||||
{
|
||||
<MudIcon Icon="@context.Icon" Size="Size.Small" Class="mr-1" />
|
||||
}
|
||||
@context.Name
|
||||
@if (context.IsSystemCategory)
|
||||
{
|
||||
<MudTooltip Text="System-Kategorie (nicht löschbar)">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Lock" Size="Size.Small" Color="Color.Secondary" Class="ml-1" />
|
||||
</MudTooltip>
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Beschreibung" Style="@(context.IsActive ? "" : "opacity: 0.5;")">
|
||||
@context.Description
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Typ">
|
||||
<MudChip T="string" Size="Size.Small" Color="@GetTypeColor(context.CategoryType)">
|
||||
@GetTypeLabel(context.CategoryType)
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Status">
|
||||
@if (context.IsActive)
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Color="Color.Success">Aktiv</MudChip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Color="Color.Default">Inaktiv</MudChip>
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Aktionen">
|
||||
<MudTooltip Text="Bearbeiten">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
||||
Size="Size.Small"
|
||||
OnClick="@(() => OpenEditDialog(context))"/>
|
||||
</MudTooltip>
|
||||
@if (!context.IsSystemCategory)
|
||||
{
|
||||
<MudTooltip Text="Löschen">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
Size="Size.Small"
|
||||
Color="Color.Error"
|
||||
OnClick="@(() => ConfirmDelete(context))"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
<PagerContent>
|
||||
<MudTablePager />
|
||||
</PagerContent>
|
||||
<NoRecordsContent>
|
||||
<MudText>Keine Kategorien gefunden</MudText>
|
||||
</NoRecordsContent>
|
||||
</MudTable>
|
||||
|
||||
@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<CategoryDialog>("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<CategoryDialog>("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<Shared.ConfirmDialog>("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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
@using Koogle.Application.DTOs
|
||||
@using Koogle.Domain.Enums
|
||||
@using MudBlazor.Utilities
|
||||
|
||||
<MudDialog>
|
||||
<TitleContent>
|
||||
<MudText Typo="Typo.h6">
|
||||
@(IsEditMode ? "Kategorie bearbeiten" : "Neue Kategorie")
|
||||
</MudText>
|
||||
</TitleContent>
|
||||
<DialogContent>
|
||||
<MudForm @ref="_form" @bind-IsValid="_isValid">
|
||||
<MudTextField @bind-Value="_name"
|
||||
Label="Name"
|
||||
Required="true"
|
||||
RequiredError="Name ist erforderlich"
|
||||
Immediate="true"
|
||||
Class="mb-4" />
|
||||
|
||||
<MudTextField @bind-Value="_description"
|
||||
Label="Beschreibung"
|
||||
Lines="2"
|
||||
Class="mb-4" />
|
||||
|
||||
@if (!IsEditMode)
|
||||
{
|
||||
<MudSelect T="BookingCategoryType" @bind-Value="_categoryType"
|
||||
Label="Typ"
|
||||
Required="true"
|
||||
AnchorOrigin="Origin.BottomCenter"
|
||||
Class="mb-4">
|
||||
<MudSelectItem Value="BookingCategoryType.Income">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowUpward" Color="Color.Success" Size="Size.Small" />
|
||||
<MudText>Einnahme</MudText>
|
||||
</MudStack>
|
||||
</MudSelectItem>
|
||||
<MudSelectItem Value="BookingCategoryType.Expense">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowDownward" Color="Color.Error" Size="Size.Small" />
|
||||
<MudText>Ausgabe</MudText>
|
||||
</MudStack>
|
||||
</MudSelectItem>
|
||||
</MudSelect>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary" Class="mb-4">
|
||||
Typ: @GetTypeLabel(Category!.CategoryType)
|
||||
</MudText>
|
||||
}
|
||||
|
||||
<MudDivider Class="my-4" />
|
||||
<MudText Typo="Typo.subtitle2" Class="mb-2">Darstellung</MudText>
|
||||
|
||||
<MudStack Row="true" Spacing="4" Class="mb-4">
|
||||
<MudColorPicker @bind-Value="_color"
|
||||
Label="Farbe"
|
||||
DisableAlpha="true"
|
||||
ColorPickerMode="ColorPickerMode.HEX"
|
||||
Style="width: 150px;" />
|
||||
@if (!string.IsNullOrWhiteSpace(_color?.Value))
|
||||
{
|
||||
<MudIcon Icon="@Icons.Material.Filled.Circle" Style="@($"color: {_color.Value}; align-self: center;")" />
|
||||
}
|
||||
</MudStack>
|
||||
|
||||
<MudSelect T="string" @bind-Value="_icon"
|
||||
Label="Icon"
|
||||
Clearable="true"
|
||||
AnchorOrigin="Origin.BottomCenter"
|
||||
Class="mb-4">
|
||||
@foreach (var iconOption in IconOptions)
|
||||
{
|
||||
<MudSelectItem Value="@iconOption.Value">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@iconOption.Value" Size="Size.Small" />
|
||||
<MudText>@iconOption.Label</MudText>
|
||||
</MudStack>
|
||||
</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
@if (IsEditMode && !Category!.IsSystemCategory)
|
||||
{
|
||||
<MudDivider Class="my-4" />
|
||||
<MudCheckBox @bind-Value="_isActive" Label="Aktiv" Color="Color.Primary" />
|
||||
}
|
||||
</MudForm>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Abbrechen</MudButton>
|
||||
<MudButton Color="Color.Primary" Variant="Variant.Filled" Disabled="!_isValid" OnClick="Submit">
|
||||
@(IsEditMode ? "Speichern" : "Erstellen")
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue