feat(dashboard): admin "Beheer" section linking the admin pages
CI / frontend (push) Has been cancelled
CI / backend (push) Has been cancelled
CI / semgrep (push) Has been cancelled
CI / storybook-a11y (push) Has been cancelled
CI / e2e (push) Has been cancelled
CI / api-client-drift (push) Has been cancelled

Add a capability-gated Beheer section to the dashboard listing the admin pages
(Huisstijl, Stamdata, Aanvragen), each shown when the principal holds its
capability — so admin pages are discoverable, not URL-only. Extract the admin
link list to shared/layout/admin-links.ts as one source of truth, reused by the
site header (was ADMIN_NAV_ITEMS) and the new section. Capability-gated, never
role-derived (PRD-0002).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
eho
2026-07-23 12:51:00 +02:00
co-authored by Claude Opus 4.8
parent 5b6023045e
commit 63fdacf622
5 changed files with 161 additions and 80 deletions
+23
View File
@@ -10,6 +10,8 @@ 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 { ASYNC } from '@shared/ui/async/async.component';
import { AccessStore } from '@shared/application/access.store';
import { ADMIN_LINKS } from '@shared/layout/admin-links';
import { RegistrationSummaryComponent } from '@registratie/ui/registration-summary/registration-summary.component';
import { RegistrationTableComponent } from '@registratie/ui/registration-table/registration-table.component';
import { AanvraagBlockComponent } from '@registratie/ui/aanvraag-block/aanvraag-block.component';
@@ -185,6 +187,22 @@ import { tasksFromProfile } from '@registratie/domain/tasks';
}
</app-application-list>
</section>
@if (adminLinks().length) {
<section>
<app-heading [level]="2" i18n="@@dashboard.beheer">Beheer</app-heading>
<app-application-list class="app-section">
@for (link of adminLinks(); track link.to) {
<li
app-application-link
[heading]="link.label"
[subtitle]="link.description"
[to]="link.to"
></li>
}
</app-application-list>
</section>
}
</div>
</app-page-shell>
`,
@@ -192,8 +210,13 @@ import { tasksFromProfile } from '@registratie/domain/tasks';
export class DashboardPage {
protected store = inject(BigProfileStore);
private apps = inject(ApplicationsStore);
private access = inject(AccessStore);
private router = inject(Router);
/** Admin pages the current principal may reach — capability-gated (never role-derived),
the same source + filter the site header uses. Empty for a non-admin → section hidden. */
protected adminLinks = computed(() => ADMIN_LINKS.filter((l) => this.access.can(l.cap)));
/** Pure view mapping for a submitted aanvraag → CIBG aanvragen-row fields. */
protected submittedRow = submittedRow;
+34
View File
@@ -0,0 +1,34 @@
import { Capability } from '@shared/domain/capability';
/** One admin page: its label, a short description, its route, and the capability that
gates it. Single source of truth for "which admin pages exist" — consumed by the site
header's admin nav AND the dashboard's Beheer section, both filtered by `AccessStore.can`.
Capability-gated, never role-derived (PRD-0002 §6): the FE only mirrors server-resolved
capabilities, so this stays correct when real authz replaces the X-Role stub. */
export interface AdminLink {
readonly label: string;
readonly description: string;
readonly to: string;
readonly cap: Capability;
}
export const ADMIN_LINKS: readonly AdminLink[] = [
{
label: $localize`:@@header.nav.huisstijl:Huisstijl`,
description: $localize`:@@admin.link.huisstijl.desc:Organisatiesjablonen voor brieven beheren`,
to: '/brief/huisstijl',
cap: 'orgtemplate:edit',
},
{
label: $localize`:@@header.nav.stamdata:Stamdata`,
description: $localize`:@@admin.link.stamdata.desc:Business-tabellen onderhouden`,
to: '/beheer/stamdata',
cap: 'stamdata:edit',
},
{
label: $localize`:@@header.nav.zaken:Aanvragen`,
description: $localize`:@@admin.link.zaken.desc:Alle aanvragen bekijken en beheren`,
to: '/beheer/zaken',
cap: 'cases:manage',
},
];
@@ -4,7 +4,7 @@ import { NavigationEnd, Router, RouterLink, RouterLinkActive } from '@angular/ro
import { filter, map } from 'rxjs/operators';
import { SESSION_PORT } from '@shared/application/session.port';
import { AccessStore } from '@shared/application/access.store';
import { Capability } from '@shared/domain/capability';
import { ADMIN_LINKS } from '@shared/layout/admin-links';
import { BreadcrumbComponent } from '@shared/layout/breadcrumb/breadcrumb.component';
import { trailFor } from '@shared/layout/breadcrumb/breadcrumb-trail';
@@ -20,26 +20,6 @@ const NAV_ITEMS: readonly HeaderNavItem[] = [
{ label: $localize`:@@header.nav.inschrijven:Inschrijven`, to: '/registreren' },
];
/** Admin-only nav, shown only when `/me` grants the matching capability — the pages
are otherwise reachable by URL alone. */
const ADMIN_NAV_ITEMS: readonly (HeaderNavItem & { readonly cap: Capability })[] = [
{
label: $localize`:@@header.nav.huisstijl:Huisstijl`,
to: '/brief/huisstijl',
cap: 'orgtemplate:edit',
},
{
label: $localize`:@@header.nav.stamdata:Stamdata`,
to: '/beheer/stamdata',
cap: 'stamdata:edit',
},
{
label: $localize`:@@header.nav.zaken:Aanvragen`,
to: '/beheer/zaken',
cap: 'cases:manage',
},
];
/** Organism: CIBG Huisstijl site header — logo block, robijn titlebar (breadcrumb +
user menu), horizontal nav. ponytail: text wordmark, not the licensed Rijksoverheid
beeldmerk; no search box (no search feature yet). */
@@ -131,7 +111,7 @@ export class SiteHeaderComponent {
private access = inject(AccessStore);
/** Injecting AccessStore here also warms `/me` at app start (the header renders on
every page), so the admin routes' guard usually finds caps already resolved. */
protected adminItems = computed(() => ADMIN_NAV_ITEMS.filter((i) => this.access.can(i.cap)));
protected adminItems = computed(() => ADMIN_LINKS.filter((i) => this.access.can(i.cap)));
readonly session = computed(() => this.sessionPort?.session() ?? null);
private url = toSignal(
+16
View File
@@ -3642,6 +3642,22 @@
<source>Aanvragen</source>
<target datatype="html">Cases</target>
</trans-unit>
<trans-unit id="dashboard.beheer" datatype="html">
<source>Beheer</source>
<target datatype="html">Administration</target>
</trans-unit>
<trans-unit id="admin.link.huisstijl.desc" datatype="html">
<source>Organisatiesjablonen voor brieven beheren</source>
<target datatype="html">Manage organisation letter templates</target>
</trans-unit>
<trans-unit id="admin.link.stamdata.desc" datatype="html">
<source>Business-tabellen onderhouden</source>
<target datatype="html">Maintain business reference tables</target>
</trans-unit>
<trans-unit id="admin.link.zaken.desc" datatype="html">
<source>Alle aanvragen bekijken en beheren</source>
<target datatype="html">View and manage all cases</target>
</trans-unit>
<trans-unit id="adminCases.heading" datatype="html">
<source>Aanvragen beheren</source>
<target datatype="html">Manage cases</target>
+86 -58
View File
@@ -1959,228 +1959,235 @@
<source>Mijn overzicht</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">45,46</context>
<context context-type="linenumber">47,48</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.intro" datatype="html">
<source>Welkom in uw persoonlijke omgeving van het BIG-register. Hier ziet u uw registratie en regelt u uw zaken.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">47,49</context>
<context context-type="linenumber">49,51</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.mijnAanvragen" datatype="html">
<source>Mijn aanvragen</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">63,65</context>
<context context-type="linenumber">65,67</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.pendingHerregistratie" datatype="html">
<source>Uw herregistratie-aanvraag is in behandeling.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">85,89</context>
<context context-type="linenumber">87,91</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.watMoetIkRegelen" datatype="html">
<source>Wat moet ik regelen</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">99,101</context>
<context context-type="linenumber">101,103</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">104,106</context>
<context context-type="linenumber">106,108</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.nietsOpenstaan" datatype="html">
<source> U heeft op dit moment niets openstaan. </source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">107,108</context>
<context context-type="linenumber">109,110</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.mijnRegistratie" datatype="html">
<source>Mijn registratie</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">114,116</context>
<context context-type="linenumber">116,118</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.persoonsgegevens" datatype="html">
<source>Persoonsgegevens (BRP)</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">122,124</context>
<context context-type="linenumber">124,126</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.straat" datatype="html">
<source>Straat</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">128</context>
<context context-type="linenumber">130</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.postcode" datatype="html">
<source>Postcode</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">133,134</context>
<context context-type="linenumber">135,136</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.woonplaats" datatype="html">
<source>Woonplaats</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">139,140</context>
<context context-type="linenumber">141,142</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.specialismen" datatype="html">
<source>Specialismen en aantekeningen</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">153,155</context>
<context context-type="linenumber">155,157</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.geenSpecialismen" datatype="html">
<source> U heeft nog geen specialismen of aantekeningen. </source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">167,169</context>
<context context-type="linenumber">169,171</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.watWiltUDoen" datatype="html">
<source>Wat wilt u doen?</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">175,176</context>
<context context-type="linenumber">177,178</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.beheer" datatype="html">
<source>Beheer</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">193,194</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.actie.inschrijven.titel" datatype="html">
<source>Inschrijven</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">265</context>
<context context-type="linenumber">288</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.actie.inschrijven.tekst" datatype="html">
<source>Schrijf u in in het BIG-register via de registratiewizard.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">266</context>
<context context-type="linenumber">289</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.actie.inschrijven.actie" datatype="html">
<source>Start inschrijving</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">267</context>
<context context-type="linenumber">290</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.actie.herregistratie.titel" datatype="html">
<source>Herregistratie aanvragen</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">271</context>
<context context-type="linenumber">294</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.actie.herregistratie.tekst" datatype="html">
<source>Verleng uw registratie voor de komende periode.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">272</context>
<context context-type="linenumber">295</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.actie.herregistratie.actie" datatype="html">
<source>Vraag aan</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">273</context>
<context context-type="linenumber">296</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.actie.intake.titel" datatype="html">
<source>Herregistratie-intake</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">277</context>
<context context-type="linenumber">300</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.actie.intake.tekst" datatype="html">
<source>Vragenlijst met vertakkingen.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">278</context>
<context context-type="linenumber">301</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.actie.intake.actie" datatype="html">
<source>Start intake</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">279</context>
<context context-type="linenumber">302</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.actie.wijzigen.titel" datatype="html">
<source>Gegevens wijzigen</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">283</context>
<context context-type="linenumber">306</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.actie.wijzigen.tekst" datatype="html">
<source>Bekijk uw gegevens of geef een wijziging door.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">284</context>
<context context-type="linenumber">307</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.actie.wijzigen.actie" datatype="html">
<source>Bekijk gegevens</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">285</context>
<context context-type="linenumber">308</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.actie.concepten.titel" datatype="html">
<source>Functionele patronen</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">289</context>
<context context-type="linenumber">312</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.actie.concepten.tekst" datatype="html">
<source>Bekijk de FP/TEA-bouwstenen van deze POC.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">290</context>
<context context-type="linenumber">313</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.actie.concepten.actie" datatype="html">
<source>Bekijk patronen</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">291</context>
<context context-type="linenumber">314</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.actie.brief.titel" datatype="html">
<source>Brief opstellen</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">295</context>
<context context-type="linenumber">318</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.actie.brief.tekst" datatype="html">
<source>Stel een brief samen uit vaste en vrije onderdelen.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">296</context>
<context context-type="linenumber">319</context>
</context-group>
</trans-unit>
<trans-unit id="dashboard.actie.brief.actie" datatype="html">
<source>Start brief</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/registratie/ui/dashboard.page.ts</context>
<context context-type="linenumber">297</context>
<context context-type="linenumber">320</context>
</context-group>
</trans-unit>
<trans-unit id="registratie.kanaalEmail" datatype="html">
@@ -2607,6 +2614,48 @@
<context context-type="linenumber">28</context>
</context-group>
</trans-unit>
<trans-unit id="header.nav.huisstijl" datatype="html">
<source>Huisstijl</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/layout/admin-links.ts</context>
<context context-type="linenumber">17</context>
</context-group>
</trans-unit>
<trans-unit id="admin.link.huisstijl.desc" datatype="html">
<source>Organisatiesjablonen voor brieven beheren</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/layout/admin-links.ts</context>
<context context-type="linenumber">18</context>
</context-group>
</trans-unit>
<trans-unit id="header.nav.stamdata" datatype="html">
<source>Stamdata</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/layout/admin-links.ts</context>
<context context-type="linenumber">23</context>
</context-group>
</trans-unit>
<trans-unit id="admin.link.stamdata.desc" datatype="html">
<source>Business-tabellen onderhouden</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/layout/admin-links.ts</context>
<context context-type="linenumber">24</context>
</context-group>
</trans-unit>
<trans-unit id="header.nav.zaken" datatype="html">
<source>Aanvragen</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/layout/admin-links.ts</context>
<context context-type="linenumber">29</context>
</context-group>
</trans-unit>
<trans-unit id="admin.link.zaken.desc" datatype="html">
<source>Alle aanvragen bekijken en beheren</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/layout/admin-links.ts</context>
<context context-type="linenumber">30</context>
</context-group>
</trans-unit>
<trans-unit id="crumb.dashboard" datatype="html">
<source>Mijn overzicht</source>
<context-group purpose="location">
@@ -2761,53 +2810,32 @@
<context context-type="linenumber">20</context>
</context-group>
</trans-unit>
<trans-unit id="header.nav.huisstijl" datatype="html">
<source>Huisstijl</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/layout/site-header/site-header.component.ts</context>
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit id="header.nav.stamdata" datatype="html">
<source>Stamdata</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/layout/site-header/site-header.component.ts</context>
<context context-type="linenumber">32</context>
</context-group>
</trans-unit>
<trans-unit id="header.nav.zaken" datatype="html">
<source>Aanvragen</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/layout/site-header/site-header.component.ts</context>
<context context-type="linenumber">37</context>
</context-group>
</trans-unit>
<trans-unit id="header.sender" datatype="html">
<source>BIG-register</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/layout/site-header/site-header.component.ts</context>
<context context-type="linenumber">75,76</context>
<context context-type="linenumber">55,56</context>
</context-group>
</trans-unit>
<trans-unit id="header.ministry" datatype="html">
<source>Ministerie van Volksgezondheid, Welzijn en Sport</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/layout/site-header/site-header.component.ts</context>
<context context-type="linenumber">77,79</context>
<context context-type="linenumber">57,59</context>
</context-group>
</trans-unit>
<trans-unit id="header.uitloggen" datatype="html">
<source> Uitloggen </source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/layout/site-header/site-header.component.ts</context>
<context context-type="linenumber">99,100</context>
<context context-type="linenumber">79,80</context>
</context-group>
</trans-unit>
<trans-unit id="header.navAria" datatype="html">
<source>Hoofdnavigatie</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/layout/site-header/site-header.component.ts</context>
<context context-type="linenumber">107,108</context>
<context context-type="linenumber">87,88</context>
</context-group>
</trans-unit>
<trans-unit id="wizard.naarStap" datatype="html">