diff --git a/src/components/brand/ArtikelMarke.jsx b/src/components/brand/ArtikelMarke.jsx index 88dfde9..77eed0c 100644 --- a/src/components/brand/ArtikelMarke.jsx +++ b/src/components/brand/ArtikelMarke.jsx @@ -1,4 +1,5 @@ import { PureComponent } from 'react'; +import Box from '@mui/material/Box'; import IconButton from '@mui/material/IconButton'; import ListItem from '@mui/material/ListItem'; import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined'; @@ -9,6 +10,7 @@ import brandAutomat from '../../automats/brandAutomat'; import { notifyError } from '../../utils/notify'; import BrandThumb from './BrandThumb'; import BrandImageSearchDialog from './BrandImageSearchDialog'; +import BrandLogoPreviewDialog from './BrandLogoPreviewDialog'; class ArtikelMarke extends PureComponent { constructor(props) { @@ -16,6 +18,7 @@ class ArtikelMarke extends PureComponent { this.state = { draft: props.brand?.name ?? '', logoOpen: false, + previewOpen: false, }; } @@ -29,6 +32,8 @@ class ArtikelMarke extends PureComponent { } } + hasLogo = () => Boolean(this.props.brand?.pics?.[0]?.fullHash || this.props.brand?.pics?.[0]?.thumbHash); + handleChange = (next) => { this.setState({ draft: next }); this.props.onChange?.(next, this.props.brand); @@ -70,13 +75,22 @@ class ArtikelMarke extends PureComponent { }; handleSearchLogo = () => { - this.setState({ logoOpen: true }); + this.setState({ logoOpen: true, previewOpen: false }); }; handleLogoClose = () => { this.setState({ logoOpen: false }); }; + handleThumbClick = () => { + if (this.props.disabled || !this.hasLogo()) return; + this.setState({ previewOpen: true }); + }; + + handlePreviewClose = () => { + this.setState({ previewOpen: false }); + }; + handleDelete = () => { const { brand } = this.props; if (!window.confirm(`Marke „${brand.name}" wirklich löschen?`)) return; @@ -92,8 +106,9 @@ class ArtikelMarke extends PureComponent { render() { const { brand, disabled, sx } = this.props; - const { draft, logoOpen } = this.state; + const { draft, logoOpen, previewOpen } = this.state; const name = brand?.name ?? ''; + const hasLogo = this.hasLogo(); return ( <> @@ -101,20 +116,30 @@ class ArtikelMarke extends PureComponent { disableGutters sx={{ gap: 1, py: 0.75, alignItems: 'center', width: '100%', minWidth: 0, ...sx }} > - - - - } - /> + {hasLogo ? ( + + + + ) : ( + + + + )} ) : null} + {previewOpen ? ( + + ) : null} ); } diff --git a/src/components/brand/BrandLogoPreviewDialog.jsx b/src/components/brand/BrandLogoPreviewDialog.jsx new file mode 100644 index 0000000..2b4775f --- /dev/null +++ b/src/components/brand/BrandLogoPreviewDialog.jsx @@ -0,0 +1,176 @@ +import { PureComponent } from 'react'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import AppDialog from '../AppDialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogTitle from '@mui/material/DialogTitle'; +import Typography from '@mui/material/Typography'; +import CloseIcon from '@mui/icons-material/Close'; +import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined'; +import ImageSearchIcon from '@mui/icons-material/ImageSearch'; +import { enqueueSnackbar } from 'notistack'; +import { thumb } from '../../api'; +import brandAutomat from '../../automats/brandAutomat'; +import { notifyError } from '../../utils/notify'; +import ThumbHashImage from '../article/ThumbHashImage'; +import { + CHECKERBOARD, + SHADOW_THUMB_RING, + WHITE, +} from '../../colors'; + +/** Max edge (px) of the presized logo preview inside the dialog. */ +const MAX_PREVIEW = 240; + +/** + * Small fixed-size brand logo preview. + * Shows the stored thumb presized with thumbW/thumbH and offers + * Löschen / Neues Suchen / Schließen. + * + * Props: open, brand, onClose, onSearchNew, disabled? + */ +export default class BrandLogoPreviewDialog extends PureComponent { + constructor(props) { + super(props); + this.state = { busy: false }; + } + + handleDelete = async () => { + const { brand } = this.props; + if (!brand?.id || this.state.busy) return; + if (!window.confirm(`Logo von „${brand.name ?? ''}" wirklich löschen?`)) return; + this.setState({ busy: true }); + try { + await brandAutomat.actions.deleteImage(brand.id); + enqueueSnackbar('Markenlogo gelöscht.', { variant: 'success' }); + this.props.onClose?.(); + } catch (err) { + notifyError(err); + } finally { + this.setState({ busy: false }); + } + }; + + render() { + const { open, brand, disabled, onClose, onSearchNew } = this.props; + const { busy } = this.state; + const pic = brand?.pics?.[0]; + const { fullHash, thumbHash, thumbW, thumbH } = pic ?? {}; + const name = brand?.name ?? ''; + + // Presize the preview with thumbW/thumbH, capped to MAX_PREVIEW. + const tw = Number(thumbW); + const th = Number(thumbH); + let w = MAX_PREVIEW; + let h = MAX_PREVIEW; + if (Number.isFinite(tw) && Number.isFinite(th) && tw > 0 && th > 0) { + const scale = Math.min(1, MAX_PREVIEW / Math.max(tw, th)); + w = Math.max(1, Math.round(tw * scale)); + h = Math.max(1, Math.round(th * scale)); + } + + return ( + + + Logo: {name} + + + + + + {thumbHash ? : null} + {fullHash ? ( + + ) : ( + + + Kein Logo hinterlegt. + + + )} + + + + + + + + + + + ); + } +} diff --git a/src/components/brand/index.js b/src/components/brand/index.js index 3739128..639071b 100644 --- a/src/components/brand/index.js +++ b/src/components/brand/index.js @@ -1,6 +1,7 @@ export { default as BrandThumb } from './BrandThumb'; export { default as BrandLabel } from './BrandLabel'; export { default as BrandImageSearchDialog } from './BrandImageSearchDialog'; +export { default as BrandLogoPreviewDialog } from './BrandLogoPreviewDialog'; export { default as ArtikelMarke } from './ArtikelMarke'; export { default as ArtikelMarkeNew } from './ArtikelMarkeNew'; export { default as ArtikelMarkenTable } from './ArtikelMarkenTable';