From f38f727a606967fecfe993152ab5814f487e71c8 Mon Sep 17 00:00:00 2001 From: Edwin van den Houdt Date: Fri, 26 Jun 2026 09:38:34 +0200 Subject: [PATCH] Regenerate compodoc documentation.json Reflects the herregistratie jaren field and the branching intake wizard. Co-Authored-By: Claude Opus 4.8 --- documentation.json | 2425 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 2317 insertions(+), 108 deletions(-) diff --git a/documentation.json b/documentation.json index 19c6bb1..4a7de95 100644 --- a/documentation.json +++ b/documentation.json @@ -3,12 +3,12 @@ "interfaces": [ { "name": "Aantekening", - "id": "interface-Aantekening-2ecca36e413e22c1c5967d07b811fa4171ff578eb6b47a7275723bcca44e93a2db27d77a765b82e5f6182492464cc8b9268c9724e13b400718d30da7bc4ba50c", + "id": "interface-Aantekening-8e28c6bf4ec154e4db100e2ec1ee19fe9c2d50bc429733f8288f32277ce91963f67ec0363028632e71cfb7e05ce25001908c8dd8ee66d732632d67b993c0645b", "file": "src/app/registratie/domain/registration.ts", "deprecated": false, "deprecationMessage": "", "type": "interface", - "sourceCode": "export type RegistrationStatus =\n | { tag: 'Geregistreerd'; herregistratieDatum: string } // ISO date\n | { tag: 'Geschorst'; geschorstTot: string; reden: string }\n | { tag: 'Doorgehaald'; doorgehaaldOp: string; reden: string };\n\n/** Just the discriminant — for atoms that only need the label/color. */\nexport type StatusTag = RegistrationStatus['tag'];\n\nexport interface Registration {\n bigNummer: string;\n naam: string;\n beroep: string; // arts, verpleegkundige, apotheker, ...\n registratiedatum: string; // ISO date\n geboortedatum: string;\n status: RegistrationStatus;\n}\n\nexport interface Aantekening {\n type: string; // specialisme of aantekening\n omschrijving: string;\n datum: string;\n}\n", + "sourceCode": "export type RegistrationStatus =\n | { tag: 'Geregistreerd'; herregistratieDatum: string } // ISO date\n | { tag: 'Geschorst'; geschorstTot: string; reden: string }\n | { tag: 'Doorgehaald'; doorgehaaldOp: string; reden: string };\n\n/** Just the discriminant — for atoms that only need the label/color. */\nexport type StatusTag = RegistrationStatus['tag'];\n\nexport interface Registration {\n bigNummer: string;\n naam: string;\n beroep: string; // arts, verpleegkundige, apotheker, ...\n registratiedatum: string; // ISO date\n geboortedatum: string;\n status: RegistrationStatus;\n}\n\n/** A note is either a recognised specialism or a plain annotation — a closed set,\n not an open string, so a typo can't slip through. */\nexport type AantekeningType = 'Specialisme' | 'Aantekening';\n\nexport interface Aantekening {\n type: AantekeningType;\n omschrijving: string;\n datum: string;\n}\n", "properties": [ { "name": "datum", @@ -18,7 +18,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 28 + "line": 32 }, { "name": "omschrijving", @@ -28,17 +28,17 @@ "indexKey": "", "optional": false, "description": "", - "line": 27 + "line": 31 }, { "name": "type", "deprecated": false, "deprecationMessage": "", - "type": "string", + "type": "AantekeningType", "indexKey": "", "optional": false, "description": "", - "line": 26 + "line": 30 } ], "indexSignatures": [], @@ -93,6 +93,83 @@ "methods": [], "extends": [] }, + { + "name": "Answers", + "id": "interface-Answers-53c06ae2dad9470cd0c742a7e5903458a870e5eaaf70ce3043cec53935f09d2cb0d5f9980b723ce79be2460d550908bc894743c867abdf0974f17f7596cdd05d", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "interface", + "sourceCode": "import { Result, ok, err, assertNever } from '@shared/kernel/fp';\nimport { Uren, parseUren } from '@registratie/domain/value-objects/uren';\n\n/**\n * A BRANCHING wizard. Unlike herregistratie.machine (fixed 2 steps), the set of\n * steps here is NOT stored — it's *derived* from the answers by `visibleSteps`.\n * Answer \"buiten Nederland gewerkt? → ja\" and two extra steps appear; report few\n * hours and a scholing-question appears. The progress bar's denominator changes\n * as you type. \"Which question comes next\" is a pure function, so it's trivial\n * to test and impossible to get out of sync with the data.\n */\n\nexport type JaNee = 'ja' | 'nee';\n\n/** Every possible question, as a union — never a bare string. */\nexport type StepId = 'buitenland' | 'buitenlandDetails' | 'uren' | 'scholing' | 'punten' | 'review';\n\n/** One record carried across every step (and persisted). All optional: the user\n fills it in gradually, and branches may never ask some fields. */\nexport interface Answers {\n buitenlandGewerkt?: JaNee; // Q1\n land?: string; // Q1a — only when buitenlandGewerkt === 'ja'\n buitenlandseUren?: string; // Q1b — only when buitenlandGewerkt === 'ja'\n uren?: string; // Q2 — uren in NL\n scholingGevolgd?: JaNee; // Q3 — only when total hours are below the threshold\n punten?: string; // Q4\n}\n\n/** What we have after the review step parses — guaranteed valid/typed. */\nexport interface ValidIntake {\n werktBuitenland: boolean;\n land?: string;\n buitenlandseUren?: Uren;\n uren: Uren;\n aanvullendeScholing?: boolean;\n punten: Uren;\n}\n\n/** Below this many NL-hours we ask whether extra scholing was followed. */\nconst LAGE_UREN_DREMPEL = 1000;\n\nfunction lageUren(a: Answers): boolean {\n const r = parseUren(a.uren ?? '');\n return r.ok && r.value < LAGE_UREN_DREMPEL;\n}\n\n/**\n * THE branching, as one pure function. The step list is recomputed on every\n * transition, so changing an earlier answer immediately adds/removes later steps.\n */\nexport function visibleSteps(a: Answers): StepId[] {\n const steps: StepId[] = ['buitenland'];\n if (a.buitenlandGewerkt === 'ja') steps.push('buitenlandDetails');\n steps.push('uren');\n if (lageUren(a)) steps.push('scholing');\n steps.push('punten', 'review');\n return steps;\n}\n\nexport type IntakeState =\n | { tag: 'Answering'; answers: Answers; cursor: number; errors: Partial> }\n | { tag: 'Submitting'; data: ValidIntake }\n | { tag: 'Submitted'; data: ValidIntake }\n | { tag: 'Failed'; data: ValidIntake; error: string };\n\nexport const initial: IntakeState = { tag: 'Answering', answers: {}, cursor: 0, errors: {} };\n\n/** Which step the cursor currently points at (clamped to the live step list). */\nexport function currentStep(s: Extract): StepId {\n const steps = visibleSteps(s.answers);\n return steps[Math.min(s.cursor, steps.length - 1)];\n}\n\n/** Validate ONE step's fields. Returns the per-field error keyed by StepId. */\nfunction validateStep(step: StepId, a: Answers): Result>, void> {\n switch (step) {\n case 'buitenland':\n return a.buitenlandGewerkt ? ok(undefined) : err({ buitenland: 'Maak een keuze.' });\n case 'buitenlandDetails': {\n if (!a.land || a.land.trim() === '') return err({ buitenlandDetails: 'Vul een land in.' });\n const u = parseUren(a.buitenlandseUren ?? '');\n return u.ok ? ok(undefined) : err({ buitenlandDetails: u.error });\n }\n case 'uren': {\n const u = parseUren(a.uren ?? '');\n return u.ok ? ok(undefined) : err({ uren: u.error });\n }\n case 'scholing':\n return a.scholingGevolgd ? ok(undefined) : err({ scholing: 'Maak een keuze.' });\n case 'punten': {\n const p = parseUren(a.punten ?? '');\n return p.ok ? ok(undefined) : err({ punten: p.error });\n }\n case 'review':\n return ok(undefined); // review shows a summary; no own fields\n default:\n return assertNever(step);\n }\n}\n\n/** Parse the whole questionnaire into a ValidIntake (called on submit). */\nfunction validateAll(a: Answers): Result>, ValidIntake> {\n const errors: Partial> = {};\n for (const step of visibleSteps(a)) {\n const r = validateStep(step, a);\n if (!r.ok) Object.assign(errors, r.error);\n }\n if (Object.keys(errors).length > 0) return err(errors);\n\n const uren = parseUren(a.uren ?? '');\n const punten = parseUren(a.punten ?? '');\n // visibleSteps guaranteed these parse, but keep the compiler happy.\n if (!uren.ok || !punten.ok) return err(errors);\n\n const werktBuitenland = a.buitenlandGewerkt === 'ja';\n const buitenland = parseUren(a.buitenlandseUren ?? '');\n return ok({\n werktBuitenland,\n land: werktBuitenland ? a.land : undefined,\n buitenlandseUren: werktBuitenland && buitenland.ok ? buitenland.value : undefined,\n uren: uren.value,\n aanvullendeScholing: lageUren(a) ? a.scholingGevolgd === 'ja' : undefined,\n punten: punten.value,\n });\n}\n\nexport function setAnswer(s: IntakeState, key: keyof Answers, value: string): IntakeState {\n if (s.tag !== 'Answering') return s;\n const answers = { ...s.answers, [key]: value };\n // Editing an earlier answer can shrink the step list; clamp the cursor.\n const cursor = Math.min(s.cursor, visibleSteps(answers).length - 1);\n return { ...s, answers, cursor };\n}\n\nexport function next(s: IntakeState): IntakeState {\n if (s.tag !== 'Answering') return s;\n const r = validateStep(currentStep(s), s.answers);\n if (!r.ok) return { ...s, errors: r.error };\n const last = visibleSteps(s.answers).length - 1;\n return { ...s, cursor: Math.min(s.cursor + 1, last), errors: {} };\n}\n\nexport function back(s: IntakeState): IntakeState {\n if (s.tag !== 'Answering' || s.cursor === 0) return s;\n return { ...s, cursor: s.cursor - 1, errors: {} };\n}\n\nexport function submit(s: IntakeState): IntakeState {\n if (s.tag !== 'Answering') return s;\n const r = validateAll(s.answers);\n return r.ok ? { tag: 'Submitting', data: r.value } : { ...s, errors: r.error };\n}\n\nexport function resolve(s: IntakeState, r: Result): IntakeState {\n if (s.tag !== 'Submitting') return s;\n return r.ok ? { tag: 'Submitted', data: s.data } : { tag: 'Failed', data: s.data, error: r.error };\n}\n\nexport type IntakeMsg =\n | { tag: 'SetAnswer'; key: keyof Answers; value: string }\n | { tag: 'Next' }\n | { tag: 'Back' }\n | { tag: 'Submit' }\n | { tag: 'Retry' }\n | { tag: 'SubmitConfirmed' }\n | { tag: 'SubmitFailed'; error: string }\n | { tag: 'Seed'; state: IntakeState };\n\nexport function reduce(s: IntakeState, m: IntakeMsg): IntakeState {\n switch (m.tag) {\n case 'SetAnswer':\n return setAnswer(s, m.key, m.value);\n case 'Next':\n return next(s);\n case 'Back':\n return back(s);\n case 'Submit':\n return submit(s);\n case 'Retry':\n return s.tag === 'Failed' ? { tag: 'Submitting', data: s.data } : s;\n case 'SubmitConfirmed':\n return s.tag === 'Submitting' ? { tag: 'Submitted', data: s.data } : s;\n case 'SubmitFailed':\n return s.tag === 'Submitting' ? { tag: 'Failed', data: s.data, error: m.error } : s;\n case 'Seed':\n return m.state;\n default:\n return assertNever(m);\n }\n}\n", + "properties": [ + { + "name": "buitenlandGewerkt", + "deprecated": false, + "deprecationMessage": "", + "type": "JaNee", + "indexKey": "", + "optional": true, + "description": "", + "line": 21 + }, + { + "name": "buitenlandseUren", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": true, + "description": "", + "line": 23 + }, + { + "name": "land", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": true, + "description": "", + "line": 22 + }, + { + "name": "punten", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": true, + "description": "", + "line": 26 + }, + { + "name": "scholingGevolgd", + "deprecated": false, + "deprecationMessage": "", + "type": "JaNee", + "indexKey": "", + "optional": true, + "description": "", + "line": 25 + }, + { + "name": "uren", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": true, + "description": "", + "line": 24 + } + ], + "indexSignatures": [], + "kind": 172, + "description": "

One record carried across every step (and persisted). All optional: the user\nfills it in gradually, and branches may never ask some fields.

\n", + "rawdescription": "\nOne record carried across every step (and persisted). All optional: the user\nfills it in gradually, and branches may never ask some fields.", + "methods": [], + "extends": [] + }, { "name": "BigProfile", "id": "interface-BigProfile-c363e317899ff3cd424b0033c2f57ea14e9617f52401171543b5a82d67046a894a2806afc198812808d0080b3093471e081f7e79b12f2c8c37eef68785e47a43", @@ -179,13 +256,23 @@ }, { "name": "Draft", - "id": "interface-Draft-6d0ec48dfedad1e9d3dc32caaf0639d35f1430f1e8081a0a719ad0675d06912a90de0d8159326e13c9cc3c8f82521e3fded8d06a5feb1480be4c084a967e6aad", + "id": "interface-Draft-097a742aa05bc7bbfee387dd3ea5b2ac382815db8ca18107e699d11e6da110cdeae2eb377bcea284f415cfd263937c3b0f6e7ed2ee9090d660d898dddefe3a4f", "file": "src/app/herregistratie/domain/herregistratie.machine.ts", "deprecated": false, "deprecationMessage": "", "type": "interface", - "sourceCode": "import { Result, assertNever } from '@shared/kernel/fp';\nimport { Uren, parseUren } from '@registratie/domain/value-objects/uren';\n\n/** What the user is typing (raw, possibly invalid). */\nexport interface Draft {\n uren: string;\n punten: string;\n}\n\n/** What we have AFTER parsing — branded/typed, guaranteed valid. */\nexport interface Valid {\n uren: Uren;\n punten: number;\n}\n\n/**\n * The whole wizard as one tagged union. `step` and `errors` exist ONLY while\n * Editing; Submitting/Submitted/Failed carry a `Valid` payload and nothing else.\n * So \"submitting while a field is invalid\" or \"showing the success screen with\n * errors set\" are unrepresentable — the bug class is gone by construction.\n */\nexport type WizardState =\n | { tag: 'Editing'; step: 1 | 2; draft: Draft; errors: Partial> }\n | { tag: 'Submitting'; data: Valid }\n | { tag: 'Submitted'; data: Valid }\n | { tag: 'Failed'; data: Valid; error: string };\n\nexport const initial: WizardState = { tag: 'Editing', step: 1, draft: { uren: '', punten: '' }, errors: {} };\n\n/** Parse every field; on success hand back a Valid, else the per-field errors. */\nfunction validate(draft: Draft): Result>, Valid> {\n const uren = parseUren(draft.uren);\n const punten = parseUren(draft.punten);\n const errors: Partial> = {};\n if (!uren.ok) errors.uren = uren.error;\n if (!punten.ok) errors.punten = punten.error;\n if (uren.ok && punten.ok) return { ok: true, value: { uren: uren.value, punten: punten.value } };\n return { ok: false, error: errors };\n}\n\n/** Step 1 → 2: only advance if the uren field parses. Illegal elsewhere = no-op. */\nexport function next(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 1) return s;\n const uren = parseUren(s.draft.uren);\n return uren.ok\n ? { ...s, step: 2, errors: {} }\n : { ...s, errors: { uren: uren.error } };\n}\n\nexport function back(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 2) return s;\n return { ...s, step: 1, errors: {} };\n}\n\n/** Step 2 submit: parse everything; move to Submitting only with Valid data. */\nexport function submit(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 2) return s;\n const result = validate(s.draft);\n return result.ok ? { tag: 'Submitting', data: result.value } : { ...s, errors: result.error };\n}\n\n/** Resolve the async submit. Only meaningful while Submitting. */\nexport function resolve(s: WizardState, r: Result): WizardState {\n if (s.tag !== 'Submitting') return s;\n return r.ok ? { tag: 'Submitted', data: s.data } : { tag: 'Failed', data: s.data, error: r.error };\n}\n\n/** Update one draft field while editing; ignored in any other state. */\nexport function setField(s: WizardState, key: keyof Draft, value: string): WizardState {\n if (s.tag !== 'Editing') return s;\n return { ...s, draft: { ...s.draft, [key]: value } };\n}\n\n/**\n * Every event that can happen to the wizard, as one message type. The component\n * sends a WizardMsg; `reduce` decides the next state. This is the Elm\n * Model+Msg+update pattern: ONE pure function describes all state changes.\n */\nexport type WizardMsg =\n | { tag: 'SetField'; key: keyof Draft; value: string }\n | { tag: 'Next' }\n | { tag: 'Back' }\n | { tag: 'Submit' }\n | { tag: 'Retry' }\n | { tag: 'SubmitConfirmed' }\n | { tag: 'SubmitFailed'; error: string }\n | { tag: 'Seed'; state: WizardState }; // mount a specific state (stories/showcase)\n\nexport function reduce(s: WizardState, m: WizardMsg): WizardState {\n switch (m.tag) {\n case 'SetField':\n return setField(s, m.key, m.value);\n case 'Next':\n return next(s);\n case 'Back':\n return back(s);\n case 'Submit':\n return submit(s);\n case 'Retry':\n return s.tag === 'Failed' ? { tag: 'Submitting', data: s.data } : s;\n case 'SubmitConfirmed':\n return s.tag === 'Submitting' ? { tag: 'Submitted', data: s.data } : s;\n case 'SubmitFailed':\n return s.tag === 'Submitting' ? { tag: 'Failed', data: s.data, error: m.error } : s;\n case 'Seed':\n return m.state;\n default:\n return assertNever(m);\n }\n}\n", + "sourceCode": "import { Result, assertNever } from '@shared/kernel/fp';\nimport { Uren, parseUren } from '@registratie/domain/value-objects/uren';\n\n/** What the user is typing (raw, possibly invalid). */\nexport interface Draft {\n uren: string;\n jaren: string;\n punten: string;\n}\n\n/** What we have AFTER parsing — branded/typed, guaranteed valid. */\nexport interface Valid {\n uren: Uren;\n jaren: number;\n punten: number;\n}\n\n/**\n * The whole wizard as one tagged union. `step` and `errors` exist ONLY while\n * Editing; Submitting/Submitted/Failed carry a `Valid` payload and nothing else.\n * So \"submitting while a field is invalid\" or \"showing the success screen with\n * errors set\" are unrepresentable — the bug class is gone by construction.\n */\nexport type WizardState =\n | { tag: 'Editing'; step: 1 | 2; draft: Draft; errors: Partial> }\n | { tag: 'Submitting'; data: Valid }\n | { tag: 'Submitted'; data: Valid }\n | { tag: 'Failed'; data: Valid; error: string };\n\nexport const initial: WizardState = { tag: 'Editing', step: 1, draft: { uren: '', jaren: '', punten: '' }, errors: {} };\n\n/** Parse every field; on success hand back a Valid, else the per-field errors. */\nfunction validate(draft: Draft): Result>, Valid> {\n const uren = parseUren(draft.uren);\n const jaren = parseUren(draft.jaren);\n const punten = parseUren(draft.punten);\n const errors: Partial> = {};\n if (!uren.ok) errors.uren = uren.error;\n if (!jaren.ok) errors.jaren = jaren.error;\n if (!punten.ok) errors.punten = punten.error;\n if (uren.ok && jaren.ok && punten.ok) {\n return { ok: true, value: { uren: uren.value, jaren: jaren.value, punten: punten.value } };\n }\n return { ok: false, error: errors };\n}\n\n/** Step 1 → 2: advance only when BOTH step-1 fields parse. Illegal elsewhere = no-op. */\nexport function next(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 1) return s;\n const uren = parseUren(s.draft.uren);\n const jaren = parseUren(s.draft.jaren);\n const errors: Partial> = {};\n if (!uren.ok) errors.uren = uren.error;\n if (!jaren.ok) errors.jaren = jaren.error;\n return Object.keys(errors).length === 0 ? { ...s, step: 2, errors: {} } : { ...s, errors };\n}\n\nexport function back(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 2) return s;\n return { ...s, step: 1, errors: {} };\n}\n\n/** Step 2 submit: parse everything; move to Submitting only with Valid data. */\nexport function submit(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 2) return s;\n const result = validate(s.draft);\n return result.ok ? { tag: 'Submitting', data: result.value } : { ...s, errors: result.error };\n}\n\n/** Resolve the async submit. Only meaningful while Submitting. */\nexport function resolve(s: WizardState, r: Result): WizardState {\n if (s.tag !== 'Submitting') return s;\n return r.ok ? { tag: 'Submitted', data: s.data } : { tag: 'Failed', data: s.data, error: r.error };\n}\n\n/** Update one draft field while editing; ignored in any other state. */\nexport function setField(s: WizardState, key: keyof Draft, value: string): WizardState {\n if (s.tag !== 'Editing') return s;\n return { ...s, draft: { ...s.draft, [key]: value } };\n}\n\n/**\n * Every event that can happen to the wizard, as one message type. The component\n * sends a WizardMsg; `reduce` decides the next state. This is the Elm\n * Model+Msg+update pattern: ONE pure function describes all state changes.\n */\nexport type WizardMsg =\n | { tag: 'SetField'; key: keyof Draft; value: string }\n | { tag: 'Next' }\n | { tag: 'Back' }\n | { tag: 'Submit' }\n | { tag: 'Retry' }\n | { tag: 'SubmitConfirmed' }\n | { tag: 'SubmitFailed'; error: string }\n | { tag: 'Seed'; state: WizardState }; // mount a specific state (stories/showcase)\n\nexport function reduce(s: WizardState, m: WizardMsg): WizardState {\n switch (m.tag) {\n case 'SetField':\n return setField(s, m.key, m.value);\n case 'Next':\n return next(s);\n case 'Back':\n return back(s);\n case 'Submit':\n return submit(s);\n case 'Retry':\n return s.tag === 'Failed' ? { tag: 'Submitting', data: s.data } : s;\n case 'SubmitConfirmed':\n return s.tag === 'Submitting' ? { tag: 'Submitted', data: s.data } : s;\n case 'SubmitFailed':\n return s.tag === 'Submitting' ? { tag: 'Failed', data: s.data, error: m.error } : s;\n case 'Seed':\n return m.state;\n default:\n return assertNever(m);\n }\n}\n", "properties": [ + { + "name": "jaren", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": false, + "description": "", + "line": 7 + }, { "name": "punten", "deprecated": false, @@ -194,7 +281,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 7 + "line": 8 }, { "name": "uren", @@ -259,14 +346,49 @@ "methods": [], "extends": [] }, + { + "name": "RadioOption", + "id": "interface-RadioOption-ac09f04d2bd5bb44d96600b5081778fdf878ad9137e373a0832855877706afbe37687f7937f568a890e541467637b067a6c8646dcb2005ab746f1a829d8d39a2", + "file": "src/app/shared/ui/radio-group/radio-group.component.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "interface", + "sourceCode": "import { Component, forwardRef, input } from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\n\nexport interface RadioOption {\n value: string;\n label: string;\n}\n\n/** Atom: a radio group. Thin wrapper over the Utrecht/RHC radio CSS, wired as a\n form control so it works with ngModel just like the text-input atom. */\n@Component({\n selector: 'app-radio-group',\n template: `\n
\n @for (opt of options(); track opt.value) {\n \n }\n
\n `,\n providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => RadioGroupComponent), multi: true }],\n})\nexport class RadioGroupComponent implements ControlValueAccessor {\n options = input.required();\n name = input.required();\n\n value = '';\n disabled = false;\n onChange: (v: string) => void = () => {};\n onTouched: () => void = () => {};\n\n select(v: string) {\n this.value = v;\n this.onChange(v);\n }\n writeValue(v: string) { this.value = v ?? ''; }\n registerOnChange(fn: (v: string) => void) { this.onChange = fn; }\n registerOnTouched(fn: () => void) { this.onTouched = fn; }\n setDisabledState(d: boolean) { this.disabled = d; }\n}\n", + "properties": [ + { + "name": "label", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": false, + "description": "", + "line": 6 + }, + { + "name": "value", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": false, + "description": "", + "line": 5 + } + ], + "indexSignatures": [], + "kind": 172, + "methods": [], + "extends": [] + }, { "name": "Registration", - "id": "interface-Registration-2ecca36e413e22c1c5967d07b811fa4171ff578eb6b47a7275723bcca44e93a2db27d77a765b82e5f6182492464cc8b9268c9724e13b400718d30da7bc4ba50c", + "id": "interface-Registration-8e28c6bf4ec154e4db100e2ec1ee19fe9c2d50bc429733f8288f32277ce91963f67ec0363028632e71cfb7e05ce25001908c8dd8ee66d732632d67b993c0645b", "file": "src/app/registratie/domain/registration.ts", "deprecated": false, "deprecationMessage": "", "type": "interface", - "sourceCode": "export type RegistrationStatus =\n | { tag: 'Geregistreerd'; herregistratieDatum: string } // ISO date\n | { tag: 'Geschorst'; geschorstTot: string; reden: string }\n | { tag: 'Doorgehaald'; doorgehaaldOp: string; reden: string };\n\n/** Just the discriminant — for atoms that only need the label/color. */\nexport type StatusTag = RegistrationStatus['tag'];\n\nexport interface Registration {\n bigNummer: string;\n naam: string;\n beroep: string; // arts, verpleegkundige, apotheker, ...\n registratiedatum: string; // ISO date\n geboortedatum: string;\n status: RegistrationStatus;\n}\n\nexport interface Aantekening {\n type: string; // specialisme of aantekening\n omschrijving: string;\n datum: string;\n}\n", + "sourceCode": "export type RegistrationStatus =\n | { tag: 'Geregistreerd'; herregistratieDatum: string } // ISO date\n | { tag: 'Geschorst'; geschorstTot: string; reden: string }\n | { tag: 'Doorgehaald'; doorgehaaldOp: string; reden: string };\n\n/** Just the discriminant — for atoms that only need the label/color. */\nexport type StatusTag = RegistrationStatus['tag'];\n\nexport interface Registration {\n bigNummer: string;\n naam: string;\n beroep: string; // arts, verpleegkundige, apotheker, ...\n registratiedatum: string; // ISO date\n geboortedatum: string;\n status: RegistrationStatus;\n}\n\n/** A note is either a recognised specialism or a plain annotation — a closed set,\n not an open string, so a typo can't slip through. */\nexport type AantekeningType = 'Specialisme' | 'Aantekening';\n\nexport interface Aantekening {\n type: AantekeningType;\n omschrijving: string;\n datum: string;\n}\n", "properties": [ { "name": "beroep", @@ -445,13 +567,23 @@ }, { "name": "Valid", - "id": "interface-Valid-6d0ec48dfedad1e9d3dc32caaf0639d35f1430f1e8081a0a719ad0675d06912a90de0d8159326e13c9cc3c8f82521e3fded8d06a5feb1480be4c084a967e6aad", + "id": "interface-Valid-097a742aa05bc7bbfee387dd3ea5b2ac382815db8ca18107e699d11e6da110cdeae2eb377bcea284f415cfd263937c3b0f6e7ed2ee9090d660d898dddefe3a4f", "file": "src/app/herregistratie/domain/herregistratie.machine.ts", "deprecated": false, "deprecationMessage": "", "type": "interface", - "sourceCode": "import { Result, assertNever } from '@shared/kernel/fp';\nimport { Uren, parseUren } from '@registratie/domain/value-objects/uren';\n\n/** What the user is typing (raw, possibly invalid). */\nexport interface Draft {\n uren: string;\n punten: string;\n}\n\n/** What we have AFTER parsing — branded/typed, guaranteed valid. */\nexport interface Valid {\n uren: Uren;\n punten: number;\n}\n\n/**\n * The whole wizard as one tagged union. `step` and `errors` exist ONLY while\n * Editing; Submitting/Submitted/Failed carry a `Valid` payload and nothing else.\n * So \"submitting while a field is invalid\" or \"showing the success screen with\n * errors set\" are unrepresentable — the bug class is gone by construction.\n */\nexport type WizardState =\n | { tag: 'Editing'; step: 1 | 2; draft: Draft; errors: Partial> }\n | { tag: 'Submitting'; data: Valid }\n | { tag: 'Submitted'; data: Valid }\n | { tag: 'Failed'; data: Valid; error: string };\n\nexport const initial: WizardState = { tag: 'Editing', step: 1, draft: { uren: '', punten: '' }, errors: {} };\n\n/** Parse every field; on success hand back a Valid, else the per-field errors. */\nfunction validate(draft: Draft): Result>, Valid> {\n const uren = parseUren(draft.uren);\n const punten = parseUren(draft.punten);\n const errors: Partial> = {};\n if (!uren.ok) errors.uren = uren.error;\n if (!punten.ok) errors.punten = punten.error;\n if (uren.ok && punten.ok) return { ok: true, value: { uren: uren.value, punten: punten.value } };\n return { ok: false, error: errors };\n}\n\n/** Step 1 → 2: only advance if the uren field parses. Illegal elsewhere = no-op. */\nexport function next(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 1) return s;\n const uren = parseUren(s.draft.uren);\n return uren.ok\n ? { ...s, step: 2, errors: {} }\n : { ...s, errors: { uren: uren.error } };\n}\n\nexport function back(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 2) return s;\n return { ...s, step: 1, errors: {} };\n}\n\n/** Step 2 submit: parse everything; move to Submitting only with Valid data. */\nexport function submit(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 2) return s;\n const result = validate(s.draft);\n return result.ok ? { tag: 'Submitting', data: result.value } : { ...s, errors: result.error };\n}\n\n/** Resolve the async submit. Only meaningful while Submitting. */\nexport function resolve(s: WizardState, r: Result): WizardState {\n if (s.tag !== 'Submitting') return s;\n return r.ok ? { tag: 'Submitted', data: s.data } : { tag: 'Failed', data: s.data, error: r.error };\n}\n\n/** Update one draft field while editing; ignored in any other state. */\nexport function setField(s: WizardState, key: keyof Draft, value: string): WizardState {\n if (s.tag !== 'Editing') return s;\n return { ...s, draft: { ...s.draft, [key]: value } };\n}\n\n/**\n * Every event that can happen to the wizard, as one message type. The component\n * sends a WizardMsg; `reduce` decides the next state. This is the Elm\n * Model+Msg+update pattern: ONE pure function describes all state changes.\n */\nexport type WizardMsg =\n | { tag: 'SetField'; key: keyof Draft; value: string }\n | { tag: 'Next' }\n | { tag: 'Back' }\n | { tag: 'Submit' }\n | { tag: 'Retry' }\n | { tag: 'SubmitConfirmed' }\n | { tag: 'SubmitFailed'; error: string }\n | { tag: 'Seed'; state: WizardState }; // mount a specific state (stories/showcase)\n\nexport function reduce(s: WizardState, m: WizardMsg): WizardState {\n switch (m.tag) {\n case 'SetField':\n return setField(s, m.key, m.value);\n case 'Next':\n return next(s);\n case 'Back':\n return back(s);\n case 'Submit':\n return submit(s);\n case 'Retry':\n return s.tag === 'Failed' ? { tag: 'Submitting', data: s.data } : s;\n case 'SubmitConfirmed':\n return s.tag === 'Submitting' ? { tag: 'Submitted', data: s.data } : s;\n case 'SubmitFailed':\n return s.tag === 'Submitting' ? { tag: 'Failed', data: s.data, error: m.error } : s;\n case 'Seed':\n return m.state;\n default:\n return assertNever(m);\n }\n}\n", + "sourceCode": "import { Result, assertNever } from '@shared/kernel/fp';\nimport { Uren, parseUren } from '@registratie/domain/value-objects/uren';\n\n/** What the user is typing (raw, possibly invalid). */\nexport interface Draft {\n uren: string;\n jaren: string;\n punten: string;\n}\n\n/** What we have AFTER parsing — branded/typed, guaranteed valid. */\nexport interface Valid {\n uren: Uren;\n jaren: number;\n punten: number;\n}\n\n/**\n * The whole wizard as one tagged union. `step` and `errors` exist ONLY while\n * Editing; Submitting/Submitted/Failed carry a `Valid` payload and nothing else.\n * So \"submitting while a field is invalid\" or \"showing the success screen with\n * errors set\" are unrepresentable — the bug class is gone by construction.\n */\nexport type WizardState =\n | { tag: 'Editing'; step: 1 | 2; draft: Draft; errors: Partial> }\n | { tag: 'Submitting'; data: Valid }\n | { tag: 'Submitted'; data: Valid }\n | { tag: 'Failed'; data: Valid; error: string };\n\nexport const initial: WizardState = { tag: 'Editing', step: 1, draft: { uren: '', jaren: '', punten: '' }, errors: {} };\n\n/** Parse every field; on success hand back a Valid, else the per-field errors. */\nfunction validate(draft: Draft): Result>, Valid> {\n const uren = parseUren(draft.uren);\n const jaren = parseUren(draft.jaren);\n const punten = parseUren(draft.punten);\n const errors: Partial> = {};\n if (!uren.ok) errors.uren = uren.error;\n if (!jaren.ok) errors.jaren = jaren.error;\n if (!punten.ok) errors.punten = punten.error;\n if (uren.ok && jaren.ok && punten.ok) {\n return { ok: true, value: { uren: uren.value, jaren: jaren.value, punten: punten.value } };\n }\n return { ok: false, error: errors };\n}\n\n/** Step 1 → 2: advance only when BOTH step-1 fields parse. Illegal elsewhere = no-op. */\nexport function next(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 1) return s;\n const uren = parseUren(s.draft.uren);\n const jaren = parseUren(s.draft.jaren);\n const errors: Partial> = {};\n if (!uren.ok) errors.uren = uren.error;\n if (!jaren.ok) errors.jaren = jaren.error;\n return Object.keys(errors).length === 0 ? { ...s, step: 2, errors: {} } : { ...s, errors };\n}\n\nexport function back(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 2) return s;\n return { ...s, step: 1, errors: {} };\n}\n\n/** Step 2 submit: parse everything; move to Submitting only with Valid data. */\nexport function submit(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 2) return s;\n const result = validate(s.draft);\n return result.ok ? { tag: 'Submitting', data: result.value } : { ...s, errors: result.error };\n}\n\n/** Resolve the async submit. Only meaningful while Submitting. */\nexport function resolve(s: WizardState, r: Result): WizardState {\n if (s.tag !== 'Submitting') return s;\n return r.ok ? { tag: 'Submitted', data: s.data } : { tag: 'Failed', data: s.data, error: r.error };\n}\n\n/** Update one draft field while editing; ignored in any other state. */\nexport function setField(s: WizardState, key: keyof Draft, value: string): WizardState {\n if (s.tag !== 'Editing') return s;\n return { ...s, draft: { ...s.draft, [key]: value } };\n}\n\n/**\n * Every event that can happen to the wizard, as one message type. The component\n * sends a WizardMsg; `reduce` decides the next state. This is the Elm\n * Model+Msg+update pattern: ONE pure function describes all state changes.\n */\nexport type WizardMsg =\n | { tag: 'SetField'; key: keyof Draft; value: string }\n | { tag: 'Next' }\n | { tag: 'Back' }\n | { tag: 'Submit' }\n | { tag: 'Retry' }\n | { tag: 'SubmitConfirmed' }\n | { tag: 'SubmitFailed'; error: string }\n | { tag: 'Seed'; state: WizardState }; // mount a specific state (stories/showcase)\n\nexport function reduce(s: WizardState, m: WizardMsg): WizardState {\n switch (m.tag) {\n case 'SetField':\n return setField(s, m.key, m.value);\n case 'Next':\n return next(s);\n case 'Back':\n return back(s);\n case 'Submit':\n return submit(s);\n case 'Retry':\n return s.tag === 'Failed' ? { tag: 'Submitting', data: s.data } : s;\n case 'SubmitConfirmed':\n return s.tag === 'Submitting' ? { tag: 'Submitted', data: s.data } : s;\n case 'SubmitFailed':\n return s.tag === 'Submitting' ? { tag: 'Failed', data: s.data, error: m.error } : s;\n case 'Seed':\n return m.state;\n default:\n return assertNever(m);\n }\n}\n", "properties": [ + { + "name": "jaren", + "deprecated": false, + "deprecationMessage": "", + "type": "number", + "indexKey": "", + "optional": false, + "description": "", + "line": 14 + }, { "name": "punten", "deprecated": false, @@ -460,7 +592,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 13 + "line": 15 }, { "name": "uren", @@ -470,7 +602,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 12 + "line": 13 } ], "indexSignatures": [], @@ -479,6 +611,83 @@ "rawdescription": "\nWhat we have AFTER parsing — branded/typed, guaranteed valid.", "methods": [], "extends": [] + }, + { + "name": "ValidIntake", + "id": "interface-ValidIntake-53c06ae2dad9470cd0c742a7e5903458a870e5eaaf70ce3043cec53935f09d2cb0d5f9980b723ce79be2460d550908bc894743c867abdf0974f17f7596cdd05d", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "interface", + "sourceCode": "import { Result, ok, err, assertNever } from '@shared/kernel/fp';\nimport { Uren, parseUren } from '@registratie/domain/value-objects/uren';\n\n/**\n * A BRANCHING wizard. Unlike herregistratie.machine (fixed 2 steps), the set of\n * steps here is NOT stored — it's *derived* from the answers by `visibleSteps`.\n * Answer \"buiten Nederland gewerkt? → ja\" and two extra steps appear; report few\n * hours and a scholing-question appears. The progress bar's denominator changes\n * as you type. \"Which question comes next\" is a pure function, so it's trivial\n * to test and impossible to get out of sync with the data.\n */\n\nexport type JaNee = 'ja' | 'nee';\n\n/** Every possible question, as a union — never a bare string. */\nexport type StepId = 'buitenland' | 'buitenlandDetails' | 'uren' | 'scholing' | 'punten' | 'review';\n\n/** One record carried across every step (and persisted). All optional: the user\n fills it in gradually, and branches may never ask some fields. */\nexport interface Answers {\n buitenlandGewerkt?: JaNee; // Q1\n land?: string; // Q1a — only when buitenlandGewerkt === 'ja'\n buitenlandseUren?: string; // Q1b — only when buitenlandGewerkt === 'ja'\n uren?: string; // Q2 — uren in NL\n scholingGevolgd?: JaNee; // Q3 — only when total hours are below the threshold\n punten?: string; // Q4\n}\n\n/** What we have after the review step parses — guaranteed valid/typed. */\nexport interface ValidIntake {\n werktBuitenland: boolean;\n land?: string;\n buitenlandseUren?: Uren;\n uren: Uren;\n aanvullendeScholing?: boolean;\n punten: Uren;\n}\n\n/** Below this many NL-hours we ask whether extra scholing was followed. */\nconst LAGE_UREN_DREMPEL = 1000;\n\nfunction lageUren(a: Answers): boolean {\n const r = parseUren(a.uren ?? '');\n return r.ok && r.value < LAGE_UREN_DREMPEL;\n}\n\n/**\n * THE branching, as one pure function. The step list is recomputed on every\n * transition, so changing an earlier answer immediately adds/removes later steps.\n */\nexport function visibleSteps(a: Answers): StepId[] {\n const steps: StepId[] = ['buitenland'];\n if (a.buitenlandGewerkt === 'ja') steps.push('buitenlandDetails');\n steps.push('uren');\n if (lageUren(a)) steps.push('scholing');\n steps.push('punten', 'review');\n return steps;\n}\n\nexport type IntakeState =\n | { tag: 'Answering'; answers: Answers; cursor: number; errors: Partial> }\n | { tag: 'Submitting'; data: ValidIntake }\n | { tag: 'Submitted'; data: ValidIntake }\n | { tag: 'Failed'; data: ValidIntake; error: string };\n\nexport const initial: IntakeState = { tag: 'Answering', answers: {}, cursor: 0, errors: {} };\n\n/** Which step the cursor currently points at (clamped to the live step list). */\nexport function currentStep(s: Extract): StepId {\n const steps = visibleSteps(s.answers);\n return steps[Math.min(s.cursor, steps.length - 1)];\n}\n\n/** Validate ONE step's fields. Returns the per-field error keyed by StepId. */\nfunction validateStep(step: StepId, a: Answers): Result>, void> {\n switch (step) {\n case 'buitenland':\n return a.buitenlandGewerkt ? ok(undefined) : err({ buitenland: 'Maak een keuze.' });\n case 'buitenlandDetails': {\n if (!a.land || a.land.trim() === '') return err({ buitenlandDetails: 'Vul een land in.' });\n const u = parseUren(a.buitenlandseUren ?? '');\n return u.ok ? ok(undefined) : err({ buitenlandDetails: u.error });\n }\n case 'uren': {\n const u = parseUren(a.uren ?? '');\n return u.ok ? ok(undefined) : err({ uren: u.error });\n }\n case 'scholing':\n return a.scholingGevolgd ? ok(undefined) : err({ scholing: 'Maak een keuze.' });\n case 'punten': {\n const p = parseUren(a.punten ?? '');\n return p.ok ? ok(undefined) : err({ punten: p.error });\n }\n case 'review':\n return ok(undefined); // review shows a summary; no own fields\n default:\n return assertNever(step);\n }\n}\n\n/** Parse the whole questionnaire into a ValidIntake (called on submit). */\nfunction validateAll(a: Answers): Result>, ValidIntake> {\n const errors: Partial> = {};\n for (const step of visibleSteps(a)) {\n const r = validateStep(step, a);\n if (!r.ok) Object.assign(errors, r.error);\n }\n if (Object.keys(errors).length > 0) return err(errors);\n\n const uren = parseUren(a.uren ?? '');\n const punten = parseUren(a.punten ?? '');\n // visibleSteps guaranteed these parse, but keep the compiler happy.\n if (!uren.ok || !punten.ok) return err(errors);\n\n const werktBuitenland = a.buitenlandGewerkt === 'ja';\n const buitenland = parseUren(a.buitenlandseUren ?? '');\n return ok({\n werktBuitenland,\n land: werktBuitenland ? a.land : undefined,\n buitenlandseUren: werktBuitenland && buitenland.ok ? buitenland.value : undefined,\n uren: uren.value,\n aanvullendeScholing: lageUren(a) ? a.scholingGevolgd === 'ja' : undefined,\n punten: punten.value,\n });\n}\n\nexport function setAnswer(s: IntakeState, key: keyof Answers, value: string): IntakeState {\n if (s.tag !== 'Answering') return s;\n const answers = { ...s.answers, [key]: value };\n // Editing an earlier answer can shrink the step list; clamp the cursor.\n const cursor = Math.min(s.cursor, visibleSteps(answers).length - 1);\n return { ...s, answers, cursor };\n}\n\nexport function next(s: IntakeState): IntakeState {\n if (s.tag !== 'Answering') return s;\n const r = validateStep(currentStep(s), s.answers);\n if (!r.ok) return { ...s, errors: r.error };\n const last = visibleSteps(s.answers).length - 1;\n return { ...s, cursor: Math.min(s.cursor + 1, last), errors: {} };\n}\n\nexport function back(s: IntakeState): IntakeState {\n if (s.tag !== 'Answering' || s.cursor === 0) return s;\n return { ...s, cursor: s.cursor - 1, errors: {} };\n}\n\nexport function submit(s: IntakeState): IntakeState {\n if (s.tag !== 'Answering') return s;\n const r = validateAll(s.answers);\n return r.ok ? { tag: 'Submitting', data: r.value } : { ...s, errors: r.error };\n}\n\nexport function resolve(s: IntakeState, r: Result): IntakeState {\n if (s.tag !== 'Submitting') return s;\n return r.ok ? { tag: 'Submitted', data: s.data } : { tag: 'Failed', data: s.data, error: r.error };\n}\n\nexport type IntakeMsg =\n | { tag: 'SetAnswer'; key: keyof Answers; value: string }\n | { tag: 'Next' }\n | { tag: 'Back' }\n | { tag: 'Submit' }\n | { tag: 'Retry' }\n | { tag: 'SubmitConfirmed' }\n | { tag: 'SubmitFailed'; error: string }\n | { tag: 'Seed'; state: IntakeState };\n\nexport function reduce(s: IntakeState, m: IntakeMsg): IntakeState {\n switch (m.tag) {\n case 'SetAnswer':\n return setAnswer(s, m.key, m.value);\n case 'Next':\n return next(s);\n case 'Back':\n return back(s);\n case 'Submit':\n return submit(s);\n case 'Retry':\n return s.tag === 'Failed' ? { tag: 'Submitting', data: s.data } : s;\n case 'SubmitConfirmed':\n return s.tag === 'Submitting' ? { tag: 'Submitted', data: s.data } : s;\n case 'SubmitFailed':\n return s.tag === 'Submitting' ? { tag: 'Failed', data: s.data, error: m.error } : s;\n case 'Seed':\n return m.state;\n default:\n return assertNever(m);\n }\n}\n", + "properties": [ + { + "name": "aanvullendeScholing", + "deprecated": false, + "deprecationMessage": "", + "type": "boolean", + "indexKey": "", + "optional": true, + "description": "", + "line": 35 + }, + { + "name": "buitenlandseUren", + "deprecated": false, + "deprecationMessage": "", + "type": "Uren", + "indexKey": "", + "optional": true, + "description": "", + "line": 33 + }, + { + "name": "land", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": true, + "description": "", + "line": 32 + }, + { + "name": "punten", + "deprecated": false, + "deprecationMessage": "", + "type": "Uren", + "indexKey": "", + "optional": false, + "description": "", + "line": 36 + }, + { + "name": "uren", + "deprecated": false, + "deprecationMessage": "", + "type": "Uren", + "indexKey": "", + "optional": false, + "description": "", + "line": 34 + }, + { + "name": "werktBuitenland", + "deprecated": false, + "deprecationMessage": "", + "type": "boolean", + "indexKey": "", + "optional": false, + "description": "", + "line": 31 + } + ], + "indexSignatures": [], + "kind": 172, + "description": "

What we have after the review step parses — guaranteed valid/typed.

\n", + "rawdescription": "\nWhat we have after the review step parses — guaranteed valid/typed.", + "methods": [], + "extends": [] } ], "injectables": [ @@ -1636,7 +1845,7 @@ }, { "name": "ConceptsPage", - "id": "component-ConceptsPage-951fb54ea356331be584db1248fbc5d516618c3b5b9f960359a4a642e7d9ce7d4d240852a31bbbcfea0e0bec6c0feeb398ae79c181480422a7477c3b600c88a5", + "id": "component-ConceptsPage-ab97eabf19e01429585206b9d60c4a9a576f262daa6eda12eef4bcb48a74a35c2a95cd9a4a5468e3367917201f918965ed03b6fdcbc2a0eca655f2d00a72f209", "file": "src/app/showcase/concepts.page.ts", "encapsulation": [], "entryComponents": [], @@ -1646,9 +1855,9 @@ "selector": "app-concepts-page", "styleUrls": [], "styles": [ - "\n .cols { display:flex; flex-wrap:wrap; gap:1.5rem; margin:1rem 0 2.5rem }\n .col { flex:1 1 20rem; min-width:18rem }\n .tag { font-weight:700; font-size:0.8rem; text-transform:uppercase; letter-spacing:0.04em }\n .bad { color:var(--rhc-color-rood-500) }\n .good { color:var(--rhc-color-groen-500) }\n pre { background:var(--rhc-color-grijs-100,#f3f3f3); padding:1rem; border-radius:4px; overflow:auto; font-size:0.85rem }\n " + "\n .section { margin: 0 0 3rem }\n .lead { color: var(--rhc-color-grijs-700); max-width: 46rem; margin: 0.25rem 0 1.25rem }\n .cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); gap: 1.5rem; align-items: start }\n .card { border: 1px solid var(--rhc-color-grijs-200, #e5e5e5); border-radius: 10px; padding: 1.25rem; background: #fff }\n .card--bad { border-color: var(--rhc-color-rood-300, #f0b4b4) }\n .card--good { border-color: var(--rhc-color-groen-300, #b4e0b4) }\n .tag { display: inline-flex; align-items: center; gap: 0.4rem; font-weight: 700; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; margin: 0 0 0.75rem }\n .tag::before { content: ''; width: 0.6rem; height: 0.6rem; border-radius: 50% }\n .tag.bad { color: var(--rhc-color-rood-600, #a30000) } .tag.bad::before { background: var(--rhc-color-rood-500, #d52b1e) }\n .tag.good { color: var(--rhc-color-groen-700, #277337) } .tag.good::before { background: var(--rhc-color-groen-500, #39870c) }\n .tag.plain { color: var(--rhc-color-grijs-700) } .tag.plain::before { display: none }\n pre { background: #1e2430; color: #e6e9ef; padding: 1rem; border-radius: 8px; overflow: auto; font-size: 0.82rem; line-height: 1.55; margin: 0 }\n pre .k { color: #c792ea } pre .s { color: #c3e88d } pre .c { color: #7e8aa0; font-style: italic }\n .note { font-size: 0.9rem; color: var(--rhc-color-grijs-700); margin: 0.75rem 0 0 }\n /* live state diagram */\n .machine { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0 0 1rem }\n .node { padding: 0.4rem 0.8rem; border-radius: 999px; border: 1px solid var(--rhc-color-grijs-300, #ccc); font-size: 0.82rem; color: var(--rhc-color-grijs-700); transition: all .15s }\n .node.on { background: var(--rhc-color-hemelblauw-100, #e5f1fb); border-color: var(--rhc-color-hemelblauw-500, #007bc7); color: var(--rhc-color-hemelblauw-700, #00567d); font-weight: 700 }\n .steplist { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; margin: 0 0 1rem }\n .pill { padding: 0.3rem 0.7rem; border-radius: 8px; background: var(--rhc-color-grijs-100, #f3f3f3); font-size: 0.8rem }\n .pill.extra { background: var(--rhc-color-geel-100, #fff6d6); border: 1px dashed var(--rhc-color-geel-600, #c79a00) }\n .arrow { color: var(--rhc-color-grijs-400, #999) }\n " ], - "template": "\n \n Vier functionele patronen die met atomic design makkelijker te tonen zijn — telkens \"fout\"\n (de oude vorm liet het toe) naast \"goed\" (het type maakt het onmogelijk).\n \n\n \n 1 · Discriminated unions\n
\n
\n

Fout — vlakke interface

\n
{{ unionBad }}
\n

Een doorgehaalde registratie houdt tóch een herregistratiedatum: onmogelijke toestand.

\n
\n
\n

Goed — sum type

\n \n

De variant Doorgehaald kent geen herregistratiedatum, dus de rij bestaat simpelweg niet.

\n
\n
\n\n \n 2 · RemoteData fold\n
\n
\n

Eén molecuul, vier elkaar uitsluitende toestanden

\n

Loading

\n {{ v }}\n

Empty

\n {{ v }}\n

Failure

\n {{ v }}\n

Success

\n
    @for (i of v; track i) {
  • {{ i }}
  • }
\n
\n
\n

De exhaustieve fold

\n
{{ foldCode }}
\n

Een nieuwe variant toevoegen breekt de compile via assertNever tot je hem afhandelt.

\n
\n
\n\n \n 3 · Parse, don't validate\n
\n
\n

Smart constructor → Result

\n \n
\n
\n @if (parsed().ok) {\n

ok

\n
Postcode = \"{{ parsed().ok ? $any(parsed()).value : '' }}\"
\n

Een gevalideerde Postcode is een ander type dan een ruwe string.

\n } @else {\n

err

\n
{{ $any(parsed()).error }}
\n }\n
\n
\n\n \n 4 · Form als state machine\n
\n
\n

Fout — losse booleans

\n
{{ machineBad }}
\n

Niets verhindert \"submitting\" mét validatiefouten of een successcherm met errors.

\n
\n
\n

Goed — één tagged union stuurt de UI

\n \n
\n
\n
\n", + "template": "\n

\n Vijf functionele patronen die atomic design makkelijker maakt om te tonen — telkens\n \"fout\" (de oude vorm liet het toe) naast \"goed\" (het type maakt het onmogelijk).\n

\n\n \n
\n 1 · Discriminated unions\n

Laat elke variant precies de gegevens dragen die kloppen — niets meer.

\n
\n
\n

Fout — vlakke interface

\n
\n        

Een doorgehaalde registratie houdt tóch een herregistratiedatum: onmogelijke toestand.

\n
\n
\n

Goed — sum type

\n \n

De variant Doorgehaald kent geen herregistratiedatum, dus de rij bestaat simpelweg niet.

\n
\n
\n
\n\n \n
\n 2 · RemoteData fold\n

Eén waarde met vier elkaar uitsluitende toestanden in plaats van drie losse booleans.

\n
\n
\n

Vier toestanden, één molecuul

\n

Loading

\n {{ v }}\n

Empty

\n {{ v }}\n

Failure

\n {{ v }}\n

Success

\n
    @for (i of v; track i) {
  • {{ i }}
  • }
\n
\n
\n

De exhaustieve fold

\n
\n        

Een nieuwe variant toevoegen breekt de compile via assertNever tot je hem afhandelt.

\n
\n
\n
\n\n \n
\n 3 · Parse, don't validate\n

Na het parsen onthoudt het type dat de waarde geldig is.

\n
\n
\n

Smart constructor → Result

\n \n
\n
\n @if (parsed().ok) {\n

ok

\n
Postcode = \"{{ $any(parsed()).value }}\"
\n

Een gevalideerde Postcode is een ander type dan een ruwe string.

\n } @else {\n

err

\n
{{ $any(parsed()).error }}
\n }\n
\n
\n
\n\n \n
\n 4 · Form als state machine\n

Eén tagged union stuurt de UI. Speel met de wizard — de gemarkeerde toestand is de huidige.

\n
\n
\n

Fout — losse booleans

\n
\n        

Niets verhindert \"submitting\" mét validatiefouten of een successcherm met errors.

\n
\n
\n

Goed — één tagged union

\n
\n @for (n of ['Editing','Submitting','Submitted','Failed']; track n) {\n {{ n }}\n }\n
\n \n
\n
\n
\n\n \n
\n 5 · Vertakkende vragenlijst — \"afleiden, niet opslaan\"\n

\n Welke stappen bestaan is geen opgeslagen toestand maar een pure functie van de antwoorden\n (visibleSteps(answers)). Antwoord \"ja\" op buitenland of vul weinig uren in, en de\n lijst hieronder groeit mee.\n

\n
\n
\n

Live afgeleide stappen

\n
\n @for (s of iw.steps(); track s; let last = $last) {\n {{ s }}\n @if (!last) { }\n }\n
\n

De gestippelde stappen verschijnen alleen door eerdere antwoorden. De voortgang \"van N\" verandert mee.

\n
\n
\n

De wizard

\n \n
\n
\n
\n
\n", "templateUrl": [], "viewProviders": [], "hostDirectives": [], @@ -1664,7 +1873,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 117 + "line": 176 }, { "name": "emptyRes", @@ -1675,7 +1884,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 124 + "line": 183 }, { "name": "errorRes", @@ -1686,18 +1895,18 @@ "indexKey": "", "optional": false, "description": "", - "line": 125 + "line": 184 }, { "name": "foldCode", - "defaultValue": "`foldRemote(rd, {\n loading: () => spinner,\n empty: () => 'geen data',\n failure: (e) => alert(e),\n success: (v) => render(v),\n}); // mist er één → compile-fout`", + "defaultValue": "`foldRemote(rd, {\n loading: () => spinner,\n empty: () => 'geen data',\n failure: (e) => alert(e),\n success: (v) => render(v),\n}); // mist er één → compile-fout`", "deprecated": false, "deprecationMessage": "", "type": "unknown", "indexKey": "", "optional": false, "description": "", - "line": 136 + "line": 195 }, { "name": "isEmpty", @@ -1708,7 +1917,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 115 + "line": 174 }, { "name": "loadingRes", @@ -1719,18 +1928,18 @@ "indexKey": "", "optional": false, "description": "", - "line": 123 + "line": 182 }, { "name": "machineBad", - "defaultValue": "`submitting = signal(false);\nsubmitted = signal(false);\nerrors = signal<...>({});\n// submitting === true && errors.size > 0 ? 🤷`", + "defaultValue": "`submitting = signal(false);\nsubmitted = signal(false);\nerrors = signal<...>({});\n// submitting === true && errors.size > 0 ? 🤷`", "deprecated": false, "deprecationMessage": "", "type": "unknown", "indexKey": "", "optional": false, "description": "", - "line": 143 + "line": 202 }, { "name": "parsed", @@ -1741,7 +1950,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 129 + "line": 188 }, { "name": "raw", @@ -1752,7 +1961,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 128 + "line": 187 }, { "name": "successRes", @@ -1763,18 +1972,18 @@ "indexKey": "", "optional": false, "description": "", - "line": 126 + "line": 185 }, { "name": "unionBad", - "defaultValue": "`interface Registration {\n status: 'Geregistreerd' | 'Doorgehaald';\n herregistratieDatum: string; // altijd aanwezig 😬\n}`", + "defaultValue": "`interface Registration {\n status: 'Geregistreerd' | 'Doorgehaald';\n herregistratieDatum: string; // altijd aanwezig 😬\n}`", "deprecated": false, "deprecationMessage": "", "type": "unknown", "indexKey": "", "optional": false, "description": "", - "line": 131 + "line": 190 } ], "methodsClass": [], @@ -1796,10 +2005,6 @@ "name": "HeadingComponent", "type": "component" }, - { - "name": "AlertComponent", - "type": "component" - }, { "name": "TextInputComponent", "type": "component" @@ -1818,20 +2023,24 @@ { "name": "HerregistratieWizardComponent", "type": "component" + }, + { + "name": "IntakeWizardComponent", + "type": "component" } ], "description": "

