feat(G5): add PendingMembershipsWidget

- new PendingMembershipsWidget.razor shows pending membership count
- only visible to club admins/super admins
- integrated into Dashboard.razor
- click navigates to admin users page

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
beo3000 2025-12-25 20:53:41 +01:00
parent 8e2342fd74
commit ec50e971a9
2 changed files with 96 additions and 0 deletions

View File

@ -4,6 +4,7 @@
@using Koogle.Application.DTOs
@using Koogle.Application.Interfaces
@using Koogle.Domain.Enums
@using Koogle.Web.Components.Shared
@using Microsoft.AspNetCore.Authorization
@inject IDashboardService DashboardService
@ -102,6 +103,11 @@ else if (_dashboard is not null)
</MudCard>
</MudItem>
<!-- Pending Memberships Widget (Admin only) -->
<MudItem xs="12">
<PendingMembershipsWidget />
</MudItem>
<!-- Top Penalty Recipients -->
<MudItem xs="12" md="6">
<MudCard Elevation="2">

View File

@ -0,0 +1,90 @@
@using Fluxor
@using Koogle.Application.DTOs
@using Koogle.Application.Interfaces
@using Koogle.Web.Store.AuthState
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
@inject IUserService UserService
@inject IState<AuthState> AuthState
@inject NavigationManager NavigationManager
@inject ISnackbar Snackbar
@if (_isLoading)
{
<MudProgressLinear Indeterminate="true" Color="Color.Primary" />
}
else if (_pendingCount > 0)
{
<MudCard Elevation="2" Style="cursor: pointer" @onclick="NavigateToUsers">
<MudCardContent Class="d-flex flex-column align-center">
<MudBadge Content="@_pendingCount" Color="Color.Error" Overlap="true">
<MudIcon Icon="@Icons.Material.Filled.PersonAdd" Size="Size.Large" Color="Color.Warning" />
</MudBadge>
<MudText Typo="Typo.h6" Class="mt-2">Offene Beitrittsanträge</MudText>
<MudText Typo="Typo.body2" Color="Color.Secondary">
@(_pendingCount == 1 ? "1 Antrag wartet auf Freigabe" : $"{_pendingCount} Anträge warten auf Freigabe")
</MudText>
</MudCardContent>
</MudCard>
}
@code {
private int _pendingCount;
private bool _isLoading = true;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
await LoadPendingCountAsync();
}
private async Task LoadPendingCountAsync()
{
try
{
_isLoading = true;
var state = AuthState.Value;
var clubId = state.CurrentClub?.ClubId ?? Guid.Empty;
if (clubId == Guid.Empty)
{
_pendingCount = 0;
return;
}
// Only load for club admins or super admins
if (!state.IsClubAdmin && !state.IsSuperAdmin)
{
_pendingCount = 0;
return;
}
var pending = await UserService.GetPendingMembershipsAsync(clubId);
_pendingCount = pending.Count;
}
catch (Exception ex)
{
Snackbar.Add($"Fehler beim Laden der Anträge: {ex.Message}", Severity.Error);
_pendingCount = 0;
}
finally
{
_isLoading = false;
}
}
/// <summary>
/// Reloads pending membership count.
/// </summary>
public async Task RefreshAsync()
{
await LoadPendingCountAsync();
StateHasChanged();
}
private void NavigateToUsers()
{
NavigationManager.NavigateTo("/admin/users");
}
}