import { PureComponent } from 'react'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import CircularProgress from '@mui/material/CircularProgress'; import IconButton from '@mui/material/IconButton'; import List from '@mui/material/List'; import ListItem from '@mui/material/ListItem'; import Tooltip from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined'; import CopyrightIcon from '@mui/icons-material/Copyright'; import ImageSearchIcon from '@mui/icons-material/ImageSearch'; import BackendError from '../../components/BackendError'; import SingleLineTextEdit from 'dashgui/SingleLineTextEdit'; import { PanelTile, PanelTileGrid } from 'dashgui/panels'; import { backendErrorFrom } from '../../utils/backendError'; import { notifyError } from '../../utils/notify'; import { setLastArticleSku } from '../../automats'; import brandAutomat from '../../automats/brandAutomat'; import { enqueueSnackbar } from 'notistack'; import { BrandThumb, BrandImageSearchDialog } from '../../components/brand'; function draftsFrom(brands) { return Object.fromEntries(brands.map((b) => [b.id, b.name])); } class ArtikelMarkenPage extends PureComponent { constructor(props) { super(props); const initialBrands = brandAutomat.state.brands || []; this.state = { status: brandAutomat.state.status === 'succeeded' ? 'ready' : 'loading', error: brandAutomat.state.error ? backendErrorFrom({ message: brandAutomat.state.error }) : null, brands: initialBrands, drafts: draftsFrom(initialBrands), newName: '', busyKey: null, searchBrand: null, }; this._cancelled = false; this.handleCreate = this.handleCreate.bind(this); this.handleRetry = this.handleRetry.bind(this); this.loadBrands = this.loadBrands.bind(this); } componentDidMount() { setLastArticleSku(null); this._cancelled = false; this.unsub = brandAutomat.subscribe((state) => { if (this._cancelled) return; if (state.status === 'succeeded') { this.applyBrands(state.brands); } else if (state.status === 'failed') { this.setState({ status: 'failed', error: backendErrorFrom({ message: state.error }) }); } }); if (brandAutomat.state.status !== 'succeeded') { void this.loadBrands(); } } componentWillUnmount() { this._cancelled = true; this.unsub?.(); } applyBrands(brands) { this.setState((prev) => { const currentDrafts = prev.drafts || {}; const nextDrafts = { ...draftsFrom(brands) }; for (const b of brands) { if (prev.busyKey !== b.id && currentDrafts[b.id] !== undefined) { nextDrafts[b.id] = currentDrafts[b.id]; } } return { status: 'ready', error: null, brands, drafts: nextDrafts, }; }); } async loadBrands() { if (brandAutomat.state.status !== 'succeeded') { this.setState({ status: 'loading', error: null }); } try { const list = await brandAutomat.actions.load(); if (this._cancelled) return; this.applyBrands(list); } catch (err) { if (this._cancelled) return; this.setState({ status: 'failed', error: backendErrorFrom(err) }); } } handleRetry() { void this.loadBrands(); } async handleCreate() { const name = this.state.newName.trim(); if (!name || this.state.busyKey != null) return; this.setState({ busyKey: 'new' }); try { const row = await brandAutomat.actions.upsert({ name }); if (this._cancelled) return; this.setState({ newName: '', busyKey: null }); enqueueSnackbar(`Marke „${row.name || name}" angelegt.`, { variant: 'success' }); } catch (err) { if (this._cancelled) return; notifyError(err); this.setState({ busyKey: null }); } } async handleRename(brand) { const draft = String(this.state.drafts[brand.id] ?? '').trim(); if (!draft) { this.setState((prev) => ({ drafts: { ...prev.drafts, [brand.id]: brand.name }, })); return; } if (draft === brand.name || this.state.busyKey != null) return; this.setState({ busyKey: brand.id }); try { await brandAutomat.actions.upsert({ name: draft }); if (this._cancelled) return; this.setState({ busyKey: null }); enqueueSnackbar('Marke gespeichert.', { variant: 'success' }); } catch (err) { if (this._cancelled) return; notifyError(err); this.setState((prev) => ({ busyKey: null, drafts: { ...prev.drafts, [brand.id]: brand.name }, })); } } async handleDelete(brand) { if (this.state.busyKey != null) return; if (!window.confirm(`Marke „${brand.name}" wirklich löschen?`)) return; this.setState({ busyKey: brand.id }); try { await brandAutomat.actions.remove(brand.id); if (this._cancelled) return; this.setState({ busyKey: null }); enqueueSnackbar('Marke gelöscht.', { variant: 'success' }); } catch (err) { if (this._cancelled) return; notifyError(err); this.setState({ busyKey: null }); } } render() { const { status, error, brands, drafts, newName, busyKey } = this.state; const busy = busyKey != null; if (status === 'loading') { return ( ); } if (status === 'failed') { return ; } return ( {brands.length === 0 ? ( Noch keine Marken angelegt. ) : ( brands.map((b) => ( } /> this.setState((prev) => ({ drafts: { ...prev.drafts, [b.id]: next }, })) } onBlur={() => void this.handleRename(b)} onKeyDown={(event) => { if (event.key === 'Enter') { event.preventDefault(); event.target.blur(); } if (event.key === 'Escape') { event.preventDefault(); this.setState((prev) => ({ drafts: { ...prev.drafts, [b.id]: b.name }, })); event.target.blur(); } }} sx={{ flex: 1 }} /> this.setState({ searchBrand: b })} > void this.handleDelete(b)} > )) )} this.setState({ newName: next })} onKeyDown={(event) => { if (event.key === 'Enter') { event.preventDefault(); void this.handleCreate(); } }} sx={{ flex: 1 }} /> this.setState({ searchBrand: null })} /> ); } } export default ArtikelMarkenPage;