feat(cibg): WP-11 — render "Mijn aanvragen" as the CIBG Aanvragen component

- application-link switches to a `li[app-application-link]` attribute selector
  (native <li> child of the <ul> — axe-clean list) and drops the invented,
  dead `.application` / `.application-title` classes for the real vendored
  `.dashboard-block.applications li a` chain (h3.h3 / .subtitle / .status / .cta).
  Content stacks in a flex column; a non-navigating row mirrors the card surface
  from tokens. Re-enables a11y on the application-link/list stories.
- Dashboard "Mijn aanvragen" now renders through app-application-list +
  <li app-application-link> rows (was a keuzelijst), mapped by a new pure
  submittedRow() view helper (+ spec). Concepts stay the resumable melding.
- aanvraag-block is now concept-only (submitted mapping moved to aanvraag-view).

WP-11 grep gate clean. GREEN: lint, tokens, 181 tests, build, 136 axe stories.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-02 21:34:21 +02:00
parent 82fc3c493d
commit 98fd7e4bcd
8 changed files with 410 additions and 203 deletions

View File

@@ -1,28 +1,19 @@
import { Component, computed, input, output } from '@angular/core';
import { ButtonComponent } from '@shared/ui/button/button.component';
import { AlertComponent } from '@shared/ui/alert/alert.component';
import { ChoiceLinkComponent } from '@shared/ui/choice-link/choice-link.component';
import { Aanvraag, AanvraagType } from '@registratie/domain/aanvraag';
import { Aanvraag } from '@registratie/domain/aanvraag';
import { TYPE_LABELS } from '@registratie/domain/aanvraag-view';
import { blockActions } from '@registratie/domain/block-actions';
const TYPE_LABELS: Record<AanvraagType, string> = {
registratie: $localize`:@@aanvraagBlock.type.registratie:Inschrijving`,
herregistratie: $localize`:@@aanvraagBlock.type.herregistratie:Herregistratie`,
intake: $localize`:@@aanvraagBlock.type.intake:Herregistratie-intake`,
};
/** Organism: one application on the dashboard. A resumable Concept ("lopende
aanvraag") renders as a CIBG "melding" (warning) with its actions — verwijderen
as a link, aanvraag openen as a button; a submitted/resolved status renders as a
plain keuzelijst card. Which actions show is driven by the pure
`blockActions(status)`; the block only renders. NOTE: the caller places the two
shapes differently — a Concept melding is a block element, the rest are `<li>`s
that must sit inside a keuzelijst `<ul>` (see dashboard.page.ts). */
/** Organism: a resumable Concept ("lopende aanvraag") on the dashboard, rendered as
a CIBG "melding" (warning) with its actions — verwijderen as a link, aanvraag
openen as a button. Which actions show is driven by the pure `blockActions(status)`;
the block only renders. Submitted/resolved aanvragen are NOT rendered here — they
render as CIBG "aanvragen" rows (see application-link + aanvraag-view). */
@Component({
selector: 'app-aanvraag-block',
imports: [ButtonComponent, AlertComponent, ChoiceLinkComponent],
imports: [ButtonComponent, AlertComponent],
styles: [`
:host{display:contents} /* see choice-link.component.ts (keeps <li> a direct <ul> child) */
.actions{display:flex;align-items:center;gap:var(--rhc-space-max-md);margin-block-start:var(--rhc-space-max-sm)}
`],
template: `
@@ -39,8 +30,6 @@ const TYPE_LABELS: Record<AanvraagType, string> = {
}
</div>
</app-alert>
} @else {
<app-choice-link [heading]="typeLabel()" [instructions]="instructions()" />
}
`,
})
@@ -67,30 +56,6 @@ export class AanvraagBlockComponent {
if (s.tag !== 'Concept') return '';
return $localize`:@@aanvraagBlock.conceptMelding:Deze aanvraag is nog niet volledig afgerond — u bent gebleven bij stap ${s.stepIndex + 1}:stap: van ${s.stepCount}:totaal:. Rond de aanvraag af vóór ${formatNL(this.deadline())}:datum:.`;
});
// Status-tag → the choice's description line (the UI's mapping, not a domain rule).
private statusText = computed(() => {
const s = this.aanvraag().status;
switch (s.tag) {
case 'Concept': return '';
case 'InBehandeling': return $localize`:@@aanvraagBlock.inBehandeling:Referentie ${s.referentie}:ref: · ingediend op ${formatNL(this.aanvraag().submittedAt)}:datum:`;
case 'Goedgekeurd': return $localize`:@@aanvraagBlock.goedgekeurd:Referentie ${s.referentie}:ref:`;
case 'Afgewezen': return $localize`:@@aanvraagBlock.afgewezen:Referentie ${s.referentie}:ref:`;
}
});
// Secondary note appended to the status line, when there's one to show.
private subtitle = computed(() => {
const s = this.aanvraag().status;
if (s.tag === 'InBehandeling' && s.manual) return $localize`:@@aanvraagBlock.manual:Uw aanvraag wordt handmatig beoordeeld in de backoffice.`;
if (s.tag === 'Afgewezen') return s.reden;
return '';
});
// The keuzelijst has one description paragraph — combine status + secondary note.
protected instructions = computed(() => {
const status = this.statusText();
const sub = this.subtitle();
return sub ? `${status} ${sub}` : status;
});
}
function formatNL(iso?: string): string {

View File

@@ -9,7 +9,6 @@ import { DataBlockComponent } from '@shared/ui/data-block/data-block.component';
import { TaskListComponent } from '@shared/ui/task-list/task-list.component';
import { ApplicationListComponent } from '@shared/ui/application-list/application-list.component';
import { ApplicationLinkComponent } from '@shared/ui/application-link/application-link.component';
import { ChoiceListComponent } from '@shared/ui/choice-list/choice-list.component';
import { ASYNC } from '@shared/ui/async/async.component';
import { RegistrationSummaryComponent } from '@registratie/ui/registration-summary/registration-summary.component';
import { RegistrationTableComponent } from '@registratie/ui/registration-table/registration-table.component';
@@ -18,6 +17,7 @@ import { BigProfileStore } from '@registratie/application/big-profile.store';
import { ApplicationsStore } from '@registratie/application/applications.store';
import { Registration } from '@registratie/domain/registration';
import { Aanvraag, AanvraagType } from '@registratie/domain/aanvraag';
import { submittedRow } from '@registratie/domain/aanvraag-view';
import { tasksFromProfile } from '@registratie/domain/tasks';
/** Page:"Mijn overzicht" — the portal home, following the NL Design System
@@ -27,7 +27,7 @@ import { tasksFromProfile } from '@registratie/domain/tasks';
imports: [
PageShellComponent, HeadingComponent, AlertComponent, SkeletonComponent,
DataRowComponent, DataBlockComponent, TaskListComponent, ApplicationListComponent, ApplicationLinkComponent,
ChoiceListComponent, ...ASYNC,
...ASYNC,
RegistrationSummaryComponent, RegistrationTableComponent, AanvraagBlockComponent,
],
template: `
@@ -39,11 +39,13 @@ import { tasksFromProfile } from '@registratie/domain/tasks';
<app-aanvraag-block animate.enter="app-item-enter" animate.leave="app-item-leave" [aanvraag]="a" (resume)="resume(a)" (cancel)="cancelAanvraag(a)" />
}
@if (ingediend().length) {
<app-choice-list i18n-heading="@@dashboard.mijnAanvragen" heading="Mijn aanvragen" class="app-section">
<app-heading [level]="2" class="app-section" i18n="@@dashboard.mijnAanvragen">Mijn aanvragen</app-heading>
<app-application-list>
@for (a of ingediend(); track a.id) {
<app-aanvraag-block animate.enter="app-item-enter" animate.leave="app-item-leave" [aanvraag]="a" />
@let row = submittedRow(a);
<li app-application-link animate.enter="app-item-enter" animate.leave="app-item-leave" [heading]="row.heading" [status]="row.status" [subtitle]="row.subtitle"></li>
}
</app-choice-list>
</app-application-list>
}
</section>
}
@@ -103,7 +105,7 @@ import { tasksFromProfile } from '@registratie/domain/tasks';
<app-heading [level]="2" i18n="@@dashboard.watWiltUDoen">Wat wilt u doen?</app-heading>
<app-application-list class="app-section">
@for (a of acties; track a.to) {
<app-application-link [heading]="a.titel" [subtitle]="a.tekst" [cta]="a.actie" [to]="a.to" />
<li app-application-link [heading]="a.titel" [subtitle]="a.tekst" [cta]="a.actie" [to]="a.to"></li>
}
</app-application-list>
</section>
@@ -116,6 +118,9 @@ export class DashboardPage {
private apps = inject(ApplicationsStore);
private router = inject(Router);
/** Pure view mapping for a submitted aanvraag → CIBG aanvragen-row fields. */
protected submittedRow = submittedRow;
constructor() {
// Re-fetch on each visit so server-computed auto-approval transitions show up
// (Concept → In behandeling → Goedgekeurd after the processing window).