feat(G6): add pending memberships tab to Admin Users
- Add tabs: "Alle Benutzer" + "Ausstehende Anträge" - Show pending membership requests with approve/reject buttons - Add RejectMembershipDialog with optional rejection reason 🤖 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
c48a518dda
commit
e62bea77d8
|
|
@ -0,0 +1,29 @@
|
|||
@using Koogle.Application.DTOs
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudText Class="mb-3">
|
||||
Mitgliedschaftsantrag von <strong>@Pending.DisplayName</strong> für Club <strong>@Pending.ClubName</strong> ablehnen?
|
||||
</MudText>
|
||||
<MudTextField @bind-Value="_reason"
|
||||
Label="Begründung (optional)"
|
||||
Variant="Variant.Outlined"
|
||||
Lines="3"
|
||||
Placeholder="z.B. Kein Platz im Club verfügbar..." />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Abbrechen</MudButton>
|
||||
<MudButton Color="Color.Error" Variant="Variant.Filled" OnClick="Submit">Ablehnen</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!;
|
||||
|
||||
[Parameter] public PendingMembershipDto Pending { get; set; } = null!;
|
||||
|
||||
private string _reason = "";
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
private void Submit() => MudDialog.Close(DialogResult.Ok(_reason));
|
||||
}
|
||||
|
|
@ -15,73 +15,123 @@
|
|||
|
||||
<MudText Typo="Typo.h4" Class="mb-4">Benutzer verwalten</MudText>
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
<MudProgressCircular Indeterminate="true" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTable Items="_users" Dense="true" Hover="true" Filter="FilterFunc" Loading="_isLoading">
|
||||
<ToolBarContent>
|
||||
<MudTextField @bind-Value="_searchString"
|
||||
Placeholder="Suchen..."
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Search"
|
||||
IconSize="Size.Medium"
|
||||
Class="mt-0" />
|
||||
</ToolBarContent>
|
||||
<HeaderContent>
|
||||
<MudTh>Name</MudTh>
|
||||
<MudTh>E-Mail</MudTh>
|
||||
<MudTh>Clubs</MudTh>
|
||||
<MudTh>Aktionen</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Name">@context.DisplayName</MudTd>
|
||||
<MudTd DataLabel="E-Mail">@context.Identity.Email</MudTd>
|
||||
<MudTd DataLabel="Clubs">
|
||||
@if (context.ClubMemberships.Count == 0)
|
||||
{
|
||||
<MudText Typo="Typo.caption" Color="Color.Warning">Kein Club</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var membership in context.ClubMemberships)
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Color="@(membership.IsDefault ? Color.Primary : Color.Default)" Class="mr-1 mb-1">
|
||||
@membership.ClubName
|
||||
@if (membership.Roles.Any())
|
||||
<MudTabs @bind-ActivePanelIndex="_activeTabIndex" Elevation="0" Rounded="true" ApplyEffectsToContainer="true" Class="mb-4">
|
||||
<MudTabPanel Text="Alle Benutzer" BadgeData="@(_users.Count)" BadgeColor="Color.Primary">
|
||||
@if (_isLoading)
|
||||
{
|
||||
<MudProgressCircular Indeterminate="true" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTable Items="_users" Dense="true" Hover="true" Filter="FilterFunc" Loading="_isLoading">
|
||||
<ToolBarContent>
|
||||
<MudTextField @bind-Value="_searchString"
|
||||
Placeholder="Suchen..."
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Search"
|
||||
IconSize="Size.Medium"
|
||||
Class="mt-0" />
|
||||
</ToolBarContent>
|
||||
<HeaderContent>
|
||||
<MudTh>Name</MudTh>
|
||||
<MudTh>E-Mail</MudTh>
|
||||
<MudTh>Clubs</MudTh>
|
||||
<MudTh>Aktionen</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Name">@context.DisplayName</MudTd>
|
||||
<MudTd DataLabel="E-Mail">@context.Identity.Email</MudTd>
|
||||
<MudTd DataLabel="Clubs">
|
||||
@if (context.ClubMemberships.Count == 0)
|
||||
{
|
||||
<MudText Typo="Typo.caption" Color="Color.Warning">Kein Club</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var membership in context.ClubMemberships)
|
||||
{
|
||||
<span class="ml-1">(@string.Join(", ", membership.Roles))</span>
|
||||
<MudChip T="string" Size="Size.Small" Color="@(membership.IsDefault ? Color.Primary : Color.Default)" Class="mr-1 mb-1">
|
||||
@membership.ClubName
|
||||
@if (membership.Roles.Any())
|
||||
{
|
||||
<span class="ml-1">(@string.Join(", ", membership.Roles))</span>
|
||||
}
|
||||
</MudChip>
|
||||
}
|
||||
</MudChip>
|
||||
}
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Aktionen">
|
||||
<MudTooltip Text="Club zuweisen">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.GroupAdd"
|
||||
Size="Size.Small"
|
||||
OnClick="@(() => OpenAssignClubDialog(context))"/>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="Rollen verwalten">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Security"
|
||||
Size="Size.Small"
|
||||
OnClick="@(() => OpenManageRolesDialog(context))"/>
|
||||
</MudTooltip>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
<PagerContent>
|
||||
<MudTablePager />
|
||||
</PagerContent>
|
||||
</MudTable>
|
||||
}
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Aktionen">
|
||||
<MudTooltip Text="Club zuweisen">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.GroupAdd"
|
||||
Size="Size.Small"
|
||||
OnClick="@(() => OpenAssignClubDialog(context))"/>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="Rollen verwalten">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Security"
|
||||
Size="Size.Small"
|
||||
OnClick="@(() => OpenManageRolesDialog(context))"/>
|
||||
</MudTooltip>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
<PagerContent>
|
||||
<MudTablePager />
|
||||
</PagerContent>
|
||||
</MudTable>
|
||||
}
|
||||
</MudTabPanel>
|
||||
|
||||
<MudTabPanel Text="Ausstehende Anträge" BadgeData="@(_pendingMemberships.Count)" BadgeColor="@(_pendingMemberships.Any() ? Color.Warning : Color.Default)">
|
||||
@if (_isLoadingPending)
|
||||
{
|
||||
<MudProgressCircular Indeterminate="true" />
|
||||
}
|
||||
else if (!_pendingMemberships.Any())
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Class="mt-4">Keine ausstehenden Mitgliedschaftsanträge.</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTable Items="_pendingMemberships" Dense="true" Hover="true" Class="mt-2">
|
||||
<HeaderContent>
|
||||
<MudTh>Name</MudTh>
|
||||
<MudTh>E-Mail</MudTh>
|
||||
<MudTh>Club</MudTh>
|
||||
<MudTh>Angefragt am</MudTh>
|
||||
<MudTh>Aktionen</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Name">@context.DisplayName</MudTd>
|
||||
<MudTd DataLabel="E-Mail">@context.Email</MudTd>
|
||||
<MudTd DataLabel="Club">@context.ClubName</MudTd>
|
||||
<MudTd DataLabel="Angefragt am">@context.RequestedAt.ToString("dd.MM.yyyy HH:mm")</MudTd>
|
||||
<MudTd DataLabel="Aktionen">
|
||||
<MudTooltip Text="Genehmigen">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Check"
|
||||
Color="Color.Success"
|
||||
Size="Size.Small"
|
||||
OnClick="@(() => ApproveMembership(context))"/>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="Ablehnen">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Close"
|
||||
Color="Color.Error"
|
||||
Size="Size.Small"
|
||||
OnClick="@(() => OpenRejectDialog(context))"/>
|
||||
</MudTooltip>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
}
|
||||
</MudTabPanel>
|
||||
</MudTabs>
|
||||
|
||||
@code {
|
||||
private List<UserDto> _users = new();
|
||||
private List<ClubDto> _clubs = new();
|
||||
private List<PendingMembershipDto> _pendingMemberships = new();
|
||||
private bool _isLoading = true;
|
||||
private bool _isLoadingPending = true;
|
||||
private string _searchString = "";
|
||||
private int _activeTabIndex = 0;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
|
|
@ -91,6 +141,7 @@ else
|
|||
private async Task LoadData()
|
||||
{
|
||||
_isLoading = true;
|
||||
_isLoadingPending = true;
|
||||
try
|
||||
{
|
||||
var usersTask = UserService.GetAllUsersAsync();
|
||||
|
|
@ -99,10 +150,33 @@ else
|
|||
|
||||
_users = (await usersTask).ToList();
|
||||
_clubs = await clubsTask;
|
||||
_isLoading = false;
|
||||
|
||||
await LoadPendingMemberships();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoading = false;
|
||||
_isLoadingPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadPendingMemberships()
|
||||
{
|
||||
_isLoadingPending = true;
|
||||
try
|
||||
{
|
||||
var allPending = new List<PendingMembershipDto>();
|
||||
foreach (var club in _clubs)
|
||||
{
|
||||
var clubPending = await UserService.GetPendingMembershipsAsync(club.Id);
|
||||
allPending.AddRange(clubPending);
|
||||
}
|
||||
_pendingMemberships = allPending.OrderByDescending(p => p.RequestedAt).ToList();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoadingPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -174,4 +248,58 @@ else
|
|||
await LoadData();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApproveMembership(PendingMembershipDto pending)
|
||||
{
|
||||
var currentUser = await UserService.GetCurrentUserAsync();
|
||||
if (currentUser == null)
|
||||
{
|
||||
Snackbar.Add("Nicht angemeldet", Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var success = await UserService.ApproveMembershipAsync(pending.UserProfileId, pending.ClubId, currentUser.ProfileId);
|
||||
if (success)
|
||||
{
|
||||
Snackbar.Add($"Mitgliedschaft von {pending.DisplayName} genehmigt", Severity.Success);
|
||||
await LoadData();
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add("Fehler beim Genehmigen der Mitgliedschaft", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OpenRejectDialog(PendingMembershipDto pending)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ "Pending", pending }
|
||||
};
|
||||
|
||||
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Small };
|
||||
var dialog = await DialogService.ShowAsync<RejectMembershipDialog>("Mitgliedschaft ablehnen", parameters, options);
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (result != null && !result.Canceled && result.Data is string reason)
|
||||
{
|
||||
var currentUser = await UserService.GetCurrentUserAsync();
|
||||
if (currentUser == null)
|
||||
{
|
||||
Snackbar.Add("Nicht angemeldet", Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var success = await UserService.RejectMembershipAsync(pending.UserProfileId, pending.ClubId, currentUser.ProfileId, reason);
|
||||
if (success)
|
||||
{
|
||||
Snackbar.Add($"Mitgliedschaft von {pending.DisplayName} abgelehnt", Severity.Success);
|
||||
await LoadData();
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add("Fehler beim Ablehnen der Mitgliedschaft", Severity.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue