diff --git a/frontend/src/components/MouvementsShared.jsx b/frontend/src/components/MouvementsShared.jsx index d781682..026bfd5 100644 --- a/frontend/src/components/MouvementsShared.jsx +++ b/frontend/src/components/MouvementsShared.jsx @@ -134,6 +134,99 @@ export function ExportDropdown({ disabled, onCSV, onXLS, onJSON }) { ); } +/* ── Menu déroulant "+" — actions rapides sur les mouvements (extensible : + pour l'instant uniquement la création d'une correction de solde) ── */ +export function AddDropdown({ disabled, onNewCorrection }) { + const [open, setOpen] = useState(false); + const ref = useRef(null); + useEffect(() => { + if (!open) return; + const h = e => { if (!ref.current?.contains(e.target)) setOpen(false); }; + document.addEventListener('mousedown', h); + return () => document.removeEventListener('mousedown', h); + }, [open]); + const choose = fn => { setOpen(false); fn(); }; + return ( +
+ + {open && ( +
+ +
+ )} +
+ ); +} + +/** Petite modale de sélection plateforme + date, avant d'ouvrir CorrectionModal + en saisie libre (déclenchée depuis AddDropdown, pas depuis un mouvement existant). */ +export function CorrectionPickerModal({ open, onClose, onContinue, plateformes = [], multiDetenteur, defaultPlateformeId }) { + const [platId, setPlatId] = useState(''); + const [date, setDate] = useState(''); + + useEffect(() => { + if (open) { + setPlatId(defaultPlateformeId ? String(defaultPlateformeId) : ''); + setDate(today()); + } + }, [open, defaultPlateformeId]); + + if (!open) return null; + return ( +
+
+
+ Nouvelle correction de solde +
+
+ Choisissez la plateforme et la date de référence pour la correction. +
+ +
+ + +
+
+ + setDate(e.target.value)} style={{ width: '100%' }} /> +
+ +
+ + +
+
+
+ ); +} + /* ── Panneau de détail d'un mouvement ────────────────────────── */ export function DetailPanel({ row, onEdit, onDeleteCorrection, onViewRemb, onViewInvestissement }) { if (!row) return ( @@ -371,8 +464,11 @@ export function CorrectionModal({ open, onClose, onConfirm, fontSize: 'var(--fs-sm)', color: 'var(--text-muted)', marginBottom: 18, lineHeight: 1.5, }}> - Après ce {isRetrait ? 'retrait' : 'dépôt'} sur {plateforme_nom}, - quel est le solde indiqué sur la plateforme ? + {type_operation ? ( + <>Après ce {isRetrait ? 'retrait' : 'dépôt'} sur {plateforme_nom}, quel est le solde indiqué sur la plateforme ? + ) : ( + <>Quel est le solde indiqué sur {plateforme_nom} à la date du {date ? fmtDate(date) : ''} ? + )} {' '}Si différent du solde calculé, une correction sera créée automatiquement. diff --git a/frontend/src/pages/DepotsRetraits.jsx b/frontend/src/pages/DepotsRetraits.jsx index 68d5f39..460ac5f 100644 --- a/frontend/src/pages/DepotsRetraits.jsx +++ b/frontend/src/pages/DepotsRetraits.jsx @@ -16,7 +16,7 @@ import SuiviObjectifs from '../components/SuiviObjectifs.jsx'; import * as XLSX from 'xlsx'; import { DEBIT_TYPES, signedMontant, MOUV_TYPE_LABELS, mouvToCSV, mouvToXLS, mouvToJSON, - ExportDropdown, DetailPanel, CorrectionModal, MOIS_FR, emptyMouvementForm, + ExportDropdown, AddDropdown, CorrectionPickerModal, DetailPanel, CorrectionModal, MOIS_FR, emptyMouvementForm, timestampSuffix, dlBlob, } from '../components/MouvementsShared.jsx'; @@ -156,6 +156,8 @@ export default function DepotsRetraits() { open: false, plateforme_nom: '', type_operation: 'retrait', computedBalance: 0, date: '', platId: null, investisseurId: null, }); + // État de la modale de sélection plateforme/date (correction de solde autonome, bouton "+") + const [corrPickerOpen, setCorrPickerOpen] = useState(false); const load = async () => { if (!activeId && activeView !== 'all') return; @@ -705,6 +707,32 @@ export default function DepotsRetraits() { }); }; + /* Correction de solde autonome (bouton "+" du tableau Mouvements) — pas de + mouvement dépôt/retrait déclencheur, l'utilisateur choisit plateforme + date. */ + const openCorrectionManual = async ({ plateforme_id, date }) => { + const plat = plats.find(p => p.id === plateforme_id); + const investId = investisseurForPlat(plateforme_id); + + let computedBalance = 0; + try { + const result = await api.get('/dashboard/solde-historique', { plateforme_id, date }); + computedBalance = result.solde ?? 0; + } catch (e) { + console.error('solde-historique error:', e); + } + + setCorrPickerOpen(false); + setCorrModal({ + open: true, + plateforme_nom: plat?.nom || '', + type_operation: null, + computedBalance, + date, + platId: plateforme_id, + investisseurId: investId ? Number(investId) : null, + }); + }; + const close = () => { setModalOpen(false); setEditingId(null); setForm(emptyMouvementForm); setErr(null); }; const submit = async (e) => { @@ -766,7 +794,9 @@ export default function DepotsRetraits() { plateforme_id: corrModal.platId, date: corrModal.date, montant: diff, - notes: `Correction solde porte-monnaie — ${corrModal.type_operation === 'retrait' ? 'retrait' : 'dépôt'}`, + notes: corrModal.type_operation + ? `Correction solde porte-monnaie — ${corrModal.type_operation === 'retrait' ? 'retrait' : 'dépôt'}` + : 'Correction solde porte-monnaie — saisie manuelle', }); } catch (e) { console.error('Correction creation failed:', e); @@ -1116,6 +1146,10 @@ export default function DepotsRetraits() { onXLS={() => dlBlob(mouvToXLS(tableRows, multiDetenteur), `Mouvements Dépôts Retraits ${timestampSuffix()}.xlsx`, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')} onJSON={() => dlBlob(mouvToJSON(tableRows), `Mouvements Dépôts Retraits ${timestampSuffix()}.json`, 'application/json')} /> + setCorrPickerOpen(true)} + />