->Refonte visuelle + Prise en charge du mode sombre par l'ensemble des composants.
162 lines
5.9 KiB
JavaScript
162 lines
5.9 KiB
JavaScript
import React, { useState, useEffect, useRef } from 'react';
|
|
|
|
export default function MaterielCard({ materiel, onEdit, onDelete }) {
|
|
const [index, setIndex] = useState(0);
|
|
const imgContainerRef = useRef(null);
|
|
const imgRef = useRef(null);
|
|
const imgs = React.useMemo(() => {
|
|
if (!materiel) return [];
|
|
if (Array.isArray(materiel.images) && materiel.images.length > 0) return materiel.images;
|
|
if (Array.isArray(materiel.image_url) && materiel.image_url.length > 0) return materiel.image_url;
|
|
if (typeof materiel.image_url === 'string' && materiel.image_url) return [materiel.image_url];
|
|
return [];
|
|
}, [materiel]);
|
|
|
|
const [offsetX, setOffsetX] = useState(0);
|
|
const [maxOffset, setMaxOffset] = useState(0);
|
|
|
|
const recomputeSizes = () => {
|
|
const c = imgContainerRef.current;
|
|
const i = imgRef.current;
|
|
const cw = c ? c.clientWidth : 0;
|
|
const iw = (i && i.naturalWidth && i.naturalHeight && c) ? Math.round(i.naturalWidth * (c.clientHeight / i.naturalHeight)) : (i ? i.clientWidth : 0);
|
|
const computedMaxOffset = Math.min(0, Math.floor(cw - iw));
|
|
setMaxOffset(computedMaxOffset);
|
|
setOffsetX(prev => Math.max(computedMaxOffset, Math.min(0, prev)));
|
|
};
|
|
|
|
useEffect(() => {
|
|
recomputeSizes();
|
|
const onResize = () => recomputeSizes();
|
|
window.addEventListener('resize', onResize);
|
|
return () => window.removeEventListener('resize', onResize);
|
|
}, [index, imgs.length]);
|
|
|
|
const next = (e) => {
|
|
e && e.stopPropagation();
|
|
if (!imgs || imgs.length === 0) return;
|
|
if (imgs.length > 1) {
|
|
setIndex((i) => (i + 1) % imgs.length);
|
|
} else {
|
|
const c = imgContainerRef.current;
|
|
if (!c) return;
|
|
const step = Math.max(80, Math.floor(c.clientWidth / 2));
|
|
setOffsetX(prev => Math.max(maxOffset, prev - step));
|
|
}
|
|
};
|
|
|
|
const prev = (e) => {
|
|
e && e.stopPropagation();
|
|
if (!imgs || imgs.length === 0) return;
|
|
if (imgs.length > 1) {
|
|
setIndex((i) => (i - 1 + imgs.length) % imgs.length);
|
|
} else {
|
|
const c = imgContainerRef.current;
|
|
if (!c) return;
|
|
const step = Math.max(80, Math.floor(c.clientWidth / 2));
|
|
setOffsetX(prev => Math.min(0, prev + step));
|
|
}
|
|
};
|
|
|
|
const current = imgs && imgs.length > 0 ? imgs[index] : null;
|
|
|
|
return (
|
|
<div
|
|
className="materiel-card-modern"
|
|
style={{
|
|
padding: '16px',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
justifyContent: 'space-between',
|
|
height: '100%'
|
|
}}
|
|
>
|
|
<div>
|
|
<div style={{ position: 'relative' }}>
|
|
<div
|
|
ref={imgContainerRef}
|
|
style={{
|
|
width: '100%',
|
|
height: 140,
|
|
borderRadius: 10,
|
|
marginBottom: 12,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'flex-start',
|
|
overflow: 'hidden',
|
|
position: 'relative',
|
|
background: '#f1f5f9'
|
|
}}
|
|
>
|
|
{current ? (
|
|
<img
|
|
ref={imgRef}
|
|
src={current}
|
|
alt={materiel?.nom || ''}
|
|
onLoad={() => recomputeSizes()}
|
|
style={{ height: '100%', width: 'auto', objectFit: 'contain', objectPosition: 'left center', display: 'block', transform: `translateX(${offsetX}px)` }}
|
|
/>
|
|
) : (
|
|
<div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', gap: 4 }}>
|
|
<wcs-mat-icon icon="train" size="m"></wcs-mat-icon>
|
|
<span style={{ fontSize: 12 }}>Sans image</span>
|
|
</div>
|
|
)}
|
|
|
|
{(() => {
|
|
const isMulti = imgs.length > 1;
|
|
const canPrev = isMulti ? index > 0 : offsetX < 0;
|
|
const canNext = isMulti ? index < imgs.length - 1 : offsetX > maxOffset;
|
|
return (
|
|
<>
|
|
{canPrev && (
|
|
<button
|
|
aria-label="Précédent"
|
|
onClick={prev}
|
|
className="materiel-card-nav-btn prev"
|
|
>
|
|
<wcs-mat-icon icon="chevron_left" size="s"></wcs-mat-icon>
|
|
</button>
|
|
)}
|
|
|
|
{canNext && (
|
|
<button
|
|
aria-label="Suivant"
|
|
onClick={next}
|
|
className="materiel-card-nav-btn next"
|
|
>
|
|
<wcs-mat-icon icon="chevron_right" size="s"></wcs-mat-icon>
|
|
</button>
|
|
)}
|
|
</>
|
|
);
|
|
})()}
|
|
</div>
|
|
|
|
<div style={{ marginBottom: 10 }}>
|
|
<div style={{ fontWeight: 800, fontSize: 16, color: '#0f172a' }}>{materiel?.nom ?? '-'}</div>
|
|
<div style={{ fontSize: 12, color: '#64748b', fontWeight: 600 }}>{materiel?.nom_technique ?? '-'}</div>
|
|
</div>
|
|
|
|
<div style={{ fontSize: 13, color: '#334155', display: 'flex', flexDirection: 'column', gap: 4, marginBottom: 12 }}>
|
|
<div>Capacité : <strong>{materiel?.capacite ? `${materiel.capacite} places` : '-'}</strong></div>
|
|
<div>Type de train : <strong>{materiel?.type_train ?? '-'}</strong></div>
|
|
<div>N° de série : <strong>{materiel?.numero_serie ?? '-'}</strong></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 12, borderTop: '1px solid #f1f5f9', paddingTop: 10 }}>
|
|
<wcs-button className="wcs-secondary" onClick={onEdit}>
|
|
<wcs-mat-icon icon="edit" size="s"></wcs-mat-icon>
|
|
Modifier
|
|
</wcs-button>
|
|
<wcs-button className="wcs-danger" onClick={onDelete}>
|
|
<wcs-mat-icon icon="delete" size="s"></wcs-mat-icon>
|
|
Supprimer
|
|
</wcs-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|