import { assertNever } from '@shared/kernel/fp'; import { StamRow, StamTable, emptyRow } from '@beheer/domain/stamdata'; /** * The stamdata table editor as one Elm-style tagged union (the house form idiom). While * `loaded`, the draft `rows` are the edit state and `original` is the loaded snapshot the * diff compares against — no separate `dirty` flag (derive it, don't store it). Loading and * failure are states here too, so the page can render them via ``. * * There is no submit/save Msg: an edit stays local until the admin downloads the file (the * apply path is a reviewed PR, not a runtime write — ADR-0004). */ export type StamdataEditorState = | { tag: 'loading' } | { tag: 'failed'; reason: string } | { tag: 'loaded'; table: StamTable; rows: StamRow[]; original: readonly StamRow[] }; export type StamdataEditorMsg = | { tag: 'Loading' } | { tag: 'Loaded'; table: StamTable; rows: StamRow[] } | { tag: 'LoadFailed'; reason: string } | { tag: 'CellEdited'; row: number; column: string; value: string } | { tag: 'RowAdded' } | { tag: 'RowRemoved'; row: number } | { tag: 'Seed'; state: StamdataEditorState }; // mount a specific state (stories/tests) export const initial: StamdataEditorState = { tag: 'loading' }; const copy = (rows: readonly StamRow[]): StamRow[] => rows.map((r) => ({ ...r })); export function reduce(s: StamdataEditorState, m: StamdataEditorMsg): StamdataEditorState { switch (m.tag) { case 'Loading': return { tag: 'loading' }; case 'Loaded': // original is an independent snapshot so later edits never mutate it (drives the diff). return { tag: 'loaded', table: m.table, rows: copy(m.rows), original: copy(m.rows) }; case 'LoadFailed': return { tag: 'failed', reason: m.reason }; case 'CellEdited': if (s.tag !== 'loaded') return s; return { ...s, rows: s.rows.map((r, i) => (i === m.row ? { ...r, [m.column]: m.value } : r)), }; case 'RowAdded': return s.tag === 'loaded' ? { ...s, rows: [...s.rows, emptyRow(s.table)] } : s; case 'RowRemoved': return s.tag === 'loaded' ? { ...s, rows: s.rows.filter((_, i) => i !== m.row) } : s; case 'Seed': return m.state; default: return assertNever(m); } }