import React, { Component } from 'react'; import Box from '@mui/material/Box'; import Card from '@mui/material/Card'; import CardContent from '@mui/material/CardContent'; import CardMedia from '@mui/material/CardMedia'; import Typography from '@mui/material/Typography'; import CircularProgress from '@mui/material/CircularProgress'; import IconButton from '@mui/material/IconButton'; import AddToCartButton from './AddToCartButton.js'; import { Link, useNavigate } from 'react-router-dom'; import { withI18n } from '../i18n/withTranslation.js'; import ZoomInIcon from '@mui/icons-material/ZoomIn'; // Helper function to find level 1 category ID from any category ID const findLevel1CategoryId = (categoryId) => { try { const currentLanguage = 'de'; // Default to German const categoryTreeCache = window.categoryService?.getSync(209, currentLanguage); if (!categoryTreeCache || !categoryTreeCache.children) { return null; } // Helper function to find category by ID and get its level 1 parent const findCategoryAndLevel1 = (categories, targetId) => { for (const category of categories) { if (category.id === targetId) { // Found the category, now find its level 1 parent return findLevel1Parent(categoryTreeCache.children, category); } if (category.children && category.children.length > 0) { const result = findCategoryAndLevel1(category.children, targetId); if (result) return result; } } return null; }; // Helper function to find the level 1 parent (direct child of root category 209) const findLevel1Parent = (level1Categories, category) => { // If this category's parent is 209, it's already level 1 if (category.parentId === 209) { return category.id; } // Otherwise, find the parent and check if it's level 1 for (const level1Category of level1Categories) { if (level1Category.id === category.parentId) { return level1Category.id; } // If parent has children, search recursively if (level1Category.children && level1Category.children.length > 0) { const result = findLevel1Parent(level1Category.children, category); if (result) return result; } } return null; }; return findCategoryAndLevel1(categoryTreeCache.children, parseInt(categoryId)); } catch (error) { console.error('Error finding level 1 category:', error); return null; } }; class Product extends Component { constructor(props) { super(props); this._isMounted = false; if (!window.smallPicCache) { window.smallPicCache = {}; } const pictureIds = (this.props.pictureList && this.props.pictureList.length > 0) ? this.props.pictureList.split(',').filter(id => id.trim().length > 0) : []; if (pictureIds.length > 0) { const initialImages = pictureIds.map(id => window.smallPicCache[id] || null); const isFirstCached = !!initialImages[0]; this.state = { images: initialImages, currentImageIndex: 0, loading: !isFirstCached, error: false, isHovering: false }; pictureIds.forEach((id, index) => { if (!window.smallPicCache[id]) { this.loadImage(id, index); } }); } else { this.state = { images: [], currentImageIndex: 0, loading: false, error: false, isHovering: false }; } } componentDidMount() { this._isMounted = true; this.startRandomFading(); } startRandomFading = () => { if (this.state.isHovering) return; const pictureIds = (this.props.pictureList && this.props.pictureList.length > 0) ? this.props.pictureList.split(',').filter(id => id.trim().length > 0) : []; if (pictureIds.length > 1) { const minInterval = 4000; const maxInterval = 8000; const randomInterval = Math.floor(Math.random() * (maxInterval - minInterval + 1)) + minInterval; this.fadeTimeout = setTimeout(() => { if (this._isMounted) { this.setState(prevState => { let nextIndex = (prevState.currentImageIndex + 1) % pictureIds.length; let attempts = 0; while (!prevState.images[nextIndex] && attempts < pictureIds.length) { nextIndex = (nextIndex + 1) % pictureIds.length; attempts++; } if (attempts < pictureIds.length && nextIndex !== prevState.currentImageIndex) { return { currentImageIndex: nextIndex }; } return null; }, () => { this.startRandomFading(); }); } }, randomInterval); } } handleMouseMove = (e) => { const pictureIds = (this.props.pictureList && this.props.pictureList.length > 0) ? this.props.pictureList.split(',').filter(id => id.trim().length > 0) : []; if (pictureIds.length > 1) { if (this.fadeTimeout) { clearTimeout(this.fadeTimeout); this.fadeTimeout = null; } const { left, width } = e.currentTarget.getBoundingClientRect(); const x = e.clientX - left; const segmentWidth = width / pictureIds.length; let targetIndex = Math.floor(x / segmentWidth); if (targetIndex >= pictureIds.length) targetIndex = pictureIds.length - 1; if (targetIndex < 0) targetIndex = 0; if (this.state.currentImageIndex !== targetIndex) { if (this.state.images[targetIndex]) { this.setState({ currentImageIndex: targetIndex, isHovering: true }); } else { this.setState({ isHovering: true }); } } else if (!this.state.isHovering) { this.setState({ isHovering: true }); } } } handleMouseLeave = () => { if (this.state.isHovering) { this.setState({ isHovering: false }, () => { this.startRandomFading(); }); } } loadImage = (bildId, index) => { console.log('loadImagevisSocket', bildId); window.socketManager.emit('getPic', { bildId, size: 'small' }, (res) => { if (res.success) { window.smallPicCache[bildId] = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/avif' })); if (this._isMounted) { this.setState(prevState => { const newImages = [...prevState.images]; newImages[index] = window.smallPicCache[bildId]; return { images: newImages, loading: index === 0 ? false : prevState.loading }; }); } else { this.state.images[index] = window.smallPicCache[bildId]; if (index === 0) this.state.loading = false; } } else { console.log('Fehler beim Laden des Bildes:', res); if (this._isMounted) { this.setState(prevState => ({ error: index === 0 ? true : prevState.error, loading: index === 0 ? false : prevState.loading })); } else { if (index === 0) { this.state.error = true; this.state.loading = false; } } } }); } componentWillUnmount() { this._isMounted = false; if (this.fadeTimeout) { clearTimeout(this.fadeTimeout); } } handleQuantityChange = (quantity) => { console.log(`Product: ${this.props.name}, Quantity: ${quantity}`); // In a real app, this would update a cart state in a parent component or Redux store } handleProductClick = (e) => { e.preventDefault(); const { categoryId } = this.props; // Find the level 1 category for this product const level1CategoryId = categoryId ? findLevel1CategoryId(categoryId) : null; // Navigate to the product page WITH the category information in the state const navigate = this.props.navigate; if (navigate) { navigate(`/Artikel/${this.props.seoName}`, { state: { articleCategoryId: level1CategoryId } }); } } render() { const { id, name, price, available, manufacturer, seoName, currency, vat, cGrundEinheit, fGrundPreis, thc, floweringWeeks, incoming, neu, weight, versandklasse, availableSupplier, komponenten } = this.props; const isNew = neu && (new Date().getTime() - new Date(neu).getTime() < 30 * 24 * 60 * 60 * 1000); const showThcBadge = thc > 0; let thcBadgeColor = '#4caf50'; // Green default if (thc > 30) { thcBadgeColor = '#f44336'; // Red for > 30 } else if (thc > 25) { thcBadgeColor = '#ffeb3b'; // Yellow for > 25 } const showFloweringWeeksBadge = floweringWeeks > 0; let floweringWeeksBadgeColor = '#4caf50'; // Green default if (floweringWeeks > 12) { floweringWeeksBadgeColor = '#f44336'; // Red for > 12 } else if (floweringWeeks > 8) { floweringWeeksBadgeColor = '#ffeb3b'; // Yellow for > 8 } return ( {isNew && (
{/* Background star - slightly larger and rotated */} {/* Middle star - medium size with different rotation */} {/* Foreground star - main star with text */} {/* Text as a separate element to position it at the top */}
{this.props.t ? this.props.t('product.new') : 'NEU'}
)} {showThcBadge && (
25 && thc <= 30 ? '#000000' : '#ffffff', fontWeight: 'bold', padding: '2px 0', width: '80px', textAlign: 'center', zIndex: 999, fontSize: '9px', boxShadow: '0px 2px 4px rgba(0,0,0,0.2)', transform: 'rotate(-45deg) translateX(-40px) translateY(15px)', transformOrigin: 'top left' }} > THC {thc}%
)} {showFloweringWeeksBadge && (
8 && floweringWeeks <= 12 ? '#000000' : '#ffffff', fontWeight: 'bold', padding: '1px 0', width: '100px', textAlign: 'center', zIndex: 999, fontSize: '9px', boxShadow: '0px 2px 4px rgba(0,0,0,0.2)', transform: 'rotate(-45deg) translateX(-50px) translateY(32px)', transformOrigin: 'top left' }} > {floweringWeeks} {this.props.t ? this.props.t('product.weeks') : 'Wochen'}
)} {this.state.loading ? ( ) : this.state.images && this.state.images.length > 0 && this.state.images.some(img => img !== null) ? ( this.state.images.map((imgSrc, index) => { if (!imgSrc) return null; return ( { // Ensure alt text is always present even on error if (!e.target.alt) { e.target.alt = name || 'Produktbild'; } }} sx={{ objectFit: 'contain', borderTopLeftRadius: '8px', borderTopRightRadius: '8px', width: '100%', height: '100%', position: 'absolute', top: 0, left: 0, opacity: this.state.currentImageIndex === index ? 1 : 0, transition: this.state.isHovering ? 'opacity 0.2s ease-in-out' : 'opacity 1s ease-in-out' }} /> ); }) ) : ( { // Ensure alt text is always present even on error if (!e.target.alt) { e.target.alt = name || 'Produktbild'; } }} sx={{ objectFit: 'contain', borderTopLeftRadius: '8px', borderTopRightRadius: '8px', width: '100%', height: '100%', position: 'absolute', top: 0, left: 0 }} /> )} {name} {manufacturer || ''}
{this.props.rebate && this.props.rebate > 0 && ( {(() => { const rebatePct = this.props.rebate / 100; const originalPrice = Math.round((price / (1 - rebatePct)) * 10) / 10; return new Intl.NumberFormat('de-DE', { style: 'currency', currency: currency || 'EUR' }).format(originalPrice); })()} )} {new Intl.NumberFormat('de-DE', { style: 'currency', currency: currency || 'EUR' }).format(price)} ({this.props.t ? this.props.t('product.inclVatFooter', { vat }) : `incl. ${vat}% USt.,*`})
{cGrundEinheit && fGrundPreis && fGrundPreis != price && ( ({new Intl.NumberFormat('de-DE', { style: 'currency', currency: currency || 'EUR' }).format(fGrundPreis)}/{cGrundEinheit}) )}
{/*incoming*/}
); } } // Wrapper component to provide navigate hook const ProductWithNavigation = (props) => { const navigate = useNavigate(); return ; }; export default withI18n()(ProductWithNavigation);