selection des remboursements en masse
This commit is contained in:
1 parent
eab9271b0f
commit
6453218e2b
1 file changed
+65
-36
@@ -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 && (
|
||||
|
||||
Reference in new issue
Block a user