Teaching showcase: each section pairs the impossible-state-permitting "before"\nwith the "after" where the type system rules it out. Composition-only.

\n", "rawdescription": "\nTeaching showcase: each section pairs the impossible-state-permitting \"before\"\nwith the \"after\" where the type system rules it out. Composition-only.", "type": "component", - "sourceCode": "import { Component, computed, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport type { Resource } from '@angular/core';\nimport { PageShellComponent } from '@shared/layout/page-shell/page-shell.component';\nimport { HeadingComponent } from '@shared/ui/heading/heading.component';\nimport { AlertComponent } from '@shared/ui/alert/alert.component';\nimport { TextInputComponent } from '@shared/ui/text-input/text-input.component';\nimport { ASYNC } from '@shared/ui/async/async.component';\nimport { SkeletonComponent } from '@shared/ui/skeleton/skeleton.component';\nimport { RegistrationSummaryComponent } from '@registratie/ui/registration-summary/registration-summary.component';\nimport { HerregistratieWizardComponent } from '@herregistratie/ui/herregistratie-wizard/herregistratie-wizard.component';\nimport { Registration } from '@registratie/domain/registration';\nimport { parsePostcode } from '@registratie/domain/value-objects/postcode';\n\n/** Minimal fake Resource so can be driven through every state without HTTP. */\nfunction fakeResource(status: string, value?: T, error?: Error): Resource {\n return { value: () => value as T, status: () => status, error: () => error, hasValue: () => value !== undefined, reload: () => {} } as unknown as Resource;\n}\n\n/** Teaching showcase: each section pairs the impossible-state-permitting \"before\"\n with the \"after\" where the type system rules it out. Composition-only. */\n@Component({\n selector: 'app-concepts-page',\n imports: [\n FormsModule, PageShellComponent, HeadingComponent, AlertComponent, TextInputComponent,\n ...ASYNC, SkeletonComponent, RegistrationSummaryComponent, HerregistratieWizardComponent,\n ],\n styles: [`\n .cols { display:flex; flex-wrap:wrap; gap:1.5rem; margin:1rem 0 2.5rem }\n .col { flex:1 1 20rem; min-width:18rem }\n .tag { font-weight:700; font-size:0.8rem; text-transform:uppercase; letter-spacing:0.04em }\n .bad { color:var(--rhc-color-rood-500) }\n .good { color:var(--rhc-color-groen-500) }\n pre { background:var(--rhc-color-grijs-100,#f3f3f3); padding:1rem; border-radius:4px; overflow:auto; font-size:0.85rem }\n `],\n template: `\n \n \n Vier functionele patronen die met atomic design makkelijker te tonen zijn — telkens \"fout\"\n (de oude vorm liet het toe) naast \"goed\" (het type maakt het onmogelijk).\n \n\n \n 1 · Discriminated unions\n
\n
\n

