diff --git a/eeg_frontend/src/app/api/api/adminIAM.service.ts b/eeg_frontend/src/app/api/api/adminIAM.service.ts new file mode 100644 index 0000000..e260eb4 --- /dev/null +++ b/eeg_frontend/src/app/api/api/adminIAM.service.ts @@ -0,0 +1,255 @@ +/** + * OpenAPI definition + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +/* tslint:disable:no-unused-variable member-ordering */ + +import { Inject, Injectable, Optional } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpParams, + HttpResponse, HttpEvent, HttpContext + } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { OpenApiHttpParams, QueryParamStyle } from '../query.params'; + +// @ts-ignore +import { UserProfileResponse } from '../model/userProfileResponse'; + +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; +import { Configuration } from '../configuration'; +import { BaseService } from '../api.base.service'; + + + +@Injectable({ + providedIn: 'root' +}) +export class AdminIAMService extends BaseService { + + constructor(protected httpClient: HttpClient, @Optional() @Inject(BASE_PATH) basePath: string|string[], @Optional() configuration?: Configuration) { + super(basePath, configuration); + } + + /** + * User freigeben + * Setzt enabled=true, status=APPROVED und triggert die finale EDA-Anmeldung. + * @endpoint post /api/admin/users/{userId}/approve + * @param userId + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public approveUser(userId: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable; + public approveUser(userId: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable>; + public approveUser(userId: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable>; + public approveUser(userId: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable { + if (userId === null || userId === undefined) { + throw new Error('Required parameter userId was null or undefined when calling approveUser.'); + } + + let localVarHeaders = this.defaultHeaders; + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/api/admin/users/${this.configuration.encodeParam({name: "userId", value: userId, in: "path", style: "simple", explode: false, dataType: "string", dataFormat: "uuid"})}/approve`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request('post', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + + /** + * Lädt alle wartenden User + * Zeigt User im Status PENDING inkl. der Ergebnisse des EDA-Netz-Checks. + * @endpoint get /api/admin/users/pending + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public getPendingUsers(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public getPendingUsers(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>>; + public getPendingUsers(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>>; + public getPendingUsers(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + + let localVarHeaders = this.defaultHeaders; + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + 'application/json' + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/api/admin/users/pending`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request>('get', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + + /** + * Lädt alle User + * Zeigt alle registrierten Benutzer. + * @endpoint get /api/admin/users + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public getAllUsers(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public getAllUsers(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>>; + public getAllUsers(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>>; + public getAllUsers(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + + let localVarHeaders = this.defaultHeaders; + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + 'application/json' + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/api/admin/users`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request>('get', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + + /** + * User ablehnen + * Lehnt den Antrag ab (z.B. weil Zählpunkt nicht ins Netz passt). + * @endpoint post /api/admin/users/{userId}/reject + * @param userId + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public rejectUser(userId: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable; + public rejectUser(userId: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable>; + public rejectUser(userId: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable>; + public rejectUser(userId: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable { + if (userId === null || userId === undefined) { + throw new Error('Required parameter userId was null or undefined when calling rejectUser.'); + } + + let localVarHeaders = this.defaultHeaders; + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/api/admin/users/${this.configuration.encodeParam({name: "userId", value: userId, in: "path", style: "simple", explode: false, dataType: "string", dataFormat: "uuid"})}/reject`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request('post', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + +} diff --git a/eeg_frontend/src/app/pages/admin-memberships/admin-memberships.html b/eeg_frontend/src/app/pages/admin-memberships/admin-memberships.html index b40211f..04ed6c9 100644 --- a/eeg_frontend/src/app/pages/admin-memberships/admin-memberships.html +++ b/eeg_frontend/src/app/pages/admin-memberships/admin-memberships.html @@ -1,46 +1,132 @@ -
-

Mitgliedschaftsanträge

+
+

Mitgliedschaften verwalten

+ + +
+ +
@if (isLoading()) {
-
Lade Anträge...
-
- } @else if (pendingMemberships().length === 0) { -
-

Keine wartenden Mitgliedschaftsanträge vorhanden.

+
Lade Daten...
} @else { -
- @for (membership of pendingMemberships(); track membership.id) { -
-
-
-
- {{ membership.firstName }} {{ membership.lastName }} - ({{ membership.userEmail }}) -
-
- Community: {{ membership.communityName }} | - Zählpunkt: {{ membership.atNumber }} | - Priorität: {{ membership.priorityLevel }} + + @if (activeTab() === 'pending') { + @if (pendingMemberships().length === 0) { +
+

Keine wartenden Mitgliedschaftsanträge vorhanden.

+
+ } @else { +
+ @for (membership of pendingMemberships(); track membership.id) { +
+
+
+
+ {{ membership.firstName }} {{ membership.lastName }} + ({{ membership.userEmail }}) +
+
+ Community: {{ membership.communityName }} | + Zählpunkt: {{ membership.atNumber }} | + Priorität: {{ membership.priorityLevel }} +
+
+
+ + +
-
- - -
-
+ }
} -
+ } + + + @if (activeTab() === 'active' || activeTab() === 'all') { + @let displayMemberships = activeTab() === 'all' ? allMemberships() : allMemberships().filter(m => m.status === 'ACTIVE'); + @if (displayMemberships.length === 0) { +
+

+ @if (activeTab() === 'active') { + Keine aktiven Mitgliedschaften vorhanden. + } @else { + Keine Mitgliedschaften vorhanden. + } +

+
+ } @else { +
+ + + + + + + + + + + + + @for (membership of displayMemberships; track membership.id) { + + + + + + + + + } + +
NameEmailCommunityZählpunktPrioritätStatus
{{ membership.firstName }} {{ membership.lastName }}{{ membership.userEmail }}{{ membership.communityName }}{{ membership.atNumber }}{{ membership.priorityLevel }} + + {{ getStatusLabel(membership.status) }} + +
+
+ } + } }
diff --git a/eeg_frontend/src/app/pages/admin-memberships/admin-memberships.spec.ts b/eeg_frontend/src/app/pages/admin-memberships/admin-memberships.spec.ts new file mode 100644 index 0000000..bd68647 --- /dev/null +++ b/eeg_frontend/src/app/pages/admin-memberships/admin-memberships.spec.ts @@ -0,0 +1,247 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; +import { AdminMembershipsComponent } from './admin-memberships'; +import { PendingMembershipResponse } from '../../services/membership'; + +describe('AdminMembershipsComponent', () => { + let component: AdminMembershipsComponent; + let fixture: ComponentFixture; + let httpMock: HttpTestingController; + + const mockPendingMembership: PendingMembershipResponse = { + id: '1', + userEmail: 'max@test.at', + firstName: 'Max', + lastName: 'Mustermann', + atNumber: 'AT0000000000000000001', + communityName: 'Test Community', + priorityLevel: 1, + status: 'PENDING', + }; + + const mockActiveMembership: PendingMembershipResponse = { + id: '2', + userEmail: 'anna@test.at', + firstName: 'Anna', + lastName: 'Schmidt', + atNumber: 'AT0000000000000000002', + communityName: 'Active Community', + priorityLevel: 2, + status: 'ACTIVE', + }; + + const mockInactiveMembership: PendingMembershipResponse = { + id: '3', + userEmail: 'tom@test.at', + firstName: 'Tom', + lastName: 'Berger', + atNumber: 'AT0000000000000000003', + communityName: 'Inactive Community', + priorityLevel: 3, + status: 'INACTIVE', + }; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AdminMembershipsComponent], + providers: [provideHttpClient(), provideHttpClientTesting()], + }).compileComponents(); + + fixture = TestBed.createComponent(AdminMembershipsComponent); + component = fixture.componentInstance; + httpMock = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpMock.verify(); + }); + + it('should create', () => { + fixture.detectChanges(); + const req = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending')); + req.flush([]); + expect(component).toBeTruthy(); + }); + + it('should default activeTab to pending', () => { + fixture.detectChanges(); + const req = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending')); + req.flush([]); + expect(component.activeTab()).toBe('pending'); + }); + + it('should initialize allMemberships as empty array', () => { + fixture.detectChanges(); + const req = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending')); + req.flush([]); + expect(component.allMemberships()).toEqual([]); + }); + + it('should load pending memberships on init', () => { + fixture.detectChanges(); + const req = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending')); + expect(req.request.method).toBe('GET'); + req.flush([mockPendingMembership]); + expect(component.pendingMemberships().length).toBe(1); + expect(component.pendingMemberships()[0].firstName).toBe('Max'); + }); + + it('should load all memberships when switching to active tab', () => { + fixture.detectChanges(); + const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending')); + pendingReq.flush([]); + + component.switchTab('active'); + + const allReq = httpMock.expectOne( + (r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'), + ); + expect(allReq.request.method).toBe('GET'); + allReq.flush([mockPendingMembership, mockActiveMembership, mockInactiveMembership]); + + expect(component.allMemberships().length).toBe(3); + }); + + it('should load all memberships when switching to all tab', () => { + fixture.detectChanges(); + const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending')); + pendingReq.flush([]); + + component.switchTab('all'); + + const allReq = httpMock.expectOne( + (r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'), + ); + expect(allReq.request.method).toBe('GET'); + allReq.flush([mockPendingMembership, mockActiveMembership]); + + expect(component.allMemberships().length).toBe(2); + }); + + it('should switch activeTab signal', () => { + fixture.detectChanges(); + const req = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending')); + req.flush([]); + + component.switchTab('active'); + const activeReq = httpMock.expectOne( + (r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'), + ); + activeReq.flush([]); + expect(component.activeTab()).toBe('active'); + + component.switchTab('pending'); + expect(component.activeTab()).toBe('pending'); + + component.switchTab('all'); + const allReq = httpMock.expectOne( + (r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'), + ); + allReq.flush([]); + expect(component.activeTab()).toBe('all'); + }); + + describe('getStatusLabel', () => { + it('should return "Ausstehend" for PENDING', () => { + expect(component.getStatusLabel('PENDING')).toBe('Ausstehend'); + }); + + it('should return "Aktiv" for ACTIVE', () => { + expect(component.getStatusLabel('ACTIVE')).toBe('Aktiv'); + }); + + it('should return "Inaktiv" for INACTIVE', () => { + expect(component.getStatusLabel('INACTIVE')).toBe('Inaktiv'); + }); + + it('should return the original status for unknown values', () => { + expect(component.getStatusLabel('UNKNOWN')).toBe('UNKNOWN'); + }); + }); + + describe('getStatusBadgeClass', () => { + it('should return yellow badge for PENDING', () => { + expect(component.getStatusBadgeClass('PENDING')).toContain('yellow'); + }); + + it('should return green badge for ACTIVE', () => { + expect(component.getStatusBadgeClass('ACTIVE')).toContain('green'); + }); + + it('should return red badge for INACTIVE', () => { + expect(component.getStatusBadgeClass('INACTIVE')).toContain('red'); + }); + }); + + describe('template', () => { + it('should render three tabs', () => { + fixture.detectChanges(); + const req = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending')); + req.flush([]); + fixture.detectChanges(); + + const tabs = fixture.nativeElement.querySelectorAll('[role="tab"]'); + expect(tabs.length).toBe(3); + expect(tabs[0].textContent).toContain('Ausstehende Anträge'); + expect(tabs[1].textContent).toContain('Aktive Mitgliedschaften'); + expect(tabs[2].textContent).toContain('Alle'); + }); + + it('should show pending tab as active by default', () => { + fixture.detectChanges(); + const req = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending')); + req.flush([]); + fixture.detectChanges(); + + const activeTab = fixture.nativeElement.querySelector('[role="tab"][aria-selected="true"]'); + expect(activeTab?.textContent).toContain('Ausstehende Anträge'); + }); + + it('should show card view for pending tab', () => { + fixture.detectChanges(); + const req = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending')); + req.flush([mockPendingMembership]); + fixture.detectChanges(); + + const cards = fixture.nativeElement.querySelectorAll('.bg-white.border'); + expect(cards.length).toBe(1); + }); + + it('should show table view for active tab', () => { + fixture.detectChanges(); + const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending')); + pendingReq.flush([]); + + component.switchTab('active'); + const allReq = httpMock.expectOne( + (r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'), + ); + allReq.flush([mockActiveMembership]); + fixture.detectChanges(); + + const table = fixture.nativeElement.querySelector('table'); + expect(table).toBeTruthy(); + const rows = fixture.nativeElement.querySelectorAll('tbody tr'); + expect(rows.length).toBe(1); + }); + + it('should show table view for all tab', () => { + fixture.detectChanges(); + const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending')); + pendingReq.flush([]); + + component.switchTab('all'); + const allReq = httpMock.expectOne( + (r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'), + ); + allReq.flush([mockPendingMembership, mockActiveMembership]); + fixture.detectChanges(); + + const table = fixture.nativeElement.querySelector('table'); + expect(table).toBeTruthy(); + const rows = fixture.nativeElement.querySelectorAll('tbody tr'); + expect(rows.length).toBe(2); + }); + }); +}); diff --git a/eeg_frontend/src/app/pages/admin-memberships/admin-memberships.ts b/eeg_frontend/src/app/pages/admin-memberships/admin-memberships.ts index 20866e1..418e18e 100644 --- a/eeg_frontend/src/app/pages/admin-memberships/admin-memberships.ts +++ b/eeg_frontend/src/app/pages/admin-memberships/admin-memberships.ts @@ -1,17 +1,21 @@ -import {Component, inject, OnInit, signal} from '@angular/core'; -import {MembershipService, PendingMembershipResponse} from '../../services/membership'; -import {ToastService} from '../../services/toast'; +import { Component, DestroyRef, inject, OnInit, signal } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { MembershipService, PendingMembershipResponse } from '../../services/membership'; +import { ToastService } from '../../services/toast'; @Component({ selector: 'app-admin-memberships', standalone: true, - templateUrl: './admin-memberships.html' + templateUrl: './admin-memberships.html', }) export class AdminMembershipsComponent implements OnInit { private membershipService = inject(MembershipService); private toastService = inject(ToastService); + private destroyRef = inject(DestroyRef); + activeTab = signal<'pending' | 'active' | 'all'>('pending'); pendingMemberships = signal([]); + allMemberships = signal([]); isLoading = signal(true); isProcessing = signal(null); @@ -19,8 +23,16 @@ export class AdminMembershipsComponent implements OnInit { this.loadPendingMemberships(); } + switchTab(tab: 'pending' | 'active' | 'all') { + this.activeTab.set(tab); + if ((tab === 'active' || tab === 'all') && this.allMemberships().length === 0) { + this.loadAllMemberships(); + } + } + loadPendingMemberships() { - this.membershipService.getPendingMemberships().subscribe({ + this.isLoading.set(true); + this.membershipService.getPendingMemberships().pipe(takeUntilDestroyed(this.destroyRef)).subscribe({ next: memberships => { this.pendingMemberships.set(memberships); this.isLoading.set(false); @@ -32,6 +44,46 @@ export class AdminMembershipsComponent implements OnInit { }); } + loadAllMemberships() { + this.isLoading.set(true); + this.membershipService.getAllMemberships().pipe(takeUntilDestroyed(this.destroyRef)).subscribe({ + next: memberships => { + this.allMemberships.set(memberships); + this.isLoading.set(false); + }, + error: () => { + this.isLoading.set(false); + this.toastService.error('Fehler beim Laden aller Mitgliedschaften.'); + } + }); + } + + getStatusLabel(status: string): string { + switch (status) { + case 'PENDING': + return 'Ausstehend'; + case 'ACTIVE': + return 'Aktiv'; + case 'INACTIVE': + return 'Inaktiv'; + default: + return status; + } + } + + getStatusBadgeClass(status: string): string { + switch (status) { + case 'PENDING': + return 'bg-yellow-100 text-yellow-800'; + case 'ACTIVE': + return 'bg-green-100 text-green-800'; + case 'INACTIVE': + return 'bg-red-100 text-red-800'; + default: + return 'bg-gray-100 text-gray-800'; + } + } + approveMembership(id: string) { if (this.isProcessing()) return; this.isProcessing.set(id); diff --git a/eeg_frontend/src/app/pages/user-tariff/user-tariff.ts b/eeg_frontend/src/app/pages/user-tariff/user-tariff.ts index 3a722ef..ba001ab 100644 --- a/eeg_frontend/src/app/pages/user-tariff/user-tariff.ts +++ b/eeg_frontend/src/app/pages/user-tariff/user-tariff.ts @@ -5,6 +5,7 @@ import {UserTariffControllerService, TariffControllerService, UserTariffRequest, import {MeteringPointService, MeteringPoint} from '../../services/metering-point'; import {MembershipService, MembershipResponse} from '../../services/membership'; import {ToastService} from '../../services/toast'; +import {AuthService} from '../../services/auth'; @Component({ selector: 'app-user-tariff', @@ -19,6 +20,7 @@ export class UserTariffComponent implements OnInit, OnDestroy { private meteringPointService = inject(MeteringPointService); private membershipService = inject(MembershipService); private toastService = inject(ToastService); + private authService = inject(AuthService); private fb = inject(FormBuilder); memberships = signal([]); @@ -187,7 +189,9 @@ export class UserTariffComponent implements OnInit, OnDestroy { onDelete(tariff: UserTariffResponse) { if (!confirm('Möchtest du diesen Tarif wirklich löschen?')) return; - this.userTariffService.deleteUserTariff(tariff.id!).subscribe({ + const userId = this.authService.getCurrentUserId(); + if (!userId) return; + this.userTariffService.deleteUserTariff(tariff.id!, userId).subscribe({ next: () => { this.toastService.success('Tarif erfolgreich gelöscht.'); this.loadUserTariffs(this.selectedCommunityId());