import React, { Component } from 'react'; import { Container, Paper, Box, Typography, Button, Divider, List, ListItem, ListItemText, ListItemSecondaryAction, Grid, CardMedia, CircularProgress, Stack, } from '@mui/material'; import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; import { TentShapeSelector, ExtrasSelector } from '../components/configurator/index.js'; import { tentShapes } from '../data/configuratorData.js'; import { Link } from 'react-router-dom'; import IconButton from '@mui/material/IconButton'; import ZoomInIcon from '@mui/icons-material/ZoomIn'; function setCachedCategoryData(categoryId, data, language = 'de') { if (!window.productCache) { window.productCache = {}; } if (!window.productDetailCache) { window.productDetailCache = {}; } try { const cacheKey = `categoryProducts_${categoryId}_${language}`; if(data.products) for(const product of data.products) { const productCacheKey = `product_${product.id}_${language}`; window.productDetailCache[productCacheKey] = product; } window.productCache[cacheKey] = { ...data, timestamp: Date.now() }; } catch (err) { console.error('Error writing to cache:', err); } } function getCachedCategoryData(categoryId, language = 'de') { if (!window.productCache) { window.productCache = {}; } try { const cacheKey = `categoryProducts_${categoryId}_${language}`; const cachedData = window.productCache[cacheKey]; if (cachedData) { const { timestamp } = cachedData; const cacheAge = Date.now() - timestamp; const tenMinutes = 10 * 60 * 1000; if (cacheAge < tenMinutes) { return cachedData; } } } catch (err) { console.error('Error reading from cache:', err); } return null; } import { withI18n } from '../i18n/withTranslation.js'; class GrowTentKonfigurator extends Component { constructor(props) { super(props); // Try to restore state from window object const savedState = window.growTentKonfiguratorState; this.state = { selectedTentShape: savedState?.selectedTentShape || '80x80', selectedTentSize: savedState?.selectedTentSize || 'tent_80x80x160', selectedLightType: savedState?.selectedLightType || 'led_quantum_board', selectedVentilationType: savedState?.selectedVentilationType || 'premium_ventilation', selectedExtras: savedState?.selectedExtras || [], totalPrice: savedState?.totalPrice || 0, categoryLoadStatus: { "Zelte": false, "Lampen": false, "Abluft-sets": false, "Set-zubehoer": false } }; this.handleTentShapeSelect = this.handleTentShapeSelect.bind(this); this.handleTentSizeSelect = this.handleTentSizeSelect.bind(this); this.handleLightTypeSelect = this.handleLightTypeSelect.bind(this); this.handleVentilationSelect = this.handleVentilationSelect.bind(this); this.handleExtraToggle = this.handleExtraToggle.bind(this); this.calculateTotalPrice = this.calculateTotalPrice.bind(this); this.saveStateToWindow = this.saveStateToWindow.bind(this); this.checkCacheValidity = this.checkCacheValidity.bind(this); this.handleAddToCart = this.handleAddToCart.bind(this); } saveStateToWindow() { // Save current state to window object for backup window.growTentKonfiguratorState = { selectedTentShape: this.state.selectedTentShape, selectedTentSize: this.state.selectedTentSize, selectedLightType: this.state.selectedLightType, selectedVentilationType: this.state.selectedVentilationType, selectedExtras: this.state.selectedExtras, totalPrice: this.state.totalPrice, timestamp: Date.now() // Add timestamp for debugging }; } componentDidMount() { // @note Calculate initial total price with preselected products //this.calculateTotalPrice(); this.fetchCategoryData("Zelte"); this.fetchCategoryData("Lampen"); this.fetchCategoryData("Abluft-sets"); this.fetchCategoryData("Set-zubehoer"); this.cacheCheckInterval = setInterval(this.checkCacheValidity, 60000); } componentDidUpdate(prevProps, prevState) { // Reset tent size selection if shape changes if (prevState.selectedTentShape !== this.state.selectedTentShape && this.state.selectedTentShape !== prevState.selectedTentShape) { this.setState({ selectedTentSize: '' }); } // Reset ventilation selection if current selection is not deliverable for new tent shape if (prevState.selectedTentShape !== this.state.selectedTentShape && this.state.selectedVentilationType) { const availableVentilation = this.getVentilationForTentShape(this.state.selectedTentShape); const currentVentilation = availableVentilation.find(v => v.id === this.state.selectedVentilationType); if (!currentVentilation || !currentVentilation.isDeliverable) { this.setState({ selectedVentilationType: '' }); } } // Recalculate total price when selections change if ( prevState.selectedTentSize !== this.state.selectedTentSize || prevState.selectedLightType !== this.state.selectedLightType || prevState.selectedVentilationType !== this.state.selectedVentilationType || prevState.selectedExtras !== this.state.selectedExtras ) { this.calculateTotalPrice(); } // Save state to window object whenever selections change if ( prevState.selectedTentShape !== this.state.selectedTentShape || prevState.selectedTentSize !== this.state.selectedTentSize || prevState.selectedLightType !== this.state.selectedLightType || prevState.selectedVentilationType !== this.state.selectedVentilationType || prevState.selectedExtras !== this.state.selectedExtras || prevState.totalPrice !== this.state.totalPrice ) { this.saveStateToWindow(); } // Check if language changed and reload category data const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language; const prevLanguage = prevProps.languageContext?.currentLanguage || prevProps.i18n?.language; if (currentLanguage && prevLanguage && currentLanguage !== prevLanguage) { console.log(`Language changed from ${prevLanguage} to ${currentLanguage} - reloading configurator data`); // Reset category load status to trigger loading state UI this.setState({ categoryLoadStatus: { "Zelte": false, "Lampen": false, "Abluft-sets": false, "Set-zubehoer": false } }, () => { // Fetch data for all categories with new language this.fetchCategoryData("Zelte"); this.fetchCategoryData("Lampen"); this.fetchCategoryData("Abluft-sets"); this.fetchCategoryData("Set-zubehoer"); }); } } componentWillUnmount() { if (this.cacheCheckInterval) { clearInterval(this.cacheCheckInterval); } } fetchCategoryData(categoryId) { const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; const cachedData = getCachedCategoryData(categoryId, currentLanguage); if (cachedData) { this.setState(prevState => ({ categoryLoadStatus: { ...prevState.categoryLoadStatus, [categoryId]: true } })); return; } console.log(`Cache miss for category ${categoryId}, fetching...`); window.socketManager.off(`productList:${categoryId}`); window.socketManager.on(`productList:${categoryId}`,(response) => { setCachedCategoryData(categoryId, response, currentLanguage); this.setState(prevState => ({ categoryLoadStatus: { ...prevState.categoryLoadStatus, [categoryId]: true } })); }); window.socketManager.emit( "getCategoryProducts", { categoryId: categoryId, language: currentLanguage, requestTranslation: currentLanguage === 'de' ? false : true }, () => {} ); } checkCacheValidity() { const categories = ["Zelte", "Lampen", "Abluft-sets", "Set-zubehoer"]; const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; let needsUpdate = false; const newStatus = { ...this.state.categoryLoadStatus }; for (const categoryId of categories) { if (newStatus[categoryId] && !getCachedCategoryData(categoryId, currentLanguage)) { console.log(`Refetching ${categoryId} due to cache miss/expiry`); newStatus[categoryId] = false; needsUpdate = true; this.fetchCategoryData(categoryId); } } if (needsUpdate) { this.setState({ categoryLoadStatus: newStatus }); } } handleTentShapeSelect(shapeId) { this.setState({ selectedTentShape: shapeId }); } handleTentSizeSelect(tentId) { this.setState({ selectedTentSize: tentId }); } handleLightTypeSelect(lightId) { this.setState({ selectedLightType: lightId }); } handleVentilationSelect(ventilationId) { this.setState({ selectedVentilationType: ventilationId }); } handleExtraToggle(extraId) { const { selectedExtras } = this.state; const newSelectedExtras = selectedExtras.includes(extraId) ? selectedExtras.filter(id => id !== extraId) : [...selectedExtras, extraId]; this.setState({ selectedExtras: newSelectedExtras }); } handleAddToCart() { if (!window.cart) window.cart = []; // Collect components const setComponents = []; // Tent let selectedTent = null; const allShapes = ['60x60', '80x80', '100x100', '120x60']; for (const shape of allShapes) { const tents = this.getTentsForShape(shape); selectedTent = tents.find(t => t.id === this.state.selectedTentSize); if (selectedTent) { setComponents.push(selectedTent); break; } } // Light if (this.state.selectedLightType) { const availableLamps = this.getLampsForTentShape(this.state.selectedTentShape); const light = availableLamps.find(l => l.id === this.state.selectedLightType); if (light) setComponents.push(light); } // Ventilation if (this.state.selectedVentilationType) { const availableVentilation = this.getVentilationForTentShape(this.state.selectedTentShape); const ventilation = availableVentilation.find(v => v.id === this.state.selectedVentilationType); if (ventilation) setComponents.push(ventilation); } // Extras const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; const extrasData = getCachedCategoryData('Set-zubehoer', currentLanguage); if (extrasData && Array.isArray(extrasData.products)) { this.state.selectedExtras.forEach(extraId => { const extra = extrasData.products.find(e => e.id === extraId); if (extra) setComponents.push(extra); }); } // Now push to cart const totalPrice = this.state.totalPrice; const setName = this.props.t ? this.props.t("kitConfig.setName", { shape: this.state.selectedTentShape }) : `Growbox Set - ${this.state.selectedTentShape}`; window.cart.push({ id: null, name: setName, seoName: null, pictureList: null, price: totalPrice, fGrundPreis: null, cGrundEinheit: null, quantity: 1, weight: null, vat: 19, versandklasse: null, availableSupplier: 0, komponenten: setComponents, available: 1 }); window.dispatchEvent(new CustomEvent("cart")); } // Helper function to filter real tent products by shape filterTentsByShape(tentShape, products, attributes) { if (!products || !attributes || !tentShape) { return []; } // Parse the shape dimensions (e.g., '80x80' -> width: 80, depth: 80) const [widthStr, depthStr] = tentShape.split('x'); const targetWidth = parseInt(widthStr); const targetDepth = parseInt(depthStr); // Group attributes by product for efficient lookup const productAttributes = {}; attributes.forEach(attr => { if (!productAttributes[attr.kArtikel]) { productAttributes[attr.kArtikel] = {}; } productAttributes[attr.kArtikel][attr.cName] = attr.cWert; }); // Filter products that match the target dimensions const matchingProducts = products.filter(product => { const attrs = productAttributes[product.id]; if (!attrs) return false; // Check width (Breite) const widthAttr = attrs['Breite']; if (!widthAttr) return false; const widthMatch = widthAttr.match(/(\d+)cm breit/i); if (!widthMatch) return false; const productWidth = parseInt(widthMatch[1]); // Check depth (Tiefe) const depthAttr = attrs['Tiefe']; if (!depthAttr) return false; const depthMatch = depthAttr.match(/(\d+)cm tief/i); if (!depthMatch) return false; const productDepth = parseInt(depthMatch[1]); // Check if dimensions match const sizeMatch = productWidth === targetWidth && productDepth === targetDepth; // Check availability - only show products that are available for delivery // Following same logic as Content.js: available > 0 OR availableSupplier == 1 const isAvailable = (product.available > 0) || (product.availableSupplier === 1); return sizeMatch && isAvailable; }); return matchingProducts.map(product => { return product; }); // No sorting needed } // Filter lamps by tent size using "passend für Zelt" attribute filterLampsByTentSize(tentShape, products, attributes) { if (!products || !attributes || !tentShape) return []; // Group attributes by product ID and attribute name const attributesByProduct = {}; attributes.forEach(attr => { if (!attributesByProduct[attr.kArtikel]) { attributesByProduct[attr.kArtikel] = {}; } attributesByProduct[attr.kArtikel][attr.cName] = attr.cWert; }); // Filter products by matching tent size in "passend für Zelt" attribute const matchingProducts = products.filter(product => { const attrs = attributesByProduct[product.id]; if (!attrs) return false; // Check "passend für Zelt" attribute const tentSizeAttr = attrs['passend für Zelt']; if (!tentSizeAttr) return false; // Check if tent size matches const sizeMatch = tentSizeAttr === tentShape; // Check availability - only show products that are available for delivery const isAvailable = (product.available > 0) || (product.availableSupplier === 1); return sizeMatch && isAvailable; }); return matchingProducts; } // Get lamps for selected tent shape getLampsForTentShape(shapeId) { const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; const cachedData = getCachedCategoryData('Lampen', currentLanguage); if (!cachedData || !cachedData.products || !cachedData.attributes) { return []; } return this.filterLampsByTentSize(shapeId, cachedData.products, cachedData.attributes); } // Get ventilation products for selected tent shape getVentilationForTentShape(shapeId) { const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; const cachedData = getCachedCategoryData('Abluft-sets', currentLanguage); if (!cachedData || !cachedData.products || !cachedData.attributes) { return []; } return this.filterVentilationByTentSize(shapeId, cachedData.products, cachedData.attributes); } // Filter ventilation by tent size using "passend für Zelt" attribute filterVentilationByTentSize(tentShape, products, attributes) { if (!products || !attributes || !tentShape) return []; // Group attributes by product ID and attribute name const attributesByProduct = {}; attributes.forEach(attr => { if (!attributesByProduct[attr.kArtikel]) { attributesByProduct[attr.kArtikel] = {}; } attributesByProduct[attr.kArtikel][attr.cName] = attr.cWert; }); // Filter products by matching tent size in "passend für Zelt" attribute // Include all size-matching products, but mark availability status const matchingProducts = products.filter(product => { const attrs = attributesByProduct[product.id]; if (!attrs) return false; // Check "passend für Zelt" attribute const tentSizeAttr = attrs['passend für Zelt']; if (!tentSizeAttr) return false; // Only filter by tent size match, not by availability const sizeMatch = tentSizeAttr === tentShape; return sizeMatch; }).map(product => { // Add availability flag to each product for UI rendering const isAvailable = (product.available > 0) || (product.availableSupplier === 1); return { ...product, isDeliverable: isAvailable }; }); return matchingProducts; } // Helper function to generate coverage descriptions getCoverageDescription(width, depth) { const { t } = this.props; const area = width * depth; if (area <= 3600) return t ? t('kitConfig.plants1to2') : '1-2 Pflanzen'; // 60x60 if (area <= 6400) return t ? t('kitConfig.plants2to4') : '2-4 Pflanzen'; // 80x80 if (area <= 10000) return t ? t('kitConfig.plants4to6') : '4-6 Pflanzen'; // 100x100 return t ? t('kitConfig.plants3to6') : '3-6 Pflanzen'; // 120x60 and larger } // Render tent image using working code from Product component renderTentImage(product) { if (!window.smallPicCache) { window.smallPicCache = {}; } const pictureList = product.pictureList; if (!pictureList || pictureList.length === 0 || !pictureList.split(',').length) { return ( ); } const bildId = pictureList.split(',')[0]; if (window.smallPicCache[bildId]) { return ( ); } // Load image if not cached if (!this.loadingImages) this.loadingImages = new Set(); if (!this.loadingImages.has(bildId)) { this.loadingImages.add(bildId); window.socketManager.emit('getPic', { bildId, size:'small' }, (res) => { if (res.success) { window.smallPicCache[bildId] = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/avif' })); this.forceUpdate(); } this.loadingImages.delete(bildId); }); } return ( ); } // Get real tent products for the selected shape getTentsForShape(shapeId) { const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; const cachedData = getCachedCategoryData('Zelte', currentLanguage); if (!cachedData || !cachedData.products || !cachedData.attributes) { return []; } return this.filterTentsByShape(shapeId, cachedData.products, cachedData.attributes); } calculateTotalPrice() { let total = 0; const { selectedTentSize, selectedLightType, selectedVentilationType, selectedExtras } = this.state; let itemCount = 0; // Add tent price if (selectedTentSize) { // Find the selected tent from all available shapes let selectedTent = null; const allShapes = ['60x60', '80x80', '100x100', '120x60']; for (const shape of allShapes) { const tents = this.getTentsForShape(shape); selectedTent = tents.find(t => t.id === selectedTentSize); if (selectedTent) break; } if (selectedTent) { total += selectedTent.price; itemCount++; } } // Add light price if (selectedLightType) { const availableLamps = this.getLampsForTentShape(this.state.selectedTentShape); const light = availableLamps.find(l => l.id === selectedLightType); if (light && light.price) { total += light.price; itemCount++; } } // Add ventilation price if (selectedVentilationType) { const availableVentilation = this.getVentilationForTentShape(this.state.selectedTentShape); const ventilation = availableVentilation.find(v => v.id === selectedVentilationType); if (ventilation && ventilation.price && ventilation.isDeliverable) { total += ventilation.price; itemCount++; } } const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; const extrasData = getCachedCategoryData('Set-zubehoer', currentLanguage); if (!extrasData || !Array.isArray(extrasData.products)) { console.warn('Extras data not available; skipping extras price in total calculation'); } else { // Add extras prices selectedExtras.forEach(extraId => { const extra = extrasData.products.find(e => e.id === extraId); if (extra) { total += extra.price; itemCount++; } }); } // Apply bundle discount let discountPercentage = 0; if (itemCount >= 3) discountPercentage = 15; // 15% for 3+ items if (itemCount >= 5) discountPercentage = 22; // 22% for 5+ items if (itemCount >= 7) discountPercentage = 28; // 28% for 7+ items const discountedTotal = total * (1 - discountPercentage / 100); this.setState({ totalPrice: discountedTotal }); } formatPrice(price) { return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(price); } calculateSavings() { // Bundle discount calculation const { selectedTentSize, selectedLightType, selectedVentilationType, selectedExtras } = this.state; let itemCount = 0; let originalTotal = 0; // Calculate original total without discount if (selectedTentSize) { // Find the selected tent from all available shapes let selectedTent = null; const allShapes = ['60x60', '80x80', '100x100', '120x60']; for (const shape of allShapes) { const tents = this.getTentsForShape(shape); selectedTent = tents.find(t => t.id === selectedTentSize); if (selectedTent) break; } if (selectedTent) { originalTotal += selectedTent.price; itemCount++; } } if (selectedLightType) { const availableLamps = this.getLampsForTentShape(this.state.selectedTentShape); const light = availableLamps.find(l => l.id === selectedLightType); if (light && light.price) { originalTotal += light.price; itemCount++; } } if (selectedVentilationType) { const availableVentilation = this.getVentilationForTentShape(this.state.selectedTentShape); const ventilation = availableVentilation.find(v => v.id === selectedVentilationType); if (ventilation && ventilation.price && ventilation.isDeliverable) { originalTotal += ventilation.price; itemCount++; } } const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; const extrasData = getCachedCategoryData('Set-zubehoer', currentLanguage); if (!extrasData || !Array.isArray(extrasData.products)) { console.warn('Extras data not available; skipping extras in savings calculation'); } else { selectedExtras.forEach(extraId => { const extra = extrasData.products.find(e => e.id === extraId); if (extra) { originalTotal += extra.price; itemCount++; } }); } // Progressive discount based on number of selected items let discountPercentage = 0; if (itemCount >= 3) discountPercentage = 15; // 15% for 3+ items if (itemCount >= 5) discountPercentage = 22; // 22% for 5+ items if (itemCount >= 7) discountPercentage = 28; // 28% for 7+ items const savings = originalTotal * (discountPercentage / 100); return { savings: savings, discountPercentage: discountPercentage, hasDiscount: discountPercentage > 0 }; } renderTentShapeSection() { const { selectedTentShape } = this.state; const { t } = this.props; return ( ); } renderTentSizeSection() { const { selectedTentShape } = this.state; const { t } = this.props; if (!selectedTentShape) { return null; // Don't show tent sizes until shape is selected } if (!this.state.categoryLoadStatus["Zelte"]) { return ( {t ? t("kitConfig.loadingProducts") : "Lade Growbox-Produkte..."} ); } // Get real filtered tent products for the selected shape const filteredTents = this.getTentsForShape(selectedTentShape); if (filteredTents.length === 0) { return ( {t ? t("kitConfig.noProductsAvailable") : "Keine Produkte für diese Größe verfügbar"} ); } return ( {t ? t("kitConfig.selectProductTitle") : "2. Growbox Produkt auswählen"} {t ? t("kitConfig.selectProductSubtitle", { shape: selectedTentShape }) : `Wähle das passende Produkt für deine ${selectedTentShape} Growbox`} {filteredTents.map((product, _index) => ( this.handleTentSizeSelect(product.id)}> {/* Image */} {this.renderTentImage(product)} {/* Content */} {/* Name */} {product.name} {product.kurzBeschreibung} {/* Price with VAT - Same as Product.js */} {product.price ? new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(product.price) : (t ? t("kitConfig.noPrice") : 'Kein Preis')} {product.vat && ( ({t ? t("product.inclVat", { vat: product.vat }) : `incl. ${product.vat}% MwSt.,*`}) )} {/* Selection Indicator - Separate line */} {this.state.selectedTentSize === product.id && ( {t ? t("kitConfig.selected") : "✓ Ausgewählt"} )} event.stopPropagation()} > ))} ); } renderLightSection() { const { selectedLightType, selectedTentShape } = this.state; const { t } = this.props; if (!selectedTentShape) { return ( {t ? t("kitConfig.selectLightingTitle") : "3. Beleuchtung wählen"} {t ? t("kitConfig.selectLightingSubtitle") : "Bitte wählen Sie zuerst eine Zeltgröße aus."} ); } if (!this.state.categoryLoadStatus["Lampen"]) { return ( {t ? t("kitConfig.selectLightingTitleShape", { shape: selectedTentShape }) : `3. Beleuchtung wählen - ${selectedTentShape}`} {t ? t("kitConfig.loadingLighting") : "Lade Beleuchtungs-Produkte..."} ); } const availableLamps = this.getLampsForTentShape(selectedTentShape); if (availableLamps.length === 0) { return ( {t ? t("kitConfig.selectLightingTitleShape", { shape: selectedTentShape }) : `3. Beleuchtung wählen - ${selectedTentShape}`} {t ? t("kitConfig.noLightingAvailable", { shape: selectedTentShape }) : `Keine passenden Lampen für Zeltgröße ${selectedTentShape} verfügbar.`} ); } return ( {t ? t("kitConfig.selectLightingTitleShape", { shape: selectedTentShape }) : `3. Beleuchtung wählen - ${selectedTentShape}`} {availableLamps.map((lamp) => ( this.handleLightTypeSelect(lamp.id)} > {/* Image */} {this.renderTentImage(lamp)} {/* Content */} {/* Name */} {lamp.name} {lamp.kurzBeschreibung} {/* Price with VAT */} {lamp.price ? new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(lamp.price) : (t ? t("kitConfig.noPrice") : 'Kein Preis')} {lamp.vat && ( ({t ? t("product.inclVat", { vat: lamp.vat }) : `incl. ${lamp.vat}% MwSt.,*`}) )} {/* Selection Indicator */} {selectedLightType === lamp.id && ( {t ? t("kitConfig.selected") : "✓ Ausgewählt"} )} event.stopPropagation()} > ))} ); } renderVentilationSection() { const { selectedVentilationType, selectedTentShape } = this.state; const { t } = this.props; if (!selectedTentShape) { return ( {t ? t("kitConfig.selectVentilationTitle") : "4. Belüftung auswählen"} {t ? t("kitConfig.selectVentilationSubtitle") : "Bitte wählen Sie zuerst eine Zeltgröße aus."} ); } if (!this.state.categoryLoadStatus["Abluft-sets"]) { return ( {t ? t("kitConfig.selectVentilationTitleShape", { shape: selectedTentShape }) : `4. Belüftung auswählen - ${selectedTentShape}`} {t ? t("kitConfig.loadingVentilation") : "Lade Belüftungs-Produkte..."} ); } const availableVentilation = this.getVentilationForTentShape(selectedTentShape); if (availableVentilation.length === 0) { return ( {t ? t("kitConfig.selectVentilationTitleShape", { shape: selectedTentShape }) : `4. Belüftung auswählen - ${selectedTentShape}`} {t ? t("kitConfig.noVentilationAvailable", { shape: selectedTentShape }) : `Keine passenden Belüftung für Zeltgröße ${selectedTentShape} verfügbar.`} ); } return ( {t ? t("kitConfig.selectVentilationTitleShape", { shape: selectedTentShape }) : `4. Belüftung auswählen - ${selectedTentShape}`} {availableVentilation.map((ventilation) => ( { if (ventilation.isDeliverable) { this.handleVentilationSelect(ventilation.id); } }} > {/* Non-deliverable overlay */} {!ventilation.isDeliverable && ( {t ? t("kitConfig.notDeliverable") : "Nicht lieferbar"} )} {/* Image */} {this.renderTentImage(ventilation)} {/* Content */} {/* Name */} {ventilation.name} {ventilation.kurzBeschreibung} {/* Price with VAT */} {ventilation.price ? new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(ventilation.price) : (t ? t("kitConfig.noPrice") : 'Kein Preis')} {ventilation.vat && ( ({t ? t("product.inclVat", { vat: ventilation.vat }) : `incl. ${ventilation.vat}% MwSt.,*`}) )} {/* Selection Indicator */} {selectedVentilationType === ventilation.id && ventilation.isDeliverable && ( {t ? t("kitConfig.selected") : "✓ Ausgewählt"} )} event.stopPropagation()} > ))} ); } renderExtrasSection() { const { selectedExtras } = this.state; const { t } = this.props; if (!this.state.categoryLoadStatus["Set-zubehoer"]) { return ( {t ? t("kitConfig.selectExtrasTitle") : "5. Extras hinzufügen (optional)"} {t ? t("kitConfig.loadingExtras") : "Lade Extras..."} ); } const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; const extrasData = getCachedCategoryData('Set-zubehoer', currentLanguage); if (!extrasData) { return ( {t ? t("kitConfig.selectExtrasTitle") : "5. Extras hinzufügen (optional)"} {t ? t("kitConfig.noExtrasAvailable") : "Keine Extras verfügbar"} ); } const extras = Array.isArray(extrasData.products) ? extrasData.products : []; if (extras.length === 0) { return ( {t ? t("kitConfig.selectExtrasTitle") : "5. Extras hinzufügen (optional)"} {t ? t("kitConfig.noExtrasAvailable") : "Keine Extras verfügbar"} ); } return ( ); } renderInlineSummary() { const { selectedTentSize, selectedLightType, selectedVentilationType, selectedExtras, totalPrice } = this.state; const { t } = this.props; // Find the selected tent from all available shapes let selectedTent = null; const allShapes = ['60x60', '80x80', '100x100', '120x60']; for (const shape of allShapes) { const tents = this.getTentsForShape(shape); selectedTent = tents.find(t => t.id === selectedTentSize); if (selectedTent) break; } const availableLamps = this.state.selectedTentShape ? this.getLampsForTentShape(this.state.selectedTentShape) : []; const selectedLight = availableLamps.find(l => l.id === selectedLightType); const availableVentilation = this.state.selectedTentShape ? this.getVentilationForTentShape(this.state.selectedTentShape) : []; const selectedVentilation = availableVentilation.find(v => v.id === selectedVentilationType && v.isDeliverable); const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; const extrasData = getCachedCategoryData('Set-zubehoer', currentLanguage); const selectedExtrasItems = Array.isArray(extrasData?.products) ? extrasData.products.filter(e => selectedExtras.includes(e.id)) : []; const savingsInfo = this.calculateSavings(); return ( {t ? t("kitConfig.yourConfiguration") : "🎯 Ihre Konfiguration"} {selectedTent && ( {this.formatPrice(selectedTent.price)} {selectedTent.vat && ( ({t ? t("product.inclVat", { vat: selectedTent.vat }) : `incl. ${selectedTent.vat}% MwSt.,*`}) )} )} {selectedLight && ( {this.formatPrice(selectedLight.price)} {selectedLight.vat && ( ({t ? t("product.inclVat", { vat: selectedLight.vat }) : `incl. ${selectedLight.vat}% MwSt.,*`}) )} )} {selectedVentilation && ( {this.formatPrice(selectedVentilation.price)} {selectedVentilation.vat && ( ({t ? t("product.inclVat", { vat: selectedVentilation.vat }) : `incl. ${selectedVentilation.vat}% MwSt.,*`}) )} )} {selectedExtrasItems.map(extra => ( {this.formatPrice(extra.price)} {extra.vat && ( ({t ? t("product.inclVat", { vat: extra.vat }) : `incl. ${extra.vat}% MwSt.,*`}) )} ))} {savingsInfo.hasDiscount && ( {t ? t("product.youSave", { amount: this.formatPrice(savingsInfo.savings) }) : `Sie sparen: ${this.formatPrice(savingsInfo.savings)}`} ({savingsInfo.discountPercentage}% Bundle-Rabatt) ({t ? t("product.inclVat", { vat: 19 }) : "incl. 19% MwSt.,*"}) )} {t ? t("kitConfig.totalPrice") : "Gesamtpreis:"} {this.formatPrice(totalPrice)} ({t ? t("product.inclVat", { vat: 19 }) : "incl. 19% MwSt.,*"}) ); } /* window.cart.push({ id: null, name: "Set 80x80", seoName: nnull, pictureList: null, price: total, fGrundPreis: null, cGrundEinheit: null, quantity: 1, weight: null, vat: vat, versandklasse: null, availableSupplier: null, komponenten: setComponents, available: null }); */ render() { const { t } = this.props; return ( {t ? t("kitConfig.pageTitle") : "🌱 Growbox Konfigurator"} {t ? t("kitConfig.pageSubtitle") : "Stelle dein perfektes Indoor Grow Setup zusammen"} {/* Bundle Discount Information */} {t ? t("kitConfig.bundleDiscountTitle") : "🎯 Bundle-Rabatt sichern!"} 15% {/* Note: Translation key would be: product.discount.from3Products */} {t ? t("product.discount.from3Products") : "ab 3 Produkten"} 22% {/* Note: Translation key would be: product.discount.from5Products */} {t ? t("product.discount.from5Products") : "ab 5 Produkten"} 28% {/* Note: Translation key would be: product.discount.from7Products */} {t ? t("product.discount.from7Products") : "ab 7 Produkten"} {/* Note: Translation key would be: product.discount.moreProductsMoreSavings */} {t ? t("product.discount.moreProductsMoreSavings") : "Je mehr Produkte du auswählst, desto mehr sparst du!"} {this.renderTentShapeSection()} {this.renderTentSizeSection()} {this.state.selectedTentShape && } {this.renderLightSection()} {this.renderVentilationSection()} {this.renderExtrasSection()} {/* Inline summary section - expands when scrolling to bottom */} {this.renderInlineSummary()} ); } } export default withI18n()(GrowTentKonfigurator);