feat: Add GPSR safety info translation and remove pre-order cutting button functionality.
This commit is contained in:
@@ -190,8 +190,8 @@ class AddToCartButton extends Component {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{this.props.steckling ?
|
||||
(this.props.t ? this.props.t('cart.preorderCutting') : "Als Steckling vorbestellen") :
|
||||
{/*this.props.steckling ?
|
||||
(this.props.t ? this.props.t('cart.preorderCutting') : "Als Steckling vorbestellen") : */
|
||||
(this.props.t ? this.props.t('cart.addToCart') : "In den Korb")}
|
||||
</Button>
|
||||
);
|
||||
@@ -342,8 +342,8 @@ class AddToCartButton extends Component {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{this.props.steckling ?
|
||||
(this.props.t ? this.props.t('cart.preorderCutting') : "Als Steckling vorbestellen") :
|
||||
{/*this.props.steckling ?
|
||||
(this.props.t ? this.props.t('cart.preorderCutting') : "Als Steckling vorbestellen") :*/
|
||||
(this.props.t ? this.props.t('cart.addToCart') : "In den Korb")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -71,11 +71,11 @@ class ProductDetailPage extends Component {
|
||||
|
||||
// Initialize komponenten from cached product data
|
||||
const komponenten = [];
|
||||
if(cachedData.product.komponenten) {
|
||||
for(const komponent of cachedData.product.komponenten.split(",")) {
|
||||
if (cachedData.product.komponenten) {
|
||||
for (const komponent of cachedData.product.komponenten.split(",")) {
|
||||
// Handle both "x" and "×" as separators
|
||||
const [id, count] = komponent.split(/[x×]/);
|
||||
komponenten.push({id: id.trim(), count: count.trim()});
|
||||
komponenten.push({ id: id.trim(), count: count.trim() });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,11 +123,11 @@ class ProductDetailPage extends Component {
|
||||
|
||||
// Initialize komponenten from partial product data if available
|
||||
const komponenten = [];
|
||||
if(partialProduct.komponenten) {
|
||||
for(const komponent of partialProduct.komponenten.split(",")) {
|
||||
if (partialProduct.komponenten) {
|
||||
for (const komponent of partialProduct.komponenten.split(",")) {
|
||||
// Handle both "x" and "×" as separators
|
||||
const [id, count] = komponent.split(/[x×]/);
|
||||
komponenten.push({id: id.trim(), count: count.trim()});
|
||||
komponenten.push({ id: id.trim(), count: count.trim() });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@ class ProductDetailPage extends Component {
|
||||
}
|
||||
|
||||
if (this.state.komponenten.length > 0 && !this.state.komponentenLoaded) {
|
||||
for(const komponent of this.state.komponenten) {
|
||||
for (const komponent of this.state.komponenten) {
|
||||
this.loadKomponent(komponent.id, komponent.count);
|
||||
}
|
||||
}
|
||||
@@ -403,7 +403,7 @@ class ProductDetailPage extends Component {
|
||||
|
||||
window.socketManager.emit(
|
||||
"getProductView",
|
||||
{ articleId: id, language: currentLanguage, requestTranslation: currentLanguage === "de" ? false : true},
|
||||
{ articleId: id, language: currentLanguage, requestTranslation: currentLanguage === "de" ? false : true },
|
||||
(res) => {
|
||||
if (res.success) {
|
||||
// Use translated product if available, otherwise use original product
|
||||
@@ -429,10 +429,10 @@ class ProductDetailPage extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
// Check if all remaining komponenten are loaded
|
||||
const allLoaded = prevState.komponenten.every(k =>
|
||||
newKomponentenData[k.id] && newKomponentenData[k.id].loaded
|
||||
);
|
||||
// Check if all remaining komponenten are loaded
|
||||
const allLoaded = prevState.komponenten.every(k =>
|
||||
newKomponentenData[k.id] && newKomponentenData[k.id].loaded
|
||||
);
|
||||
|
||||
// Calculate totals if all loaded
|
||||
let totalKomponentenPrice = 0;
|
||||
@@ -592,7 +592,7 @@ class ProductDetailPage extends Component {
|
||||
|
||||
window.socketManager.emit(
|
||||
"getProductView",
|
||||
{ seoName: this.props.seoName, language: currentLanguage, requestTranslation: currentLanguage === "de" ? false : true},
|
||||
{ seoName: this.props.seoName, language: currentLanguage, requestTranslation: currentLanguage === "de" ? false : true },
|
||||
(res) => {
|
||||
if (res.success) {
|
||||
// Use translated product if available, otherwise use original product
|
||||
@@ -618,11 +618,11 @@ class ProductDetailPage extends Component {
|
||||
}
|
||||
|
||||
const komponenten = [];
|
||||
if(productData.komponenten) {
|
||||
for(const komponent of productData.komponenten.split(",")) {
|
||||
if (productData.komponenten) {
|
||||
for (const komponent of productData.komponenten.split(",")) {
|
||||
// Handle both "x" and "×" as separators
|
||||
const [id, count] = komponent.split(/[x×]/);
|
||||
komponenten.push({id: id.trim(), count: count.trim()});
|
||||
komponenten.push({ id: id.trim(), count: count.trim() });
|
||||
}
|
||||
}
|
||||
this.setState({
|
||||
@@ -636,22 +636,22 @@ class ProductDetailPage extends Component {
|
||||
komponentenLoaded: komponenten.length === 0, // If no komponenten, mark as loaded
|
||||
similarProducts: res.similarProducts || []
|
||||
}, () => {
|
||||
// Update context
|
||||
if (this.props.setCurrentProduct) {
|
||||
console.log('ProductDetailPage: Setting product context from fetch', productData.name);
|
||||
this.props.setCurrentProduct({
|
||||
name: productData.name,
|
||||
categoryId: productData.kategorien ? productData.kategorien.split(',')[0] : undefined
|
||||
});
|
||||
} else {
|
||||
console.warn('ProductDetailPage: setCurrentProduct prop is missing after fetch');
|
||||
}
|
||||
// Update context
|
||||
if (this.props.setCurrentProduct) {
|
||||
console.log('ProductDetailPage: Setting product context from fetch', productData.name);
|
||||
this.props.setCurrentProduct({
|
||||
name: productData.name,
|
||||
categoryId: productData.kategorien ? productData.kategorien.split(',')[0] : undefined
|
||||
});
|
||||
} else {
|
||||
console.warn('ProductDetailPage: setCurrentProduct prop is missing after fetch');
|
||||
}
|
||||
|
||||
if(komponenten.length > 0) {
|
||||
for(const komponent of komponenten) {
|
||||
this.loadKomponent(komponent.id, komponent.count);
|
||||
}
|
||||
if (komponenten.length > 0) {
|
||||
for (const komponent of komponenten) {
|
||||
this.loadKomponent(komponent.id, komponent.count);
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log("getProductView", res);
|
||||
|
||||
@@ -821,9 +821,9 @@ class ProductDetailPage extends Component {
|
||||
|
||||
const body = this.props.t
|
||||
? this.props.t("productDialogs.shareEmailBody", {
|
||||
name: productName,
|
||||
url: url
|
||||
})
|
||||
name: productName,
|
||||
url: url
|
||||
})
|
||||
: `Hallo,\n\nich möchte dir dieses Produkt empfehlen:\n\n${productName}\n${url}\n\nViele Grüße`;
|
||||
|
||||
const emailUrl = `mailto:?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
|
||||
@@ -1080,14 +1080,14 @@ class ProductDetailPage extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
componentWillUnmount() {
|
||||
if (this.props.setCurrentProduct) {
|
||||
this.props.setCurrentProduct(null);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { product, loading, upgrading, error, attributeImages, isSteckling, attributes, komponentenLoaded, komponentenData, komponentenImages, totalKomponentenPrice, totalSavings, shareAnchorEl, sharePopperOpen, snackbarOpen, snackbarMessage, snackbarSeverity } =
|
||||
const { product, loading, upgrading, error, attributeImages, /*isSteckling,*/ attributes, komponentenLoaded, komponentenData, komponentenImages, totalKomponentenPrice, totalSavings, shareAnchorEl, sharePopperOpen, snackbarOpen, snackbarMessage, snackbarSeverity } =
|
||||
this.state;
|
||||
|
||||
// Debug alerts removed
|
||||
@@ -1305,7 +1305,7 @@ class ProductDetailPage extends Component {
|
||||
.map((attribute) => {
|
||||
const key = attribute.kMerkmalWert;
|
||||
return (
|
||||
<Box key={key} sx={{ mb: 1,border: "1px solid #e0e0e0", borderRadius: 1 }}>
|
||||
<Box key={key} sx={{ mb: 1, border: "1px solid #e0e0e0", borderRadius: 1 }}>
|
||||
<CardMedia
|
||||
component="img"
|
||||
style={{ width: "72px", height: "98px" }}
|
||||
@@ -1444,7 +1444,7 @@ class ProductDetailPage extends Component {
|
||||
<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}</>
|
||||
<>; {new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(product.fGrundPreis)}/{product.cGrundEinheit}</>
|
||||
)}
|
||||
</Typography>
|
||||
|
||||
@@ -1459,38 +1459,38 @@ class ProductDetailPage extends Component {
|
||||
|
||||
{/* Savings comparison - positioned between price and cart button */}
|
||||
{product.komponenten && komponentenLoaded && totalKomponentenPrice > product.price &&
|
||||
(totalKomponentenPrice - product.price >= 2 &&
|
||||
(totalKomponentenPrice - product.price) / product.price >= 0.02) && (
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
minWidth: { xs: "100%", sm: "200px" }
|
||||
}}>
|
||||
<Box sx={{ p: 2, borderRadius: 1, backgroundColor: "#e8f5e8", textAlign: "center" }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
color: "success.main"
|
||||
}}
|
||||
>
|
||||
{this.props.t ? this.props.t('product.youSave', {
|
||||
amount: new Intl.NumberFormat("de-DE", {
|
||||
(totalKomponentenPrice - product.price >= 2 &&
|
||||
(totalKomponentenPrice - product.price) / product.price >= 0.02) && (
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
minWidth: { xs: "100%", sm: "200px" }
|
||||
}}>
|
||||
<Box sx={{ p: 2, borderRadius: 1, backgroundColor: "#e8f5e8", textAlign: "center" }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
color: "success.main"
|
||||
}}
|
||||
>
|
||||
{this.props.t ? this.props.t('product.youSave', {
|
||||
amount: new Intl.NumberFormat("de-DE", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
}).format(totalKomponentenPrice - product.price)
|
||||
}) : `Sie sparen: ${new Intl.NumberFormat("de-DE", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
}).format(totalKomponentenPrice - product.price)
|
||||
}) : `Sie sparen: ${new Intl.NumberFormat("de-DE", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
}).format(totalKomponentenPrice - product.price)}`}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{this.props.t ? this.props.t('product.cheaperThanIndividual') : 'Günstiger als Einzelkauf'}
|
||||
</Typography>
|
||||
}).format(totalKomponentenPrice - product.price)}`}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{this.props.t ? this.props.t('product.cheaperThanIndividual') : 'Günstiger als Einzelkauf'}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
@@ -1500,7 +1500,7 @@ class ProductDetailPage extends Component {
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
{isSteckling && product.available == 1 && (
|
||||
{/*isSteckling && product.available == 1 && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -1534,7 +1534,7 @@ class ProductDetailPage extends Component {
|
||||
{this.props.t ? this.props.t('product.pickupPrice') : 'Abholpreis: 19,90 € pro Steckling.'}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
)*/}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -1663,6 +1663,18 @@ class ProductDetailPage extends Component {
|
||||
</Box>
|
||||
) : null}
|
||||
</Box>
|
||||
|
||||
{/* GPSR Information */}
|
||||
{product.gpsr && (
|
||||
<Box sx={{ mt: 3, pt: 2, borderTop: "1px solid #eee" }}>
|
||||
<Typography variant="subtitle2" sx={{ fontSize: "0.75rem", fontWeight: "bold", mb: 0.5 }}>
|
||||
{this.props.t ? this.props.t("product.gpsrSafetyInfo") : "GPSR Informationen zur Produktsicherheit:"}
|
||||
</Typography>
|
||||
<Typography variant="caption" component="div" sx={{ whiteSpace: "pre-wrap", color: "text.secondary" }}>
|
||||
{product.gpsr}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -1760,25 +1772,174 @@ class ProductDetailPage extends Component {
|
||||
<Typography variant="h2" component="h2" gutterBottom>{this.props.t ? this.props.t('product.consistsOf') : 'Bestehend aus:'}</Typography>
|
||||
<Box sx={{ maxWidth: 800, mx: "auto" }}>
|
||||
|
||||
{(console.log("komponentenLoaded:", komponentenLoaded), komponentenLoaded) ? (
|
||||
<>
|
||||
{console.log("Rendering loaded komponenten:", this.state.komponenten.length, "komponentenData:", Object.keys(komponentenData).length)}
|
||||
{this.state.komponenten.map((komponent, index) => {
|
||||
const komponentData = komponentenData[komponent.id];
|
||||
console.log(`Rendering komponent ${komponent.id}:`, komponentData);
|
||||
{(console.log("komponentenLoaded:", komponentenLoaded), komponentenLoaded) ? (
|
||||
<>
|
||||
{console.log("Rendering loaded komponenten:", this.state.komponenten.length, "komponentenData:", Object.keys(komponentenData).length)}
|
||||
{this.state.komponenten.map((komponent, index) => {
|
||||
const komponentData = komponentenData[komponent.id];
|
||||
console.log(`Rendering komponent ${komponent.id}:`, komponentData);
|
||||
|
||||
// Don't show border on last item (pricing section has its own top border)
|
||||
const isLastItem = index === this.state.komponenten.length - 1;
|
||||
const showBorder = !isLastItem;
|
||||
// Don't show border on last item (pricing section has its own top border)
|
||||
const isLastItem = index === this.state.komponenten.length - 1;
|
||||
const showBorder = !isLastItem;
|
||||
|
||||
if (!komponentData || !komponentData.loaded) {
|
||||
if (!komponentData || !komponentData.loaded) {
|
||||
return (
|
||||
<Box key={komponent.id} sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
py: 1,
|
||||
borderBottom: showBorder ? "1px solid #eee" : "none",
|
||||
minHeight: "70px" // Consistent height to prevent layout shifts
|
||||
}}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||
<Box sx={{ width: 50, height: 50, flexShrink: 0, backgroundColor: "#f5f5f5", borderRadius: 1, border: "1px solid #e0e0e0" }}>
|
||||
{/* Empty placeholder for image */}
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="body1">
|
||||
{index + 1}. Lädt...
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{komponent.count}x
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Typography variant="body1" sx={{ fontWeight: 500 }}>
|
||||
-
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const itemPrice = komponentData.price * parseInt(komponent.count);
|
||||
const formattedPrice = new Intl.NumberFormat("de-DE", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
}).format(itemPrice);
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={komponent.id}
|
||||
component={Link}
|
||||
to={`/Artikel/${komponentData.seoName}`}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
py: 1,
|
||||
borderBottom: showBorder ? "1px solid #eee" : "none",
|
||||
textDecoration: "none",
|
||||
color: "inherit",
|
||||
minHeight: "70px", // Consistent height to prevent layout shifts
|
||||
"&:hover": {
|
||||
backgroundColor: "#f5f5f5"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||
<Box sx={{ width: 50, height: 50, flexShrink: 0 }}>
|
||||
{komponentenImages[komponent.id] ? (
|
||||
<CardMedia
|
||||
component="img"
|
||||
height="50"
|
||||
image={komponentenImages[komponent.id]}
|
||||
alt={komponentData.name}
|
||||
sx={{
|
||||
objectFit: "contain",
|
||||
borderRadius: 1,
|
||||
border: "1px solid #e0e0e0"
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<CardMedia
|
||||
component="img"
|
||||
height="50"
|
||||
image="/assets/images/nopicture.jpg"
|
||||
alt={komponentData.name}
|
||||
sx={{
|
||||
objectFit: "contain",
|
||||
borderRadius: 1,
|
||||
border: "1px solid #e0e0e0"
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="body1" sx={{ fontWeight: 500 }}>
|
||||
{index + 1}. {cleanProductName(komponentData.name)}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{komponent.count}x à {new Intl.NumberFormat("de-DE", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
}).format(komponentData.price)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Typography variant="body1" sx={{ fontWeight: 500 }}>
|
||||
{formattedPrice}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Total price and savings display - only show when prices differ meaningfully */}
|
||||
{totalKomponentenPrice > product.price &&
|
||||
(totalKomponentenPrice - product.price >= 2 &&
|
||||
(totalKomponentenPrice - product.price) / product.price >= 0.02) && (
|
||||
<Box sx={{ mt: 3, pt: 2, borderTop: "2px solid #eee" }}>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1 }}>
|
||||
<Typography variant="h6">
|
||||
{this.props.t ? this.props.t('product.individualPriceTotal') : 'Einzelpreis gesamt:'}
|
||||
</Typography>
|
||||
<Typography variant="h6" sx={{ textDecoration: "line-through", color: "text.secondary" }}>
|
||||
{new Intl.NumberFormat("de-DE", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
}).format(totalKomponentenPrice)}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1 }}>
|
||||
<Typography variant="h6">
|
||||
{this.props.t ? this.props.t('product.setPrice') : 'Set-Preis:'}
|
||||
</Typography>
|
||||
<Typography variant="h6" color="primary" sx={{ fontWeight: "bold" }}>
|
||||
{new Intl.NumberFormat("de-DE", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
}).format(product.price)}
|
||||
</Typography>
|
||||
</Box>
|
||||
{totalSavings > 0 && (
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mt: 2, p: 2, backgroundColor: "#e8f5e8", borderRadius: 1 }}>
|
||||
<Typography variant="h6" color="success.main" sx={{ fontWeight: "bold" }}>
|
||||
{this.props.t ? this.props.t('product.yourSavings') : 'Ihre Ersparnis:'}
|
||||
</Typography>
|
||||
<Typography variant="h6" color="success.main" sx={{ fontWeight: "bold" }}>
|
||||
{new Intl.NumberFormat("de-DE", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
}).format(totalSavings)}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
// Loading state
|
||||
<Box>
|
||||
{this.state.komponenten.map((komponent, index) => {
|
||||
// For loading state, we don't know if pricing will be shown, so show all borders
|
||||
return (
|
||||
<Box key={komponent.id} sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
py: 1,
|
||||
borderBottom: showBorder ? "1px solid #eee" : "none",
|
||||
borderBottom: "1px solid #eee",
|
||||
minHeight: "70px" // Consistent height to prevent layout shifts
|
||||
}}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||
@@ -1787,7 +1948,7 @@ class ProductDetailPage extends Component {
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="body1">
|
||||
{index + 1}. Lädt...
|
||||
{this.props.t ? this.props.t('product.loadingComponentDetails', { index: index + 1 }) : `${index + 1}. Lädt Komponent-Details...`}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{komponent.count}x
|
||||
@@ -1799,158 +1960,9 @@ class ProductDetailPage extends Component {
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const itemPrice = komponentData.price * parseInt(komponent.count);
|
||||
const formattedPrice = new Intl.NumberFormat("de-DE", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
}).format(itemPrice);
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={komponent.id}
|
||||
component={Link}
|
||||
to={`/Artikel/${komponentData.seoName}`}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
py: 1,
|
||||
borderBottom: showBorder ? "1px solid #eee" : "none",
|
||||
textDecoration: "none",
|
||||
color: "inherit",
|
||||
minHeight: "70px", // Consistent height to prevent layout shifts
|
||||
"&:hover": {
|
||||
backgroundColor: "#f5f5f5"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||
<Box sx={{ width: 50, height: 50, flexShrink: 0 }}>
|
||||
{komponentenImages[komponent.id] ? (
|
||||
<CardMedia
|
||||
component="img"
|
||||
height="50"
|
||||
image={komponentenImages[komponent.id]}
|
||||
alt={komponentData.name}
|
||||
sx={{
|
||||
objectFit: "contain",
|
||||
borderRadius: 1,
|
||||
border: "1px solid #e0e0e0"
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<CardMedia
|
||||
component="img"
|
||||
height="50"
|
||||
image="/assets/images/nopicture.jpg"
|
||||
alt={komponentData.name}
|
||||
sx={{
|
||||
objectFit: "contain",
|
||||
borderRadius: 1,
|
||||
border: "1px solid #e0e0e0"
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="body1" sx={{ fontWeight: 500 }}>
|
||||
{index + 1}. {cleanProductName(komponentData.name)}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{komponent.count}x à {new Intl.NumberFormat("de-DE", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
}).format(komponentData.price)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Typography variant="body1" sx={{ fontWeight: 500 }}>
|
||||
{formattedPrice}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Total price and savings display - only show when prices differ meaningfully */}
|
||||
{totalKomponentenPrice > product.price &&
|
||||
(totalKomponentenPrice - product.price >= 2 &&
|
||||
(totalKomponentenPrice - product.price) / product.price >= 0.02) && (
|
||||
<Box sx={{ mt: 3, pt: 2, borderTop: "2px solid #eee" }}>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1 }}>
|
||||
<Typography variant="h6">
|
||||
{this.props.t ? this.props.t('product.individualPriceTotal') : 'Einzelpreis gesamt:'}
|
||||
</Typography>
|
||||
<Typography variant="h6" sx={{ textDecoration: "line-through", color: "text.secondary" }}>
|
||||
{new Intl.NumberFormat("de-DE", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
}).format(totalKomponentenPrice)}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1 }}>
|
||||
<Typography variant="h6">
|
||||
{this.props.t ? this.props.t('product.setPrice') : 'Set-Preis:'}
|
||||
</Typography>
|
||||
<Typography variant="h6" color="primary" sx={{ fontWeight: "bold" }}>
|
||||
{new Intl.NumberFormat("de-DE", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
}).format(product.price)}
|
||||
</Typography>
|
||||
</Box>
|
||||
{totalSavings > 0 && (
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mt: 2, p: 2, backgroundColor: "#e8f5e8", borderRadius: 1 }}>
|
||||
<Typography variant="h6" color="success.main" sx={{ fontWeight: "bold" }}>
|
||||
{this.props.t ? this.props.t('product.yourSavings') : 'Ihre Ersparnis:'}
|
||||
</Typography>
|
||||
<Typography variant="h6" color="success.main" sx={{ fontWeight: "bold" }}>
|
||||
{new Intl.NumberFormat("de-DE", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
}).format(totalSavings)}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
// Loading state
|
||||
<Box>
|
||||
{this.state.komponenten.map((komponent, index) => {
|
||||
// For loading state, we don't know if pricing will be shown, so show all borders
|
||||
return (
|
||||
<Box key={komponent.id} sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
py: 1,
|
||||
borderBottom: "1px solid #eee",
|
||||
minHeight: "70px" // Consistent height to prevent layout shifts
|
||||
}}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||
<Box sx={{ width: 50, height: 50, flexShrink: 0, backgroundColor: "#f5f5f5", borderRadius: 1, border: "1px solid #e0e0e0" }}>
|
||||
{/* Empty placeholder for image */}
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="body1">
|
||||
{this.props.t ? this.props.t('product.loadingComponentDetails', { index: index + 1 }) : `${index + 1}. Lädt Komponent-Details...`}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{komponent.count}x
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Typography variant="body1" sx={{ fontWeight: 500 }}>
|
||||
-
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -24,6 +24,7 @@ export default {
|
||||
"youSave": "Sie sparen: {{amount}}",
|
||||
"cheaperThanIndividual": "Günstiger als Einzelkauf",
|
||||
"pickupPrice": "Abholpreis: 19,90 € pro Steckling.",
|
||||
"gpsrSafetyInfo": "GPSR Informationen zur Produktsicherheit:",
|
||||
"consistsOf": "Bestehend aus:",
|
||||
"loadingComponentDetails": "{{index}}. Lädt Komponent-Details...",
|
||||
"loadingProduct": "Produkt wird geladen...",
|
||||
|
||||
Reference in New Issue
Block a user