Add Days List Page (E2)
🤖 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
e1a0969d6e
commit
642c4d11bf
|
|
@ -0,0 +1,147 @@
|
||||||
|
@using Koogle.Application.DTOs
|
||||||
|
@using Koogle.Domain.Enums
|
||||||
|
@using Fluxor
|
||||||
|
@using Koogle.Web.Store.PersonState
|
||||||
|
|
||||||
|
@inject IState<PersonState> PersonState
|
||||||
|
@inject IDispatcher Dispatcher
|
||||||
|
|
||||||
|
<MudDialog>
|
||||||
|
<TitleContent>
|
||||||
|
<MudText Typo="Typo.h6">
|
||||||
|
@(IsEditMode ? "Spieltag bearbeiten" : "Neuer Spieltag")
|
||||||
|
</MudText>
|
||||||
|
</TitleContent>
|
||||||
|
<DialogContent>
|
||||||
|
<MudForm @ref="_form" @bind-IsValid="_isValid">
|
||||||
|
<MudDatePicker @bind-Date="_postDate"
|
||||||
|
Label="Datum"
|
||||||
|
Required="true"
|
||||||
|
RequiredError="Datum ist erforderlich"
|
||||||
|
DateFormat="dd.MM.yyyy"
|
||||||
|
Class="mb-4" />
|
||||||
|
|
||||||
|
@if (IsEditMode)
|
||||||
|
{
|
||||||
|
<MudSelect T="DayStatus" @bind-Value="_status"
|
||||||
|
Label="Status"
|
||||||
|
AnchorOrigin="Origin.BottomCenter"
|
||||||
|
Class="mb-4">
|
||||||
|
<MudSelectItem Value="DayStatus.New">Neu</MudSelectItem>
|
||||||
|
<MudSelectItem Value="DayStatus.Started">Gestartet</MudSelectItem>
|
||||||
|
<MudSelectItem Value="DayStatus.Postponed">Verschoben</MudSelectItem>
|
||||||
|
<MudSelectItem Value="DayStatus.Closed">Abgeschlossen</MudSelectItem>
|
||||||
|
</MudSelect>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.subtitle2" Class="mb-2">Teilnehmer vorauswählen</MudText>
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary" Class="mb-2">
|
||||||
|
Mitglieder sind automatisch vorausgewählt. Sie können Teilnehmer später noch ändern.
|
||||||
|
</MudText>
|
||||||
|
|
||||||
|
@if (PersonState.Value.IsLoading)
|
||||||
|
{
|
||||||
|
<MudProgressLinear Indeterminate="true" Class="mb-4" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudChipSet T="Guid" SelectionMode="SelectionMode.MultiSelection" @bind-SelectedValues="_selectedPersonIds" Class="mb-4">
|
||||||
|
@foreach (var person in PersonState.Value.Persons.OrderByDescending(p => p.PersonStatus == PersonStatus.Member).ThenBy(p => p.Name))
|
||||||
|
{
|
||||||
|
<MudChip T="Guid"
|
||||||
|
Value="@person.Id"
|
||||||
|
Default="@(person.PersonStatus == PersonStatus.Member)"
|
||||||
|
Color="@(person.PersonStatus == PersonStatus.Member ? Color.Primary : Color.Default)"
|
||||||
|
Variant="Variant.Outlined">
|
||||||
|
@person.Name
|
||||||
|
@if (person.PersonStatus == PersonStatus.Guest)
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption" Class="ml-1">(Gast)</MudText>
|
||||||
|
}
|
||||||
|
</MudChip>
|
||||||
|
}
|
||||||
|
</MudChipSet>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</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 DaySummaryDto? Day { get; set; }
|
||||||
|
|
||||||
|
private MudForm? _form;
|
||||||
|
private bool _isValid;
|
||||||
|
private DateTime? _postDate = DateTime.Today;
|
||||||
|
private DayStatus _status = DayStatus.New;
|
||||||
|
private IReadOnlyCollection<Guid> _selectedPersonIds = [];
|
||||||
|
|
||||||
|
private bool IsEditMode => Day is not null;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
if (Day is not null)
|
||||||
|
{
|
||||||
|
_postDate = Day.PostDate;
|
||||||
|
_status = Day.Status;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Load persons for selection when creating new day
|
||||||
|
Dispatcher.Dispatch(new LoadPersonsAction());
|
||||||
|
|
||||||
|
// Pre-select all members
|
||||||
|
_selectedPersonIds = PersonState.Value.Persons
|
||||||
|
.Where(p => p.PersonStatus == PersonStatus.Member)
|
||||||
|
.Select(p => p.Id)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnAfterRender(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender && !IsEditMode)
|
||||||
|
{
|
||||||
|
// Update selection after persons are loaded
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Cancel() => MudDialog?.Cancel();
|
||||||
|
|
||||||
|
private void Submit()
|
||||||
|
{
|
||||||
|
if (!_isValid || _postDate is null) return;
|
||||||
|
|
||||||
|
if (IsEditMode)
|
||||||
|
{
|
||||||
|
var dto = new UpdateDayDto
|
||||||
|
{
|
||||||
|
Id = Day!.Id,
|
||||||
|
PostDate = _postDate.Value,
|
||||||
|
Status = _status
|
||||||
|
};
|
||||||
|
MudDialog?.Close(DialogResult.Ok(dto));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var dto = new CreateDayDto
|
||||||
|
{
|
||||||
|
PostDate = _postDate.Value,
|
||||||
|
ParticipantIds = _selectedPersonIds.ToList()
|
||||||
|
};
|
||||||
|
MudDialog?.Close(DialogResult.Ok(dto));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,214 @@
|
||||||
|
@page "/days"
|
||||||
|
@attribute [Authorize(Policy = "ClubViewer")]
|
||||||
|
|
||||||
|
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
|
||||||
|
|
||||||
|
@using Fluxor
|
||||||
|
@using Koogle.Application.DTOs
|
||||||
|
@using Koogle.Domain.Enums
|
||||||
|
@using Koogle.Web.Store.DayState
|
||||||
|
@using Microsoft.AspNetCore.Authorization
|
||||||
|
|
||||||
|
@inject IState<DayState> DayState
|
||||||
|
@inject IDispatcher Dispatcher
|
||||||
|
@inject ISnackbar Snackbar
|
||||||
|
@inject IDialogService DialogService
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
|
<PageTitle>Spieltage</PageTitle>
|
||||||
|
|
||||||
|
<MudText Typo="Typo.h4" Class="mb-4">Spieltage</MudText>
|
||||||
|
|
||||||
|
@if (DayState.Value.Error is not null)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Error" Class="mb-4" ShowCloseIcon="true" CloseIconClicked="ClearError">
|
||||||
|
@DayState.Value.Error
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
|
||||||
|
<MudTable Items="DayState.Value.Days" Dense="true" Hover="true" Loading="DayState.Value.IsLoading"
|
||||||
|
OnRowClick="@((TableRowClickEventArgs<DaySummaryDto> args) => { if (args.Item is not null) NavigateToDayDetails(args.Item); })">
|
||||||
|
<ToolBarContent>
|
||||||
|
<MudSelect T="int?" @bind-Value="_yearFilter" Label="Jahr" Clearable="true" Class="mr-4" Style="width: 120px;">
|
||||||
|
@foreach (var year in AvailableYears)
|
||||||
|
{
|
||||||
|
<MudSelectItem Value="@((int?)year)">@year</MudSelectItem>
|
||||||
|
}
|
||||||
|
</MudSelect>
|
||||||
|
<MudSelect T="DayStatus?" @bind-Value="_statusFilter" Label="Status" Clearable="true" Class="mr-4" Style="width: 150px;">
|
||||||
|
<MudSelectItem Value="@((DayStatus?)DayStatus.New)">Neu</MudSelectItem>
|
||||||
|
<MudSelectItem Value="@((DayStatus?)DayStatus.Started)">Gestartet</MudSelectItem>
|
||||||
|
<MudSelectItem Value="@((DayStatus?)DayStatus.Postponed)">Verschoben</MudSelectItem>
|
||||||
|
<MudSelectItem Value="@((DayStatus?)DayStatus.Closed)">Abgeschlossen</MudSelectItem>
|
||||||
|
</MudSelect>
|
||||||
|
<MudButton Variant="Variant.Outlined" Color="Color.Default" StartIcon="@Icons.Material.Filled.FilterAlt"
|
||||||
|
OnClick="ApplyFilter" Class="mr-2">
|
||||||
|
Filtern
|
||||||
|
</MudButton>
|
||||||
|
<MudSpacer />
|
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Add"
|
||||||
|
OnClick="OpenCreateDialog">
|
||||||
|
Neuer Spieltag
|
||||||
|
</MudButton>
|
||||||
|
</ToolBarContent>
|
||||||
|
<HeaderContent>
|
||||||
|
<MudTh><MudTableSortLabel SortBy="new Func<DaySummaryDto, object>(x => x.PostDate)">Datum</MudTableSortLabel></MudTh>
|
||||||
|
<MudTh>Status</MudTh>
|
||||||
|
<MudTh>Teilnehmer</MudTh>
|
||||||
|
<MudTh>Aktionen</MudTh>
|
||||||
|
</HeaderContent>
|
||||||
|
<RowTemplate>
|
||||||
|
<MudTd DataLabel="Datum">@context.PostDate.ToString("dddd, dd. MMMM yyyy")</MudTd>
|
||||||
|
<MudTd DataLabel="Status">
|
||||||
|
<MudChip T="string" Size="Size.Small" Color="GetStatusColor(context.Status)">
|
||||||
|
@GetStatusLabel(context.Status)
|
||||||
|
</MudChip>
|
||||||
|
</MudTd>
|
||||||
|
<MudTd DataLabel="Teilnehmer">
|
||||||
|
<MudChip T="string" Size="Size.Small" Color="Color.Default" Icon="@Icons.Material.Filled.People">
|
||||||
|
@context.ParticipantCount
|
||||||
|
</MudChip>
|
||||||
|
</MudTd>
|
||||||
|
<MudTd DataLabel="Aktionen">
|
||||||
|
<MudTooltip Text="Details anzeigen">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Visibility"
|
||||||
|
Size="Size.Small"
|
||||||
|
OnClick="@(() => NavigateToDayDetails(context))"
|
||||||
|
OnClick:stopPropagation="true"/>
|
||||||
|
</MudTooltip>
|
||||||
|
<MudTooltip Text="Bearbeiten">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
||||||
|
Size="Size.Small"
|
||||||
|
Disabled="context.Status == DayStatus.Closed"
|
||||||
|
OnClick="@(() => OpenEditDialog(context))"
|
||||||
|
OnClick:stopPropagation="true"/>
|
||||||
|
</MudTooltip>
|
||||||
|
<MudTooltip Text="Löschen">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||||
|
Size="Size.Small"
|
||||||
|
Color="Color.Error"
|
||||||
|
Disabled="context.Status == DayStatus.Closed"
|
||||||
|
OnClick="@(() => ConfirmDelete(context))"
|
||||||
|
OnClick:stopPropagation="true"/>
|
||||||
|
</MudTooltip>
|
||||||
|
</MudTd>
|
||||||
|
</RowTemplate>
|
||||||
|
<PagerContent>
|
||||||
|
<MudTablePager />
|
||||||
|
</PagerContent>
|
||||||
|
<NoRecordsContent>
|
||||||
|
<MudText>Keine Spieltage gefunden</MudText>
|
||||||
|
</NoRecordsContent>
|
||||||
|
</MudTable>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private int? _yearFilter;
|
||||||
|
private DayStatus? _statusFilter;
|
||||||
|
|
||||||
|
private IEnumerable<int> AvailableYears
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var currentYear = DateTime.Now.Year;
|
||||||
|
return Enumerable.Range(currentYear - 5, 7).Reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
base.OnInitialized();
|
||||||
|
_yearFilter = DateTime.Now.Year;
|
||||||
|
LoadDays();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadDays()
|
||||||
|
{
|
||||||
|
var filter = new DayFilterDto
|
||||||
|
{
|
||||||
|
Year = _yearFilter,
|
||||||
|
Status = _statusFilter
|
||||||
|
};
|
||||||
|
Dispatcher.Dispatch(new LoadDaysAction(filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyFilter()
|
||||||
|
{
|
||||||
|
LoadDays();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearError()
|
||||||
|
{
|
||||||
|
Dispatcher.Dispatch(new ClearDayErrorAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetStatusLabel(DayStatus status) => status switch
|
||||||
|
{
|
||||||
|
DayStatus.New => "Neu",
|
||||||
|
DayStatus.Started => "Gestartet",
|
||||||
|
DayStatus.Postponed => "Verschoben",
|
||||||
|
DayStatus.Closed => "Abgeschlossen",
|
||||||
|
_ => status.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
|
private static Color GetStatusColor(DayStatus status) => status switch
|
||||||
|
{
|
||||||
|
DayStatus.New => Color.Info,
|
||||||
|
DayStatus.Started => Color.Warning,
|
||||||
|
DayStatus.Postponed => Color.Secondary,
|
||||||
|
DayStatus.Closed => Color.Success,
|
||||||
|
_ => Color.Default
|
||||||
|
};
|
||||||
|
|
||||||
|
private void NavigateToDayDetails(DaySummaryDto day)
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo($"/days/{day.Id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OpenCreateDialog()
|
||||||
|
{
|
||||||
|
var dialog = await DialogService.ShowAsync<DayEditDialog>("Neuer Spieltag");
|
||||||
|
var result = await dialog.Result;
|
||||||
|
|
||||||
|
if (result != null && !result.Canceled && result.Data is CreateDayDto dto)
|
||||||
|
{
|
||||||
|
Dispatcher.Dispatch(new CreateDayAction(dto));
|
||||||
|
Snackbar.Add("Spieltag wird erstellt...", Severity.Info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OpenEditDialog(DaySummaryDto day)
|
||||||
|
{
|
||||||
|
var parameters = new DialogParameters
|
||||||
|
{
|
||||||
|
{ "Day", day }
|
||||||
|
};
|
||||||
|
|
||||||
|
var dialog = await DialogService.ShowAsync<DayEditDialog>("Spieltag bearbeiten", parameters);
|
||||||
|
var result = await dialog.Result;
|
||||||
|
|
||||||
|
if (result != null && !result.Canceled && result.Data is UpdateDayDto dto)
|
||||||
|
{
|
||||||
|
Dispatcher.Dispatch(new UpdateDayAction(dto));
|
||||||
|
Snackbar.Add("Spieltag wird aktualisiert...", Severity.Info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ConfirmDelete(DaySummaryDto day)
|
||||||
|
{
|
||||||
|
var parameters = new DialogParameters
|
||||||
|
{
|
||||||
|
{ "ContentText", $"Möchten Sie den Spieltag vom {day.PostDate:dd.MM.yyyy} 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>("Spieltag löschen", parameters);
|
||||||
|
var result = await dialog.Result;
|
||||||
|
|
||||||
|
if (result != null && !result.Canceled)
|
||||||
|
{
|
||||||
|
Dispatcher.Dispatch(new DeleteDayAction(day.Id));
|
||||||
|
Snackbar.Add("Spieltag wird gelöscht...", Severity.Info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue