u
This commit is contained in:
@@ -31,9 +31,10 @@ import {
|
||||
class ManifestFormLine extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const sku = props.supplierArticle?.article?.sku;
|
||||
const sku = this.getArticleSku(props);
|
||||
this.state = {
|
||||
cachedArticle: sku ? getArticle(sku) : null,
|
||||
loadingArticle: false,
|
||||
};
|
||||
this.handleRemove = this.handleRemove.bind(this);
|
||||
this.handleBlur = this.handleBlur.bind(this);
|
||||
@@ -42,13 +43,20 @@ class ManifestFormLine extends PureComponent {
|
||||
this.handleRemoveSupplierArticle = this.handleRemoveSupplierArticle.bind(this);
|
||||
}
|
||||
|
||||
getArticleSku(props = this.props) {
|
||||
if (props.kind === 'delivery') {
|
||||
return props.supplierArticle?.article?.sku || null;
|
||||
}
|
||||
return props.line?.sku ? String(props.line.sku).trim() : null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribeArticle(this.props.supplierArticle?.article?.sku);
|
||||
this.subscribeArticle(this.getArticleSku());
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const prevSku = prevProps.supplierArticle?.article?.sku;
|
||||
const nextSku = this.props.supplierArticle?.article?.sku;
|
||||
const prevSku = this.getArticleSku(prevProps);
|
||||
const nextSku = this.getArticleSku(this.props);
|
||||
if (prevSku !== nextSku) {
|
||||
this.subscribeArticle(nextSku);
|
||||
}
|
||||
@@ -62,19 +70,35 @@ class ManifestFormLine extends PureComponent {
|
||||
this.unsubArticle?.();
|
||||
this.unsubArticle = null;
|
||||
if (!sku) {
|
||||
if (this.state.cachedArticle) {
|
||||
this.setState({ cachedArticle: null });
|
||||
if (this.state.cachedArticle || this.state.loadingArticle) {
|
||||
this.setState({ cachedArticle: null, loadingArticle: false });
|
||||
}
|
||||
return;
|
||||
}
|
||||
const current = getArticle(sku);
|
||||
this.setState({ cachedArticle: current });
|
||||
if (!current) {
|
||||
artikelAutomat.actions.loadArticle(sku).catch(() => { });
|
||||
if (current) {
|
||||
this.setState({ cachedArticle: current, loadingArticle: false });
|
||||
} else {
|
||||
this.setState({ cachedArticle: null, loadingArticle: true });
|
||||
artikelAutomat.actions.loadArticle(sku)
|
||||
.then((art) => {
|
||||
if (this.getArticleSku() === sku) {
|
||||
this.setState({ cachedArticle: art, loadingArticle: false });
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (this.getArticleSku() === sku) {
|
||||
this.setState({ cachedArticle: null, loadingArticle: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
this.unsubArticle = artikelAutomat.subscribe(this, (state) => ({
|
||||
cachedArticle: createArticleSelector(sku)(state),
|
||||
}));
|
||||
this.unsubArticle = artikelAutomat.subscribe(this, (state) => {
|
||||
const art = createArticleSelector(sku)(state);
|
||||
return {
|
||||
cachedArticle: art ?? null,
|
||||
...(art ? { loadingArticle: false } : {}),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
handleRemove() {
|
||||
@@ -251,8 +275,255 @@ class ManifestFormLine extends PureComponent {
|
||||
}
|
||||
|
||||
renderSecondLine() {
|
||||
const { kind = 'delivery' } = this.props;
|
||||
const rawSku = this.props.line?.sku != null ? String(this.props.line.sku).trim() : '';
|
||||
|
||||
if (kind === 'delivery') {
|
||||
return this.renderDeliverySecondLine(rawSku);
|
||||
}
|
||||
return this.renderArticleSecondLine(rawSku);
|
||||
}
|
||||
|
||||
renderArticleSecondLine(rawSku) {
|
||||
const { loading } = this.props;
|
||||
const { cachedArticle, loadingArticle } = this.state;
|
||||
|
||||
if (loading || loadingArticle) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, py: 0.25 }}>
|
||||
<CircularProgress size={14} thickness={5} sx={{ color: 'primary.main' }} />
|
||||
<Typography variant="caption" color="text.secondary" sx={{ fontStyle: 'italic', fontSize: '0.72rem' }}>
|
||||
Artikeldaten werden geladen…
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (cachedArticle) {
|
||||
const photo = cachedArticle.photos?.[0] || cachedArticle.articlepic?.[0];
|
||||
const fullHash = photo?.hash || photo?.fullHash;
|
||||
const thumbHash = photo?.thumbhash || photo?.thumbHash;
|
||||
const thumbW = photo?.thumbw || photo?.thumbW;
|
||||
const thumbH = photo?.thumbh || photo?.thumbH;
|
||||
const hasPhoto = fullHash != null || thumbHash != null;
|
||||
|
||||
const eans = cachedArticle.eans ?? cachedArticle.articleean ?? [];
|
||||
const primaryEan = Array.isArray(eans) ? eans[0] : eans;
|
||||
|
||||
const vkPrice = primaryPrice(cachedArticle);
|
||||
const vkNetto = vkPrice?.net;
|
||||
const vkBrutto = vkPrice
|
||||
? priceBrutto(vkPrice) ??
|
||||
(vkPrice.net != null && vkPrice.taxrate != null
|
||||
? vkPrice.net * (1 + vkPrice.taxrate / 100)
|
||||
: null)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1.25,
|
||||
py: 0.25,
|
||||
flexWrap: 'wrap',
|
||||
}}
|
||||
>
|
||||
{/* Article Photo Thumbnail */}
|
||||
<Box
|
||||
sx={{
|
||||
width: 28,
|
||||
height: 28,
|
||||
minWidth: 28,
|
||||
borderRadius: 0.75,
|
||||
overflow: 'hidden',
|
||||
bgcolor: BORDER,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
border: '1px solid',
|
||||
borderColor: 'divider',
|
||||
}}
|
||||
>
|
||||
{hasPhoto ? (
|
||||
<ArticlePhoto
|
||||
photo={{ fullHash, thumbHash, thumbW, thumbH }}
|
||||
size={28}
|
||||
/>
|
||||
) : (
|
||||
<Inventory2OutlinedIcon sx={{ fontSize: 16, color: 'text.secondary' }} />
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Internal Article SKU */}
|
||||
{cachedArticle.sku && (
|
||||
<Chip
|
||||
component={RouterLink}
|
||||
to={artikelDetailPath(cachedArticle)}
|
||||
clickable
|
||||
size="small"
|
||||
label={`Art.-Nr.: ${cachedArticle.sku}`}
|
||||
sx={{
|
||||
height: 20,
|
||||
fontSize: '0.7rem',
|
||||
fontWeight: 600,
|
||||
bgcolor: 'primary.50',
|
||||
color: 'primary.dark',
|
||||
border: '1px solid',
|
||||
borderColor: 'primary.200',
|
||||
cursor: 'pointer',
|
||||
textDecoration: 'none',
|
||||
'&:hover': {
|
||||
bgcolor: 'primary.100',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Brand */}
|
||||
{cachedArticle.brand?.name && (
|
||||
<BrandLabel
|
||||
brand={cachedArticle.brand}
|
||||
size={16}
|
||||
typographyProps={{
|
||||
variant: 'caption',
|
||||
color: 'text.secondary',
|
||||
fontWeight: 500,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Article Name */}
|
||||
{cachedArticle.name && (
|
||||
<Typography
|
||||
variant="caption"
|
||||
fontWeight={600}
|
||||
noWrap
|
||||
sx={{ color: 'text.primary', maxWidth: { xs: 200, sm: 300, md: 400 } }}
|
||||
title={cachedArticle.name}
|
||||
>
|
||||
{cachedArticle.name}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{/* Primary EAN */}
|
||||
{primaryEan && (
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="text.secondary"
|
||||
sx={{
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '0.68rem',
|
||||
bgcolor: 'action.hover',
|
||||
px: 0.5,
|
||||
py: 0.1,
|
||||
borderRadius: 0.5,
|
||||
}}
|
||||
>
|
||||
EAN: {primaryEan}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{/* VK Netto & VK Brutto */}
|
||||
{(vkNetto != null || vkBrutto != null) && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1.5,
|
||||
ml: 'auto',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{vkNetto != null && (
|
||||
<Typography
|
||||
variant="caption"
|
||||
fontWeight={600}
|
||||
sx={{ color: 'text.secondary', fontSize: '0.72rem' }}
|
||||
>
|
||||
VK Netto: {formatPrice(vkNetto)}
|
||||
</Typography>
|
||||
)}
|
||||
{vkBrutto != null && (
|
||||
<Typography
|
||||
variant="caption"
|
||||
fontWeight={600}
|
||||
sx={{ color: 'primary.dark', fontSize: '0.72rem' }}
|
||||
>
|
||||
VK Brutto: {formatPrice(vkBrutto)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (rawSku) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, py: 0.25 }}>
|
||||
{this.props.onSearchArticle && (
|
||||
<Button
|
||||
size="small"
|
||||
variant="text"
|
||||
startIcon={<SearchIcon sx={{ fontSize: 14 }} />}
|
||||
onClick={this.handleSearchArticle}
|
||||
sx={{
|
||||
fontSize: '0.7rem',
|
||||
fontWeight: 600,
|
||||
textTransform: 'none',
|
||||
minWidth: 'auto',
|
||||
py: 0.25,
|
||||
px: 1,
|
||||
color: 'primary.main',
|
||||
'&:hover': {
|
||||
bgcolor: 'primary.50',
|
||||
},
|
||||
}}
|
||||
>
|
||||
Artikel suchen
|
||||
</Button>
|
||||
)}
|
||||
<HelpOutlineIcon sx={{ fontSize: 15, color: 'warning.main' }} />
|
||||
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.72rem' }}>
|
||||
Kein Artikel für SKU „{rawSku}“ gefunden.
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.onSearchArticle) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, py: 0.25 }}>
|
||||
<Button
|
||||
size="small"
|
||||
variant="text"
|
||||
startIcon={<SearchIcon sx={{ fontSize: 14 }} />}
|
||||
onClick={this.handleSearchArticle}
|
||||
sx={{
|
||||
fontSize: '0.7rem',
|
||||
fontWeight: 600,
|
||||
textTransform: 'none',
|
||||
minWidth: 'auto',
|
||||
py: 0.25,
|
||||
px: 1,
|
||||
color: 'primary.main',
|
||||
'&:hover': {
|
||||
bgcolor: 'primary.50',
|
||||
},
|
||||
}}
|
||||
>
|
||||
Artikel suchen
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
renderDeliverySecondLine(rawSku) {
|
||||
const { line, supplierArticle, loading } = this.props;
|
||||
const rawSku = line?.sku != null ? String(line.sku).trim() : '';
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user