All checks were successful
Build, test and publish / verify (push) Successful in 7m31s
159 lines
4.7 KiB
TypeScript
159 lines
4.7 KiB
TypeScript
import { Component, inject, input, output, signal } from '@angular/core';
|
|
import { HttpClient } from '@angular/common/http';
|
|
import { firstValueFrom } from 'rxjs';
|
|
import { apiError } from '../core/api-error';
|
|
|
|
interface Preview {
|
|
name: string;
|
|
dancers: number;
|
|
groups: number;
|
|
shows: number;
|
|
existingId: number | null;
|
|
existingName: string | null;
|
|
existingRevision: number | null;
|
|
}
|
|
@Component({
|
|
selector: 'app-school-transfer',
|
|
template: `
|
|
<div class="quick-actions">
|
|
@if (schoolId() !== undefined) {
|
|
<button type="button" class="button secondary" (click)="download()" [disabled]="busy()">
|
|
JSON exporteren
|
|
</button>
|
|
}
|
|
<label
|
|
>JSON importeren
|
|
<input
|
|
type="file"
|
|
accept=".json,application/json"
|
|
[disabled]="busy()"
|
|
(change)="select($event)"
|
|
/></label>
|
|
</div>
|
|
@if (error()) {
|
|
<p class="alert error" role="alert">{{ error() }}</p>
|
|
}
|
|
@if (notice()) {
|
|
<p role="status">{{ notice() }}</p>
|
|
}
|
|
@if (preview(); as p) {
|
|
<section class="panel" aria-label="Importvoorbeeld">
|
|
<h2>{{ p.name }}</h2>
|
|
<p>{{ p.dancers }} dansers · {{ p.groups }} dansgroepen · {{ p.shows }} shows</p>
|
|
@if (p.existingId !== null) {
|
|
<p>
|
|
De bestaande school {{ p.existingName }} wordt volledig vervangen. Alle huidige gegevens
|
|
van deze school verdwijnen.
|
|
</p>
|
|
<button type="button" class="button primary" [disabled]="busy()" (click)="confirm()">
|
|
Volledig vervangen bevestigen
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="button secondary"
|
|
[disabled]="busy()"
|
|
(click)="notice.set('Samenvoegen wordt nog niet ondersteund.')"
|
|
>
|
|
Samenvoegen
|
|
</button>
|
|
} @else {
|
|
<button type="button" class="button primary" [disabled]="busy()" (click)="confirm()">
|
|
School toevoegen bevestigen
|
|
</button>
|
|
}
|
|
<button type="button" class="button secondary" [disabled]="busy()" (click)="cancel()">
|
|
Annuleren
|
|
</button>
|
|
</section>
|
|
}
|
|
`,
|
|
})
|
|
export class SchoolTransfer {
|
|
readonly schoolId = input<number>();
|
|
readonly imported = output<void>();
|
|
private readonly http = inject(HttpClient);
|
|
readonly preview = signal<Preview | null>(null);
|
|
readonly busy = signal(false);
|
|
readonly error = signal('');
|
|
readonly notice = signal('');
|
|
private json = '';
|
|
private readonly base = '/api/dance-schools';
|
|
|
|
cancel(): void {
|
|
this.preview.set(null);
|
|
this.json = '';
|
|
this.notice.set('');
|
|
}
|
|
async select(event: Event): Promise<void> {
|
|
const input = event.target as HTMLInputElement;
|
|
const file = input.files?.[0];
|
|
input.value = '';
|
|
this.cancel();
|
|
this.error.set('');
|
|
if (!file) return;
|
|
this.busy.set(true);
|
|
try {
|
|
this.json = await file.text();
|
|
this.preview.set(
|
|
await firstValueFrom(
|
|
this.http.post<Preview>(`${this.base}/import/preview`, this.json, {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}),
|
|
),
|
|
);
|
|
} catch (e) {
|
|
this.error.set(
|
|
`Import niet gelukt: ${e instanceof Error && !('status' in e) ? e.message : apiError(e)}`,
|
|
);
|
|
} finally {
|
|
this.busy.set(false);
|
|
}
|
|
}
|
|
async confirm(): Promise<void> {
|
|
const p = this.preview();
|
|
if (!p || this.busy()) return;
|
|
this.busy.set(true);
|
|
this.error.set('');
|
|
try {
|
|
const query = p.existingId === null ? '' : `?replaceId=${p.existingId}`;
|
|
await firstValueFrom(
|
|
this.http.post(`${this.base}/import${query}`, this.json, {
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...(p.existingRevision === null
|
|
? {}
|
|
: { 'X-School-Revision': String(p.existingRevision) }),
|
|
},
|
|
}),
|
|
);
|
|
this.cancel();
|
|
this.notice.set('School geïmporteerd.');
|
|
this.imported.emit();
|
|
} catch (e) {
|
|
this.error.set(`Import niet gelukt: ${apiError(e)}`);
|
|
this.preview.set(null);
|
|
} finally {
|
|
this.busy.set(false);
|
|
}
|
|
}
|
|
async download(): Promise<void> {
|
|
this.error.set('');
|
|
this.busy.set(true);
|
|
try {
|
|
const blob = await firstValueFrom(
|
|
this.http.get(`${this.base}/${this.schoolId()}/export`, { responseType: 'blob' }),
|
|
);
|
|
const url = URL.createObjectURL(blob);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = `school-${this.schoolId()}.json`;
|
|
link.click();
|
|
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
} catch (e) {
|
|
this.error.set(`Export niet gelukt: ${apiError(e)}`);
|
|
} finally {
|
|
this.busy.set(false);
|
|
}
|
|
}
|
|
}
|