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;
|
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,61 +2378,80 @@ 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>
|
||||||
) : (
|
) : (() => {
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
|
const selectedCount = bulkRembItems.filter(it => it.selected).length;
|
||||||
<p style={{ margin: 0, fontSize: 13, color: 'var(--text-muted)', lineHeight: 1.6 }}>
|
const allSelected = selectedCount === bulkRembItems.length;
|
||||||
{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.
|
return (
|
||||||
</p>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
|
||||||
<div style={{ maxHeight: 260, overflowY: 'auto', border: '1px solid var(--border)', borderRadius: 6 }}>
|
<p style={{ margin: 0, fontSize: 13, color: 'var(--text-muted)', lineHeight: 1.6 }}>
|
||||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
|
{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.
|
||||||
<thead>
|
</p>
|
||||||
<tr style={{ background: 'var(--surface-2)' }}>
|
<div style={{ maxHeight: 260, overflowY: 'auto', border: '1px solid var(--border)', borderRadius: 6 }}>
|
||||||
<th style={{ padding: '6px 10px', textAlign: 'left', fontWeight: 600 }}>Date</th>
|
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
|
||||||
<th style={{ padding: '6px 10px', textAlign: 'right', fontWeight: 600 }}>Capital</th>
|
<thead>
|
||||||
<th style={{ padding: '6px 10px', textAlign: 'right', fontWeight: 600 }}>Intérêts bruts</th>
|
<tr style={{ background: 'var(--surface-2)' }}>
|
||||||
<th style={{ padding: '6px 10px', textAlign: 'right', fontWeight: 600 }}>Total</th>
|
<th style={{ padding: '6px 8px', width: 28 }}>
|
||||||
</tr>
|
<input type="checkbox" checked={allSelected}
|
||||||
</thead>
|
onChange={e => toggleBulkRembAll(e.target.checked)} />
|
||||||
<tbody>
|
</th>
|
||||||
{bulkRembItems.map((item, idx) => (
|
<th style={{ padding: '6px 10px', textAlign: 'left', fontWeight: 600 }}>Date</th>
|
||||||
<tr key={idx} style={{ borderTop: '1px solid var(--border)' }}>
|
<th style={{ padding: '6px 10px', textAlign: 'right', fontWeight: 600 }}>Capital</th>
|
||||||
<td style={{ padding: '6px 10px' }}>{fmtDate(item.payload.date_remb)}</td>
|
<th style={{ padding: '6px 10px', textAlign: 'right', fontWeight: 600 }}>Intérêts bruts</th>
|
||||||
<td style={{ padding: '6px 10px', textAlign: 'right' }}>{fmtEUR(item.payload.capital)}</td>
|
<th style={{ padding: '6px 10px', textAlign: 'right', fontWeight: 600 }}>Total</th>
|
||||||
<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>
|
</tr>
|
||||||
))}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
{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>
|
||||||
</div>
|
);
|
||||||
)}
|
})()}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
{simulMenu && (
|
{simulMenu && (
|
||||||
|
|||||||
Reference in new issue
Block a user