Files
crowdlending-app/frontend/src/pages/SimulRemboursements.jsx
T
Olivier CROGUENNEC 48ed7fe65e Initial commit
2026-06-13 14:57:15 +02:00

111 lines
4.2 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useState } from 'react';
import { api } from '../api.js';
import { useInvestisseur } from '../context/InvestisseurContext.jsx';
import { fmtEUR, fmtDate } from '../utils/format.js';
export default function SimulRemboursements() {
const { activeId } = useInvestisseur();
const [investissements, setInvestissements] = useState([]);
const [selected, setSelected] = useState('');
const [echeances, setEcheances] = useState([]);
const [busy, setBusy] = useState(false);
const [msg, setMsg] = useState(null);
useEffect(() => {
if (!activeId) return;
api.get('/investissements').then(setInvestissements);
}, [activeId]);
useEffect(() => {
if (!selected) { setEcheances([]); return; }
api.get('/simul', { investissement_id: selected }).then(setEcheances);
}, [selected]);
const generate = async () => {
if (!selected) return;
setBusy(true); setMsg(null);
try {
const r = await api.post('/simul/generate', {
investissement_id: Number(selected),
replace: true,
});
setMsg(`✔ ${r.inserted} échéances générées.`);
const e = await api.get('/simul', { investissement_id: selected });
setEcheances(e);
} catch (e) {
setMsg(`✗ ${e.message}`);
} finally { setBusy(false); }
};
const inv = investissements.find(i => i.id === Number(selected));
const totals = echeances.reduce((acc, e) => {
acc.capital += e.capital_prevu;
acc.interets += e.interets_prevus;
acc.total += e.total_prevu;
return acc;
}, { capital: 0, interets: 0, total: 0 });
return (
<>
<div className="topbar"><h2>Projections Remboursements</h2></div>
<div className="card">
<div className="row">
<div style={{ flex: 2 }}>
<label>Investissement</label>
<select value={selected} onChange={e => setSelected(e.target.value)}>
<option value=""> Choisir </option>
{investissements.map(i =>
<option key={i.id} value={i.id}>
{i.nom_projet} ({fmtEUR(i.montant_investi)} {i.taux_interet ?? '?'}% {i.duree_mois ?? '?'}m {i.type_remb || '?'})
</option>
)}
</select>
</div>
<div style={{ flex: 1 }}>
<button className="primary" onClick={generate} disabled={!selected || busy} style={{ width: '100%' }}>
{busy ? '…' : 'Générer / Régénérer'}
</button>
</div>
</div>
{msg && <div className={msg.startsWith('✔') ? 'success-msg' : 'error'} style={{ marginTop: 12 }}>{msg}</div>}
{inv && (!inv.taux_interet || !inv.duree_mois) && (
<div className="error" style={{ marginTop: 12 }}>
Cet investissement n'a pas de <strong>taux</strong> et/ou de <strong>durée</strong>. Renseignez-les dans la fiche.
</div>
)}
</div>
{echeances.length > 0 && (
<div className="card">
<div className="kpi-grid" style={{ marginBottom: 12 }}>
<div className="kpi"><div className="label">Capital prévu</div><div className="value">{fmtEUR(totals.capital)}</div></div>
<div className="kpi"><div className="label">Intérêts prévus</div><div className="value success">{fmtEUR(totals.interets)}</div></div>
<div className="kpi"><div className="label">Total prévu</div><div className="value">{fmtEUR(totals.total)}</div></div>
</div>
<table>
<thead>
<tr>
<th>#</th><th>Date prévue</th>
<th className="num">Capital</th><th className="num">Intérêts</th>
<th className="num">Total échéance</th>
</tr>
</thead>
<tbody>
{echeances.map(e => (
<tr key={e.id}>
<td>{e.numero_echeance}</td>
<td>{fmtDate(e.date_prevue)}</td>
<td className="num">{fmtEUR(e.capital_prevu)}</td>
<td className="num">{fmtEUR(e.interets_prevus)}</td>
<td className="num">{fmtEUR(e.total_prevu)}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</>
);
}