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;