MIGRATION VERS GITEA
This commit is contained in:
commit
5f9d991c15
333 files changed
+108514
No files matched your search
@@ -0,0 +1,169 @@
|
||||
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="card materiel-card"
|
||||
style={{
|
||||
border: '1px solid #e0e0e0',
|
||||
borderRadius: 8,
|
||||
padding: 12,
|
||||
boxShadow: '0 1px 2px rgba(0,0,0,0.04)',
|
||||
background: '#fff',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
height: '100%'
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<div ref={imgContainerRef} style={{ width: '100%', height: 140, borderRadius: 6, marginBottom: 12, display: 'flex', alignItems: 'center', justifyContent: 'flex-start', overflow: 'hidden', position: 'relative' }}>
|
||||
{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%', background: '#f5f5f5', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#999', borderRadius: 6 }}>No image</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}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: 6,
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
background: 'rgba(0,0,0,0.55)',
|
||||
color: '#fff',
|
||||
border: 'none',
|
||||
borderRadius: 6,
|
||||
padding: '8px 10px',
|
||||
cursor: 'pointer',
|
||||
zIndex: 3,
|
||||
boxShadow: '0 2px 6px rgba(0,0,0,0.2)'
|
||||
}}
|
||||
>
|
||||
‹
|
||||
</button>
|
||||
)}
|
||||
|
||||
{canNext && (
|
||||
<button
|
||||
aria-label="Suivant"
|
||||
onClick={next}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: 6,
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
background: 'rgba(0,0,0,0.55)',
|
||||
color: '#fff',
|
||||
border: 'none',
|
||||
borderRadius: 6,
|
||||
padding: '8px 10px',
|
||||
cursor: 'pointer',
|
||||
zIndex: 3,
|
||||
boxShadow: '0 2px 6px rgba(0,0,0,0.2)'
|
||||
}}
|
||||
>
|
||||
›
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<div style={{ fontWeight: 600, fontSize: 15 }}>{materiel?.nom ?? '-'}</div>
|
||||
<div style={{ fontSize: 12, color: '#666' }}>{materiel?.nom_technique ?? '-'}</div>
|
||||
</div>
|
||||
|
||||
<div style={{ fontSize: 13, color: '#333', marginBottom: 8 }}>
|
||||
<div>Capacité: <strong>{materiel?.capacite ?? '-'}</strong></div>
|
||||
<div>Type: <strong>{materiel?.type_train ?? '-'}</strong></div>
|
||||
<div>Numéro série: <strong>{materiel?.numero_serie ?? '-'}</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 8 }}>
|
||||
<wcs-button onClick={onEdit}>Modifier</wcs-button>
|
||||
<wcs-button className="wcs-danger" onClick={onDelete}>Supprimer</wcs-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user