article and relayout
This commit is contained in:
341
src/pages/artikel/sku.jsx
Normal file
341
src/pages/artikel/sku.jsx
Normal file
@@ -0,0 +1,341 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link as RouterLink, useNavigate, useParams } from 'react-router-dom';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import Box from '@mui/material/Box';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import Link from '@mui/material/Link';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableHead from '@mui/material/TableHead';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { article } from '../../api';
|
||||
import { formatPrice, getBrutto } from '../../utils/price';
|
||||
import { formatStock } from './searchFormat';
|
||||
|
||||
function DetailRow({ label, children }) {
|
||||
if (children == null || children === '') return null;
|
||||
return (
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: '160px 1fr', gap: 1, py: 0.75 }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{label}
|
||||
</Typography>
|
||||
<Typography variant="body2" component="div">
|
||||
{children}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function Section({ title, children }) {
|
||||
if (!children) return null;
|
||||
return (
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 600, mb: 1 }}>
|
||||
{title}
|
||||
</Typography>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export function ArticleView({ article: a }) {
|
||||
const brutto =
|
||||
a.fVKNEtto != null ? getBrutto(a.fVKNEtto, a.kSteuerklasse) : null;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 1, mb: 2 }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 700, mr: 1 }}>
|
||||
{a.cName}
|
||||
</Typography>
|
||||
<Chip
|
||||
size="small"
|
||||
label={a.cAktiv === 'Y' ? 'Aktiv' : 'Inaktiv'}
|
||||
color={a.cAktiv === 'Y' ? 'success' : 'default'}
|
||||
variant="outlined"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Paper variant="outlined" sx={{ p: 2 }}>
|
||||
<DetailRow label="Artikelnr.">{a.cArtNr}</DetailRow>
|
||||
<DetailRow label="Barcode">{a.cBarcode}</DetailRow>
|
||||
<DetailRow label="Hersteller">{a.cHersteller}</DetailRow>
|
||||
<DetailRow label="VK Netto">
|
||||
{a.fVKNEtto != null ? formatPrice(a.fVKNEtto) : null}
|
||||
</DetailRow>
|
||||
<DetailRow label="VK Brutto">
|
||||
{brutto != null ? formatPrice(brutto) : null}
|
||||
</DetailRow>
|
||||
<DetailRow label="UVP">{a.fUVP != null ? formatPrice(a.fUVP) : null}</DetailRow>
|
||||
<DetailRow label="Bestand">{formatStock(a.fBestand)}</DetailRow>
|
||||
<DetailRow label="Garage">{a.garage != null ? formatStock(a.garage) : null}</DetailRow>
|
||||
<DetailRow label="Keller">{a.keller != null ? formatStock(a.keller) : null}</DetailRow>
|
||||
<DetailRow label="Versandklasse">{a.tVersandklassecName}</DetailRow>
|
||||
<DetailRow label="Gewicht">
|
||||
{a.fGewicht != null
|
||||
? `${new Intl.NumberFormat('de-DE').format(a.fGewicht)} kg`
|
||||
: null}
|
||||
</DetailRow>
|
||||
{a.vater?.cArtNr && (
|
||||
<DetailRow label="Vaterartikel">
|
||||
<Link component={RouterLink} to={`/artikel/${encodeURIComponent(a.vater.cArtNr)}`}>
|
||||
{a.vater.cArtNr}
|
||||
</Link>
|
||||
</DetailRow>
|
||||
)}
|
||||
{(a.cEigenschaftName || a.cEigenschaftWertName) && (
|
||||
<DetailRow label="Variante">
|
||||
{[a.cEigenschaftName, a.cEigenschaftWertName].filter(Boolean).join(': ')}
|
||||
</DetailRow>
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
{a.cKurzBeschreibung && (
|
||||
<Section title="Kurzbeschreibung">
|
||||
<Typography variant="body2">{a.cKurzBeschreibung}</Typography>
|
||||
</Section>
|
||||
)}
|
||||
|
||||
{a.cBeschreibung && (
|
||||
<Section title="Beschreibung">
|
||||
<Box
|
||||
sx={{ typography: 'body2', '& p': { mt: 0 } }}
|
||||
dangerouslySetInnerHTML={{ __html: a.cBeschreibung }}
|
||||
/>
|
||||
</Section>
|
||||
)}
|
||||
|
||||
{a.EANs?.length > 0 && (
|
||||
<Section title="EANs">
|
||||
<Typography variant="body2">
|
||||
{a.EANs.map((row) => row.cEAN).filter(Boolean).join(', ')}
|
||||
</Typography>
|
||||
</Section>
|
||||
)}
|
||||
|
||||
{a.KATs?.length > 0 && (
|
||||
<Section title="Kategorien">
|
||||
<Typography variant="body2">
|
||||
{a.KATs.map((row) => row.kKategorie).join(', ')}
|
||||
</Typography>
|
||||
</Section>
|
||||
)}
|
||||
|
||||
{a.staffel?.length > 0 && (
|
||||
<Section title="Staffelpreise">
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Menge</TableCell>
|
||||
<TableCell align="right">VK Brutto</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{a.staffel.map((row, i) => (
|
||||
<TableRow key={i}>
|
||||
<TableCell>{row.menge}</TableCell>
|
||||
<TableCell align="right">{row.fVKBrutto}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Section>
|
||||
)}
|
||||
|
||||
{a.lief?.length > 0 && (
|
||||
<Section title="Lieferanten">
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Firma</TableCell>
|
||||
<TableCell>Lief.-Artikelnr.</TableCell>
|
||||
<TableCell align="right">EK Netto</TableCell>
|
||||
<TableCell align="right">Lager</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{a.lief.map((row, i) => (
|
||||
<TableRow key={i}>
|
||||
<TableCell>
|
||||
{row.cFirma}
|
||||
{row.nStandard ? ' ★' : ''}
|
||||
</TableCell>
|
||||
<TableCell>{row.cLiefArtNr || '—'}</TableCell>
|
||||
<TableCell align="right">
|
||||
{row.fEKNetto != null ? formatPrice(row.fEKNetto) : '—'}
|
||||
</TableCell>
|
||||
<TableCell align="right">{formatStock(row.fLagerbestand)}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Section>
|
||||
)}
|
||||
|
||||
{a.merkmale?.length > 0 && (
|
||||
<Section title="Merkmale">
|
||||
<Paper variant="outlined" sx={{ p: 2 }}>
|
||||
{a.merkmale.map((row, i) => (
|
||||
<DetailRow key={i} label={row.cName}>
|
||||
{row.cWert}
|
||||
</DetailRow>
|
||||
))}
|
||||
</Paper>
|
||||
</Section>
|
||||
)}
|
||||
|
||||
{a.children?.length > 0 && (
|
||||
<Section title="Varianten">
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Artikelnr.</TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{a.children.map((row) => (
|
||||
<TableRow key={row.cArtNr || row.kArtikel} hover>
|
||||
<TableCell>
|
||||
{row.cArtNr ? (
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={`/artikel/${encodeURIComponent(row.cArtNr)}`}
|
||||
>
|
||||
{row.cArtNr}
|
||||
</Link>
|
||||
) : (
|
||||
'—'
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>{row.cName || '—'}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Section>
|
||||
)}
|
||||
|
||||
{a.liste?.length > 0 && (
|
||||
<Section title="Stückliste">
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Artikelnr.</TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell align="right">Menge</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{a.liste.map((row, i) => (
|
||||
<TableRow key={i}>
|
||||
<TableCell>
|
||||
{row.cArtNr ? (
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={`/artikel/${encodeURIComponent(row.cArtNr)}`}
|
||||
>
|
||||
{row.cArtNr}
|
||||
</Link>
|
||||
) : (
|
||||
'—'
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>{row.cName || '—'}</TableCell>
|
||||
<TableCell align="right">{formatStock(row.fAnzahl ?? row.menge)}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Section>
|
||||
)}
|
||||
|
||||
{a.cAnmerkung && (
|
||||
<Section title="Interne Anmerkung">
|
||||
<Typography variant="body2" sx={{ whiteSpace: 'pre-wrap' }}>
|
||||
{a.cAnmerkung}
|
||||
</Typography>
|
||||
</Section>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ArtikelSkuPage() {
|
||||
const { sku } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const target = sku ? decodeURIComponent(sku) : '';
|
||||
const [data, setData] = useState(null);
|
||||
const [error, setError] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
function onKeyDown(e) {
|
||||
if (e.key !== 'Escape') return;
|
||||
e.preventDefault();
|
||||
navigate(-1);
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', onKeyDown);
|
||||
return () => document.removeEventListener('keydown', onKeyDown);
|
||||
}, [navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
async function load() {
|
||||
if (!target) {
|
||||
setError('Keine Artikelnr. angegeben');
|
||||
setData(null);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const res = await article.get({ target });
|
||||
if (!cancelled) {
|
||||
setData(res.article ?? null);
|
||||
if (!res.article) setError('Artikel nicht gefunden');
|
||||
}
|
||||
} catch (err) {
|
||||
if (!cancelled) {
|
||||
setData(null);
|
||||
setError(err.message || 'Artikel konnte nicht geladen werden');
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
load();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [target]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 6 }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Alert severity="error" sx={{ maxWidth: 640 }}>
|
||||
{error}
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
return <ArticleView article={data} />;
|
||||
}
|
||||
Reference in New Issue
Block a user