feat: integrate ventilation selection into GrowTentKonfigurator
- Removed unused imports related to ventilation types and updated the product selection logic to dynamically filter ventilation options based on the selected tent shape. - Implemented new methods to retrieve and filter available ventilation products, enhancing the user experience by ensuring only relevant options are displayed. - Updated the UI to reflect the selected tent shape and provide feedback when no matching ventilation products are available. - Improved overall rendering of the ventilation section with better styling and selection indicators.
This commit is contained in:
@@ -42,181 +42,6 @@ export const tentShapes = [
|
||||
}
|
||||
];
|
||||
|
||||
export const tentSizes = [
|
||||
// 60x60 tents
|
||||
{
|
||||
id: 'tent_60x60x140',
|
||||
name: 'Basic 140cm',
|
||||
description: 'Einsteigermodell',
|
||||
price: 89.99,
|
||||
image: '/assets/images/nopicture.jpg',
|
||||
dimensions: '60x60x140cm',
|
||||
coverage: '1-2 Pflanzen',
|
||||
shapeId: '60x60',
|
||||
height: 140
|
||||
},
|
||||
{
|
||||
id: 'tent_60x60x160',
|
||||
name: 'Premium 160cm',
|
||||
description: 'Mehr Höhe für größere Pflanzen',
|
||||
price: 109.99,
|
||||
image: '/assets/images/nopicture.jpg',
|
||||
dimensions: '60x60x160cm',
|
||||
coverage: '1-2 Pflanzen',
|
||||
shapeId: '60x60',
|
||||
height: 160
|
||||
},
|
||||
// 80x80 tents
|
||||
{
|
||||
id: 'tent_80x80x160',
|
||||
name: 'Standard 160cm',
|
||||
description: 'Beliebtes Mittelklasse-Modell',
|
||||
price: 129.99,
|
||||
image: '/assets/images/nopicture.jpg',
|
||||
dimensions: '80x80x160cm',
|
||||
coverage: '2-4 Pflanzen',
|
||||
shapeId: '80x80',
|
||||
height: 160
|
||||
},
|
||||
{
|
||||
id: 'tent_80x80x180',
|
||||
name: 'Pro 180cm',
|
||||
description: 'Extra Höhe für optimales Wachstum',
|
||||
price: 149.99,
|
||||
image: '/assets/images/nopicture.jpg',
|
||||
dimensions: '80x80x180cm',
|
||||
coverage: '2-4 Pflanzen',
|
||||
shapeId: '80x80',
|
||||
height: 180
|
||||
},
|
||||
// 100x100 tents
|
||||
{
|
||||
id: 'tent_100x100x180',
|
||||
name: 'Professional 180cm',
|
||||
description: 'Für anspruchsvolle Projekte',
|
||||
price: 189.99,
|
||||
image: '/assets/images/nopicture.jpg',
|
||||
dimensions: '100x100x180cm',
|
||||
coverage: '4-6 Pflanzen',
|
||||
shapeId: '100x100',
|
||||
height: 180
|
||||
},
|
||||
{
|
||||
id: 'tent_100x100x200',
|
||||
name: 'Expert 200cm',
|
||||
description: 'Maximum an Wuchshöhe',
|
||||
price: 219.99,
|
||||
image: '/assets/images/nopicture.jpg',
|
||||
dimensions: '100x100x200cm',
|
||||
coverage: '4-6 Pflanzen',
|
||||
shapeId: '100x100',
|
||||
height: 200
|
||||
},
|
||||
// 120x60 tents
|
||||
{
|
||||
id: 'tent_120x60x160',
|
||||
name: 'Rectangular 160cm',
|
||||
description: 'Platzsparend und effizient',
|
||||
price: 139.99,
|
||||
image: '/assets/images/nopicture.jpg',
|
||||
dimensions: '120x60x160cm',
|
||||
coverage: '3-6 Pflanzen',
|
||||
shapeId: '120x60',
|
||||
height: 160
|
||||
},
|
||||
{
|
||||
id: 'tent_120x60x180',
|
||||
name: 'Rectangular Pro 180cm',
|
||||
description: 'Optimale Raumausnutzung',
|
||||
price: 169.99,
|
||||
image: '/assets/images/nopicture.jpg',
|
||||
dimensions: '120x60x180cm',
|
||||
coverage: '3-6 Pflanzen',
|
||||
shapeId: '120x60',
|
||||
height: 180
|
||||
}
|
||||
];
|
||||
|
||||
export const lightTypes = [
|
||||
{
|
||||
id: 'led_quantum_board',
|
||||
name: 'LED Quantum Board',
|
||||
description: 'Energieeffizient, geringe Wärmeentwicklung',
|
||||
price: 159.99,
|
||||
image: '/assets/images/nopicture.jpg',
|
||||
wattage: '240W',
|
||||
coverage: 'Bis 100x100cm',
|
||||
spectrum: 'Vollspektrum',
|
||||
efficiency: 'Sehr hoch'
|
||||
},
|
||||
{
|
||||
id: 'led_cob',
|
||||
name: 'LED COB',
|
||||
description: 'Hochintensive COB-LEDs',
|
||||
price: 199.99,
|
||||
image: '/assets/images/nopicture.jpg',
|
||||
wattage: '300W',
|
||||
coverage: 'Bis 120x120cm',
|
||||
spectrum: 'Vollspektrum',
|
||||
efficiency: 'Hoch'
|
||||
},
|
||||
{
|
||||
id: 'hps_400w',
|
||||
name: 'HPS 400W',
|
||||
description: 'Bewährte Natriumdampflampe',
|
||||
price: 89.99,
|
||||
image: '/assets/images/nopicture.jpg',
|
||||
wattage: '400W',
|
||||
coverage: 'Bis 80x80cm',
|
||||
spectrum: 'Blüte-optimiert',
|
||||
efficiency: 'Mittel'
|
||||
},
|
||||
{
|
||||
id: 'cmh_315w',
|
||||
name: 'CMH 315W',
|
||||
description: 'Keramik-Metallhalogenid',
|
||||
price: 129.99,
|
||||
image: '/assets/images/nopicture.jpg',
|
||||
wattage: '315W',
|
||||
coverage: 'Bis 90x90cm',
|
||||
spectrum: 'Natürlich',
|
||||
efficiency: 'Hoch'
|
||||
}
|
||||
];
|
||||
|
||||
export const ventilationTypes = [
|
||||
{
|
||||
id: 'basic_exhaust',
|
||||
name: 'Basic Abluft-Set',
|
||||
description: 'Lüfter + Aktivkohlefilter',
|
||||
price: 79.99,
|
||||
image: '/assets/images/nopicture.jpg',
|
||||
airflow: '187 m³/h',
|
||||
noiseLevel: '35 dB',
|
||||
includes: ['Rohrventilator', 'Aktivkohlefilter', 'Aluflexrohr']
|
||||
},
|
||||
{
|
||||
id: 'premium_ventilation',
|
||||
name: 'Premium Klima-Set',
|
||||
description: 'Komplette Klimakontrolle',
|
||||
price: 159.99,
|
||||
image: '/assets/images/nopicture.jpg',
|
||||
airflow: '280 m³/h',
|
||||
noiseLevel: '28 dB',
|
||||
includes: ['EC-Lüfter', 'Aktivkohlefilter', 'Thermostat', 'Feuchtigkeitsmesser']
|
||||
},
|
||||
{
|
||||
id: 'pro_climate',
|
||||
name: 'Profi Klima-System',
|
||||
description: 'Automatisierte Klimasteuerung',
|
||||
price: 299.99,
|
||||
image: '/assets/images/nopicture.jpg',
|
||||
airflow: '420 m³/h',
|
||||
noiseLevel: '25 dB',
|
||||
includes: ['Digitaler Controller', 'EC-Lüfter', 'Aktivkohlefilter', 'Zu-/Abluft']
|
||||
}
|
||||
];
|
||||
|
||||
export const extras = [
|
||||
{
|
||||
id: 'ph_tester',
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
CircularProgress,
|
||||
} from '@mui/material';
|
||||
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
|
||||
import { TentShapeSelector, ProductSelector, ExtrasSelector } from '../components/configurator/index.js';
|
||||
import { tentShapes, ventilationTypes, extras } from '../data/configuratorData.js';
|
||||
import { TentShapeSelector, ExtrasSelector } from '../components/configurator/index.js';
|
||||
import { tentShapes, extras } from '../data/configuratorData.js';
|
||||
|
||||
function setCachedCategoryData(categoryId, data) {
|
||||
if (!window.productCache) {
|
||||
@@ -312,6 +312,58 @@ class GrowTentKonfigurator extends Component {
|
||||
return this.filterLampsByTentSize(shapeId, cachedData.products, cachedData.attributes);
|
||||
}
|
||||
|
||||
// Get ventilation products for selected tent shape
|
||||
getVentilationForTentShape(shapeId) {
|
||||
const cachedData = getCachedCategoryData('Abluft-sets');
|
||||
if (!cachedData || !cachedData.products || !cachedData.attributes) {
|
||||
console.log('No cached ventilation data available');
|
||||
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 [];
|
||||
|
||||
console.log('Filtering ventilation for tent shape:', tentShape);
|
||||
console.log('Available ventilation products:', products.length);
|
||||
console.log('Available ventilation attributes:', attributes.length);
|
||||
|
||||
// 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);
|
||||
|
||||
console.log(`Ventilation Product ${product.id}: tentSize=${tentSizeAttr}, match=${sizeMatch}, available=${isAvailable}`);
|
||||
|
||||
return sizeMatch && isAvailable;
|
||||
});
|
||||
|
||||
console.log('Filtered ventilation:', matchingProducts.length);
|
||||
return matchingProducts;
|
||||
}
|
||||
|
||||
// Helper function to generate coverage descriptions
|
||||
getCoverageDescription(width, depth) {
|
||||
const area = width * depth;
|
||||
@@ -423,8 +475,9 @@ class GrowTentKonfigurator extends Component {
|
||||
|
||||
// Add ventilation price
|
||||
if (selectedVentilationType) {
|
||||
const ventilation = ventilationTypes.find(v => v.id === selectedVentilationType);
|
||||
if (ventilation) {
|
||||
const availableVentilation = this.getVentilationForTentShape(this.state.selectedTentShape);
|
||||
const ventilation = availableVentilation.find(v => v.id === selectedVentilationType);
|
||||
if (ventilation && ventilation.price) {
|
||||
total += ventilation.price;
|
||||
itemCount++;
|
||||
}
|
||||
@@ -495,8 +548,9 @@ class GrowTentKonfigurator extends Component {
|
||||
}
|
||||
|
||||
if (selectedVentilationType) {
|
||||
const ventilation = ventilationTypes.find(v => v.id === selectedVentilationType);
|
||||
if (ventilation) {
|
||||
const availableVentilation = this.getVentilationForTentShape(this.state.selectedTentShape);
|
||||
const ventilation = availableVentilation.find(v => v.id === selectedVentilationType);
|
||||
if (ventilation && ventilation.price) {
|
||||
originalTotal += ventilation.price;
|
||||
itemCount++;
|
||||
}
|
||||
@@ -801,17 +855,128 @@ class GrowTentKonfigurator extends Component {
|
||||
}
|
||||
|
||||
renderVentilationSection() {
|
||||
const { selectedVentilationType } = this.state;
|
||||
const { selectedVentilationType, selectedTentShape } = this.state;
|
||||
|
||||
if (!selectedTentShape) {
|
||||
return (
|
||||
<Box sx={{ mt: 4 }}>
|
||||
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
|
||||
4. Belüftung auswählen
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Bitte wählen Sie zuerst eine Zeltgröße aus.
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Get real ventilation products for selected tent shape
|
||||
const availableVentilation = this.getVentilationForTentShape(selectedTentShape);
|
||||
const cachedData = getCachedCategoryData('Abluft-sets');
|
||||
|
||||
if (!cachedData) {
|
||||
return (
|
||||
<Box sx={{ mt: 4 }}>
|
||||
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
|
||||
4. Belüftung auswählen - {selectedTentShape}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Lade Belüftungs-Produkte...
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (availableVentilation.length === 0) {
|
||||
return (
|
||||
<Box sx={{ mt: 4 }}>
|
||||
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
|
||||
4. Belüftung auswählen - {selectedTentShape}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Keine passenden Belüftung für Zeltgröße {selectedTentShape} verfügbar.
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ProductSelector
|
||||
products={ventilationTypes}
|
||||
selectedValue={selectedVentilationType}
|
||||
onSelect={this.handleVentilationSelect}
|
||||
productType="ventilation"
|
||||
title="4. Belüftung auswählen"
|
||||
gridSize={{ xs: 12, md: 4 }}
|
||||
/>
|
||||
<Box sx={{ mt: 4 }}>
|
||||
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
|
||||
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: 'pointer',
|
||||
border: '2px solid',
|
||||
borderColor: selectedVentilationType === ventilation.id ? '#2e7d32' : '#e0e0e0',
|
||||
backgroundColor: selectedVentilationType === ventilation.id ? '#f1f8e9' : '#ffffff',
|
||||
'&:hover': {
|
||||
boxShadow: 6,
|
||||
borderColor: '#2e7d32'
|
||||
},
|
||||
transition: 'all 0.3s ease'
|
||||
}}
|
||||
onClick={() => this.handleVentilationSelect(ventilation.id)}
|
||||
>
|
||||
{/* 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' }}>
|
||||
{ventilation.name}
|
||||
</Typography>
|
||||
|
||||
{/* Price with VAT */}
|
||||
<Typography variant="h6" sx={{
|
||||
color: '#2e7d32',
|
||||
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) : 'Kein Preis'}</span>
|
||||
{ventilation.vat && (
|
||||
<small style={{ color: '#77aa77', fontSize: '0.6em' }}>
|
||||
(incl. {ventilation.vat}% MwSt.,*)
|
||||
</small>
|
||||
)}
|
||||
</Typography>
|
||||
|
||||
{/* Selection Indicator */}
|
||||
{selectedVentilationType === ventilation.id && (
|
||||
<Typography variant="body2" sx={{
|
||||
color: '#2e7d32',
|
||||
fontWeight: 'bold',
|
||||
mt: 1,
|
||||
textAlign: 'center'
|
||||
}}>
|
||||
✓ Ausgewählt
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -843,7 +1008,8 @@ class GrowTentKonfigurator extends Component {
|
||||
}
|
||||
const availableLamps = this.state.selectedTentShape ? this.getLampsForTentShape(this.state.selectedTentShape) : [];
|
||||
const selectedLight = availableLamps.find(l => l.id === selectedLightType);
|
||||
const selectedVentilation = ventilationTypes.find(v => v.id === selectedVentilationType);
|
||||
const availableVentilation = this.state.selectedTentShape ? this.getVentilationForTentShape(this.state.selectedTentShape) : [];
|
||||
const selectedVentilation = availableVentilation.find(v => v.id === selectedVentilationType);
|
||||
const selectedExtrasItems = extras.filter(e => selectedExtras.includes(e.id));
|
||||
const savingsInfo = this.calculateSavings();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user