import { PureComponent, createRef } from 'react'; import Alert from '@mui/material/Alert'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import CircularProgress from '@mui/material/CircularProgress'; import List from '@mui/material/List'; import Typography from '@mui/material/Typography'; import Sortable from 'sortablejs'; import { enqueueSnackbar } from 'notistack'; import BackendError from '../../components/BackendError'; import SingleLineTextEdit from 'dashgui/SingleLineTextEdit'; import { PanelTile, PanelTileGrid } from 'dashgui/panels'; import withNavigate from '../../components/withNavigate'; import { backendErrorFrom } from '../../utils/backendError'; import { notifyError } from '../../utils/notify'; import { setLastArticleSku } from '../../automats/navigationAutomat'; import companyAutomat from '../../automats/companyAutomat'; import { ArtikelLager } from '../../components/warehouse'; import { GREEN_PANEL } from '../../colors'; function getInitialParams(props) { const searchParams = typeof window !== 'undefined' ? new URLSearchParams(window.location.search) : null; const prefill = props.location?.state?.name ?? props.location?.state?.newName ?? searchParams?.get('name') ?? searchParams?.get('newName') ?? ''; const returnTo = props.location?.state?.returnTo ?? searchParams?.get('returnTo') ?? null; return { prefill: prefill.trim(), returnTo }; } class ArtikelLagerSearchField extends PureComponent { constructor(props) { super(props); const initial = companyAutomat.state.warehouseFilter || props.prefill || ''; this.state = { input: initial, submitting: false, }; if (props.prefill && !companyAutomat.state.warehouseFilter) { companyAutomat.actions.filterWarehouses(props.prefill); } this.handleSubmit = this.handleSubmit.bind(this); } componentDidMount() { this.unsub = companyAutomat.subscribe(this, (state) => ({ input: state.warehouseFilter, })); } componentWillUnmount() { this.unsub?.(); } onInputChange(input) { this.setState({ input }); companyAutomat.actions.filterWarehouses(input); } async handleSubmit() { const name = this.state.input.trim(); if (!name || this.state.submitting) return; this.setState({ submitting: true }); try { const row = await companyAutomat.actions.createWarehouse({ name }); enqueueSnackbar(`Lager „${row.name || name}“ angelegt.`, { variant: 'success' }); this.setState({ input: '', submitting: false }); companyAutomat.actions.filterWarehouses(''); if (this.props.returnTo && this.props.navigate) { this.props.navigate(this.props.returnTo); } } catch (err) { this.setState({ submitting: false }); notifyError(err); } } render() { const { inputRef, returnTo, navigate } = this.props; const { input, submitting } = this.state; const canSubmit = Boolean(input.trim()) && !submitting; return ( {returnTo ? ( navigate?.(returnTo)} > Zurück } > {input ? `Lagername „${input}“ übernommen.` : 'Neues Lager anlegen.'} ) : null} this.onInputChange(next)} onKeyDown={(event) => { if (event.key === 'Enter') { event.preventDefault(); void this.handleSubmit(); } }} /> ); } } class ArtikelLagerPage extends PureComponent { constructor(props) { super(props); const initialWarehouses = companyAutomat.state.filteredWarehouses ?? companyAutomat.state.warehouses ?? []; this.state = { status: companyAutomat.state.warehousesStatus === 'ready' ? 'ready' : 'loading', error: companyAutomat.state.warehousesError ? backendErrorFrom(companyAutomat.state.warehousesError) : null, warehouses: initialWarehouses, filter: companyAutomat.state.warehouseFilter ?? '', }; this.searchRef = createRef(); this.listRef = createRef(); this._sortable = null; this._cancelled = false; this.handleRetry = this.handleRetry.bind(this); this._initSortable = this._initSortable.bind(this); this._destroySortable = this._destroySortable.bind(this); } componentDidMount() { setLastArticleSku(null); this._cancelled = false; this.unsub = companyAutomat.subscribe(this, (state) => ({ warehouses: state.filteredWarehouses ?? state.warehouses, filter: state.warehouseFilter ?? '', status: state.warehousesStatus, error: state.warehousesError ? backendErrorFrom(state.warehousesError) : null, })); if (this.state.status === 'ready') { this._initSortable(); } } componentDidUpdate(prevProps, prevState) { if ( prevState.status !== this.state.status || prevState.warehouses !== this.state.warehouses || prevState.filter !== this.state.filter ) { if (this.state.status === 'ready') { this._initSortable(); } } } componentWillUnmount() { this._cancelled = true; this.unsub?.(); this._destroySortable(); } _initSortable() { this._destroySortable(); const el = this.listRef.current; if (!el) return; const isFiltered = Boolean(this.state.filter?.trim()); this._sortable = Sortable.create(el, { handle: '.drag-handle', animation: 150, ghostClass: 'sortable-ghost', chosenClass: 'sortable-chosen', disabled: isFiltered, onEnd: (evt) => { const { oldIndex, newIndex } = evt; if (oldIndex === newIndex) return; this.setState( (prev) => { const next = [...prev.warehouses]; const [moved] = next.splice(oldIndex, 1); next.splice(newIndex, 0, moved); return { warehouses: next }; }, () => { const ids = this.state.warehouses.map((w) => w.id); void this._persistOrder(ids); } ); }, }); } _destroySortable() { this._sortable?.destroy(); this._sortable = null; } async _persistOrder(warehouseIds) { try { await companyAutomat.actions.reorderWarehouses(warehouseIds); } catch (err) { if (this._cancelled) return; notifyError(err); void companyAutomat.actions.reloadWarehouses(); } } handleRetry() { void companyAutomat.actions.loadWarehouses().catch((err) => notifyError(err)); } render() { const { status, error, warehouses, filter } = this.state; const { prefill, returnTo } = getInitialParams(this.props); if (status === 'loading' || status === 'idle') { return ( ); } if (status === 'failed') { return ; } const trimmedFilter = filter?.trim() ?? ''; return ( <> {warehouses.length === 0 ? ( {trimmedFilter ? `Keine Treffer für „${trimmedFilter}“.` : 'Noch keine Lager angelegt.'} ) : ( warehouses.map((wh) => ( )) )} ); } } export { ArtikelLager, ArtikelLagerSearchField }; export default withNavigate(ArtikelLagerPage);