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:
sebseb7
2025-09-04 05:54:01 +02:00
parent 21ae00b3f7
commit 8862f0c6b8
2 changed files with 182 additions and 191 deletions

View File

@@ -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',

View File

@@ -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();