Fout — vlakke interface

\n
{{ unionBad }}
\n

Een doorgehaalde registratie houdt tóch een herregistratiedatum: onmogelijke toestand.

\n
\n
\n

Goed — sum type

\n \n

De variant Doorgehaald kent geen herregistratiedatum, dus de rij bestaat simpelweg niet.

\n
\n
\n\n \n 2 · RemoteData fold\n
\n
\n

Eén molecuul, vier elkaar uitsluitende toestanden

\n

Loading

\n {{ v }}\n

Empty

\n {{ v }}\n

Failure

\n {{ v }}\n

Success

\n
    @for (i of v; track i) {
  • {{ i }}
  • }
\n
\n
\n

De exhaustieve fold

\n
{{ foldCode }}
\n

Een nieuwe variant toevoegen breekt de compile via assertNever tot je hem afhandelt.

\n
\n
\n\n \n 3 · Parse, don't validate\n
\n
\n

Smart constructor → Result

\n \n
\n
\n @if (parsed().ok) {\n

ok

\n
Postcode = \"{{ parsed().ok ? $any(parsed()).value : '' }}\"
\n

Een gevalideerde Postcode is een ander type dan een ruwe string.

\n } @else {\n

err

\n
{{ $any(parsed()).error }}
\n }\n
\n
\n\n \n 4 · Form als state machine\n
\n
\n

