u
This commit is contained in:
209
src/components/article/ArticleView.jsx
Normal file
209
src/components/article/ArticleView.jsx
Normal file
@@ -0,0 +1,209 @@
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import Box from '@mui/material/Box';
|
||||
import Link from '@mui/material/Link';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { article } from '../../api';
|
||||
import { EditablePanelTile, PanelTile, PanelTileGrid } from '../panels';
|
||||
import { requireObject } from '../../utils/apiShape';
|
||||
import { formatPrice } from '../../utils/price';
|
||||
import { formatStock } from '../../pages/artikel/searchFormat';
|
||||
|
||||
function PanelValue({ label, children }) {
|
||||
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 ArticlePhoto() {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: 200,
|
||||
height: 200,
|
||||
maxWidth: '100%',
|
||||
bgcolor: 'action.hover',
|
||||
borderRadius: 1,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const headerTileSx = {
|
||||
p: 1.25,
|
||||
minHeight: 0,
|
||||
};
|
||||
|
||||
function CountLine({ label, count }) {
|
||||
if (count == null) return null;
|
||||
return (
|
||||
<Typography variant="body2">
|
||||
{label}: {count}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
function RelationLine({ label, active, children }) {
|
||||
return (
|
||||
<Typography variant="body2" sx={{ mb: 0.5 }}>
|
||||
{label}:{' '}
|
||||
{active ? (
|
||||
children ?? 'ja'
|
||||
) : (
|
||||
<Typography component="span" variant="body2" color="text.secondary">
|
||||
nein
|
||||
</Typography>
|
||||
)}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
function articleBrutto(article) {
|
||||
if (article.net == null || article.taxRate == null) return null;
|
||||
return article.net * (1 + article.taxRate / 100);
|
||||
}
|
||||
|
||||
function nameDraftDirty(draft, saved) {
|
||||
return draft.name.trim() !== String(saved.name ?? '').trim();
|
||||
}
|
||||
|
||||
function nameDraftValid(draft) {
|
||||
return draft.name.trim().length > 0;
|
||||
}
|
||||
|
||||
export default function ArticleView({ article: articleData, onArticleUpdated }) {
|
||||
const brutto = articleBrutto(articleData);
|
||||
|
||||
const handleSaveName = async (draft) => {
|
||||
const name = draft.name.trim();
|
||||
const res = await article.update(articleData.sku, { name });
|
||||
requireObject(res, 'PATCH /api/article/:sku');
|
||||
requireObject(res.article, 'article');
|
||||
onArticleUpdated?.(res.article);
|
||||
};
|
||||
|
||||
return (
|
||||
<PanelTileGrid>
|
||||
<EditablePanelTile
|
||||
wide={3}
|
||||
minHeight={0}
|
||||
sx={headerTileSx}
|
||||
value={{ name: articleData.name ?? '' }}
|
||||
onSave={handleSaveName}
|
||||
isDirty={nameDraftDirty}
|
||||
canSave={nameDraftValid}
|
||||
renderDisplay={({ name }) => (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
minHeight: { xs: 0, sm: 200 },
|
||||
pr: 4,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" sx={{ fontWeight: 700, lineHeight: 1.25 }}>
|
||||
{name}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
renderEdit={(draft, onChange, inputRef) => (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
minHeight: { xs: 0, sm: 200 },
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
inputRef={inputRef}
|
||||
fullWidth
|
||||
variant="standard"
|
||||
value={draft.name}
|
||||
onChange={(event) => onChange({ name: event.target.value })}
|
||||
sx={{
|
||||
'& .MuiInput-input': {
|
||||
fontSize: '1.25rem',
|
||||
fontWeight: 700,
|
||||
lineHeight: 1.25,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
/>
|
||||
|
||||
<PanelTile wide={1} minHeight={0} sx={headerTileSx}>
|
||||
<ArticlePhoto />
|
||||
</PanelTile>
|
||||
|
||||
<PanelTile wide={1} title="Bestand">
|
||||
<PanelValue>
|
||||
{articleData.stock != null ? formatStock(articleData.stock) : null}
|
||||
</PanelValue>
|
||||
</PanelTile>
|
||||
|
||||
<PanelTile wide={1} title="Marke">
|
||||
<PanelValue>{articleData.brand}</PanelValue>
|
||||
</PanelTile>
|
||||
|
||||
<PanelTile wide={1} title="SKU / EAN">
|
||||
<PanelValue label="SKU">{articleData.sku}</PanelValue>
|
||||
<PanelValue label="EAN">{articleData.ean}</PanelValue>
|
||||
</PanelTile>
|
||||
|
||||
<PanelTile wide={1} title="Preis">
|
||||
<PanelValue label="Netto">
|
||||
{articleData.net != null ? formatPrice(articleData.net) : null}
|
||||
</PanelValue>
|
||||
<PanelValue label="MwSt.">
|
||||
{articleData.taxRate != null ? `${articleData.taxRate} %` : null}
|
||||
</PanelValue>
|
||||
<PanelValue label="Brutto">
|
||||
{brutto != null ? formatPrice(brutto) : null}
|
||||
</PanelValue>
|
||||
</PanelTile>
|
||||
|
||||
<PanelTile wide={3} title="SEO & Beschreibung" minHeight={200}>
|
||||
<PanelValue label="Slug">{articleData.slug}</PanelValue>
|
||||
<PanelValue label="SEO-Titel">{articleData.seoTitle}</PanelValue>
|
||||
<PanelValue label="SEO-Beschreibung">{articleData.seoDescription}</PanelValue>
|
||||
<PanelValue label="Beschreibung">{articleData.description}</PanelValue>
|
||||
</PanelTile>
|
||||
|
||||
<PanelTile wide={1} title="Listen" minHeight={200}>
|
||||
<RelationLine
|
||||
label="Vaterartikel"
|
||||
active={articleData.childCount > 0}
|
||||
>
|
||||
{articleData.childCount}
|
||||
</RelationLine>
|
||||
<RelationLine
|
||||
label="Kindartikel"
|
||||
active={articleData.fatherArticleId != null}
|
||||
>
|
||||
{articleData.fatherSku ? (
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={`/artikel/${encodeURIComponent(articleData.fatherSku)}`}
|
||||
>
|
||||
{articleData.fatherSku}
|
||||
</Link>
|
||||
) : null}
|
||||
</RelationLine>
|
||||
<CountLine label="Optionen" count={articleData.optionsCount} />
|
||||
<CountLine label="Stücklisten" count={articleData.articleListCount} />
|
||||
<CountLine label="Varianten" count={articleData.childCount} />
|
||||
</PanelTile>
|
||||
</PanelTileGrid>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user