feat: build admin user management UI

This commit is contained in:
Bastian Wagner
2026-08-01 10:01:25 +02:00
parent d0ec000bff
commit 3ae0fd2000
6 changed files with 1256 additions and 9 deletions

View File

@@ -0,0 +1,123 @@
<main class="users-page">
<a mat-button routerLink="/" class="back-link"><mat-icon>arrow_back</mat-icon>Zurück</a>
<header class="page-header">
<p class="eyebrow">Organisation</p>
<h1>Benutzer</h1>
<p>Konten und sichtbare Teamzuordnungen im Überblick.</p>
</header>
<form class="directory-search" (submit)="submitSearch(); $event.preventDefault()" role="search">
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-label>Benutzer suchen</mat-label>
<mat-icon matPrefix>search</mat-icon>
<input
matInput
type="search"
name="directorySearch"
[value]="searchDraft()"
(input)="searchDraft.set($any($event.target).value)"
/>
</mat-form-field>
<button mat-stroked-button type="submit" [disabled]="loading()">Suchen</button>
</form>
@if (mutationError()) {
<div class="message message--error" role="alert">{{ mutationError() }}</div>
}
@if (loading()) {
<div class="page-state" aria-live="polite">
<mat-spinner diameter="36" />
<span>Benutzer werden geladen …</span>
</div>
} @else if (loadError()) {
<div class="page-state page-state--error" role="alert">
<mat-icon>error_outline</mat-icon>
<strong>{{ loadError() }}</strong>
<button mat-stroked-button type="button" (click)="loadDirectory()">Erneut versuchen</button>
</div>
} @else if (directory()?.data?.length === 0) {
<div class="page-state">
<mat-icon>group_off</mat-icon>
<strong>Keine Benutzer gefunden</strong>
<span>Versuche einen anderen Suchbegriff.</span>
</div>
} @else if (directory()) {
<div class="directory" aria-live="polite">
@for (user of directory()!.data; track user.id) {
<article class="user-row" [attr.data-user-id]="user.id" [class.user-row--inactive]="user.status?.id === 2">
<div class="user-row__summary">
<div class="avatar" aria-hidden="true">{{ initials(user) }}</div>
<div class="user-row__identity">
<div class="user-row__name">
<h2>{{ fullName(user) }}</h2>
<span class="status" [class.status--inactive]="user.status?.id === 2">
{{ statusName(user.status?.id) }}
</span>
</div>
@if (adminDetails(user); as details) {
<span>{{ details.email ?? 'Keine E-Mail' }} · {{ roleName(details.role?.id) }}</span>
}
@if (user.assignments.length === 0) {
<span>Keine Spielerzuordnung sichtbar</span>
} @else {
<div class="assignment-summary">
@for (assignment of user.assignments; track assignment.id) {
<span>
{{ assignment.team.name }} · {{ assignment.firstName }} {{ assignment.lastName }} ·
{{ teamRoleName(assignment.teamRole?.name) }}
@if (!assignment.active) { · Inaktiv }
</span>
}
</div>
}
</div>
</div>
@if (adminDetails(user); as details) {
<div class="user-row__actions">
<button mat-button type="button" (click)="toggleEdit(user.id)">Bearbeiten</button>
<button mat-button type="button" (click)="toggleAssignments(user.id)">Zuordnungen verwalten</button>
<button
mat-stroked-button
type="button"
[disabled]="isSelf(user) || pendingUserId() !== null"
[attr.title]="isSelf(user) ? 'Das eigene Konto kann nicht deaktiviert werden.' : null"
(click)="changeStatus(details)"
>
{{ user.status?.id === 2 ? 'Aktivieren' : 'Deaktivieren' }}
</button>
</div>
@if (editingUserId() === user.id) {
<app-user-edit
[user]="details"
[self]="isSelf(user)"
[saving]="pendingUserId() === user.id"
(saved)="saveEdit(details, $event)"
(cancel)="editingUserId.set(null)"
/>
}
@if (assignmentUserId() === user.id) {
<app-player-assignments
[user]="details"
(directoryChanged)="assignmentsChanged()"
(closed)="assignmentUserId.set(null)"
/>
}
}
</article>
}
</div>
<nav class="pagination" aria-label="Benutzerseiten">
<button mat-button type="button" [disabled]="directory()!.page <= 1 || loading()" (click)="previousPage()">
<mat-icon>chevron_left</mat-icon>Zurück
</button>
<span>Seite {{ directory()!.page }} · {{ directory()!.total }} Benutzer</span>
<button mat-button type="button" [disabled]="!directory()!.hasNextPage || loading()" (click)="nextPage()">
Weiter<mat-icon>chevron_right</mat-icon>
</button>
</nav>
}
</main>