566 lines
20 KiB
JavaScript
566 lines
20 KiB
JavaScript
const React = require('react');
|
|
const {
|
|
Container,
|
|
Typography,
|
|
Card,
|
|
CardMedia,
|
|
Grid,
|
|
Box,
|
|
Chip,
|
|
Stack,
|
|
AppBar,
|
|
Toolbar,
|
|
Button
|
|
} = require('@mui/material');
|
|
const Footer = require('./components/Footer.js').default;
|
|
const { Logo } = require('./components/header/index.js');
|
|
const ProductImage = require('./components/ProductImage.js').default;
|
|
|
|
// Utility function to clean product names by removing trailing number in parentheses
|
|
const cleanProductName = (name) => {
|
|
if (!name) return "";
|
|
// Remove patterns like " (1)", " (3)", " (10)" at the end of the string
|
|
return name.replace(/\s*\(\d+\)\s*$/, "").trim();
|
|
};
|
|
|
|
class PrerenderProduct extends React.Component {
|
|
render() {
|
|
const { productData } = this.props;
|
|
|
|
if (!productData) {
|
|
return React.createElement(
|
|
Box,
|
|
{ sx: { p: 4, textAlign: "center" } },
|
|
React.createElement(
|
|
Typography,
|
|
{ variant: 'h5', gutterBottom: true },
|
|
'Produkt nicht gefunden'
|
|
),
|
|
React.createElement(
|
|
Typography,
|
|
null,
|
|
'Das gesuchte Produkt existiert nicht oder wurde entfernt.'
|
|
)
|
|
);
|
|
}
|
|
|
|
const product = productData.product;
|
|
const attributes = productData.attributes || [];
|
|
const mainImage = product.pictureList && product.pictureList.trim()
|
|
? `/assets/images/prod${product.pictureList.split(',')[0].trim()}.jpg`
|
|
: '/assets/images/nopicture.jpg';
|
|
|
|
// Format price with tax
|
|
const priceWithTax = new Intl.NumberFormat("de-DE", {
|
|
style: "currency",
|
|
currency: "EUR",
|
|
}).format(product.price);
|
|
|
|
return React.createElement(
|
|
Box,
|
|
{
|
|
sx: {
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
minHeight: '100vh',
|
|
mb: 0,
|
|
pb: 0,
|
|
bgcolor: 'background.default'
|
|
}
|
|
},
|
|
React.createElement(
|
|
AppBar,
|
|
{ position: 'sticky', color: 'primary', elevation: 0, sx: { zIndex: 1100 } },
|
|
React.createElement(
|
|
Toolbar,
|
|
{ sx: { minHeight: 64, py: { xs: 0.5, sm: 0 } } },
|
|
React.createElement(
|
|
Container,
|
|
{ maxWidth: 'lg', sx: { display: 'flex', alignItems: 'center', px: { xs: 0, sm: 3 } } },
|
|
// Desktop: simple layout, Mobile: column layout with SearchBar space
|
|
React.createElement(
|
|
Box,
|
|
{
|
|
sx: {
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
width: '100%',
|
|
flexDirection: { xs: 'column', sm: 'row' }
|
|
}
|
|
},
|
|
// First row: Logo and invisible placeholders to match SPA layout
|
|
React.createElement(
|
|
Box,
|
|
{
|
|
sx: {
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
width: '100%',
|
|
justifyContent: { xs: 'space-between', sm: 'flex-start' }, // Match SPA layout
|
|
minHeight: { xs: 52, sm: 'auto' },
|
|
px: { xs: 0, sm: 0 }
|
|
}
|
|
},
|
|
React.createElement(Logo),
|
|
// Invisible SearchBar placeholder on desktop to match SPA spacing
|
|
React.createElement(
|
|
Box,
|
|
{
|
|
sx: {
|
|
display: { xs: 'none', sm: 'block' },
|
|
flexGrow: 1,
|
|
mx: { xs: 0, sm: 2, md: 4 },
|
|
visibility: 'hidden',
|
|
height: 40 // Match SearchBar height
|
|
}
|
|
}
|
|
),
|
|
// Invisible ButtonGroup placeholder to match SPA spacing
|
|
React.createElement(
|
|
Box,
|
|
{
|
|
sx: {
|
|
display: { xs: 'flex', sm: 'flex' },
|
|
alignItems: { xs: 'flex-end', sm: 'center' },
|
|
transform: { xs: 'translateY(4px) translateX(9px)', sm: 'none' },
|
|
ml: { xs: 0, sm: 0 },
|
|
visibility: 'hidden',
|
|
width: { xs: 'auto', sm: '120px' }, // Approximate ButtonGroup width
|
|
height: 40
|
|
}
|
|
}
|
|
)
|
|
),
|
|
// Second row: SearchBar placeholder only on mobile
|
|
React.createElement(
|
|
Box,
|
|
{
|
|
sx: {
|
|
display: { xs: 'block', sm: 'none' },
|
|
width: '100%',
|
|
mt: { xs: 1, sm: 0 },
|
|
mb: { xs: 0.5, sm: 0 },
|
|
px: { xs: 0, sm: 0 },
|
|
height: 41, // Small TextField height
|
|
visibility: 'hidden'
|
|
}
|
|
}
|
|
)
|
|
)
|
|
)
|
|
)
|
|
),
|
|
React.createElement(
|
|
Box,
|
|
{ sx: { flexGrow: 1 } },
|
|
React.createElement(
|
|
Container,
|
|
{
|
|
maxWidth: "lg",
|
|
sx: {
|
|
p: { xs: 2, md: 2 },
|
|
pb: { xs: 4, md: 8 },
|
|
flexGrow: 1
|
|
}
|
|
},
|
|
// Back button (breadcrumbs section)
|
|
React.createElement(
|
|
Box,
|
|
{
|
|
sx: {
|
|
mb: 2,
|
|
position: ["-webkit-sticky", "sticky"],
|
|
top: {
|
|
xs: "80px",
|
|
sm: "80px",
|
|
md: "80px",
|
|
lg: "80px",
|
|
},
|
|
left: 0,
|
|
width: "100%",
|
|
display: "flex",
|
|
zIndex: 999, // Just below the AppBar
|
|
py: 0,
|
|
px: 2,
|
|
}
|
|
},
|
|
React.createElement(
|
|
Box,
|
|
{
|
|
sx: {
|
|
ml: { xs: 0, md: 0 },
|
|
display: "inline-flex",
|
|
px: 0,
|
|
py: 1,
|
|
backgroundColor: "#2e7d32", // primary dark green
|
|
borderRadius: 1,
|
|
}
|
|
},
|
|
React.createElement(
|
|
Typography,
|
|
{ variant: "body2", color: "text.secondary" },
|
|
React.createElement(
|
|
'a',
|
|
{
|
|
href: "#",
|
|
onClick: (e) => {
|
|
e.preventDefault();
|
|
if (window.history.length > 1) {
|
|
window.history.back();
|
|
} else {
|
|
window.location.href = '/';
|
|
}
|
|
},
|
|
style: {
|
|
paddingLeft: 16,
|
|
paddingRight: 16,
|
|
paddingTop: 8,
|
|
paddingBottom: 8,
|
|
textDecoration: "none",
|
|
color: "#fff",
|
|
fontWeight: "bold",
|
|
cursor: "pointer"
|
|
}
|
|
},
|
|
this.props.t ? this.props.t('common.back') : 'Zurück'
|
|
)
|
|
)
|
|
)
|
|
),
|
|
React.createElement(
|
|
Box,
|
|
{
|
|
sx: {
|
|
display: "flex",
|
|
flexDirection: { xs: "column", md: "row" },
|
|
gap: 4,
|
|
background: "#fff",
|
|
borderRadius: 2,
|
|
boxShadow: "0 2px 8px rgba(0,0,0,0.08)",
|
|
}
|
|
},
|
|
// Product Image Section
|
|
React.createElement(
|
|
ProductImage,
|
|
{
|
|
product: product,
|
|
socket: null,
|
|
socketB: null,
|
|
fullscreenOpen: false,
|
|
onOpenFullscreen: null,
|
|
onCloseFullscreen: null
|
|
}
|
|
),
|
|
// Product Details Section
|
|
React.createElement(
|
|
Box,
|
|
{
|
|
sx: {
|
|
flex: "1 1 60%",
|
|
p: { xs: 2, md: 4 },
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
}
|
|
},
|
|
// Product identifiers
|
|
React.createElement(
|
|
Box,
|
|
{ sx: { mb: 1 } },
|
|
React.createElement(
|
|
Typography,
|
|
{ variant: 'body2', color: 'text.secondary' },
|
|
(this.props.t ? this.props.t('product.articleNumber') : 'Artikelnummer')+': '+product.articleNumber+' '+(product.gtin ? ` | GTIN: ${product.gtin}` : "")
|
|
)
|
|
),
|
|
// Product title
|
|
React.createElement(
|
|
Typography,
|
|
{
|
|
variant: 'h4',
|
|
component: 'h1',
|
|
gutterBottom: true,
|
|
sx: {
|
|
fontWeight: 600,
|
|
color: "#333"
|
|
}
|
|
},
|
|
cleanProductName(product.name)
|
|
),
|
|
// Manufacturer if available - exact match to SPA: only render Box if manufacturer exists
|
|
product.manufacturer && React.createElement(
|
|
Box,
|
|
{ sx: { display: "flex", alignItems: "center", mb: 2 } },
|
|
React.createElement(
|
|
Typography,
|
|
{ variant: 'body2', sx: { fontStyle: "italic" } },
|
|
(this.props.t ? this.props.t('product.manufacturer') : 'Hersteller')+': '+product.manufacturer
|
|
)
|
|
),
|
|
// Attribute images and chips with action buttons section - exact replica of SPA version
|
|
// SPA condition: (attributes.some(attr => attributeImages[attr.kMerkmalWert]) || attributes.some(attr => !attributeImages[attr.kMerkmalWert]))
|
|
// This essentially means "if there are any attributes at all"
|
|
// For products with no attributes (like Vakuumbeutel), this section should NOT render
|
|
(attributes.length > 0) && React.createElement(
|
|
Box,
|
|
{ sx: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", mb: 2, gap: 2 } },
|
|
// Left side - attributes
|
|
React.createElement(
|
|
Stack,
|
|
{ direction: 'row', spacing: 2, sx: { flexWrap: "wrap", gap: 1, flex: 1 } },
|
|
// In prerender: attributes.filter(attribute => attributeImages[attribute.kMerkmalWert]) = [] (empty)
|
|
// Then: attributes.filter(attribute => !attributeImages[attribute.kMerkmalWert]) = all attributes as Chips
|
|
...attributes.map((attribute, index) =>
|
|
React.createElement(
|
|
Chip,
|
|
{
|
|
key: attribute.kMerkmalWert || index,
|
|
label: attribute.cWert,
|
|
disabled: true,
|
|
sx: { mb: 1 }
|
|
}
|
|
)
|
|
)
|
|
),
|
|
// Right side - action buttons (exact replica with invisible versions)
|
|
React.createElement(
|
|
Stack,
|
|
{ direction: 'column', spacing: 1, sx: { flexShrink: 0 } },
|
|
// "Frage zum Artikel" button - exact replica but invisible
|
|
React.createElement(
|
|
Button,
|
|
{
|
|
variant: "outlined",
|
|
size: "small",
|
|
sx: {
|
|
fontSize: "0.75rem",
|
|
px: 1.5,
|
|
py: 0.5,
|
|
minWidth: "auto",
|
|
whiteSpace: "nowrap",
|
|
visibility: "hidden",
|
|
pointerEvents: "none"
|
|
}
|
|
},
|
|
"Frage zum Artikel"
|
|
),
|
|
// "Artikel Bewerten" button - exact replica but invisible
|
|
React.createElement(
|
|
Button,
|
|
{
|
|
variant: "outlined",
|
|
size: "small",
|
|
sx: {
|
|
fontSize: "0.75rem",
|
|
px: 1.5,
|
|
py: 0.5,
|
|
minWidth: "auto",
|
|
whiteSpace: "nowrap",
|
|
visibility: "hidden",
|
|
pointerEvents: "none"
|
|
}
|
|
},
|
|
"Artikel Bewerten"
|
|
),
|
|
// "Verfügbarkeit anfragen" button - conditional, exact replica but invisible
|
|
(product.available !== 1 && product.availableSupplier !== 1) && React.createElement(
|
|
Button,
|
|
{
|
|
variant: "outlined",
|
|
size: "small",
|
|
sx: {
|
|
fontSize: "0.75rem",
|
|
px: 1.5,
|
|
py: 0.5,
|
|
minWidth: "auto",
|
|
whiteSpace: "nowrap",
|
|
borderColor: "warning.main",
|
|
color: "warning.main",
|
|
"&:hover": {
|
|
borderColor: "warning.dark",
|
|
backgroundColor: "warning.light"
|
|
},
|
|
visibility: "hidden",
|
|
pointerEvents: "none"
|
|
}
|
|
},
|
|
"Verfügbarkeit anfragen"
|
|
)
|
|
)
|
|
),
|
|
// Weight
|
|
(product.weight && product.weight > 0) ? React.createElement(
|
|
Box,
|
|
{ sx: { mb: 2 } },
|
|
React.createElement(
|
|
Typography,
|
|
{ variant: 'body2', color: 'text.secondary' },
|
|
(this.props.t ? this.props.t('product.weight', { weight: product.weight.toFixed(1).replace(".", ",") }) : `Gewicht: ${product.weight.toFixed(1).replace(".", ",")} kg`)
|
|
)
|
|
) : null,
|
|
// Price and availability section
|
|
React.createElement(
|
|
Box,
|
|
{
|
|
sx: {
|
|
mt: "auto",
|
|
transform: "translateY(-1px)", // Move 1px up
|
|
p: 3,
|
|
background: "#f9f9f9",
|
|
borderRadius: 2,
|
|
}
|
|
},
|
|
React.createElement(
|
|
Box,
|
|
{
|
|
sx: {
|
|
display: "flex",
|
|
flexDirection: { xs: "column", sm: "row" },
|
|
justifyContent: "space-between",
|
|
alignItems: { xs: "flex-start", sm: "flex-start" },
|
|
gap: 2,
|
|
}
|
|
},
|
|
// Left side - Price information (exact match to SPA)
|
|
React.createElement(
|
|
Box,
|
|
null,
|
|
React.createElement(
|
|
Typography,
|
|
{
|
|
variant: "h4",
|
|
color: "primary",
|
|
sx: { fontWeight: "bold" }
|
|
},
|
|
priceWithTax
|
|
),
|
|
// VAT info (exact match to SPA - direct Typography, no wrapper)
|
|
React.createElement(
|
|
Typography,
|
|
{ variant: 'body2', color: 'text.secondary' },
|
|
(this.props.t ? this.props.t('product.inclVat', { vat: product.vat }) : `inkl. ${product.vat}% MwSt.`) +
|
|
(product.cGrundEinheit && product.fGrundPreis ?
|
|
`; ${new Intl.NumberFormat('de-DE', {style: 'currency', currency: 'EUR'}).format(product.fGrundPreis)}/${product.cGrundEinheit}` :
|
|
"")
|
|
),
|
|
// Shipping class (exact match to SPA - direct Typography, conditional render)
|
|
product.versandklasse &&
|
|
product.versandklasse != "standard" &&
|
|
product.versandklasse != "kostenlos" && React.createElement(
|
|
Typography,
|
|
{ variant: 'body2', color: 'text.secondary' },
|
|
product.versandklasse
|
|
)
|
|
),
|
|
// Right side - Complex cart button area structure (matching SPA exactly)
|
|
React.createElement(
|
|
Box,
|
|
{
|
|
sx: {
|
|
display: "flex",
|
|
flexDirection: { xs: "column", sm: "row" },
|
|
gap: 2,
|
|
alignItems: "flex-start",
|
|
}
|
|
},
|
|
// Empty steckling column placeholder - maintains flex positioning
|
|
React.createElement(
|
|
Box,
|
|
{ sx: { display: "flex", flexDirection: "column" } }
|
|
// Empty - no steckling for this product
|
|
),
|
|
// Main cart button column (exact match to SPA structure)
|
|
React.createElement(
|
|
Box,
|
|
{
|
|
sx: {
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
}
|
|
},
|
|
// AddToCartButton placeholder - invisible button that reserves exact space
|
|
React.createElement(
|
|
Button,
|
|
{
|
|
variant: "contained",
|
|
size: "large",
|
|
sx: {
|
|
visibility: "hidden",
|
|
pointerEvents: "none",
|
|
height: "36px",
|
|
width: "140px",
|
|
minWidth: "140px",
|
|
maxWidth: "140px"
|
|
}
|
|
},
|
|
"In den Warenkorb"
|
|
),
|
|
// Delivery time Typography (exact match to SPA)
|
|
React.createElement(
|
|
Typography,
|
|
{
|
|
variant: 'caption',
|
|
sx: {
|
|
fontStyle: "italic",
|
|
color: "text.secondary",
|
|
textAlign: "center",
|
|
mt: 1
|
|
}
|
|
},
|
|
product.id && product.id.toString().endsWith("steckling") ?
|
|
(this.props.t ? this.props.t('delivery.times.cutting14Days') : "Lieferzeit: 14 Tage") :
|
|
product.available == 1 ?
|
|
(this.props.t ? this.props.t('delivery.times.standard2to3Days') : "Lieferzeit: 2-3 Tage") :
|
|
product.availableSupplier == 1 ?
|
|
(this.props.t ? this.props.t('delivery.times.supplier7to9Days') : "Lieferzeit: 7-9 Tage") :
|
|
""
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
),
|
|
// Product full description - separate card
|
|
product.description && React.createElement(
|
|
Box,
|
|
{
|
|
sx: {
|
|
mt: 4,
|
|
p: 4,
|
|
background: "#fff",
|
|
borderRadius: 2,
|
|
boxShadow: "0 2px 8px rgba(0,0,0,0.08)",
|
|
}
|
|
},
|
|
React.createElement(
|
|
Box,
|
|
{
|
|
sx: {
|
|
mt: 2,
|
|
lineHeight: 1.7,
|
|
"& p": { mt: 0, mb: 2 },
|
|
"& strong": { fontWeight: 600 },
|
|
}
|
|
},
|
|
React.createElement(
|
|
'div',
|
|
{
|
|
dangerouslySetInnerHTML: { __html: product.description },
|
|
style: {
|
|
fontFamily: '"Roboto","Helvetica","Arial",sans-serif',
|
|
fontSize: '1rem',
|
|
lineHeight: '1.7',
|
|
color: '#333'
|
|
}
|
|
}
|
|
)
|
|
)
|
|
)
|
|
)
|
|
),
|
|
React.createElement(Footer)
|
|
);
|
|
}
|
|
}
|
|
|
|
module.exports = { default: PrerenderProduct };
|