Fout — losse booleans

\n
{{ machineBad }}
\n

Niets verhindert \"submitting\" mét validatiefouten of een successcherm met errors.

\n
\n
\n

Goed — één tagged union stuurt de UI

\n \n
\n
\n
\n `,\n})\nexport class ConceptsPage {\n isEmpty = (v: string[]) => !v || v.length === 0;\n\n doorgehaald: Registration = {\n bigNummer: '19012345601', naam: 'Dr. A. (Anna) de Vries', beroep: 'Arts',\n registratiedatum: '2012-09-01', geboortedatum: '1985-03-14',\n status: { tag: 'Doorgehaald', doorgehaaldOp: '2024-05-01', reden: 'Op eigen verzoek' },\n };\n\n loadingRes = fakeResource('loading');\n emptyRes = fakeResource('resolved', []);\n errorRes = fakeResource('error', undefined, new Error('Demo'));\n successRes = fakeResource('resolved', ['Huisartsgeneeskunde', 'Spoedeisende hulp']);\n\n raw = signal('');\n parsed = computed(() => parsePostcode(this.raw()));\n\n unionBad = `interface Registration {\n status: 'Geregistreerd' | 'Doorgehaald';\n herregistratieDatum: string; // altijd aanwezig 😬\n}`;\n\n foldCode = `foldRemote(rd, {\n loading: () => spinner,\n empty: () => 'geen data',\n failure: (e) => alert(e),\n success: (v) => render(v),\n}); // mist er één → compile-fout`;\n\n machineBad = `submitting = signal(false);\nsubmitted = signal(false);\nerrors = signal<...>({});\n// submitting === true && errors.size > 0 ? 🤷`;\n}\n", + "sourceCode": "import { Component, computed, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport type { Resource } from '@angular/core';\nimport { PageShellComponent } from '@shared/layout/page-shell/page-shell.component';\nimport { HeadingComponent } from '@shared/ui/heading/heading.component';\nimport { TextInputComponent } from '@shared/ui/text-input/text-input.component';\nimport { ASYNC } from '@shared/ui/async/async.component';\nimport { SkeletonComponent } from '@shared/ui/skeleton/skeleton.component';\nimport { RegistrationSummaryComponent } from '@registratie/ui/registration-summary/registration-summary.component';\nimport { HerregistratieWizardComponent } from '@herregistratie/ui/herregistratie-wizard/herregistratie-wizard.component';\nimport { IntakeWizardComponent } from '@herregistratie/ui/intake-wizard/intake-wizard.component';\nimport { Registration } from '@registratie/domain/registration';\nimport { parsePostcode } from '@registratie/domain/value-objects/postcode';\n\n/** Minimal fake Resource so can be driven through every state without HTTP. */\nfunction fakeResource(status: string, value?: T, error?: Error): Resource {\n return { value: () => value as T, status: () => status, error: () => error, hasValue: () => value !== undefined, reload: () => {} } as unknown as Resource;\n}\n\n/** Teaching showcase: each section pairs the impossible-state-permitting \"before\"\n with the \"after\" where the type system rules it out. Composition-only. */\n@Component({\n selector: 'app-concepts-page',\n imports: [\n FormsModule, PageShellComponent, HeadingComponent, TextInputComponent,\n ...ASYNC, SkeletonComponent, RegistrationSummaryComponent, HerregistratieWizardComponent, IntakeWizardComponent,\n ],\n styles: [`\n .section { margin: 0 0 3rem }\n .lead { color: var(--rhc-color-grijs-700); max-width: 46rem; margin: 0.25rem 0 1.25rem }\n .cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); gap: 1.5rem; align-items: start }\n .card { border: 1px solid var(--rhc-color-grijs-200, #e5e5e5); border-radius: 10px; padding: 1.25rem; background: #fff }\n .card--bad { border-color: var(--rhc-color-rood-300, #f0b4b4) }\n .card--good { border-color: var(--rhc-color-groen-300, #b4e0b4) }\n .tag { display: inline-flex; align-items: center; gap: 0.4rem; font-weight: 700; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; margin: 0 0 0.75rem }\n .tag::before { content: ''; width: 0.6rem; height: 0.6rem; border-radius: 50% }\n .tag.bad { color: var(--rhc-color-rood-600, #a30000) } .tag.bad::before { background: var(--rhc-color-rood-500, #d52b1e) }\n .tag.good { color: var(--rhc-color-groen-700, #277337) } .tag.good::before { background: var(--rhc-color-groen-500, #39870c) }\n .tag.plain { color: var(--rhc-color-grijs-700) } .tag.plain::before { display: none }\n pre { background: #1e2430; color: #e6e9ef; padding: 1rem; border-radius: 8px; overflow: auto; font-size: 0.82rem; line-height: 1.55; margin: 0 }\n pre .k { color: #c792ea } pre .s { color: #c3e88d } pre .c { color: #7e8aa0; font-style: italic }\n .note { font-size: 0.9rem; color: var(--rhc-color-grijs-700); margin: 0.75rem 0 0 }\n /* live state diagram */\n .machine { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0 0 1rem }\n .node { padding: 0.4rem 0.8rem; border-radius: 999px; border: 1px solid var(--rhc-color-grijs-300, #ccc); font-size: 0.82rem; color: var(--rhc-color-grijs-700); transition: all .15s }\n .node.on { background: var(--rhc-color-hemelblauw-100, #e5f1fb); border-color: var(--rhc-color-hemelblauw-500, #007bc7); color: var(--rhc-color-hemelblauw-700, #00567d); font-weight: 700 }\n .steplist { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; margin: 0 0 1rem }\n .pill { padding: 0.3rem 0.7rem; border-radius: 8px; background: var(--rhc-color-grijs-100, #f3f3f3); font-size: 0.8rem }\n .pill.extra { background: var(--rhc-color-geel-100, #fff6d6); border: 1px dashed var(--rhc-color-geel-600, #c79a00) }\n .arrow { color: var(--rhc-color-grijs-400, #999) }\n `],\n template: `\n \n

\n Vijf functionele patronen die atomic design makkelijker maakt om te tonen — telkens\n \"fout\" (de oude vorm liet het toe) naast \"goed\" (het type maakt het onmogelijk).\n

\n\n \n
\n 1 · Discriminated unions\n

Laat elke variant precies de gegevens dragen die kloppen — niets meer.

\n
\n
\n

Fout — vlakke interface

\n
\n            

Een doorgehaalde registratie houdt tóch een herregistratiedatum: onmogelijke toestand.

\n
\n
\n

Goed — sum type

\n \n

De variant Doorgehaald kent geen herregistratiedatum, dus de rij bestaat simpelweg niet.

\n
\n
\n
\n\n \n
\n 2 · RemoteData fold\n

Eén waarde met vier elkaar uitsluitende toestanden in plaats van drie losse booleans.

\n
\n
\n

Vier toestanden, één molecuul

\n

Loading

\n {{ v }}\n

Empty

\n {{ v }}\n

Failure

\n {{ v }}\n

Success

\n
    @for (i of v; track i) {
  • {{ i }}
  • }
\n
\n
\n

De exhaustieve fold

\n
\n            

Een nieuwe variant toevoegen breekt de compile via assertNever tot je hem afhandelt.

\n
\n
\n
\n\n \n
\n 3 · Parse, don't validate\n

Na het parsen onthoudt het type dat de waarde geldig is.

\n
\n
\n

Smart constructor → Result

\n \n
\n
\n @if (parsed().ok) {\n

ok

\n
Postcode = \"{{ $any(parsed()).value }}\"
\n

Een gevalideerde Postcode is een ander type dan een ruwe string.

\n } @else {\n

err

\n
{{ $any(parsed()).error }}
\n }\n
\n
\n
\n\n \n
\n 4 · Form als state machine\n

Eén tagged union stuurt de UI. Speel met de wizard — de gemarkeerde toestand is de huidige.

\n
\n
\n

Fout — losse booleans

\n
\n            

Niets verhindert \"submitting\" mét validatiefouten of een successcherm met errors.

\n
\n
\n

Goed — één tagged union

\n
\n @for (n of ['Editing','Submitting','Submitted','Failed']; track n) {\n {{ n }}\n }\n
\n \n
\n
\n
\n\n \n
\n 5 · Vertakkende vragenlijst — \"afleiden, niet opslaan\"\n

\n Welke stappen bestaan is geen opgeslagen toestand maar een pure functie van de antwoorden\n (visibleSteps(answers)). Antwoord \"ja\" op buitenland of vul weinig uren in, en de\n lijst hieronder groeit mee.\n

\n
\n
\n

Live afgeleide stappen

\n
\n @for (s of iw.steps(); track s; let last = $last) {\n {{ s }}\n @if (!last) { }\n }\n
\n

De gestippelde stappen verschijnen alleen door eerdere antwoorden. De voortgang \"van N\" verandert mee.

\n
\n
\n

De wizard

\n \n
\n
\n
\n
\n `,\n})\nexport class ConceptsPage {\n isEmpty = (v: string[]) => !v || v.length === 0;\n\n doorgehaald: Registration = {\n bigNummer: '19012345601', naam: 'Dr. A. (Anna) de Vries', beroep: 'Arts',\n registratiedatum: '2012-09-01', geboortedatum: '1985-03-14',\n status: { tag: 'Doorgehaald', doorgehaaldOp: '2024-05-01', reden: 'Op eigen verzoek' },\n };\n\n loadingRes = fakeResource('loading');\n emptyRes = fakeResource('resolved', []);\n errorRes = fakeResource('error', undefined, new Error('Demo'));\n successRes = fakeResource('resolved', ['Huisartsgeneeskunde', 'Spoedeisende hulp']);\n\n raw = signal('');\n parsed = computed(() => parsePostcode(this.raw()));\n\n unionBad = `interface Registration {\n status: 'Geregistreerd' | 'Doorgehaald';\n herregistratieDatum: string; // altijd aanwezig 😬\n}`;\n\n foldCode = `foldRemote(rd, {\n loading: () => spinner,\n empty: () => 'geen data',\n failure: (e) => alert(e),\n success: (v) => render(v),\n}); // mist er één → compile-fout`;\n\n machineBad = `submitting = signal(false);\nsubmitted = signal(false);\nerrors = signal<...>({});\n// submitting === true && errors.size > 0 ? 🤷`;\n}\n", "assetsDirs": [], "styleUrlsData": "", - "stylesData": "\n .cols { display:flex; flex-wrap:wrap; gap:1.5rem; margin:1rem 0 2.5rem }\n .col { flex:1 1 20rem; min-width:18rem }\n .tag { font-weight:700; font-size:0.8rem; text-transform:uppercase; letter-spacing:0.04em }\n .bad { color:var(--rhc-color-rood-500) }\n .good { color:var(--rhc-color-groen-500) }\n pre { background:var(--rhc-color-grijs-100,#f3f3f3); padding:1rem; border-radius:4px; overflow:auto; font-size:0.85rem }\n \n", + "stylesData": "\n .section { margin: 0 0 3rem }\n .lead { color: var(--rhc-color-grijs-700); max-width: 46rem; margin: 0.25rem 0 1.25rem }\n .cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); gap: 1.5rem; align-items: start }\n .card { border: 1px solid var(--rhc-color-grijs-200, #e5e5e5); border-radius: 10px; padding: 1.25rem; background: #fff }\n .card--bad { border-color: var(--rhc-color-rood-300, #f0b4b4) }\n .card--good { border-color: var(--rhc-color-groen-300, #b4e0b4) }\n .tag { display: inline-flex; align-items: center; gap: 0.4rem; font-weight: 700; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; margin: 0 0 0.75rem }\n .tag::before { content: ''; width: 0.6rem; height: 0.6rem; border-radius: 50% }\n .tag.bad { color: var(--rhc-color-rood-600, #a30000) } .tag.bad::before { background: var(--rhc-color-rood-500, #d52b1e) }\n .tag.good { color: var(--rhc-color-groen-700, #277337) } .tag.good::before { background: var(--rhc-color-groen-500, #39870c) }\n .tag.plain { color: var(--rhc-color-grijs-700) } .tag.plain::before { display: none }\n pre { background: #1e2430; color: #e6e9ef; padding: 1rem; border-radius: 8px; overflow: auto; font-size: 0.82rem; line-height: 1.55; margin: 0 }\n pre .k { color: #c792ea } pre .s { color: #c3e88d } pre .c { color: #7e8aa0; font-style: italic }\n .note { font-size: 0.9rem; color: var(--rhc-color-grijs-700); margin: 0.75rem 0 0 }\n /* live state diagram */\n .machine { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0 0 1rem }\n .node { padding: 0.4rem 0.8rem; border-radius: 999px; border: 1px solid var(--rhc-color-grijs-300, #ccc); font-size: 0.82rem; color: var(--rhc-color-grijs-700); transition: all .15s }\n .node.on { background: var(--rhc-color-hemelblauw-100, #e5f1fb); border-color: var(--rhc-color-hemelblauw-500, #007bc7); color: var(--rhc-color-hemelblauw-700, #00567d); font-weight: 700 }\n .steplist { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; margin: 0 0 1rem }\n .pill { padding: 0.3rem 0.7rem; border-radius: 8px; background: var(--rhc-color-grijs-100, #f3f3f3); font-size: 0.8rem }\n .pill.extra { background: var(--rhc-color-geel-100, #fff6d6); border: 1px dashed var(--rhc-color-geel-600, #c79a00) }\n .arrow { color: var(--rhc-color-grijs-400, #999) }\n \n", "extends": [] }, { "name": "DashboardPage", - "id": "component-DashboardPage-3876b122573c06608aa1fa82196e04af53fa3720906e138b095aa46a13efbd18be0b42e0a6024ee8df168faecfbf50c6a9ad9a5a7afff5447fec52dcafc73d49", + "id": "component-DashboardPage-9c8e05a1223f1fa7aa977bcae5c26df7680a5a86891cd2ceb4c36e67c4c89752b060e1ca473bf27eff342208371a1bfc6a565718d678a77e501f3a8adefc5876", "file": "src/app/registratie/ui/dashboard.page.ts", "encapsulation": [], "entryComponents": [], @@ -1841,7 +2050,7 @@ "selector": "app-dashboard-page", "styleUrls": [], "styles": [], - "template": "\n @if (store.pendingHerregistratie()) {\n Uw herregistratie-aanvraag is in behandeling.\n }\n\n \n \n \n \n
\n Persoonsgegevens (BRP)\n
\n \n \n \n
\n
\n
\n \n \n \n
\n\n
\n Specialismen en aantekeningen\n \n \n \n \n \n \n \n \n

U heeft nog geen specialismen of aantekeningen.

\n
\n
\n
\n\n

\n Gegevens bekijken of een wijziging doorgeven →\n

\n

\n Herregistratie aanvragen →\n

\n

\n Functionele patronen (impossible states) →\n

\n
\n", + "template": "\n @if (store.pendingHerregistratie()) {\n Uw herregistratie-aanvraag is in behandeling.\n }\n\n \n \n \n \n
\n Persoonsgegevens (BRP)\n
\n \n \n \n
\n
\n
\n \n \n \n
\n\n
\n Specialismen en aantekeningen\n \n \n \n \n \n \n \n \n

U heeft nog geen specialismen of aantekeningen.

\n
\n
\n
\n\n

\n Gegevens bekijken of een wijziging doorgeven →\n

\n

\n Herregistratie aanvragen →\n

\n

\n Herregistratie-intake (vragenlijst met vertakkingen) →\n

\n

\n Functionele patronen (impossible states) →\n

\n
\n", "templateUrl": [], "viewProviders": [], "hostDirectives": [], @@ -1857,7 +2066,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 71, + "line": 74, "modifierKind": [ 124 ] @@ -1909,7 +2118,7 @@ "description": "", "rawdescription": "\n", "type": "component", - "sourceCode": "import { Component, inject } from '@angular/core';\nimport { PageShellComponent } from '@shared/layout/page-shell/page-shell.component';\nimport { HeadingComponent } from '@shared/ui/heading/heading.component';\nimport { LinkComponent } from '@shared/ui/link/link.component';\nimport { AlertComponent } from '@shared/ui/alert/alert.component';\nimport { SkeletonComponent } from '@shared/ui/skeleton/skeleton.component';\nimport { DataRowComponent } from '@shared/ui/data-row/data-row.component';\nimport { ASYNC } from '@shared/ui/async/async.component';\nimport { RegistrationSummaryComponent } from '@registratie/ui/registration-summary/registration-summary.component';\nimport { RegistrationTableComponent } from '@registratie/ui/registration-table/registration-table.component';\nimport { BigProfileStore } from '@registratie/application/big-profile.store';\n\n@Component({\n selector: 'app-dashboard-page',\n imports: [\n PageShellComponent, HeadingComponent, LinkComponent, AlertComponent, SkeletonComponent,\n DataRowComponent, ...ASYNC, RegistrationSummaryComponent, RegistrationTableComponent,\n ],\n template: `\n \n @if (store.pendingHerregistratie()) {\n Uw herregistratie-aanvraag is in behandeling.\n }\n\n \n \n \n \n
\n Persoonsgegevens (BRP)\n
\n \n \n \n
\n
\n
\n \n \n \n
\n\n
\n Specialismen en aantekeningen\n \n \n \n \n \n \n \n \n

U heeft nog geen specialismen of aantekeningen.

\n
\n
\n
\n\n

\n Gegevens bekijken of een wijziging doorgeven →\n

\n

\n Herregistratie aanvragen →\n

\n

\n Functionele patronen (impossible states) →\n

\n
\n `,\n})\nexport class DashboardPage {\n protected store = inject(BigProfileStore);\n}\n", + "sourceCode": "import { Component, inject } from '@angular/core';\nimport { PageShellComponent } from '@shared/layout/page-shell/page-shell.component';\nimport { HeadingComponent } from '@shared/ui/heading/heading.component';\nimport { LinkComponent } from '@shared/ui/link/link.component';\nimport { AlertComponent } from '@shared/ui/alert/alert.component';\nimport { SkeletonComponent } from '@shared/ui/skeleton/skeleton.component';\nimport { DataRowComponent } from '@shared/ui/data-row/data-row.component';\nimport { ASYNC } from '@shared/ui/async/async.component';\nimport { RegistrationSummaryComponent } from '@registratie/ui/registration-summary/registration-summary.component';\nimport { RegistrationTableComponent } from '@registratie/ui/registration-table/registration-table.component';\nimport { BigProfileStore } from '@registratie/application/big-profile.store';\n\n@Component({\n selector: 'app-dashboard-page',\n imports: [\n PageShellComponent, HeadingComponent, LinkComponent, AlertComponent, SkeletonComponent,\n DataRowComponent, ...ASYNC, RegistrationSummaryComponent, RegistrationTableComponent,\n ],\n template: `\n \n @if (store.pendingHerregistratie()) {\n Uw herregistratie-aanvraag is in behandeling.\n }\n\n \n \n \n \n
\n Persoonsgegevens (BRP)\n
\n \n \n \n
\n
\n
\n \n \n \n
\n\n
\n Specialismen en aantekeningen\n \n \n \n \n \n \n \n \n

U heeft nog geen specialismen of aantekeningen.

\n
\n
\n
\n\n

\n Gegevens bekijken of een wijziging doorgeven →\n

\n

\n Herregistratie aanvragen →\n

\n

\n Herregistratie-intake (vragenlijst met vertakkingen) →\n

\n

\n Functionele patronen (impossible states) →\n

\n
\n `,\n})\nexport class DashboardPage {\n protected store = inject(BigProfileStore);\n}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "", @@ -2188,7 +2397,7 @@ }, { "name": "HerregistratieWizardComponent", - "id": "component-HerregistratieWizardComponent-45976389894f1e7f8d41c6517df80210bfb72ce0c6d94a26e8efaea3128977466c34eab7a568b372079d6c5295253c988e2b3d96ffc7267e02a1160e75cef160", + "id": "component-HerregistratieWizardComponent-95ad9f587488b2044f1c353c07a0cc97c2d9e77df8204260ec2c543c6c6d85799a062e6565b85faa9e143b1cfe8d72dfe964f3b0ed8edb52b48fd06bde9c41d3", "file": "src/app/herregistratie/ui/herregistratie-wizard/herregistratie-wizard.component.ts", "encapsulation": [], "entryComponents": [], @@ -2198,7 +2407,7 @@ "selector": "app-herregistratie-wizard", "styleUrls": [], "styles": [], - "template": "@switch (state().tag) {\n @case ('Editing') {\n

Stap {{ step() }} van 2

\n
\n @if (step() === 1) {\n \n \n \n Volgende\n } @else {\n \n \n \n
\n Vorige\n Herregistratie aanvragen\n
\n }\n
\n }\n @case ('Submitting') {\n Aanvraag wordt verwerkt…\n }\n @case ('Submitted') {\n Uw aanvraag tot herregistratie is ontvangen.\n }\n @case ('Failed') {\n Indienen mislukt: {{ failedError() }}\n
\n Opnieuw proberen\n
\n }\n}\n", + "template": "@switch (state().tag) {\n @case ('Editing') {\n

Stap {{ step() }} van 2

\n
\n @if (step() === 1) {\n \n \n \n \n \n \n Volgende\n } @else {\n \n \n \n
\n Vorige\n Herregistratie aanvragen\n
\n }\n
\n }\n @case ('Submitting') {\n Aanvraag wordt verwerkt…\n }\n @case ('Submitted') {\n Uw aanvraag tot herregistratie is ontvangen.\n }\n @case ('Failed') {\n Indienen mislukt: {{ failedError() }}\n
\n Opnieuw proberen\n
\n }\n}\n", "templateUrl": [], "viewProviders": [], "hostDirectives": [], @@ -2212,7 +2421,7 @@ "indexKey": "", "optional": false, "description": "

