Add Person Pages (D2)

- Persons.razor: MudTable with search/filter, CRUD dialogs
- PersonEditDialog.razor: Create/Edit form for persons

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
beo3000 2025-12-24 15:06:37 +01:00
parent 5441bbed3f
commit 2d6a49309f
2 changed files with 266 additions and 0 deletions

View File

@ -0,0 +1,95 @@
@using Koogle.Application.DTOs
@using Koogle.Domain.Enums
<MudDialog>
<TitleContent>
<MudText Typo="Typo.h6">
@(IsEditMode ? "Person bearbeiten" : "Neue Person")
</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="PersonStatus" @bind-Value="_personStatus"
Label="Status"
AnchorOrigin="Origin.BottomCenter">
<MudSelectItem Value="PersonStatus.Member">Mitglied</MudSelectItem>
<MudSelectItem Value="PersonStatus.Guest">Gast</MudSelectItem>
</MudSelect>
<MudText Typo="Typo.caption" Class="mt-2" Color="Color.Secondary">
@GetStatusDescription(_personStatus)
</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 PersonDto? Person { get; set; }
private MudForm? _form;
private bool _isValid;
private string _name = "";
private PersonStatus _personStatus = PersonStatus.Member;
private bool IsEditMode => Person is not null;
protected override void OnInitialized()
{
if (Person is not null)
{
_name = Person.Name;
_personStatus = Person.PersonStatus;
}
}
private static string GetStatusDescription(PersonStatus status) => status switch
{
PersonStatus.Member => "Mitglieder sind feste Vereinsmitglieder und werden bei neuen Spieltagen automatisch als Teilnehmer vorausgewählt.",
PersonStatus.Guest => "Gäste sind keine festen Mitglieder und müssen bei Spieltagen manuell hinzugefügt werden.",
_ => ""
};
private void Cancel() => MudDialog?.Cancel();
private void Submit()
{
if (!_isValid) return;
if (IsEditMode)
{
var dto = new UpdatePersonDto
{
Id = Person!.Id,
Name = _name,
PersonStatus = _personStatus
};
MudDialog?.Close(DialogResult.Ok(dto));
}
else
{
var dto = new CreatePersonDto
{
Name = _name,
PersonStatus = _personStatus
};
MudDialog?.Close(DialogResult.Ok(dto));
}
}
}

View File

@ -0,0 +1,171 @@
@page "/persons"
@attribute [Authorize(Policy = "ClubViewer")]
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
@using Fluxor
@using Koogle.Application.DTOs
@using Koogle.Domain.Enums
@using Koogle.Web.Store.PersonState
@using Microsoft.AspNetCore.Authorization
@inject IState<PersonState> PersonState
@inject IDispatcher Dispatcher
@inject ISnackbar Snackbar
@inject IDialogService DialogService
<PageTitle>Personen</PageTitle>
<MudText Typo="Typo.h4" Class="mb-4">Personen</MudText>
@if (PersonState.Value.Error is not null)
{
<MudAlert Severity="Severity.Error" Class="mb-4" ShowCloseIcon="true" CloseIconClicked="ClearError">
@PersonState.Value.Error
</MudAlert>
}
<MudTable Items="FilteredPersons" Dense="true" Hover="true" Loading="PersonState.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 />
<MudSelect T="PersonStatus?" @bind-Value="_statusFilter" Label="Status" Clearable="true" Class="mr-4" Style="width: 150px;">
<MudSelectItem Value="@((PersonStatus?)PersonStatus.Member)">Mitglied</MudSelectItem>
<MudSelectItem Value="@((PersonStatus?)PersonStatus.Guest)">Gast</MudSelectItem>
</MudSelect>
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Add"
OnClick="OpenCreateDialog">
Neue Person
</MudButton>
</ToolBarContent>
<HeaderContent>
<MudTh>Name</MudTh>
<MudTh>Status</MudTh>
<MudTh>Erstellt</MudTh>
<MudTh>Aktionen</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Name">@context.Name</MudTd>
<MudTd DataLabel="Status">
<MudChip T="string" Size="Size.Small" Color="GetStatusColor(context.PersonStatus)">
@GetStatusLabel(context.PersonStatus)
</MudChip>
</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 Personen gefunden</MudText>
</NoRecordsContent>
</MudTable>
@code {
private string _searchString = "";
private PersonStatus? _statusFilter;
private IEnumerable<PersonDto> FilteredPersons => _statusFilter.HasValue
? PersonState.Value.Persons.Where(p => p.PersonStatus == _statusFilter.Value)
: PersonState.Value.Persons;
protected override void OnInitialized()
{
base.OnInitialized();
Dispatcher.Dispatch(new LoadPersonsAction());
}
private bool FilterFunc(PersonDto person)
{
if (string.IsNullOrWhiteSpace(_searchString))
return true;
return person.Name.Contains(_searchString, StringComparison.OrdinalIgnoreCase);
}
private void ClearError()
{
Dispatcher.Dispatch(new ClearPersonErrorAction());
}
private static string GetStatusLabel(PersonStatus status) => status switch
{
PersonStatus.Member => "Mitglied",
PersonStatus.Guest => "Gast",
_ => status.ToString()
};
private static Color GetStatusColor(PersonStatus status) => status switch
{
PersonStatus.Member => Color.Primary,
PersonStatus.Guest => Color.Secondary,
_ => Color.Default
};
private async Task OpenCreateDialog()
{
var dialog = await DialogService.ShowAsync<PersonEditDialog>("Neue Person");
var result = await dialog.Result;
if (result != null && !result.Canceled && result.Data is CreatePersonDto dto)
{
Dispatcher.Dispatch(new CreatePersonAction(dto));
Snackbar.Add("Person wird erstellt...", Severity.Info);
}
}
private async Task OpenEditDialog(PersonDto person)
{
var parameters = new DialogParameters
{
{ "Person", person }
};
var dialog = await DialogService.ShowAsync<PersonEditDialog>("Person bearbeiten", parameters);
var result = await dialog.Result;
if (result != null && !result.Canceled && result.Data is UpdatePersonDto dto)
{
Dispatcher.Dispatch(new UpdatePersonAction(dto));
Snackbar.Add("Person wird aktualisiert...", Severity.Info);
}
}
private async Task ConfirmDelete(PersonDto person)
{
var parameters = new DialogParameters
{
{ "ContentText", $"Möchten Sie \"{person.Name}\" wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden." },
{ "ButtonText", "Löschen" },
{ "Color", Color.Error }
};
var dialog = await DialogService.ShowAsync<Koogle.Web.Components.Shared.ConfirmDialog>("Person löschen", parameters);
var result = await dialog.Result;
if (result != null && !result.Canceled)
{
Dispatcher.Dispatch(new DeletePersonAction(person.Id));
Snackbar.Add("Person wird gelöscht...", Severity.Info);
}
}
}