Add Club admin page with CRUD (Phase C2)
- Clubs.razor: MudTable with search, create/edit/delete - ClubEditDialog.razor: Form for club name + expense calculation - ConfirmDialog.razor: Reusable confirmation 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
3642ebb9bf
commit
93b55a11e6
|
|
@ -0,0 +1,97 @@
|
||||||
|
@using Koogle.Application.DTOs
|
||||||
|
@using Koogle.Domain.Enums
|
||||||
|
|
||||||
|
<MudDialog>
|
||||||
|
<TitleContent>
|
||||||
|
<MudText Typo="Typo.h6">
|
||||||
|
@(IsEditMode ? "Club bearbeiten" : "Neuer Club")
|
||||||
|
</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" />
|
||||||
|
|
||||||
|
<MudSelect T="ExpenseCalculation" @bind-Value="_expenseCalculation"
|
||||||
|
Label="Kostenberechnung"
|
||||||
|
AnchorOrigin="Origin.BottomCenter">
|
||||||
|
<MudSelectItem Value="ExpenseCalculation.None">Keine</MudSelectItem>
|
||||||
|
<MudSelectItem Value="ExpenseCalculation.Average">Durchschnitt</MudSelectItem>
|
||||||
|
<MudSelectItem Value="ExpenseCalculation.Maximum">Maximum</MudSelectItem>
|
||||||
|
</MudSelect>
|
||||||
|
|
||||||
|
<MudText Typo="Typo.caption" Class="mt-2" Color="Color.Secondary">
|
||||||
|
@GetCalculationDescription(_expenseCalculation)
|
||||||
|
</MudText>
|
||||||
|
</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 ClubDto? Club { get; set; }
|
||||||
|
|
||||||
|
private MudForm? _form;
|
||||||
|
private bool _isValid;
|
||||||
|
private string _name = "";
|
||||||
|
private ExpenseCalculation _expenseCalculation = ExpenseCalculation.None;
|
||||||
|
|
||||||
|
private bool IsEditMode => Club is not null;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
if (Club is not null)
|
||||||
|
{
|
||||||
|
_name = Club.Name;
|
||||||
|
_expenseCalculation = Club.ExpenseCalculation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetCalculationDescription(ExpenseCalculation calculation) => calculation switch
|
||||||
|
{
|
||||||
|
ExpenseCalculation.None => "Keine automatische Kostenberechnung für abwesende Personen",
|
||||||
|
ExpenseCalculation.Average => "Abwesende Personen zahlen den Durchschnitt aller Kosten des Tages",
|
||||||
|
ExpenseCalculation.Maximum => "Abwesende Personen zahlen das Maximum aller Kosten des Tages",
|
||||||
|
_ => ""
|
||||||
|
};
|
||||||
|
|
||||||
|
private void Cancel() => MudDialog?.Cancel();
|
||||||
|
|
||||||
|
private void Submit()
|
||||||
|
{
|
||||||
|
if (!_isValid) return;
|
||||||
|
|
||||||
|
if (IsEditMode)
|
||||||
|
{
|
||||||
|
var dto = new UpdateClubDto
|
||||||
|
{
|
||||||
|
Id = Club!.Id,
|
||||||
|
Name = _name,
|
||||||
|
ExpenseCalculation = _expenseCalculation
|
||||||
|
};
|
||||||
|
MudDialog?.Close(DialogResult.Ok(dto));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var dto = new CreateClubDto
|
||||||
|
{
|
||||||
|
Name = _name,
|
||||||
|
ExpenseCalculation = _expenseCalculation
|
||||||
|
};
|
||||||
|
MudDialog?.Close(DialogResult.Ok(dto));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
@page "/admin/clubs"
|
||||||
|
@attribute [Authorize(Roles = "SuperAdmin")]
|
||||||
|
|
||||||
|
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
|
||||||
|
|
||||||
|
@using Fluxor
|
||||||
|
@using Koogle.Application.DTOs
|
||||||
|
@using Koogle.Domain.Enums
|
||||||
|
@using Koogle.Web.Store.ClubState
|
||||||
|
@using Microsoft.AspNetCore.Authorization
|
||||||
|
|
||||||
|
@inject IState<ClubState> ClubState
|
||||||
|
@inject IDispatcher Dispatcher
|
||||||
|
@inject ISnackbar Snackbar
|
||||||
|
@inject IDialogService DialogService
|
||||||
|
|
||||||
|
<PageTitle>Clubs verwalten</PageTitle>
|
||||||
|
|
||||||
|
<MudText Typo="Typo.h4" Class="mb-4">Clubs verwalten</MudText>
|
||||||
|
|
||||||
|
@if (ClubState.Value.Error is not null)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Error" Class="mb-4" ShowCloseIcon="true" CloseIconClicked="ClearError">
|
||||||
|
@ClubState.Value.Error
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
|
||||||
|
<MudTable Items="ClubState.Value.Clubs" Dense="true" Hover="true" Loading="ClubState.Value.IsLoading"
|
||||||
|
Filter="FilterFunc">
|
||||||
|
<ToolBarContent>
|
||||||
|
<MudTextField @bind-Value="_searchString"
|
||||||
|
Placeholder="Suchen..."
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.Search"
|
||||||
|
IconSize="Size.Medium"
|
||||||
|
Class="mt-0" />
|
||||||
|
<MudSpacer />
|
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Add"
|
||||||
|
OnClick="OpenCreateDialog">
|
||||||
|
Neuer Club
|
||||||
|
</MudButton>
|
||||||
|
</ToolBarContent>
|
||||||
|
<HeaderContent>
|
||||||
|
<MudTh>Name</MudTh>
|
||||||
|
<MudTh>Kostenberechnung</MudTh>
|
||||||
|
<MudTh>Mitglieder</MudTh>
|
||||||
|
<MudTh>Gäste</MudTh>
|
||||||
|
<MudTh>Tage</MudTh>
|
||||||
|
<MudTh>Erstellt</MudTh>
|
||||||
|
<MudTh>Aktionen</MudTh>
|
||||||
|
</HeaderContent>
|
||||||
|
<RowTemplate>
|
||||||
|
<MudTd DataLabel="Name">@context.Name</MudTd>
|
||||||
|
<MudTd DataLabel="Kostenberechnung">
|
||||||
|
<MudChip T="string" Size="Size.Small" Color="GetCalculationColor(context.ExpenseCalculation)">
|
||||||
|
@GetCalculationLabel(context.ExpenseCalculation)
|
||||||
|
</MudChip>
|
||||||
|
</MudTd>
|
||||||
|
<MudTd DataLabel="Mitglieder">@context.MemberCount</MudTd>
|
||||||
|
<MudTd DataLabel="Gäste">@context.GuestCount</MudTd>
|
||||||
|
<MudTd DataLabel="Tage">@context.DayCount</MudTd>
|
||||||
|
<MudTd DataLabel="Erstellt">@context.CreatedAt.ToString("dd.MM.yyyy")</MudTd>
|
||||||
|
<MudTd DataLabel="Aktionen">
|
||||||
|
<MudTooltip Text="Bearbeiten">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
||||||
|
Size="Size.Small"
|
||||||
|
OnClick="@(() => OpenEditDialog(context))"/>
|
||||||
|
</MudTooltip>
|
||||||
|
<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 Clubs gefunden</MudText>
|
||||||
|
</NoRecordsContent>
|
||||||
|
</MudTable>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string _searchString = "";
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
base.OnInitialized();
|
||||||
|
Dispatcher.Dispatch(new LoadClubsAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool FilterFunc(ClubDto club)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(_searchString))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return club.Name.Contains(_searchString, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearError()
|
||||||
|
{
|
||||||
|
Dispatcher.Dispatch(new ClearClubErrorAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetCalculationLabel(ExpenseCalculation calculation) => calculation switch
|
||||||
|
{
|
||||||
|
ExpenseCalculation.None => "Keine",
|
||||||
|
ExpenseCalculation.Average => "Durchschnitt",
|
||||||
|
ExpenseCalculation.Maximum => "Maximum",
|
||||||
|
_ => calculation.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
|
private static Color GetCalculationColor(ExpenseCalculation calculation) => calculation switch
|
||||||
|
{
|
||||||
|
ExpenseCalculation.None => Color.Default,
|
||||||
|
ExpenseCalculation.Average => Color.Info,
|
||||||
|
ExpenseCalculation.Maximum => Color.Warning,
|
||||||
|
_ => Color.Default
|
||||||
|
};
|
||||||
|
|
||||||
|
private async Task OpenCreateDialog()
|
||||||
|
{
|
||||||
|
var dialog = await DialogService.ShowAsync<ClubEditDialog>("Neuer Club");
|
||||||
|
var result = await dialog.Result;
|
||||||
|
|
||||||
|
if (result != null && !result.Canceled && result.Data is CreateClubDto dto)
|
||||||
|
{
|
||||||
|
Dispatcher.Dispatch(new CreateClubAction(dto));
|
||||||
|
Snackbar.Add("Club wird erstellt...", Severity.Info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OpenEditDialog(ClubDto club)
|
||||||
|
{
|
||||||
|
var parameters = new DialogParameters
|
||||||
|
{
|
||||||
|
{ "Club", club }
|
||||||
|
};
|
||||||
|
|
||||||
|
var dialog = await DialogService.ShowAsync<ClubEditDialog>("Club bearbeiten", parameters);
|
||||||
|
var result = await dialog.Result;
|
||||||
|
|
||||||
|
if (result != null && !result.Canceled && result.Data is UpdateClubDto dto)
|
||||||
|
{
|
||||||
|
Dispatcher.Dispatch(new UpdateClubAction(dto));
|
||||||
|
Snackbar.Add("Club wird aktualisiert...", Severity.Info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ConfirmDelete(ClubDto club)
|
||||||
|
{
|
||||||
|
var parameters = new DialogParameters
|
||||||
|
{
|
||||||
|
{ "ContentText", $"Möchten Sie den Club \"{club.Name}\" wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden." },
|
||||||
|
{ "ButtonText", "Löschen" },
|
||||||
|
{ "Color", Color.Error }
|
||||||
|
};
|
||||||
|
|
||||||
|
var dialog = await DialogService.ShowAsync<ConfirmDialog>("Club löschen", parameters);
|
||||||
|
var result = await dialog.Result;
|
||||||
|
|
||||||
|
if (result != null && !result.Canceled)
|
||||||
|
{
|
||||||
|
Dispatcher.Dispatch(new DeleteClubAction(club.Id));
|
||||||
|
Snackbar.Add("Club wird gelöscht...", Severity.Info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<MudDialog>
|
||||||
|
<DialogContent>
|
||||||
|
<MudText>@ContentText</MudText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<MudButton OnClick="Cancel">Abbrechen</MudButton>
|
||||||
|
<MudButton Color="@Color" Variant="Variant.Filled" OnClick="Submit">@ButtonText</MudButton>
|
||||||
|
</DialogActions>
|
||||||
|
</MudDialog>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[CascadingParameter]
|
||||||
|
private IMudDialogInstance? MudDialog { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string ContentText { get; set; } = "Sind Sie sicher?";
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string ButtonText { get; set; } = "OK";
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public Color Color { get; set; } = Color.Primary;
|
||||||
|
|
||||||
|
private void Cancel() => MudDialog?.Cancel();
|
||||||
|
private void Submit() => MudDialog?.Close(DialogResult.Ok(true));
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue