Merge branch 'mimocode/curious-nebula'
This commit is contained in:
commit
dbd39643aa
@ -4,6 +4,36 @@
|
|||||||
<p class="text-gray-600 mt-2">Prüfung der ausstehenden Anmeldungen und EDA-Netz-Checks.</p>
|
<p class="text-gray-600 mt-2">Prüfung der ausstehenden Anmeldungen und EDA-Netz-Checks.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Tab Navigation -->
|
||||||
|
<div class="border-b border-gray-200 mb-6">
|
||||||
|
<nav class="flex gap-8" aria-label="Tabs">
|
||||||
|
<button
|
||||||
|
role="tab"
|
||||||
|
[attr.aria-selected]="activeTab() === 'pending'"
|
||||||
|
[class]="
|
||||||
|
activeTab() === 'pending'
|
||||||
|
? 'border-b-2 border-indigo-500 text-indigo-600 py-3 px-1 text-sm font-medium'
|
||||||
|
: 'border-b-2 border-transparent text-gray-500 hover:text-gray-700 py-3 px-1 text-sm font-medium'
|
||||||
|
"
|
||||||
|
(click)="switchTab('pending')"
|
||||||
|
>
|
||||||
|
Ausstehende Anträge
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
role="tab"
|
||||||
|
[attr.aria-selected]="activeTab() === 'all'"
|
||||||
|
[class]="
|
||||||
|
activeTab() === 'all'
|
||||||
|
? 'border-b-2 border-indigo-500 text-indigo-600 py-3 px-1 text-sm font-medium'
|
||||||
|
: 'border-b-2 border-transparent text-gray-500 hover:text-gray-700 py-3 px-1 text-sm font-medium'
|
||||||
|
"
|
||||||
|
(click)="switchTab('all')"
|
||||||
|
>
|
||||||
|
Alle Benutzer
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
@if (errorMessage()) {
|
@if (errorMessage()) {
|
||||||
<div class="bg-red-50 border-l-4 border-red-500 text-red-700 p-4 mb-6" role="alert">
|
<div class="bg-red-50 border-l-4 border-red-500 text-red-700 p-4 mb-6" role="alert">
|
||||||
{{ errorMessage() }}
|
{{ errorMessage() }}
|
||||||
@ -12,55 +42,67 @@
|
|||||||
|
|
||||||
@if (isLoading()) {
|
@if (isLoading()) {
|
||||||
<div class="flex items-center justify-center py-12">
|
<div class="flex items-center justify-center py-12">
|
||||||
<div class="text-gray-500 text-lg">Lade ausstehende Anträge...</div>
|
<div class="text-gray-500 text-lg">Lade Daten...</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (!isLoading() && pendingUsers().length === 0) {
|
@if (!isLoading() && activeTab() === 'pending') {
|
||||||
|
@if (pendingUsers().length === 0) {
|
||||||
<div class="bg-gray-50 border border-gray-200 rounded-lg p-8 text-center">
|
<div class="bg-gray-50 border border-gray-200 rounded-lg p-8 text-center">
|
||||||
<p class="text-gray-600 text-lg">Aktuell gibt es keine neuen User im Status PENDING.</p>
|
<p class="text-gray-600 text-lg">Aktuell gibt es keine neuen User im Status PENDING.</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
} @else {
|
||||||
|
|
||||||
@if (!isLoading() && pendingUsers().length > 0) {
|
|
||||||
<div class="bg-white shadow-sm border border-gray-200 rounded-lg overflow-hidden">
|
<div class="bg-white shadow-sm border border-gray-200 rounded-lg overflow-hidden">
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="w-full text-left border-collapse">
|
<table class="w-full text-left border-collapse">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-gray-50 border-b border-gray-200">
|
<tr class="bg-gray-50 border-b border-gray-200">
|
||||||
<th class="p-4 text-sm font-semibold text-gray-700 uppercase tracking-wider">Benutzer</th>
|
<th class="p-4 text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
||||||
<th class="p-4 text-sm font-semibold text-gray-700 uppercase tracking-wider">Status</th>
|
Benutzer
|
||||||
<th class="p-4 text-sm font-semibold text-gray-700 uppercase tracking-wider">Status</th>
|
</th>
|
||||||
<th class="p-4 text-sm font-semibold text-gray-700 uppercase tracking-wider text-right">Aktionen</th>
|
<th class="p-4 text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
||||||
|
Status
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="p-4 text-sm font-semibold text-gray-700 uppercase tracking-wider text-right"
|
||||||
|
>
|
||||||
|
Aktionen
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200">
|
<tbody class="divide-y divide-gray-200">
|
||||||
@for (user of pendingUsers(); track user.id) {
|
@for (user of pendingUsers(); track user.id) {
|
||||||
<tr class="hover:bg-gray-50 transition-colors">
|
<tr class="hover:bg-gray-50 transition-colors">
|
||||||
|
|
||||||
<td class="p-4">
|
<td class="p-4">
|
||||||
<div class="font-medium text-gray-900">{{ user.firstName }} {{ user.lastName }}</div>
|
<div class="font-medium text-gray-900">
|
||||||
|
{{ user.firstName }} {{ user.lastName }}
|
||||||
|
</div>
|
||||||
<div class="text-sm text-gray-500">{{ user.email }}</div>
|
<div class="text-sm text-gray-500">{{ user.email }}</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="p-4">
|
<td class="p-4">
|
||||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
|
<span
|
||||||
{{ user.status }}
|
[class]="
|
||||||
|
'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ' +
|
||||||
|
getStatusBadgeClass(user.status || 'PENDING')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ getStatusLabel(user.status || 'PENDING') }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="p-4 flex justify-end gap-3">
|
<td class="p-4 flex justify-end gap-3">
|
||||||
<button (click)="approveUser(user.id)"
|
<button
|
||||||
class="inline-flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition">
|
(click)="approveUser(user.id)"
|
||||||
|
class="inline-flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition"
|
||||||
|
>
|
||||||
Freigeben
|
Freigeben
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
<button (click)="rejectUser(user)"
|
(click)="rejectUser(user)"
|
||||||
class="inline-flex items-center justify-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition">
|
class="inline-flex items-center justify-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition"
|
||||||
|
>
|
||||||
Ablehnen
|
Ablehnen
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -68,4 +110,58 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!isLoading() && activeTab() === 'all') {
|
||||||
|
@if (allUsers().length === 0) {
|
||||||
|
<div class="bg-gray-50 border border-gray-200 rounded-lg p-8 text-center">
|
||||||
|
<p class="text-gray-600 text-lg">Keine Benutzer vorhanden.</p>
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
|
<div class="bg-white shadow-sm border border-gray-200 rounded-lg overflow-hidden">
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="w-full text-left border-collapse">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-gray-50 border-b border-gray-200">
|
||||||
|
<th class="p-4 text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
||||||
|
Benutzer
|
||||||
|
</th>
|
||||||
|
<th class="p-4 text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
||||||
|
Status
|
||||||
|
</th>
|
||||||
|
<th class="p-4 text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
||||||
|
Rolle
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-gray-200">
|
||||||
|
@for (user of allUsers(); track user.id) {
|
||||||
|
<tr class="hover:bg-gray-50 transition-colors">
|
||||||
|
<td class="p-4">
|
||||||
|
<div class="font-medium text-gray-900">
|
||||||
|
{{ user.firstName }} {{ user.lastName }}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-500">{{ user.email }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="p-4">
|
||||||
|
<span
|
||||||
|
[class]="
|
||||||
|
'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ' +
|
||||||
|
getStatusBadgeClass(user.status || 'UNKNOWN')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ getStatusLabel(user.status || 'UNKNOWN') }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="p-4 text-sm text-gray-700">
|
||||||
|
{{ user.participantType === 'COMPANY' ? 'Unternehmen' : 'Privatperson' }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,22 +1,230 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { provideHttpClient } from '@angular/common/http';
|
||||||
|
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
|
||||||
|
import { vi } from 'vitest';
|
||||||
import { AdminUserApprovalComponent } from './admin-user-approval';
|
import { AdminUserApprovalComponent } from './admin-user-approval';
|
||||||
|
import { UserProfileResponse } from '../../api';
|
||||||
|
|
||||||
describe('AdminUserApproval', () => {
|
describe('AdminUserApprovalComponent', () => {
|
||||||
let component: AdminUserApprovalComponent;
|
let component: AdminUserApprovalComponent;
|
||||||
let fixture: ComponentFixture<AdminUserApprovalComponent>;
|
let fixture: ComponentFixture<AdminUserApprovalComponent>;
|
||||||
|
let httpMock: HttpTestingController;
|
||||||
|
|
||||||
|
const mockPendingUser: UserProfileResponse = {
|
||||||
|
id: '1',
|
||||||
|
firstName: 'Max',
|
||||||
|
lastName: 'Mustermann',
|
||||||
|
email: 'max@test.at',
|
||||||
|
status: 'PENDING',
|
||||||
|
participantType: UserProfileResponse.ParticipantTypeEnum.Private,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockApprovedUser: UserProfileResponse = {
|
||||||
|
id: '2',
|
||||||
|
firstName: 'Anna',
|
||||||
|
lastName: 'Schmidt',
|
||||||
|
email: 'anna@test.at',
|
||||||
|
status: 'APPROVED',
|
||||||
|
participantType: UserProfileResponse.ParticipantTypeEnum.Company,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockRejectedUser: UserProfileResponse = {
|
||||||
|
id: '3',
|
||||||
|
firstName: 'Tom',
|
||||||
|
lastName: 'Berger',
|
||||||
|
email: 'tom@test.at',
|
||||||
|
status: 'REJECTED',
|
||||||
|
participantType: UserProfileResponse.ParticipantTypeEnum.Private,
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [AdminUserApprovalComponent],
|
imports: [AdminUserApprovalComponent],
|
||||||
|
providers: [provideHttpClient(), provideHttpClientTesting()],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
|
||||||
fixture = TestBed.createComponent(AdminUserApprovalComponent);
|
fixture = TestBed.createComponent(AdminUserApprovalComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
await fixture.whenStable();
|
httpMock = TestBed.inject(HttpTestingController);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
httpMock.verify();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending'));
|
||||||
|
req.flush([]);
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should default activeTab to pending', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending'));
|
||||||
|
req.flush([]);
|
||||||
|
expect(component.activeTab()).toBe('pending');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should initialize allUsers as empty array', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending'));
|
||||||
|
req.flush([]);
|
||||||
|
expect(component.allUsers()).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load pending users on init', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending'));
|
||||||
|
expect(req.request.method).toBe('GET');
|
||||||
|
req.flush([mockPendingUser]);
|
||||||
|
expect(component.pendingUsers().length).toBe(1);
|
||||||
|
expect(component.pendingUsers()[0].firstName).toBe('Max');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load all users when switching to all tab', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/users/pending'));
|
||||||
|
pendingReq.flush([]);
|
||||||
|
|
||||||
|
component.switchTab('all');
|
||||||
|
|
||||||
|
const allReq = httpMock.expectOne(
|
||||||
|
(r) => r.url.includes('/admin/users') && !r.url.includes('/pending'),
|
||||||
|
);
|
||||||
|
expect(allReq.request.method).toBe('GET');
|
||||||
|
allReq.flush([mockPendingUser, mockApprovedUser, mockRejectedUser]);
|
||||||
|
|
||||||
|
expect(component.allUsers().length).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should switch activeTab signal', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending'));
|
||||||
|
req.flush([]);
|
||||||
|
|
||||||
|
component.switchTab('all');
|
||||||
|
const allReq = httpMock.expectOne(
|
||||||
|
(r) => r.url.includes('/admin/users') && !r.url.includes('/pending'),
|
||||||
|
);
|
||||||
|
allReq.flush([]);
|
||||||
|
expect(component.activeTab()).toBe('all');
|
||||||
|
|
||||||
|
component.switchTab('pending');
|
||||||
|
expect(component.activeTab()).toBe('pending');
|
||||||
|
});
|
||||||
|
|
||||||
|
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 "Freigeschaltet" for APPROVED', () => {
|
||||||
|
expect(component.getStatusLabel('APPROVED')).toBe('Freigeschaltet');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return "Inaktiv" for INACTIVE', () => {
|
||||||
|
expect(component.getStatusLabel('INACTIVE')).toBe('Inaktiv');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return "Abgelehnt" for REJECTED', () => {
|
||||||
|
expect(component.getStatusLabel('REJECTED')).toBe('Abgelehnt');
|
||||||
|
});
|
||||||
|
|
||||||
|
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 APPROVED', () => {
|
||||||
|
expect(component.getStatusBadgeClass('APPROVED')).toContain('green');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return green badge for ACTIVE', () => {
|
||||||
|
expect(component.getStatusBadgeClass('ACTIVE')).toContain('green');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return red badge for REJECTED', () => {
|
||||||
|
expect(component.getStatusBadgeClass('REJECTED')).toContain('red');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return red badge for INACTIVE', () => {
|
||||||
|
expect(component.getStatusBadgeClass('INACTIVE')).toContain('red');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should approve user and remove from pending list', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/users/pending'));
|
||||||
|
pendingReq.flush([mockPendingUser]);
|
||||||
|
|
||||||
|
vi.spyOn(window, 'confirm').mockReturnValue(true);
|
||||||
|
component.approveUser(mockPendingUser.id);
|
||||||
|
|
||||||
|
const approveReq = httpMock.expectOne((r) => r.url.includes('/approve'));
|
||||||
|
approveReq.flush({});
|
||||||
|
expect(component.pendingUsers().length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject user and remove from pending list', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/users/pending'));
|
||||||
|
pendingReq.flush([mockPendingUser]);
|
||||||
|
|
||||||
|
vi.spyOn(window, 'confirm').mockReturnValue(true);
|
||||||
|
component.rejectUser(mockPendingUser);
|
||||||
|
|
||||||
|
const rejectReq = httpMock.expectOne((r) => r.url.includes('/reject'));
|
||||||
|
rejectReq.flush({});
|
||||||
|
expect(component.pendingUsers().length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('template', () => {
|
||||||
|
it('should render tab navigation', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending'));
|
||||||
|
req.flush([]);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const tabs = fixture.nativeElement.querySelectorAll('[role="tab"]');
|
||||||
|
expect(tabs.length).toBe(2);
|
||||||
|
expect(tabs[0].textContent).toContain('Ausstehende Anträge');
|
||||||
|
expect(tabs[1].textContent).toContain('Alle Benutzer');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show pending tab as active by default', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const req = httpMock.expectOne((r) => r.url.includes('/admin/users/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 only one Status column header (no duplicate)', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending'));
|
||||||
|
req.flush([mockPendingUser]);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const allHeaders = fixture.nativeElement.querySelectorAll('th');
|
||||||
|
let statusCount = 0;
|
||||||
|
for (let i = 0; i < allHeaders.length; i++) {
|
||||||
|
if (allHeaders[i].textContent?.trim() === 'Status') {
|
||||||
|
statusCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(statusCount).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -8,11 +8,12 @@ import {ToastService} from '../../services/toast';
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [],
|
imports: [],
|
||||||
templateUrl: './admin-user-approval.html',
|
templateUrl: './admin-user-approval.html',
|
||||||
styleUrls: ['./admin-user-approval.scss']
|
styleUrls: ['./admin-user-approval.scss'],
|
||||||
})
|
})
|
||||||
export class AdminUserApprovalComponent implements OnInit {
|
export class AdminUserApprovalComponent implements OnInit {
|
||||||
// Signals für reaktives State-Management (perfekt für den Zoneless-Modus)
|
public readonly activeTab = signal<'pending' | 'all'>('pending');
|
||||||
public readonly pendingUsers = signal<UserProfileResponse[]>([]);
|
public readonly pendingUsers = signal<UserProfileResponse[]>([]);
|
||||||
|
public readonly allUsers = signal<UserProfileResponse[]>([]);
|
||||||
public readonly isLoading = signal<boolean>(true);
|
public readonly isLoading = signal<boolean>(true);
|
||||||
public readonly errorMessage = signal<string>('');
|
public readonly errorMessage = signal<string>('');
|
||||||
|
|
||||||
@ -24,11 +25,53 @@ export class AdminUserApprovalComponent implements OnInit {
|
|||||||
this.loadPendingUsers();
|
this.loadPendingUsers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public switchTab(tab: 'pending' | 'all'): void {
|
||||||
|
this.activeTab.set(tab);
|
||||||
|
if (tab === 'all' && this.allUsers().length === 0) {
|
||||||
|
this.loadAllUsers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getStatusLabel(status: string): string {
|
||||||
|
switch (status) {
|
||||||
|
case 'PENDING':
|
||||||
|
return 'Ausstehend';
|
||||||
|
case 'ACTIVE':
|
||||||
|
return 'Aktiv';
|
||||||
|
case 'APPROVED':
|
||||||
|
return 'Freigeschaltet';
|
||||||
|
case 'INACTIVE':
|
||||||
|
return 'Inaktiv';
|
||||||
|
case 'REJECTED':
|
||||||
|
return 'Abgelehnt';
|
||||||
|
default:
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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 'APPROVED':
|
||||||
|
return 'bg-green-100 text-green-800';
|
||||||
|
case 'REJECTED':
|
||||||
|
return 'bg-red-100 text-red-800';
|
||||||
|
case 'INACTIVE':
|
||||||
|
return 'bg-red-100 text-red-800';
|
||||||
|
default:
|
||||||
|
return 'bg-gray-100 text-gray-800';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private loadPendingUsers(): void {
|
private loadPendingUsers(): void {
|
||||||
this.isLoading.set(true);
|
this.isLoading.set(true);
|
||||||
this.errorMessage.set('');
|
this.errorMessage.set('');
|
||||||
|
|
||||||
this.adminService.getPendingUsers()
|
this.adminService
|
||||||
|
.getPendingUsers()
|
||||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (users: UserProfileResponse[]) => {
|
next: (users: UserProfileResponse[]) => {
|
||||||
@ -38,7 +81,26 @@ export class AdminUserApprovalComponent implements OnInit {
|
|||||||
error: () => {
|
error: () => {
|
||||||
this.errorMessage.set('Fehler beim Laden der Daten aus dem EDA-Netz-Check.');
|
this.errorMessage.set('Fehler beim Laden der Daten aus dem EDA-Netz-Check.');
|
||||||
this.isLoading.set(false);
|
this.isLoading.set(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private loadAllUsers(): void {
|
||||||
|
this.isLoading.set(true);
|
||||||
|
this.errorMessage.set('');
|
||||||
|
|
||||||
|
this.adminService
|
||||||
|
.getAllUsers()
|
||||||
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||||
|
.subscribe({
|
||||||
|
next: (users: UserProfileResponse[]) => {
|
||||||
|
this.allUsers.set(users);
|
||||||
|
this.isLoading.set(false);
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.errorMessage.set('Fehler beim Laden aller Benutzer.');
|
||||||
|
this.isLoading.set(false);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,29 +108,34 @@ export class AdminUserApprovalComponent implements OnInit {
|
|||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
this.errorMessage.set('');
|
this.errorMessage.set('');
|
||||||
|
|
||||||
this.adminService.approveUser(userId)
|
this.adminService
|
||||||
|
.approveUser(userId)
|
||||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.pendingUsers.update(users => users.filter(user => user.id !== userId));
|
this.pendingUsers.update((users) => users.filter((user) => user.id !== userId));
|
||||||
},
|
},
|
||||||
error: () => {
|
error: () => {
|
||||||
this.errorMessage.set('Der User konnte nicht freigegeben werden.');
|
this.errorMessage.set('Der User konnte nicht freigegeben werden.');
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public rejectUser(user: UserProfileResponse): void {
|
public rejectUser(user: UserProfileResponse): void {
|
||||||
if (!user.id) return;
|
if (!user.id) return;
|
||||||
|
|
||||||
if (confirm(`Möchtest du die Registrierung von ${user.firstName} ${user.lastName} wirklich ablehnen?`)) {
|
if (
|
||||||
|
confirm(
|
||||||
|
`Möchtest du die Registrierung von ${user.firstName} ${user.lastName} wirklich ablehnen?`,
|
||||||
|
)
|
||||||
|
) {
|
||||||
this.adminService.rejectUser(user.id).subscribe({
|
this.adminService.rejectUser(user.id).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.pendingUsers.update(users => users.filter(u => u.id !== user.id));
|
this.pendingUsers.update((users) => users.filter((u) => u.id !== user.id));
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
this.toastService.error(err.error?.message || 'Der User konnte nicht abgelehnt werden.');
|
this.toastService.error(err.error?.message || 'Der User konnte nicht abgelehnt werden.');
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user