diff --git a/documentation.json b/documentation.json
index 5417ace..4fbdf98 100644
--- a/documentation.json
+++ b/documentation.json
@@ -3,12 +3,12 @@
"interfaces": [
{
"name": "Aantekening",
- "id": "interface-Aantekening-ad224cc8184441fa9923164457d2a4916359232fbcc5bc89a42031573682d3c39f25d7596de394302d8d9e7a7c2157118f79ba2c6dc80ebbf36b836a6d7db8df",
+ "id": "interface-Aantekening-2ecca36e413e22c1c5967d07b811fa4171ff578eb6b47a7275723bcca44e93a2db27d77a765b82e5f6182492464cc8b9268c9724e13b400718d30da7bc4ba50c",
"file": "src/app/core/models.ts",
"deprecated": false,
"deprecationMessage": "",
"type": "interface",
- "sourceCode": "export type RegistrationStatus = 'Geregistreerd' | 'Doorgehaald' | 'Geschorst';\n\nexport interface Registration {\n bigNummer: string;\n naam: string;\n beroep: string; // arts, verpleegkundige, apotheker, ...\n status: RegistrationStatus;\n registratiedatum: string; // ISO date\n herregistratieDatum: string;\n geboortedatum: string;\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\nexport interface Aantekening {\n type: string; // specialisme of aantekening\n omschrijving: string;\n datum: string;\n}\n",
"properties": [
{
"name": "datum",
@@ -18,7 +18,7 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 16
+ "line": 28
},
{
"name": "omschrijving",
@@ -28,7 +28,7 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 15
+ "line": 27
},
{
"name": "type",
@@ -38,7 +38,7 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 14
+ "line": 26
}
],
"indexSignatures": [],
@@ -46,14 +46,98 @@
"methods": [],
"extends": []
},
+ {
+ "name": "ChangeRequest",
+ "id": "interface-ChangeRequest-e366867f9f2a82d4c6cafc6fbdce9749e7775f651192c4de62cefc40a237a6e2c6772537e64a4aeeb6667ffa33d9a69ee6114c69160674807408090b1cbc283a",
+ "file": "src/app/organisms/change-request-form/change-request-form.component.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "interface",
+ "sourceCode": "import { Component, output, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { FormFieldComponent } from '../../molecules/form-field/form-field.component';\nimport { TextInputComponent } from '../../atoms/text-input/text-input.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { HeadingComponent } from '../../atoms/heading/heading.component';\nimport { Postcode, parsePostcode } from '../../core/parse';\n\n/** A submitted change request carries a *parsed* postcode (branded Postcode),\n not a raw string — downstream code can't receive an unvalidated one. */\nexport interface ChangeRequest {\n street: string;\n zip: Postcode;\n city: string;\n}\n\n/** Organism: change-request (adreswijziging) form. Reuses the same form-field\n molecule + text-input/button atoms as the login form. Field errors come\n straight from the parser's Result — no parallel \"is it valid\" flag to drift. */\n@Component({\n selector: 'app-change-request-form',\n imports: [FormsModule, FormFieldComponent, TextInputComponent, ButtonComponent, HeadingComponent],\n template: `\n Adreswijziging doorgeven\n
\n `,\n})\nexport class ChangeRequestFormComponent {\n street = '';\n zip = '';\n city = '';\n streetError = signal('');\n zipError = signal('');\n submitted = output();\n\n onSubmit() {\n const street = this.street.trim();\n this.streetError.set(street ? '' : 'Vul straat en huisnummer in.');\n\n const postcode = parsePostcode(this.zip);\n this.zipError.set(postcode.ok ? '' : postcode.error);\n\n if (!street || !postcode.ok) return;\n this.submitted.emit({ street, zip: postcode.value, city: this.city.trim() });\n }\n}\n",
+ "properties": [
+ {
+ "name": "city",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "string",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 14
+ },
+ {
+ "name": "street",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "string",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 12
+ },
+ {
+ "name": "zip",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "Postcode",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 13
+ }
+ ],
+ "indexSignatures": [],
+ "kind": 172,
+ "description": "A submitted change request carries a parsed postcode (branded Postcode),\nnot a raw string — downstream code can't receive an unvalidated one.
\n",
+ "rawdescription": "\nA submitted change request carries a *parsed* postcode (branded Postcode),\nnot a raw string — downstream code can't receive an unvalidated one.",
+ "methods": [],
+ "extends": []
+ },
+ {
+ "name": "Draft",
+ "id": "interface-Draft-6fcaabe58c126a645622a7a77942a847877e8d80e60ddd45cb9ba4c247cede3ee197def542b645ef02b0ce5dd0deab6c644edddf2f37dec67a129b27ba53bb23",
+ "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "interface",
+ "sourceCode": "import { Result } from '../../core/fp';\nimport { Uren, parseUren } from '../../core/parse';\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",
+ "properties": [
+ {
+ "name": "punten",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "string",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 7
+ },
+ {
+ "name": "uren",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "string",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 6
+ }
+ ],
+ "indexSignatures": [],
+ "kind": 172,
+ "description": "What the user is typing (raw, possibly invalid).
\n",
+ "rawdescription": "\nWhat the user is typing (raw, possibly invalid).",
+ "methods": [],
+ "extends": []
+ },
{
"name": "Registration",
- "id": "interface-Registration-ad224cc8184441fa9923164457d2a4916359232fbcc5bc89a42031573682d3c39f25d7596de394302d8d9e7a7c2157118f79ba2c6dc80ebbf36b836a6d7db8df",
+ "id": "interface-Registration-2ecca36e413e22c1c5967d07b811fa4171ff578eb6b47a7275723bcca44e93a2db27d77a765b82e5f6182492464cc8b9268c9724e13b400718d30da7bc4ba50c",
"file": "src/app/core/models.ts",
"deprecated": false,
"deprecationMessage": "",
"type": "interface",
- "sourceCode": "export type RegistrationStatus = 'Geregistreerd' | 'Doorgehaald' | 'Geschorst';\n\nexport interface Registration {\n bigNummer: string;\n naam: string;\n beroep: string; // arts, verpleegkundige, apotheker, ...\n status: RegistrationStatus;\n registratiedatum: string; // ISO date\n herregistratieDatum: string;\n geboortedatum: string;\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\nexport interface Aantekening {\n type: string; // specialisme of aantekening\n omschrijving: string;\n datum: string;\n}\n",
"properties": [
{
"name": "beroep",
@@ -63,7 +147,7 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 6
+ "line": 19
},
{
"name": "bigNummer",
@@ -73,7 +157,7 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 4
+ "line": 17
},
{
"name": "geboortedatum",
@@ -83,17 +167,7 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 10
- },
- {
- "name": "herregistratieDatum",
- "deprecated": false,
- "deprecationMessage": "",
- "type": "string",
- "indexKey": "",
- "optional": false,
- "description": "",
- "line": 9
+ "line": 21
},
{
"name": "naam",
@@ -103,7 +177,7 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 5
+ "line": 18
},
{
"name": "registratiedatum",
@@ -113,7 +187,7 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 8
+ "line": 20
},
{
"name": "status",
@@ -123,13 +197,50 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 7
+ "line": 22
}
],
"indexSignatures": [],
"kind": 172,
"methods": [],
"extends": []
+ },
+ {
+ "name": "Valid",
+ "id": "interface-Valid-6fcaabe58c126a645622a7a77942a847877e8d80e60ddd45cb9ba4c247cede3ee197def542b645ef02b0ce5dd0deab6c644edddf2f37dec67a129b27ba53bb23",
+ "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "interface",
+ "sourceCode": "import { Result } from '../../core/fp';\nimport { Uren, parseUren } from '../../core/parse';\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",
+ "properties": [
+ {
+ "name": "punten",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "number",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 13
+ },
+ {
+ "name": "uren",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "Uren",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 12
+ }
+ ],
+ "indexSignatures": [],
+ "kind": 172,
+ "description": "What we have AFTER parsing — branded/typed, guaranteed valid.
\n",
+ "rawdescription": "\nWhat we have AFTER parsing — branded/typed, guaranteed valid.",
+ "methods": [],
+ "extends": []
}
],
"injectables": [
@@ -175,12 +286,12 @@
"directives": [
{
"name": "AsyncEmptyDirective",
- "id": "directive-AsyncEmptyDirective-2f8f54349a396c3f59c90536339a2de5b28ea23c435f301c585563e8dcd7e5652d6b6dee37cd8065a1f9573e0fbbc44511e14a02310dba1fd9d0a8354991e759",
+ "id": "directive-AsyncEmptyDirective-2c3bb2a3fee90c1c02ec41aacfd672fcdf754eccc5e8761a7590b96d19724dc55523a26a1eff26c0caeaf28a297042b8fdf7e78d70272079bd5465f72e58dbc9",
"file": "src/app/molecules/async/async.component.ts",
"type": "directive",
"description": "",
"rawdescription": "\n",
- "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\ntype State = 'loading' | 'error' | 'empty' | 'loaded';\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). The states are mutually exclusive by\n * construction, so the UI can never show two at once (\"impossible states\").\n * Unprovided slots fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (state()) {\n @case ('loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('error') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n \n }\n }\n @case ('empty') {\n @if (emptyTpl()) { }\n @else { Geen gegevens gevonden.
}\n }\n @case ('loaded') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n protected value = computed(() => {\n const r = this.resource();\n return r.hasValue() ? r.value() : undefined;\n });\n\n protected state = computed(() => {\n const r = this.resource();\n if (r.status() === 'error') return 'error';\n if (r.status() === 'loading') return 'loading';\n if (r.hasValue()) return this.isEmpty()(r.value()) ? 'empty' : 'loaded';\n return 'loading';\n });\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n",
+ "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { fromResource, foldRemote } from '../../core/remote-data';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). Built on a RemoteData tagged union (see\n * core/remote-data.ts), so the states are mutually exclusive by construction —\n * the UI can never show two at once (\"impossible states\"). Unprovided slots\n * fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n \n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else { Geen gegevens gevonden.
}\n }\n @case ('Success') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n // Single source of truth: the resource projected into a RemoteData union.\n protected rd = computed(() => fromResource(this.resource(), this.isEmpty()));\n\n // value/error are pulled out via the exhaustive fold — only Success carries a\n // value, only Failure carries an error, so these can't lie.\n protected value = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n );\n protected error = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n );\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n",
"selector": "[appAsyncEmpty]",
"providers": [],
"hostDirectives": [],
@@ -200,7 +311,7 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 19,
+ "line": 20,
"modifierKind": [
125
]
@@ -223,7 +334,7 @@
"deprecationMessage": ""
}
],
- "line": 18,
+ "line": 19,
"jsdoctags": [
{
"name": "tpl",
@@ -241,12 +352,12 @@
},
{
"name": "AsyncErrorDirective",
- "id": "directive-AsyncErrorDirective-2f8f54349a396c3f59c90536339a2de5b28ea23c435f301c585563e8dcd7e5652d6b6dee37cd8065a1f9573e0fbbc44511e14a02310dba1fd9d0a8354991e759",
+ "id": "directive-AsyncErrorDirective-2c3bb2a3fee90c1c02ec41aacfd672fcdf754eccc5e8761a7590b96d19724dc55523a26a1eff26c0caeaf28a297042b8fdf7e78d70272079bd5465f72e58dbc9",
"file": "src/app/molecules/async/async.component.ts",
"type": "directive",
"description": "",
"rawdescription": "\n",
- "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\ntype State = 'loading' | 'error' | 'empty' | 'loaded';\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). The states are mutually exclusive by\n * construction, so the UI can never show two at once (\"impossible states\").\n * Unprovided slots fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (state()) {\n @case ('loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('error') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n \n }\n }\n @case ('empty') {\n @if (emptyTpl()) { }\n @else { Geen gegevens gevonden.
}\n }\n @case ('loaded') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n protected value = computed(() => {\n const r = this.resource();\n return r.hasValue() ? r.value() : undefined;\n });\n\n protected state = computed(() => {\n const r = this.resource();\n if (r.status() === 'error') return 'error';\n if (r.status() === 'loading') return 'loading';\n if (r.hasValue()) return this.isEmpty()(r.value()) ? 'empty' : 'loaded';\n return 'loading';\n });\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n",
+ "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { fromResource, foldRemote } from '../../core/remote-data';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). Built on a RemoteData tagged union (see\n * core/remote-data.ts), so the states are mutually exclusive by construction —\n * the UI can never show two at once (\"impossible states\"). Unprovided slots\n * fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n \n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else { Geen gegevens gevonden.
}\n }\n @case ('Success') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n // Single source of truth: the resource projected into a RemoteData union.\n protected rd = computed(() => fromResource(this.resource(), this.isEmpty()));\n\n // value/error are pulled out via the exhaustive fold — only Success carries a\n // value, only Failure carries an error, so these can't lie.\n protected value = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n );\n protected error = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n );\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n",
"selector": "[appAsyncError]",
"providers": [],
"hostDirectives": [],
@@ -266,7 +377,7 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 23,
+ "line": 24,
"modifierKind": [
125
]
@@ -289,7 +400,7 @@
"deprecationMessage": ""
}
],
- "line": 22,
+ "line": 23,
"jsdoctags": [
{
"name": "tpl",
@@ -307,12 +418,12 @@
},
{
"name": "AsyncLoadedDirective",
- "id": "directive-AsyncLoadedDirective-2f8f54349a396c3f59c90536339a2de5b28ea23c435f301c585563e8dcd7e5652d6b6dee37cd8065a1f9573e0fbbc44511e14a02310dba1fd9d0a8354991e759",
+ "id": "directive-AsyncLoadedDirective-2c3bb2a3fee90c1c02ec41aacfd672fcdf754eccc5e8761a7590b96d19724dc55523a26a1eff26c0caeaf28a297042b8fdf7e78d70272079bd5465f72e58dbc9",
"file": "src/app/molecules/async/async.component.ts",
"type": "directive",
"description": "",
"rawdescription": "\n",
- "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\ntype State = 'loading' | 'error' | 'empty' | 'loaded';\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). The states are mutually exclusive by\n * construction, so the UI can never show two at once (\"impossible states\").\n * Unprovided slots fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (state()) {\n @case ('loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('error') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n \n }\n }\n @case ('empty') {\n @if (emptyTpl()) { }\n @else { Geen gegevens gevonden.
}\n }\n @case ('loaded') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n protected value = computed(() => {\n const r = this.resource();\n return r.hasValue() ? r.value() : undefined;\n });\n\n protected state = computed(() => {\n const r = this.resource();\n if (r.status() === 'error') return 'error';\n if (r.status() === 'loading') return 'loading';\n if (r.hasValue()) return this.isEmpty()(r.value()) ? 'empty' : 'loaded';\n return 'loading';\n });\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n",
+ "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { fromResource, foldRemote } from '../../core/remote-data';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). Built on a RemoteData tagged union (see\n * core/remote-data.ts), so the states are mutually exclusive by construction —\n * the UI can never show two at once (\"impossible states\"). Unprovided slots\n * fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n \n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else { Geen gegevens gevonden.
}\n }\n @case ('Success') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n // Single source of truth: the resource projected into a RemoteData union.\n protected rd = computed(() => fromResource(this.resource(), this.isEmpty()));\n\n // value/error are pulled out via the exhaustive fold — only Success carries a\n // value, only Failure carries an error, so these can't lie.\n protected value = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n );\n protected error = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n );\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n",
"selector": "[appAsyncLoaded]",
"providers": [],
"hostDirectives": [],
@@ -332,7 +443,7 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 11,
+ "line": 12,
"modifierKind": [
125
]
@@ -355,7 +466,7 @@
"deprecationMessage": ""
}
],
- "line": 10,
+ "line": 11,
"jsdoctags": [
{
"name": "tpl",
@@ -373,12 +484,12 @@
},
{
"name": "AsyncLoadingDirective",
- "id": "directive-AsyncLoadingDirective-2f8f54349a396c3f59c90536339a2de5b28ea23c435f301c585563e8dcd7e5652d6b6dee37cd8065a1f9573e0fbbc44511e14a02310dba1fd9d0a8354991e759",
+ "id": "directive-AsyncLoadingDirective-2c3bb2a3fee90c1c02ec41aacfd672fcdf754eccc5e8761a7590b96d19724dc55523a26a1eff26c0caeaf28a297042b8fdf7e78d70272079bd5465f72e58dbc9",
"file": "src/app/molecules/async/async.component.ts",
"type": "directive",
"description": "",
"rawdescription": "\n",
- "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\ntype State = 'loading' | 'error' | 'empty' | 'loaded';\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). The states are mutually exclusive by\n * construction, so the UI can never show two at once (\"impossible states\").\n * Unprovided slots fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (state()) {\n @case ('loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('error') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n \n }\n }\n @case ('empty') {\n @if (emptyTpl()) { }\n @else { Geen gegevens gevonden.
}\n }\n @case ('loaded') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n protected value = computed(() => {\n const r = this.resource();\n return r.hasValue() ? r.value() : undefined;\n });\n\n protected state = computed(() => {\n const r = this.resource();\n if (r.status() === 'error') return 'error';\n if (r.status() === 'loading') return 'loading';\n if (r.hasValue()) return this.isEmpty()(r.value()) ? 'empty' : 'loaded';\n return 'loading';\n });\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n",
+ "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { fromResource, foldRemote } from '../../core/remote-data';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). Built on a RemoteData tagged union (see\n * core/remote-data.ts), so the states are mutually exclusive by construction —\n * the UI can never show two at once (\"impossible states\"). Unprovided slots\n * fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n \n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else { Geen gegevens gevonden.
}\n }\n @case ('Success') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n // Single source of truth: the resource projected into a RemoteData union.\n protected rd = computed(() => fromResource(this.resource(), this.isEmpty()));\n\n // value/error are pulled out via the exhaustive fold — only Success carries a\n // value, only Failure carries an error, so these can't lie.\n protected value = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n );\n protected error = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n );\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n",
"selector": "[appAsyncLoading]",
"providers": [],
"hostDirectives": [],
@@ -398,7 +509,7 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 15,
+ "line": 16,
"modifierKind": [
125
]
@@ -421,7 +532,7 @@
"deprecationMessage": ""
}
],
- "line": 14,
+ "line": 15,
"jsdoctags": [
{
"name": "tpl",
@@ -528,7 +639,7 @@
},
{
"name": "AsyncComponent",
- "id": "component-AsyncComponent-2f8f54349a396c3f59c90536339a2de5b28ea23c435f301c585563e8dcd7e5652d6b6dee37cd8065a1f9573e0fbbc44511e14a02310dba1fd9d0a8354991e759",
+ "id": "component-AsyncComponent-2c3bb2a3fee90c1c02ec41aacfd672fcdf754eccc5e8761a7590b96d19724dc55523a26a1eff26c0caeaf28a297042b8fdf7e78d70272079bd5465f72e58dbc9",
"file": "src/app/molecules/async/async.component.ts",
"encapsulation": [],
"entryComponents": [],
@@ -538,7 +649,7 @@
"selector": "app-async",
"styleUrls": [],
"styles": [],
- "template": "@switch (state()) {\n @case ('loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('error') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n \n }\n }\n @case ('empty') {\n @if (emptyTpl()) { }\n @else { Geen gegevens gevonden.
}\n }\n @case ('loaded') {\n \n }\n}\n",
+ "template": "@switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n \n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else { Geen gegevens gevonden.
}\n }\n @case ('Success') {\n \n }\n}\n",
"templateUrl": [],
"viewProviders": [],
"hostDirectives": [],
@@ -580,6 +691,20 @@
"description": "",
"line": 71
},
+ {
+ "name": "error",
+ "defaultValue": "computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n )",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "unknown",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 82,
+ "modifierKind": [
+ 124
+ ]
+ },
{
"name": "errorTpl",
"defaultValue": "contentChild(AsyncErrorDirective)",
@@ -613,6 +738,20 @@
"description": "",
"line": 70
},
+ {
+ "name": "rd",
+ "defaultValue": "computed(() => fromResource(this.resource(), this.isEmpty()))",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "unknown",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 75,
+ "modifierKind": [
+ 124
+ ]
+ },
{
"name": "retry",
"defaultValue": "() => {...}",
@@ -622,11 +761,11 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 87
+ "line": 86
},
{
- "name": "state",
- "defaultValue": "computed(() => {\n const r = this.resource();\n if (r.status() === 'error') return 'error';\n if (r.status() === 'loading') return 'loading';\n if (r.hasValue()) return this.isEmpty()(r.value()) ? 'empty' : 'loaded';\n return 'loading';\n })",
+ "name": "value",
+ "defaultValue": "computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n )",
"deprecated": false,
"deprecationMessage": "",
"type": "unknown",
@@ -637,20 +776,6 @@
"modifierKind": [
124
]
- },
- {
- "name": "value",
- "defaultValue": "computed(() => {\n const r = this.resource();\n return r.hasValue() ? r.value() : undefined;\n })",
- "deprecated": false,
- "deprecationMessage": "",
- "type": "unknown",
- "indexKey": "",
- "optional": false,
- "description": "",
- "line": 74,
- "modifierKind": [
- 124
- ]
}
],
"methodsClass": [],
@@ -676,10 +801,10 @@
"type": "component"
}
],
- "description": "Renders exactly ONE of loading / empty / error / loaded for a signal-based\nresource (e.g. httpResource). The states are mutually exclusive by\nconstruction, so the UI can never show two at once ("impossible states").\nUnprovided slots fall back to sensible defaults.
\n",
- "rawdescription": "\n\nRenders exactly ONE of loading / empty / error / loaded for a signal-based\nresource (e.g. httpResource). The states are mutually exclusive by\nconstruction, so the UI can never show two at once (\"impossible states\").\nUnprovided slots fall back to sensible defaults.\n",
+ "description": "Renders exactly ONE of loading / empty / error / loaded for a signal-based\nresource (e.g. httpResource). Built on a RemoteData tagged union (see\ncore/remote-data.ts), so the states are mutually exclusive by construction —\nthe UI can never show two at once ("impossible states"). Unprovided slots\nfall back to sensible defaults.
\n",
+ "rawdescription": "\n\nRenders exactly ONE of loading / empty / error / loaded for a signal-based\nresource (e.g. httpResource). Built on a RemoteData tagged union (see\ncore/remote-data.ts), so the states are mutually exclusive by construction —\nthe UI can never show two at once (\"impossible states\"). Unprovided slots\nfall back to sensible defaults.\n",
"type": "component",
- "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\ntype State = 'loading' | 'error' | 'empty' | 'loaded';\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). The states are mutually exclusive by\n * construction, so the UI can never show two at once (\"impossible states\").\n * Unprovided slots fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (state()) {\n @case ('loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('error') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n \n }\n }\n @case ('empty') {\n @if (emptyTpl()) { }\n @else { Geen gegevens gevonden.
}\n }\n @case ('loaded') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n protected value = computed(() => {\n const r = this.resource();\n return r.hasValue() ? r.value() : undefined;\n });\n\n protected state = computed(() => {\n const r = this.resource();\n if (r.status() === 'error') return 'error';\n if (r.status() === 'loading') return 'loading';\n if (r.hasValue()) return this.isEmpty()(r.value()) ? 'empty' : 'loaded';\n return 'loading';\n });\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n",
+ "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { fromResource, foldRemote } from '../../core/remote-data';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). Built on a RemoteData tagged union (see\n * core/remote-data.ts), so the states are mutually exclusive by construction —\n * the UI can never show two at once (\"impossible states\"). Unprovided slots\n * fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n \n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else { Geen gegevens gevonden.
}\n }\n @case ('Success') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n // Single source of truth: the resource projected into a RemoteData union.\n protected rd = computed(() => fromResource(this.resource(), this.isEmpty()));\n\n // value/error are pulled out via the exhaustive fold — only Success carries a\n // value, only Failure carries an error, so these can't lie.\n protected value = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n );\n protected error = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n );\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n",
"assetsDirs": [],
"styleUrlsData": "",
"stylesData": "",
@@ -758,7 +883,7 @@
},
{
"name": "ChangeRequestFormComponent",
- "id": "component-ChangeRequestFormComponent-81277b5d39f6d8389da0d8313e3b14f50df8058e30513eca8776b01c490aed300ca483ab7ea00363d41e0a98360a792cc030698f4bf3918e14719ce4b08f64fa",
+ "id": "component-ChangeRequestFormComponent-e366867f9f2a82d4c6cafc6fbdce9749e7775f651192c4de62cefc40a237a6e2c6772537e64a4aeeb6667ffa33d9a69ee6114c69160674807408090b1cbc283a",
"file": "src/app/organisms/change-request-form/change-request-form.component.ts",
"encapsulation": [],
"entryComponents": [],
@@ -768,7 +893,7 @@
"selector": "app-change-request-form",
"styleUrls": [],
"styles": [],
- "template": "Adreswijziging doorgeven\n\n",
+ "template": "Adreswijziging doorgeven\n\n",
"templateUrl": [],
"viewProviders": [],
"hostDirectives": [],
@@ -778,11 +903,11 @@
"name": "submitted",
"deprecated": false,
"deprecationMessage": "",
- "type": "void",
+ "type": "ChangeRequest",
"indexKey": "",
"optional": false,
"description": "",
- "line": 36,
+ "line": 47,
"required": false
}
],
@@ -796,7 +921,7 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 34
+ "line": 44
},
{
"name": "street",
@@ -807,7 +932,7 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 32
+ "line": 42
},
{
"name": "streetError",
@@ -818,7 +943,7 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 35
+ "line": 45
},
{
"name": "zip",
@@ -829,7 +954,18 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 33
+ "line": 43
+ },
+ {
+ "name": "zipError",
+ "defaultValue": "signal('')",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "unknown",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 46
}
],
"methodsClass": [
@@ -839,7 +975,7 @@
"optional": false,
"returnType": "void",
"typeParameters": [],
- "line": 38,
+ "line": 49,
"deprecated": false,
"deprecationMessage": ""
}
@@ -871,18 +1007,213 @@
"type": "component"
}
],
- "description": "Organism: change-request (adreswijziging) form. Reuses the same form-field\nmolecule + text-input/button atoms as the login form.
\n",
- "rawdescription": "\nOrganism: change-request (adreswijziging) form. Reuses the same form-field\nmolecule + text-input/button atoms as the login form.",
+ "description": "Organism: change-request (adreswijziging) form. Reuses the same form-field\nmolecule + text-input/button atoms as the login form. Field errors come\nstraight from the parser's Result — no parallel "is it valid" flag to drift.
\n",
+ "rawdescription": "\nOrganism: change-request (adreswijziging) form. Reuses the same form-field\nmolecule + text-input/button atoms as the login form. Field errors come\nstraight from the parser's Result — no parallel \"is it valid\" flag to drift.",
"type": "component",
- "sourceCode": "import { Component, output, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { FormFieldComponent } from '../../molecules/form-field/form-field.component';\nimport { TextInputComponent } from '../../atoms/text-input/text-input.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { HeadingComponent } from '../../atoms/heading/heading.component';\n\n/** Organism: change-request (adreswijziging) form. Reuses the same form-field\n molecule + text-input/button atoms as the login form. */\n@Component({\n selector: 'app-change-request-form',\n imports: [FormsModule, FormFieldComponent, TextInputComponent, ButtonComponent, HeadingComponent],\n template: `\n Adreswijziging doorgeven\n \n `,\n})\nexport class ChangeRequestFormComponent {\n street = '';\n zip = '';\n city = '';\n streetError = signal('');\n submitted = output();\n\n onSubmit() {\n if (!this.street.trim()) { this.streetError.set('Vul straat en huisnummer in.'); return; }\n this.streetError.set('');\n this.submitted.emit();\n }\n}\n",
+ "sourceCode": "import { Component, output, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { FormFieldComponent } from '../../molecules/form-field/form-field.component';\nimport { TextInputComponent } from '../../atoms/text-input/text-input.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { HeadingComponent } from '../../atoms/heading/heading.component';\nimport { Postcode, parsePostcode } from '../../core/parse';\n\n/** A submitted change request carries a *parsed* postcode (branded Postcode),\n not a raw string — downstream code can't receive an unvalidated one. */\nexport interface ChangeRequest {\n street: string;\n zip: Postcode;\n city: string;\n}\n\n/** Organism: change-request (adreswijziging) form. Reuses the same form-field\n molecule + text-input/button atoms as the login form. Field errors come\n straight from the parser's Result — no parallel \"is it valid\" flag to drift. */\n@Component({\n selector: 'app-change-request-form',\n imports: [FormsModule, FormFieldComponent, TextInputComponent, ButtonComponent, HeadingComponent],\n template: `\n Adreswijziging doorgeven\n \n `,\n})\nexport class ChangeRequestFormComponent {\n street = '';\n zip = '';\n city = '';\n streetError = signal('');\n zipError = signal('');\n submitted = output();\n\n onSubmit() {\n const street = this.street.trim();\n this.streetError.set(street ? '' : 'Vul straat en huisnummer in.');\n\n const postcode = parsePostcode(this.zip);\n this.zipError.set(postcode.ok ? '' : postcode.error);\n\n if (!street || !postcode.ok) return;\n this.submitted.emit({ street, zip: postcode.value, city: this.city.trim() });\n }\n}\n",
"assetsDirs": [],
"styleUrlsData": "",
"stylesData": "",
"extends": []
},
+ {
+ "name": "ConceptsPage",
+ "id": "component-ConceptsPage-6ebc285de6e31b814c5a5499d296626fc51bce08971ae012bfa0176580e767b207f13bbc91e38ad1d616cb28e386208e7686eef9bdf8f5e0ddf89b7ee67362cb",
+ "file": "src/app/pages/concepts/concepts.page.ts",
+ "encapsulation": [],
+ "entryComponents": [],
+ "inputs": [],
+ "outputs": [],
+ "providers": [],
+ "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 "
+ ],
+ "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",
+ "templateUrl": [],
+ "viewProviders": [],
+ "hostDirectives": [],
+ "inputsClass": [],
+ "outputsClass": [],
+ "propertiesClass": [
+ {
+ "name": "doorgehaald",
+ "defaultValue": "{\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 }",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "Registration",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 117
+ },
+ {
+ "name": "emptyRes",
+ "defaultValue": "fakeResource('resolved', [])",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "unknown",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 124
+ },
+ {
+ "name": "errorRes",
+ "defaultValue": "fakeResource('error', undefined, new Error('Demo'))",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "unknown",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 125
+ },
+ {
+ "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`",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "unknown",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 136
+ },
+ {
+ "name": "isEmpty",
+ "defaultValue": "() => {...}",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "unknown",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 115
+ },
+ {
+ "name": "loadingRes",
+ "defaultValue": "fakeResource('loading')",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "unknown",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 123
+ },
+ {
+ "name": "machineBad",
+ "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
+ },
+ {
+ "name": "parsed",
+ "defaultValue": "computed(() => parsePostcode(this.raw()))",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "unknown",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 129
+ },
+ {
+ "name": "raw",
+ "defaultValue": "signal('')",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "unknown",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 128
+ },
+ {
+ "name": "successRes",
+ "defaultValue": "fakeResource('resolved', ['Huisartsgeneeskunde', 'Spoedeisende hulp'])",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "unknown",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 126
+ },
+ {
+ "name": "unionBad",
+ "defaultValue": "`interface Registration {\n status: 'Geregistreerd' | 'Doorgehaald';\n herregistratieDatum: string; // altijd aanwezig 😬\n}`",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "unknown",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 131
+ }
+ ],
+ "methodsClass": [],
+ "deprecated": false,
+ "deprecationMessage": "",
+ "hostBindings": [],
+ "hostListeners": [],
+ "standalone": false,
+ "imports": [
+ {
+ "name": "FormsModule",
+ "type": "module"
+ },
+ {
+ "name": "PageShellComponent",
+ "type": "component"
+ },
+ {
+ "name": "HeadingComponent",
+ "type": "component"
+ },
+ {
+ "name": "AlertComponent",
+ "type": "component"
+ },
+ {
+ "name": "TextInputComponent",
+ "type": "component"
+ },
+ {
+ "name": "ASYNC"
+ },
+ {
+ "name": "SkeletonComponent",
+ "type": "component"
+ },
+ {
+ "name": "RegistrationSummaryComponent",
+ "type": "component"
+ },
+ {
+ "name": "HerregistratieWizardComponent",
+ "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 '../../templates/page-shell/page-shell.component';\nimport { HeadingComponent } from '../../atoms/heading/heading.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { TextInputComponent } from '../../atoms/text-input/text-input.component';\nimport { ASYNC } from '../../molecules/async/async.component';\nimport { SkeletonComponent } from '../../atoms/skeleton/skeleton.component';\nimport { RegistrationSummaryComponent } from '../../organisms/registration-summary/registration-summary.component';\nimport { HerregistratieWizardComponent } from '../../organisms/herregistratie-wizard/herregistratie-wizard.component';\nimport { Registration } from '../../core/models';\nimport { parsePostcode } from '../../core/parse';\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",
+ "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",
+ "extends": []
+ },
{
"name": "DashboardPage",
- "id": "component-DashboardPage-36586da5e82d0e8ebdd85752f770845da4b3cc25f67a64c1fbaa2fdc227f11a3686aa7e85d20ba00f27796d75849db0f6e52fc10e1f2c59c8dd10c6c940c6c91",
+ "id": "component-DashboardPage-f363992c5154b037dc0dc2a461da66f9a297af7bc7ba9dd9f19a1bef213af9b9fb15a92e6d5e1922040acbf5aa3f7ec27db8035944e496c8f97bb519b4652cb3",
"file": "src/app/pages/dashboard/dashboard.page.ts",
"encapsulation": [],
"entryComponents": [],
@@ -892,7 +1223,7 @@
"selector": "app-dashboard-page",
"styleUrls": [],
"styles": [],
- "template": "\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",
+ "template": "\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",
"templateUrl": [],
"viewProviders": [],
"hostDirectives": [],
@@ -908,7 +1239,7 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 57
+ "line": 60
},
{
"name": "notesEmpty",
@@ -919,7 +1250,7 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 58
+ "line": 61
},
{
"name": "reg",
@@ -930,7 +1261,7 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 56
+ "line": 59
},
{
"name": "regEmpty",
@@ -941,7 +1272,7 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 59
+ "line": 62
},
{
"name": "svc",
@@ -952,7 +1283,7 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 55,
+ "line": 58,
"modifierKind": [
123
]
@@ -996,7 +1327,7 @@
"description": "",
"rawdescription": "\n",
"type": "component",
- "sourceCode": "import { Component, inject } from '@angular/core';\nimport { PageShellComponent } from '../../templates/page-shell/page-shell.component';\nimport { HeadingComponent } from '../../atoms/heading/heading.component';\nimport { LinkComponent } from '../../atoms/link/link.component';\nimport { SkeletonComponent } from '../../atoms/skeleton/skeleton.component';\nimport { ASYNC } from '../../molecules/async/async.component';\nimport { RegistrationSummaryComponent } from '../../organisms/registration-summary/registration-summary.component';\nimport { RegistrationTableComponent } from '../../organisms/registration-table/registration-table.component';\nimport { RegistrationService } from '../../core/registration.service';\nimport { Aantekening } from '../../core/models';\n\n@Component({\n selector: 'app-dashboard-page',\n imports: [\n PageShellComponent, HeadingComponent, LinkComponent, SkeletonComponent, ...ASYNC,\n RegistrationSummaryComponent, RegistrationTableComponent,\n ],\n template: `\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 `,\n})\nexport class DashboardPage {\n private svc = inject(RegistrationService);\n reg = this.svc.registrationResource();\n notes = this.svc.aantekeningenResource();\n notesEmpty = (v: Aantekening[]) => v.length === 0;\n regEmpty = (v: unknown) => !v || Object.keys(v).length === 0;\n}\n",
+ "sourceCode": "import { Component, inject } from '@angular/core';\nimport { PageShellComponent } from '../../templates/page-shell/page-shell.component';\nimport { HeadingComponent } from '../../atoms/heading/heading.component';\nimport { LinkComponent } from '../../atoms/link/link.component';\nimport { SkeletonComponent } from '../../atoms/skeleton/skeleton.component';\nimport { ASYNC } from '../../molecules/async/async.component';\nimport { RegistrationSummaryComponent } from '../../organisms/registration-summary/registration-summary.component';\nimport { RegistrationTableComponent } from '../../organisms/registration-table/registration-table.component';\nimport { RegistrationService } from '../../core/registration.service';\nimport { Aantekening } from '../../core/models';\n\n@Component({\n selector: 'app-dashboard-page',\n imports: [\n PageShellComponent, HeadingComponent, LinkComponent, SkeletonComponent, ...ASYNC,\n RegistrationSummaryComponent, RegistrationTableComponent,\n ],\n template: `\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 private svc = inject(RegistrationService);\n reg = this.svc.registrationResource();\n notes = this.svc.aantekeningenResource();\n notesEmpty = (v: Aantekening[]) => v.length === 0;\n regEmpty = (v: unknown) => !v || Object.keys(v).length === 0;\n}\n",
"assetsDirs": [],
"styleUrlsData": "",
"stylesData": "",
@@ -1195,7 +1526,7 @@
},
{
"name": "HerregistratiePage",
- "id": "component-HerregistratiePage-ab0497d7957e16f74b4c042109d559a772b5fd5c364d0d3c721504a6467354672c7afef26ddf84aa3d7a9e64ec48213993f5550e57ca65a536d10403b61ec754",
+ "id": "component-HerregistratiePage-e6eeb0932b0d6c7f4073bda82d95601ef7d31f17ac310061dcf3347c8f30d1444f50e124bfedda286285ebfda9932bb075fdaf20683413b524570e51fa350c3f",
"file": "src/app/pages/herregistratie/herregistratie.page.ts",
"encapsulation": [],
"entryComponents": [],
@@ -1205,68 +1536,252 @@
"selector": "app-herregistratie-page",
"styleUrls": [],
"styles": [],
- "template": "\n \n Uw huidige registratie verloopt op 1 september 2027. Vraag tijdig herregistratie aan.\n \n\n @if (submitted()) {\n \n
Uw aanvraag tot herregistratie is ontvangen.\n
\n } @else {\n \n }\n\n",
+ "template": "\n \n Uw huidige registratie verloopt op 1 september 2027. Vraag tijdig herregistratie aan.\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": "HerregistratieWizardComponent",
+ "type": "component"
+ }
+ ],
+ "description": "A whole new page built from existing building blocks — no new components.\nThis is the atomic-design payoff: a new flow is just composition.
\n",
+ "rawdescription": "\nA whole new page built from existing building blocks — no new components.\nThis is the atomic-design payoff: a new flow is just composition.",
+ "type": "component",
+ "sourceCode": "import { Component } from '@angular/core';\nimport { PageShellComponent } from '../../templates/page-shell/page-shell.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { HerregistratieWizardComponent } from '../../organisms/herregistratie-wizard/herregistratie-wizard.component';\n\n/** A whole new page built from existing building blocks — no new components.\n This is the atomic-design payoff: a new flow is just composition. */\n@Component({\n selector: 'app-herregistratie-page',\n imports: [PageShellComponent, AlertComponent, HerregistratieWizardComponent],\n template: `\n \n \n Uw huidige registratie verloopt op 1 september 2027. Vraag tijdig herregistratie aan.\n \n \n \n `,\n})\nexport class HerregistratiePage {}\n",
+ "assetsDirs": [],
+ "styleUrlsData": "",
+ "stylesData": "",
+ "extends": []
+ },
+ {
+ "name": "HerregistratieWizardComponent",
+ "id": "component-HerregistratieWizardComponent-ad587121f38c0c6e6bb4c8525e7b626054b7790ec708c99a0f6e66b3b7703b935b47c6b141072f7f8c9e567974dd162961ef5e17b983e373d6d7a3a7814aaf80",
+ "file": "src/app/organisms/herregistratie-wizard/herregistratie-wizard.component.ts",
+ "encapsulation": [],
+ "entryComponents": [],
+ "inputs": [],
+ "outputs": [],
+ "providers": [],
+ "selector": "app-herregistratie-wizard",
+ "styleUrls": [],
+ "styles": [],
+ "template": "@switch (state().tag) {\n @case ('Editing') {\n Stap {{ step() }} van 2
\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. Probeer het opnieuw.\n \n }\n}\n",
+ "templateUrl": [],
+ "viewProviders": [],
+ "hostDirectives": [],
+ "inputsClass": [
+ {
+ "name": "seed",
+ "defaultValue": "initial",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "WizardState",
+ "indexKey": "",
+ "optional": false,
+ "description": "Optional seed so Storybook / the showcase can mount any state directly.
\n",
+ "line": 58,
+ "rawdescription": "\nOptional seed so Storybook / the showcase can mount any state directly.",
+ "required": false
+ }
+ ],
+ "outputsClass": [],
"propertiesClass": [
{
- "name": "punten",
- "defaultValue": "''",
- "deprecated": false,
- "deprecationMessage": "",
- "type": "string",
- "indexKey": "",
- "optional": false,
- "description": "",
- "line": 45
- },
- {
- "name": "submitted",
- "defaultValue": "signal(false)",
+ "name": "back",
+ "defaultValue": "back",
"deprecated": false,
"deprecationMessage": "",
"type": "unknown",
"indexKey": "",
"optional": false,
"description": "",
- "line": 47
+ "line": 61
},
{
- "name": "uren",
- "defaultValue": "''",
- "deprecated": false,
- "deprecationMessage": "",
- "type": "string",
- "indexKey": "",
- "optional": false,
- "description": "",
- "line": 44
- },
- {
- "name": "urenError",
- "defaultValue": "signal('')",
+ "name": "draft",
+ "defaultValue": "computed(() => this.editing()?.draft ?? { uren: '', punten: '' })",
"deprecated": false,
"deprecationMessage": "",
"type": "unknown",
"indexKey": "",
"optional": false,
"description": "",
- "line": 46
+ "line": 65,
+ "modifierKind": [
+ 124
+ ]
+ },
+ {
+ "name": "editing",
+ "defaultValue": "computed(() => (this.state().tag === 'Editing' ? (this.state() as Extract) : null))",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "unknown",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 63,
+ "modifierKind": [
+ 123
+ ]
+ },
+ {
+ "name": "errPunten",
+ "defaultValue": "computed(() => this.editing()?.errors.punten ?? '')",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "unknown",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 67,
+ "modifierKind": [
+ 124
+ ]
+ },
+ {
+ "name": "errUren",
+ "defaultValue": "computed(() => this.editing()?.errors.uren ?? '')",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "unknown",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 66,
+ "modifierKind": [
+ 124
+ ]
+ },
+ {
+ "name": "state",
+ "defaultValue": "signal(initial)",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "unknown",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 59
+ },
+ {
+ "name": "step",
+ "defaultValue": "computed(() => this.editing()?.step ?? 1)",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "unknown",
+ "indexKey": "",
+ "optional": false,
+ "description": "",
+ "line": 64,
+ "modifierKind": [
+ 124
+ ]
}
],
"methodsClass": [
{
- "name": "onSubmit",
+ "name": "onPrimary",
"args": [],
"optional": false,
"returnType": "void",
"typeParameters": [],
- "line": 49,
+ "line": 79,
"deprecated": false,
"deprecationMessage": ""
+ },
+ {
+ "name": "onRetry",
+ "args": [],
+ "optional": false,
+ "returnType": "void",
+ "typeParameters": [],
+ "line": 86,
+ "deprecated": false,
+ "deprecationMessage": ""
+ },
+ {
+ "name": "runIfSubmitting",
+ "args": [],
+ "optional": false,
+ "returnType": "void",
+ "typeParameters": [],
+ "line": 94,
+ "deprecated": false,
+ "deprecationMessage": "",
+ "modifierKind": [
+ 123
+ ]
+ },
+ {
+ "name": "set",
+ "args": [
+ {
+ "name": "key",
+ "type": "unknown",
+ "optional": false,
+ "dotDotDotToken": false,
+ "deprecated": false,
+ "deprecationMessage": ""
+ },
+ {
+ "name": "value",
+ "type": "string",
+ "optional": false,
+ "dotDotDotToken": false,
+ "deprecated": false,
+ "deprecationMessage": ""
+ }
+ ],
+ "optional": false,
+ "returnType": "void",
+ "typeParameters": [],
+ "line": 73,
+ "deprecated": false,
+ "deprecationMessage": "",
+ "jsdoctags": [
+ {
+ "name": "key",
+ "type": "unknown",
+ "optional": false,
+ "dotDotDotToken": false,
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ },
+ {
+ "name": "value",
+ "type": "string",
+ "optional": false,
+ "dotDotDotToken": false,
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
}
],
"deprecated": false,
@@ -1280,11 +1795,11 @@
"type": "module"
},
{
- "name": "PageShellComponent",
+ "name": "FormFieldComponent",
"type": "component"
},
{
- "name": "AlertComponent",
+ "name": "TextInputComponent",
"type": "component"
},
{
@@ -1292,21 +1807,29 @@
"type": "component"
},
{
- "name": "FormFieldComponent",
+ "name": "AlertComponent",
"type": "component"
},
{
- "name": "TextInputComponent",
+ "name": "SpinnerComponent",
"type": "component"
}
],
- "description": "A whole new page built from existing building blocks — no new components.\nThis is the atomic-design payoff: a new flow is just composition.
\n",
- "rawdescription": "\nA whole new page built from existing building blocks — no new components.\nThis is the atomic-design payoff: a new flow is just composition.",
+ "description": "Organism: multi-step herregistratie wizard driven entirely by a state\nmachine (wizard.machine.ts). The signal IS the union, so the UI just folds\nover its tag — no booleans like submitting/submitted that could contradict\neach other. Composition-only: reuses existing form-field/input/button/alert.
\n",
+ "rawdescription": "\nOrganism: multi-step herregistratie wizard driven entirely by a state\nmachine (wizard.machine.ts). The signal IS the union, so the UI just folds\nover its tag — no booleans like `submitting`/`submitted` that could contradict\neach other. Composition-only: reuses existing form-field/input/button/alert.",
"type": "component",
- "sourceCode": "import { Component, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { PageShellComponent } from '../../templates/page-shell/page-shell.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { FormFieldComponent } from '../../molecules/form-field/form-field.component';\nimport { TextInputComponent } from '../../atoms/text-input/text-input.component';\n\n/** A whole new page built from existing building blocks — no new components.\n This is the atomic-design payoff: a new flow is just composition. */\n@Component({\n selector: 'app-herregistratie-page',\n imports: [\n FormsModule, PageShellComponent, AlertComponent,\n ButtonComponent, FormFieldComponent, TextInputComponent,\n ],\n template: `\n \n \n Uw huidige registratie verloopt op 1 september 2027. Vraag tijdig herregistratie aan.\n \n\n @if (submitted()) {\n \n
Uw aanvraag tot herregistratie is ontvangen.\n
\n } @else {\n \n }\n \n `,\n})\nexport class HerregistratiePage {\n uren = '';\n punten = '';\n urenError = signal('');\n submitted = signal(false);\n\n onSubmit() {\n if (!this.uren.trim()) { this.urenError.set('Vul het aantal gewerkte uren in.'); return; }\n this.urenError.set('');\n this.submitted.set(true);\n }\n}\n",
+ "sourceCode": "import { Component, computed, input, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { FormFieldComponent } from '../../molecules/form-field/form-field.component';\nimport { TextInputComponent } from '../../atoms/text-input/text-input.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { ok } from '../../core/fp';\nimport { WizardState, Draft, initial, next, back, submit, resolve } from './wizard.machine';\n\n/** Organism: multi-step herregistratie wizard driven entirely by a state\n machine (wizard.machine.ts). The signal IS the union, so the UI just folds\n over its tag — no booleans like `submitting`/`submitted` that could contradict\n each other. Composition-only: reuses existing form-field/input/button/alert. */\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 }\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. Probeer het opnieuw.\n \n }\n }\n `,\n})\nexport class HerregistratieWizardComponent {\n /** Optional seed so Storybook / the showcase can mount any state directly. */\n seed = input(initial);\n state = signal(initial);\n\n back = back;\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\n constructor() {\n queueMicrotask(() => this.state.set(this.seed()));\n }\n\n set(key: keyof Draft, value: string) {\n const s = this.state();\n if (s.tag !== 'Editing') return;\n this.state.set({ ...s, draft: { ...s.draft, [key]: value } });\n }\n\n onPrimary() {\n const s = this.state();\n if (s.tag !== 'Editing') return;\n this.state.set(s.step === 1 ? next(s) : submit(s));\n this.runIfSubmitting();\n }\n\n onRetry() {\n const s = this.state();\n if (s.tag !== 'Failed') return;\n this.state.set({ tag: 'Submitting', data: s.data });\n this.runIfSubmitting();\n }\n\n // ponytail: fake the backend with a timer; swap for a real httpResource call later.\n private runIfSubmitting() {\n if (this.state().tag !== 'Submitting') return;\n setTimeout(() => this.state.set(resolve(this.state(), ok(undefined))), 800);\n }\n}\n",
"assetsDirs": [],
"styleUrlsData": "",
"stylesData": "",
+ "constructorObj": {
+ "name": "constructor",
+ "description": "",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "args": [],
+ "line": 67
+ },
"extends": []
},
{
@@ -1729,7 +2252,7 @@
},
{
"name": "RegistrationSummaryComponent",
- "id": "component-RegistrationSummaryComponent-eb956e029fecdfc5c3173f0c48515cad94e7bb8023ebcc4d0a37ae311c0b39c215fb46643ac356b4dd5c0f4ead6123e79f4f7aeecc3b631eff0269e9f1a3e914",
+ "id": "component-RegistrationSummaryComponent-cad630ad4fd52e8743ba17ebf8f1cf8362c4f8677937ac6e41fadc055438d0c35c6756d7f2d39b67239ceb2af09e0a11c141fa9cd398f80e5d18a8e6960d90ee",
"file": "src/app/organisms/registration-summary/registration-summary.component.ts",
"encapsulation": [],
"entryComponents": [],
@@ -1739,7 +2262,7 @@
"selector": "app-registration-summary",
"styleUrls": [],
"styles": [],
- "template": "\n
\n \n \n \n \n \n \n \n \n
\n
\n",
+ "template": "\n
\n \n \n \n \n \n \n \n \n @switch (reg().status.tag) {\n @case ('Geregistreerd') {\n \n }\n @case ('Geschorst') {\n \n \n }\n @case ('Doorgehaald') {\n \n \n }\n }\n
\n
\n",
"templateUrl": [],
"viewProviders": [],
"hostDirectives": [],
@@ -1752,7 +2275,7 @@
"indexKey": "",
"optional": false,
"description": "",
- "line": 27,
+ "line": 40,
"required": true
}
],
@@ -1781,7 +2304,7 @@
"description": "Organism: registration summary card. Composes data-row molecules + status-badge atom.
\n",
"rawdescription": "\nOrganism: registration summary card. Composes data-row molecules + status-badge atom.",
"type": "component",
- "sourceCode": "import { Component, input } from '@angular/core';\nimport { DatePipe } from '@angular/common';\nimport { Registration } from '../../core/models';\nimport { DataRowComponent } from '../../molecules/data-row/data-row.component';\nimport { StatusBadgeComponent } from '../../atoms/status-badge/status-badge.component';\n\n/** Organism: registration summary card. Composes data-row molecules + status-badge atom. */\n@Component({\n selector: 'app-registration-summary',\n imports: [DatePipe, DataRowComponent, StatusBadgeComponent],\n template: `\n \n
\n \n \n \n \n \n \n \n \n
\n
\n `,\n})\nexport class RegistrationSummaryComponent {\n reg = input.required();\n}\n",
+ "sourceCode": "import { Component, input } from '@angular/core';\nimport { DatePipe } from '@angular/common';\nimport { Registration } from '../../core/models';\nimport { DataRowComponent } from '../../molecules/data-row/data-row.component';\nimport { StatusBadgeComponent } from '../../atoms/status-badge/status-badge.component';\n\n/** Organism: registration summary card. Composes data-row molecules + status-badge atom. */\n@Component({\n selector: 'app-registration-summary',\n imports: [DatePipe, DataRowComponent, StatusBadgeComponent],\n template: `\n \n
\n \n \n \n \n \n \n \n \n @switch (reg().status.tag) {\n @case ('Geregistreerd') {\n \n }\n @case ('Geschorst') {\n \n \n }\n @case ('Doorgehaald') {\n \n \n }\n }\n
\n
\n `,\n})\nexport class RegistrationSummaryComponent {\n reg = input.required();\n}\n",
"assetsDirs": [],
"styleUrlsData": "",
"stylesData": "",
@@ -2231,7 +2754,7 @@
},
{
"name": "StatusBadgeComponent",
- "id": "component-StatusBadgeComponent-3374b85f056b7a50e5cce348202733fea8109c0244004ec59ac829d32a629dbd750f1a087eabbdad5fce0aba1cf9495090e7a949f0bec2cdc371f854fc77d365",
+ "id": "component-StatusBadgeComponent-5cb9f831687f564e86bbc72b1276fd5b10bc1bc877bf15a2dfff580724e2795f7abdc861ad6ccce41846084a022c11d4c570924d3f7ec1388a1cd5e31b9fd8a2",
"file": "src/app/atoms/status-badge/status-badge.component.ts",
"encapsulation": [],
"entryComponents": [],
@@ -2250,11 +2773,11 @@
"name": "status",
"deprecated": false,
"deprecationMessage": "",
- "type": "RegistrationStatus",
+ "type": "StatusTag",
"indexKey": "",
"optional": false,
"description": "",
- "line": 16,
+ "line": 18,
"required": true
}
],
@@ -2262,14 +2785,14 @@
"propertiesClass": [
{
"name": "color",
- "defaultValue": "computed(() => ({\n Geregistreerd: 'var(--rhc-color-groen-500)',\n Doorgehaald: 'var(--rhc-color-rood-500)',\n Geschorst: 'var(--rhc-color-oranje-500)',\n }[this.status()]))",
+ "defaultValue": "computed(() => {\n const tag = this.status();\n switch (tag) {\n case 'Geregistreerd':\n return 'var(--rhc-color-groen-500)';\n case 'Doorgehaald':\n return 'var(--rhc-color-rood-500)';\n case 'Geschorst':\n return 'var(--rhc-color-oranje-500)';\n default:\n return assertNever(tag);\n }\n })",
"deprecated": false,
"deprecationMessage": "",
"type": "unknown",
"indexKey": "",
"optional": false,
"description": "",
- "line": 17
+ "line": 19
}
],
"methodsClass": [],
@@ -2279,10 +2802,10 @@
"hostListeners": [],
"standalone": false,
"imports": [],
- "description": "Atom: status badge = colored RHC dot-badge + label. Hand-built to show how\nyou compose a new atom from design tokens (dot color driven per status).
\n",
- "rawdescription": "\nAtom: status badge = colored RHC dot-badge + label. Hand-built to show how\nyou compose a new atom from design tokens (dot color driven per status).",
+ "description": "Atom: status badge = colored RHC dot-badge + label. The color is a total\nfunction of the status tag — assertNever makes a new status fail to compile\nuntil a color is chosen for it.
\n",
+ "rawdescription": "\nAtom: status badge = colored RHC dot-badge + label. The color is a total\nfunction of the status tag — assertNever makes a new status fail to compile\nuntil a color is chosen for it.",
"type": "component",
- "sourceCode": "import { Component, computed, input } from '@angular/core';\nimport { RegistrationStatus } from '../../core/models';\n\n/** Atom: status badge = colored RHC dot-badge + label. Hand-built to show how\n you compose a new atom from design tokens (dot color driven per status). */\n@Component({\n selector: 'app-status-badge',\n template: `\n \n \n {{ status() }}\n \n `,\n})\nexport class StatusBadgeComponent {\n status = input.required();\n color = computed(() => ({\n Geregistreerd: 'var(--rhc-color-groen-500)',\n Doorgehaald: 'var(--rhc-color-rood-500)',\n Geschorst: 'var(--rhc-color-oranje-500)',\n }[this.status()]));\n}\n",
+ "sourceCode": "import { Component, computed, input } from '@angular/core';\nimport { StatusTag } from '../../core/models';\nimport { assertNever } from '../../core/fp';\n\n/** Atom: status badge = colored RHC dot-badge + label. The color is a total\n function of the status tag — assertNever makes a new status fail to compile\n until a color is chosen for it. */\n@Component({\n selector: 'app-status-badge',\n template: `\n \n \n {{ status() }}\n \n `,\n})\nexport class StatusBadgeComponent {\n status = input.required();\n color = computed(() => {\n const tag = this.status();\n switch (tag) {\n case 'Geregistreerd':\n return 'var(--rhc-color-groen-500)';\n case 'Doorgehaald':\n return 'var(--rhc-color-rood-500)';\n case 'Geschorst':\n return 'var(--rhc-color-oranje-500)';\n default:\n return assertNever(tag);\n }\n });\n}\n",
"assetsDirs": [],
"styleUrlsData": "",
"stylesData": "",
@@ -2630,6 +3153,36 @@
"rawdescription": "Convenience: import this array to get the wrapper + all slot directives.",
"description": "Convenience: import this array to get the wrapper + all slot directives.
\n"
},
+ {
+ "name": "err",
+ "ctype": "miscellaneous",
+ "subtype": "variable",
+ "file": "src/app/core/fp.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "unknown",
+ "defaultValue": "(error: E): Result => ({ ok: false, error })"
+ },
+ {
+ "name": "initial",
+ "ctype": "miscellaneous",
+ "subtype": "variable",
+ "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "WizardState",
+ "defaultValue": "{ tag: 'Editing', step: 1, draft: { uren: '', punten: '' }, errors: {} }"
+ },
+ {
+ "name": "ok",
+ "ctype": "miscellaneous",
+ "subtype": "variable",
+ "file": "src/app/core/fp.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "unknown",
+ "defaultValue": "(value: T): Result => ({ ok: true, value })"
+ },
{
"name": "routes",
"ctype": "miscellaneous",
@@ -2638,7 +3191,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('./pages/login/login.page').then(m => m.LoginPage)\" },\n { path: 'dashboard', loadComponent: () => \"import('./pages/dashboard/dashboard.page').then(m => m.DashboardPage)\" },\n { path: 'registratie', loadComponent: () => \"import('./pages/registration-detail/registration-detail.page').then(m => m.RegistrationDetailPage)\" },\n { path: 'herregistratie', loadComponent: () => \"import('./pages/herregistratie/herregistratie.page').then(m => m.HerregistratiePage)\" },\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('./pages/login/login.page').then(m => m.LoginPage)\" },\n { path: 'dashboard', loadComponent: () => \"import('./pages/dashboard/dashboard.page').then(m => m.DashboardPage)\" },\n { path: 'registratie', loadComponent: () => \"import('./pages/registration-detail/registration-detail.page').then(m => m.RegistrationDetailPage)\" },\n { path: 'herregistratie', loadComponent: () => \"import('./pages/herregistratie/herregistratie.page').then(m => m.HerregistratiePage)\" },\n { path: 'concepts', loadComponent: () => \"import('./pages/concepts/concepts.page').then(m => m.ConceptsPage)\" },\n { path: '**', redirectTo: 'login' },\n ],\n },\n]"
},
{
"name": "scenarioInterceptor",
@@ -2664,6 +3217,64 @@
}
],
"functions": [
+ {
+ "name": "assertNever",
+ "file": "src/app/core/fp.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "Tiny native-TS functional toolkit. No dependency — this is the whole "library".\nReused by every "impossible states" concept in the POC.
\n",
+ "args": [
+ {
+ "name": "x",
+ "type": "never",
+ "deprecated": false,
+ "deprecationMessage": ""
+ }
+ ],
+ "returnType": "never",
+ "jsdoctags": [
+ {
+ "name": "x",
+ "type": "never",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
+ },
+ {
+ "name": "back",
+ "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "",
+ "args": [
+ {
+ "name": "s",
+ "type": "WizardState",
+ "deprecated": false,
+ "deprecationMessage": ""
+ }
+ ],
+ "returnType": "WizardState",
+ "jsdoctags": [
+ {
+ "name": "s",
+ "type": "WizardState",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
+ },
{
"name": "currentScenario",
"file": "src/app/core/scenario.ts",
@@ -2674,6 +3285,344 @@
"description": "Reads ?scenario= from the URL so a demo can force each async state.
\n",
"args": [],
"returnType": "Scenario"
+ },
+ {
+ "name": "fakeResource",
+ "file": "src/app/pages/concepts/concepts.page.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "Minimal fake Resource so can be driven through every state without HTTP.
\n",
+ "args": [
+ {
+ "name": "status",
+ "type": "string",
+ "deprecated": false,
+ "deprecationMessage": ""
+ },
+ {
+ "name": "value",
+ "type": "T",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "optional": true
+ },
+ {
+ "name": "error",
+ "type": "Error",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "optional": true
+ }
+ ],
+ "returnType": "Resource",
+ "jsdoctags": [
+ {
+ "name": "status",
+ "type": "string",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ },
+ {
+ "name": "value",
+ "type": "T",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "optional": true,
+ "tagName": {
+ "text": "param"
+ }
+ },
+ {
+ "name": "error",
+ "type": "Error",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "optional": true,
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
+ },
+ {
+ "name": "foldRemote",
+ "file": "src/app/core/remote-data.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "Exhaustive fold: you must handle every case, checked at compile time.
\n",
+ "args": [
+ {
+ "name": "rd",
+ "type": "RemoteData",
+ "deprecated": false,
+ "deprecationMessage": ""
+ },
+ {
+ "name": "h",
+ "deprecated": false,
+ "deprecationMessage": ""
+ }
+ ],
+ "returnType": "R",
+ "jsdoctags": [
+ {
+ "name": "rd",
+ "type": "RemoteData",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ },
+ {
+ "name": "h",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
+ },
+ {
+ "name": "fromResource",
+ "file": "src/app/core/remote-data.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "Project Angular's loosely-typed Resource into a RemoteData value.
\n",
+ "args": [
+ {
+ "name": "r",
+ "type": "Resource",
+ "deprecated": false,
+ "deprecationMessage": ""
+ },
+ {
+ "name": "isEmpty",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "defaultValue": "() => false"
+ }
+ ],
+ "returnType": "RemoteData",
+ "jsdoctags": [
+ {
+ "name": "r",
+ "type": "Resource",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ },
+ {
+ "name": "isEmpty",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "defaultValue": "() => false",
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
+ },
+ {
+ "name": "next",
+ "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "Step 1 → 2: only advance if the uren field parses. Illegal elsewhere = no-op.
\n",
+ "args": [
+ {
+ "name": "s",
+ "type": "WizardState",
+ "deprecated": false,
+ "deprecationMessage": ""
+ }
+ ],
+ "returnType": "WizardState",
+ "jsdoctags": [
+ {
+ "name": "s",
+ "type": "WizardState",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
+ },
+ {
+ "name": "parsePostcode",
+ "file": "src/app/core/parse.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "",
+ "args": [
+ {
+ "name": "raw",
+ "type": "string",
+ "deprecated": false,
+ "deprecationMessage": ""
+ }
+ ],
+ "returnType": "Result",
+ "jsdoctags": [
+ {
+ "name": "raw",
+ "type": "string",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
+ },
+ {
+ "name": "parseUren",
+ "file": "src/app/core/parse.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "",
+ "args": [
+ {
+ "name": "raw",
+ "type": "string",
+ "deprecated": false,
+ "deprecationMessage": ""
+ }
+ ],
+ "returnType": "Result",
+ "jsdoctags": [
+ {
+ "name": "raw",
+ "type": "string",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
+ },
+ {
+ "name": "resolve",
+ "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "Resolve the async submit. Only meaningful while Submitting.
\n",
+ "args": [
+ {
+ "name": "s",
+ "type": "WizardState",
+ "deprecated": false,
+ "deprecationMessage": ""
+ },
+ {
+ "name": "r",
+ "type": "Result",
+ "deprecated": false,
+ "deprecationMessage": ""
+ }
+ ],
+ "returnType": "WizardState",
+ "jsdoctags": [
+ {
+ "name": "s",
+ "type": "WizardState",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ },
+ {
+ "name": "r",
+ "type": "Result",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
+ },
+ {
+ "name": "submit",
+ "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "Step 2 submit: parse everything; move to Submitting only with Valid data.
\n",
+ "args": [
+ {
+ "name": "s",
+ "type": "WizardState",
+ "deprecated": false,
+ "deprecationMessage": ""
+ }
+ ],
+ "returnType": "WizardState",
+ "jsdoctags": [
+ {
+ "name": "s",
+ "type": "WizardState",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
+ },
+ {
+ "name": "validate",
+ "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "Parse every field; on success hand back a Valid, else the per-field errors.
\n",
+ "args": [
+ {
+ "name": "draft",
+ "type": "Draft",
+ "deprecated": false,
+ "deprecationMessage": ""
+ }
+ ],
+ "returnType": "Result>, Valid>",
+ "jsdoctags": [
+ {
+ "name": "draft",
+ "type": "Draft",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
}
],
"typealiases": [
@@ -2688,15 +3637,59 @@
"description": "",
"kind": 193
},
+ {
+ "name": "Brand",
+ "ctype": "miscellaneous",
+ "subtype": "typealias",
+ "rawtype": "unknown",
+ "file": "src/app/core/fp.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "Nominal typing: Brand<string, 'Postcode'> is assignable from a plain string\nonly through an explicit cast — so a smart constructor is the only minter.
\n",
+ "kind": 194
+ },
+ {
+ "name": "Postcode",
+ "ctype": "miscellaneous",
+ "subtype": "typealias",
+ "rawtype": "Brand",
+ "file": "src/app/core/parse.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": ""Parse, don't validate." A validated value gets its own branded type, and the\nsmart constructor is the ONLY way to mint one. So once you hold a Postcode you\nknow it's well-formed — validity is carried in the type, not re-checked\neverywhere or tracked in a parallel error flag.
\n",
+ "kind": 184
+ },
{
"name": "RegistrationStatus",
"ctype": "miscellaneous",
"subtype": "typealias",
- "rawtype": "\"Geregistreerd\" | \"Doorgehaald\" | \"Geschorst\"",
+ "rawtype": "literal type | literal type | literal type",
"file": "src/app/core/models.ts",
"deprecated": false,
"deprecationMessage": "",
- "description": "",
+ "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": "RemoteData",
+ "ctype": "miscellaneous",
+ "subtype": "typealias",
+ "rawtype": "literal type | literal type | literal type | literal type",
+ "file": "src/app/core/remote-data.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "The four mutually-exclusive states of an async fetch, as a tagged union.\nCrucially the data lives ON the state: only Failure has an error, only\nSuccess has a value. "Loaded but no value" or "error with stale value"\nare unrepresentable — Richard Feldman's RemoteData.
\n",
+ "kind": 193
+ },
+ {
+ "name": "Result",
+ "ctype": "miscellaneous",
+ "subtype": "typealias",
+ "rawtype": "literal type | literal type",
+ "file": "src/app/core/fp.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "A computation that either succeeded with a value or failed with an error.\nPlain objects (no classes) to match the signal/httpResource ergonomics.
\n",
"kind": 193
},
{
@@ -2711,15 +3704,26 @@
"kind": 193
},
{
- "name": "State",
+ "name": "StatusTag",
"ctype": "miscellaneous",
"subtype": "typealias",
- "rawtype": "\"loading\" | \"error\" | \"empty\" | \"loaded\"",
- "file": "src/app/molecules/async/async.component.ts",
+ "rawtype": "RegistrationStatus",
+ "file": "src/app/core/models.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "Just the discriminant — for atoms that only need the label/color.
\n",
+ "kind": 200
+ },
+ {
+ "name": "Uren",
+ "ctype": "miscellaneous",
+ "subtype": "typealias",
+ "rawtype": "Brand",
+ "file": "src/app/core/parse.ts",
"deprecated": false,
"deprecationMessage": "",
"description": "",
- "kind": 193
+ "kind": 184
},
{
"name": "Variant",
@@ -2731,6 +3735,17 @@
"deprecationMessage": "",
"description": "",
"kind": 193
+ },
+ {
+ "name": "WizardState",
+ "ctype": "miscellaneous",
+ "subtype": "typealias",
+ "rawtype": "literal type | literal type | literal type | literal type",
+ "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "The whole wizard as one tagged union. step and errors exist ONLY while\nEditing; Submitting/Submitted/Failed carry a Valid payload and nothing else.\nSo "submitting while a field is invalid" or "showing the success screen with\nerrors set" are unrepresentable — the bug class is gone by construction.
\n",
+ "kind": 193
}
],
"enumerations": [],
@@ -2761,6 +3776,40 @@
"description": "Convenience: import this array to get the wrapper + all slot directives.
\n"
}
],
+ "src/app/core/fp.ts": [
+ {
+ "name": "err",
+ "ctype": "miscellaneous",
+ "subtype": "variable",
+ "file": "src/app/core/fp.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "unknown",
+ "defaultValue": "(error: E): Result => ({ ok: false, error })"
+ },
+ {
+ "name": "ok",
+ "ctype": "miscellaneous",
+ "subtype": "variable",
+ "file": "src/app/core/fp.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "unknown",
+ "defaultValue": "(value: T): Result => ({ ok: true, value })"
+ }
+ ],
+ "src/app/organisms/herregistratie-wizard/wizard.machine.ts": [
+ {
+ "name": "initial",
+ "ctype": "miscellaneous",
+ "subtype": "variable",
+ "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "type": "WizardState",
+ "defaultValue": "{ tag: 'Editing', step: 1, draft: { uren: '', punten: '' }, errors: {} }"
+ }
+ ],
"src/app/app.routes.ts": [
{
"name": "routes",
@@ -2770,7 +3819,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('./pages/login/login.page').then(m => m.LoginPage)\" },\n { path: 'dashboard', loadComponent: () => \"import('./pages/dashboard/dashboard.page').then(m => m.DashboardPage)\" },\n { path: 'registratie', loadComponent: () => \"import('./pages/registration-detail/registration-detail.page').then(m => m.RegistrationDetailPage)\" },\n { path: 'herregistratie', loadComponent: () => \"import('./pages/herregistratie/herregistratie.page').then(m => m.HerregistratiePage)\" },\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('./pages/login/login.page').then(m => m.LoginPage)\" },\n { path: 'dashboard', loadComponent: () => \"import('./pages/dashboard/dashboard.page').then(m => m.DashboardPage)\" },\n { path: 'registratie', loadComponent: () => \"import('./pages/registration-detail/registration-detail.page').then(m => m.RegistrationDetailPage)\" },\n { path: 'herregistratie', loadComponent: () => \"import('./pages/herregistratie/herregistratie.page').then(m => m.HerregistratiePage)\" },\n { path: 'concepts', loadComponent: () => \"import('./pages/concepts/concepts.page').then(m => m.ConceptsPage)\" },\n { path: '**', redirectTo: 'login' },\n ],\n },\n]"
}
],
"src/app/core/scenario.interceptor.ts": [
@@ -2801,6 +3850,199 @@
]
},
"groupedFunctions": {
+ "src/app/core/fp.ts": [
+ {
+ "name": "assertNever",
+ "file": "src/app/core/fp.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "Tiny native-TS functional toolkit. No dependency — this is the whole "library".\nReused by every "impossible states" concept in the POC.
\n",
+ "args": [
+ {
+ "name": "x",
+ "type": "never",
+ "deprecated": false,
+ "deprecationMessage": ""
+ }
+ ],
+ "returnType": "never",
+ "jsdoctags": [
+ {
+ "name": "x",
+ "type": "never",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
+ }
+ ],
+ "src/app/organisms/herregistratie-wizard/wizard.machine.ts": [
+ {
+ "name": "back",
+ "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "",
+ "args": [
+ {
+ "name": "s",
+ "type": "WizardState",
+ "deprecated": false,
+ "deprecationMessage": ""
+ }
+ ],
+ "returnType": "WizardState",
+ "jsdoctags": [
+ {
+ "name": "s",
+ "type": "WizardState",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
+ },
+ {
+ "name": "next",
+ "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "Step 1 → 2: only advance if the uren field parses. Illegal elsewhere = no-op.
\n",
+ "args": [
+ {
+ "name": "s",
+ "type": "WizardState",
+ "deprecated": false,
+ "deprecationMessage": ""
+ }
+ ],
+ "returnType": "WizardState",
+ "jsdoctags": [
+ {
+ "name": "s",
+ "type": "WizardState",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
+ },
+ {
+ "name": "resolve",
+ "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "Resolve the async submit. Only meaningful while Submitting.
\n",
+ "args": [
+ {
+ "name": "s",
+ "type": "WizardState",
+ "deprecated": false,
+ "deprecationMessage": ""
+ },
+ {
+ "name": "r",
+ "type": "Result",
+ "deprecated": false,
+ "deprecationMessage": ""
+ }
+ ],
+ "returnType": "WizardState",
+ "jsdoctags": [
+ {
+ "name": "s",
+ "type": "WizardState",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ },
+ {
+ "name": "r",
+ "type": "Result",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
+ },
+ {
+ "name": "submit",
+ "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "Step 2 submit: parse everything; move to Submitting only with Valid data.
\n",
+ "args": [
+ {
+ "name": "s",
+ "type": "WizardState",
+ "deprecated": false,
+ "deprecationMessage": ""
+ }
+ ],
+ "returnType": "WizardState",
+ "jsdoctags": [
+ {
+ "name": "s",
+ "type": "WizardState",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
+ },
+ {
+ "name": "validate",
+ "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "Parse every field; on success hand back a Valid, else the per-field errors.
\n",
+ "args": [
+ {
+ "name": "draft",
+ "type": "Draft",
+ "deprecated": false,
+ "deprecationMessage": ""
+ }
+ ],
+ "returnType": "Result>, Valid>",
+ "jsdoctags": [
+ {
+ "name": "draft",
+ "type": "Draft",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
+ }
+ ],
"src/app/core/scenario.ts": [
{
"name": "currentScenario",
@@ -2813,6 +4055,219 @@
"args": [],
"returnType": "Scenario"
}
+ ],
+ "src/app/pages/concepts/concepts.page.ts": [
+ {
+ "name": "fakeResource",
+ "file": "src/app/pages/concepts/concepts.page.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "Minimal fake Resource so can be driven through every state without HTTP.
\n",
+ "args": [
+ {
+ "name": "status",
+ "type": "string",
+ "deprecated": false,
+ "deprecationMessage": ""
+ },
+ {
+ "name": "value",
+ "type": "T",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "optional": true
+ },
+ {
+ "name": "error",
+ "type": "Error",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "optional": true
+ }
+ ],
+ "returnType": "Resource",
+ "jsdoctags": [
+ {
+ "name": "status",
+ "type": "string",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ },
+ {
+ "name": "value",
+ "type": "T",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "optional": true,
+ "tagName": {
+ "text": "param"
+ }
+ },
+ {
+ "name": "error",
+ "type": "Error",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "optional": true,
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
+ }
+ ],
+ "src/app/core/remote-data.ts": [
+ {
+ "name": "foldRemote",
+ "file": "src/app/core/remote-data.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "Exhaustive fold: you must handle every case, checked at compile time.
\n",
+ "args": [
+ {
+ "name": "rd",
+ "type": "RemoteData",
+ "deprecated": false,
+ "deprecationMessage": ""
+ },
+ {
+ "name": "h",
+ "deprecated": false,
+ "deprecationMessage": ""
+ }
+ ],
+ "returnType": "R",
+ "jsdoctags": [
+ {
+ "name": "rd",
+ "type": "RemoteData",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ },
+ {
+ "name": "h",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
+ },
+ {
+ "name": "fromResource",
+ "file": "src/app/core/remote-data.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "Project Angular's loosely-typed Resource into a RemoteData value.
\n",
+ "args": [
+ {
+ "name": "r",
+ "type": "Resource",
+ "deprecated": false,
+ "deprecationMessage": ""
+ },
+ {
+ "name": "isEmpty",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "defaultValue": "() => false"
+ }
+ ],
+ "returnType": "RemoteData",
+ "jsdoctags": [
+ {
+ "name": "r",
+ "type": "Resource",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ },
+ {
+ "name": "isEmpty",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "defaultValue": "() => false",
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
+ }
+ ],
+ "src/app/core/parse.ts": [
+ {
+ "name": "parsePostcode",
+ "file": "src/app/core/parse.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "",
+ "args": [
+ {
+ "name": "raw",
+ "type": "string",
+ "deprecated": false,
+ "deprecationMessage": ""
+ }
+ ],
+ "returnType": "Result",
+ "jsdoctags": [
+ {
+ "name": "raw",
+ "type": "string",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
+ },
+ {
+ "name": "parseUren",
+ "file": "src/app/core/parse.ts",
+ "ctype": "miscellaneous",
+ "subtype": "function",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "",
+ "args": [
+ {
+ "name": "raw",
+ "type": "string",
+ "deprecated": false,
+ "deprecationMessage": ""
+ }
+ ],
+ "returnType": "Result",
+ "jsdoctags": [
+ {
+ "name": "raw",
+ "type": "string",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "tagName": {
+ "text": "param"
+ }
+ }
+ ]
+ }
]
},
"groupedEnumerations": {},
@@ -2830,16 +4285,88 @@
"kind": 193
}
],
+ "src/app/core/fp.ts": [
+ {
+ "name": "Brand",
+ "ctype": "miscellaneous",
+ "subtype": "typealias",
+ "rawtype": "unknown",
+ "file": "src/app/core/fp.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "Nominal typing: Brand<string, 'Postcode'> is assignable from a plain string\nonly through an explicit cast — so a smart constructor is the only minter.
\n",
+ "kind": 194
+ },
+ {
+ "name": "Result",
+ "ctype": "miscellaneous",
+ "subtype": "typealias",
+ "rawtype": "literal type | literal type",
+ "file": "src/app/core/fp.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "A computation that either succeeded with a value or failed with an error.\nPlain objects (no classes) to match the signal/httpResource ergonomics.
\n",
+ "kind": 193
+ }
+ ],
+ "src/app/core/parse.ts": [
+ {
+ "name": "Postcode",
+ "ctype": "miscellaneous",
+ "subtype": "typealias",
+ "rawtype": "Brand",
+ "file": "src/app/core/parse.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": ""Parse, don't validate." A validated value gets its own branded type, and the\nsmart constructor is the ONLY way to mint one. So once you hold a Postcode you\nknow it's well-formed — validity is carried in the type, not re-checked\neverywhere or tracked in a parallel error flag.
\n",
+ "kind": 184
+ },
+ {
+ "name": "Uren",
+ "ctype": "miscellaneous",
+ "subtype": "typealias",
+ "rawtype": "Brand",
+ "file": "src/app/core/parse.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "",
+ "kind": 184
+ }
+ ],
"src/app/core/models.ts": [
{
"name": "RegistrationStatus",
"ctype": "miscellaneous",
"subtype": "typealias",
- "rawtype": "\"Geregistreerd\" | \"Doorgehaald\" | \"Geschorst\"",
+ "rawtype": "literal type | literal type | literal type",
"file": "src/app/core/models.ts",
"deprecated": false,
"deprecationMessage": "",
- "description": "",
+ "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/core/models.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "Just the discriminant — for atoms that only need the label/color.
\n",
+ "kind": 200
+ }
+ ],
+ "src/app/core/remote-data.ts": [
+ {
+ "name": "RemoteData",
+ "ctype": "miscellaneous",
+ "subtype": "typealias",
+ "rawtype": "literal type | literal type | literal type | literal type",
+ "file": "src/app/core/remote-data.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "The four mutually-exclusive states of an async fetch, as a tagged union.\nCrucially the data lives ON the state: only Failure has an error, only\nSuccess has a value. "Loaded but no value" or "error with stale value"\nare unrepresentable — Richard Feldman's RemoteData.
\n",
"kind": 193
}
],
@@ -2856,19 +4383,6 @@
"kind": 193
}
],
- "src/app/molecules/async/async.component.ts": [
- {
- "name": "State",
- "ctype": "miscellaneous",
- "subtype": "typealias",
- "rawtype": "\"loading\" | \"error\" | \"empty\" | \"loaded\"",
- "file": "src/app/molecules/async/async.component.ts",
- "deprecated": false,
- "deprecationMessage": "",
- "description": "",
- "kind": 193
- }
- ],
"src/app/atoms/button/button.component.ts": [
{
"name": "Variant",
@@ -2881,6 +4395,19 @@
"description": "",
"kind": 193
}
+ ],
+ "src/app/organisms/herregistratie-wizard/wizard.machine.ts": [
+ {
+ "name": "WizardState",
+ "ctype": "miscellaneous",
+ "subtype": "typealias",
+ "rawtype": "literal type | literal type | literal type | literal type",
+ "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "deprecated": false,
+ "deprecationMessage": "",
+ "description": "The whole wizard as one tagged union. step and errors exist ONLY while\nEditing; Submitting/Submitted/Failed carry a Valid payload and nothing else.\nSo "submitting while a field is invalid" or "showing the success screen with\nerrors set" are unrepresentable — the bug class is gone by construction.
\n",
+ "kind": 193
+ }
]
}
},
@@ -2913,6 +4440,11 @@
"kind": "route-path",
"filename": "src/app/app.routes.ts"
},
+ {
+ "name": "concepts",
+ "kind": "route-path",
+ "filename": "src/app/app.routes.ts"
+ },
{
"name": "**",
"kind": "route-path",
@@ -2931,8 +4463,8 @@
]
},
"coverage": {
- "count": 25,
- "status": "low",
+ "count": 41,
+ "status": "medium",
"files": [
{
"filePath": "src/app/app.config.ts",
@@ -3055,6 +4587,56 @@
"coverageCount": "1/14",
"status": "low"
},
+ {
+ "filePath": "src/app/core/fp.ts",
+ "type": "function",
+ "linktype": "miscellaneous",
+ "linksubtype": "function",
+ "name": "assertNever",
+ "coveragePercent": 100,
+ "coverageCount": "1/1",
+ "status": "very-good"
+ },
+ {
+ "filePath": "src/app/core/fp.ts",
+ "type": "variable",
+ "linktype": "miscellaneous",
+ "linksubtype": "variable",
+ "name": "err",
+ "coveragePercent": 0,
+ "coverageCount": "0/1",
+ "status": "low"
+ },
+ {
+ "filePath": "src/app/core/fp.ts",
+ "type": "variable",
+ "linktype": "miscellaneous",
+ "linksubtype": "variable",
+ "name": "ok",
+ "coveragePercent": 0,
+ "coverageCount": "0/1",
+ "status": "low"
+ },
+ {
+ "filePath": "src/app/core/fp.ts",
+ "type": "type alias",
+ "linktype": "miscellaneous",
+ "linksubtype": "typealias",
+ "name": "Brand",
+ "coveragePercent": 100,
+ "coverageCount": "1/1",
+ "status": "very-good"
+ },
+ {
+ "filePath": "src/app/core/fp.ts",
+ "type": "type alias",
+ "linktype": "miscellaneous",
+ "linksubtype": "typealias",
+ "name": "Result",
+ "coveragePercent": 100,
+ "coverageCount": "1/1",
+ "status": "very-good"
+ },
{
"filePath": "src/app/core/models.ts",
"type": "interface",
@@ -3070,7 +4652,7 @@
"linktype": "interface",
"name": "Registration",
"coveragePercent": 0,
- "coverageCount": "0/8",
+ "coverageCount": "0/7",
"status": "low"
},
{
@@ -3079,6 +4661,56 @@
"linktype": "miscellaneous",
"linksubtype": "typealias",
"name": "RegistrationStatus",
+ "coveragePercent": 100,
+ "coverageCount": "1/1",
+ "status": "very-good"
+ },
+ {
+ "filePath": "src/app/core/models.ts",
+ "type": "type alias",
+ "linktype": "miscellaneous",
+ "linksubtype": "typealias",
+ "name": "StatusTag",
+ "coveragePercent": 100,
+ "coverageCount": "1/1",
+ "status": "very-good"
+ },
+ {
+ "filePath": "src/app/core/parse.ts",
+ "type": "function",
+ "linktype": "miscellaneous",
+ "linksubtype": "function",
+ "name": "parsePostcode",
+ "coveragePercent": 0,
+ "coverageCount": "0/1",
+ "status": "low"
+ },
+ {
+ "filePath": "src/app/core/parse.ts",
+ "type": "function",
+ "linktype": "miscellaneous",
+ "linksubtype": "function",
+ "name": "parseUren",
+ "coveragePercent": 0,
+ "coverageCount": "0/1",
+ "status": "low"
+ },
+ {
+ "filePath": "src/app/core/parse.ts",
+ "type": "type alias",
+ "linktype": "miscellaneous",
+ "linksubtype": "typealias",
+ "name": "Postcode",
+ "coveragePercent": 100,
+ "coverageCount": "1/1",
+ "status": "very-good"
+ },
+ {
+ "filePath": "src/app/core/parse.ts",
+ "type": "type alias",
+ "linktype": "miscellaneous",
+ "linksubtype": "typealias",
+ "name": "Uren",
"coveragePercent": 0,
"coverageCount": "0/1",
"status": "low"
@@ -3092,6 +4724,36 @@
"coverageCount": "1/3",
"status": "medium"
},
+ {
+ "filePath": "src/app/core/remote-data.ts",
+ "type": "function",
+ "linktype": "miscellaneous",
+ "linksubtype": "function",
+ "name": "foldRemote",
+ "coveragePercent": 100,
+ "coverageCount": "1/1",
+ "status": "very-good"
+ },
+ {
+ "filePath": "src/app/core/remote-data.ts",
+ "type": "function",
+ "linktype": "miscellaneous",
+ "linksubtype": "function",
+ "name": "fromResource",
+ "coveragePercent": 100,
+ "coverageCount": "1/1",
+ "status": "very-good"
+ },
+ {
+ "filePath": "src/app/core/remote-data.ts",
+ "type": "type alias",
+ "linktype": "miscellaneous",
+ "linksubtype": "typealias",
+ "name": "RemoteData",
+ "coveragePercent": 100,
+ "coverageCount": "1/1",
+ "status": "very-good"
+ },
{
"filePath": "src/app/core/scenario.interceptor.ts",
"type": "variable",
@@ -3137,8 +4799,8 @@
"type": "component",
"linktype": "component",
"name": "AsyncComponent",
- "coveragePercent": 10,
- "coverageCount": "1/10",
+ "coveragePercent": 9,
+ "coverageCount": "1/11",
"status": "low"
},
{
@@ -3187,16 +4849,6 @@
"coverageCount": "1/1",
"status": "very-good"
},
- {
- "filePath": "src/app/molecules/async/async.component.ts",
- "type": "type alias",
- "linktype": "miscellaneous",
- "linksubtype": "typealias",
- "name": "State",
- "coveragePercent": 0,
- "coverageCount": "0/1",
- "status": "low"
- },
{
"filePath": "src/app/molecules/data-row/data-row.component.ts",
"type": "component",
@@ -3220,10 +4872,116 @@
"type": "component",
"linktype": "component",
"name": "ChangeRequestFormComponent",
- "coveragePercent": 14,
- "coverageCount": "1/7",
+ "coveragePercent": 12,
+ "coverageCount": "1/8",
"status": "low"
},
+ {
+ "filePath": "src/app/organisms/change-request-form/change-request-form.component.ts",
+ "type": "interface",
+ "linktype": "interface",
+ "name": "ChangeRequest",
+ "coveragePercent": 25,
+ "coverageCount": "1/4",
+ "status": "low"
+ },
+ {
+ "filePath": "src/app/organisms/herregistratie-wizard/herregistratie-wizard.component.ts",
+ "type": "component",
+ "linktype": "component",
+ "name": "HerregistratieWizardComponent",
+ "coveragePercent": 14,
+ "coverageCount": "2/14",
+ "status": "low"
+ },
+ {
+ "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "type": "interface",
+ "linktype": "interface",
+ "name": "Draft",
+ "coveragePercent": 33,
+ "coverageCount": "1/3",
+ "status": "medium"
+ },
+ {
+ "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "type": "interface",
+ "linktype": "interface",
+ "name": "Valid",
+ "coveragePercent": 33,
+ "coverageCount": "1/3",
+ "status": "medium"
+ },
+ {
+ "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "type": "function",
+ "linktype": "miscellaneous",
+ "linksubtype": "function",
+ "name": "back",
+ "coveragePercent": 0,
+ "coverageCount": "0/1",
+ "status": "low"
+ },
+ {
+ "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "type": "function",
+ "linktype": "miscellaneous",
+ "linksubtype": "function",
+ "name": "next",
+ "coveragePercent": 100,
+ "coverageCount": "1/1",
+ "status": "very-good"
+ },
+ {
+ "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "type": "function",
+ "linktype": "miscellaneous",
+ "linksubtype": "function",
+ "name": "resolve",
+ "coveragePercent": 100,
+ "coverageCount": "1/1",
+ "status": "very-good"
+ },
+ {
+ "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "type": "function",
+ "linktype": "miscellaneous",
+ "linksubtype": "function",
+ "name": "submit",
+ "coveragePercent": 100,
+ "coverageCount": "1/1",
+ "status": "very-good"
+ },
+ {
+ "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "type": "function",
+ "linktype": "miscellaneous",
+ "linksubtype": "function",
+ "name": "validate",
+ "coveragePercent": 100,
+ "coverageCount": "1/1",
+ "status": "very-good"
+ },
+ {
+ "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "type": "variable",
+ "linktype": "miscellaneous",
+ "linksubtype": "variable",
+ "name": "initial",
+ "coveragePercent": 0,
+ "coverageCount": "0/1",
+ "status": "low"
+ },
+ {
+ "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts",
+ "type": "type alias",
+ "linktype": "miscellaneous",
+ "linksubtype": "typealias",
+ "name": "WizardState",
+ "coveragePercent": 100,
+ "coverageCount": "1/1",
+ "status": "very-good"
+ },
{
"filePath": "src/app/organisms/login-form/login-form.component.ts",
"type": "component",
@@ -3269,6 +5027,25 @@
"coverageCount": "1/2",
"status": "medium"
},
+ {
+ "filePath": "src/app/pages/concepts/concepts.page.ts",
+ "type": "component",
+ "linktype": "component",
+ "name": "ConceptsPage",
+ "coveragePercent": 8,
+ "coverageCount": "1/12",
+ "status": "low"
+ },
+ {
+ "filePath": "src/app/pages/concepts/concepts.page.ts",
+ "type": "function",
+ "linktype": "miscellaneous",
+ "linksubtype": "function",
+ "name": "fakeResource",
+ "coveragePercent": 100,
+ "coverageCount": "1/1",
+ "status": "very-good"
+ },
{
"filePath": "src/app/pages/dashboard/dashboard.page.ts",
"type": "component",
@@ -3283,9 +5060,9 @@
"type": "component",
"linktype": "component",
"name": "HerregistratiePage",
- "coveragePercent": 16,
- "coverageCount": "1/6",
- "status": "low"
+ "coveragePercent": 100,
+ "coverageCount": "1/1",
+ "status": "very-good"
},
{
"filePath": "src/app/pages/login/login.page.ts",