From e93e61cfcf5a6d26c74a4d11a8cda79eac211545 Mon Sep 17 00:00:00 2001 From: seb Date: Thu, 8 Oct 2026 19:08:39 +0200 Subject: [PATCH] u --- src/components/brand/ArtikelMarke.jsx | 124 ++++++++++++++++ src/components/brand/ArtikelMarkenTable.jsx | 44 ++++++ src/components/brand/index.js | 2 + src/components/tiles/ArticleTile.jsx | 10 +- src/routes/artikel/marken.jsx | 148 ++++---------------- 5 files changed, 205 insertions(+), 123 deletions(-) create mode 100644 src/components/brand/ArtikelMarke.jsx create mode 100644 src/components/brand/ArtikelMarkenTable.jsx diff --git a/src/components/brand/ArtikelMarke.jsx b/src/components/brand/ArtikelMarke.jsx new file mode 100644 index 0000000..cf8a0f0 --- /dev/null +++ b/src/components/brand/ArtikelMarke.jsx @@ -0,0 +1,124 @@ +import { PureComponent } from 'react'; +import IconButton from '@mui/material/IconButton'; +import ListItem from '@mui/material/ListItem'; +import Tooltip from '@mui/material/Tooltip'; +import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined'; +import CopyrightIcon from '@mui/icons-material/Copyright'; +import ImageSearchIcon from '@mui/icons-material/ImageSearch'; +import SingleLineTextEdit from 'dashgui/SingleLineTextEdit'; +import BrandThumb from './BrandThumb'; + +class ArtikelMarke extends PureComponent { + constructor(props) { + super(props); + this.state = { + draft: props.brand?.name ?? '', + }; + } + + componentDidUpdate(prevProps) { + if (prevProps.brand?.name !== this.props.brand?.name) { + this.setState({ draft: this.props.brand?.name ?? '' }); + } else if (prevProps.disabled && !this.props.disabled) { + if (this.state.draft !== this.props.brand?.name) { + this.setState({ draft: this.props.brand?.name ?? '' }); + } + } + } + + handleChange = (next) => { + this.setState({ draft: next }); + this.props.onChange?.(next, this.props.brand); + }; + + handleBlur = () => { + const { onRename, brand } = this.props; + const draft = this.state.draft.trim(); + if (!draft) { + this.setState({ draft: brand?.name ?? '' }); + return; + } + if (draft !== brand?.name) { + onRename?.(brand, draft); + } + }; + + handleKeyDown = (event) => { + const { brand } = this.props; + if (event.key === 'Enter') { + event.preventDefault(); + event.target.blur(); + } + if (event.key === 'Escape') { + event.preventDefault(); + this.setState({ draft: brand?.name ?? '' }); + event.target.blur(); + } + }; + + handleSearchLogo = () => { + const { onSearchLogo, brand } = this.props; + onSearchLogo?.(brand); + }; + + handleDelete = () => { + const { onDelete, brand } = this.props; + onDelete?.(brand); + }; + + render() { + const { brand, disabled, sx } = this.props; + const { draft } = this.state; + const name = brand?.name ?? ''; + + return ( + + } + /> + + + + + + + + + + + + + + + + + ); + } +} + +export default ArtikelMarke; diff --git a/src/components/brand/ArtikelMarkenTable.jsx b/src/components/brand/ArtikelMarkenTable.jsx new file mode 100644 index 0000000..5821859 --- /dev/null +++ b/src/components/brand/ArtikelMarkenTable.jsx @@ -0,0 +1,44 @@ +import { PureComponent } from 'react'; +import List from '@mui/material/List'; +import Typography from '@mui/material/Typography'; +import ArtikelMarke from './ArtikelMarke'; + +class ArtikelMarkenTable extends PureComponent { + render() { + const { + brands = [], + busyKey, + disabled, + onRename, + onSearchLogo, + onDelete, + emptyMessage = 'Noch keine Marken angelegt.', + sx, + } = this.props; + + const isBusy = disabled || busyKey != null; + + return ( + + {brands.length === 0 ? ( + + {emptyMessage} + + ) : ( + brands.map((b) => ( + + )) + )} + + ); + } +} + +export default ArtikelMarkenTable; diff --git a/src/components/brand/index.js b/src/components/brand/index.js index b265eaa..23375fa 100644 --- a/src/components/brand/index.js +++ b/src/components/brand/index.js @@ -1,3 +1,5 @@ export { default as BrandThumb, extractBrandThumbInfo } from './BrandThumb'; export { default as BrandLabel } from './BrandLabel'; export { default as BrandImageSearchDialog } from './BrandImageSearchDialog'; +export { default as ArtikelMarke } from './ArtikelMarke'; +export { default as ArtikelMarkenTable } from './ArtikelMarkenTable'; diff --git a/src/components/tiles/ArticleTile.jsx b/src/components/tiles/ArticleTile.jsx index f887983..f2faf4f 100644 --- a/src/components/tiles/ArticleTile.jsx +++ b/src/components/tiles/ArticleTile.jsx @@ -22,7 +22,7 @@ import { SCRIM_LABEL, SCRIM_TILE, TEXT_GLOW_DARK, - VEIL_MUTED, + VEIL_SUBTLE, WHITE, } from '../../colors'; @@ -447,21 +447,19 @@ export class ArticleTile extends Tile { px: 1, py: '2px', my: 0.25, - bgcolor: VEIL_MUTED, - backdropFilter: 'blur(4px)', - WebkitBackdropFilter: 'blur(4px)', + bgcolor: VEIL_SUBTLE, + backdropFilter: 'blur(2px)', + WebkitBackdropFilter: 'blur(2px)', }} > [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), + brands: brandAutomat.state.brands || [], newName: '', busyKey: null, searchBrand: null, @@ -44,6 +34,9 @@ class ArtikelMarkenPage extends PureComponent { this.handleCreate = this.handleCreate.bind(this); this.handleRetry = this.handleRetry.bind(this); this.loadBrands = this.loadBrands.bind(this); + this.handleRename = this.handleRename.bind(this); + this.handleDelete = this.handleDelete.bind(this); + this.handleSearchLogo = this.handleSearchLogo.bind(this); } componentDidMount() { @@ -70,20 +63,10 @@ class ArtikelMarkenPage extends PureComponent { } 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, - }; + this.setState({ + status: 'ready', + error: null, + brands, }); } @@ -105,6 +88,10 @@ class ArtikelMarkenPage extends PureComponent { void this.loadBrands(); } + handleSearchLogo(brand) { + this.setState({ searchBrand: brand }); + } + async handleCreate() { const name = this.state.newName.trim(); if (!name || this.state.busyKey != null) return; @@ -122,29 +109,20 @@ class ArtikelMarkenPage extends PureComponent { } } - 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; + async handleRename(brand, draft) { + const nextName = String(draft ?? '').trim(); + if (!nextName || nextName === brand.name || this.state.busyKey != null) return; this.setState({ busyKey: brand.id }); try { - await brandAutomat.actions.upsert({ name: draft }); + await brandAutomat.actions.upsert({ name: nextName }); 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 }, - })); + this.setState({ busyKey: null }); } } @@ -166,7 +144,7 @@ class ArtikelMarkenPage extends PureComponent { } render() { - const { status, error, brands, drafts, newName, busyKey } = this.state; + const { status, error, brands, newName, busyKey } = this.state; const busy = busyKey != null; if (status === 'loading') { @@ -184,78 +162,13 @@ class ArtikelMarkenPage extends PureComponent { 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)} - > - - - - - - )) - )} - +