Files
dashfront/src/pages/artikel/sku.jsx
2026-07-19 19:39:07 +02:00

342 lines
10 KiB
JavaScript

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} />;
}