diff --git a/src/components/article/ArticleUnitEditorDialog.jsx b/src/components/article/ArticleUnitEditorDialog.jsx index 395158b..3727e15 100644 --- a/src/components/article/ArticleUnitEditorDialog.jsx +++ b/src/components/article/ArticleUnitEditorDialog.jsx @@ -68,28 +68,27 @@ export default class ArticleUnitEditorDialog extends Component { super(props); this.state = { busy: false, - units: [], - unitsLoading: false, + units: companyAutomat.state.units, + unitsLoading: companyAutomat.state.unitsStatus === 'loading', form: emptyForm(), selectedSi: null, }; } + componentDidMount() { + this.unsub = companyAutomat.subscribe(this, (state) => ({ + units: state.units, + unitsLoading: state.unitsStatus === 'loading', + })); + } + + componentWillUnmount() { + this.unsub?.(); + } + componentDidUpdate(prevProps) { if (this.props.open && !prevProps.open) { this.setState({ form: emptyForm(), selectedSi: null }); - void this.loadUnits(); - } - } - - async loadUnits() { - this.setState({ unitsLoading: true }); - try { - const units = await companyAutomat.actions.loadUnits(); - this.setState({ units, unitsLoading: false }); - } catch (err) { - this.setState({ unitsLoading: false }); - notifyError(err); } } @@ -116,23 +115,22 @@ export default class ArticleUnitEditorDialog extends Component { if (!formValid(form)) return; this.setState({ busy: true }); try { - const data = await unitApi.upsert({ + const payload = { si: String(form.si ?? '').trim(), basesi: String(form.basesi ?? '').trim(), basesifactor: parseFactor(form.basesifactor), symbol: String(form.symbol ?? '').trim(), name: String(form.name ?? '').trim(), - }); - requireObject(data, 'PUT /api/unit'); - requireObject(data.unit, 'unit'); - const units = await companyAutomat.actions.loadUnits(); + }; + const units = await companyAutomat.actions.upsertUnit(payload); + const saved = units.find((u) => u.si === payload.si) ?? payload; this.setState({ busy: false, units, - form: formFromUnit(data.unit), - selectedSi: data.unit.si, + form: formFromUnit(saved), + selectedSi: saved.si, }); - this.props.onUnitUpserted?.(data.unit); + this.props.onUnitUpserted?.(saved); } catch (err) { this.setState({ busy: false }); notifyError(err); @@ -144,9 +142,13 @@ export default class ArticleUnitEditorDialog extends Component { if (!selectedSi || selectedSi === DEFAULT_UNIT_SI) return; this.setState({ busy: true }); try { - await unitApi.delete(selectedSi); - const units = await companyAutomat.actions.loadUnits(); - this.setState({ busy: false, units, form: emptyForm(), selectedSi: null }); + await companyAutomat.actions.deleteUnit(selectedSi); + this.setState({ + busy: false, + units: companyAutomat.state.units, + form: emptyForm(), + selectedSi: null, + }); this.props.onUnitDeleted?.(selectedSi); } catch (err) { this.setState({ busy: false }); diff --git a/src/components/merkmale/AuswahlMerkmaleTab.jsx b/src/components/merkmale/AuswahlMerkmaleTab.jsx index cadf409..2bf14e7 100644 --- a/src/components/merkmale/AuswahlMerkmaleTab.jsx +++ b/src/components/merkmale/AuswahlMerkmaleTab.jsx @@ -82,9 +82,7 @@ class AuswahlMerkmaleTab extends PureComponent { } }, ['attributes', 'attributesStatus', 'attributesError']); - if (companyAutomat.state.attributesStatus !== 'succeeded') { - void this.load(); - } else { + if (companyAutomat.state.attributesStatus === 'succeeded') { this._initAttrSortable(); this._initValSortable(); } diff --git a/src/components/merkmale/TextMerkmaleTab.jsx b/src/components/merkmale/TextMerkmaleTab.jsx index 4401ebe..e5a54ce 100644 --- a/src/components/merkmale/TextMerkmaleTab.jsx +++ b/src/components/merkmale/TextMerkmaleTab.jsx @@ -65,9 +65,7 @@ class TextMerkmaleTab extends PureComponent { } }, ['properties', 'propertiesStatus', 'propertiesError']); - if (companyAutomat.state.propertiesStatus !== 'succeeded') { - void this.load(); - } else { + if (companyAutomat.state.propertiesStatus === 'succeeded') { this._initPropSortable(); } } diff --git a/src/components/settings/CompanySettingsPanel.jsx b/src/components/settings/CompanySettingsPanel.jsx index c8ce721..fb6732f 100644 --- a/src/components/settings/CompanySettingsPanel.jsx +++ b/src/components/settings/CompanySettingsPanel.jsx @@ -45,8 +45,6 @@ export default class CompanySettingsPanel extends Component { }); if (companyAutomat.state.current) { this.syncFormFromCompany(companyAutomat.state.current); - } else { - void this.loadCompany(); } } diff --git a/src/components/settings/NumberSettingsPanel.jsx b/src/components/settings/NumberSettingsPanel.jsx index 2155c22..b63d14e 100644 --- a/src/components/settings/NumberSettingsPanel.jsx +++ b/src/components/settings/NumberSettingsPanel.jsx @@ -1,4 +1,4 @@ -import { Component } from 'react'; +import { PureComponent } from 'react'; import Alert from '@mui/material/Alert'; import Box from '@mui/material/Box'; import CircularProgress from '@mui/material/CircularProgress'; @@ -17,7 +17,7 @@ import { enqueueSnackbar } from 'notistack'; import BackendError from '../BackendError'; import { number as numberApi } from '../../api'; import companyAutomat from '../../automats/companyAutomat'; -import { requireArray, requireDefined, requireObject } from '../../utils/apiShape'; +import { requireDefined, requireObject } from '../../utils/apiShape'; import { backendErrorFrom } from '../../utils/backendError'; import { notifyError } from '../../utils/notify'; @@ -31,44 +31,76 @@ function parseNext(value) { return Number.isInteger(n) && n >= 0 ? n : null; } -export default class NumberSettingsPanel extends Component { +function buildDrafts(numbers, prevDrafts = {}) { + return Object.fromEntries( + (Array.isArray(numbers) ? numbers : []).map((row) => { + const existing = prevDrafts[row.id]; + return [ + row.id, + existing?.busy + ? existing + : { + prefix: row.prefix ?? '', + padding: row.padding != null ? String(row.padding) : '', + next: row.next != null ? String(row.next) : '', + busy: false, + saveError: null, + }, + ]; + }), + ); +} + +export default class NumberSettingsPanel extends PureComponent { constructor(props) { super(props); + const numbers = Array.isArray(companyAutomat.state.numbers) + ? companyAutomat.state.numbers + : []; + const numbersStatus = companyAutomat.state.numbersStatus; this.state = { - status: 'loading', - error: null, - numbers: [], + status: + numbersStatus === 'ready' + ? 'ready' + : numbersStatus === 'failed' + ? 'failed' + : 'loading', + error: companyAutomat.state.numbersError + ? backendErrorFrom(companyAutomat.state.numbersError) + : null, + numbers, // id → { prefix, padding, next, busy, saveError } - drafts: {}, + drafts: buildDrafts(numbers), }; this.handleRetry = this.handleRetry.bind(this); this.handleSave = this.handleSave.bind(this); } componentDidMount() { - void this.load(); + this._unmounted = false; + this.unsub = companyAutomat.subscribe(this, (state) => { + const numbers = Array.isArray(state.numbers) ? state.numbers : []; + return { + status: + state.numbersStatus === 'ready' + ? 'ready' + : state.numbersStatus === 'failed' + ? 'failed' + : 'loading', + error: state.numbersError ? backendErrorFrom(state.numbersError) : null, + numbers, + drafts: + numbers !== this.state.numbers + ? buildDrafts(numbers, this.state.drafts) + : this.state.drafts, + }; + }); } async load() { this.setState({ status: 'loading', error: null }); try { - const numbers = requireArray( - await companyAutomat.actions.reloadNumbers(), - 'numbers', - ); - const drafts = Object.fromEntries( - numbers.map((row) => [ - row.id, - { - prefix: row.prefix ?? '', - padding: row.padding != null ? String(row.padding) : '', - next: row.next != null ? String(row.next) : '', - busy: false, - saveError: null, - }, - ]), - ); - this.setState({ status: 'ready', numbers, drafts }); + await companyAutomat.actions.reloadNumbers(); } catch (err) { notifyError(err); this.setState({ status: 'failed', error: backendErrorFrom(err) }); @@ -145,8 +177,9 @@ export default class NumberSettingsPanel extends Component { requireObject(data.number, 'number'); const updated = requireDefined(data.number, 'number'); - const numbers = this.state.numbers.map((n) => (n.id === id ? { ...n, ...body } : n)); - await companyAutomat.actions.reloadNumbers(); + const numbers = companyAutomat.actions.replaceNumbers( + this.state.numbers.map((n) => (n.id === id ? { ...n, ...updated } : n)), + ); this.setState({ numbers, drafts: { @@ -174,6 +207,7 @@ export default class NumberSettingsPanel extends Component { componentWillUnmount() { this._unmounted = true; + this.unsub?.(); } render() { diff --git a/src/components/settings/PosTerminalSettingsPanel.jsx b/src/components/settings/PosTerminalSettingsPanel.jsx index c0720ce..2bb648c 100644 --- a/src/components/settings/PosTerminalSettingsPanel.jsx +++ b/src/components/settings/PosTerminalSettingsPanel.jsx @@ -78,9 +78,6 @@ export default class PosTerminalSettingsPanel extends Component { if (categoryAutomat.state.treeStatus === 'idle') { void categoryAutomat.actions.loadTree(); } - if (companyAutomat.state.terminalsStatus !== 'succeeded') { - void this.loadTerminals(); - } } componentWillUnmount() { @@ -120,7 +117,6 @@ export default class PosTerminalSettingsPanel extends Component { await whenCategoryTreeReady(); } - await companyAutomat.actions.loadTerminals(); this.setState({ newTerminalName: '' }); enqueueSnackbar('Terminal hinzugefügt.', { variant: 'success' }); } catch (err) { @@ -134,7 +130,6 @@ export default class PosTerminalSettingsPanel extends Component { this.setState({ deletingId: terminal.id }); try { await companyAutomat.actions.deleteTerminal(terminal.id); - await companyAutomat.actions.loadTerminals(); enqueueSnackbar('Terminal gelöscht.', { variant: 'info' }); } catch (err) { notifyError(err);