Optional seed so Storybook / the showcase can mount any state directly.

\n", - "line": 65, + "line": 69, "rawdescription": "\nOptional seed so Storybook / the showcase can mount any state directly.", "required": false } @@ -2228,21 +2437,21 @@ "indexKey": "", "optional": false, "description": "", - "line": 68, + "line": 72, "modifierKind": [ 124 ] }, { "name": "draft", - "defaultValue": "computed(() => this.editing()?.draft ?? { uren: '', punten: '' })", + "defaultValue": "computed(() => this.editing()?.draft ?? { uren: '', jaren: '', punten: '' })", "deprecated": false, "deprecationMessage": "", "type": "unknown", "indexKey": "", "optional": false, "description": "", - "line": 72, + "line": 76, "modifierKind": [ 124 ] @@ -2256,11 +2465,25 @@ "indexKey": "", "optional": false, "description": "", - "line": 70, + "line": 74, "modifierKind": [ 123 ] }, + { + "name": "errJaren", + "defaultValue": "computed(() => this.editing()?.errors.jaren ?? '')", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 78, + "modifierKind": [ + 124 + ] + }, { "name": "errPunten", "defaultValue": "computed(() => this.editing()?.errors.punten ?? '')", @@ -2270,7 +2493,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 74, + "line": 79, "modifierKind": [ 124 ] @@ -2284,7 +2507,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 73, + "line": 77, "modifierKind": [ 124 ] @@ -2298,7 +2521,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 75, + "line": 80, "modifierKind": [ 124 ] @@ -2312,7 +2535,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 61, + "line": 65, "modifierKind": [ 123 ] @@ -2326,9 +2549,9 @@ "indexKey": "", "optional": false, "description": "", - "line": 67, + "line": 71, "modifierKind": [ - 124 + 148 ] }, { @@ -2340,7 +2563,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 71, + "line": 75, "modifierKind": [ 124 ] @@ -2354,7 +2577,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 62, + "line": 66, "modifierKind": [ 123 ] @@ -2367,7 +2590,7 @@ "optional": false, "returnType": "void", "typeParameters": [], - "line": 81, + "line": 86, "deprecated": false, "deprecationMessage": "" }, @@ -2377,7 +2600,7 @@ "optional": false, "returnType": "void", "typeParameters": [], - "line": 88, + "line": 93, "deprecated": false, "deprecationMessage": "" }, @@ -2387,7 +2610,7 @@ "optional": false, "returnType": "any", "typeParameters": [], - "line": 95, + "line": 100, "deprecated": false, "deprecationMessage": "", "rawdescription": "\nThe effect: when we entered Submitting, call the backend command, flip the\noptimistic cross-page flag, then dispatch the result (and commit/rollback).", @@ -2432,7 +2655,7 @@ "description": "

Organism: multi-step herregistratie wizard. ALL state lives in one signal\ndriven by the pure reduce function (see herregistratie.machine.ts) via an\nElm-style store. The UI just sends messages and folds over the state's tag —\nno booleans like submitting/submitted that could contradict each other.\nSubmitting also flips an optimistic flag on the shared BigProfileStore, so\nthe dashboard shows "in behandeling" immediately.

\n", "rawdescription": "\nOrganism: multi-step herregistratie wizard. ALL state lives in one signal\ndriven by the pure `reduce` function (see herregistratie.machine.ts) via an\nElm-style store. The UI just sends messages and folds over the state's tag —\nno booleans like `submitting`/`submitted` that could contradict each other.\nSubmitting also flips an optimistic flag on the shared BigProfileStore, so\nthe dashboard shows \"in behandeling\" immediately.", "type": "component", - "sourceCode": "import { Component, computed, inject, input } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { FormFieldComponent } from '@shared/ui/form-field/form-field.component';\nimport { TextInputComponent } from '@shared/ui/text-input/text-input.component';\nimport { ButtonComponent } from '@shared/ui/button/button.component';\nimport { AlertComponent } from '@shared/ui/alert/alert.component';\nimport { SpinnerComponent } from '@shared/ui/spinner/spinner.component';\nimport { createStore } from '@shared/application/store';\nimport { BigProfileStore } from '@registratie/application/big-profile.store';\nimport { WizardState, WizardMsg, Draft, initial, reduce } from '@herregistratie/domain/herregistratie.machine';\nimport { submitHerregistratie } from '@herregistratie/application/submit-herregistratie';\n\n/** Organism: multi-step herregistratie wizard. ALL state lives in one signal\n driven by the pure `reduce` function (see herregistratie.machine.ts) via an\n Elm-style store. The UI just sends messages and folds over the state's tag —\n no booleans like `submitting`/`submitted` that could contradict each other.\n Submitting also flips an optimistic flag on the shared BigProfileStore, so\n the dashboard shows \"in behandeling\" immediately. */\n@Component({\n selector: 'app-herregistratie-wizard',\n imports: [FormsModule, FormFieldComponent, TextInputComponent, ButtonComponent, AlertComponent, SpinnerComponent],\n template: `\n @switch (state().tag) {\n @case ('Editing') {\n

Stap {{ step() }} van 2

