163 lines
5.3 KiB
TypeScript
163 lines
5.3 KiB
TypeScript
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);
|
|
}
|
|
}
|
|
}
|