204 WP-NN/RB-NN comments named a closed ticket instead of the code they sit next to. git blame already records history and stays correct when code moves; the comment does not. This sweep removes the reference and keeps the sentence, across 95 files in apps/ and libs/ plus the behaviour-spec generator's header text. Eleven references stay: five story files justify an a11y disable per the README's rule, and one line in a11y.mdx documents that convention. Two sentences needed a rewrite, not a deletion, so the reference's meaning survives its removal. behaviour-spec.mdx is regenerated, not hand-edited. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
93 lines
3.1 KiB
TypeScript
93 lines
3.1 KiB
TypeScript
import { Result, assertNever } from '@shared/kernel/fp';
|
|
|
|
/** The three actions the beoordeling screen offers a behandelaar — mirrors the
|
|
backend's `Besluit` enum member names 1:1 (the wire convention: a string, not a raw
|
|
enum — see `RecordBesluitRequest`). */
|
|
const BESLUIT_TAGS = ['Goedkeuren', 'Afwijzen', 'MeerInfoOpvragen'] as const;
|
|
export type BesluitTag = (typeof BESLUIT_TAGS)[number];
|
|
|
|
function isBesluitTag(v: string): v is BesluitTag {
|
|
return (BESLUIT_TAGS as readonly string[]).includes(v);
|
|
}
|
|
|
|
/** What the user picked (raw, possibly empty while nothing is selected yet). */
|
|
export interface Draft {
|
|
besluit: string;
|
|
toelichting: string;
|
|
}
|
|
|
|
/** After parsing — besluit is the narrow tag; toelichting is present only when given
|
|
(required for Afwijzen/MeerInfoOpvragen, optional for Goedkeuren — enforced by validate). */
|
|
export interface Valid {
|
|
besluit: BesluitTag;
|
|
toelichting?: string;
|
|
}
|
|
|
|
export type Errors = Partial<Record<keyof Draft, string>>;
|
|
|
|
/** The decision form as one tagged union — same idiom as every other form in this
|
|
house (form-machine skill), single-step. draft/errors exist only while Editing. */
|
|
export type BesluitState =
|
|
| { tag: 'Editing'; draft: Draft; errors: Errors }
|
|
| { tag: 'Submitting'; data: Valid }
|
|
| { tag: 'Submitted'; data: Valid }
|
|
| { tag: 'Failed'; data: Valid; error: string };
|
|
|
|
export const initial: BesluitState = {
|
|
tag: 'Editing',
|
|
draft: { besluit: '', toelichting: '' },
|
|
errors: {},
|
|
};
|
|
|
|
function validate(draft: Draft): Result<Errors, Valid> {
|
|
if (!isBesluitTag(draft.besluit)) {
|
|
return {
|
|
ok: false,
|
|
error: { besluit: $localize`:@@besluit.error.verplicht:Kies een besluit.` },
|
|
};
|
|
}
|
|
const toelichting = draft.toelichting.trim();
|
|
if (draft.besluit !== 'Goedkeuren' && toelichting === '') {
|
|
return {
|
|
ok: false,
|
|
error: {
|
|
toelichting: $localize`:@@besluit.error.toelichtingVerplicht:Geef een toelichting.`,
|
|
},
|
|
};
|
|
}
|
|
return { ok: true, value: { besluit: draft.besluit, toelichting: toelichting || undefined } };
|
|
}
|
|
|
|
export type BesluitMsg =
|
|
| { tag: 'SetField'; key: keyof Draft; value: string }
|
|
| { tag: 'Submit' }
|
|
| { tag: 'Retry' }
|
|
| { tag: 'SubmitConfirmed' }
|
|
| { tag: 'SubmitFailed'; error: string }
|
|
| { tag: 'Reset' }
|
|
| { tag: 'Seed'; state: BesluitState }; // mount a specific state (stories/tests)
|
|
|
|
export function reduce(s: BesluitState, m: BesluitMsg): BesluitState {
|
|
switch (m.tag) {
|
|
case 'SetField':
|
|
return s.tag === 'Editing' ? { ...s, draft: { ...s.draft, [m.key]: m.value } } : s;
|
|
case 'Submit': {
|
|
if (s.tag !== 'Editing') return s;
|
|
const r = validate(s.draft);
|
|
return r.ok ? { tag: 'Submitting', data: r.value } : { ...s, errors: r.error };
|
|
}
|
|
case 'Retry':
|
|
return s.tag === 'Failed' ? { tag: 'Submitting', data: s.data } : s;
|
|
case 'SubmitConfirmed':
|
|
return s.tag === 'Submitting' ? { tag: 'Submitted', data: s.data } : s;
|
|
case 'SubmitFailed':
|
|
return s.tag === 'Submitting' ? { tag: 'Failed', data: s.data, error: m.error } : s;
|
|
case 'Reset':
|
|
return initial;
|
|
case 'Seed':
|
|
return m.state;
|
|
default:
|
|
return assertNever(m);
|
|
}
|
|
}
|