import { Component, inject, signal } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { RouterLink } from '@angular/router'; import { ApiClientService, type AdminUserListItemDto, type ApiErrorBody, type RoleDto, } from '@boilerplate/api-client'; type ActiveFilter = 'all' | 'active' | 'inactive'; type UserSort = 'name' | 'email' | 'lastLoginAt' | 'createdAt'; @Component({ standalone: true, imports: [FormsModule, RouterLink], template: `
@if (error(); as currentError) {
{{ currentError.message }} @if (currentError.requestId) { Request-ID: {{ currentError.requestId }} }
} @if (loading()) {
Benutzer werden geladen.
} @else if (users().length === 0) {
Keine Benutzer gefunden.
} @else {
@for (user of users(); track user.id) {
{{ user.name }} {{ user.email || 'Keine E-Mail' }} {{ user.active ? 'Aktiv' : 'Deaktiviert' }}
@for (role of user.roles; track role.id) { {{ role.name }} }
Sessions
{{ user.activeSessionCount }}
Letzter Login
{{ user.lastLoginAt || 'nie' }}
Details
}
} `, styles: [ ` .toolbar { display: grid; gap: 12px; margin-bottom: 16px; } label, .summary, dl { display: grid; gap: 6px; } input, select, button, .button { min-height: 44px; } input, select { border: 1px solid var(--color-border); border-radius: 6px; padding: 0 10px; } button, .button { border: 0; border-radius: 6px; background: var(--color-primary); color: var(--color-surface); padding: 0 14px; } .button { display: inline-flex; align-items: center; justify-content: center; text-decoration: none; } .notice, article { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: 8px; padding: 16px; } .notice.error { border-color: var(--color-danger); color: var(--color-danger); } .list { display: grid; gap: 12px; } article { display: grid; gap: 12px; } article.inactive { border-left: 4px solid var(--color-border-strong); } .chips { display: flex; flex-wrap: wrap; gap: 6px; } .chips span { border: 1px solid var(--color-border); border-radius: 999px; padding: 4px 8px; background: var(--color-background); } dl { margin: 0; } dt { color: var(--color-text-muted); font-size: 0.88rem; } dd { margin: 0; } .pagination { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 16px; } @media (min-width: 900px) { .toolbar { grid-template-columns: 2fr 1fr 1fr 1fr auto; align-items: end; } article { grid-template-columns: 1.4fr 1fr 1.2fr auto; align-items: center; } } `, ], }) export class AdminUsersPageComponent { private readonly api = inject(ApiClientService); readonly users = signal([]); readonly roles = signal([]); readonly loading = signal(false); readonly error = signal(null); search = ''; active: ActiveFilter = 'all'; roleId = ''; sort: UserSort = 'name'; page = 1; pageSize = 25; total = 0; constructor() { this.api.adminRoles().subscribe((roles) => this.roles.set(roles)); this.load(); } load(): void { this.loading.set(true); this.error.set(null); this.api .adminUsers({ search: this.search, active: this.active, roleId: this.roleId, sort: this.sort, page: this.page, pageSize: this.pageSize, }) .subscribe({ next: (page) => { this.users.set(page.items); this.total = page.total; this.page = page.page; this.pageSize = page.pageSize; this.loading.set(false); }, error: (error: { error?: ApiErrorBody }) => { this.error.set(error.error ?? this.genericError()); this.loading.set(false); }, }); } previous(): void { this.page -= 1; this.load(); } next(): void { this.page += 1; this.load(); } totalPages(): number { return Math.max(1, Math.ceil(this.total / this.pageSize)); } private genericError(): ApiErrorBody { return { status: 0, code: 'UNKNOWN', message: 'Benutzer konnten nicht geladen werden.', requestId: '', }; } }