merge day detils
This commit is contained in:
commit
27be43fe53
|
|
@ -7,7 +7,8 @@
|
|||
"Bash(git checkout:*)",
|
||||
"Bash(git add:*)",
|
||||
"Bash(git commit:*)",
|
||||
"Bash(mkdir:*)"
|
||||
"Bash(mkdir:*)",
|
||||
"WebSearch"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -343,7 +343,7 @@ NavMenu.razor:
|
|||
| ✓ | D4 | Expenses | Expense Pages | 1 Razor |
|
||||
| ✓ | E1 | Days | DayState Fluxor | 4 State-Dateien |
|
||||
| ✓ | E2 | Days | Days List Page | 1 Razor |
|
||||
| ☐ | E3 | Days | Day Details Page | 1 Razor |
|
||||
| ✓ | E3 | Days | Day Details Page | 1 Razor |
|
||||
| ☐ | E4 | Days | PersonExpense Management | Components in DayDetails |
|
||||
| ☐ | F1 | Dashboard | Dashboard Page | 1 Razor |
|
||||
| ☐ | F2 | Dashboard | Evaluation Components | 2 Shared Components |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
@using Koogle.Application.DTOs
|
||||
@using Koogle.Domain.Enums
|
||||
|
||||
<MudDialog>
|
||||
<TitleContent>
|
||||
<MudText Typo="Typo.h6">Teilnehmer hinzufügen</MudText>
|
||||
</TitleContent>
|
||||
<DialogContent>
|
||||
@if (AvailablePersons.Count == 0)
|
||||
{
|
||||
<MudText Color="Color.Secondary">Keine weiteren Personen verfügbar</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Class="mb-4">Wählen Sie eine Person aus:</MudText>
|
||||
<MudList T="PersonDto" Dense="true" @bind-SelectedValue="_selectedPerson" SelectionMode="SelectionMode.SingleSelection">
|
||||
@foreach (var person in AvailablePersons.OrderByDescending(p => p.PersonStatus == PersonStatus.Member).ThenBy(p => p.Name))
|
||||
{
|
||||
<MudListItem T="PersonDto" Value="person">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||
<MudAvatar Size="Size.Small" Color="@(person.PersonStatus == PersonStatus.Member ? Color.Primary : Color.Secondary)">
|
||||
@person.Name[0]
|
||||
</MudAvatar>
|
||||
<MudText>@person.Name</MudText>
|
||||
@if (person.PersonStatus == PersonStatus.Guest)
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Variant="Variant.Outlined" Color="Color.Secondary">Gast</MudChip>
|
||||
}
|
||||
</MudStack>
|
||||
</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Abbrechen</MudButton>
|
||||
<MudButton Color="Color.Primary" Variant="Variant.Filled" Disabled="@(_selectedPerson is null)" OnClick="Submit">
|
||||
Hinzufügen
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
private IMudDialogInstance? MudDialog { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public IReadOnlyList<PersonDto> AvailablePersons { get; set; } = [];
|
||||
|
||||
[Parameter]
|
||||
public Guid DayId { get; set; }
|
||||
|
||||
private PersonDto? _selectedPerson;
|
||||
|
||||
private void Cancel() => MudDialog?.Cancel();
|
||||
|
||||
private void Submit()
|
||||
{
|
||||
if (_selectedPerson is null) return;
|
||||
MudDialog?.Close(DialogResult.Ok(_selectedPerson.Id));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,407 @@
|
|||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue