import { Component, computed, inject, signal } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { ApiClientService, type ApiErrorBody, type Permission, type RoleDto, } from '@boilerplate/api-client'; import { adminPermissionGroups } from './admin-permissions'; @Component({ standalone: true, imports: [FormsModule, RouterLink], template: ` Zurueck zur Rollenliste @if (error(); as currentError) {
{{ messageFor(currentError) }} @if (currentError.requestId) { Request-ID: {{ currentError.requestId }} }
} @if (!loading()) {

{{ isNew() ? 'Rolle anlegen' : 'Rolle bearbeiten' }}

@if (role()?.system) { Systemrolle }

Permissions

@for (group of groups; track group.title) {
{{ group.title }} @for (permission of group.permissions; track permission.id) { }
}
@if (role(); as currentRole) { @if (!currentRole.system) { } }
} @else {
Rolle wird geladen.
} `, styles: [ ` a { display: inline-flex; margin-bottom: 16px; color: var(--color-primary-hover); } .notice, .editor { 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); margin-bottom: 16px; } .editor, section, .groups, fieldset, .actions { display: grid; gap: 14px; } h2, h3 { margin: 0; } label { display: grid; gap: 6px; } input, textarea, button { min-height: 44px; } input, textarea { border: 1px solid var(--color-border); border-radius: 6px; padding: 8px 10px; } textarea { min-height: 96px; resize: vertical; } button { border: 0; border-radius: 6px; background: var(--color-primary); color: var(--color-surface); padding: 0 14px; } button.danger { background: var(--color-danger); } button:disabled { opacity: 0.55; } fieldset { border: 1px solid var(--color-border); border-radius: 8px; padding: 12px; } fieldset label { grid-template-columns: 24px 1fr; align-items: start; } code { display: block; margin-bottom: 2px; } .system { width: fit-content; border: 1px solid var(--color-border); border-radius: 999px; padding: 4px 8px; color: var(--color-text-secondary); } @media (min-width: 900px) { .groups { grid-template-columns: 1fr 1fr; } .actions { grid-template-columns: auto auto; justify-content: start; } } `, ], }) export class AdminRoleDetailPageComponent { private readonly api = inject(ApiClientService); private readonly route = inject(ActivatedRoute); private readonly router = inject(Router); readonly role = signal(null); readonly loading = signal(false); readonly error = signal(null); readonly selectedPermissions = signal(new Set()); readonly groups = adminPermissionGroups; readonly isNew = computed(() => this.route.snapshot.paramMap.get('id') === 'new'); name = ''; description = ''; constructor() { if (!this.isNew()) this.load(); } load(): void { const id = this.route.snapshot.paramMap.get('id'); if (!id) return; this.loading.set(true); this.error.set(null); this.api.adminRole(id).subscribe({ next: (role) => { this.role.set(role); this.name = role.name; this.description = role.description; this.selectedPermissions.set(new Set(role.permissions.map((permission) => permission.id))); this.loading.set(false); }, error: (error: { error?: ApiErrorBody }) => { this.error.set(error.error ?? this.genericError()); this.loading.set(false); }, }); } toggle(permission: Permission): void { if (this.role()?.name === 'admin') return; const next = new Set(this.selectedPermissions()); if (next.has(permission)) next.delete(permission); else next.add(permission); this.selectedPermissions.set(next); } save(): void { const body = { name: this.name, description: this.description, permissions: Array.from(this.selectedPermissions()), }; const request = this.isNew() ? this.api.createAdminRole(body) : this.api.updateAdminRole(this.role()?.id ?? '', body); request.subscribe({ next: (role) => { void this.router.navigate(['/admin/roles', role.id]); }, error: (error: { error?: ApiErrorBody }) => this.error.set(error.error ?? this.genericError()), }); } delete(role: RoleDto): void { if (!confirm('Rolle wirklich loeschen?')) return; this.api.deleteAdminRole(role.id).subscribe({ next: () => { void this.router.navigate(['/admin/roles']); }, error: (error: { error?: ApiErrorBody }) => this.error.set(error.error ?? this.genericError()), }); } messageFor(error: ApiErrorBody): string { if (error.code === 'ROLE_STILL_ASSIGNED') return 'Diese Rolle ist noch Benutzern zugewiesen.'; if (error.code === 'SYSTEM_ROLE_PROTECTED') return 'Diese Systemrolle ist geschuetzt.'; if (error.code === 'LAST_ACTIVE_ADMIN_REQUIRED') { return 'Mindestens ein aktiver Administrator muss erhalten bleiben.'; } return error.message; } private genericError(): ApiErrorBody { return { status: 0, code: 'UNKNOWN', message: 'Die Rolle konnte nicht verarbeitet werden.', requestId: '', }; } }