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>
|
<MudText Typo="Typo.h4" Class="mb-4">Benutzer verwalten</MudText>
|
||||||
|
|
||||||
@if (_isLoading)
|
<MudTabs @bind-ActivePanelIndex="_activeTabIndex" Elevation="0" Rounded="true" ApplyEffectsToContainer="true" Class="mb-4">
|
||||||
{
|
<MudTabPanel Text="Alle Benutzer" BadgeData="@(_users.Count)" BadgeColor="Color.Primary">
|
||||||
<MudProgressCircular Indeterminate="true" />
|
@if (_isLoading)
|
||||||
}
|
{
|
||||||
else
|
<MudProgressCircular Indeterminate="true" />
|
||||||
{
|
}
|
||||||
<MudTable Items="_users" Dense="true" Hover="true" Filter="FilterFunc" Loading="_isLoading">
|
else
|
||||||
<ToolBarContent>
|
{
|
||||||
<MudTextField @bind-Value="_searchString"
|
<MudTable Items="_users" Dense="true" Hover="true" Filter="FilterFunc" Loading="_isLoading">
|
||||||
Placeholder="Suchen..."
|
<ToolBarContent>
|
||||||
Adornment="Adornment.Start"
|
<MudTextField @bind-Value="_searchString"
|
||||||
AdornmentIcon="@Icons.Material.Filled.Search"
|
Placeholder="Suchen..."
|
||||||
IconSize="Size.Medium"
|
Adornment="Adornment.Start"
|
||||||
Class="mt-0" />
|
AdornmentIcon="@Icons.Material.Filled.Search"
|
||||||
</ToolBarContent>
|
IconSize="Size.Medium"
|
||||||
<HeaderContent>
|
Class="mt-0" />
|
||||||
<MudTh>Name</MudTh>
|
</ToolBarContent>
|
||||||
<MudTh>E-Mail</MudTh>
|
<HeaderContent>
|
||||||
<MudTh>Clubs</MudTh>
|
<MudTh>Name</MudTh>
|
||||||
<MudTh>Aktionen</MudTh>
|
<MudTh>E-Mail</MudTh>
|
||||||
</HeaderContent>
|
<MudTh>Clubs</MudTh>
|
||||||
<RowTemplate>
|
<MudTh>Aktionen</MudTh>
|
||||||
<MudTd DataLabel="Name">@context.DisplayName</MudTd>
|
</HeaderContent>
|
||||||
<MudTd DataLabel="E-Mail">@context.Identity.Email</MudTd>
|
<RowTemplate>
|
||||||
<MudTd DataLabel="Clubs">
|
<MudTd DataLabel="Name">@context.DisplayName</MudTd>
|
||||||
@if (context.ClubMemberships.Count == 0)
|
<MudTd DataLabel="E-Mail">@context.Identity.Email</MudTd>
|
||||||
{
|
<MudTd DataLabel="Clubs">
|
||||||
<MudText Typo="Typo.caption" Color="Color.Warning">Kein Club</MudText>
|
@if (context.ClubMemberships.Count == 0)
|
||||||
}
|
{
|
||||||
else
|
<MudText Typo="Typo.caption" Color="Color.Warning">Kein Club</MudText>
|
||||||
{
|
}
|
||||||
@foreach (var membership in context.ClubMemberships)
|
else
|
||||||
{
|
{
|
||||||
<MudChip T="string" Size="Size.Small" Color="@(membership.IsDefault ? Color.Primary : Color.Default)" Class="mr-1 mb-1">
|
@foreach (var membership in context.ClubMemberships)
|
||||||
@membership.ClubName
|
|
||||||
@if (membership.Roles.Any())
|
|
||||||
{
|
{
|
||||||
<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">
|
||||||
</MudTd>
|
<MudTooltip Text="Club zuweisen">
|
||||||
<MudTd DataLabel="Aktionen">
|
<MudIconButton Icon="@Icons.Material.Filled.GroupAdd"
|
||||||
<MudTooltip Text="Club zuweisen">
|
Size="Size.Small"
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.GroupAdd"
|
OnClick="@(() => OpenAssignClubDialog(context))"/>
|
||||||
Size="Size.Small"
|
</MudTooltip>
|
||||||
OnClick="@(() => OpenAssignClubDialog(context))"/>
|
<MudTooltip Text="Rollen verwalten">
|
||||||
</MudTooltip>
|
<MudIconButton Icon="@Icons.Material.Filled.Security"
|
||||||
<MudTooltip Text="Rollen verwalten">
|
Size="Size.Small"
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Security"
|
OnClick="@(() => OpenManageRolesDialog(context))"/>
|
||||||
Size="Size.Small"
|
</MudTooltip>
|
||||||
OnClick="@(() => OpenManageRolesDialog(context))"/>
|
</MudTd>
|
||||||
</MudTooltip>
|
</RowTemplate>
|
||||||
</MudTd>
|
<PagerContent>
|
||||||
</RowTemplate>
|
<MudTablePager />
|
||||||
<PagerContent>
|
</PagerContent>
|
||||||
<MudTablePager />
|
</MudTable>
|
||||||
</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 {
|
@code {
|
||||||
private List<UserDto> _users = new();
|
private List<UserDto> _users = new();
|
||||||
private List<ClubDto> _clubs = new();
|
private List<ClubDto> _clubs = new();
|
||||||
|
private List<PendingMembershipDto> _pendingMemberships = new();
|
||||||
private bool _isLoading = true;
|
private bool _isLoading = true;
|
||||||
|
private bool _isLoadingPending = true;
|
||||||
private string _searchString = "";
|
private string _searchString = "";
|
||||||
|
private int _activeTabIndex = 0;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
|
@ -91,6 +141,7 @@ else
|
||||||
private async Task LoadData()
|
private async Task LoadData()
|
||||||
{
|
{
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
|
_isLoadingPending = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var usersTask = UserService.GetAllUsersAsync();
|
var usersTask = UserService.GetAllUsersAsync();
|
||||||
|
|
@ -99,10 +150,33 @@ else
|
||||||
|
|
||||||
_users = (await usersTask).ToList();
|
_users = (await usersTask).ToList();
|
||||||
_clubs = await clubsTask;
|
_clubs = await clubsTask;
|
||||||
|
_isLoading = false;
|
||||||
|
|
||||||
|
await LoadPendingMemberships();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_isLoading = false;
|
_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();
|
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