\n
\n @if (step() === 1) {\n \n \n \n Volgende\n } @else {\n \n \n \n
\n Vorige\n Herregistratie aanvragen\n
\n }\n
\n }\n @case ('Submitting') {\n Aanvraag wordt verwerkt…\n }\n @case ('Submitted') {\n Uw aanvraag tot herregistratie is ontvangen.\n }\n @case ('Failed') {\n Indienen mislukt: {{ failedError() }}\n
\n Opnieuw proberen\n
\n }\n }\n `,\n})\nexport class HerregistratieWizardComponent {\n private profile = inject(BigProfileStore);\n private store = createStore(initial, reduce);\n\n /** Optional seed so Storybook / the showcase can mount any state directly. */\n seed = input(initial);\n\n protected state = this.store.model;\n protected dispatch = this.store.dispatch;\n\n private editing = computed(() => (this.state().tag === 'Editing' ? (this.state() as Extract) : null));\n protected step = computed(() => this.editing()?.step ?? 1);\n protected draft = computed(() => this.editing()?.draft ?? { uren: '', punten: '' });\n protected errUren = computed(() => this.editing()?.errors.uren ?? '');\n protected errPunten = computed(() => this.editing()?.errors.punten ?? '');\n protected failedError = computed(() => (this.state().tag === 'Failed' ? (this.state() as Extract).error : ''));\n\n constructor() {\n queueMicrotask(() => this.dispatch({ tag: 'Seed', state: this.seed() }));\n }\n\n onPrimary() {\n const s = this.state();\n if (s.tag !== 'Editing') return;\n this.dispatch(s.step === 1 ? { tag: 'Next' } : { tag: 'Submit' });\n this.runIfSubmitting();\n }\n\n onRetry() {\n this.dispatch({ tag: 'Retry' });\n this.runIfSubmitting();\n }\n\n /** The effect: when we entered Submitting, call the backend command, flip the\n optimistic cross-page flag, then dispatch the result (and commit/rollback). */\n private async runIfSubmitting() {\n const s = this.state();\n if (s.tag !== 'Submitting') return;\n this.profile.beginHerregistratie();\n const r = await submitHerregistratie(s.data);\n if (r.ok) {\n this.dispatch({ tag: 'SubmitConfirmed' });\n this.profile.confirmHerregistratie();\n } else {\n this.dispatch({ tag: 'SubmitFailed', error: r.error });\n this.profile.rollbackHerregistratie();\n }\n }\n}\n", + "sourceCode": "import { Component, computed, inject, input } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { FormFieldComponent } from '@shared/ui/form-field/form-field.component';\nimport { TextInputComponent } from '@shared/ui/text-input/text-input.component';\nimport { ButtonComponent } from '@shared/ui/button/button.component';\nimport { AlertComponent } from '@shared/ui/alert/alert.component';\nimport { SpinnerComponent } from '@shared/ui/spinner/spinner.component';\nimport { createStore } from '@shared/application/store';\nimport { BigProfileStore } from '@registratie/application/big-profile.store';\nimport { WizardState, WizardMsg, Draft, initial, reduce } from '@herregistratie/domain/herregistratie.machine';\nimport { submitHerregistratie } from '@herregistratie/application/submit-herregistratie';\n\n/** Organism: multi-step herregistratie wizard. ALL state lives in one signal\n driven by the pure `reduce` function (see herregistratie.machine.ts) via an\n Elm-style store. The UI just sends messages and folds over the state's tag —\n no booleans like `submitting`/`submitted` that could contradict each other.\n Submitting also flips an optimistic flag on the shared BigProfileStore, so\n the dashboard shows \"in behandeling\" immediately. */\n@Component({\n selector: 'app-herregistratie-wizard',\n imports: [FormsModule, FormFieldComponent, TextInputComponent, ButtonComponent, AlertComponent, SpinnerComponent],\n template: `\n @switch (state().tag) {\n @case ('Editing') {\n

Stap {{ step() }} van 2

\n
\n @if (step() === 1) {\n \n \n \n \n \n \n Volgende\n } @else {\n \n \n \n
\n Vorige\n Herregistratie aanvragen\n
\n }\n
\n }\n @case ('Submitting') {\n Aanvraag wordt verwerkt…\n }\n @case ('Submitted') {\n Uw aanvraag tot herregistratie is ontvangen.\n }\n @case ('Failed') {\n Indienen mislukt: {{ failedError() }}\n
\n Opnieuw proberen\n
\n }\n }\n `,\n})\nexport class HerregistratieWizardComponent {\n private profile = inject(BigProfileStore);\n private store = createStore(initial, reduce);\n\n /** Optional seed so Storybook / the showcase can mount any state directly. */\n seed = input(initial);\n\n readonly state = this.store.model; // public so the showcase can highlight the live state\n protected dispatch = this.store.dispatch;\n\n private editing = computed(() => (this.state().tag === 'Editing' ? (this.state() as Extract) : null));\n protected step = computed(() => this.editing()?.step ?? 1);\n protected draft = computed(() => this.editing()?.draft ?? { uren: '', jaren: '', punten: '' });\n protected errUren = computed(() => this.editing()?.errors.uren ?? '');\n protected errJaren = computed(() => this.editing()?.errors.jaren ?? '');\n protected errPunten = computed(() => this.editing()?.errors.punten ?? '');\n protected failedError = computed(() => (this.state().tag === 'Failed' ? (this.state() as Extract).error : ''));\n\n constructor() {\n queueMicrotask(() => this.dispatch({ tag: 'Seed', state: this.seed() }));\n }\n\n onPrimary() {\n const s = this.state();\n if (s.tag !== 'Editing') return;\n this.dispatch(s.step === 1 ? { tag: 'Next' } : { tag: 'Submit' });\n this.runIfSubmitting();\n }\n\n onRetry() {\n this.dispatch({ tag: 'Retry' });\n this.runIfSubmitting();\n }\n\n /** The effect: when we entered Submitting, call the backend command, flip the\n optimistic cross-page flag, then dispatch the result (and commit/rollback). */\n private async runIfSubmitting() {\n const s = this.state();\n if (s.tag !== 'Submitting') return;\n this.profile.beginHerregistratie();\n const r = await submitHerregistratie(s.data);\n if (r.ok) {\n this.dispatch({ tag: 'SubmitConfirmed' });\n this.profile.confirmHerregistratie();\n } else {\n this.dispatch({ tag: 'SubmitFailed', error: r.error });\n this.profile.rollbackHerregistratie();\n }\n }\n}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "", @@ -2442,7 +2665,385 @@ "deprecated": false, "deprecationMessage": "", "args": [], - "line": 75 + "line": 80 + }, + "extends": [] + }, + { + "name": "IntakePage", + "id": "component-IntakePage-12d65756545a1c065f64a50a3dacfa1708ad3d1d40663c41306c4c15727ecde5b547f243cda5ec5201137f0a376e7807986a45f89fd717c7e30fac41fc22a6b3", + "file": "src/app/herregistratie/ui/intake.page.ts", + "encapsulation": [], + "entryComponents": [], + "inputs": [], + "outputs": [], + "providers": [], + "selector": "app-intake-page", + "styleUrls": [], + "styles": [], + "template": "\n \n Een paar vragen bepalen welke gegevens we nodig hebben. Afhankelijk van uw\n antwoorden verschijnen er extra vragen. Uw antwoorden blijven bewaard als u\n de pagina herlaadt.\n \n
\n \n
\n
\n", + "templateUrl": [], + "viewProviders": [], + "hostDirectives": [], + "inputsClass": [], + "outputsClass": [], + "propertiesClass": [], + "methodsClass": [], + "deprecated": false, + "deprecationMessage": "", + "hostBindings": [], + "hostListeners": [], + "standalone": false, + "imports": [ + { + "name": "PageShellComponent", + "type": "component" + }, + { + "name": "AlertComponent", + "type": "component" + }, + { + "name": "IntakeWizardComponent", + "type": "component" + } + ], + "description": "

Page: the branching intake questionnaire. Built entirely from existing\nbuilding blocks (page shell + alert + the intake-wizard organism).

\n", + "rawdescription": "\nPage: the branching intake questionnaire. Built entirely from existing\nbuilding blocks (page shell + alert + the intake-wizard organism).", + "type": "component", + "sourceCode": "import { Component } from '@angular/core';\nimport { PageShellComponent } from '@shared/layout/page-shell/page-shell.component';\nimport { AlertComponent } from '@shared/ui/alert/alert.component';\nimport { IntakeWizardComponent } from '@herregistratie/ui/intake-wizard/intake-wizard.component';\n\n/** Page: the branching intake questionnaire. Built entirely from existing\n building blocks (page shell + alert + the intake-wizard organism). */\n@Component({\n selector: 'app-intake-page',\n imports: [PageShellComponent, AlertComponent, IntakeWizardComponent],\n template: `\n \n \n Een paar vragen bepalen welke gegevens we nodig hebben. Afhankelijk van uw\n antwoorden verschijnen er extra vragen. Uw antwoorden blijven bewaard als u\n de pagina herlaadt.\n \n
\n \n
\n
\n `,\n})\nexport class IntakePage {}\n", + "assetsDirs": [], + "styleUrlsData": "", + "stylesData": "", + "extends": [] + }, + { + "name": "IntakeWizardComponent", + "id": "component-IntakeWizardComponent-f9c390987674a1103c9b4b44873432dbacb4550534e7dd1fa207ac5add0926a003ecdef05cb4a7252221298d5f7c5735b07e34ce9704f012f9699aa70756f2ae", + "file": "src/app/herregistratie/ui/intake-wizard/intake-wizard.component.ts", + "encapsulation": [], + "entryComponents": [], + "inputs": [], + "outputs": [], + "providers": [], + "selector": "app-intake-wizard", + "styleUrls": [], + "styles": [], + "template": "@switch (state().tag) {\n @case ('Answering') {\n

Stap {{ cursor() + 1 }} van {{ steps().length }}

\n
\n @switch (step()) {\n @case ('buitenland') {\n \n \n \n }\n @case ('buitenlandDetails') {\n \n \n \n \n \n \n }\n @case ('uren') {\n \n \n \n }\n @case ('scholing') {\n \n \n \n }\n @case ('punten') {\n \n \n \n }\n @case ('review') {\n Controleer uw antwoorden en dien de aanvraag in.\n
\n
Buiten NL gewerkt
{{ answers().buitenlandGewerkt ?? '—' }}
\n @if (answers().buitenlandGewerkt === 'ja') {\n
Land
{{ answers().land }}
\n
Buitenlandse uren
{{ answers().buitenlandseUren }}
\n }\n
Uren NL
{{ answers().uren }}
\n @if (steps().includes('scholing')) {\n
Aanvullende scholing
{{ answers().scholingGevolgd }}
\n }\n
Nascholingspunten
{{ answers().punten }}
\n
\n }\n }\n
\n @if (cursor() > 0) {\n Vorige\n }\n {{ step() === 'review' ? 'Aanvraag indienen' : 'Volgende' }}\n
\n
\n }\n @case ('Submitting') {\n Aanvraag wordt verwerkt…\n }\n @case ('Submitted') {\n Uw aanvraag tot herregistratie is ontvangen.\n
\n Opnieuw beginnen\n
\n }\n @case ('Failed') {\n Indienen mislukt: {{ failedError() }}\n
\n Opnieuw proberen\n
\n }\n}\n", + "templateUrl": [], + "viewProviders": [], + "hostDirectives": [], + "inputsClass": [ + { + "name": "seed", + "defaultValue": "initial", + "deprecated": false, + "deprecationMessage": "", + "type": "IntakeState", + "indexKey": "", + "optional": false, + "description": "

Optional seed so Storybook / the showcase can mount any state directly.

\n", + "line": 116, + "rawdescription": "\nOptional seed so Storybook / the showcase can mount any state directly.", + "required": false + } + ], + "outputsClass": [], + "propertiesClass": [ + { + "name": "answering", + "defaultValue": "computed(() => (this.state().tag === 'Answering' ? (this.state() as Extract) : null))", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 122, + "modifierKind": [ + 123 + ] + }, + { + "name": "answers", + "defaultValue": "computed(() => this.answering()?.answers ?? {})", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 126, + "modifierKind": [ + 124 + ] + }, + { + "name": "cursor", + "defaultValue": "computed(() => this.answering()?.cursor ?? 0)", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 125, + "modifierKind": [ + 124 + ] + }, + { + "name": "dispatch", + "defaultValue": "this.store.dispatch", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 120, + "modifierKind": [ + 148 + ] + }, + { + "name": "err", + "defaultValue": "() => {...}", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 130, + "modifierKind": [ + 124 + ] + }, + { + "name": "failedError", + "defaultValue": "computed(() => (this.state().tag === 'Failed' ? (this.state() as Extract).error : ''))", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 128, + "modifierKind": [ + 124 + ] + }, + { + "name": "jaNee", + "defaultValue": "JA_NEE", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 118, + "modifierKind": [ + 148 + ] + }, + { + "name": "profile", + "defaultValue": "inject(BigProfileStore)", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 112, + "modifierKind": [ + 123 + ] + }, + { + "name": "set", + "defaultValue": "() => {...}", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 131, + "modifierKind": [ + 124 + ] + }, + { + "name": "state", + "defaultValue": "this.store.model", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 119, + "modifierKind": [ + 148 + ] + }, + { + "name": "step", + "defaultValue": "computed(() => this.steps()[Math.min(this.cursor(), this.steps().length - 1)])", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 127, + "modifierKind": [ + 124 + ] + }, + { + "name": "steps", + "defaultValue": "computed(() => visibleSteps(this.answering()?.answers ?? {}))", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "

Public so the showcase can render the live step list next to the wizard.

\n", + "line": 124, + "rawdescription": "\nPublic so the showcase can render the live step list next to the wizard.", + "modifierKind": [ + 148 + ] + }, + { + "name": "store", + "defaultValue": "createStore(initial, reduce)", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 113, + "modifierKind": [ + 123 + ] + } + ], + "methodsClass": [ + { + "name": "onPrimary", + "args": [], + "optional": false, + "returnType": "void", + "typeParameters": [], + "line": 155, + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "onRetry", + "args": [], + "optional": false, + "returnType": "void", + "typeParameters": [], + "line": 162, + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "restart", + "args": [], + "optional": false, + "returnType": "void", + "typeParameters": [], + "line": 167, + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "restore", + "args": [], + "optional": false, + "returnType": "IntakeState | null", + "typeParameters": [], + "line": 145, + "deprecated": false, + "deprecationMessage": "", + "modifierKind": [ + 123 + ] + }, + { + "name": "runIfSubmitting", + "args": [], + "optional": false, + "returnType": "any", + "typeParameters": [], + "line": 173, + "deprecated": false, + "deprecationMessage": "", + "rawdescription": "\nThe effect: when we enter Submitting, call the backend, flip the optimistic\ncross-page flag, then dispatch the outcome (and commit/rollback).", + "description": "

The effect: when we enter Submitting, call the backend, flip the optimistic\ncross-page flag, then dispatch the outcome (and commit/rollback).

\n", + "modifierKind": [ + 123, + 134 + ] + } + ], + "deprecated": false, + "deprecationMessage": "", + "hostBindings": [], + "hostListeners": [], + "standalone": false, + "imports": [ + { + "name": "FormsModule", + "type": "module" + }, + { + "name": "FormFieldComponent", + "type": "component" + }, + { + "name": "TextInputComponent", + "type": "component" + }, + { + "name": "RadioGroupComponent", + "type": "component" + }, + { + "name": "ButtonComponent", + "type": "component" + }, + { + "name": "AlertComponent", + "type": "component" + }, + { + "name": "SpinnerComponent", + "type": "component" + } + ], + "description": "

Organism: a BRANCHING intake questionnaire. All state lives in one signal\ndriven by the pure reduce (intake.machine.ts). Which step renders is derived\nfrom the answers via visibleSteps, never stored — so editing an earlier\nanswer immediately changes the remaining steps. Answers are persisted to\nlocalStorage so a page reload keeps the user's progress.

\n", + "rawdescription": "\nOrganism: a BRANCHING intake questionnaire. All state lives in one signal\ndriven by the pure `reduce` (intake.machine.ts). Which step renders is derived\nfrom the answers via `visibleSteps`, never stored — so editing an earlier\nanswer immediately changes the remaining steps. Answers are persisted to\nlocalStorage so a page reload keeps the user's progress.", + "type": "component", + "sourceCode": "import { Component, computed, effect, inject, input } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { FormFieldComponent } from '@shared/ui/form-field/form-field.component';\nimport { TextInputComponent } from '@shared/ui/text-input/text-input.component';\nimport { RadioGroupComponent } from '@shared/ui/radio-group/radio-group.component';\nimport { ButtonComponent } from '@shared/ui/button/button.component';\nimport { AlertComponent } from '@shared/ui/alert/alert.component';\nimport { SpinnerComponent } from '@shared/ui/spinner/spinner.component';\nimport { createStore } from '@shared/application/store';\nimport { BigProfileStore } from '@registratie/application/big-profile.store';\nimport {\n IntakeState,\n IntakeMsg,\n Answers,\n StepId,\n initial,\n reduce,\n visibleSteps,\n} from '@herregistratie/domain/intake.machine';\nimport { submitIntake } from '@herregistratie/application/submit-intake';\n\nconst STORAGE_KEY = 'intake-v1'; // ponytail: bump the suffix if the Answers shape changes; no migration.\nconst JA_NEE = [{ value: 'ja', label: 'Ja' }, { value: 'nee', label: 'Nee' }];\n\n/** Organism: a BRANCHING intake questionnaire. All state lives in one signal\n driven by the pure `reduce` (intake.machine.ts). Which step renders is derived\n from the answers via `visibleSteps`, never stored — so editing an earlier\n answer immediately changes the remaining steps. Answers are persisted to\n localStorage so a page reload keeps the user's progress. */\n@Component({\n selector: 'app-intake-wizard',\n imports: [FormsModule, FormFieldComponent, TextInputComponent, RadioGroupComponent, ButtonComponent, AlertComponent, SpinnerComponent],\n template: `\n @switch (state().tag) {\n @case ('Answering') {\n

Stap {{ cursor() + 1 }} van {{ steps().length }}

\n
\n @switch (step()) {\n @case ('buitenland') {\n \n \n \n }\n @case ('buitenlandDetails') {\n \n \n \n \n \n \n }\n @case ('uren') {\n \n \n \n }\n @case ('scholing') {\n \n \n \n }\n @case ('punten') {\n \n \n \n }\n @case ('review') {\n Controleer uw antwoorden en dien de aanvraag in.\n
\n
Buiten NL gewerkt
{{ answers().buitenlandGewerkt ?? '—' }}
\n @if (answers().buitenlandGewerkt === 'ja') {\n
Land
{{ answers().land }}
\n
Buitenlandse uren
{{ answers().buitenlandseUren }}
\n }\n
Uren NL
{{ answers().uren }}
\n @if (steps().includes('scholing')) {\n
Aanvullende scholing
{{ answers().scholingGevolgd }}
\n }\n
Nascholingspunten
{{ answers().punten }}
\n
\n }\n }\n
\n @if (cursor() > 0) {\n Vorige\n }\n {{ step() === 'review' ? 'Aanvraag indienen' : 'Volgende' }}\n
\n
\n }\n @case ('Submitting') {\n Aanvraag wordt verwerkt…\n }\n @case ('Submitted') {\n Uw aanvraag tot herregistratie is ontvangen.\n
\n Opnieuw beginnen\n
\n }\n @case ('Failed') {\n Indienen mislukt: {{ failedError() }}\n
\n Opnieuw proberen\n
\n }\n }\n `,\n})\nexport class IntakeWizardComponent {\n private profile = inject(BigProfileStore);\n private store = createStore(initial, reduce);\n\n /** Optional seed so Storybook / the showcase can mount any state directly. */\n seed = input(initial);\n\n readonly jaNee = JA_NEE;\n readonly state = this.store.model;\n readonly dispatch = this.store.dispatch;\n\n private answering = computed(() => (this.state().tag === 'Answering' ? (this.state() as Extract) : null));\n /** Public so the showcase can render the live step list next to the wizard. */\n readonly steps = computed(() => visibleSteps(this.answering()?.answers ?? {}));\n protected cursor = computed(() => this.answering()?.cursor ?? 0);\n protected answers = computed(() => this.answering()?.answers ?? {});\n protected step = computed(() => this.steps()[Math.min(this.cursor(), this.steps().length - 1)]);\n protected failedError = computed(() => (this.state().tag === 'Failed' ? (this.state() as Extract).error : ''));\n\n protected err = (k: StepId) => this.answering()?.errors[k] ?? '';\n protected set = (key: keyof Answers, value: string) => this.dispatch({ tag: 'SetAnswer', key, value });\n\n constructor() {\n // An explicit seed (stories) wins; otherwise resume from localStorage.\n const seeded = this.seed();\n queueMicrotask(() => this.dispatch({ tag: 'Seed', state: seeded !== initial ? seeded : (this.restore() ?? initial) }));\n // Persist only while answering; clear once the flow is done.\n effect(() => {\n const s = this.state();\n if (s.tag === 'Answering') localStorage.setItem(STORAGE_KEY, JSON.stringify(s));\n else localStorage.removeItem(STORAGE_KEY);\n });\n }\n\n private restore(): IntakeState | null {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (!raw) return null;\n try {\n return JSON.parse(raw) as IntakeState;\n } catch {\n return null; // ponytail: corrupt entry -> start fresh, no migration.\n }\n }\n\n onPrimary() {\n const s = this.state();\n if (s.tag !== 'Answering') return;\n this.dispatch(this.step() === 'review' ? { tag: 'Submit' } : { tag: 'Next' });\n this.runIfSubmitting();\n }\n\n onRetry() {\n this.dispatch({ tag: 'Retry' });\n this.runIfSubmitting();\n }\n\n restart() {\n this.dispatch({ tag: 'Seed', state: initial });\n }\n\n /** The effect: when we enter Submitting, call the backend, flip the optimistic\n cross-page flag, then dispatch the outcome (and commit/rollback). */\n private async runIfSubmitting() {\n const s = this.state();\n if (s.tag !== 'Submitting') return;\n this.profile.beginHerregistratie();\n const r = await submitIntake(s.data);\n if (r.ok) {\n this.dispatch({ tag: 'SubmitConfirmed' });\n this.profile.confirmHerregistratie();\n } else {\n this.dispatch({ tag: 'SubmitFailed', error: r.error });\n this.profile.rollbackHerregistratie();\n }\n }\n}\n", + "assetsDirs": [], + "styleUrlsData": "", + "stylesData": "", + "constructorObj": { + "name": "constructor", + "description": "", + "deprecated": false, + "deprecationMessage": "", + "args": [], + "line": 131 }, "extends": [] }, @@ -2499,7 +3100,7 @@ }, { "name": "LoginFormComponent", - "id": "component-LoginFormComponent-5a1ea0dfad0a47819d0884389f35e026337bfc135ed3cb5f34d5874d22b773b812864e1dfdcebf784acf886a77e9f59d3f244c7f3bbe1edafb38f0d8fb866a43", + "id": "component-LoginFormComponent-8dba6a4389a532f36aaf74d6c5258521a2020ac11eb8c207aa1b0605da6f3971e1aeada702062295b5310aa9043c413de220e6062d0b8de97241f4a37f280ff0", "file": "src/app/auth/ui/login-form/login-form.component.ts", "encapsulation": [], "entryComponents": [], @@ -2509,14 +3110,14 @@ "selector": "app-login-form", "styleUrls": [], "styles": [], - "template": "
\n \n \n \n\n \n \n \n\n
\n Inloggen met DigiD\n
\n
\n", + "template": "
\n \n \n \n\n \n \n \n\n
\n Inloggen met DigiD\n
\n
\n", "templateUrl": [], "viewProviders": [], "hostDirectives": [], "inputsClass": [], "outputsClass": [ { - "name": "submit", + "name": "submitted", "deprecated": false, "deprecationMessage": "", "type": "string", @@ -2578,7 +3179,7 @@ "description": "

Organism: DigiD-style mock login. No real auth — just composes atoms/molecules.

\n", "rawdescription": "\nOrganism: DigiD-style mock login. No real auth — just composes atoms/molecules.", "type": "component", - "sourceCode": "import { Component, output } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { FormFieldComponent } from '@shared/ui/form-field/form-field.component';\nimport { TextInputComponent } from '@shared/ui/text-input/text-input.component';\nimport { ButtonComponent } from '@shared/ui/button/button.component';\n\n/** Organism: DigiD-style mock login. No real auth — just composes atoms/molecules. */\n@Component({\n selector: 'app-login-form',\n imports: [FormsModule, FormFieldComponent, TextInputComponent, ButtonComponent],\n template: `\n
\n \n \n \n\n \n \n \n\n
\n Inloggen met DigiD\n
\n
\n `,\n})\nexport class LoginFormComponent {\n bsn = '';\n password = '';\n submit = output();\n}\n", + "sourceCode": "import { Component, output } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { FormFieldComponent } from '@shared/ui/form-field/form-field.component';\nimport { TextInputComponent } from '@shared/ui/text-input/text-input.component';\nimport { ButtonComponent } from '@shared/ui/button/button.component';\n\n/** Organism: DigiD-style mock login. No real auth — just composes atoms/molecules. */\n@Component({\n selector: 'app-login-form',\n imports: [FormsModule, FormFieldComponent, TextInputComponent, ButtonComponent],\n template: `\n
\n \n \n \n\n \n \n \n\n
\n Inloggen met DigiD\n
\n
\n `,\n})\nexport class LoginFormComponent {\n bsn = '';\n password = '';\n submitted = output();\n}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "", @@ -2586,7 +3187,7 @@ }, { "name": "LoginPage", - "id": "component-LoginPage-9e33c7b584edbd0642e3db190f47b540bad1779cd292050761380e92715a150b03eb5d869d57c6678b4d73523803c07e2d70d6e70183b34461ac31c15cafa95b", + "id": "component-LoginPage-0cdc77f70073f58aa4eb17a72400adf1897b91d2c2d9df1dba75768c6f53a7f702d7389d6e9b43082faec0c24240d1963d21bd0f564f8980240b53009f615749", "file": "src/app/auth/ui/login.page.ts", "encapsulation": [], "entryComponents": [], @@ -2596,7 +3197,7 @@ "selector": "app-login-page", "styleUrls": [], "styles": [], - "template": "\n @if (error()) { {{ error() }} }\n \n\n", + "template": "\n @if (error()) { {{ error() }} }\n \n\n", "templateUrl": [], "viewProviders": [], "hostDirectives": [], @@ -2702,7 +3303,7 @@ "description": "", "rawdescription": "\n", "type": "component", - "sourceCode": "import { Component, inject, signal } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { PageShellComponent } from '@shared/layout/page-shell/page-shell.component';\nimport { AlertComponent } from '@shared/ui/alert/alert.component';\nimport { LoginFormComponent } from '@auth/ui/login-form/login-form.component';\nimport { SessionStore } from '@auth/application/session.store';\n\n@Component({\n selector: 'app-login-page',\n imports: [PageShellComponent, AlertComponent, LoginFormComponent],\n template: `\n \n @if (error()) { {{ error() }} }\n \n \n `,\n})\nexport class LoginPage {\n private store = inject(SessionStore);\n private router = inject(Router);\n error = signal('');\n\n async login(bsn: string) {\n const r = await this.store.login(bsn);\n if (r.ok) this.router.navigate(['/dashboard']);\n else this.error.set(r.error);\n }\n}\n", + "sourceCode": "import { Component, inject, signal } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { PageShellComponent } from '@shared/layout/page-shell/page-shell.component';\nimport { AlertComponent } from '@shared/ui/alert/alert.component';\nimport { LoginFormComponent } from '@auth/ui/login-form/login-form.component';\nimport { SessionStore } from '@auth/application/session.store';\n\n@Component({\n selector: 'app-login-page',\n imports: [PageShellComponent, AlertComponent, LoginFormComponent],\n template: `\n \n @if (error()) { {{ error() }} }\n \n \n `,\n})\nexport class LoginPage {\n private store = inject(SessionStore);\n private router = inject(Router);\n error = signal('');\n\n async login(bsn: string) {\n const r = await this.store.login(bsn);\n if (r.ok) this.router.navigate(['/dashboard']);\n else this.error.set(r.error);\n }\n}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "", @@ -2811,6 +3412,299 @@ "stylesData": ":host{display:block}\n", "extends": [] }, + { + "name": "RadioGroupComponent", + "id": "component-RadioGroupComponent-ac09f04d2bd5bb44d96600b5081778fdf878ad9137e373a0832855877706afbe37687f7937f568a890e541467637b067a6c8646dcb2005ab746f1a829d8d39a2", + "file": "src/app/shared/ui/radio-group/radio-group.component.ts", + "encapsulation": [], + "entryComponents": [], + "inputs": [], + "outputs": [], + "providers": [ + { + "name": ")" + } + ], + "selector": "app-radio-group", + "styleUrls": [], + "styles": [], + "template": "
\n @for (opt of options(); track opt.value) {\n \n }\n
\n", + "templateUrl": [], + "viewProviders": [], + "hostDirectives": [], + "inputsClass": [ + { + "name": "name", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": false, + "description": "", + "line": 35, + "required": true + }, + { + "name": "options", + "deprecated": false, + "deprecationMessage": "", + "type": "RadioOption[]", + "indexKey": "", + "optional": false, + "description": "", + "line": 34, + "required": true + } + ], + "outputsClass": [], + "propertiesClass": [ + { + "name": "disabled", + "defaultValue": "false", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 38 + }, + { + "name": "onChange", + "defaultValue": "() => {...}", + "deprecated": false, + "deprecationMessage": "", + "type": "function", + "indexKey": "", + "optional": false, + "description": "", + "line": 39 + }, + { + "name": "onTouched", + "defaultValue": "() => {...}", + "deprecated": false, + "deprecationMessage": "", + "type": "function", + "indexKey": "", + "optional": false, + "description": "", + "line": 40 + }, + { + "name": "value", + "defaultValue": "''", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": false, + "description": "", + "line": 37 + } + ], + "methodsClass": [ + { + "name": "registerOnChange", + "args": [ + { + "name": "fn", + "type": "function", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "", + "function": [ + { + "name": "v", + "type": "string", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "" + } + ] + } + ], + "optional": false, + "returnType": "void", + "typeParameters": [], + "line": 47, + "deprecated": false, + "deprecationMessage": "", + "jsdoctags": [ + { + "name": "fn", + "type": "function", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "", + "function": [ + { + "name": "v", + "type": "string", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "" + } + ], + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "registerOnTouched", + "args": [ + { + "name": "fn", + "type": "function", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "", + "function": [] + } + ], + "optional": false, + "returnType": "void", + "typeParameters": [], + "line": 48, + "deprecated": false, + "deprecationMessage": "", + "jsdoctags": [ + { + "name": "fn", + "type": "function", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "", + "function": [], + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "select", + "args": [ + { + "name": "v", + "type": "string", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "" + } + ], + "optional": false, + "returnType": "void", + "typeParameters": [], + "line": 42, + "deprecated": false, + "deprecationMessage": "", + "jsdoctags": [ + { + "name": "v", + "type": "string", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "setDisabledState", + "args": [ + { + "name": "d", + "type": "boolean", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "" + } + ], + "optional": false, + "returnType": "void", + "typeParameters": [], + "line": 49, + "deprecated": false, + "deprecationMessage": "", + "jsdoctags": [ + { + "name": "d", + "type": "boolean", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "writeValue", + "args": [ + { + "name": "v", + "type": "string", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "" + } + ], + "optional": false, + "returnType": "void", + "typeParameters": [], + "line": 46, + "deprecated": false, + "deprecationMessage": "", + "jsdoctags": [ + { + "name": "v", + "type": "string", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + } + ], + "deprecated": false, + "deprecationMessage": "", + "hostBindings": [], + "hostListeners": [], + "standalone": false, + "imports": [], + "description": "

Atom: a radio group. Thin wrapper over the Utrecht/RHC radio CSS, wired as a\nform control so it works with ngModel just like the text-input atom.

\n", + "rawdescription": "\nAtom: a radio group. Thin wrapper over the Utrecht/RHC radio CSS, wired as a\nform control so it works with ngModel just like the text-input atom.", + "type": "component", + "sourceCode": "import { Component, forwardRef, input } from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\n\nexport interface RadioOption {\n value: string;\n label: string;\n}\n\n/** Atom: a radio group. Thin wrapper over the Utrecht/RHC radio CSS, wired as a\n form control so it works with ngModel just like the text-input atom. */\n@Component({\n selector: 'app-radio-group',\n template: `\n
\n @for (opt of options(); track opt.value) {\n \n }\n
\n `,\n providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => RadioGroupComponent), multi: true }],\n})\nexport class RadioGroupComponent implements ControlValueAccessor {\n options = input.required();\n name = input.required();\n\n value = '';\n disabled = false;\n onChange: (v: string) => void = () => {};\n onTouched: () => void = () => {};\n\n select(v: string) {\n this.value = v;\n this.onChange(v);\n }\n writeValue(v: string) { this.value = v ?? ''; }\n registerOnChange(fn: (v: string) => void) { this.onChange = fn; }\n registerOnTouched(fn: () => void) { this.onTouched = fn; }\n setDisabledState(d: boolean) { this.disabled = d; }\n}\n", + "assetsDirs": [], + "styleUrlsData": "", + "stylesData": "", + "extends": [], + "implements": [ + "ControlValueAccessor" + ] + }, { "name": "RegistrationDetailPage", "id": "component-RegistrationDetailPage-5aa270b47adfa8bd0334225a51df3a1656468e109d14ca9967660455a734884f4fb358b0f2a6c97e19b4df859f4c5e9773d5dd9c728dbcd785a3d43a60472811", @@ -3857,7 +4751,39 @@ "deprecated": false, "deprecationMessage": "", "type": "WizardState", - "defaultValue": "{ tag: 'Editing', step: 1, draft: { uren: '', punten: '' }, errors: {} }" + "defaultValue": "{ tag: 'Editing', step: 1, draft: { uren: '', jaren: '', punten: '' }, errors: {} }" + }, + { + "name": "initial", + "ctype": "miscellaneous", + "subtype": "variable", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "IntakeState", + "defaultValue": "{ tag: 'Answering', answers: {}, cursor: 0, errors: {} }" + }, + { + "name": "JA_NEE", + "ctype": "miscellaneous", + "subtype": "variable", + "file": "src/app/herregistratie/ui/intake-wizard/intake-wizard.component.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "[]", + "defaultValue": "[{ value: 'ja', label: 'Ja' }, { value: 'nee', label: 'Nee' }]" + }, + { + "name": "LAGE_UREN_DREMPEL", + "ctype": "miscellaneous", + "subtype": "variable", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "number", + "defaultValue": "1000", + "rawdescription": "Below this many NL-hours we ask whether extra scholing was followed.", + "description": "

Below this many NL-hours we ask whether extra scholing was followed.

\n" }, { "name": "ok", @@ -3877,7 +4803,7 @@ "deprecated": false, "deprecationMessage": "", "type": "Routes", - "defaultValue": "[\n {\n path: '',\n component: ShellComponent, // persistent header/footer; only children swap\n children: [\n { path: '', pathMatch: 'full', redirectTo: 'login' },\n { path: 'login', loadComponent: () => \"import('@auth/ui/login.page').then(m => m.LoginPage)\" },\n { path: 'dashboard', canActivate: [authGuard], loadComponent: () => \"import('@registratie/ui/dashboard.page').then(m => m.DashboardPage)\" },\n { path: 'registratie', canActivate: [authGuard], loadComponent: () => \"import('@registratie/ui/registration-detail.page').then(m => m.RegistrationDetailPage)\" },\n { path: 'herregistratie', canActivate: [authGuard], loadComponent: () => \"import('@herregistratie/ui/herregistratie.page').then(m => m.HerregistratiePage)\" },\n { path: 'concepts', loadComponent: () => \"import('./showcase/concepts.page').then(m => m.ConceptsPage)\" },\n { path: '**', redirectTo: 'login' },\n ],\n },\n]" + "defaultValue": "[\n {\n path: '',\n component: ShellComponent, // persistent header/footer; only children swap\n children: [\n { path: '', pathMatch: 'full', redirectTo: 'login' },\n { path: 'login', loadComponent: () => \"import('@auth/ui/login.page').then(m => m.LoginPage)\" },\n { path: 'dashboard', canActivate: [authGuard], loadComponent: () => \"import('@registratie/ui/dashboard.page').then(m => m.DashboardPage)\" },\n { path: 'registratie', canActivate: [authGuard], loadComponent: () => \"import('@registratie/ui/registration-detail.page').then(m => m.RegistrationDetailPage)\" },\n { path: 'herregistratie', canActivate: [authGuard], loadComponent: () => \"import('@herregistratie/ui/herregistratie.page').then(m => m.HerregistratiePage)\" },\n { path: 'intake', canActivate: [authGuard], loadComponent: () => \"import('@herregistratie/ui/intake.page').then(m => m.IntakePage)\" },\n { path: 'concepts', loadComponent: () => \"import('./showcase/concepts.page').then(m => m.ConceptsPage)\" },\n { path: '**', redirectTo: 'login' },\n ],\n },\n]" }, { "name": "scenarioInterceptor", @@ -3891,6 +4817,16 @@ "rawdescription": "Demo-only: rewrites the timing/outcome of mock data requests based on\n?scenario= so loading / empty / error states can be shown on demand.\nReal requests are untouched.", "description": "

Demo-only: rewrites the timing/outcome of mock data requests based on\n?scenario= so loading / empty / error states can be shown on demand.\nReal requests are untouched.

\n" }, + { + "name": "STORAGE_KEY", + "ctype": "miscellaneous", + "subtype": "variable", + "file": "src/app/herregistratie/ui/intake-wizard/intake-wizard.component.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "defaultValue": "'intake-v1'" + }, { "name": "VALID", "ctype": "miscellaneous", @@ -4003,6 +4939,35 @@ } ] }, + { + "name": "back", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "IntakeState", + "jsdoctags": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, { "name": "createStore", "file": "src/app/shared/application/store.ts", @@ -4056,6 +5021,35 @@ "args": [], "returnType": "Scenario" }, + { + "name": "currentStep", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Which step the cursor currently points at (clamped to the live step list).

\n", + "args": [ + { + "name": "s", + "type": "Extract", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "StepId", + "jsdoctags": [ + { + "name": "s", + "type": "Extract", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, { "name": "fakeResource", "file": "src/app/showcase/concepts.page.ts", @@ -4351,6 +5345,35 @@ } ] }, + { + "name": "lageUren", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "a", + "type": "Answers", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "boolean", + "jsdoctags": [ + { + "name": "a", + "type": "Answers", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, { "name": "map", "file": "src/app/shared/application/remote-data.ts", @@ -4529,7 +5552,7 @@ "subtype": "function", "deprecated": false, "deprecationMessage": "", - "description": "

Step 1 → 2: only advance if the uren field parses. Illegal elsewhere = no-op.

\n", + "description": "

Step 1 → 2: advance only when BOTH step-1 fields parse. Illegal elsewhere = no-op.

\n", "args": [ { "name": "s", @@ -4551,6 +5574,35 @@ } ] }, + { + "name": "next", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "IntakeState", + "jsdoctags": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, { "name": "parseBigNummer", "file": "src/app/registratie/domain/value-objects/big-nummer.ts", @@ -4682,6 +5734,50 @@ } ] }, + { + "name": "reduce", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "m", + "type": "IntakeMsg", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "IntakeState", + "jsdoctags": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "m", + "type": "IntakeMsg", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, { "name": "resolve", "file": "src/app/herregistratie/domain/herregistratie.machine.ts", @@ -4726,6 +5822,107 @@ } ] }, + { + "name": "resolve", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "r", + "type": "Result", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "IntakeState", + "jsdoctags": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "r", + "type": "Result", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "setAnswer", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "key", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "value", + "type": "string", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "IntakeState", + "jsdoctags": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "key", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "value", + "type": "string", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, { "name": "setField", "file": "src/app/herregistratie/domain/herregistratie.machine.ts", @@ -4870,6 +6067,35 @@ } ] }, + { + "name": "submit", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "IntakeState", + "jsdoctags": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, { "name": "submitHerregistratie", "file": "src/app/herregistratie/application/submit-herregistratie.ts", @@ -4899,6 +6125,35 @@ } ] }, + { + "name": "submitIntake", + "file": "src/app/herregistratie/application/submit-intake.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Command: send the intake questionnaire to the backend. Returns a Result so the\ncaller branches on success/failure without try/catch. ponytail: faked with a\ntimer; swap for a real POST when there's an API. The "uren must be > 0" rule\nlets the demo show the failure path.

\n", + "args": [ + { + "name": "data", + "type": "ValidIntake", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "Promise>", + "jsdoctags": [ + { + "name": "data", + "type": "ValidIntake", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, { "name": "validate", "file": "src/app/herregistratie/domain/herregistratie.machine.ts", @@ -4927,9 +6182,122 @@ } } ] + }, + { + "name": "validateAll", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Parse the whole questionnaire into a ValidIntake (called on submit).

\n", + "args": [ + { + "name": "a", + "type": "Answers", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "Result>, ValidIntake>", + "jsdoctags": [ + { + "name": "a", + "type": "Answers", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "validateStep", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Validate ONE step's fields. Returns the per-field error keyed by StepId.

\n", + "args": [ + { + "name": "step", + "type": "StepId", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "a", + "type": "Answers", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "Result>, void>", + "jsdoctags": [ + { + "name": "step", + "type": "StepId", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "a", + "type": "Answers", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "visibleSteps", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

THE branching, as one pure function. The step list is recomputed on every\ntransition, so changing an earlier answer immediately adds/removes later steps.

\n", + "args": [ + { + "name": "a", + "type": "Answers", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "StepId[]", + "jsdoctags": [ + { + "name": "a", + "type": "Answers", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] } ], "typealiases": [ + { + "name": "AantekeningType", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "\"Specialisme\" | \"Aantekening\"", + "file": "src/app/registratie/domain/registration.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

A note is either a recognised specialism or a plain annotation — a closed set,\nnot an open string, so a typo can't slip through.

\n", + "kind": 193 + }, { "name": "AlertType", "ctype": "miscellaneous", @@ -4974,6 +6342,39 @@ "description": "", "kind": 193 }, + { + "name": "IntakeMsg", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "literal type | literal type | literal type | literal type | literal type | literal type | literal type | literal type", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "kind": 193 + }, + { + "name": "IntakeState", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "literal type | literal type | literal type | literal type", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "kind": 193 + }, + { + "name": "JaNee", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "\"ja\" | \"nee\"", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

A BRANCHING wizard. Unlike herregistratie.machine (fixed 2 steps), the set of\nsteps here is NOT stored — it's derived from the answers by visibleSteps.\nAnswer "buiten Nederland gewerkt? → ja" and two extra steps appear; report few\nhours and a scholing-question appears. The progress bar's denominator changes\nas you type. "Which question comes next" is a pure function, so it's trivial\nto test and impossible to get out of sync with the data.

\n", + "kind": 193 + }, { "name": "Postcode", "ctype": "miscellaneous", @@ -5040,6 +6441,17 @@ "description": "

Just the discriminant — for atoms that only need the label/color.

\n", "kind": 200 }, + { + "name": "StepId", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "\"buitenland\" | \"buitenlandDetails\" | \"uren\" | \"scholing\" | \"punten\" | \"review\"", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

Every possible question, as a union — never a bare string.

\n", + "kind": 193 + }, { "name": "Uren", "ctype": "miscellaneous", @@ -5158,7 +6570,53 @@ "deprecated": false, "deprecationMessage": "", "type": "WizardState", - "defaultValue": "{ tag: 'Editing', step: 1, draft: { uren: '', punten: '' }, errors: {} }" + "defaultValue": "{ tag: 'Editing', step: 1, draft: { uren: '', jaren: '', punten: '' }, errors: {} }" + } + ], + "src/app/herregistratie/domain/intake.machine.ts": [ + { + "name": "initial", + "ctype": "miscellaneous", + "subtype": "variable", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "IntakeState", + "defaultValue": "{ tag: 'Answering', answers: {}, cursor: 0, errors: {} }" + }, + { + "name": "LAGE_UREN_DREMPEL", + "ctype": "miscellaneous", + "subtype": "variable", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "number", + "defaultValue": "1000", + "rawdescription": "Below this many NL-hours we ask whether extra scholing was followed.", + "description": "

Below this many NL-hours we ask whether extra scholing was followed.

\n" + } + ], + "src/app/herregistratie/ui/intake-wizard/intake-wizard.component.ts": [ + { + "name": "JA_NEE", + "ctype": "miscellaneous", + "subtype": "variable", + "file": "src/app/herregistratie/ui/intake-wizard/intake-wizard.component.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "[]", + "defaultValue": "[{ value: 'ja', label: 'Ja' }, { value: 'nee', label: 'Nee' }]" + }, + { + "name": "STORAGE_KEY", + "ctype": "miscellaneous", + "subtype": "variable", + "file": "src/app/herregistratie/ui/intake-wizard/intake-wizard.component.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "defaultValue": "'intake-v1'" } ], "src/app/app.routes.ts": [ @@ -5170,7 +6628,7 @@ "deprecated": false, "deprecationMessage": "", "type": "Routes", - "defaultValue": "[\n {\n path: '',\n component: ShellComponent, // persistent header/footer; only children swap\n children: [\n { path: '', pathMatch: 'full', redirectTo: 'login' },\n { path: 'login', loadComponent: () => \"import('@auth/ui/login.page').then(m => m.LoginPage)\" },\n { path: 'dashboard', canActivate: [authGuard], loadComponent: () => \"import('@registratie/ui/dashboard.page').then(m => m.DashboardPage)\" },\n { path: 'registratie', canActivate: [authGuard], loadComponent: () => \"import('@registratie/ui/registration-detail.page').then(m => m.RegistrationDetailPage)\" },\n { path: 'herregistratie', canActivate: [authGuard], loadComponent: () => \"import('@herregistratie/ui/herregistratie.page').then(m => m.HerregistratiePage)\" },\n { path: 'concepts', loadComponent: () => \"import('./showcase/concepts.page').then(m => m.ConceptsPage)\" },\n { path: '**', redirectTo: 'login' },\n ],\n },\n]" + "defaultValue": "[\n {\n path: '',\n component: ShellComponent, // persistent header/footer; only children swap\n children: [\n { path: '', pathMatch: 'full', redirectTo: 'login' },\n { path: 'login', loadComponent: () => \"import('@auth/ui/login.page').then(m => m.LoginPage)\" },\n { path: 'dashboard', canActivate: [authGuard], loadComponent: () => \"import('@registratie/ui/dashboard.page').then(m => m.DashboardPage)\" },\n { path: 'registratie', canActivate: [authGuard], loadComponent: () => \"import('@registratie/ui/registration-detail.page').then(m => m.RegistrationDetailPage)\" },\n { path: 'herregistratie', canActivate: [authGuard], loadComponent: () => \"import('@herregistratie/ui/herregistratie.page').then(m => m.HerregistratiePage)\" },\n { path: 'intake', canActivate: [authGuard], loadComponent: () => \"import('@herregistratie/ui/intake.page').then(m => m.IntakePage)\" },\n { path: 'concepts', loadComponent: () => \"import('./showcase/concepts.page').then(m => m.ConceptsPage)\" },\n { path: '**', redirectTo: 'login' },\n ],\n },\n]" } ], "src/app/shared/infrastructure/scenario.interceptor.ts": [ @@ -5570,7 +7028,7 @@ "subtype": "function", "deprecated": false, "deprecationMessage": "", - "description": "

Step 1 → 2: only advance if the uren field parses. Illegal elsewhere = no-op.

\n", + "description": "

Step 1 → 2: advance only when BOTH step-1 fields parse. Illegal elsewhere = no-op.

\n", "args": [ { "name": "s", @@ -5796,6 +7254,400 @@ ] } ], + "src/app/herregistratie/domain/intake.machine.ts": [ + { + "name": "back", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "IntakeState", + "jsdoctags": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "currentStep", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Which step the cursor currently points at (clamped to the live step list).

\n", + "args": [ + { + "name": "s", + "type": "Extract", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "StepId", + "jsdoctags": [ + { + "name": "s", + "type": "Extract", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "lageUren", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "a", + "type": "Answers", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "boolean", + "jsdoctags": [ + { + "name": "a", + "type": "Answers", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "next", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "IntakeState", + "jsdoctags": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "reduce", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "m", + "type": "IntakeMsg", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "IntakeState", + "jsdoctags": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "m", + "type": "IntakeMsg", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "resolve", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "r", + "type": "Result", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "IntakeState", + "jsdoctags": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "r", + "type": "Result", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "setAnswer", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "key", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "value", + "type": "string", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "IntakeState", + "jsdoctags": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "key", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "value", + "type": "string", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "submit", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "IntakeState", + "jsdoctags": [ + { + "name": "s", + "type": "IntakeState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "validateAll", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Parse the whole questionnaire into a ValidIntake (called on submit).

\n", + "args": [ + { + "name": "a", + "type": "Answers", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "Result>, ValidIntake>", + "jsdoctags": [ + { + "name": "a", + "type": "Answers", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "validateStep", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Validate ONE step's fields. Returns the per-field error keyed by StepId.

\n", + "args": [ + { + "name": "step", + "type": "StepId", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "a", + "type": "Answers", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "Result>, void>", + "jsdoctags": [ + { + "name": "step", + "type": "StepId", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "a", + "type": "Answers", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "visibleSteps", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

THE branching, as one pure function. The step list is recomputed on every\ntransition, so changing an earlier answer immediately adds/removes later steps.

\n", + "args": [ + { + "name": "a", + "type": "Answers", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "StepId[]", + "jsdoctags": [ + { + "name": "a", + "type": "Answers", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + } + ], "src/app/shared/application/store.ts": [ { "name": "createStore", @@ -6249,10 +8101,76 @@ } ] } + ], + "src/app/herregistratie/application/submit-intake.ts": [ + { + "name": "submitIntake", + "file": "src/app/herregistratie/application/submit-intake.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Command: send the intake questionnaire to the backend. Returns a Result so the\ncaller branches on success/failure without try/catch. ponytail: faked with a\ntimer; swap for a real POST when there's an API. The "uren must be > 0" rule\nlets the demo show the failure path.

\n", + "args": [ + { + "name": "data", + "type": "ValidIntake", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "Promise>", + "jsdoctags": [ + { + "name": "data", + "type": "ValidIntake", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + } ] }, "groupedEnumerations": {}, "groupedTypeAliases": { + "src/app/registratie/domain/registration.ts": [ + { + "name": "AantekeningType", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "\"Specialisme\" | \"Aantekening\"", + "file": "src/app/registratie/domain/registration.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

A note is either a recognised specialism or a plain annotation — a closed set,\nnot an open string, so a typo can't slip through.

\n", + "kind": 193 + }, + { + "name": "RegistrationStatus", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "literal type | literal type | literal type", + "file": "src/app/registratie/domain/registration.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

Registration status as a discriminated union: each variant owns exactly the\ndata that makes sense for it. Only an active (Geregistreerd) registration has\na herregistratie date; a struck-off (Doorgehaald) one cannot carry one. The\nold flat interface allowed that impossible combination — this makes it\nunrepresentable.

\n", + "kind": 193 + }, + { + "name": "StatusTag", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "RegistrationStatus", + "file": "src/app/registratie/domain/registration.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

Just the discriminant — for atoms that only need the label/color.

\n", + "kind": 200 + } + ], "src/app/shared/ui/alert/alert.component.ts": [ { "name": "AlertType", @@ -6316,6 +8234,52 @@ "kind": 193 } ], + "src/app/herregistratie/domain/intake.machine.ts": [ + { + "name": "IntakeMsg", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "literal type | literal type | literal type | literal type | literal type | literal type | literal type | literal type", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "kind": 193 + }, + { + "name": "IntakeState", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "literal type | literal type | literal type | literal type", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "kind": 193 + }, + { + "name": "JaNee", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "\"ja\" | \"nee\"", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

A BRANCHING wizard. Unlike herregistratie.machine (fixed 2 steps), the set of\nsteps here is NOT stored — it's derived from the answers by visibleSteps.\nAnswer "buiten Nederland gewerkt? → ja" and two extra steps appear; report few\nhours and a scholing-question appears. The progress bar's denominator changes\nas you type. "Which question comes next" is a pure function, so it's trivial\nto test and impossible to get out of sync with the data.

\n", + "kind": 193 + }, + { + "name": "StepId", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "\"buitenland\" | \"buitenlandDetails\" | \"uren\" | \"scholing\" | \"punten\" | \"review\"", + "file": "src/app/herregistratie/domain/intake.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

Every possible question, as a union — never a bare string.

\n", + "kind": 193 + } + ], "src/app/registratie/domain/value-objects/postcode.ts": [ { "name": "Postcode", @@ -6329,30 +8293,6 @@ "kind": 184 } ], - "src/app/registratie/domain/registration.ts": [ - { - "name": "RegistrationStatus", - "ctype": "miscellaneous", - "subtype": "typealias", - "rawtype": "literal type | literal type | literal type", - "file": "src/app/registratie/domain/registration.ts", - "deprecated": false, - "deprecationMessage": "", - "description": "

Registration status as a discriminated union: each variant owns exactly the\ndata that makes sense for it. Only an active (Geregistreerd) registration has\na herregistratie date; a struck-off (Doorgehaald) one cannot carry one. The\nold flat interface allowed that impossible combination — this makes it\nunrepresentable.

\n", - "kind": 193 - }, - { - "name": "StatusTag", - "ctype": "miscellaneous", - "subtype": "typealias", - "rawtype": "RegistrationStatus", - "file": "src/app/registratie/domain/registration.ts", - "deprecated": false, - "deprecationMessage": "", - "description": "

Just the discriminant — for atoms that only need the label/color.

\n", - "kind": 200 - } - ], "src/app/shared/application/remote-data.ts": [ { "name": "RemoteData", @@ -6460,6 +8400,11 @@ "kind": "route-path", "filename": "src/app/app.routes.ts" }, + { + "name": "intake", + "kind": "route-path", + "filename": "src/app/app.routes.ts" + }, { "name": "concepts", "kind": "route-path", @@ -6483,7 +8428,7 @@ ] }, "coverage": { - "count": 47, + "count": 45, "status": "medium", "files": [ { @@ -6590,23 +8535,33 @@ "coverageCount": "1/1", "status": "very-good" }, + { + "filePath": "src/app/herregistratie/application/submit-intake.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "submitIntake", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, { "filePath": "src/app/herregistratie/domain/herregistratie.machine.ts", "type": "interface", "linktype": "interface", "name": "Draft", - "coveragePercent": 33, - "coverageCount": "1/3", - "status": "medium" + "coveragePercent": 25, + "coverageCount": "1/4", + "status": "low" }, { "filePath": "src/app/herregistratie/domain/herregistratie.machine.ts", "type": "interface", "linktype": "interface", "name": "Valid", - "coveragePercent": 33, - "coverageCount": "1/3", - "status": "medium" + "coveragePercent": 25, + "coverageCount": "1/4", + "status": "low" }, { "filePath": "src/app/herregistratie/domain/herregistratie.machine.ts", @@ -6708,13 +8663,201 @@ "coverageCount": "1/1", "status": "very-good" }, + { + "filePath": "src/app/herregistratie/domain/intake.machine.ts", + "type": "interface", + "linktype": "interface", + "name": "Answers", + "coveragePercent": 14, + "coverageCount": "1/7", + "status": "low" + }, + { + "filePath": "src/app/herregistratie/domain/intake.machine.ts", + "type": "interface", + "linktype": "interface", + "name": "ValidIntake", + "coveragePercent": 14, + "coverageCount": "1/7", + "status": "low" + }, + { + "filePath": "src/app/herregistratie/domain/intake.machine.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "back", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/herregistratie/domain/intake.machine.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "currentStep", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/herregistratie/domain/intake.machine.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "lageUren", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/herregistratie/domain/intake.machine.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "next", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/herregistratie/domain/intake.machine.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "reduce", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/herregistratie/domain/intake.machine.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "resolve", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/herregistratie/domain/intake.machine.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "setAnswer", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/herregistratie/domain/intake.machine.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "submit", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/herregistratie/domain/intake.machine.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "validateAll", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/herregistratie/domain/intake.machine.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "validateStep", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/herregistratie/domain/intake.machine.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "visibleSteps", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/herregistratie/domain/intake.machine.ts", + "type": "variable", + "linktype": "miscellaneous", + "linksubtype": "variable", + "name": "initial", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/herregistratie/domain/intake.machine.ts", + "type": "variable", + "linktype": "miscellaneous", + "linksubtype": "variable", + "name": "LAGE_UREN_DREMPEL", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/herregistratie/domain/intake.machine.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "IntakeMsg", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/herregistratie/domain/intake.machine.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "IntakeState", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/herregistratie/domain/intake.machine.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "JaNee", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/herregistratie/domain/intake.machine.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "StepId", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, { "filePath": "src/app/herregistratie/ui/herregistratie-wizard/herregistratie-wizard.component.ts", "type": "component", "linktype": "component", "name": "HerregistratieWizardComponent", - "coveragePercent": 18, - "coverageCount": "3/16", + "coveragePercent": 17, + "coverageCount": "3/17", "status": "low" }, { @@ -6726,6 +8869,44 @@ "coverageCount": "1/3", "status": "medium" }, + { + "filePath": "src/app/herregistratie/ui/intake-wizard/intake-wizard.component.ts", + "type": "component", + "linktype": "component", + "name": "IntakeWizardComponent", + "coveragePercent": 19, + "coverageCount": "4/21", + "status": "low" + }, + { + "filePath": "src/app/herregistratie/ui/intake-wizard/intake-wizard.component.ts", + "type": "variable", + "linktype": "miscellaneous", + "linksubtype": "variable", + "name": "JA_NEE", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/herregistratie/ui/intake-wizard/intake-wizard.component.ts", + "type": "variable", + "linktype": "miscellaneous", + "linksubtype": "variable", + "name": "STORAGE_KEY", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/herregistratie/ui/intake.page.ts", + "type": "component", + "linktype": "component", + "name": "IntakePage", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, { "filePath": "src/app/registratie/application/big-profile.store.ts", "type": "injectable", @@ -6840,6 +9021,16 @@ "coverageCount": "0/7", "status": "low" }, + { + "filePath": "src/app/registratie/domain/registration.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "AantekeningType", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, { "filePath": "src/app/registratie/domain/registration.ts", "type": "type alias", @@ -7336,6 +9527,24 @@ "coverageCount": "1/2", "status": "medium" }, + { + "filePath": "src/app/shared/ui/radio-group/radio-group.component.ts", + "type": "component", + "linktype": "component", + "name": "RadioGroupComponent", + "coveragePercent": 8, + "coverageCount": "1/12", + "status": "low" + }, + { + "filePath": "src/app/shared/ui/radio-group/radio-group.component.ts", + "type": "interface", + "linktype": "interface", + "name": "RadioOption", + "coveragePercent": 0, + "coverageCount": "0/3", + "status": "low" + }, { "filePath": "src/app/shared/ui/skeleton/skeleton.component.ts", "type": "component",