Rijkshuisstijl restyle + wizard fixes

Chrome: two-tier Rijksoverheid header (white brand bar + lint-blue
breadcrumb bar, route-driven), dark multi-column footer, white page
surface. Session shown via a shared SESSION_PORT token (keeps shared/
free of the auth context).

Overview ("Mijn overzicht") rebuilt to the NL Design System #392 pattern:
side-nav + "Wat moet ik regelen" task list (derived) + "Mijn registratie"
cards. New shared components: card, task-list, side-nav; pure
tasksFromProfile (+spec).

Wizards: grey form panel, connected numbered stepper, form-field
"(verplicht)" markers + styled description/error, full-width inputs.
Propagated to login, detail, change-request, address-fields.

Bug fixes:
- wizard-shell: add FormsModule so NgForm intercepts submit (wizards now
  advance; no native GET leaking choices into the URL).
- wizard-shell: error-summary links focus the field instead of navigating
  (a fragment href resolved against <base href="/"> reloaded to "/" and
  bounced to login).
- wizard-shell: error-summary focus only on the rising edge, so typing
  while errors are shown no longer scrolls the page up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
eho
2026-06-27 13:21:54 +02:00
co-authored by Claude Opus 4.8
parent d08f3877f7
commit 7a582ae2fa
30 changed files with 677 additions and 149 deletions
+90 -50
View File
@@ -5,82 +5,122 @@ import { LinkComponent } from '@shared/ui/link/link.component';
import { AlertComponent } from '@shared/ui/alert/alert.component';
import { SkeletonComponent } from '@shared/ui/skeleton/skeleton.component';
import { DataRowComponent } from '@shared/ui/data-row/data-row.component';
import { CardComponent } from '@shared/ui/card/card.component';
import { TaskListComponent } from '@shared/ui/task-list/task-list.component';
import { SideNavComponent, NavItem } from '@shared/layout/side-nav/side-nav.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';
import { BigProfileStore } from '@registratie/application/big-profile.store';
import { Registration } from '@registratie/domain/registration';
import { tasksFromProfile } from '@registratie/domain/tasks';
/** Page: "Mijn overzicht" — the portal home, following the NL Design System
"Mijn omgeving" pattern (side nav + "Wat moet ik regelen" + "Mijn zaken"). */
@Component({
selector: 'app-dashboard-page',
imports: [
PageShellComponent, HeadingComponent, LinkComponent, AlertComponent, SkeletonComponent,
DataRowComponent, ...ASYNC, RegistrationSummaryComponent, RegistrationTableComponent,
DataRowComponent, CardComponent, TaskListComponent, SideNavComponent, ...ASYNC,
RegistrationSummaryComponent, RegistrationTableComponent,
],
template: `
<app-page-shell heading="Mijn BIG-registratie">
<div class="app-stack">
@if (store.pendingHerregistratie()) {
<app-alert type="info">Uw herregistratie-aanvraag is in behandeling.</app-alert>
}
<app-page-shell heading="Mijn overzicht" intro="Welkom in uw persoonlijke omgeving van het BIG-register. Hier ziet u uw registratie en regelt u uw zaken.">
<div class="app-overview">
<app-side-nav [items]="nav" />
<!-- ONE state for two combined services (BIG-register + BRP). -->
<app-async [data]="store.profile()">
<ng-template appAsyncLoaded let-p>
<app-registration-summary [reg]="$any(p).registration" />
<div class="rhc-card rhc-card--default app-section">
<app-heading [level]="2">Persoonsgegevens (BRP)</app-heading>
<dl class="rhc-data-summary rhc-data-summary--row">
<app-data-row key="Straat" [value]="$any(p).person.adres.straat" />
<app-data-row key="Postcode" [value]="$any(p).person.adres.postcode" />
<app-data-row key="Woonplaats" [value]="$any(p).person.adres.woonplaats" />
</dl>
</div>
</ng-template>
<ng-template appAsyncLoading>
<app-skeleton height="2.5rem" [count]="6" />
</ng-template>
</app-async>
<div class="app-stack">
@if (store.pendingHerregistratie()) {
<app-alert type="info">Uw herregistratie-aanvraag is in behandeling.</app-alert>
}
<section>
<app-heading [level]="2">Specialismen en aantekeningen</app-heading>
<app-async [data]="store.aantekeningen()">
<ng-template appAsyncLoaded let-r>
<app-registration-table [rows]="$any(r)" />
<app-async [data]="store.profile()">
<ng-template appAsyncLoaded let-p>
@let tasks = tasksFor($any(p).registration);
<section>
<app-heading [level]="2">Wat moet ik regelen</app-heading>
@if (tasks.length) {
<app-task-list class="app-section" [tasks]="tasks" />
} @else {
<p class="rhc-paragraph app-text-subtle">U heeft op dit moment niets openstaan.</p>
}
</section>
<section>
<app-heading [level]="2">Mijn registratie</app-heading>
<div class="app-section">
<app-registration-summary [reg]="$any(p).registration" />
</div>
<app-card class="app-section" heading="Persoonsgegevens (BRP)">
<dl class="rhc-data-summary rhc-data-summary--row">
<app-data-row key="Straat" [value]="$any(p).person.adres.straat" />
<app-data-row key="Postcode" [value]="$any(p).person.adres.postcode" />
<app-data-row key="Woonplaats" [value]="$any(p).person.adres.woonplaats" />
</dl>
</app-card>
</section>
</ng-template>
<ng-template appAsyncLoading>
<app-skeleton height="2.5rem" [count]="3" />
</ng-template>
<ng-template appAsyncEmpty>
<p class="rhc-paragraph">U heeft nog geen specialismen of aantekeningen.</p>
<app-skeleton height="2.5rem" [count]="6" />
</ng-template>
</app-async>
</section>
<section>
<app-heading [level]="2">Wat wilt u doen?</app-heading>
<ul class="app-card-grid app-section">
@for (a of acties; track a.to) {
<li class="rhc-card rhc-card--default">
<app-heading [level]="3">{{ a.titel }}</app-heading>
<p class="rhc-paragraph">{{ a.tekst }}</p>
<app-link [to]="a.to">{{ a.actie }} →</app-link>
</li>
}
</ul>
</section>
<section>
<app-heading [level]="2">Specialismen en aantekeningen</app-heading>
<div class="app-section">
<app-async [data]="store.aantekeningen()">
<ng-template appAsyncLoaded let-r>
<app-registration-table [rows]="$any(r)" />
</ng-template>
<ng-template appAsyncLoading>
<app-skeleton height="2.5rem" [count]="3" />
</ng-template>
<ng-template appAsyncEmpty>
<p class="rhc-paragraph app-text-subtle">U heeft nog geen specialismen of aantekeningen.</p>
</ng-template>
</app-async>
</div>
</section>
<section>
<app-heading [level]="2">Wat wilt u doen?</app-heading>
<ul class="app-card-grid app-section">
@for (a of acties; track a.to) {
<li>
<app-card [heading]="a.titel">
<p class="rhc-paragraph">{{ a.tekst }}</p>
<app-link [to]="a.to">{{ a.actie }} →</app-link>
</app-card>
</li>
}
</ul>
</section>
</div>
</div>
</app-page-shell>
`,
})
export class DashboardPage {
protected store = inject(BigProfileStore);
private readonly today = new Date();
/** Primary actions, rendered as an accessible card grid. */
protected tasksFor(reg: Registration) {
return tasksFromProfile(reg, this.today);
}
/** Portal sections (navigation, not actions). */
protected readonly nav: NavItem[] = [
{ label: 'Overzicht', to: '/dashboard' },
{ label: 'Mijn gegevens', to: '/registratie' },
{ label: 'Herregistratie', to: '/herregistratie' },
{ label: 'Inschrijven', to: '/registreren' },
];
/** Primary transactional actions, as a card grid. */
protected readonly acties = [
{ to: '/registreren', titel: 'Inschrijven', tekst: 'Schrijf u in in het BIG-register via de registratiewizard.', actie: 'Start inschrijving' },
{ to: '/registratie', titel: 'Mijn gegevens', tekst: 'Bekijk uw gegevens of geef een wijziging door.', actie: 'Bekijk gegevens' },
{ to: '/herregistratie', titel: 'Herregistratie', tekst: 'Vraag uw herregistratie aan.', actie: 'Vraag aan' },
{ to: '/intake', titel: 'Herregistratie-intake', tekst: 'Vragenlijst met vertakkingen.', actie: 'Start intake' },
{ to: '/concepts', titel: 'Functionele patronen', tekst: 'Onmogelijke toestanden onmogelijk maken.', actie: 'Bekijk patronen' },
{ to: '/herregistratie', titel: 'Herregistratie aanvragen', tekst: 'Verleng uw registratie voor de komende periode.', actie: 'Vraag aan' },
{ to: '/registratie', titel: 'Gegevens wijzigen', tekst: 'Bekijk uw gegevens of geef een wijziging door.', actie: 'Bekijk gegevens' },
];
}