feat(behandelportal): WP-64 werkvoorraad (queue) screen
CI / changes (pull_request) Successful in 15s
CI / lint (pull_request) Successful in 57s
CI / frontend (pull_request) Successful in 2m36s
CI / storybook-a11y (pull_request) Failing after 3m14s
CI / backend (pull_request) Successful in 2m1s
CI / semgrep (pull_request) Successful in 1m10s
CI / e2e (pull_request) Successful in 3m3s
CI / api-client-drift (pull_request) Successful in 2m1s
CI / changes (pull_request) Successful in 15s
CI / lint (pull_request) Successful in 57s
CI / frontend (pull_request) Successful in 2m36s
CI / storybook-a11y (pull_request) Failing after 3m14s
CI / backend (pull_request) Successful in 2m1s
CI / semgrep (pull_request) Successful in 1m10s
CI / e2e (pull_request) Successful in 3m3s
CI / api-client-drift (pull_request) Successful in 2m1s
New GET /werkvoorraad endpoint lists aanvragen still open (Ingediend/InBehandeling), gated by the medewerker capability (CanBeoordelen) rather than the admin role — reuses the existing ApplicationSummaryDto, no new DTO. GET /me now surfaces aanvraag:beoordelen for a behandelaar so the FE can gate with the same AccessStore/capabilityGuard idiom every other page uses. FE: a behandeling domain type deliberately narrower than ssp's full AanvraagStatus union (only the two open tags — illegal states unrepresentable), composed into a werkvoorraad-list organism from existing shared/ui molecules. Replaces WP-61's scaffold placeholder as the app's real landing page. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
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 { SkeletonComponent } from '@shared/ui/skeleton/skeleton.component';
|
||||
import { ASYNC } from '@shared/ui/async/async.component';
|
||||
import { AccessStore } from '@shared/application/access.store';
|
||||
import { WerkvoorraadStore } from '@behandeling/application/werkvoorraad.store';
|
||||
import { WerkvoorraadListComponent } from '@behandeling/ui/werkvoorraad-list/werkvoorraad-list.component';
|
||||
|
||||
/**
|
||||
* Page: the behandelaar's werkvoorraad (WP-64) — the behandelportal's landing page.
|
||||
* Deny-by-default capability gate (`aanvraag:beoordelen`), same idiom as ssp's
|
||||
* AdminCasesPage: a denial alert for a non-behandelaar, the queue for one. Opening
|
||||
* a case's detail is out of scope here (WP-65).
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-werkvoorraad-page',
|
||||
imports: [
|
||||
PageShellComponent,
|
||||
AlertComponent,
|
||||
ButtonComponent,
|
||||
SkeletonComponent,
|
||||
WerkvoorraadListComponent,
|
||||
...ASYNC,
|
||||
],
|
||||
template: `
|
||||
<app-page-shell [heading]="heading" [intro]="intro">
|
||||
@if (!access.ready()) {
|
||||
<!-- wait for /me before deciding — avoids flashing the denial to a behandelaar -->
|
||||
} @else if (!canBeoordelen()) {
|
||||
<app-alert type="error">{{ deniedText }}</app-alert>
|
||||
} @else {
|
||||
<app-async [data]="store.items()" (retryClicked)="store.reload()">
|
||||
<ng-template appAsyncLoading>
|
||||
<app-skeleton height="2.5rem" [count]="4" />
|
||||
</ng-template>
|
||||
<ng-template appAsyncError>
|
||||
<app-alert type="error">{{ failedText }}</app-alert>
|
||||
<app-button variant="secondary" (click)="store.reload()">{{ retryText }}</app-button>
|
||||
</ng-template>
|
||||
<ng-template appAsyncLoaded>
|
||||
@if (items().length === 0) {
|
||||
<app-alert type="info">{{ emptyText }}</app-alert>
|
||||
} @else {
|
||||
<app-werkvoorraad-list [items]="items()" />
|
||||
}
|
||||
</ng-template>
|
||||
</app-async>
|
||||
}
|
||||
</app-page-shell>
|
||||
`,
|
||||
})
|
||||
export class WerkvoorraadPage {
|
||||
protected store = inject(WerkvoorraadStore);
|
||||
protected access = inject(AccessStore);
|
||||
|
||||
protected canBeoordelen = computed(() => this.access.can('aanvraag:beoordelen'));
|
||||
protected items = computed(() => {
|
||||
const rd = this.store.items();
|
||||
return rd.tag === 'Success' ? rd.value : [];
|
||||
});
|
||||
|
||||
protected heading = $localize`:@@werkvoorraad.heading:Werkvoorraad`;
|
||||
protected intro = $localize`:@@werkvoorraad.intro:Aanvragen die op beoordeling wachten.`;
|
||||
protected deniedText = $localize`:@@werkvoorraad.denied:U hebt geen rechten om de werkvoorraad te bekijken.`;
|
||||
protected failedText = $localize`:@@werkvoorraad.failed:De werkvoorraad kon niet worden geladen.`;
|
||||
protected emptyText = $localize`:@@werkvoorraad.empty:Er staan geen aanvragen open.`;
|
||||
protected retryText = $localize`:@@werkvoorraad.retry:Opnieuw proberen`;
|
||||
|
||||
private loadRequested = false;
|
||||
constructor() {
|
||||
// Load once the capability resolves to allowed (a 403 GET would be wasted otherwise) —
|
||||
// same guard-against-the-loop idiom as AdminCasesPage (WP-26 lesson).
|
||||
effect(() => {
|
||||
if (this.canBeoordelen() && !this.loadRequested) {
|
||||
this.loadRequested = true;
|
||||
void this.store.load();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user