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 |
|
||||
| ✓ | H5 | Games | Scheiss-Spiel + Trigger-Integration | 6 |
|
||||
| ✓ | H6 | UI | Game Setup Dialog | 4 |
|
||||
| ☐ | H7 | Integration | DayDetails Tabs | 3 |
|
||||
| ✓ | H7 | Integration | DayDetails Tabs | 3 |
|
||||
| ☐ | H8 | Features | Undo (unbegrenzt, ohne Redo) | 2 |
|
||||
| ☐ | H9 | Persistenz | DB Persistence & Recovery | 4 |
|
||||
| ☐ | 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.Domain.Enums
|
||||
@using Koogle.Web.Store.DayState
|
||||
@using Koogle.Web.Store.GameState
|
||||
@using Koogle.Web.Store.PersonState
|
||||
@using Koogle.Web.Components.Game
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
|
||||
@inject IState<DayState> DayState
|
||||
@inject IState<GameState> GameState
|
||||
@inject IState<PersonState> PersonState
|
||||
@inject IDispatcher Dispatcher
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IDialogService DialogService
|
||||
|
|
@ -46,6 +51,28 @@ else
|
|||
</MudStack>
|
||||
</MudItem>
|
||||
<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)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled"
|
||||
|
|
@ -71,7 +98,10 @@ else
|
|||
</MudGrid>
|
||||
</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>
|
||||
<!-- Participants Section -->
|
||||
<MudItem xs="12" md="6">
|
||||
|
|
@ -381,6 +411,24 @@ else
|
|||
</MudPaper>
|
||||
</MudItem>
|
||||
</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 {
|
||||
|
|
@ -391,6 +439,7 @@ else
|
|||
private IReadOnlyList<PersonExpenseDto> Expenses => DayState.Value.SelectedDayExpenses;
|
||||
|
||||
private Guid? _selectedParticipantId;
|
||||
private int _activeTabIndex = 0;
|
||||
private DayParticipantDto? SelectedParticipant => Day?.Participants.FirstOrDefault(p => p.PersonId == _selectedParticipantId);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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