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,162 @@
import { Component, computed, DestroyRef, inject, OnInit, signal } from '@angular/core';
import { DatePipe } from '@angular/common';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ActivatedRoute, Router, RouterLink, RouterLinkActive } from '@angular/router';
import { catchError, EMPTY, firstValueFrom, switchMap } from 'rxjs';
import {
DanceGroup,
EditorTarget,
GROUP_STYLES,
GROUP_TYPES,
ResourceKind,
SchoolData,
} from '../core/api-models';
import { apiError } from '../core/api-error';
import { SchoolApi } from '../core/school-api';
import { EntityEditor } from '../shared/entity-editor';
import { ConfirmDialog } from '../shared/confirm-dialog';
import { GroupMembers } from '../shared/group-members';
interface DeleteTarget {
kind: ResourceKind;
id: number;
name: string;
explanation: string;
}
@Component({
selector: 'app-school-page',
imports: [DatePipe, RouterLink, RouterLinkActive, EntityEditor, ConfirmDialog, GroupMembers],
templateUrl: './school-page.html',
})
export class SchoolPage implements OnInit {
private readonly api = inject(SchoolApi);
private readonly route = inject(ActivatedRoute);
private readonly router = inject(Router);
private readonly destroyRef = inject(DestroyRef);
readonly data = signal<SchoolData | null>(null);
readonly section = signal('overview');
readonly loading = signal(true);
readonly error = signal('');
readonly notice = signal('');
readonly editor = signal<EditorTarget | null>(null);
readonly members = signal<DanceGroup | null>(null);
readonly deleting = signal<DeleteTarget | null>(null);
readonly deleteBusy = signal(false);
readonly deleteError = signal('');
readonly search = signal('');
readonly dancers = computed(() =>
[...(this.data()?.dancers ?? [])]
.filter((d) => d.name.toLocaleLowerCase('nl').includes(this.search().toLocaleLowerCase('nl')))
.sort((a, b) => a.name.localeCompare(b.name, 'nl')),
);
readonly shows = computed(() =>
[...(this.data()?.shows ?? [])].sort((a, b) => a.date.localeCompare(b.date)),
);
readonly today = new Date().toLocaleDateString('en-CA');
readonly upcoming = computed(() => this.shows().find((show) => show.date >= this.today));
readonly tabs = [
{ value: 'overview', label: 'Overzicht' },
{ value: 'dancers', label: 'Dansers' },
{ value: 'groups', label: 'Dansgroepen' },
{ value: 'shows', label: 'Shows' },
];
ngOnInit(): void {
this.route.paramMap
.pipe(
switchMap((params) => {
this.loading.set(true);
this.error.set('');
this.search.set('');
this.editor.set(null);
this.members.set(null);
this.deleting.set(null);
this.section.set(params.get('section') ?? 'overview');
return this.api.schoolData(Number(params.get('schoolId'))).pipe(
catchError((error) => {
this.data.set(null);
this.error.set(apiError(error));
this.loading.set(false);
return EMPTY;
}),
);
}),
takeUntilDestroyed(this.destroyRef),
)
.subscribe((data) => {
this.data.set(data);
this.loading.set(false);
});
}
async reload(): Promise<void> {
const id = this.data()?.school.id;
if (id === undefined) return;
this.error.set('');
try {
const data = await firstValueFrom(this.api.schoolData(id));
if (this.data()?.school.id === id) this.data.set(data);
} catch (error: unknown) {
this.error.set(apiError(error));
}
}
retry(): void {
window.location.reload();
}
async saved(): Promise<void> {
this.editor.set(null);
this.notice.set('Wijzigingen opgeslagen.');
await this.reload();
}
groupType(value: string): string {
return GROUP_TYPES.find((option) => option.value === value)?.label ?? value;
}
groupStyle(value: string): string {
return GROUP_STYLES.find((option) => option.value === value)?.label ?? value;
}
groupNames(dancerId: number): string {
return (
this.data()
?.groups.filter((group) => group.dancerIds.includes(dancerId))
.map((group) => group.category)
.join(', ') || 'Nog geen groep'
);
}
askDelete(target: DeleteTarget): void {
this.deleteError.set('');
this.deleting.set(target);
}
membershipChanged(ids: number[]): void {
const groupId = this.members()?.id;
this.data.update((data) =>
data
? {
...data,
groups: data.groups.map((group) =>
group.id === groupId ? { ...group, dancerIds: [...ids] } : group,
),
}
: data,
);
}
async confirmDelete(): Promise<void> {
const target = this.deleting();
const schoolId = this.data()?.school.id;
if (!target || schoolId === undefined || this.deleteBusy()) return;
this.deleteBusy.set(true);
this.deleteError.set('');
try {
await firstValueFrom(this.api.delete(target.kind, schoolId, target.id));
this.deleting.set(null);
if (target.kind === 'school') await this.router.navigate(['/schools']);
else {
this.notice.set(`${target.name} is verwijderd.`);
await this.reload();
}
} catch (error: unknown) {
this.deleteError.set(apiError(error));
} finally {
this.deleteBusy.set(false);
}
}
}