feat: enhance GrowTentKonfigurator with tent filtering and improved rendering
- Added helper functions to filter tent products by shape and generate coverage descriptions based on dimensions. - Implemented logic to handle product image rendering with caching and loading states. - Updated tent selection process to dynamically find and display products based on selected tent shape. - Enhanced user interface with loading indicators and improved layout for product selection.
This commit is contained in:
@@ -10,10 +10,13 @@ import {
|
||||
ListItem,
|
||||
ListItemText,
|
||||
ListItemSecondaryAction,
|
||||
Grid,
|
||||
CardMedia,
|
||||
CircularProgress,
|
||||
} from '@mui/material';
|
||||
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
|
||||
import { TentShapeSelector, ProductSelector, ExtrasSelector } from '../components/configurator/index.js';
|
||||
import { tentShapes, tentSizes, lightTypes, ventilationTypes, extras } from '../data/configuratorData.js';
|
||||
import { tentShapes, lightTypes, ventilationTypes, extras } from '../data/configuratorData.js';
|
||||
|
||||
function setCachedCategoryData(categoryId, data) {
|
||||
if (!window.productCache) {
|
||||
@@ -143,12 +146,19 @@ class GrowTentKonfigurator extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`productList:${categoryId}`);
|
||||
window.socketManager.off(`productList:${categoryId}`);
|
||||
|
||||
// Track if we've received the full response to ignore stub response if needed
|
||||
let receivedFullResponse = false;
|
||||
|
||||
window.socketManager.on(`productList:${categoryId}`,(response) => {
|
||||
console.log("getCategoryProducts full response", response);
|
||||
receivedFullResponse = true;
|
||||
setCachedCategoryData(categoryId, response);
|
||||
|
||||
// Force re-render when data arrives
|
||||
if (categoryId === 'Zelte') {
|
||||
this.forceUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de';
|
||||
@@ -156,7 +166,13 @@ class GrowTentKonfigurator extends Component {
|
||||
"getCategoryProducts",
|
||||
{ categoryId: categoryId, language: currentLanguage, requestTranslation: currentLanguage === 'de' ? false : true },
|
||||
(response) => {
|
||||
console.log("getCategoryProducts stub response", response);
|
||||
// Only process stub response if we haven't received the full response yet
|
||||
if (!receivedFullResponse) {
|
||||
setCachedCategoryData(categoryId, response);
|
||||
if (categoryId === 'Zelte') {
|
||||
this.forceUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -188,6 +204,133 @@ class GrowTentKonfigurator extends Component {
|
||||
|
||||
|
||||
|
||||
// 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
|
||||
return productWidth === targetWidth && productDepth === targetDepth;
|
||||
});
|
||||
|
||||
return matchingProducts.map(product => {
|
||||
// Convert to the format expected by the configurator
|
||||
console.log('Raw product from backend:', product);
|
||||
return product;
|
||||
}); // No sorting needed
|
||||
}
|
||||
|
||||
// Helper function to generate coverage descriptions
|
||||
getCoverageDescription(width, depth) {
|
||||
const area = width * depth;
|
||||
if (area <= 3600) return '1-2 Pflanzen'; // 60x60
|
||||
if (area <= 6400) return '2-4 Pflanzen'; // 80x80
|
||||
if (area <= 10000) return '4-6 Pflanzen'; // 100x100
|
||||
return '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/jpeg' }));
|
||||
this.forceUpdate();
|
||||
}
|
||||
this.loadingImages.delete(bildId);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<CircularProgress sx={{ color: '#90ffc0' }} />
|
||||
);
|
||||
}
|
||||
|
||||
// Get real tent products for the selected shape
|
||||
getTentsForShape(shapeId) {
|
||||
const cachedData = getCachedCategoryData('Zelte');
|
||||
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;
|
||||
@@ -195,9 +338,17 @@ class GrowTentKonfigurator extends Component {
|
||||
|
||||
// Add tent price
|
||||
if (selectedTentSize) {
|
||||
const tent = tentSizes.find(t => t.id === selectedTentSize);
|
||||
if (tent) {
|
||||
total += tent.price;
|
||||
// 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++;
|
||||
}
|
||||
}
|
||||
@@ -260,9 +411,17 @@ class GrowTentKonfigurator extends Component {
|
||||
|
||||
// Calculate original total without discount
|
||||
if (selectedTentSize) {
|
||||
const tent = tentSizes.find(t => t.id === selectedTentSize);
|
||||
if (tent) {
|
||||
originalTotal += tent.price;
|
||||
// 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++;
|
||||
}
|
||||
}
|
||||
@@ -321,25 +480,143 @@ class GrowTentKonfigurator extends Component {
|
||||
}
|
||||
|
||||
renderTentSizeSection() {
|
||||
const { selectedTentSize, selectedTentShape } = this.state;
|
||||
|
||||
// Filter tents by selected shape
|
||||
const filteredTents = tentSizes.filter(tent => tent.shapeId === selectedTentShape);
|
||||
const { selectedTentShape } = this.state;
|
||||
|
||||
if (!selectedTentShape) {
|
||||
return null; // Don't show tent sizes until shape is selected
|
||||
}
|
||||
|
||||
// Get real filtered tent products for the selected shape
|
||||
const filteredTents = this.getTentsForShape(selectedTentShape);
|
||||
|
||||
// Show loading state if data is not yet available
|
||||
if (filteredTents.length === 0) {
|
||||
const cachedData = getCachedCategoryData('Zelte');
|
||||
if (!cachedData) {
|
||||
return (
|
||||
<ProductSelector
|
||||
products={filteredTents}
|
||||
selectedValue={selectedTentSize}
|
||||
onSelect={this.handleTentSizeSelect}
|
||||
productType="tent"
|
||||
title="2. Growbox Produkt auswählen"
|
||||
subtitle={`Wähle das passende Produkt für deine ${selectedTentShape} Growbox`}
|
||||
gridSize={{ xs: 12, sm: 6, md: 3 }}
|
||||
/>
|
||||
<Box sx={{ textAlign: 'center', py: 4 }}>
|
||||
<Typography variant="h6" color="text.secondary">
|
||||
Lade Growbox-Produkte...
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
// If we have cached data but no filtered tents, show empty state
|
||||
return (
|
||||
<Box sx={{ textAlign: 'center', py: 4 }}>
|
||||
<Typography variant="h6" color="text.secondary">
|
||||
Keine Produkte für diese Größe verfügbar
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
console.log('Product display:', {
|
||||
productsCount: filteredTents.length,
|
||||
firstProduct: filteredTents[0] || null
|
||||
});
|
||||
|
||||
if (filteredTents.length === 0) {
|
||||
return (
|
||||
<Box sx={{ textAlign: 'center', py: 4 }}>
|
||||
<Typography variant="h6" color="text.secondary">
|
||||
Keine Produkte verfügbar
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ mb: 4 }}>
|
||||
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
|
||||
2. Growbox Produkt auswählen
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
|
||||
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',
|
||||
boxShadow: '0px 2px 8px rgba(0,0,0,0.1)',
|
||||
overflow: 'hidden',
|
||||
backgroundColor: '#ffffff',
|
||||
cursor: 'pointer',
|
||||
border: '2px solid',
|
||||
borderColor: this.state.selectedTentSize === product.id ? '#2e7d32' : '#e0e0e0',
|
||||
'&:hover': {
|
||||
borderColor: '#90caf9'
|
||||
}
|
||||
}}
|
||||
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"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
mb: 1,
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis'
|
||||
}}
|
||||
>
|
||||
{product.name}
|
||||
</Typography>
|
||||
|
||||
{/* Price */}
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
color: '#2e7d32',
|
||||
fontWeight: 'bold',
|
||||
mt: 'auto'
|
||||
}}
|
||||
>
|
||||
{product.price ? new Intl.NumberFormat('de-DE', {
|
||||
style: 'currency',
|
||||
currency: 'EUR'
|
||||
}).format(product.price) : 'Kein Preis'}
|
||||
</Typography>
|
||||
|
||||
{/* VAT */}
|
||||
<Typography variant="body2" sx={{ color: '#77aa77', fontSize: '0.75em' }}>
|
||||
(incl. 19% MwSt.,*)
|
||||
</Typography>
|
||||
|
||||
{/* Selection Indicator */}
|
||||
{this.state.selectedTentSize === product.id && (
|
||||
<Box sx={{ mt: 1, textAlign: 'center' }}>
|
||||
<Typography variant="body2" sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
|
||||
✓ Ausgewählt
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -391,7 +668,14 @@ class GrowTentKonfigurator extends Component {
|
||||
renderInlineSummary() {
|
||||
const { selectedTentSize, selectedLightType, selectedVentilationType, selectedExtras, totalPrice } = this.state;
|
||||
|
||||
const selectedTent = tentSizes.find(t => t.id === 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;
|
||||
}
|
||||
const selectedLight = lightTypes.find(l => l.id === selectedLightType);
|
||||
const selectedVentilation = ventilationTypes.find(v => v.id === selectedVentilationType);
|
||||
const selectedExtrasItems = extras.filter(e => selectedExtras.includes(e.id));
|
||||
|
||||
Reference in New Issue
Block a user