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
+42 -13
+42 -13
View File
@@ -432,6 +432,7 @@ export default function InvestissementDetail() {
const baseRates = brutsApresLocal; const baseRates = brutsApresLocal;
return { return {
_simul: s, _simul: s,
selected: true,
payload: { payload: {
investissement_id: Number(id), investissement_id: Number(id),
date_remb: s.date_prevue, date_remb: s.date_prevue,
@@ -453,12 +454,21 @@ export default function InvestissementDetail() {
setBulkRembModal(true); 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 runBulkRemb = async () => {
const toProcess = bulkRembItems.filter(it => it.selected);
setBulkRembProcessing(true); setBulkRembProcessing(true);
setBulkRembProgress(0); setBulkRembProgress(0);
try { try {
for (let i = 0; i < bulkRembItems.length; i++) { for (let i = 0; i < toProcess.length; i++) {
await api.post('/remboursements', bulkRembItems[i].payload); await api.post('/remboursements', toProcess[i].payload);
setBulkRembProgress(i + 1); setBulkRembProgress(i + 1);
} }
setBulkRembDone(true); setBulkRembDone(true);
@@ -2368,41 +2378,49 @@ export default function InvestissementDetail() {
title="Traitement en masse des remboursements" title="Traitement en masse des remboursements"
onClose={() => { if (!bulkRembProcessing) setBulkRembModal(false); }} onClose={() => { if (!bulkRembProcessing) setBulkRembModal(false); }}
width={560} width={560}
footer={ footer={(() => {
bulkRembDone ? ( const selectedCount = bulkRembItems.filter(it => it.selected).length;
return bulkRembDone ? (
<div style={{ display: 'flex', justifyContent: 'flex-end' }}> <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<button className="primary" onClick={() => setBulkRembModal(false)}>Fermer</button> <button className="primary" onClick={() => setBulkRembModal(false)}>Fermer</button>
</div> </div>
) : ( ) : (
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}> <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
<button onClick={() => setBulkRembModal(false)} disabled={bulkRembProcessing}>Annuler</button> <button onClick={() => setBulkRembModal(false)} disabled={bulkRembProcessing}>Annuler</button>
<button className="primary" onClick={runBulkRemb} disabled={bulkRembProcessing || bulkRembItems.length === 0}> <button className="primary" onClick={runBulkRemb} disabled={bulkRembProcessing || selectedCount === 0}>
{bulkRembProcessing ? `Traitement… (${bulkRembProgress}/${bulkRembItems.length})` : `Enregistrer ${bulkRembItems.length} remboursement${bulkRembItems.length > 1 ? 's' : ''}`} {bulkRembProcessing ? `Traitement… (${bulkRembProgress}/${selectedCount})` : `Enregistrer ${selectedCount} remboursement${selectedCount > 1 ? 's' : ''}`}
</button> </button>
</div> </div>
) );
} })()}
> >
{bulkRembDone ? ( {bulkRembDone ? (
<div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 0' }}> <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> <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 }}> <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> </span>
</div> </div>
) : bulkRembItems.length === 0 ? ( ) : bulkRembItems.length === 0 ? (
<p style={{ margin: 0, fontSize: 13, color: 'var(--text-muted)' }}> <p style={{ margin: 0, fontSize: 13, color: 'var(--text-muted)' }}>
Aucune échéance passée non enregistrée trouvée. Aucune échéance passée non enregistrée trouvée.
</p> </p>
) : ( ) : (() => {
const selectedCount = bulkRembItems.filter(it => it.selected).length;
const allSelected = selectedCount === bulkRembItems.length;
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
<p style={{ margin: 0, fontSize: 13, color: 'var(--text-muted)', lineHeight: 1.6 }}> <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. {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> </p>
<div style={{ maxHeight: 260, overflowY: 'auto', border: '1px solid var(--border)', borderRadius: 6 }}> <div style={{ maxHeight: 260, overflowY: 'auto', border: '1px solid var(--border)', borderRadius: 6 }}>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}> <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
<thead> <thead>
<tr style={{ background: 'var(--surface-2)' }}> <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: '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 }}>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 }}>Intérêts bruts</th>
@@ -2411,7 +2429,17 @@ export default function InvestissementDetail() {
</thead> </thead>
<tbody> <tbody>
{bulkRembItems.map((item, idx) => ( {bulkRembItems.map((item, idx) => (
<tr key={idx} style={{ borderTop: '1px solid var(--border)' }}> <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' }}>{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.capital)}</td>
<td style={{ padding: '6px 10px', textAlign: 'right' }}>{fmtEUR(item.payload.interets_bruts)}</td> <td style={{ padding: '6px 10px', textAlign: 'right' }}>{fmtEUR(item.payload.interets_bruts)}</td>
@@ -2422,7 +2450,8 @@ export default function InvestissementDetail() {
</table> </table>
</div> </div>
</div> </div>
)} );
})()}
</Modal> </Modal>
{simulMenu && ( {simulMenu && (