selection des remboursements en masse

This commit is contained in:
ocroguennec committed 2026-08-15 11:32:36 +02:00
1 parent eab9271b0f
commit 6453218e2b
1 file changed
+65 -36
+65 -36
View File
@@ -432,6 +432,7 @@ export default function InvestissementDetail() {
const baseRates = brutsApresLocal;
return {
_simul: s,
selected: true,
payload: {
investissement_id: Number(id),
date_remb: s.date_prevue,
@@ -453,12 +454,21 @@ export default function InvestissementDetail() {
setBulkRembModal(true);
};
const toggleBulkRembItem = (idx) => {
setBulkRembItems(items => items.map((it, i) => i === idx ? { ...it, selected: !it.selected } : it));
};
const toggleBulkRembAll = (checked) => {
setBulkRembItems(items => items.map(it => ({ ...it, selected: checked })));
};
const runBulkRemb = async () => {
const toProcess = bulkRembItems.filter(it => it.selected);
setBulkRembProcessing(true);
setBulkRembProgress(0);
try {
for (let i = 0; i < bulkRembItems.length; i++) {
await api.post('/remboursements', bulkRembItems[i].payload);
for (let i = 0; i < toProcess.length; i++) {
await api.post('/remboursements', toProcess[i].payload);
setBulkRembProgress(i + 1);
}
setBulkRembDone(true);
@@ -2368,61 +2378,80 @@ export default function InvestissementDetail() {
title="Traitement en masse des remboursements"
onClose={() => { if (!bulkRembProcessing) setBulkRembModal(false); }}
width={560}
footer={
bulkRembDone ? (
footer={(() => {
const selectedCount = bulkRembItems.filter(it => it.selected).length;
return bulkRembDone ? (
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<button className="primary" onClick={() => setBulkRembModal(false)}>Fermer</button>
</div>
) : (
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
<button onClick={() => setBulkRembModal(false)} disabled={bulkRembProcessing}>Annuler</button>
<button className="primary" onClick={runBulkRemb} disabled={bulkRembProcessing || bulkRembItems.length === 0}>
{bulkRembProcessing ? `Traitement… (${bulkRembProgress}/${bulkRembItems.length})` : `Enregistrer ${bulkRembItems.length} remboursement${bulkRembItems.length > 1 ? 's' : ''}`}
<button className="primary" onClick={runBulkRemb} disabled={bulkRembProcessing || selectedCount === 0}>
{bulkRembProcessing ? `Traitement… (${bulkRembProgress}/${selectedCount})` : `Enregistrer ${selectedCount} remboursement${selectedCount > 1 ? 's' : ''}`}
</button>
</div>
)
}
);
})()}
>
{bulkRembDone ? (
<div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 0' }}>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--success)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
<span style={{ fontSize: 14, color: 'var(--success)', fontWeight: 600 }}>
{bulkRembItems.length} remboursement{bulkRembItems.length > 1 ? 's' : ''} enregistré{bulkRembItems.length > 1 ? 's' : ''} avec succès.
{bulkRembItems.filter(it => it.selected).length} remboursement{bulkRembItems.filter(it => it.selected).length > 1 ? 's' : ''} enregistré{bulkRembItems.filter(it => it.selected).length > 1 ? 's' : ''} avec succès.
</span>
</div>
) : bulkRembItems.length === 0 ? (
<p style={{ margin: 0, fontSize: 13, color: 'var(--text-muted)' }}>
Aucune échéance passée non enregistrée trouvée.
</p>
) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
<p style={{ margin: 0, fontSize: 13, color: 'var(--text-muted)', lineHeight: 1.6 }}>
{bulkRembItems.length} échéance{bulkRembItems.length > 1 ? 's' : ''} passée{bulkRembItems.length > 1 ? 's' : ''} sans remboursement enregistré. Chaque entrée sera créée avec les montants projetés.
</p>
<div style={{ maxHeight: 260, overflowY: 'auto', border: '1px solid var(--border)', borderRadius: 6 }}>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
<thead>
<tr style={{ background: 'var(--surface-2)' }}>
<th style={{ padding: '6px 10px', textAlign: 'left', fontWeight: 600 }}>Date</th>
<th style={{ padding: '6px 10px', textAlign: 'right', fontWeight: 600 }}>Capital</th>
<th style={{ padding: '6px 10px', textAlign: 'right', fontWeight: 600 }}>Intérêts bruts</th>
<th style={{ padding: '6px 10px', textAlign: 'right', fontWeight: 600 }}>Total</th>
</tr>
</thead>
<tbody>
{bulkRembItems.map((item, idx) => (
<tr key={idx} style={{ borderTop: '1px solid var(--border)' }}>
<td style={{ padding: '6px 10px' }}>{fmtDate(item.payload.date_remb)}</td>
<td style={{ padding: '6px 10px', textAlign: 'right' }}>{fmtEUR(item.payload.capital)}</td>
<td style={{ padding: '6px 10px', textAlign: 'right' }}>{fmtEUR(item.payload.interets_bruts)}</td>
<td style={{ padding: '6px 10px', textAlign: 'right', fontWeight: 600 }}>{fmtEUR(item.payload.capital + item.payload.interets_bruts)}</td>
) : (() => {
const selectedCount = bulkRembItems.filter(it => it.selected).length;
const allSelected = selectedCount === bulkRembItems.length;
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
<p style={{ margin: 0, fontSize: 13, color: 'var(--text-muted)', lineHeight: 1.6 }}>
{bulkRembItems.length} échéance{bulkRembItems.length > 1 ? 's' : ''} passée{bulkRembItems.length > 1 ? 's' : ''} sans remboursement enregistré. Décochez celles à exclure — chaque entrée cochée sera créée avec les montants projetés.
</p>
<div style={{ maxHeight: 260, overflowY: 'auto', border: '1px solid var(--border)', borderRadius: 6 }}>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
<thead>
<tr style={{ background: 'var(--surface-2)' }}>
<th style={{ padding: '6px 8px', width: 28 }}>
<input type="checkbox" checked={allSelected}
onChange={e => toggleBulkRembAll(e.target.checked)} />
</th>
<th style={{ padding: '6px 10px', textAlign: 'left', fontWeight: 600 }}>Date</th>
<th style={{ padding: '6px 10px', textAlign: 'right', fontWeight: 600 }}>Capital</th>
<th style={{ padding: '6px 10px', textAlign: 'right', fontWeight: 600 }}>Intérêts bruts</th>
<th style={{ padding: '6px 10px', textAlign: 'right', fontWeight: 600 }}>Total</th>
</tr>
))}
</tbody>
</table>
</thead>
<tbody>
{bulkRembItems.map((item, idx) => (
<tr key={idx}
onClick={() => toggleBulkRembItem(idx)}
style={{
borderTop: '1px solid var(--border)', cursor: 'pointer',
opacity: item.selected ? 1 : 0.45,
}}>
<td style={{ padding: '6px 8px', textAlign: 'center' }}>
<input type="checkbox" checked={item.selected}
onClick={e => e.stopPropagation()}
onChange={() => toggleBulkRembItem(idx)} />
</td>
<td style={{ padding: '6px 10px' }}>{fmtDate(item.payload.date_remb)}</td>
<td style={{ padding: '6px 10px', textAlign: 'right' }}>{fmtEUR(item.payload.capital)}</td>
<td style={{ padding: '6px 10px', textAlign: 'right' }}>{fmtEUR(item.payload.interets_bruts)}</td>
<td style={{ padding: '6px 10px', textAlign: 'right', fontWeight: 600 }}>{fmtEUR(item.payload.capital + item.payload.interets_bruts)}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
)}
);
})()}
</Modal>
{simulMenu && (