408 lines
16 KiB
Plaintext
408 lines
16 KiB
Plaintext
@page "/days/{DayId:guid}"
|
|
@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>Spieltag Details</PageTitle>
|
|
|
|
@if (DayState.Value.IsLoading && DayState.Value.SelectedDay is null)
|
|
{
|
|
<MudProgressLinear Indeterminate="true" />
|
|
}
|
|
else if (DayState.Value.Error is not null)
|
|
{
|
|
<MudAlert Severity="Severity.Error" Class="mb-4" ShowCloseIcon="true" CloseIconClicked="ClearError">
|
|
@DayState.Value.Error
|
|
</MudAlert>
|
|
}
|
|
else if (Day is null)
|
|
{
|
|
<MudAlert Severity="Severity.Warning">Spieltag nicht gefunden</MudAlert>
|
|
}
|
|
else
|
|
{
|
|
<!-- Header -->
|
|
<MudPaper Class="pa-4 mb-4">
|
|
<MudGrid>
|
|
<MudItem xs="12" md="6">
|
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
|
<MudIconButton Icon="@Icons.Material.Filled.ArrowBack" OnClick="NavigateBack" />
|
|
<MudText Typo="Typo.h5">@Day.PostDate.ToString("dddd, dd. MMMM yyyy")</MudText>
|
|
<MudChip T="string" Size="Size.Medium" Color="GetStatusColor(Day.Status)">
|
|
@GetStatusLabel(Day.Status)
|
|
</MudChip>
|
|
</MudStack>
|
|
</MudItem>
|
|
<MudItem xs="12" md="6" Class="d-flex justify-end align-center">
|
|
@if (Day.Status != DayStatus.Closed)
|
|
{
|
|
<MudButton Variant="Variant.Filled"
|
|
Color="GetNextStatusColor(Day.Status)"
|
|
StartIcon="@GetNextStatusIcon(Day.Status)"
|
|
OnClick="AdvanceStatus"
|
|
Disabled="DayState.Value.IsLoading"
|
|
Class="mr-2">
|
|
@GetNextStatusLabel(Day.Status)
|
|
</MudButton>
|
|
}
|
|
@if (Day.Status == DayStatus.New)
|
|
{
|
|
<MudButton Variant="Variant.Outlined"
|
|
Color="Color.Error"
|
|
StartIcon="@Icons.Material.Filled.Delete"
|
|
OnClick="ConfirmDelete"
|
|
Disabled="DayState.Value.IsLoading">
|
|
Löschen
|
|
</MudButton>
|
|
}
|
|
</MudItem>
|
|
</MudGrid>
|
|
</MudPaper>
|
|
|
|
<!-- Main Content -->
|
|
<MudGrid>
|
|
<!-- Participants Section -->
|
|
<MudItem xs="12" md="6">
|
|
<MudPaper Class="pa-4">
|
|
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Class="mb-4">
|
|
<MudText Typo="Typo.h6">
|
|
<MudIcon Icon="@Icons.Material.Filled.People" Class="mr-2" />
|
|
Teilnehmer (@Day.ParticipantCount)
|
|
</MudText>
|
|
@if (Day.Status != DayStatus.Closed)
|
|
{
|
|
<MudButton Variant="Variant.Text"
|
|
Color="Color.Primary"
|
|
StartIcon="@Icons.Material.Filled.PersonAdd"
|
|
OnClick="OpenAddParticipantDialog"
|
|
Size="Size.Small">
|
|
Hinzufügen
|
|
</MudButton>
|
|
}
|
|
</MudStack>
|
|
|
|
@if (Day.Participants.Count == 0)
|
|
{
|
|
<MudText Color="Color.Secondary">Keine Teilnehmer</MudText>
|
|
}
|
|
else
|
|
{
|
|
<MudList T="DayParticipantDto" Dense="true">
|
|
@foreach (var participant in Day.Participants.OrderBy(p => p.PersonName))
|
|
{
|
|
<MudListItem T="DayParticipantDto">
|
|
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Style="width: 100%">
|
|
<MudStack Row="true" AlignItems="AlignItems.Center">
|
|
<MudAvatar Size="Size.Small" Color="@(participant.PersonStatus == PersonStatus.Member ? Color.Primary : Color.Secondary)">
|
|
@participant.PersonName[0]
|
|
</MudAvatar>
|
|
<MudText>@participant.PersonName</MudText>
|
|
@if (participant.PersonStatus == PersonStatus.Guest)
|
|
{
|
|
<MudChip T="string" Size="Size.Small" Variant="Variant.Outlined" Color="Color.Secondary">Gast</MudChip>
|
|
}
|
|
</MudStack>
|
|
@if (Day.Status != DayStatus.Closed)
|
|
{
|
|
<MudIconButton Icon="@Icons.Material.Filled.PersonRemove"
|
|
Size="Size.Small"
|
|
Color="Color.Error"
|
|
OnClick="@(() => RemoveParticipant(participant))" />
|
|
}
|
|
</MudStack>
|
|
</MudListItem>
|
|
}
|
|
</MudList>
|
|
}
|
|
</MudPaper>
|
|
</MudItem>
|
|
|
|
<!-- Status/Info Section -->
|
|
<MudItem xs="12" md="6">
|
|
<MudPaper Class="pa-4">
|
|
<MudText Typo="Typo.h6" Class="mb-4">
|
|
<MudIcon Icon="@Icons.Material.Filled.Info" Class="mr-2" />
|
|
Details
|
|
</MudText>
|
|
|
|
<MudSimpleTable Dense="true" Hover="true" Striped="true">
|
|
<tbody>
|
|
<tr>
|
|
<td><strong>Status</strong></td>
|
|
<td>
|
|
<MudChip T="string" Size="Size.Small" Color="GetStatusColor(Day.Status)">
|
|
@GetStatusLabel(Day.Status)
|
|
</MudChip>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Datum</strong></td>
|
|
<td>@Day.PostDate.ToString("dd.MM.yyyy")</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Teilnehmer</strong></td>
|
|
<td>@Day.ParticipantCount</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Erstellt</strong></td>
|
|
<td>@Day.CreatedAt.ToString("dd.MM.yyyy HH:mm")</td>
|
|
</tr>
|
|
@if (Day.ModifiedAt.HasValue)
|
|
{
|
|
<tr>
|
|
<td><strong>Geändert</strong></td>
|
|
<td>@Day.ModifiedAt.Value.ToString("dd.MM.yyyy HH:mm")</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</MudSimpleTable>
|
|
|
|
<MudDivider Class="my-4" />
|
|
|
|
<MudText Typo="Typo.subtitle2" Class="mb-2">Status-Workflow</MudText>
|
|
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center" Class="mt-2">
|
|
<MudChip T="string" Size="Size.Small"
|
|
Color="@(Day.Status == DayStatus.New ? Color.Info : Color.Success)"
|
|
Icon="@(Day.Status == DayStatus.New ? Icons.Material.Filled.FiberNew : Icons.Material.Filled.Check)">
|
|
Neu
|
|
</MudChip>
|
|
<MudIcon Icon="@Icons.Material.Filled.ArrowForward" Size="Size.Small" Color="Color.Default" />
|
|
<MudChip T="string" Size="Size.Small"
|
|
Color="@GetWorkflowStepColor(DayStatus.Started)"
|
|
Icon="@GetWorkflowStepIcon(DayStatus.Started)">
|
|
Gestartet
|
|
</MudChip>
|
|
<MudIcon Icon="@Icons.Material.Filled.ArrowForward" Size="Size.Small" Color="Color.Default" />
|
|
<MudChip T="string" Size="Size.Small"
|
|
Color="@GetWorkflowStepColor(DayStatus.Closed)"
|
|
Icon="@GetWorkflowStepIcon(DayStatus.Closed)">
|
|
Abgeschlossen
|
|
</MudChip>
|
|
</MudStack>
|
|
</MudPaper>
|
|
</MudItem>
|
|
|
|
<!-- PersonExpense placeholder for E4 -->
|
|
<MudItem xs="12">
|
|
<MudPaper Class="pa-4">
|
|
<MudText Typo="Typo.h6" Class="mb-4">
|
|
<MudIcon Icon="@Icons.Material.Filled.AttachMoney" Class="mr-2" />
|
|
Strafen / Kosten
|
|
</MudText>
|
|
<MudAlert Severity="Severity.Info">
|
|
Die Verwaltung von Strafen und Kosten wird in Phase E4 implementiert.
|
|
</MudAlert>
|
|
</MudPaper>
|
|
</MudItem>
|
|
</MudGrid>
|
|
}
|
|
|
|
@code {
|
|
[Parameter]
|
|
public Guid DayId { get; set; }
|
|
|
|
private DayDto? Day => DayState.Value.SelectedDay;
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
base.OnInitialized();
|
|
Dispatcher.Dispatch(new LoadDayDetailsAction(DayId));
|
|
Dispatcher.Dispatch(new LoadAvailablePersonsAction());
|
|
}
|
|
|
|
protected override void OnParametersSet()
|
|
{
|
|
base.OnParametersSet();
|
|
if (Day?.Id != DayId)
|
|
{
|
|
Dispatcher.Dispatch(new LoadDayDetailsAction(DayId));
|
|
}
|
|
}
|
|
|
|
private void ClearError()
|
|
{
|
|
Dispatcher.Dispatch(new ClearDayErrorAction());
|
|
}
|
|
|
|
private void NavigateBack()
|
|
{
|
|
NavigationManager.NavigateTo("/days");
|
|
}
|
|
|
|
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 static string GetNextStatusLabel(DayStatus status) => status switch
|
|
{
|
|
DayStatus.New => "Starten",
|
|
DayStatus.Started => "Abschließen",
|
|
DayStatus.Postponed => "Fortsetzen",
|
|
_ => ""
|
|
};
|
|
|
|
private static Color GetNextStatusColor(DayStatus status) => status switch
|
|
{
|
|
DayStatus.New => Color.Warning,
|
|
DayStatus.Started => Color.Success,
|
|
DayStatus.Postponed => Color.Warning,
|
|
_ => Color.Default
|
|
};
|
|
|
|
private static string GetNextStatusIcon(DayStatus status) => status switch
|
|
{
|
|
DayStatus.New => Icons.Material.Filled.PlayArrow,
|
|
DayStatus.Started => Icons.Material.Filled.Done,
|
|
DayStatus.Postponed => Icons.Material.Filled.PlayArrow,
|
|
_ => Icons.Material.Filled.Help
|
|
};
|
|
|
|
private Color GetWorkflowStepColor(DayStatus targetStatus)
|
|
{
|
|
if (Day is null) return Color.Default;
|
|
|
|
var statusOrder = new[] { DayStatus.New, DayStatus.Started, DayStatus.Closed };
|
|
var currentIndex = Array.IndexOf(statusOrder, Day.Status);
|
|
var targetIndex = Array.IndexOf(statusOrder, targetStatus);
|
|
|
|
if (targetIndex < currentIndex) return Color.Success; // Completed
|
|
if (targetIndex == currentIndex) return GetStatusColor(Day.Status); // Current
|
|
return Color.Default; // Future
|
|
}
|
|
|
|
private string GetWorkflowStepIcon(DayStatus targetStatus)
|
|
{
|
|
if (Day is null) return Icons.Material.Filled.Circle;
|
|
|
|
var statusOrder = new[] { DayStatus.New, DayStatus.Started, DayStatus.Closed };
|
|
var currentIndex = Array.IndexOf(statusOrder, Day.Status);
|
|
var targetIndex = Array.IndexOf(statusOrder, targetStatus);
|
|
|
|
if (targetIndex < currentIndex) return Icons.Material.Filled.Check; // Completed
|
|
if (targetIndex == currentIndex) return targetStatus switch
|
|
{
|
|
DayStatus.Started => Icons.Material.Filled.PlayArrow,
|
|
DayStatus.Closed => Icons.Material.Filled.Done,
|
|
_ => Icons.Material.Filled.Circle
|
|
};
|
|
return Icons.Material.Filled.Circle; // Future
|
|
}
|
|
|
|
private void AdvanceStatus()
|
|
{
|
|
if (Day is null) return;
|
|
Dispatcher.Dispatch(new AdvanceDayStatusAction(Day.Id));
|
|
Snackbar.Add("Status wird aktualisiert...", Severity.Info);
|
|
}
|
|
|
|
private async Task ConfirmDelete()
|
|
{
|
|
if (Day is null) return;
|
|
|
|
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);
|
|
NavigationManager.NavigateTo("/days");
|
|
}
|
|
}
|
|
|
|
private async Task OpenAddParticipantDialog()
|
|
{
|
|
if (Day is null) return;
|
|
|
|
var existingIds = Day.Participants.Select(p => p.PersonId).ToHashSet();
|
|
var availablePersons = DayState.Value.AvailablePersons
|
|
.Where(p => !existingIds.Contains(p.Id))
|
|
.ToList();
|
|
|
|
if (availablePersons.Count == 0)
|
|
{
|
|
Snackbar.Add("Alle Personen sind bereits Teilnehmer", Severity.Info);
|
|
return;
|
|
}
|
|
|
|
var parameters = new DialogParameters
|
|
{
|
|
{ "AvailablePersons", availablePersons },
|
|
{ "DayId", Day.Id }
|
|
};
|
|
|
|
var dialog = await DialogService.ShowAsync<AddParticipantDialog>("Teilnehmer hinzufügen", parameters);
|
|
var result = await dialog.Result;
|
|
|
|
if (result != null && !result.Canceled && result.Data is Guid personId)
|
|
{
|
|
var dto = new AddDayParticipantDto
|
|
{
|
|
DayId = Day.Id,
|
|
PersonId = personId
|
|
};
|
|
Dispatcher.Dispatch(new AddDayParticipantAction(dto));
|
|
Snackbar.Add("Teilnehmer wird hinzugefügt...", Severity.Info);
|
|
}
|
|
}
|
|
|
|
private async Task RemoveParticipant(DayParticipantDto participant)
|
|
{
|
|
if (Day is null) return;
|
|
|
|
var parameters = new DialogParameters
|
|
{
|
|
{ "ContentText", $"Möchten Sie \"{participant.PersonName}\" wirklich von diesem Spieltag entfernen?" },
|
|
{ "ButtonText", "Entfernen" },
|
|
{ "Color", Color.Warning }
|
|
};
|
|
|
|
var dialog = await DialogService.ShowAsync<Koogle.Web.Components.Shared.ConfirmDialog>("Teilnehmer entfernen", parameters);
|
|
var result = await dialog.Result;
|
|
|
|
if (result != null && !result.Canceled)
|
|
{
|
|
var dto = new RemoveDayParticipantDto
|
|
{
|
|
DayId = Day.Id,
|
|
PersonId = participant.PersonId
|
|
};
|
|
Dispatcher.Dispatch(new RemoveDayParticipantAction(dto));
|
|
Snackbar.Add("Teilnehmer wird entfernt...", Severity.Info);
|
|
}
|
|
}
|
|
}
|