Complete phase H7: DayDetails Tabs Integration
- GameBoardPanel: Dynamic board component rendering - CompletedGamesList: Shows game history for day - DayDetails: 3 tabs (Details/Eingabe/Tafel) + Start/End Game buttons 🤖 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
5afc8fd251
commit
23a1008a31
|
|
@ -954,7 +954,7 @@ Web: Store/GameState/, Components/Game/, Hubs/GameHub
|
||||||
| ✓ | H4 | Games | Training Game | 5 |
|
| ✓ | H4 | Games | Training Game | 5 |
|
||||||
| ✓ | H5 | Games | Scheiss-Spiel + Trigger-Integration | 6 |
|
| ✓ | H5 | Games | Scheiss-Spiel + Trigger-Integration | 6 |
|
||||||
| ✓ | H6 | UI | Game Setup Dialog | 4 |
|
| ✓ | H6 | UI | Game Setup Dialog | 4 |
|
||||||
| ☐ | H7 | Integration | DayDetails Tabs | 3 |
|
| ✓ | H7 | Integration | DayDetails Tabs | 3 |
|
||||||
| ☐ | H8 | Features | Undo (unbegrenzt, ohne Redo) | 2 |
|
| ☐ | H8 | Features | Undo (unbegrenzt, ohne Redo) | 2 |
|
||||||
| ☐ | H9 | Persistenz | DB Persistence & Recovery | 4 |
|
| ☐ | H9 | Persistenz | DB Persistence & Recovery | 4 |
|
||||||
| ☐ | H9b | Sync | SignalR komplett + Auto-Reconnect | 5 |
|
| ☐ | H9b | Sync | SignalR komplett + Auto-Reconnect | 5 |
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
@using Fluxor
|
||||||
|
@using Koogle.Domain.Enums
|
||||||
|
@using Koogle.Web.Store.GameState
|
||||||
|
@using MudBlazor
|
||||||
|
|
||||||
|
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
|
||||||
|
|
||||||
|
@inject IState<GameState> GameState
|
||||||
|
@inject IDispatcher Dispatcher
|
||||||
|
|
||||||
|
<MudPaper Class="pa-4">
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Class="mb-4">
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.History" />
|
||||||
|
<MudText Typo="Typo.h6">Abgeschlossene Spiele</MudText>
|
||||||
|
</MudStack>
|
||||||
|
@if (GameState.Value.CompletedGames.Count > 0)
|
||||||
|
{
|
||||||
|
<MudChip T="string" Size="Size.Small" Color="Color.Default">
|
||||||
|
@GameState.Value.CompletedGames.Count
|
||||||
|
</MudChip>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
@if (GameState.Value.IsLoading)
|
||||||
|
{
|
||||||
|
<MudProgressLinear Indeterminate="true" Class="mb-4" />
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (GameState.Value.CompletedGames.Count == 0)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Info" Dense="true">
|
||||||
|
Noch keine abgeschlossenen Spiele an diesem Spieltag.
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudTable Items="@GameState.Value.CompletedGames"
|
||||||
|
Dense="true"
|
||||||
|
Hover="true"
|
||||||
|
Striped="true"
|
||||||
|
Breakpoint="Breakpoint.Sm">
|
||||||
|
<HeaderContent>
|
||||||
|
<MudTh>Spieltyp</MudTh>
|
||||||
|
<MudTh>Gestartet</MudTh>
|
||||||
|
<MudTh>Beendet</MudTh>
|
||||||
|
<MudTh>Spieler</MudTh>
|
||||||
|
<MudTh>Status</MudTh>
|
||||||
|
</HeaderContent>
|
||||||
|
<RowTemplate>
|
||||||
|
<MudTd DataLabel="Spieltyp">
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||||
|
<MudIcon Icon="@GetGameIcon(context.GameTypeName)" Size="Size.Small" />
|
||||||
|
<MudText>@GetGameDisplayName(context.GameTypeName)</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudTd>
|
||||||
|
<MudTd DataLabel="Gestartet">
|
||||||
|
@(context.StartedAt?.ToString("HH:mm") ?? "-")
|
||||||
|
</MudTd>
|
||||||
|
<MudTd DataLabel="Beendet">
|
||||||
|
@(context.CompletedAt?.ToString("HH:mm") ?? "-")
|
||||||
|
</MudTd>
|
||||||
|
<MudTd DataLabel="Spieler">
|
||||||
|
@context.ParticipantCount
|
||||||
|
</MudTd>
|
||||||
|
<MudTd DataLabel="Status">
|
||||||
|
<MudChip T="string"
|
||||||
|
Size="Size.Small"
|
||||||
|
Color="@GetStatusColor(context.Status)"
|
||||||
|
Icon="@GetStatusIcon(context.Status)">
|
||||||
|
@GetStatusLabel(context.Status)
|
||||||
|
</MudChip>
|
||||||
|
</MudTd>
|
||||||
|
</RowTemplate>
|
||||||
|
</MudTable>
|
||||||
|
}
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
/// <summary>
|
||||||
|
/// ID of the day to load completed games for.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter]
|
||||||
|
public Guid DayId { get; set; }
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
base.OnInitialized();
|
||||||
|
|
||||||
|
if (DayId != Guid.Empty)
|
||||||
|
{
|
||||||
|
Dispatcher.Dispatch(new LoadCompletedGamesAction(DayId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
base.OnParametersSet();
|
||||||
|
|
||||||
|
if (DayId != Guid.Empty)
|
||||||
|
{
|
||||||
|
Dispatcher.Dispatch(new LoadCompletedGamesAction(DayId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetGameIcon(string gameTypeName) => gameTypeName switch
|
||||||
|
{
|
||||||
|
"Training" => Icons.Material.Filled.FitnessCenter,
|
||||||
|
"Shit" => Icons.Material.Filled.Casino,
|
||||||
|
_ => Icons.Material.Filled.SportsScore
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string GetGameDisplayName(string gameTypeName) => gameTypeName switch
|
||||||
|
{
|
||||||
|
"Training" => "Training",
|
||||||
|
"Shit" => "Scheiss-Spiel",
|
||||||
|
_ => gameTypeName
|
||||||
|
};
|
||||||
|
|
||||||
|
private static Color GetStatusColor(GameStatus status) => status switch
|
||||||
|
{
|
||||||
|
GameStatus.Completed => Color.Success,
|
||||||
|
GameStatus.Aborted => Color.Warning,
|
||||||
|
GameStatus.Active => Color.Info,
|
||||||
|
_ => Color.Default
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string GetStatusIcon(GameStatus status) => status switch
|
||||||
|
{
|
||||||
|
GameStatus.Completed => Icons.Material.Filled.CheckCircle,
|
||||||
|
GameStatus.Aborted => Icons.Material.Filled.Cancel,
|
||||||
|
GameStatus.Active => Icons.Material.Filled.PlayCircle,
|
||||||
|
_ => Icons.Material.Filled.Circle
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string GetStatusLabel(GameStatus status) => status switch
|
||||||
|
{
|
||||||
|
GameStatus.Completed => "Beendet",
|
||||||
|
GameStatus.Aborted => "Abgebrochen",
|
||||||
|
GameStatus.Active => "Aktiv",
|
||||||
|
_ => status.ToString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
@using Fluxor
|
||||||
|
@using Koogle.Application.Games
|
||||||
|
@using Koogle.Domain.Interfaces
|
||||||
|
@using Koogle.Web.Store.GameState
|
||||||
|
@using MudBlazor
|
||||||
|
|
||||||
|
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
|
||||||
|
|
||||||
|
@inject IState<GameState> GameState
|
||||||
|
@inject GameDefinitionRegistry GameRegistry
|
||||||
|
|
||||||
|
<MudPaper Class="pa-4">
|
||||||
|
@if (!GameState.Value.IsGameActive)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Info">
|
||||||
|
Kein aktives Spiel. Starte ein neues Spiel, um die Tafel zu sehen.
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
else if (string.IsNullOrEmpty(GameState.Value.GameTypeName))
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Warning">
|
||||||
|
Spieltyp nicht erkannt.
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@* Header with game type info *@
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Class="mb-4">
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.TableChart" />
|
||||||
|
<MudText Typo="Typo.h6">@_gameDefinition?.DisplayName Tafel</MudText>
|
||||||
|
</MudStack>
|
||||||
|
<MudChip T="string" Color="Color.Success" Size="Size.Small">
|
||||||
|
Aktiv
|
||||||
|
</MudChip>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
@* Dynamic game board component *@
|
||||||
|
@if (_gameDefinition?.BoardComponentType != null)
|
||||||
|
{
|
||||||
|
<DynamicComponent Type="@_gameDefinition.BoardComponentType" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Warning">
|
||||||
|
Keine Tafel-Komponente für diesen Spieltyp verfügbar.
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private IGameDefinition? _gameDefinition;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
base.OnInitialized();
|
||||||
|
GameState.StateChanged += OnGameStateChanged;
|
||||||
|
UpdateGameDefinition();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGameStateChanged(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
UpdateGameDefinition();
|
||||||
|
InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateGameDefinition()
|
||||||
|
{
|
||||||
|
var typeName = GameState.Value.GameTypeName;
|
||||||
|
if (!string.IsNullOrEmpty(typeName))
|
||||||
|
{
|
||||||
|
_gameDefinition = GameRegistry.Get(typeName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_gameDefinition = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
GameState.StateChanged -= OnGameStateChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,9 +7,14 @@
|
||||||
@using Koogle.Application.DTOs
|
@using Koogle.Application.DTOs
|
||||||
@using Koogle.Domain.Enums
|
@using Koogle.Domain.Enums
|
||||||
@using Koogle.Web.Store.DayState
|
@using Koogle.Web.Store.DayState
|
||||||
|
@using Koogle.Web.Store.GameState
|
||||||
|
@using Koogle.Web.Store.PersonState
|
||||||
|
@using Koogle.Web.Components.Game
|
||||||
@using Microsoft.AspNetCore.Authorization
|
@using Microsoft.AspNetCore.Authorization
|
||||||
|
|
||||||
@inject IState<DayState> DayState
|
@inject IState<DayState> DayState
|
||||||
|
@inject IState<GameState> GameState
|
||||||
|
@inject IState<PersonState> PersonState
|
||||||
@inject IDispatcher Dispatcher
|
@inject IDispatcher Dispatcher
|
||||||
@inject ISnackbar Snackbar
|
@inject ISnackbar Snackbar
|
||||||
@inject IDialogService DialogService
|
@inject IDialogService DialogService
|
||||||
|
|
@ -46,6 +51,28 @@ else
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" md="6" Class="d-flex justify-end align-center">
|
<MudItem xs="12" md="6" Class="d-flex justify-end align-center">
|
||||||
|
@if (Day.Status == DayStatus.Started && !GameState.Value.IsGameActive)
|
||||||
|
{
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Success"
|
||||||
|
StartIcon="@Icons.Material.Filled.SportsScore"
|
||||||
|
OnClick="OpenGameSetupDialog"
|
||||||
|
Disabled="DayState.Value.IsLoading || Day.Participants.Count == 0"
|
||||||
|
Class="mr-2">
|
||||||
|
Neues Spiel
|
||||||
|
</MudButton>
|
||||||
|
}
|
||||||
|
@if (GameState.Value.IsGameActive)
|
||||||
|
{
|
||||||
|
<MudButton Variant="Variant.Outlined"
|
||||||
|
Color="Color.Warning"
|
||||||
|
StartIcon="@Icons.Material.Filled.Stop"
|
||||||
|
OnClick="EndGame"
|
||||||
|
Disabled="DayState.Value.IsLoading"
|
||||||
|
Class="mr-2">
|
||||||
|
Spiel beenden
|
||||||
|
</MudButton>
|
||||||
|
}
|
||||||
@if (Day.Status != DayStatus.Closed)
|
@if (Day.Status != DayStatus.Closed)
|
||||||
{
|
{
|
||||||
<MudButton Variant="Variant.Filled"
|
<MudButton Variant="Variant.Filled"
|
||||||
|
|
@ -71,7 +98,10 @@ else
|
||||||
</MudGrid>
|
</MudGrid>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content with Tabs -->
|
||||||
|
<MudTabs @bind-ActivePanelIndex="_activeTabIndex" Elevation="0" Rounded="true" ApplyEffectsToContainer="true" PanelClass="pa-4">
|
||||||
|
<!-- Tab 1: Details (existing content) -->
|
||||||
|
<MudTabPanel Text="Details" Icon="@Icons.Material.Filled.Info">
|
||||||
<MudGrid>
|
<MudGrid>
|
||||||
<!-- Participants Section -->
|
<!-- Participants Section -->
|
||||||
<MudItem xs="12" md="6">
|
<MudItem xs="12" md="6">
|
||||||
|
|
@ -381,6 +411,24 @@ else
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
</MudGrid>
|
</MudGrid>
|
||||||
|
|
||||||
|
<!-- Completed Games List at bottom of Details tab -->
|
||||||
|
<CompletedGamesList DayId="DayId" />
|
||||||
|
</MudTabPanel>
|
||||||
|
|
||||||
|
<!-- Tab 2: Eingabe (Game Input) -->
|
||||||
|
<MudTabPanel Text="Eingabe" Icon="@Icons.Material.Filled.SportsScore" Disabled="@(!GameState.Value.IsGameActive)">
|
||||||
|
<GameInputPanel CurrentPlayerName="@GetCurrentPlayerName()"
|
||||||
|
PlayerNameResolver="@GetPlayerName"
|
||||||
|
OnShowPlayerSelector="@ShowPlayerSelector"
|
||||||
|
OnThrowCompleted="@HandleThrowCompleted" />
|
||||||
|
</MudTabPanel>
|
||||||
|
|
||||||
|
<!-- Tab 3: Tafel (Game Board) -->
|
||||||
|
<MudTabPanel Text="Tafel" Icon="@Icons.Material.Filled.TableChart" Disabled="@(!GameState.Value.IsGameActive)">
|
||||||
|
<GameBoardPanel />
|
||||||
|
</MudTabPanel>
|
||||||
|
</MudTabs>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|
@ -391,6 +439,7 @@ else
|
||||||
private IReadOnlyList<PersonExpenseDto> Expenses => DayState.Value.SelectedDayExpenses;
|
private IReadOnlyList<PersonExpenseDto> Expenses => DayState.Value.SelectedDayExpenses;
|
||||||
|
|
||||||
private Guid? _selectedParticipantId;
|
private Guid? _selectedParticipantId;
|
||||||
|
private int _activeTabIndex = 0;
|
||||||
private DayParticipantDto? SelectedParticipant => Day?.Participants.FirstOrDefault(p => p.PersonId == _selectedParticipantId);
|
private DayParticipantDto? SelectedParticipant => Day?.Participants.FirstOrDefault(p => p.PersonId == _selectedParticipantId);
|
||||||
private IReadOnlyList<ExpenseDto> OneClickExpenses => DayState.Value.AvailableExpenses.Where(e => e.IsOneClick && !e.IsVariable).ToList();
|
private IReadOnlyList<ExpenseDto> OneClickExpenses => DayState.Value.AvailableExpenses.Where(e => e.IsOneClick && !e.IsVariable).ToList();
|
||||||
|
|
||||||
|
|
@ -721,4 +770,72 @@ else
|
||||||
Snackbar.Add("Strafe wird gelöscht...", Severity.Info);
|
Snackbar.Add("Strafe wird gelöscht...", Severity.Info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Game-related methods
|
||||||
|
|
||||||
|
private async Task OpenGameSetupDialog()
|
||||||
|
{
|
||||||
|
if (Day is null) return;
|
||||||
|
|
||||||
|
var parameters = new DialogParameters
|
||||||
|
{
|
||||||
|
{ "DayId", Day.Id }
|
||||||
|
};
|
||||||
|
|
||||||
|
var options = new DialogOptions
|
||||||
|
{
|
||||||
|
MaxWidth = MaxWidth.Medium,
|
||||||
|
FullWidth = true,
|
||||||
|
CloseOnEscapeKey = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var dialog = await DialogService.ShowAsync<GameSetupDialog>("Neues Spiel starten", parameters, options);
|
||||||
|
var result = await dialog.Result;
|
||||||
|
|
||||||
|
if (result != null && !result.Canceled)
|
||||||
|
{
|
||||||
|
// Game started - switch to Eingabe tab
|
||||||
|
_activeTabIndex = 1;
|
||||||
|
Snackbar.Add("Spiel gestartet!", Severity.Success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EndGame()
|
||||||
|
{
|
||||||
|
Dispatcher.Dispatch(new EndGameAction(GameStatus.Completed));
|
||||||
|
_activeTabIndex = 0; // Switch back to Details tab
|
||||||
|
Snackbar.Add("Spiel wird beendet...", Severity.Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? GetCurrentPlayerName()
|
||||||
|
{
|
||||||
|
var playerId = GameState.Value.Participants.CurrentPlayerId;
|
||||||
|
if (playerId is null) return null;
|
||||||
|
|
||||||
|
return GetPlayerName(playerId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetPlayerName(Guid personId)
|
||||||
|
{
|
||||||
|
// First try to get from PersonState
|
||||||
|
var person = PersonState.Value.Persons.FirstOrDefault(p => p.Id == personId);
|
||||||
|
if (person != null) return person.Name;
|
||||||
|
|
||||||
|
// Fallback to day participants
|
||||||
|
var participant = Day?.Participants.FirstOrDefault(p => p.PersonId == personId);
|
||||||
|
return participant?.PersonName ?? "Unbekannt";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ShowPlayerSelector()
|
||||||
|
{
|
||||||
|
// TODO: Implement player selector dialog
|
||||||
|
await Task.CompletedTask;
|
||||||
|
Snackbar.Add("Spieler-Auswahl noch nicht implementiert", Severity.Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task HandleThrowCompleted(ThrowResult result)
|
||||||
|
{
|
||||||
|
// Throw was completed - game logic will handle state updates
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue