Files
dashfront/src/components/article/boxes/ArticleViewEANs.jsx
2026-09-27 07:08:13 +02:00

185 lines
4.9 KiB
JavaScript

import { Component, PureComponent } from 'react';
import Autocomplete from '@mui/material/Autocomplete';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import { EditablePanelTile } from 'dashgui/panels';
import { StorageManagerContext } from '../../../api/storageManagerContext';
import { requireObject } from '../../../utils/apiShape';
import { articleEans } from '../../../utils/articleDetail';
import { notifyError } from '../../../utils/notify';
import JsBarcode from 'jsbarcode';
class PanelValue extends Component {
render() {
const { label, children } = this.props;
if (children == null || children === '') return null;
return (
<Box sx={{ mb: 1 }}>
{label ? (
<Typography variant="caption" color="text.secondary" display="block">
{label}
</Typography>
) : null}
<Typography variant="body2" component="div">
{children}
</Typography>
</Box>
);
}
}
function eansDraftValue(article) {
return { eans: articleEans(article) };
}
function eansDraftDirty(draft, saved) {
return JSON.stringify(draft.eans ?? []) !== JSON.stringify(saved.eans ?? []);
}
function eansDraftValid(draft) {
return (draft.eans ?? []).every(
(ean) => typeof ean === 'string' && ean.trim().length > 0,
);
}
class SkuBarcode extends PureComponent {
componentDidMount() {
this.renderBarcode();
}
componentDidUpdate() {
this.renderBarcode();
}
renderBarcode() {
if (!this._svg || !this.props.value) return;
try {
JsBarcode(this._svg, String(this.props.value).trim(), {
format: 'CODE128',
width: 1.5,
height: 38,
displayValue: false,
margin: 0,
background: 'transparent',
lineColor: '#1e293b',
});
} catch (e) {
console.warn('Barcode render error:', e);
}
}
render() {
const { value } = this.props;
if (!value) return null;
return (
<Box sx={{ my: 1, display: 'flex', alignItems: 'center' }}>
<svg
ref={(el) => {
this._svg = el;
}}
style={{ maxWidth: '100%', height: 'auto' }}
/>
</Box>
);
}
}
const SECOND_ROW_MIN_HEIGHT = 220;
class EansDisplay extends Component {
render() {
const { sku, eans } = this.props;
return (
<Box sx={{ width: '100%' }}>
<PanelValue label="SKU">{sku}</PanelValue>
<SkuBarcode value={sku} />
{(eans ?? []).map((ean) => (
<PanelValue key={ean} label="EAN">
{ean}
</PanelValue>
))}
</Box>
);
}
}
class EansEdit extends Component {
render() {
const { sku, draft, onChange, inputRef } = this.props;
return (
<Box sx={{ width: '100%' }}>
<PanelValue label="SKU">{sku}</PanelValue>
<SkuBarcode value={sku} />
<Box sx={{ mt: 2 }}>
<Autocomplete
fullWidth
multiple
freeSolo
options={[]}
value={draft.eans ?? []}
onChange={(_event, eans) => onChange({ eans })}
renderInput={(params) => (
<TextField
{...params}
inputRef={inputRef}
label="EANs (Enter zum Hinzufügen)"
size="small"
slotProps={{
...params.slotProps,
inputLabel: {
...params.slotProps?.inputLabel,
shrink: true,
},
}}
/>
)}
/>
</Box>
</Box>
);
}
}
export default class ArticleViewEANs extends PureComponent {
static contextType = StorageManagerContext;
handleSave = async (draft) => {
const { article: articleData, onArticleUpdated } = this.props;
const eans = (draft.eans ?? [])
.map((ean) => String(ean).trim())
.filter((ean) => ean.length > 0);
const res = await this.context.article.setEans(articleData.sku, { eans });
requireObject(res, 'PUT /api/article/:sku/eans');
requireObject(res.article, 'article');
onArticleUpdated?.(res.article);
};
render() {
const { article: articleData } = this.props;
const value = eansDraftValue(articleData);
return (
<EditablePanelTile
wide={1}
minHeight={SECOND_ROW_MIN_HEIGHT}
color="#e2e8f0"
contentAlignItems="flex-start"
value={value}
onSave={this.handleSave}
onError={notifyError}
isDirty={eansDraftDirty}
canSave={eansDraftValid}
display={<EansDisplay sku={articleData.sku} eans={value.eans} />}
edit={({ draft, onChange, inputRef }) => (
<EansEdit
sku={articleData.sku}
draft={draft}
onChange={onChange}
inputRef={inputRef}
/>
)}
/>
);
}
}