111 lines
4.2 KiB
React
111 lines
4.2 KiB
React
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>
|
||
)}
|
||
</>
|
||
);
|
||
}
|