import { Component, computed, effect, inject } from '@angular/core'; import { PageShellComponent } from '@shared/layout/page-shell/page-shell.component'; import { AlertComponent } from '@shared/ui/alert/alert.component'; import { ButtonComponent } from '@shared/ui/button/button.component'; import { DataBlockComponent } from '@shared/ui/data-block/data-block.component'; import { DataRowComponent } from '@shared/ui/data-row/data-row.component'; import { ASYNC } from '@shared/ui/async/async.component'; import { AccessStore } from '@shared/application/access.store'; import { formatDatumNl } from '@shared/kernel/datum'; import { Aanvraag } from '@registratie/domain/aanvraag'; import { TYPE_LABELS, statusLabel, referentie } from '@registratie/domain/aanvraag-view'; import { AdminCasesStore } from '@registratie/application/admin-cases.store'; /** * Admin page: every case across all owners, with an admin delete (WP-36). Lives in * `registratie` (which owns the Aanvraag aggregate) — the back-office counterpart of the * user's dashboard, reusing the same view labels + trust-boundary parse. Deny-by-default * capability gate (`cases:manage`): a denial alert for non-admins, the list for admins. * Delete is guarded by a native confirm — it is irreversible and may remove submitted cases. */ @Component({ selector: 'app-admin-cases-page', imports: [ PageShellComponent, AlertComponent, ButtonComponent, DataBlockComponent, DataRowComponent, ...ASYNC, ], styles: [ ` .case { margin-block-end: var(--rhc-space-max-lg); } `, ], template: ` @if (!access.ready()) { } @else if (!canManage()) { {{ deniedText }} } @else { {{ failedText }} {{ retryText }} @if (cases().length === 0) { {{ emptyText }} } @else { @for (c of cases(); track c.id) {
@for (row of rows(c); track row.key) {
}
{{ deleteText }}
} }
}
`, }) export class AdminCasesPage { protected store = inject(AdminCasesStore); protected access = inject(AccessStore); protected canManage = computed(() => this.access.can('cases:manage')); protected cases = computed(() => { const rd = this.store.cases(); return rd.tag === 'Success' ? rd.value : []; }); protected heading = $localize`:@@adminCases.heading:Aanvragen beheren`; protected intro = $localize`:@@adminCases.intro:Alle aanvragen in het register. Een aanvraag verwijderen kan niet ongedaan worden gemaakt.`; protected deniedText = $localize`:@@adminCases.denied:U hebt geen rechten om aanvragen te beheren.`; protected failedText = $localize`:@@adminCases.failed:De aanvragen konden niet worden geladen.`; protected emptyText = $localize`:@@adminCases.empty:Er zijn geen aanvragen.`; protected retryText = $localize`:@@adminCases.retry:Opnieuw proberen`; protected deleteText = $localize`:@@adminCases.delete:Verwijderen`; private ownerKey = $localize`:@@adminCases.owner:Eigenaar (BSN)`; private statusKey = $localize`:@@adminCases.status:Status`; private refKey = $localize`:@@adminCases.referentie:Referentie`; private ingediendKey = $localize`:@@adminCases.ingediend:Ingediend op`; protected typeLabel = (c: Aanvraag) => TYPE_LABELS[c.type]; /** Key/value rows for one case (owner + lifecycle facts; the type is the block heading). */ protected rows(c: Aanvraag): { key: string; value: string }[] { return [ { key: this.ownerKey, value: c.owner ?? '—' }, { key: this.statusKey, value: statusLabel(c.status) }, { key: this.refKey, value: referentie(c.status) || '—' }, { key: this.ingediendKey, value: c.submittedAt ? formatDatumNl(c.submittedAt) : '—' }, ]; } private loadRequested = false; constructor() { // Load once the capability resolves to allowed (a 403 GET would be wasted otherwise). // Depends only on canManage() + a plain flag — never the store model (WP-26 loop lesson). effect(() => { if (this.canManage() && !this.loadRequested) { this.loadRequested = true; void this.store.load(); } }); } protected reload() { void this.store.load(); } /** Native confirm — no dialog component exists, and admin delete is irreversible. */ protected confirmDelete(c: Aanvraag) { const msg = $localize`:@@adminCases.confirm:Deze aanvraag definitief verwijderen?`; if (confirm(msg)) void this.store.delete(c.id); } }