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'; import warehouseAutomat from '../../automats/warehouseAutomat'; 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 = warehouseAutomat.state.filter || props.prefill || ''; this.state = { input: initial, submitting: false, }; if (props.prefill && !warehouseAutomat.state.filter) { warehouseAutomat.actions.filter(props.prefill); } this.handleSubmit = this.handleSubmit.bind(this); } componentDidMount() { this.unsub = warehouseAutomat.subscribe(this, (state) => ({ input: state.filter, })); } componentWillUnmount() { this.unsub?.(); } onInputChange(input) { this.setState({ input }); warehouseAutomat.actions.filter(input); } async handleSubmit() { const name = this.state.input.trim(); if (!name || this.state.submitting) return; this.setState({ submitting: true }); try { const row = await warehouseAutomat.actions.create({ name }); enqueueSnackbar(`Lager „${row.name || name}“ angelegt.`, { variant: 'success' }); this.setState({ input: '', submitting: false }); warehouseAutomat.actions.filter(''); 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 = warehouseAutomat.state.filteredWarehouses ?? warehouseAutomat.state.warehouses ?? []; this.state = { status: warehouseAutomat.state.status === 'ready' ? 'ready' : 'loading', error: warehouseAutomat.state.error ? backendErrorFrom(warehouseAutomat.state.error) : null, warehouses: initialWarehouses, filter: warehouseAutomat.state.filter ?? '', }; 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 = warehouseAutomat.subscribe(this, (state) => { if (this._cancelled) return; return { warehouses: state.filteredWarehouses, filter: state.filter, status: state.status, error: state.error ? backendErrorFrom(state.error) : null, }; }); if (warehouseAutomat.state.status !== 'ready') { void warehouseAutomat.actions.load().catch((err) => notifyError(err)); } else { 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 warehouseAutomat.actions.reorder(warehouseIds); } catch (err) { if (this._cancelled) return; notifyError(err); void warehouseAutomat.actions.reload(); } } handleRetry() { void warehouseAutomat.actions.load().catch((err) => notifyError(err)); } render() { const { status, error, warehouses } = this.state; const { prefill, returnTo } = getInitialParams(this.props); if (status === 'loading' || status === 'idle') { return ( ); } if (status === 'failed') { return ; } return ( <> {warehouses.length === 0 ? ( Noch keine Lager angelegt. ) : ( warehouses.map((wh) => ( )) )} ); } } export { ArtikelLager, ArtikelLagerSearchField }; export default withNavigate(ArtikelLagerPage);