feat: Add GPSR safety info translation and remove pre-order cutting button functionality.

This commit is contained in:
sebseb7
2025-12-19 12:46:49 +01:00
parent dbd5df28f8
commit 0ccb00db32
3 changed files with 369 additions and 356 deletions

View File

@@ -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>
);

View File

@@ -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>
)}

View File

@@ -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...",