feat(dashboard): add pending memberships card to admin overview

- Add pendingMemberships field to AdminStats DTO
- Add 4th card 'Offene Mitgliedsanträge' to admin dashboard
- Rename 'Offene Freigaben' to 'Ausstehende Freischaltungen'
- Update DashboardService to count pending memberships
- Update tests to match new AdminStats structure
This commit is contained in:
Bernhard Müller 2026-07-22 10:59:01 +02:00
parent 5949359216
commit 949e1e6c34
6 changed files with 16 additions and 5 deletions

View File

@ -4,6 +4,7 @@ public interface DashboardStatsDto {
record AdminStats(
long totalCommunities,
long pendingUsers,
long pendingMemberships,
long totalActiveMemberships
) {}

View File

@ -26,9 +26,10 @@ public class DashboardService {
public DashboardStatsDto.AdminStats getAdminStats() {
long communities = energyCommunityRepository.count();
long pendingUsers = userRepository.countByStatusAndEmailVerified(RegistrationStatus.PENDING, true);
long pendingMemberships = membershipRepository.countByStatus(MembershipStatus.PENDING);
long activeMemberships = membershipRepository.countByStatus(MembershipStatus.ACTIVE);
return new DashboardStatsDto.AdminStats(communities, pendingUsers, activeMemberships);
return new DashboardStatsDto.AdminStats(communities, pendingUsers, pendingMemberships, activeMemberships);
}
public DashboardStatsDto.UserStats getUserStats(UUID userId) {

View File

@ -25,7 +25,7 @@ class DashboardControllerTest {
@Test
void getAdminDashboard() {
DashboardStatsDto.AdminStats stats = new DashboardStatsDto.AdminStats(5, 3, 12);
DashboardStatsDto.AdminStats stats = new DashboardStatsDto.AdminStats(5, 3, 2, 12);
when(dashboardService.getAdminStats()).thenReturn(stats);
ResponseEntity<DashboardStatsDto.AdminStats> response = dashboardController.getAdminDashboard();
@ -33,18 +33,20 @@ class DashboardControllerTest {
assertEquals(200, response.getStatusCode().value());
assertEquals(5, response.getBody().totalCommunities());
assertEquals(3, response.getBody().pendingUsers());
assertEquals(2, response.getBody().pendingMemberships());
assertEquals(12, response.getBody().totalActiveMemberships());
}
@Test
void getAdminDashboard_empty() {
DashboardStatsDto.AdminStats stats = new DashboardStatsDto.AdminStats(0, 0, 0);
DashboardStatsDto.AdminStats stats = new DashboardStatsDto.AdminStats(0, 0, 0, 0);
when(dashboardService.getAdminStats()).thenReturn(stats);
ResponseEntity<DashboardStatsDto.AdminStats> response = dashboardController.getAdminDashboard();
assertEquals(0, response.getBody().totalCommunities());
assertEquals(0, response.getBody().pendingUsers());
assertEquals(0, response.getBody().pendingMemberships());
assertEquals(0, response.getBody().totalActiveMemberships());
}

View File

@ -40,12 +40,14 @@ class DashboardServiceTest {
void getAdminStats_returnsCorrectCounts() {
when(energyCommunityRepository.count()).thenReturn(5L);
when(userRepository.countByStatusAndEmailVerified(RegistrationStatus.PENDING, true)).thenReturn(3L);
when(membershipRepository.countByStatus(MembershipStatus.PENDING)).thenReturn(2L);
when(membershipRepository.countByStatus(MembershipStatus.ACTIVE)).thenReturn(10L);
DashboardStatsDto.AdminStats stats = dashboardService.getAdminStats();
assertEquals(5, stats.totalCommunities());
assertEquals(3, stats.pendingUsers());
assertEquals(2, stats.pendingMemberships());
assertEquals(10, stats.totalActiveMemberships());
}

View File

@ -8,15 +8,19 @@
<p class="text-red-600">{{ error() }}</p>
</div>
} @else if (role === 'ADMIN' && adminStats(); as stats) {
<div class="grid grid-cols-1 sm:grid-cols-3 gap-6">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-6">
<p class="text-sm text-slate-500">Energiegemeinschaften</p>
<p class="text-3xl font-bold text-slate-800 mt-2">{{ stats.totalCommunities }}</p>
</div>
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-6">
<p class="text-sm text-slate-500">Offene Freigaben</p>
<p class="text-sm text-slate-500">Ausstehende Freischaltungen</p>
<p class="text-3xl font-bold text-slate-800 mt-2">{{ stats.pendingUsers }}</p>
</div>
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-6">
<p class="text-sm text-slate-500">Offene Mitgliedsanträge</p>
<p class="text-3xl font-bold text-slate-800 mt-2">{{ stats.pendingMemberships }}</p>
</div>
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-6">
<p class="text-sm text-slate-500">Aktive Mitgliedschaften</p>
<p class="text-3xl font-bold text-slate-800 mt-2">{{ stats.totalActiveMemberships }}</p>

View File

@ -6,6 +6,7 @@ import {environment} from '../../environments/environment';
export interface AdminStats {
totalCommunities: number;
pendingUsers: number;
pendingMemberships: number;
totalActiveMemberships: number;
}