style: format frontend, docs and skills with prettier; add .prettierignore
One-time prettier --write so the new format:check CI gate starts green. .prettierignore excludes generated (api-client.ts, documentation.json), vendored (public/cibg-huisstijl), and backend (dotnet format owns it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+114
-28
@@ -3,12 +3,24 @@ import { FormsModule } from '@angular/forms';
|
||||
import { FormFieldComponent } from '@shared/ui/form-field/form-field.component';
|
||||
import { TextInputComponent } from '@shared/ui/text-input/text-input.component';
|
||||
import { AlertComponent } from '@shared/ui/alert/alert.component';
|
||||
import { WizardShellComponent, WizardError, WizardStatus, naarStapLabel } from '@shared/layout/wizard-shell/wizard-shell.component';
|
||||
import {
|
||||
WizardShellComponent,
|
||||
WizardError,
|
||||
WizardStatus,
|
||||
naarStapLabel,
|
||||
} from '@shared/layout/wizard-shell/wizard-shell.component';
|
||||
import { ConfirmationComponent } from '@shared/ui/confirmation/confirmation.component';
|
||||
import { createStore } from '@shared/application/store';
|
||||
import { whenTag } from '@shared/kernel/fp';
|
||||
import { BigProfileStore } from '@registratie/application/big-profile.store';
|
||||
import { WizardState, WizardMsg, Draft, initial, reduce, hasProgress } from '@herregistratie/domain/herregistratie.machine';
|
||||
import {
|
||||
WizardState,
|
||||
WizardMsg,
|
||||
Draft,
|
||||
initial,
|
||||
reduce,
|
||||
hasProgress,
|
||||
} from '@herregistratie/domain/herregistratie.machine';
|
||||
import { createDraftSync } from '@registratie/application/draft-sync';
|
||||
import { DocumentUploadComponent } from '@shared/ui/upload/document-upload/document-upload.component';
|
||||
import { createUploadController } from '@shared/upload/upload-controller';
|
||||
@@ -23,13 +35,22 @@ import { UploadState, initialUpload, deliveryRefs } from '@shared/upload/upload.
|
||||
the dashboard shows "in behandeling" immediately. */
|
||||
@Component({
|
||||
selector: 'app-herregistratie-wizard',
|
||||
imports: [FormsModule, FormFieldComponent, TextInputComponent, AlertComponent, ConfirmationComponent, WizardShellComponent, DocumentUploadComponent],
|
||||
imports: [
|
||||
FormsModule,
|
||||
FormFieldComponent,
|
||||
TextInputComponent,
|
||||
AlertComponent,
|
||||
ConfirmationComponent,
|
||||
WizardShellComponent,
|
||||
DocumentUploadComponent,
|
||||
],
|
||||
template: `
|
||||
<app-wizard-shell
|
||||
[steps]="stepLabels"
|
||||
[current]="step() - 1"
|
||||
[stepTitle]="stepTitle()"
|
||||
i18n-processName="@@herregWizard.processName" processName="Herregistratie aanvragen"
|
||||
i18n-processName="@@herregWizard.processName"
|
||||
processName="Herregistratie aanvragen"
|
||||
[status]="shellStatus()"
|
||||
[primaryLabel]="primaryLabel()"
|
||||
[canGoBack]="step() > 1"
|
||||
@@ -39,23 +60,62 @@ import { UploadState, initialUpload, deliveryRefs } from '@shared/upload/upload.
|
||||
(back)="dispatch({ tag: 'Back' })"
|
||||
(cancel)="restart()"
|
||||
(retry)="onRetry()"
|
||||
(goToStep)="goToStep($event)">
|
||||
|
||||
(goToStep)="goToStep($event)"
|
||||
>
|
||||
@switch (step()) {
|
||||
@case (1) {
|
||||
<app-form-field i18n-label="@@herregWizard.urenLabel" label="Gewerkte uren (afgelopen 5 jaar)" fieldId="uren" required [error]="errUren()">
|
||||
<app-text-input inputId="uren" [ngModel]="draft().uren" (ngModelChange)="dispatch({ tag: 'SetField', key: 'uren', value: $event })"
|
||||
name="uren" [invalid]="!!errUren()" i18n-placeholder="@@herregWizard.urenPlaceholder" placeholder="bijv. 4160" />
|
||||
<app-form-field
|
||||
i18n-label="@@herregWizard.urenLabel"
|
||||
label="Gewerkte uren (afgelopen 5 jaar)"
|
||||
fieldId="uren"
|
||||
required
|
||||
[error]="errUren()"
|
||||
>
|
||||
<app-text-input
|
||||
inputId="uren"
|
||||
[ngModel]="draft().uren"
|
||||
(ngModelChange)="dispatch({ tag: 'SetField', key: 'uren', value: $event })"
|
||||
name="uren"
|
||||
[invalid]="!!errUren()"
|
||||
i18n-placeholder="@@herregWizard.urenPlaceholder"
|
||||
placeholder="bijv. 4160"
|
||||
/>
|
||||
</app-form-field>
|
||||
<app-form-field i18n-label="@@herregWizard.jarenLabel" label="Aantal jaren werkzaam" fieldId="jaren" required [error]="errJaren()">
|
||||
<app-text-input inputId="jaren" [ngModel]="draft().jaren" (ngModelChange)="dispatch({ tag: 'SetField', key: 'jaren', value: $event })"
|
||||
name="jaren" [invalid]="!!errJaren()" i18n-placeholder="@@herregWizard.jarenPlaceholder" placeholder="bijv. 5" />
|
||||
<app-form-field
|
||||
i18n-label="@@herregWizard.jarenLabel"
|
||||
label="Aantal jaren werkzaam"
|
||||
fieldId="jaren"
|
||||
required
|
||||
[error]="errJaren()"
|
||||
>
|
||||
<app-text-input
|
||||
inputId="jaren"
|
||||
[ngModel]="draft().jaren"
|
||||
(ngModelChange)="dispatch({ tag: 'SetField', key: 'jaren', value: $event })"
|
||||
name="jaren"
|
||||
[invalid]="!!errJaren()"
|
||||
i18n-placeholder="@@herregWizard.jarenPlaceholder"
|
||||
placeholder="bijv. 5"
|
||||
/>
|
||||
</app-form-field>
|
||||
}
|
||||
@case (2) {
|
||||
<app-form-field i18n-label="@@herregWizard.puntenLabel" label="Behaalde nascholingspunten" fieldId="punten" required [error]="errPunten()">
|
||||
<app-text-input inputId="punten" [ngModel]="draft().punten" (ngModelChange)="dispatch({ tag: 'SetField', key: 'punten', value: $event })"
|
||||
name="punten" [invalid]="!!errPunten()" i18n-placeholder="@@herregWizard.puntenPlaceholder" placeholder="bijv. 200" />
|
||||
<app-form-field
|
||||
i18n-label="@@herregWizard.puntenLabel"
|
||||
label="Behaalde nascholingspunten"
|
||||
fieldId="punten"
|
||||
required
|
||||
[error]="errPunten()"
|
||||
>
|
||||
<app-text-input
|
||||
inputId="punten"
|
||||
[ngModel]="draft().punten"
|
||||
(ngModelChange)="dispatch({ tag: 'SetField', key: 'punten', value: $event })"
|
||||
name="punten"
|
||||
[invalid]="!!errPunten()"
|
||||
i18n-placeholder="@@herregWizard.puntenPlaceholder"
|
||||
placeholder="bijv. 200"
|
||||
/>
|
||||
</app-form-field>
|
||||
}
|
||||
@case (3) {
|
||||
@@ -66,7 +126,8 @@ import { UploadState, initialUpload, deliveryRefs } from '@shared/upload/upload.
|
||||
(removeUpload)="uploadCtl.onRemove($event)"
|
||||
(retryUpload)="uploadCtl.onRetry($event)"
|
||||
(deleteUpload)="uploadCtl.onDelete($event)"
|
||||
(channelChange)="uploadCtl.onChannelChange($event.categoryId, $event.channel)" />
|
||||
(channelChange)="uploadCtl.onChannelChange($event.categoryId, $event.channel)"
|
||||
/>
|
||||
@if (errDocumenten()) {
|
||||
<app-alert type="warning">{{ errDocumenten() }}</app-alert>
|
||||
}
|
||||
@@ -74,7 +135,10 @@ import { UploadState, initialUpload, deliveryRefs } from '@shared/upload/upload.
|
||||
}
|
||||
|
||||
<div wizardSuccess>
|
||||
<app-confirmation i18n-title="@@herregWizard.success.title" title="Uw aanvraag tot herregistratie is ontvangen" />
|
||||
<app-confirmation
|
||||
i18n-title="@@herregWizard.success.title"
|
||||
title="Uw aanvraag tot herregistratie is ontvangen"
|
||||
/>
|
||||
</div>
|
||||
</app-wizard-shell>
|
||||
`,
|
||||
@@ -102,7 +166,9 @@ export class HerregistratieWizardComponent {
|
||||
snapshot: () => {
|
||||
const s = this.state();
|
||||
if (s.tag !== 'Editing' || !hasProgress(s)) return null;
|
||||
const documentIds = deliveryRefs(s.upload).filter((r) => r.channel === 'digital' && r.documentId).map((r) => r.documentId!);
|
||||
const documentIds = deliveryRefs(s.upload)
|
||||
.filter((r) => r.channel === 'digital' && r.documentId)
|
||||
.map((r) => r.documentId!);
|
||||
return { draft: s, stepIndex: s.step - 1, stepCount: this.stepLabels.length, documentIds };
|
||||
},
|
||||
onResume: (draft) => this.dispatch({ tag: 'Seed', state: draft as WizardState }),
|
||||
@@ -110,12 +176,22 @@ export class HerregistratieWizardComponent {
|
||||
});
|
||||
|
||||
// Stepper labels + per-step heading titles (presentational only).
|
||||
readonly stepLabels = [$localize`:@@herregWizard.step.werkervaring:Werkervaring`, $localize`:@@herregWizard.step.nascholing:Nascholing`, $localize`:@@herregWizard.step.documenten:Documenten`];
|
||||
private stepTitles = [$localize`:@@herregWizard.title.werkervaring:Werkervaring (afgelopen 5 jaar)`, $localize`:@@herregWizard.title.nascholing:Nascholing`, $localize`:@@herregWizard.title.documenten:Documenten aanleveren`];
|
||||
readonly stepLabels = [
|
||||
$localize`:@@herregWizard.step.werkervaring:Werkervaring`,
|
||||
$localize`:@@herregWizard.step.nascholing:Nascholing`,
|
||||
$localize`:@@herregWizard.step.documenten:Documenten`,
|
||||
];
|
||||
private stepTitles = [
|
||||
$localize`:@@herregWizard.title.werkervaring:Werkervaring (afgelopen 5 jaar)`,
|
||||
$localize`:@@herregWizard.title.nascholing:Nascholing`,
|
||||
$localize`:@@herregWizard.title.documenten:Documenten aanleveren`,
|
||||
];
|
||||
|
||||
private editing = computed(() => whenTag(this.state(), 'Editing'));
|
||||
protected step = computed(() => this.editing()?.step ?? 1);
|
||||
protected draft = computed<Draft>(() => this.editing()?.draft ?? { uren: '', jaren: '', punten: '' });
|
||||
protected draft = computed<Draft>(
|
||||
() => this.editing()?.draft ?? { uren: '', jaren: '', punten: '' },
|
||||
);
|
||||
protected upload = computed<UploadState>(() => this.editing()?.upload ?? initialUpload);
|
||||
protected errUren = computed(() => this.editing()?.errors.uren ?? '');
|
||||
protected errJaren = computed(() => this.editing()?.errors.jaren ?? '');
|
||||
@@ -132,20 +208,28 @@ export class HerregistratieWizardComponent {
|
||||
protected stepTitle = computed(() => this.stepTitles[this.step() - 1]);
|
||||
protected primaryLabel = computed(() => {
|
||||
const step = this.step();
|
||||
return step < 3 ? naarStapLabel(step + 1, this.stepLabels[step]) : $localize`:@@herregWizard.indienen:Herregistratie aanvragen`;
|
||||
return step < 3
|
||||
? naarStapLabel(step + 1, this.stepLabels[step])
|
||||
: $localize`:@@herregWizard.indienen:Herregistratie aanvragen`;
|
||||
});
|
||||
|
||||
/** Stepper emits a 0-based index for an earlier (visited) step. */
|
||||
protected goToStep(index: number) {
|
||||
this.dispatch({ tag: 'GaNaarStap', step: (index + 1) as 1 | 2 | 3 });
|
||||
}
|
||||
protected errorMessage = computed(() => $localize`:@@wizard.indienenMislukt:Indienen mislukt:` + ` ${this.failedError()}`);
|
||||
protected errorMessage = computed(
|
||||
() => $localize`:@@wizard.indienenMislukt:Indienen mislukt:` + ` ${this.failedError()}`,
|
||||
);
|
||||
protected shellStatus = computed<WizardStatus>(() => {
|
||||
switch (this.state().tag) {
|
||||
case 'Editing': return 'editing';
|
||||
case 'Submitting': return 'submitting';
|
||||
case 'Submitted': return 'submitted';
|
||||
case 'Failed': return 'failed';
|
||||
case 'Editing':
|
||||
return 'editing';
|
||||
case 'Submitting':
|
||||
return 'submitting';
|
||||
case 'Submitted':
|
||||
return 'submitted';
|
||||
case 'Failed':
|
||||
return 'failed';
|
||||
}
|
||||
});
|
||||
/** Current step's field errors, flattened for the shell's error summary. */
|
||||
@@ -160,7 +244,9 @@ export class HerregistratieWizardComponent {
|
||||
// An explicit seed (stories/tests) wins; otherwise resume the backend draft
|
||||
// (`?aanvraag=<id>`) or start fresh. Persistence is the draftSync controller's job.
|
||||
const seeded = this.seed();
|
||||
queueMicrotask(() => (seeded !== initial ? this.dispatch({ tag: 'Seed', state: seeded }) : this.draftSync.resume()));
|
||||
queueMicrotask(() =>
|
||||
seeded !== initial ? this.dispatch({ tag: 'Seed', state: seeded }) : this.draftSync.resume(),
|
||||
);
|
||||
}
|
||||
|
||||
onPrimary() {
|
||||
|
||||
@@ -20,12 +20,55 @@ export default meta;
|
||||
type Story = StoryObj<HerregistratieWizardComponent>;
|
||||
|
||||
// Each story seeds one state of the machine — one render per union variant.
|
||||
export const Step1: Story = { args: { seed: { tag: 'Editing', step: 1, draft: { uren: '', jaren: '', punten: '' }, errors: {}, upload: initialUpload } } };
|
||||
export const Step1: Story = {
|
||||
args: {
|
||||
seed: {
|
||||
tag: 'Editing',
|
||||
step: 1,
|
||||
draft: { uren: '', jaren: '', punten: '' },
|
||||
errors: {},
|
||||
upload: initialUpload,
|
||||
},
|
||||
},
|
||||
};
|
||||
export const Step1Error: Story = {
|
||||
args: { seed: { tag: 'Editing', step: 1, draft: { uren: 'abc', jaren: '', punten: '' }, errors: { uren: 'Vul een geheel aantal in (0 of meer).', jaren: 'Vul een geheel aantal in (0 of meer).' }, upload: initialUpload } satisfies WizardState },
|
||||
args: {
|
||||
seed: {
|
||||
tag: 'Editing',
|
||||
step: 1,
|
||||
draft: { uren: 'abc', jaren: '', punten: '' },
|
||||
errors: {
|
||||
uren: 'Vul een geheel aantal in (0 of meer).',
|
||||
jaren: 'Vul een geheel aantal in (0 of meer).',
|
||||
},
|
||||
upload: initialUpload,
|
||||
} satisfies WizardState,
|
||||
},
|
||||
};
|
||||
export const Step2: Story = {
|
||||
args: {
|
||||
seed: {
|
||||
tag: 'Editing',
|
||||
step: 2,
|
||||
draft: { uren: '4160', jaren: '5', punten: '' },
|
||||
errors: {},
|
||||
upload: initialUpload,
|
||||
},
|
||||
},
|
||||
};
|
||||
export const Step3: Story = {
|
||||
args: {
|
||||
seed: {
|
||||
tag: 'Editing',
|
||||
step: 3,
|
||||
draft: { uren: '4160', jaren: '5', punten: '200' },
|
||||
errors: {},
|
||||
upload: initialUpload,
|
||||
},
|
||||
},
|
||||
};
|
||||
export const Step2: Story = { args: { seed: { tag: 'Editing', step: 2, draft: { uren: '4160', jaren: '5', punten: '' }, errors: {}, upload: initialUpload } } };
|
||||
export const Step3: Story = { args: { seed: { tag: 'Editing', step: 3, draft: { uren: '4160', jaren: '5', punten: '200' }, errors: {}, upload: initialUpload } } };
|
||||
export const Submitting: Story = { args: { seed: { tag: 'Submitting', data: validData } } };
|
||||
export const Submitted: Story = { args: { seed: { tag: 'Submitted', data: validData } } };
|
||||
export const Failed: Story = { args: { seed: { tag: 'Failed', data: validData, error: 'Netwerkfout' } } };
|
||||
export const Failed: Story = {
|
||||
args: { seed: { tag: 'Failed', data: validData, error: 'Netwerkfout' } },
|
||||
};
|
||||
|
||||
@@ -13,7 +13,11 @@ import { HerregistratieWizardComponent } from '@herregistratie/ui/herregistratie
|
||||
selector: 'app-herregistratie-page',
|
||||
imports: [PageShellComponent, AlertComponent, ...ASYNC, HerregistratieWizardComponent],
|
||||
template: `
|
||||
<app-page-shell i18n-heading="@@herregistratie.heading" heading="Herregistratie aanvragen" backLink="/dashboard">
|
||||
<app-page-shell
|
||||
i18n-heading="@@herregistratie.heading"
|
||||
heading="Herregistratie aanvragen"
|
||||
backLink="/dashboard"
|
||||
>
|
||||
<app-async [data]="eligibility()">
|
||||
<ng-template appAsyncLoaded let-eligible>
|
||||
@if (eligible) {
|
||||
|
||||
@@ -8,7 +8,12 @@ import { AlertComponent } from '@shared/ui/alert/alert.component';
|
||||
import { DataRowComponent } from '@shared/ui/data-row/data-row.component';
|
||||
import { ReviewSectionComponent } from '@shared/ui/review-section/review-section.component';
|
||||
import { ConfirmationComponent } from '@shared/ui/confirmation/confirmation.component';
|
||||
import { WizardShellComponent, WizardError, WizardStatus, naarStapLabel } from '@shared/layout/wizard-shell/wizard-shell.component';
|
||||
import {
|
||||
WizardShellComponent,
|
||||
WizardError,
|
||||
WizardStatus,
|
||||
naarStapLabel,
|
||||
} from '@shared/layout/wizard-shell/wizard-shell.component';
|
||||
import { createStore } from '@shared/application/store';
|
||||
import { whenTag } from '@shared/kernel/fp';
|
||||
import { BigProfileStore } from '@registratie/application/big-profile.store';
|
||||
@@ -34,13 +39,25 @@ import { IntakePolicyStore } from '@herregistratie/application/intake-policy.sto
|
||||
sessionStorage so a page reload keeps the user's progress (cleared on tab close). */
|
||||
@Component({
|
||||
selector: 'app-intake-wizard',
|
||||
imports: [FormsModule, FormFieldComponent, TextInputComponent, RadioGroupComponent, ButtonComponent, AlertComponent, DataRowComponent, ReviewSectionComponent, ConfirmationComponent, WizardShellComponent],
|
||||
imports: [
|
||||
FormsModule,
|
||||
FormFieldComponent,
|
||||
TextInputComponent,
|
||||
RadioGroupComponent,
|
||||
ButtonComponent,
|
||||
AlertComponent,
|
||||
DataRowComponent,
|
||||
ReviewSectionComponent,
|
||||
ConfirmationComponent,
|
||||
WizardShellComponent,
|
||||
],
|
||||
template: `
|
||||
<app-wizard-shell
|
||||
[steps]="stepLabels"
|
||||
[current]="cursor()"
|
||||
[stepTitle]="stepTitle()"
|
||||
i18n-processName="@@intake.processName" processName="Herregistratie-intake"
|
||||
i18n-processName="@@intake.processName"
|
||||
processName="Herregistratie-intake"
|
||||
[status]="shellStatus()"
|
||||
[primaryLabel]="primaryLabel()"
|
||||
[canGoBack]="cursor() > 0"
|
||||
@@ -50,68 +67,186 @@ import { IntakePolicyStore } from '@herregistratie/application/intake-policy.sto
|
||||
(back)="dispatch({ tag: 'Back' })"
|
||||
(cancel)="restart()"
|
||||
(retry)="onRetry()"
|
||||
(goToStep)="dispatch({ tag: 'GaNaarStap', cursor: $event })">
|
||||
|
||||
(goToStep)="dispatch({ tag: 'GaNaarStap', cursor: $event })"
|
||||
>
|
||||
@switch (step()) {
|
||||
@case ('buitenland') {
|
||||
<app-form-field i18n-label="@@intake.q.buitenland" label="Heeft u de afgelopen 5 jaar buiten Nederland gewerkt?" fieldId="buitenlandGewerkt" required [error]="err('buitenlandGewerkt')">
|
||||
<app-radio-group name="buitenlandGewerkt" [options]="jaNee"
|
||||
[ngModel]="answers().buitenlandGewerkt ?? ''" (ngModelChange)="set('buitenlandGewerkt', $event)" />
|
||||
<app-form-field
|
||||
i18n-label="@@intake.q.buitenland"
|
||||
label="Heeft u de afgelopen 5 jaar buiten Nederland gewerkt?"
|
||||
fieldId="buitenlandGewerkt"
|
||||
required
|
||||
[error]="err('buitenlandGewerkt')"
|
||||
>
|
||||
<app-radio-group
|
||||
name="buitenlandGewerkt"
|
||||
[options]="jaNee"
|
||||
[ngModel]="answers().buitenlandGewerkt ?? ''"
|
||||
(ngModelChange)="set('buitenlandGewerkt', $event)"
|
||||
/>
|
||||
</app-form-field>
|
||||
@if (answers().buitenlandGewerkt === 'ja') {
|
||||
<app-form-field i18n-label="@@intake.q.land" label="In welk land?" fieldId="land" required [error]="err('land')">
|
||||
<app-text-input inputId="land" [ngModel]="answers().land ?? ''" (ngModelChange)="set('land', $event)" name="land" i18n-placeholder="@@intake.q.landPlaceholder" placeholder="bijv. België" />
|
||||
<app-form-field
|
||||
i18n-label="@@intake.q.land"
|
||||
label="In welk land?"
|
||||
fieldId="land"
|
||||
required
|
||||
[error]="err('land')"
|
||||
>
|
||||
<app-text-input
|
||||
inputId="land"
|
||||
[ngModel]="answers().land ?? ''"
|
||||
(ngModelChange)="set('land', $event)"
|
||||
name="land"
|
||||
i18n-placeholder="@@intake.q.landPlaceholder"
|
||||
placeholder="bijv. België"
|
||||
/>
|
||||
</app-form-field>
|
||||
<app-form-field i18n-label="@@intake.q.buitenlandseUren" label="Hoeveel uur heeft u daar gewerkt?" fieldId="buitenlandseUren" required [error]="err('buitenlandseUren')">
|
||||
<app-text-input inputId="buitenlandseUren" [ngModel]="answers().buitenlandseUren ?? ''" (ngModelChange)="set('buitenlandseUren', $event)" name="buitenlandseUren" i18n-placeholder="@@intake.q.buitenlandseUrenPlaceholder" placeholder="bijv. 800" />
|
||||
<app-form-field
|
||||
i18n-label="@@intake.q.buitenlandseUren"
|
||||
label="Hoeveel uur heeft u daar gewerkt?"
|
||||
fieldId="buitenlandseUren"
|
||||
required
|
||||
[error]="err('buitenlandseUren')"
|
||||
>
|
||||
<app-text-input
|
||||
inputId="buitenlandseUren"
|
||||
[ngModel]="answers().buitenlandseUren ?? ''"
|
||||
(ngModelChange)="set('buitenlandseUren', $event)"
|
||||
name="buitenlandseUren"
|
||||
i18n-placeholder="@@intake.q.buitenlandseUrenPlaceholder"
|
||||
placeholder="bijv. 800"
|
||||
/>
|
||||
</app-form-field>
|
||||
}
|
||||
}
|
||||
@case ('werk') {
|
||||
<app-form-field i18n-label="@@intake.q.urenNl" label="Gewerkte uren in Nederland (afgelopen 5 jaar)" fieldId="uren" required [error]="err('uren')">
|
||||
<app-text-input inputId="uren" [ngModel]="answers().uren ?? ''" (ngModelChange)="set('uren', $event)" name="uren" i18n-placeholder="@@intake.q.urenNlPlaceholder" placeholder="bijv. 4160" />
|
||||
<app-form-field
|
||||
i18n-label="@@intake.q.urenNl"
|
||||
label="Gewerkte uren in Nederland (afgelopen 5 jaar)"
|
||||
fieldId="uren"
|
||||
required
|
||||
[error]="err('uren')"
|
||||
>
|
||||
<app-text-input
|
||||
inputId="uren"
|
||||
[ngModel]="answers().uren ?? ''"
|
||||
(ngModelChange)="set('uren', $event)"
|
||||
name="uren"
|
||||
i18n-placeholder="@@intake.q.urenNlPlaceholder"
|
||||
placeholder="bijv. 4160"
|
||||
/>
|
||||
</app-form-field>
|
||||
@if (scholingZichtbaar()) {
|
||||
<app-form-field i18n-label="@@intake.q.scholing" label="U werkte relatief weinig uren. Heeft u aanvullende scholing gevolgd?" fieldId="scholingGevolgd" required [error]="err('scholingGevolgd')">
|
||||
<app-radio-group name="scholingGevolgd" [options]="jaNee"
|
||||
[ngModel]="answers().scholingGevolgd ?? ''" (ngModelChange)="set('scholingGevolgd', $event)" />
|
||||
<app-form-field
|
||||
i18n-label="@@intake.q.scholing"
|
||||
label="U werkte relatief weinig uren. Heeft u aanvullende scholing gevolgd?"
|
||||
fieldId="scholingGevolgd"
|
||||
required
|
||||
[error]="err('scholingGevolgd')"
|
||||
>
|
||||
<app-radio-group
|
||||
name="scholingGevolgd"
|
||||
[options]="jaNee"
|
||||
[ngModel]="answers().scholingGevolgd ?? ''"
|
||||
(ngModelChange)="set('scholingGevolgd', $event)"
|
||||
/>
|
||||
</app-form-field>
|
||||
}
|
||||
@if (answers().scholingGevolgd === 'ja') {
|
||||
<app-form-field i18n-label="@@intake.q.punten" label="Behaalde nascholingspunten" fieldId="punten" required [error]="err('punten')">
|
||||
<app-text-input inputId="punten" [ngModel]="answers().punten ?? ''" (ngModelChange)="set('punten', $event)" name="punten" i18n-placeholder="@@intake.q.puntenPlaceholder" placeholder="bijv. 200" />
|
||||
<app-form-field
|
||||
i18n-label="@@intake.q.punten"
|
||||
label="Behaalde nascholingspunten"
|
||||
fieldId="punten"
|
||||
required
|
||||
[error]="err('punten')"
|
||||
>
|
||||
<app-text-input
|
||||
inputId="punten"
|
||||
[ngModel]="answers().punten ?? ''"
|
||||
(ngModelChange)="set('punten', $event)"
|
||||
name="punten"
|
||||
i18n-placeholder="@@intake.q.puntenPlaceholder"
|
||||
placeholder="bijv. 200"
|
||||
/>
|
||||
</app-form-field>
|
||||
}
|
||||
}
|
||||
@case ('review') {
|
||||
<app-alert type="info" i18n="@@intake.review.controleer">Controleer uw antwoorden en dien de aanvraag in.</app-alert>
|
||||
<app-review-section i18n-heading="@@intake.sectie.buitenland" heading="Buitenland"
|
||||
i18n-editAriaLabel="@@intake.buitenlandWijzigenAria" editAriaLabel="Wijzigen buitenland"
|
||||
(edit)="dispatch({ tag: 'GaNaarStap', cursor: 0 })">
|
||||
<div app-data-row i18n-key="@@intake.review.buitenNl" key="Buiten NL gewerkt" [value]="answers().buitenlandGewerkt ?? '—'"></div>
|
||||
<app-alert type="info" i18n="@@intake.review.controleer"
|
||||
>Controleer uw antwoorden en dien de aanvraag in.</app-alert
|
||||
>
|
||||
<app-review-section
|
||||
i18n-heading="@@intake.sectie.buitenland"
|
||||
heading="Buitenland"
|
||||
i18n-editAriaLabel="@@intake.buitenlandWijzigenAria"
|
||||
editAriaLabel="Wijzigen buitenland"
|
||||
(edit)="dispatch({ tag: 'GaNaarStap', cursor: 0 })"
|
||||
>
|
||||
<div
|
||||
app-data-row
|
||||
i18n-key="@@intake.review.buitenNl"
|
||||
key="Buiten NL gewerkt"
|
||||
[value]="answers().buitenlandGewerkt ?? '—'"
|
||||
></div>
|
||||
@if (answers().buitenlandGewerkt === 'ja') {
|
||||
<div app-data-row i18n-key="@@intake.review.land" key="Land" [value]="answers().land ?? ''"></div>
|
||||
<div app-data-row i18n-key="@@intake.review.buitenlandseUren" key="Buitenlandse uren" [value]="answers().buitenlandseUren ?? ''"></div>
|
||||
<div
|
||||
app-data-row
|
||||
i18n-key="@@intake.review.land"
|
||||
key="Land"
|
||||
[value]="answers().land ?? ''"
|
||||
></div>
|
||||
<div
|
||||
app-data-row
|
||||
i18n-key="@@intake.review.buitenlandseUren"
|
||||
key="Buitenlandse uren"
|
||||
[value]="answers().buitenlandseUren ?? ''"
|
||||
></div>
|
||||
}
|
||||
</app-review-section>
|
||||
<app-review-section class="app-section" i18n-heading="@@intake.sectie.werk" heading="Werk in Nederland"
|
||||
i18n-editAriaLabel="@@intake.werkWijzigenAria" editAriaLabel="Wijzigen werk in Nederland"
|
||||
(edit)="dispatch({ tag: 'GaNaarStap', cursor: 1 })">
|
||||
<div app-data-row i18n-key="@@intake.review.urenNl" key="Uren NL" [value]="answers().uren ?? ''"></div>
|
||||
<app-review-section
|
||||
class="app-section"
|
||||
i18n-heading="@@intake.sectie.werk"
|
||||
heading="Werk in Nederland"
|
||||
i18n-editAriaLabel="@@intake.werkWijzigenAria"
|
||||
editAriaLabel="Wijzigen werk in Nederland"
|
||||
(edit)="dispatch({ tag: 'GaNaarStap', cursor: 1 })"
|
||||
>
|
||||
<div
|
||||
app-data-row
|
||||
i18n-key="@@intake.review.urenNl"
|
||||
key="Uren NL"
|
||||
[value]="answers().uren ?? ''"
|
||||
></div>
|
||||
@if (scholingZichtbaar()) {
|
||||
<div app-data-row i18n-key="@@intake.review.scholing" key="Aanvullende scholing" [value]="answers().scholingGevolgd ?? ''"></div>
|
||||
<div
|
||||
app-data-row
|
||||
i18n-key="@@intake.review.scholing"
|
||||
key="Aanvullende scholing"
|
||||
[value]="answers().scholingGevolgd ?? ''"
|
||||
></div>
|
||||
}
|
||||
@if (answers().scholingGevolgd === 'ja') {
|
||||
<div app-data-row i18n-key="@@intake.review.punten" key="Nascholingspunten" [value]="answers().punten ?? ''"></div>
|
||||
<div
|
||||
app-data-row
|
||||
i18n-key="@@intake.review.punten"
|
||||
key="Nascholingspunten"
|
||||
[value]="answers().punten ?? ''"
|
||||
></div>
|
||||
}
|
||||
</app-review-section>
|
||||
}
|
||||
}
|
||||
|
||||
<div wizardSuccess>
|
||||
<app-confirmation i18n-title="@@intake.success.title" title="Uw aanvraag tot herregistratie is ontvangen">
|
||||
<app-confirmation
|
||||
i18n-title="@@intake.success.title"
|
||||
title="Uw aanvraag tot herregistratie is ontvangen"
|
||||
>
|
||||
<div class="app-section">
|
||||
<app-button variant="secondary" (click)="restart()" i18n="@@intake.opnieuw">Opnieuw beginnen</app-button>
|
||||
<app-button variant="secondary" (click)="restart()" i18n="@@intake.opnieuw"
|
||||
>Opnieuw beginnen</app-button
|
||||
>
|
||||
</div>
|
||||
</app-confirmation>
|
||||
</div>
|
||||
@@ -151,13 +286,19 @@ export class IntakeWizardComponent {
|
||||
protected answers = computed<Answers>(() => this.answering()?.answers ?? {});
|
||||
protected step = computed<StepId>(() => STEPS[Math.min(this.cursor(), STEPS.length - 1)]);
|
||||
/** Server-owned threshold from the policy endpoint (mirrored into machine state). */
|
||||
protected scholingThreshold = computed(() => this.answering()?.scholingThreshold ?? SCHOLING_THRESHOLD_DEFAULT);
|
||||
protected scholingThreshold = computed(
|
||||
() => this.answering()?.scholingThreshold ?? SCHOLING_THRESHOLD_DEFAULT,
|
||||
);
|
||||
/** Whether the inline scholing question is shown (and required) in the 'werk' step. */
|
||||
protected scholingZichtbaar = computed(() => lageUren(this.answers(), this.scholingThreshold()));
|
||||
protected failedError = computed(() => whenTag(this.state(), 'Failed')?.error ?? '');
|
||||
|
||||
// --- Presentational wiring for the shared wizard shell ---------------------
|
||||
readonly stepLabels = [$localize`:@@intake.step.buitenland:Buitenland`, $localize`:@@intake.step.werk:Werk`, $localize`:@@intake.step.controle:Controle`];
|
||||
readonly stepLabels = [
|
||||
$localize`:@@intake.step.buitenland:Buitenland`,
|
||||
$localize`:@@intake.step.werk:Werk`,
|
||||
$localize`:@@intake.step.controle:Controle`,
|
||||
];
|
||||
private stepTitles: Record<StepId, string> = {
|
||||
buitenland: $localize`:@@intake.title.buitenland:Werken in het buitenland`,
|
||||
werk: $localize`:@@intake.title.werk:Werkervaring in Nederland`,
|
||||
@@ -169,13 +310,19 @@ export class IntakeWizardComponent {
|
||||
const next = this.cursor() + 1;
|
||||
return naarStapLabel(next + 1, this.stepLabels[next]);
|
||||
});
|
||||
protected errorMessage = computed(() => $localize`:@@wizard.indienenMislukt:Indienen mislukt:` + ` ${this.failedError()}`);
|
||||
protected errorMessage = computed(
|
||||
() => $localize`:@@wizard.indienenMislukt:Indienen mislukt:` + ` ${this.failedError()}`,
|
||||
);
|
||||
protected shellStatus = computed<WizardStatus>(() => {
|
||||
switch (this.state().tag) {
|
||||
case 'Answering': return 'editing';
|
||||
case 'Submitting': return 'submitting';
|
||||
case 'Submitted': return 'submitted';
|
||||
case 'Failed': return 'failed';
|
||||
case 'Answering':
|
||||
return 'editing';
|
||||
case 'Submitting':
|
||||
return 'submitting';
|
||||
case 'Submitted':
|
||||
return 'submitted';
|
||||
case 'Failed':
|
||||
return 'failed';
|
||||
}
|
||||
});
|
||||
/** Current step's field errors, flattened for the shell's error summary. The
|
||||
@@ -188,13 +335,16 @@ export class IntakeWizardComponent {
|
||||
});
|
||||
|
||||
protected err = (k: keyof Answers) => this.answering()?.errors[k] ?? '';
|
||||
protected set = (key: keyof Answers, value: string) => this.dispatch({ tag: 'SetAnswer', key, value });
|
||||
protected set = (key: keyof Answers, value: string) =>
|
||||
this.dispatch({ tag: 'SetAnswer', key, value });
|
||||
|
||||
constructor() {
|
||||
// An explicit seed (stories/tests) wins; otherwise resume the backend draft
|
||||
// (`?aanvraag=<id>`) or start fresh. Persistence is the draftSync controller's job.
|
||||
const seeded = this.seed();
|
||||
queueMicrotask(() => (seeded !== initial ? this.dispatch({ tag: 'Seed', state: seeded }) : this.draftSync.resume()));
|
||||
queueMicrotask(() =>
|
||||
seeded !== initial ? this.dispatch({ tag: 'Seed', state: seeded }) : this.draftSync.resume(),
|
||||
);
|
||||
// Apply the server-owned threshold into machine state as it arrives. Track
|
||||
// only the policy value; untrack the dispatch (it reads the state signal
|
||||
// internally, which would otherwise make this effect loop on its own write).
|
||||
|
||||
@@ -17,14 +17,26 @@ const meta: Meta<IntakeWizardComponent> = {
|
||||
export default meta;
|
||||
type Story = StoryObj<IntakeWizardComponent>;
|
||||
|
||||
const answering = (answers: Answers, cursor = 0): IntakeState => ({ tag: 'Answering', answers, cursor, errors: {}, scholingThreshold: 1000 });
|
||||
const answering = (answers: Answers, cursor = 0): IntakeState => ({
|
||||
tag: 'Answering',
|
||||
answers,
|
||||
cursor,
|
||||
errors: {},
|
||||
scholingThreshold: 1000,
|
||||
});
|
||||
|
||||
export const Start: Story = { args: { seed: answering({}) } };
|
||||
// Inline reveal: country/hours appear within the buitenland step (cursor 0).
|
||||
export const AbroadBranch: Story = { args: { seed: answering({ buitenlandGewerkt: 'ja' }, 0) } };
|
||||
// Inline reveal: the scholing question appears within the werk step (cursor 1).
|
||||
export const LowHoursScholing: Story = { args: { seed: answering({ buitenlandGewerkt: 'nee', uren: '500' }, 1) } };
|
||||
export const Review: Story = { args: { seed: answering({ buitenlandGewerkt: 'nee', uren: '4160', punten: '200' }, 2) } };
|
||||
export const LowHoursScholing: Story = {
|
||||
args: { seed: answering({ buitenlandGewerkt: 'nee', uren: '500' }, 1) },
|
||||
};
|
||||
export const Review: Story = {
|
||||
args: { seed: answering({ buitenlandGewerkt: 'nee', uren: '4160', punten: '200' }, 2) },
|
||||
};
|
||||
export const Submitting: Story = { args: { seed: { tag: 'Submitting', data: validData } } };
|
||||
export const Submitted: Story = { args: { seed: { tag: 'Submitted', data: validData } } };
|
||||
export const Failed: Story = { args: { seed: { tag: 'Failed', data: validData, error: 'Netwerkfout' } } };
|
||||
export const Failed: Story = {
|
||||
args: { seed: { tag: 'Failed', data: validData, error: 'Netwerkfout' } },
|
||||
};
|
||||
|
||||
@@ -9,11 +9,14 @@ import { IntakeWizardComponent } from '@herregistratie/ui/intake-wizard/intake-w
|
||||
selector: 'app-intake-page',
|
||||
imports: [PageShellComponent, AlertComponent, IntakeWizardComponent],
|
||||
template: `
|
||||
<app-page-shell i18n-heading="@@intake.heading" heading="Herregistratie — intake" backLink="/dashboard">
|
||||
<app-page-shell
|
||||
i18n-heading="@@intake.heading"
|
||||
heading="Herregistratie — intake"
|
||||
backLink="/dashboard"
|
||||
>
|
||||
<app-alert type="info" i18n="@@intake.intro">
|
||||
Een paar vragen bepalen welke gegevens we nodig hebben. Afhankelijk van uw
|
||||
antwoorden verschijnen er extra vragen. Uw antwoorden blijven bewaard als u
|
||||
de pagina herlaadt.
|
||||
Een paar vragen bepalen welke gegevens we nodig hebben. Afhankelijk van uw antwoorden
|
||||
verschijnen er extra vragen. Uw antwoorden blijven bewaard als u de pagina herlaadt.
|
||||
</app-alert>
|
||||
<div class="app-section">
|
||||
<app-intake-wizard />
|
||||
|
||||
Reference in New Issue
Block a user