import React, { Component } from "react";
import { Box, Typography, CardMedia, Stack, Chip } from "@mui/material";
import { Link } from "react-router-dom";
import parse from "html-react-parser";
import AddToCartButton from "./AddToCartButton.js";
import Images from "./Images.js";
// 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();
};
// Product detail page with image loading
class ProductDetailPage extends Component {
constructor(props) {
super(props);
if (
window.productDetailCache &&
window.productDetailCache[this.props.seoName]
) {
this.state = {
product: window.productDetailCache[this.props.seoName],
loading: false,
error: null,
attributeImages: {},
attributes: [],
isSteckling: false,
imageDialogOpen: false,
};
} else {
this.state = {
product: null,
loading: true,
error: null,
attributeImages: {},
attributes: [],
isSteckling: false,
imageDialogOpen: false,
};
}
}
componentDidMount() {
this.loadProductData();
}
componentDidUpdate(prevProps) {
if (prevProps.seoName !== this.props.seoName)
this.setState(
{ product: null, loading: true, error: null, imageDialogOpen: false },
this.loadProductData
);
// Handle socket connection changes
const wasConnected = prevProps.socket && prevProps.socket.connected;
const isNowConnected = this.props.socket && this.props.socket.connected;
if (!wasConnected && isNowConnected && this.state.loading) {
// Socket just connected and we're still loading, retry loading data
this.loadProductData();
}
}
loadProductData = () => {
if (!this.props.socket || !this.props.socket.connected) {
// Socket not connected yet, but don't show error immediately on first load
// The componentDidUpdate will retry when socket connects
console.log("Socket not connected yet, waiting for connection to load product data");
return;
}
this.props.socket.emit(
"getProductView",
{ seoName: this.props.seoName },
(res) => {
if (res.success) {
res.product.seoName = this.props.seoName;
this.setState({
product: res.product,
loading: false,
error: null,
imageDialogOpen: false,
attributes: res.attributes
});
console.log("getProductView", res);
// Initialize window-level attribute image cache if it doesn't exist
if (!window.attributeImageCache) {
window.attributeImageCache = {};
}
if (res.attributes && res.attributes.length > 0) {
const attributeImages = {};
for (const attribute of res.attributes) {
const cacheKey = attribute.kMerkmalWert;
if (attribute.cName == "Anzahl")
this.setState({ isSteckling: true });
// Check if we have a cached result (either URL or negative result)
if (window.attributeImageCache[cacheKey]) {
const cached = window.attributeImageCache[cacheKey];
if (cached.url) {
// Use cached URL
attributeImages[cacheKey] = cached.url;
}
} else {
// Not in cache, fetch from server
if (this.props.socketB && this.props.socketB.connected) {
this.props.socketB.emit(
"getAttributePicture",
{ id: cacheKey },
(res) => {
console.log("getAttributePicture", res);
if (res.success && !res.noPicture) {
const blob = new Blob([res.imageBuffer], {
type: "image/jpeg",
});
const url = URL.createObjectURL(blob);
// Cache the successful URL
window.attributeImageCache[cacheKey] = {
url: url,
timestamp: Date.now(),
};
// Update state and force re-render
this.setState(prevState => ({
attributeImages: {
...prevState.attributeImages,
[cacheKey]: url
}
}));
} else {
// Cache negative result to avoid future requests
// This handles both failure cases and success with noPicture: true
window.attributeImageCache[cacheKey] = {
noImage: true,
timestamp: Date.now(),
};
}
}
);
}
}
}
// Set initial state with cached images
if (Object.keys(attributeImages).length > 0) {
this.setState({ attributeImages });
}
}
} else {
console.error(
"Error loading product:",
res.error || "Unknown error",
res
);
this.setState({
product: null,
loading: false,
error: "Error loading product",
imageDialogOpen: false,
});
}
}
);
};
handleOpenDialog = () => {
this.setState({ imageDialogOpen: true });
};
handleCloseDialog = () => {
this.setState({ imageDialogOpen: false });
};
render() {
const { product, loading, error, attributeImages, isSteckling, attributes } =
this.state;
if (loading) {
return (
Produkt wird geladen...
);
}
if (error) {
return (
Fehler
{error}
Zurück zur Startseite
);
}
if (!product) {
return (
Produkt nicht gefunden
Das gesuchte Produkt existiert nicht oder wurde entfernt.
Zurück zur Startseite
);
}
// Format price with tax
const priceWithTax = new Intl.NumberFormat("de-DE", {
style: "currency",
currency: "EUR",
}).format(product.price);
return (
{/* Breadcrumbs */}
theme.zIndex.appBar - 1 /* Just below the AppBar */,
py: 0,
px: 2,
}}
>
this.props.navigate(-1)}
style={{
paddingLeft: 16,
paddingRight: 16,
paddingTop: 8,
paddingBottom: 8,
textDecoration: "none",
color: "#fff",
fontWeight: "bold",
}}
>
Zurück
{!product.pictureList && (
)}
{product.pictureList && (
)}
{/* Product Details */}
{/* Product identifiers */}
Artikelnummer: {product.articleNumber} {product.gtin ? ` | GTIN: ${product.gtin}` : ""}
{/* Product title */}
{cleanProductName(product.name)}
{/* Manufacturer if available */}
{product.manufacturer && (
Hersteller: {product.manufacturer}
)}
{/* Attribute images and chips */}
{(attributes.some(attr => attributeImages[attr.kMerkmalWert]) || attributes.some(attr => !attributeImages[attr.kMerkmalWert])) && (
{attributes
.filter(attribute => attributeImages[attribute.kMerkmalWert])
.map((attribute) => {
const key = attribute.kMerkmalWert;
return (
);
})}
{attributes
.filter(attribute => !attributeImages[attribute.kMerkmalWert])
.map((attribute) => (
))}
)}
{/* Weight */}
{product.weight > 0 && (
Gewicht: {product.weight.toFixed(1).replace(".", ",")} kg
)}
{/* Price and availability section */}
{priceWithTax}
inkl. {product.vat}% MwSt.
{product.cGrundEinheit && product.fGrundPreis && (
<>; {new Intl.NumberFormat('de-DE', {style: 'currency', currency: 'EUR'}).format(product.fGrundPreis)}/{product.cGrundEinheit}>
)}
{product.versandklasse &&
product.versandklasse != "standard" &&
product.versandklasse != "kostenlos" && (
{product.versandklasse}
)}
{isSteckling && product.available == 1 && (
Abholpreis: 19,90 € pro Steckling.
)}
{product.id.toString().endsWith("steckling") ? "Lieferzeit: 14 Tage" :
product.available == 1 ? "Lieferzeit: 2-3 Tage" :
product.availableSupplier == 1 ? "Lieferzeit: 7-9 Tage" : ""}
{/* Product full description */}
{product.description && (
{parse(product.description)}
)}
);
}
}
export default ProductDetailPage;