- Changed image file extensions from JPG to AVIF in data-fetching, product, category, and image components for improved performance and reduced file sizes. - Updated image blob creation to reflect the new AVIF format in various components, ensuring consistency in image handling throughout the application.
211 lines
6.2 KiB
JavaScript
211 lines
6.2 KiB
JavaScript
import React, { Component } from 'react';
|
|
import Grid from '@mui/material/Grid';
|
|
import CardMedia from '@mui/material/CardMedia';
|
|
import Typography from '@mui/material/Typography';
|
|
import Box from '@mui/material/Box';
|
|
import Stack from '@mui/material/Stack';
|
|
import CircularProgress from '@mui/material/CircularProgress';
|
|
import { Link } from 'react-router-dom';
|
|
import IconButton from '@mui/material/IconButton';
|
|
import ZoomInIcon from '@mui/icons-material/ZoomIn';
|
|
|
|
class ExtrasSelector extends Component {
|
|
formatPrice(price) {
|
|
return new Intl.NumberFormat('de-DE', {
|
|
style: 'currency',
|
|
currency: 'EUR'
|
|
}).format(price);
|
|
}
|
|
|
|
// Render product image using working code from GrowTentKonfigurator
|
|
renderProductImage(product) {
|
|
if (!window.smallPicCache) {
|
|
window.smallPicCache = {};
|
|
}
|
|
|
|
const pictureList = product.pictureList;
|
|
|
|
if (!pictureList || pictureList.length === 0 || !pictureList.split(',').length) {
|
|
return (
|
|
<CardMedia
|
|
component="img"
|
|
height="160"
|
|
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="160"
|
|
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 (
|
|
<Box sx={{ height: '160px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
<CircularProgress sx={{ color: '#90ffc0' }} />
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
renderExtraCard(extra) {
|
|
const { selectedExtras, onExtraToggle, showImage = true } = this.props;
|
|
const isSelected = selectedExtras.includes(extra.id);
|
|
|
|
return (
|
|
<Box sx={{
|
|
width: { xs: '100%', sm: '250px' },
|
|
height: '100%',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
borderRadius: '8px',
|
|
overflow: 'hidden',
|
|
cursor: 'pointer',
|
|
border: '2px solid',
|
|
borderColor: isSelected ? '#2e7d32' : '#e0e0e0',
|
|
backgroundColor: isSelected ? '#f1f8e9' : '#ffffff',
|
|
'&:hover': {
|
|
boxShadow: 6,
|
|
borderColor: isSelected ? '#2e7d32' : '#90caf9'
|
|
},
|
|
transition: 'all 0.3s ease'
|
|
}}
|
|
onClick={() => onExtraToggle(extra.id)}>
|
|
{/* Image */}
|
|
{showImage && (
|
|
<Box sx={{
|
|
height: { xs: '240px', sm: '180px' },
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
backgroundColor: '#ffffff'
|
|
}}>
|
|
{this.renderProductImage(extra)}
|
|
</Box>
|
|
)}
|
|
|
|
{/* Content */}
|
|
<Box sx={{ p: 2, flexGrow: 1, display: 'flex', flexDirection: 'column' }}>
|
|
{/* Name */}
|
|
<Typography variant="h6" gutterBottom sx={{ fontWeight: 'bold' }}>
|
|
{extra.name}
|
|
</Typography>
|
|
|
|
<Typography gutterBottom>
|
|
{extra.kurzBeschreibung}
|
|
</Typography>
|
|
|
|
{/* Price with VAT - Same as other sections */}
|
|
<Typography variant="h6" sx={{
|
|
color: '#2e7d32',
|
|
fontWeight: 'bold',
|
|
mt: 2,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 1,
|
|
flexWrap: 'wrap'
|
|
}}>
|
|
<span>{extra.price ? this.formatPrice(extra.price) : 'Kein Preis'}</span>
|
|
{extra.vat && (
|
|
<small style={{ color: '#77aa77', fontSize: '0.6em' }}>
|
|
(incl. {extra.vat}% MwSt.,*)
|
|
</small>
|
|
)}
|
|
</Typography>
|
|
|
|
{/* Selection Indicator - Separate line */}
|
|
{isSelected && (
|
|
<Typography variant="body2" sx={{
|
|
color: '#2e7d32',
|
|
fontWeight: 'bold',
|
|
mt: 1,
|
|
textAlign: 'center'
|
|
}}>
|
|
✓ Ausgewählt
|
|
</Typography>
|
|
)}
|
|
<Stack direction="row" spacing={1} justifyContent="center">
|
|
<IconButton
|
|
component={Link}
|
|
to={`/Artikel/${extra.seoName}`}
|
|
size="small"
|
|
aria-label="Produktdetails anzeigen"
|
|
sx={{ mr: 1, color: 'text.secondary' }}
|
|
onClick={(event) => event.stopPropagation()}
|
|
>
|
|
<ZoomInIcon />
|
|
</IconButton>
|
|
</Stack>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
render() {
|
|
const { extras, title, subtitle, gridSize = { xs: 12, sm: 6, md: 4 } } = this.props;
|
|
|
|
if (!extras || !Array.isArray(extras)) {
|
|
return (
|
|
<Box sx={{ mb: 4 }}>
|
|
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
|
|
{title}
|
|
</Typography>
|
|
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
|
|
Keine Extras verfügbar
|
|
</Typography>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Box sx={{ mb: 4 }}>
|
|
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
|
|
{title}
|
|
</Typography>
|
|
{subtitle && (
|
|
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
|
|
{subtitle}
|
|
</Typography>
|
|
)}
|
|
<Grid container spacing={2}>
|
|
{extras.map(extra => (
|
|
<Grid item {...gridSize} key={extra.id}>
|
|
{this.renderExtraCard(extra)}
|
|
</Grid>
|
|
))}
|
|
</Grid>
|
|
</Box>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default ExtrasSelector;
|