u
This commit is contained in:
@@ -34,6 +34,9 @@ import { artikelDetailPath } from '../articleRoute';
|
|||||||
import { article as articleApi } from '../../../api';
|
import { article as articleApi } from '../../../api';
|
||||||
import attributeAutomat, { selectAttributes } from '../../../automats/attributeAutomat';
|
import attributeAutomat, { selectAttributes } from '../../../automats/attributeAutomat';
|
||||||
import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat';
|
import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat';
|
||||||
|
import companyAutomat from '../../../automats/companyAutomat';
|
||||||
|
import { formatStock } from '../searchFormat';
|
||||||
|
import { formatPrice, getArticleGross } from '../../../utils/price';
|
||||||
import { notifyError } from '../../../utils/notify';
|
import { notifyError } from '../../../utils/notify';
|
||||||
import articleIndexAutomat from '../../../automats/articleIndexAutomat';
|
import articleIndexAutomat from '../../../automats/articleIndexAutomat';
|
||||||
import {
|
import {
|
||||||
@@ -43,6 +46,67 @@ import {
|
|||||||
alpha,
|
alpha,
|
||||||
} from '../../../colors';
|
} from '../../../colors';
|
||||||
|
|
||||||
|
function getVariantDetails(child, allArticles, defaultTaxRate) {
|
||||||
|
const cached = child?.sku ? getArticle(child.sku) : null;
|
||||||
|
const fromIndex = !cached && child?.sku
|
||||||
|
? (allArticles || []).find((a) => a.sku === child.sku)
|
||||||
|
: null;
|
||||||
|
const merged = cached || fromIndex || child || {};
|
||||||
|
|
||||||
|
// 1. Gross price
|
||||||
|
const gross = getArticleGross(merged, defaultTaxRate);
|
||||||
|
const priceDisplay = gross != null && Number.isFinite(gross) ? formatPrice(gross) : null;
|
||||||
|
|
||||||
|
// 2. Stock & Unit
|
||||||
|
let stockVal = null;
|
||||||
|
if (Array.isArray(merged.warehouses) && merged.warehouses.length > 0) {
|
||||||
|
stockVal = merged.warehouses.reduce((sum, wh) => sum + (Number(wh.stock) || 0), 0);
|
||||||
|
} else if (merged.stock != null && !Number.isNaN(Number(merged.stock))) {
|
||||||
|
stockVal = Number(merged.stock);
|
||||||
|
} else if (child?.stock != null && !Number.isNaN(Number(child.stock))) {
|
||||||
|
stockVal = Number(child.stock);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawUnit =
|
||||||
|
merged.unit?.symbol ||
|
||||||
|
merged.unit?.name ||
|
||||||
|
child?.unit?.symbol ||
|
||||||
|
child?.unit?.name ||
|
||||||
|
'';
|
||||||
|
const unit =
|
||||||
|
rawUnit && !['stk', 'stück', 'pc', 'stk.'].includes(rawUnit.trim().toLowerCase())
|
||||||
|
? ` ${rawUnit}`
|
||||||
|
: '';
|
||||||
|
const stockDisplay = stockVal != null ? `${formatStock(stockVal)}${unit}` : null;
|
||||||
|
|
||||||
|
// 3. EAN(s)
|
||||||
|
const eanCandidates = [];
|
||||||
|
const rawEans = merged.eans ?? child?.eans;
|
||||||
|
if (Array.isArray(rawEans)) {
|
||||||
|
for (const item of rawEans) {
|
||||||
|
const val = typeof item === 'object' && item !== null ? item.ean : item;
|
||||||
|
if (val && String(val).trim()) eanCandidates.push(String(val).trim());
|
||||||
|
}
|
||||||
|
} else if (rawEans && typeof rawEans === 'string') {
|
||||||
|
eanCandidates.push(rawEans.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
const singleEan = merged.ean ?? child?.ean;
|
||||||
|
if (singleEan && typeof singleEan === 'string' && !eanCandidates.includes(singleEan.trim())) {
|
||||||
|
eanCandidates.push(singleEan.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
const primaryEan = eanCandidates[0] ?? null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
priceDisplay,
|
||||||
|
stockVal,
|
||||||
|
stockDisplay,
|
||||||
|
primaryEan,
|
||||||
|
allEans: eanCandidates,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default class ArticleViewLists extends PureComponent {
|
export default class ArticleViewLists extends PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -88,12 +152,16 @@ export default class ArticleViewLists extends PureComponent {
|
|||||||
// Confirmation dialogs
|
// Confirmation dialogs
|
||||||
removeTargetChild: null, // { sku, attributeValue }
|
removeTargetChild: null, // { sku, attributeValue }
|
||||||
confirmClearType: null, // 'stueckliste' | 'options' | null
|
confirmClearType: null, // 'stueckliste' | 'options' | null
|
||||||
|
company: companyAutomat.state.current,
|
||||||
};
|
};
|
||||||
this._cancelled = false;
|
this._cancelled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this._cancelled = false;
|
this._cancelled = false;
|
||||||
|
this.unsubCompany = companyAutomat.subscribe(this, (state) => ({
|
||||||
|
company: state.current,
|
||||||
|
}));
|
||||||
this.unsubAttr = attributeAutomat.subscribe((state) => {
|
this.unsubAttr = attributeAutomat.subscribe((state) => {
|
||||||
if (this._cancelled) return;
|
if (this._cancelled) return;
|
||||||
this.setState((prev) => {
|
this.setState((prev) => {
|
||||||
@@ -123,21 +191,48 @@ export default class ArticleViewLists extends PureComponent {
|
|||||||
void this.loadAssignedAttributes(this.props.article.sku);
|
void this.loadAssignedAttributes(this.props.article.sku);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.props.article?.isParent) {
|
||||||
|
this.ensureChildren(this.props.article.children);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.props.article?.parentSku) {
|
if (this.props.article?.parentSku) {
|
||||||
void artikelAutomat.actions.loadArticle(this.props.article.parentSku);
|
void artikelAutomat.actions.loadArticle(this.props.article.parentSku).then((parent) => {
|
||||||
|
if (!this._cancelled && parent?.children) {
|
||||||
|
this.ensureChildren(parent.children);
|
||||||
|
}
|
||||||
|
}).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void this.loadAllArticles();
|
void this.loadAllArticles();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
|
if (this.props.article?.isParent) {
|
||||||
|
if (
|
||||||
|
prevProps.article?.sku !== this.props.article?.sku ||
|
||||||
|
prevProps.article?.children !== this.props.article?.children
|
||||||
|
) {
|
||||||
|
this.ensureChildren(this.props.article.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.props.article?.parentSku) {
|
||||||
|
if (prevProps.article?.parentSku !== this.props.article?.parentSku) {
|
||||||
|
void artikelAutomat.actions.loadArticle(this.props.article.parentSku).then((parent) => {
|
||||||
|
if (!this._cancelled && parent?.children) {
|
||||||
|
this.ensureChildren(parent.children);
|
||||||
|
}
|
||||||
|
}).catch(() => {});
|
||||||
|
} else {
|
||||||
|
const parent = getArticle(this.props.article.parentSku);
|
||||||
|
if (parent?.children) {
|
||||||
|
this.ensureChildren(parent.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (prevProps.article?.sku !== this.props.article?.sku) {
|
if (prevProps.article?.sku !== this.props.article?.sku) {
|
||||||
if (this.props.article?.sku) {
|
if (this.props.article?.sku) {
|
||||||
void this.loadAssignedAttributes(this.props.article.sku);
|
void this.loadAssignedAttributes(this.props.article.sku);
|
||||||
}
|
}
|
||||||
if (this.props.article?.parentSku) {
|
|
||||||
void artikelAutomat.actions.loadArticle(this.props.article.parentSku);
|
|
||||||
}
|
|
||||||
const nextArticle = this.props.article;
|
const nextArticle = this.props.article;
|
||||||
const hasStueckliste = Boolean(
|
const hasStueckliste = Boolean(
|
||||||
nextArticle?.articlelistId != null || nextArticle?.articlelist?.length > 0
|
nextArticle?.articlelistId != null || nextArticle?.articlelist?.length > 0
|
||||||
@@ -166,10 +261,20 @@ export default class ArticleViewLists extends PureComponent {
|
|||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this._cancelled = true;
|
this._cancelled = true;
|
||||||
|
this.unsubCompany?.();
|
||||||
this.unsubAttr?.();
|
this.unsubAttr?.();
|
||||||
this.unsubArtikel?.();
|
this.unsubArtikel?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ensureChildren(children) {
|
||||||
|
if (!Array.isArray(children)) return;
|
||||||
|
for (const child of children) {
|
||||||
|
if (child?.sku && !getArticle(child.sku)) {
|
||||||
|
void artikelAutomat.actions.loadArticle(child.sku).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getApi() {
|
getApi() {
|
||||||
return articleApi;
|
return articleApi;
|
||||||
}
|
}
|
||||||
@@ -1073,6 +1178,8 @@ export default class ArticleViewLists extends PureComponent {
|
|||||||
|
|
||||||
renderChildView(parentSku, parentArticle, attributes, assignedAttributes) {
|
renderChildView(parentSku, parentArticle, attributes, assignedAttributes) {
|
||||||
const { article: articleData } = this.props;
|
const { article: articleData } = this.props;
|
||||||
|
const { allArticles } = this.state;
|
||||||
|
const defaultTaxRate = this.state.company?.taxrate ?? companyAutomat.state.current?.taxrate ?? 19;
|
||||||
const parentAttrName =
|
const parentAttrName =
|
||||||
parentArticle?.parentAttributeName ||
|
parentArticle?.parentAttributeName ||
|
||||||
(parentArticle?.parentAttributeNameId
|
(parentArticle?.parentAttributeNameId
|
||||||
@@ -1235,6 +1342,13 @@ export default class ArticleViewLists extends PureComponent {
|
|||||||
const isSelf =
|
const isSelf =
|
||||||
sibling.sku === articleData.sku ||
|
sibling.sku === articleData.sku ||
|
||||||
(articleData.id != null && sibling.id === articleData.id);
|
(articleData.id != null && sibling.id === articleData.id);
|
||||||
|
const {
|
||||||
|
priceDisplay,
|
||||||
|
stockVal,
|
||||||
|
stockDisplay,
|
||||||
|
primaryEan,
|
||||||
|
allEans,
|
||||||
|
} = getVariantDetails(sibling, allArticles, defaultTaxRate);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -1268,25 +1382,49 @@ export default class ArticleViewLists extends PureComponent {
|
|||||||
py: 0.25,
|
py: 0.25,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
<Box
|
||||||
variant="body2"
|
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: 600,
|
display: 'flex',
|
||||||
fontSize: '0.86rem',
|
alignItems: 'baseline',
|
||||||
lineHeight: 1.3,
|
justifyContent: 'space-between',
|
||||||
overflow: 'hidden',
|
gap: 1,
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
color: 'text.primary',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{sibling.attributeValue || variationValue || sibling.name || sibling.sku}
|
<Typography
|
||||||
</Typography>
|
variant="body2"
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.4, mt: 0.2 }}>
|
sx={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '0.86rem',
|
||||||
|
lineHeight: 1.3,
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
color: 'text.primary',
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{sibling.attributeValue || variationValue || sibling.name || sibling.sku}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 700,
|
||||||
|
fontSize: '0.86rem',
|
||||||
|
lineHeight: 1.3,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
color: priceDisplay ? 'text.primary' : 'text.disabled',
|
||||||
|
fontVariantNumeric: 'tabular-nums',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{priceDisplay || '—'}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 0.6, mt: 0.3 }}>
|
||||||
<Typography
|
<Typography
|
||||||
variant="caption"
|
variant="caption"
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: '0.78rem',
|
fontSize: '0.76rem',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
color: 'text.secondary',
|
color: 'text.secondary',
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
@@ -1295,6 +1433,44 @@ export default class ArticleViewLists extends PureComponent {
|
|||||||
>
|
>
|
||||||
{sibling.sku}
|
{sibling.sku}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
|
<Typography variant="caption" sx={{ color: 'text.disabled', fontSize: '0.72rem' }}>
|
||||||
|
·
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Tooltip
|
||||||
|
title={allEans.length > 1 ? `EANs: ${allEans.join(', ')}` : ''}
|
||||||
|
disableHoverListener={allEans.length <= 1}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
sx={{
|
||||||
|
fontSize: '0.74rem',
|
||||||
|
color: primaryEan ? 'text.secondary' : 'text.disabled',
|
||||||
|
fontFamily: primaryEan ? 'monospace' : 'inherit',
|
||||||
|
letterSpacing: primaryEan ? '0.01em' : 'normal',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{primaryEan ? `EAN ${primaryEan}${allEans.length > 1 ? ` (+${allEans.length - 1})` : ''}` : 'EAN —'}
|
||||||
|
</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Typography variant="caption" sx={{ color: 'text.disabled', fontSize: '0.72rem' }}>
|
||||||
|
·
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
sx={{
|
||||||
|
fontSize: '0.74rem',
|
||||||
|
fontWeight: 600,
|
||||||
|
color: stockVal == null ? 'text.disabled' : stockVal > 0 ? 'success.dark' : 'error.main',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Bestand {stockDisplay ?? '—'}
|
||||||
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
@@ -1319,37 +1495,101 @@ export default class ArticleViewLists extends PureComponent {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
<Box
|
||||||
className="child-name"
|
|
||||||
variant="body2"
|
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: 600,
|
display: 'flex',
|
||||||
fontSize: '0.86rem',
|
alignItems: 'baseline',
|
||||||
lineHeight: 1.3,
|
justifyContent: 'space-between',
|
||||||
overflow: 'hidden',
|
gap: 1,
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
color: 'text.primary',
|
|
||||||
transition: 'color 0.15s ease',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{sibling.attributeValue || sibling.name || sibling.sku}
|
|
||||||
</Typography>
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.4, mt: 0.2 }}>
|
|
||||||
<Typography
|
<Typography
|
||||||
className="child-sku"
|
className="child-name"
|
||||||
variant="caption"
|
variant="body2"
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: '0.78rem',
|
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
color: 'primary.main',
|
fontSize: '0.86rem',
|
||||||
fontFamily: 'monospace',
|
lineHeight: 1.3,
|
||||||
letterSpacing: '0.02em',
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
color: 'text.primary',
|
||||||
|
transition: 'color 0.15s ease',
|
||||||
|
flex: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{sibling.sku}
|
{sibling.attributeValue || sibling.name || sibling.sku}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 700,
|
||||||
|
fontSize: '0.86rem',
|
||||||
|
lineHeight: 1.3,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
color: priceDisplay ? 'text.primary' : 'text.disabled',
|
||||||
|
fontVariantNumeric: 'tabular-nums',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{priceDisplay || '—'}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 0.6, mt: 0.3 }}>
|
||||||
|
<Box sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.35 }}>
|
||||||
|
<Typography
|
||||||
|
className="child-sku"
|
||||||
|
variant="caption"
|
||||||
|
sx={{
|
||||||
|
fontSize: '0.76rem',
|
||||||
|
fontWeight: 600,
|
||||||
|
color: 'primary.main',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
letterSpacing: '0.02em',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{sibling.sku}
|
||||||
|
</Typography>
|
||||||
|
<OpenInNewIcon sx={{ fontSize: 12, color: 'primary.main', opacity: 0.8 }} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Typography variant="caption" sx={{ color: 'text.disabled', fontSize: '0.72rem' }}>
|
||||||
|
·
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Tooltip
|
||||||
|
title={allEans.length > 1 ? `EANs: ${allEans.join(', ')}` : ''}
|
||||||
|
disableHoverListener={allEans.length <= 1}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
sx={{
|
||||||
|
fontSize: '0.74rem',
|
||||||
|
color: primaryEan ? 'text.secondary' : 'text.disabled',
|
||||||
|
fontFamily: primaryEan ? 'monospace' : 'inherit',
|
||||||
|
letterSpacing: primaryEan ? '0.01em' : 'normal',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{primaryEan ? `EAN ${primaryEan}${allEans.length > 1 ? ` (+${allEans.length - 1})` : ''}` : 'EAN —'}
|
||||||
|
</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Typography variant="caption" sx={{ color: 'text.disabled', fontSize: '0.72rem' }}>
|
||||||
|
·
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
sx={{
|
||||||
|
fontSize: '0.74rem',
|
||||||
|
fontWeight: 600,
|
||||||
|
color: stockVal == null ? 'text.disabled' : stockVal > 0 ? 'success.dark' : 'error.main',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Bestand {stockDisplay ?? '—'}
|
||||||
</Typography>
|
</Typography>
|
||||||
<OpenInNewIcon sx={{ fontSize: 13, color: 'primary.main', opacity: 0.8 }} />
|
|
||||||
</Box>
|
</Box>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
@@ -1645,100 +1885,181 @@ export default class ArticleViewLists extends PureComponent {
|
|||||||
</Typography>
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
||||||
{children.map((child) => (
|
{children.map((child) => {
|
||||||
<Box
|
const defaultTaxRate = this.state.company?.taxrate ?? companyAutomat.state.current?.taxrate ?? 19;
|
||||||
key={child.id || child.sku}
|
const {
|
||||||
sx={{
|
priceDisplay,
|
||||||
display: 'flex',
|
stockVal,
|
||||||
alignItems: 'center',
|
stockDisplay,
|
||||||
justifyContent: 'space-between',
|
primaryEan,
|
||||||
px: 1,
|
allEans,
|
||||||
py: 0.6,
|
} = getVariantDetails(child, allArticles, defaultTaxRate);
|
||||||
borderRadius: 1,
|
|
||||||
bgcolor: 'background.paper',
|
return (
|
||||||
border: '1px solid',
|
<Box
|
||||||
borderColor: 'divider',
|
key={child.id || child.sku}
|
||||||
transition: 'all 0.15s ease-in-out',
|
|
||||||
'&:hover': {
|
|
||||||
borderColor: 'primary.main',
|
|
||||||
boxShadow: SHADOW_CARD,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Link
|
|
||||||
component={RouterLink}
|
|
||||||
to={artikelDetailPath(child.sku)}
|
|
||||||
sx={{
|
sx={{
|
||||||
minWidth: 0,
|
|
||||||
flex: 1,
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
alignItems: 'center',
|
||||||
textDecoration: 'none',
|
justifyContent: 'space-between',
|
||||||
color: 'inherit',
|
px: 1,
|
||||||
py: 0.25,
|
py: 0.6,
|
||||||
mr: 1,
|
borderRadius: 1,
|
||||||
cursor: 'pointer',
|
bgcolor: 'background.paper',
|
||||||
'&:hover .child-name': {
|
border: '1px solid',
|
||||||
color: 'primary.main',
|
borderColor: 'divider',
|
||||||
},
|
transition: 'all 0.15s ease-in-out',
|
||||||
'&:hover .child-sku': {
|
'&:hover': {
|
||||||
color: 'primary.dark',
|
borderColor: 'primary.main',
|
||||||
textDecoration: 'underline',
|
boxShadow: SHADOW_CARD,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
<Link
|
||||||
className="child-name"
|
component={RouterLink}
|
||||||
variant="body2"
|
to={artikelDetailPath(child.sku)}
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: 600,
|
minWidth: 0,
|
||||||
fontSize: '0.86rem',
|
flex: 1,
|
||||||
lineHeight: 1.3,
|
display: 'flex',
|
||||||
overflow: 'hidden',
|
flexDirection: 'column',
|
||||||
textOverflow: 'ellipsis',
|
textDecoration: 'none',
|
||||||
whiteSpace: 'nowrap',
|
color: 'inherit',
|
||||||
color: 'text.primary',
|
py: 0.25,
|
||||||
transition: 'color 0.15s ease',
|
mr: 1,
|
||||||
|
cursor: 'pointer',
|
||||||
|
'&:hover .child-name': {
|
||||||
|
color: 'primary.main',
|
||||||
|
},
|
||||||
|
'&:hover .child-sku': {
|
||||||
|
color: 'primary.dark',
|
||||||
|
textDecoration: 'underline',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{child.attributeValue || child.name || child.sku}
|
<Box
|
||||||
</Typography>
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.4, mt: 0.2 }}>
|
|
||||||
<Typography
|
|
||||||
className="child-sku"
|
|
||||||
variant="caption"
|
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: '0.78rem',
|
display: 'flex',
|
||||||
fontWeight: 600,
|
alignItems: 'baseline',
|
||||||
color: 'primary.main',
|
justifyContent: 'space-between',
|
||||||
fontFamily: 'monospace',
|
gap: 1,
|
||||||
letterSpacing: '0.02em',
|
|
||||||
display: 'inline-flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 0.35,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{child.sku}
|
<Typography
|
||||||
</Typography>
|
className="child-name"
|
||||||
<OpenInNewIcon sx={{ fontSize: 13, color: 'primary.main', opacity: 0.8 }} />
|
variant="body2"
|
||||||
</Box>
|
sx={{
|
||||||
</Link>
|
fontWeight: 600,
|
||||||
|
fontSize: '0.86rem',
|
||||||
|
lineHeight: 1.3,
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
color: 'text.primary',
|
||||||
|
transition: 'color 0.15s ease',
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{child.attributeValue || child.name || child.sku}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 700,
|
||||||
|
fontSize: '0.86rem',
|
||||||
|
lineHeight: 1.3,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
color: priceDisplay ? 'text.primary' : 'text.disabled',
|
||||||
|
fontVariantNumeric: 'tabular-nums',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{priceDisplay || '—'}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<IconButton
|
<Box
|
||||||
size="small"
|
sx={{
|
||||||
disabled={busy}
|
display: 'flex',
|
||||||
onClick={() => this.handleOpenRemoveDialog(child)}
|
alignItems: 'center',
|
||||||
title="Variante entfernen"
|
flexWrap: 'wrap',
|
||||||
sx={{
|
gap: 0.6,
|
||||||
p: 0.35,
|
mt: 0.3,
|
||||||
color: 'text.secondary',
|
}}
|
||||||
'&:hover': { color: 'error.main' },
|
>
|
||||||
}}
|
<Box sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.35 }}>
|
||||||
>
|
<Typography
|
||||||
<DeleteOutlinedIcon sx={{ fontSize: 17 }} />
|
className="child-sku"
|
||||||
</IconButton>
|
variant="caption"
|
||||||
</Box>
|
sx={{
|
||||||
))}
|
fontSize: '0.76rem',
|
||||||
|
fontWeight: 600,
|
||||||
|
color: 'primary.main',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
letterSpacing: '0.02em',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{child.sku}
|
||||||
|
</Typography>
|
||||||
|
<OpenInNewIcon sx={{ fontSize: 12, color: 'primary.main', opacity: 0.8 }} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Typography variant="caption" sx={{ color: 'text.disabled', fontSize: '0.72rem' }}>
|
||||||
|
·
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Tooltip
|
||||||
|
title={allEans.length > 1 ? `EANs: ${allEans.join(', ')}` : ''}
|
||||||
|
disableHoverListener={allEans.length <= 1}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
sx={{
|
||||||
|
fontSize: '0.74rem',
|
||||||
|
color: primaryEan ? 'text.secondary' : 'text.disabled',
|
||||||
|
fontFamily: primaryEan ? 'monospace' : 'inherit',
|
||||||
|
letterSpacing: primaryEan ? '0.01em' : 'normal',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{primaryEan ? `EAN ${primaryEan}${allEans.length > 1 ? ` (+${allEans.length - 1})` : ''}` : 'EAN —'}
|
||||||
|
</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Typography variant="caption" sx={{ color: 'text.disabled', fontSize: '0.72rem' }}>
|
||||||
|
·
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
sx={{
|
||||||
|
fontSize: '0.74rem',
|
||||||
|
fontWeight: 600,
|
||||||
|
color: stockVal == null ? 'text.disabled' : stockVal > 0 ? 'success.dark' : 'error.main',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Bestand {stockDisplay ?? '—'}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
disabled={busy}
|
||||||
|
onClick={() => this.handleOpenRemoveDialog(child)}
|
||||||
|
title="Variante entfernen"
|
||||||
|
sx={{
|
||||||
|
p: 0.35,
|
||||||
|
color: 'text.secondary',
|
||||||
|
'&:hover': { color: 'error.main' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteOutlinedIcon sx={{ fontSize: 17 }} />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
Reference in New Issue
Block a user