All checks were successful
Build, test and publish / verify (push) Successful in 7m31s
105 lines
3.5 KiB
TypeScript
105 lines
3.5 KiB
TypeScript
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 revision = input<number>();
|
|
private originalRevision?: number;
|
|
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.originalRevision = this.revision();
|
|
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.originalRevision),
|
|
);
|
|
if (this.originalRevision !== undefined) this.originalRevision++;
|
|
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);
|
|
}
|
|
}
|
|
}
|