Enhance the ExtrasSelector and ProductSelector components by introducing a button that links to detailed product views. The button features a ZoomInIcon and is styled for a consistent user experience. This addition improves navigation and accessibility for users seeking more information on products.
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/jpeg' }));
|
|
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;
|