From f76e7c99326a3d877188f5a496918add39d1a9eb Mon Sep 17 00:00:00 2001 From: Olivier Date: Sun, 16 Aug 2026 15:34:42 +0200 Subject: [PATCH] =?UTF-8?q?Am=C3=A9lioration=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/InvestissementSelect.jsx | 136 ++++++++++++++++++ frontend/src/components/RembFormModal.jsx | 16 +-- 2 files changed, 142 insertions(+), 10 deletions(-) create mode 100644 frontend/src/components/InvestissementSelect.jsx diff --git a/frontend/src/components/InvestissementSelect.jsx b/frontend/src/components/InvestissementSelect.jsx new file mode 100644 index 0000000..ad84f97 --- /dev/null +++ b/frontend/src/components/InvestissementSelect.jsx @@ -0,0 +1,136 @@ +import { useEffect, useLayoutEffect, useRef, useState } from 'react'; +import { fmtEUR } from '../utils/format.js'; + +/** + * InvestissementSelect — combobox pour le champ "Investissement" de RembFormModal. + * + * Remplace le natif via CSS, ce qui produisait une liste déroulante démesurément haute + * quand beaucoup d'investissements existent. + * + * Positionnement en `position: fixed` (calculé via getBoundingClientRect, comme InvSelect.jsx) — + * pas en `position: absolute` — car ce champ est utilisé dans RembFormModal dont le conteneur + * `.card` a `overflow: auto` (voir Modal.jsx) : un dropdown en absolute y serait rogné quand la + * fenêtre est basse. Le dropdown bascule aussi au-dessus du champ s'il n'y a pas assez de place + * en dessous, et se ferme au scroll/resize (repositionnement non géré en direct, comme InvSelect). + * + * Props : + * value : '' | 'BONUS_PARRAINAGE' | 'BONUS_PLATEFORME' | id (string/number) + * onChange : (value) => void + * options : investissementOptions — { id, plateforme_nom, nom_projet, montant_investi }[] + */ +export default function InvestissementSelect({ value, onChange, options = [] }) { + const [open, setOpen] = useState(false); + const [dropPos, setDropPos] = useState({ left: 0, width: 0, maxHeight: 260, direction: 'down', top: 0, bottom: 0 }); + + const wrapRef = useRef(null); + const triggerRef = useRef(null); + + const selectedOption = options.find(i => String(i.id) === String(value)); + const label = value === 'BONUS_PARRAINAGE' ? '🎁 Bonus Parrainage' + : value === 'BONUS_PLATEFORME' ? '🏆 Bonus Plateforme' + : selectedOption ? `${selectedOption.plateforme_nom} — ${selectedOption.nom_projet} (${fmtEUR(selectedOption.montant_investi)})` + : null; + + useLayoutEffect(() => { + if (!open || !triggerRef.current) return; + const rect = triggerRef.current.getBoundingClientRect(); + const margin = 8; + const spaceBelow = window.innerHeight - rect.bottom - margin; + const spaceAbove = rect.top - margin; + const desired = 260; // ~ moitié de la hauteur qu'affichait le onFieldChange('investissement_id', e.target.value)}> - - - - - {investissementOptions.map(i => - - )} - + onFieldChange('investissement_id', v)} + options={investissementOptions} + /> )} {!locked && isBonus && (