frontend-toegevoegd

This commit is contained in:
2026-10-27 15:34:56 +01:00
parent 777d9f199d
commit 02337fafaa
43 changed files with 10548 additions and 0 deletions

View File

@@ -0,0 +1,98 @@
import { Component, computed, inject, input, OnInit, output, signal } from '@angular/core';
import { firstValueFrom } from 'rxjs';
import { DanceGroup, Dancer } from '../core/api-models';
import { SchoolApi } from '../core/school-api';
import { apiError } from '../core/api-error';
import { DialogFrame } from './dialog-frame';
@Component({
selector: 'app-group-members',
imports: [DialogFrame],
template: `
<app-dialog-frame
[title]="'Dansers in ' + group().category"
[busy]="busy()"
(dismissed)="closed.emit()"
>
<p class="muted">
Koppel dansers aan deze groep. Een danser kan in meerdere groepen zitten. Wijzigingen worden
direct opgeslagen.
</p>
@if (error()) {
<p class="alert error" role="alert">{{ error() }}</p>
}
<p class="member-count" role="status">
{{
busy()
? 'Koppeling opslaan…'
: memberIds().length + ' van ' + dancers().length + ' dansers gekoppeld'
}}
</p>
<div class="member-list" [attr.aria-busy]="busy()">
@for (dancer of sortedDancers(); track dancer.id) {
<label class="member-row"
><span aria-hidden="true" class="avatar small">{{
dancer.name.charAt(0).toUpperCase()
}}</span>
<span>{{ dancer.name }}</span>
<input
type="checkbox"
[checked]="memberIds().includes(dancer.id)"
[disabled]="busy()"
(change)="toggle(dancer.id, $event)"
/>
</label>
} @empty {
<p class="empty-inline">
Er zijn nog geen dansers. Voeg eerst een danser toe via het tabblad Dansers.
</p>
}
</div>
<div class="dialog-actions">
<button class="button primary" type="button" [disabled]="busy()" (click)="closed.emit()">
Gereed
</button>
</div>
</app-dialog-frame>
`,
})
export class GroupMembers implements OnInit {
readonly schoolId = input.required<number>();
readonly group = input.required<DanceGroup>();
readonly dancers = input.required<Dancer[]>();
readonly closed = output<void>();
readonly changed = output<number[]>();
readonly memberIds = signal<number[]>([]);
readonly busy = signal(false);
readonly error = signal('');
readonly sortedDancers = computed(() =>
[...this.dancers()].sort((a, b) => a.name.localeCompare(b.name, 'nl')),
);
private readonly api = inject(SchoolApi);
ngOnInit(): void {
this.memberIds.set([...this.group().dancerIds]);
}
async toggle(dancerId: number, event: Event): Promise<void> {
const checkbox = event.target as HTMLInputElement;
const add = checkbox.checked;
if (this.busy()) {
checkbox.checked = this.memberIds().includes(dancerId);
return;
}
this.busy.set(true);
this.error.set('');
try {
await firstValueFrom(this.api.membership(this.schoolId(), this.group().id, dancerId, add));
this.memberIds.update((ids) =>
add ? [...ids, dancerId] : ids.filter((id) => id !== dancerId),
);
checkbox.checked = add;
this.changed.emit(this.memberIds());
} catch (error: unknown) {
checkbox.checked = this.memberIds().includes(dancerId);
this.error.set(apiError(error));
} finally {
this.busy.set(false);
}
}
}