Files
reactShop/src/pages/GrowTentKonfigurator.js
sebseb7 a08c90a521 fix
2025-11-22 10:12:41 +01:00

1505 lines
57 KiB
JavaScript

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 (
<CardMedia
component="img"
height={window.innerWidth < 600 ? "240" : "180"}
image="/assets/images/nopicture.jpg"
alt={product.name || 'Produktbild'}
sx={{
objectFit: 'contain',
width: '100%'
}}
/>
);
}
const bildId = pictureList.split(',')[0];
if (window.smallPicCache[bildId]) {
return (
<CardMedia
component="img"
height={window.innerWidth < 600 ? "240" : "180"}
image={window.smallPicCache[bildId]}
alt={product.name || 'Produktbild'}
sx={{
objectFit: 'contain',
width: '100%'
}}
/>
);
}
// 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 (
<CircularProgress sx={{ color: '#90ffc0' }} />
);
}
// 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 (
<TentShapeSelector
tentShapes={tentShapes}
selectedShape={selectedTentShape}
onShapeSelect={this.handleTentShapeSelect}
title={t ? t("kitConfig.selectShapeTitle") : "1. Growbox-Form auswählen"}
subtitle={t ? t("kitConfig.selectShapeSubtitle") : "Wähle zuerst die Grundfläche deiner Growbox aus"}
/>
);
}
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 (
<Box sx={{ textAlign: 'center', py: 4 }}>
<Typography variant="h6" color="text.secondary">
{t ? t("kitConfig.loadingProducts") : "Lade Growbox-Produkte..."}
</Typography>
</Box>
);
}
// Get real filtered tent products for the selected shape
const filteredTents = this.getTentsForShape(selectedTentShape);
if (filteredTents.length === 0) {
return (
<Box sx={{ textAlign: 'center', py: 4 }}>
<Typography variant="h6" color="text.secondary">
{t ? t("kitConfig.noProductsAvailable") : "Keine Produkte für diese Größe verfügbar"}
</Typography>
</Box>
);
}
return (
<Box sx={{ mb: 4 }}>
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
{t ? t("kitConfig.selectProductTitle") : "2. Growbox Produkt auswählen"}
</Typography>
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
{t ? t("kitConfig.selectProductSubtitle", { shape: selectedTentShape }) : `Wähle das passende Produkt für deine ${selectedTentShape} Growbox`}
</Typography>
<Grid container spacing={2}>
{filteredTents.map((product, _index) => (
<Grid item xs={12} sm={6} md={4} key={product.id}>
<Box sx={{
width: { xs: '100%', sm: '250px' },
height: '100%',
display: 'flex',
flexDirection: 'column',
borderRadius: '8px',
overflow: 'hidden',
cursor: 'pointer',
border: '2px solid',
borderColor: this.state.selectedTentSize === product.id ? '#2e7d32' : '#e0e0e0',
backgroundColor: this.state.selectedTentSize === product.id ? '#f1f8e9' : '#ffffff',
'&:hover': {
boxShadow: 6,
borderColor: this.state.selectedTentSize === product.id ? '#2e7d32' : '#90caf9'
},
transition: 'all 0.3s ease'
}}
onClick={() => this.handleTentSizeSelect(product.id)}>
{/* Image */}
<Box sx={{
height: { xs: '240px', sm: '180px' },
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#ffffff'
}}>
{this.renderTentImage(product)}
</Box>
{/* Content */}
<Box sx={{ p: 2, flexGrow: 1, display: 'flex', flexDirection: 'column' }}>
{/* Name */}
<Typography variant="h6" gutterBottom sx={{ fontWeight: 'bold' }}>
{product.name}
</Typography>
<Typography gutterBottom>
{product.kurzBeschreibung}
</Typography>
{/* Price with VAT - Same as Product.js */}
<Typography variant="h6" sx={{
color: '#2e7d32',
fontWeight: 'bold',
mt: 2,
display: 'flex',
alignItems: 'center',
gap: 1,
flexWrap: 'wrap'
}}>
<span>{product.price ? new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR'
}).format(product.price) : (t ? t("kitConfig.noPrice") : 'Kein Preis')}</span>
{product.vat && (
<small style={{ color: '#77aa77', fontSize: '0.6em' }}>
({t ? t("product.inclVat", { vat: product.vat }) : `incl. ${product.vat}% MwSt.,*`})
</small>
)}
</Typography>
{/* Selection Indicator - Separate line */}
{this.state.selectedTentSize === product.id && (
<Typography variant="body2" sx={{
color: '#2e7d32',
fontWeight: 'bold',
mt: 1,
textAlign: 'center'
}}>
{t ? t("kitConfig.selected") : "✓ Ausgewählt"}
</Typography>
)}
<Stack direction="row" spacing={1} justifyContent="center">
<IconButton
component={Link}
to={`/Artikel/${product.seoName}`}
size="small"
aria-label="Produktdetails anzeigen"
sx={{ mr: 1, color: 'text.secondary' }}
onClick={(event) => event.stopPropagation()}
>
<ZoomInIcon />
</IconButton>
</Stack>
</Box>
</Box>
</Grid>
))}
</Grid>
</Box>
);
}
renderLightSection() {
const { selectedLightType, selectedTentShape } = this.state;
const { t } = this.props;
if (!selectedTentShape) {
return (
<Box sx={{ mt: 4 }}>
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
{t ? t("kitConfig.selectLightingTitle") : "3. Beleuchtung wählen"}
</Typography>
<Typography variant="body2" color="text.secondary">
{t ? t("kitConfig.selectLightingSubtitle") : "Bitte wählen Sie zuerst eine Zeltgröße aus."}
</Typography>
</Box>
);
}
if (!this.state.categoryLoadStatus["Lampen"]) {
return (
<Box sx={{ mt: 4 }}>
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
{t ? t("kitConfig.selectLightingTitleShape", { shape: selectedTentShape }) : `3. Beleuchtung wählen - ${selectedTentShape}`}
</Typography>
<Typography variant="body2" color="text.secondary">
{t ? t("kitConfig.loadingLighting") : "Lade Beleuchtungs-Produkte..."}
</Typography>
</Box>
);
}
const availableLamps = this.getLampsForTentShape(selectedTentShape);
if (availableLamps.length === 0) {
return (
<Box sx={{ mt: 4 }}>
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
{t ? t("kitConfig.selectLightingTitleShape", { shape: selectedTentShape }) : `3. Beleuchtung wählen - ${selectedTentShape}`}
</Typography>
<Typography variant="body2" color="text.secondary">
{t ? t("kitConfig.noLightingAvailable", { shape: selectedTentShape }) : `Keine passenden Lampen für Zeltgröße ${selectedTentShape} verfügbar.`}
</Typography>
</Box>
);
}
return (
<Box sx={{ mt: 4 }}>
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
{t ? t("kitConfig.selectLightingTitleShape", { shape: selectedTentShape }) : `3. Beleuchtung wählen - ${selectedTentShape}`}
</Typography>
<Grid container spacing={2}>
{availableLamps.map((lamp) => (
<Grid item xs={12} sm={6} md={4} key={lamp.id}>
<Box sx={{
width: { xs: '100%', sm: '250px' },
height: '100%',
display: 'flex',
flexDirection: 'column',
borderRadius: '8px',
overflow: 'hidden',
cursor: 'pointer',
border: '2px solid',
borderColor: selectedLightType === lamp.id ? '#2e7d32' : '#e0e0e0',
backgroundColor: selectedLightType === lamp.id ? '#f1f8e9' : '#ffffff',
'&:hover': {
boxShadow: 6,
borderColor: '#2e7d32'
},
transition: 'all 0.3s ease'
}}
onClick={() => this.handleLightTypeSelect(lamp.id)}
>
{/* Image */}
<Box sx={{ height: 200, display: 'flex', alignItems: 'center', justifyContent: 'center', overflow: 'hidden' }}>
{this.renderTentImage(lamp)}
</Box>
{/* Content */}
<Box sx={{ p: 2, flexGrow: 1, display: 'flex', flexDirection: 'column' }}>
{/* Name */}
<Typography variant="h6" gutterBottom sx={{ fontWeight: 'bold' }}>
{lamp.name}
</Typography>
<Typography gutterBottom>
{lamp.kurzBeschreibung}
</Typography>
{/* Price with VAT */}
<Typography variant="h6" sx={{
color: '#2e7d32',
fontWeight: 'bold',
mt: 2,
display: 'flex',
alignItems: 'center',
gap: 1,
flexWrap: 'wrap'
}}>
<span>{lamp.price ? new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR'
}).format(lamp.price) : (t ? t("kitConfig.noPrice") : 'Kein Preis')}</span>
{lamp.vat && (
<small style={{ color: '#77aa77', fontSize: '0.6em' }}>
({t ? t("product.inclVat", { vat: lamp.vat }) : `incl. ${lamp.vat}% MwSt.,*`})
</small>
)}
</Typography>
{/* Selection Indicator */}
{selectedLightType === lamp.id && (
<Typography variant="body2" sx={{
color: '#2e7d32',
fontWeight: 'bold',
mt: 1,
textAlign: 'center'
}}>
{t ? t("kitConfig.selected") : "✓ Ausgewählt"}
</Typography>
)}
<Stack direction="row" spacing={1} justifyContent="center">
<IconButton
component={Link}
to={`/Artikel/${lamp.seoName}`}
size="small"
aria-label="Produktdetails anzeigen"
sx={{ mr: 1, color: 'text.secondary' }}
onClick={(event) => event.stopPropagation()}
>
<ZoomInIcon />
</IconButton>
</Stack>
</Box>
</Box>
</Grid>
))}
</Grid>
</Box>
);
}
renderVentilationSection() {
const { selectedVentilationType, selectedTentShape } = this.state;
const { t } = this.props;
if (!selectedTentShape) {
return (
<Box sx={{ mt: 4 }}>
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
{t ? t("kitConfig.selectVentilationTitle") : "4. Belüftung auswählen"}
</Typography>
<Typography variant="body2" color="text.secondary">
{t ? t("kitConfig.selectVentilationSubtitle") : "Bitte wählen Sie zuerst eine Zeltgröße aus."}
</Typography>
</Box>
);
}
if (!this.state.categoryLoadStatus["Abluft-sets"]) {
return (
<Box sx={{ mt: 4 }}>
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
{t ? t("kitConfig.selectVentilationTitleShape", { shape: selectedTentShape }) : `4. Belüftung auswählen - ${selectedTentShape}`}
</Typography>
<Typography variant="body2" color="text.secondary">
{t ? t("kitConfig.loadingVentilation") : "Lade Belüftungs-Produkte..."}
</Typography>
</Box>
);
}
const availableVentilation = this.getVentilationForTentShape(selectedTentShape);
if (availableVentilation.length === 0) {
return (
<Box sx={{ mt: 4 }}>
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
{t ? t("kitConfig.selectVentilationTitleShape", { shape: selectedTentShape }) : `4. Belüftung auswählen - ${selectedTentShape}`}
</Typography>
<Typography variant="body2" color="text.secondary">
{t ? t("kitConfig.noVentilationAvailable", { shape: selectedTentShape }) : `Keine passenden Belüftung für Zeltgröße ${selectedTentShape} verfügbar.`}
</Typography>
</Box>
);
}
return (
<Box sx={{ mt: 4 }}>
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
{t ? t("kitConfig.selectVentilationTitleShape", { shape: selectedTentShape }) : `4. Belüftung auswählen - ${selectedTentShape}`}
</Typography>
<Grid container spacing={2}>
{availableVentilation.map((ventilation) => (
<Grid item xs={12} sm={6} md={4} key={ventilation.id}>
<Box sx={{
width: { xs: '100%', sm: '250px' },
height: '100%',
display: 'flex',
flexDirection: 'column',
borderRadius: '8px',
overflow: 'hidden',
cursor: ventilation.isDeliverable ? 'pointer' : 'not-allowed',
border: '2px solid',
borderColor: selectedVentilationType === ventilation.id ? '#2e7d32' : '#e0e0e0',
backgroundColor: selectedVentilationType === ventilation.id ? '#f1f8e9' : '#ffffff',
opacity: ventilation.isDeliverable ? 1 : 0.5,
filter: ventilation.isDeliverable ? 'none' : 'grayscale(50%)',
'&:hover': ventilation.isDeliverable ? {
boxShadow: 6,
borderColor: '#2e7d32'
} : {},
transition: 'all 0.3s ease',
position: 'relative'
}}
onClick={() => {
if (ventilation.isDeliverable) {
this.handleVentilationSelect(ventilation.id);
}
}}
>
{/* Non-deliverable overlay */}
{!ventilation.isDeliverable && (
<Box sx={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'rgba(255, 255, 255, 0.8)',
zIndex: 1,
flexDirection: 'column'
}}>
<Typography variant="h6" sx={{
color: '#d32f2f',
fontWeight: 'bold',
textAlign: 'center',
backgroundColor: 'rgba(255, 255, 255, 0.9)',
p: 1,
borderRadius: 1
}}>
{t ? t("kitConfig.notDeliverable") : "Nicht lieferbar"}
</Typography>
</Box>
)}
{/* Image */}
<Box sx={{ height: 200, display: 'flex', alignItems: 'center', justifyContent: 'center', overflow: 'hidden' }}>
{this.renderTentImage(ventilation)}
</Box>
{/* Content */}
<Box sx={{ p: 2, flexGrow: 1, display: 'flex', flexDirection: 'column' }}>
{/* Name */}
<Typography variant="h6" gutterBottom sx={{
fontWeight: 'bold',
color: ventilation.isDeliverable ? 'inherit' : '#999'
}}>
{ventilation.name}
</Typography>
<Typography gutterBottom>
{ventilation.kurzBeschreibung}
</Typography>
{/* Price with VAT */}
<Typography variant="h6" sx={{
color: ventilation.isDeliverable ? '#2e7d32' : '#999',
fontWeight: 'bold',
mt: 2,
display: 'flex',
alignItems: 'center',
gap: 1,
flexWrap: 'wrap'
}}>
<span>{ventilation.price ? new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR'
}).format(ventilation.price) : (t ? t("kitConfig.noPrice") : 'Kein Preis')}</span>
{ventilation.vat && (
<small style={{ color: ventilation.isDeliverable ? '#77aa77' : '#999', fontSize: '0.6em' }}>
({t ? t("product.inclVat", { vat: ventilation.vat }) : `incl. ${ventilation.vat}% MwSt.,*`})
</small>
)}
</Typography>
{/* Selection Indicator */}
{selectedVentilationType === ventilation.id && ventilation.isDeliverable && (
<Typography variant="body2" sx={{
color: '#2e7d32',
fontWeight: 'bold',
mt: 1,
textAlign: 'center'
}}>
{t ? t("kitConfig.selected") : "✓ Ausgewählt"}
</Typography>
)}
<Stack direction="row" spacing={1} justifyContent="center">
<IconButton
component={Link}
to={`/Artikel/${ventilation.seoName}`}
size="small"
aria-label="Produktdetails anzeigen"
sx={{ mr: 1, color: 'text.secondary' }}
onClick={(event) => event.stopPropagation()}
>
<ZoomInIcon />
</IconButton>
</Stack>
</Box>
</Box>
</Grid>
))}
</Grid>
</Box>
);
}
renderExtrasSection() {
const { selectedExtras } = this.state;
const { t } = this.props;
if (!this.state.categoryLoadStatus["Set-zubehoer"]) {
return (
<Box sx={{ mb: 4 }}>
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
{t ? t("kitConfig.selectExtrasTitle") : "5. Extras hinzufügen (optional)"}
</Typography>
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
{t ? t("kitConfig.loadingExtras") : "Lade Extras..."}
</Typography>
</Box>
);
}
const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de';
const extrasData = getCachedCategoryData('Set-zubehoer', currentLanguage);
if (!extrasData) {
return (
<Box sx={{ mb: 4 }}>
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
{t ? t("kitConfig.selectExtrasTitle") : "5. Extras hinzufügen (optional)"}
</Typography>
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
{t ? t("kitConfig.noExtrasAvailable") : "Keine Extras verfügbar"}
</Typography>
</Box>
);
}
const extras = Array.isArray(extrasData.products) ? extrasData.products : [];
if (extras.length === 0) {
return (
<Box sx={{ mb: 4 }}>
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
{t ? t("kitConfig.selectExtrasTitle") : "5. Extras hinzufügen (optional)"}
</Typography>
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
{t ? t("kitConfig.noExtrasAvailable") : "Keine Extras verfügbar"}
</Typography>
</Box>
);
}
return (
<ExtrasSelector
extras={extras}
selectedExtras={selectedExtras}
onExtraToggle={this.handleExtraToggle}
title={t ? t("kitConfig.selectExtrasTitle") : "5. Extras hinzufügen (optional)"}
gridSize={{ xs: 12, sm: 6, md: 4 }}
/>
);
}
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 (
<Paper
id="inline-summary" // @note Add ID for scroll targeting
elevation={2}
sx={{
mt: 4,
p: 3,
bgcolor: '#f8f9fa',
border: '2px solid #2e7d32',
borderRadius: 2
}}
>
<Typography variant="h5" sx={{ color: '#2e7d32', fontWeight: 'bold', mb: 3, textAlign: 'center' }}>
{t ? t("kitConfig.yourConfiguration") : "🎯 Ihre Konfiguration"}
</Typography>
<List sx={{ '& .MuiListItem-root': { py: 1 } }}>
{selectedTent && (
<ListItem>
<ListItemText
primary={t ? t("kitConfig.growboxLabel", { name: selectedTent.name }) : `Growbox: ${selectedTent.name}`}
/>
<ListItemSecondaryAction>
<Typography variant="h6" sx={{ fontWeight: 'bold', display: 'flex', flexDirection: 'column', alignItems: 'flex-end' }}>
<span>{this.formatPrice(selectedTent.price)}</span>
{selectedTent.vat && (
<small style={{ color: '#77aa77', fontSize: '0.6em' }}>
({t ? t("product.inclVat", { vat: selectedTent.vat }) : `incl. ${selectedTent.vat}% MwSt.,*`})
</small>
)}
</Typography>
</ListItemSecondaryAction>
</ListItem>
)}
{selectedLight && (
<ListItem>
<ListItemText
primary={t ? t("kitConfig.lightingLabel", { name: selectedLight.name }) : `Beleuchtung: ${selectedLight.name}`}
/>
<ListItemSecondaryAction>
<Typography variant="h6" sx={{ fontWeight: 'bold', display: 'flex', flexDirection: 'column', alignItems: 'flex-end' }}>
<span>{this.formatPrice(selectedLight.price)}</span>
{selectedLight.vat && (
<small style={{ color: '#77aa77', fontSize: '0.6em' }}>
({t ? t("product.inclVat", { vat: selectedLight.vat }) : `incl. ${selectedLight.vat}% MwSt.,*`})
</small>
)}
</Typography>
</ListItemSecondaryAction>
</ListItem>
)}
{selectedVentilation && (
<ListItem>
<ListItemText
primary={t ? t("kitConfig.ventilationLabel", { name: selectedVentilation.name }) : `Belüftung: ${selectedVentilation.name}`}
/>
<ListItemSecondaryAction>
<Typography variant="h6" sx={{ fontWeight: 'bold', display: 'flex', flexDirection: 'column', alignItems: 'flex-end' }}>
<span>{this.formatPrice(selectedVentilation.price)}</span>
{selectedVentilation.vat && (
<small style={{ color: '#77aa77', fontSize: '0.6em' }}>
({t ? t("product.inclVat", { vat: selectedVentilation.vat }) : `incl. ${selectedVentilation.vat}% MwSt.,*`})
</small>
)}
</Typography>
</ListItemSecondaryAction>
</ListItem>
)}
{selectedExtrasItems.map(extra => (
<ListItem key={extra.id}>
<ListItemText
primary={t ? t("kitConfig.extraLabel", { name: extra.name }) : `Extra: ${extra.name}`}
/>
<ListItemSecondaryAction>
<Typography variant="h6" sx={{ fontWeight: 'bold', display: 'flex', flexDirection: 'column', alignItems: 'flex-end' }}>
<span>{this.formatPrice(extra.price)}</span>
{extra.vat && (
<small style={{ color: '#77aa77', fontSize: '0.6em' }}>
({t ? t("product.inclVat", { vat: extra.vat }) : `incl. ${extra.vat}% MwSt.,*`})
</small>
)}
</Typography>
</ListItemSecondaryAction>
</ListItem>
))}
</List>
<Divider sx={{ my: 3 }} />
{savingsInfo.hasDiscount && (
<Box sx={{ mb: 2, textAlign: 'center' }}>
<Typography variant="h6" sx={{ color: '#d32f2f', fontWeight: 'bold' }}>
{t ? t("product.youSave", { amount: this.formatPrice(savingsInfo.savings) }) : `Sie sparen: ${this.formatPrice(savingsInfo.savings)}`} ({savingsInfo.discountPercentage}% Bundle-Rabatt)
</Typography>
<Typography variant="caption" sx={{ color: '#77aa77' }}>
({t ? t("product.inclVat", { vat: 19 }) : "incl. 19% MwSt.,*"})
</Typography>
</Box>
)}
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
<Typography variant="h5" sx={{ fontWeight: 'bold' }}>
{t ? t("kitConfig.totalPrice") : "Gesamtpreis:"}
</Typography>
<Box sx={{ textAlign: 'right' }}>
<Typography variant="h5" sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
{this.formatPrice(totalPrice)}
</Typography>
<Typography variant="caption" sx={{ color: '#77aa77' }}>
({t ? t("product.inclVat", { vat: 19 }) : "incl. 19% MwSt.,*"})
</Typography>
</Box>
</Box>
<Box sx={{ display: 'flex', gap: 2, justifyContent: 'center' }}>
<Button
variant="contained"
size="large"
onClick={this.handleAddToCart}
startIcon={<ShoppingCartIcon />}
sx={{
bgcolor: '#2e7d32',
'&:hover': { bgcolor: '#1b5e20' },
minWidth: 250
}}
>
{t ? t("kitConfig.addToCart") : "In den Warenkorb"}
</Button>
</Box>
</Paper>
);
}
/*
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 (
<Container maxWidth="lg" sx={{ py: 4 }}>
<Paper elevation={2} sx={{ p: 4, borderRadius: 2 }}>
<Box sx={{ textAlign: 'center', mb: 4 }}>
<Typography variant="h3" component="h1" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
{t ? t("kitConfig.pageTitle") : "🌱 Growbox Konfigurator"}
</Typography>
<Typography variant="h6" color="text.secondary">
{t ? t("kitConfig.pageSubtitle") : "Stelle dein perfektes Indoor Grow Setup zusammen"}
</Typography>
{/* Bundle Discount Information */}
<Paper
elevation={1}
sx={{
mt: 3,
p: 2,
bgcolor: '#f8f9fa',
border: '1px solid #e9ecef',
maxWidth: 600,
mx: 'auto'
}}
>
<Typography variant="h6" sx={{ color: '#2e7d32', fontWeight: 'bold', mb: 2 }}>
{t ? t("kitConfig.bundleDiscountTitle") : "🎯 Bundle-Rabatt sichern!"}
</Typography>
<Box sx={{ display: 'flex', justifyContent: 'space-around', flexWrap: 'wrap', gap: 2 }}>
<Box sx={{ textAlign: 'center' }}>
<Typography variant="h6" sx={{ color: '#1976d2', fontWeight: 'bold' }}>
15%
</Typography>
<Typography variant="body2">
{/* Note: Translation key would be: product.discount.from3Products */}
{t ? t("product.discount.from3Products") : "ab 3 Produkten"}
</Typography>
</Box>
<Box sx={{ textAlign: 'center' }}>
<Typography variant="h6" sx={{ color: '#ed6c02', fontWeight: 'bold' }}>
22%
</Typography>
<Typography variant="body2">
{/* Note: Translation key would be: product.discount.from5Products */}
{t ? t("product.discount.from5Products") : "ab 5 Produkten"}
</Typography>
</Box>
<Box sx={{ textAlign: 'center' }}>
<Typography variant="h6" sx={{ color: '#d32f2f', fontWeight: 'bold' }}>
28%
</Typography>
<Typography variant="body2">
{/* Note: Translation key would be: product.discount.from7Products */}
{t ? t("product.discount.from7Products") : "ab 7 Produkten"}
</Typography>
</Box>
</Box>
<Typography variant="caption" sx={{ color: '#666', mt: 1, display: 'block' }}>
{/* Note: Translation key would be: product.discount.moreProductsMoreSavings */}
{t ? t("product.discount.moreProductsMoreSavings") : "Je mehr Produkte du auswählst, desto mehr sparst du!"}
</Typography>
</Paper>
</Box>
{this.renderTentShapeSection()}
<Divider sx={{ my: 4 }} />
{this.renderTentSizeSection()}
{this.state.selectedTentShape && <Divider sx={{ my: 4 }} />}
{this.renderLightSection()}
<Divider sx={{ my: 4 }} />
{this.renderVentilationSection()}
<Divider sx={{ my: 4 }} />
{this.renderExtrasSection()}
{/* Inline summary section - expands when scrolling to bottom */}
{this.renderInlineSummary()}
</Paper>
</Container>
);
}
}
export default withI18n()(GrowTentKonfigurator);