import { useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { fmtEUR, fmtStatut } from '../utils/format.js'; const MOIS_LONG = ['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre']; function endOfMonth(Y, M) { const d = new Date(Y, M, 0); return `${Y}-${String(M).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; } function startOfMonth(Y, M) { return `${Y}-${String(M).padStart(2,'0')}-01`; } export default function InvMensuelTable({ rows, allRembs, allReinvests, year, originFrom }) { const navigate = useNavigate(); const currentYear = new Date().getFullYear(); const currentMonth = new Date().getMonth() + 1; const displayYear = year ? Number(year) : currentYear; /* ── Precompute rembs ── */ const reinvestByInv = useMemo(() => { const map = {}; for (const rv of (allReinvests || [])) { const id = rv.investissement_id; if (!id) continue; if (!map[id]) map[id] = []; map[id].push({ date: rv.date_reinvestissement?.slice(0,10), montant: rv.montant || 0 }); } return map; }, [allReinvests]); const capRembByInv = useMemo(() => { const map = {}; for (const rb of (allRembs || [])) { const id = rb.investissement_id; if (!id || rb.type !== 'normal') continue; if (!map[id]) map[id] = []; map[id].push({ date: rb.date_remb?.slice(0,10), capital: rb.capital || 0 }); } return map; }, [allRembs]); const lastRembDateMap = useMemo(() => { const map = {}; for (const rb of (allRembs || [])) { const id = rb.investissement_id; const d = rb.date_remb?.slice(0,10); if (!id || !d) continue; if (!map[id] || d > map[id]) map[id] = d; } return map; }, [allRembs]); /* ── Capital encours d'un investissement à fin de mois M ── */ const getCapital = (inv, Y, M) => { const endM = endOfMonth(Y, M); if (inv.date_souscription > endM) return 0; const startM = startOfMonth(Y, M); const ACTIVE = ['en_cours', 'en_retard', 'procedure']; const isActive = ACTIVE.includes(inv.statut) || ((inv.date_cible || lastRembDateMap[inv.id] || null) >= startM); if (!isActive) return 0; const reinvM = (reinvestByInv[inv.id] || []) .filter(rv => rv.date && rv.date <= endM) .reduce((s, rv) => s + rv.montant, 0); const capRembM = (capRembByInv[inv.id] || []) .filter(rb => rb.date && rb.date <= endM) .reduce((s, rb) => s + rb.capital, 0); return Math.max(0, inv.montant_investi + reinvM - capRembM); }; /* ── Grille : une ligne par investissement ── */ const grid = useMemo(() => { if (!rows?.length) return []; return rows .map(inv => ({ inv, months: Array.from({ length: 12 }, (_, i) => getCapital(inv, displayYear, i + 1)), })) .filter(r => r.months.some(v => v > 0)) .sort((a, b) => (a.inv.date_souscription || '') < (b.inv.date_souscription || '') ? -1 : 1 ); // eslint-disable-next-line react-hooks/exhaustive-deps }, [rows, displayYear, reinvestByInv, capRembByInv, lastRembDateMap]); const monthTotals = useMemo(() => Array.from({ length: 12 }, (_, i) => grid.reduce((s, r) => s + r.months[i], 0)), [grid] ); if (!grid.length) { return (
Aucun investissement actif pour {displayYear}.
); } return (
{MOIS_LONG.map((m, i) => ( ))} {grid.map(({ inv, months }) => ( navigate(`/investissements/${inv.id}`, originFrom ? { state: { from: originFrom } } : undefined)}> {months.map((v, mi) => { const curClass = displayYear === currentYear && mi === currentMonth - 1 ? ' tip-col-current' : ''; if (v === 0) { // Avant la date de souscription const subYear = Number(inv.date_souscription?.slice(0, 4)); const subMo = Number(inv.date_souscription?.slice(5, 7)) - 1; const isBefore = inv.date_souscription && ( subYear > displayYear || (subYear === displayYear && mi < subMo) ); // Après le dernier remboursement (prêt remboursé) const lastDate = lastRembDateMap[inv.id]; const isAfter = inv.statut === 'rembourse' && lastDate && (() => { const lastYear = Number(lastDate.slice(0, 4)); const lastMo = Number(lastDate.slice(5, 7)) - 1; if (lastYear < displayYear) return true; if (lastYear === displayYear) return mi > lastMo; return false; })(); if (isBefore || isAfter) { return ); })} ))} ))}
{displayYear}
Investissement Statut {m}
{inv.nom_projet || '—'} {fmtStatut(inv.statut)} ; } } return ( {v > 0 ? fmtEUR(v) : }
Total {monthTotals.map((v, i) => ( {v > 0 ? fmtEUR(v) : }
); }