Ajout des mouvements nécessaire à la vérification du solde
This commit is contained in:
1 parent
ea01cf3205
commit
e95b0db5d5
1 file changed
+316
-25
@@ -56,24 +56,40 @@ function TrendBadge({ current, prev, invert = false }) {
|
||||
|
||||
|
||||
|
||||
// Montant signé, identique à ce qui est affiché à l'écran (+ crédit / − débit),
|
||||
// pour permettre de sommer directement l'extraction et contrôler le solde.
|
||||
// depot/remboursement/bonus → crédit (+) ; retrait/souscription → débit (−) ;
|
||||
// correction → déjà signé en base (montant peut être positif ou négatif).
|
||||
function signedMontant(r) {
|
||||
if (r.type === 'retrait' || r.type === 'souscription') return -Math.abs(r.montant);
|
||||
if (r.type === 'correction') return r.montant;
|
||||
return Math.abs(r.montant);
|
||||
}
|
||||
|
||||
function mouvToCSV(rows) {
|
||||
const BOM = ''; const sep = ';';
|
||||
const q = v => `"${String(v ?? '').replace(/"/g, '""')}"`;
|
||||
const headers = ['Date','Plateforme','Type','Montant (€)','Libellé','Référence','Source'];
|
||||
const headers = ['Date','Plateforme','Type','Projet','Montant (€)','Libellé','Référence','Source'];
|
||||
const data = rows.map(r => [
|
||||
r.date_operation, r.plateforme_nom||'', r.type,
|
||||
String(r.montant).replace('.',','), r.libelle||'', r.reference||'', r.source||'',
|
||||
r.date_operation, r.plateforme_nom||'', MOUV_TYPE_LABELS[r.type] || r.type, r._projet||'',
|
||||
String(signedMontant(r)).replace('.',','), r.libelle||'', r.reference||'', r.source||'',
|
||||
]);
|
||||
return BOM + [headers,...data].map(r => r.map(q).join(sep)).join('\r\n');
|
||||
}
|
||||
|
||||
const MOUV_TYPE_LABELS = {
|
||||
depot: 'Dépôt', retrait: 'Retrait', correction: 'Correction de solde',
|
||||
remboursement: 'Remboursement', bonus: 'Bonus / Cashback', souscription: 'Souscription',
|
||||
};
|
||||
|
||||
function mouvToXLS(rows, multiDetenteur) {
|
||||
const data = rows.map(r => ({
|
||||
'Date': r.date_operation,
|
||||
'Plateforme': r.plateforme_nom || '',
|
||||
...(multiDetenteur ? { 'Détenteur': r.plateforme_detenteur_nom || '' } : {}),
|
||||
'Type': r.type === 'depot' ? 'Dépôt' : 'Retrait',
|
||||
'Montant (€)': r.montant,
|
||||
'Type': MOUV_TYPE_LABELS[r.type] || r.type,
|
||||
'Projet': r._projet || '',
|
||||
'Montant (€)': signedMontant(r),
|
||||
}));
|
||||
const ws = XLSX.utils.json_to_sheet(data);
|
||||
const wb = XLSX.utils.book_new();
|
||||
@@ -115,7 +131,8 @@ function mouvToJSON(rows) {
|
||||
date_operation: r.date_operation,
|
||||
plateforme_nom: r.plateforme_nom || '',
|
||||
type: r.type,
|
||||
montant: r.montant,
|
||||
projet: r._projet || null,
|
||||
montant: signedMontant(r),
|
||||
libelle: r.libelle || null,
|
||||
reference: r.reference || null,
|
||||
source: r.source || null,
|
||||
@@ -184,7 +201,7 @@ function ExportDropdown({ disabled, onCSV, onXLS, onJSON }) {
|
||||
}
|
||||
|
||||
/* ── Panneau de détail d'un mouvement ────────────────────────── */
|
||||
function DetailPanel({ row, onEdit, onDeleteCorrection }) {
|
||||
function DetailPanel({ row, onEdit, onDeleteCorrection, onViewRemb, onViewInvestissement }) {
|
||||
if (!row) return (
|
||||
<div className="dr-detail dr-detail-empty">
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ opacity: 0.25, marginBottom: 8 }}>
|
||||
@@ -194,16 +211,25 @@ function DetailPanel({ row, onEdit, onDeleteCorrection }) {
|
||||
</div>
|
||||
);
|
||||
|
||||
const isCorrection = row.type === 'correction';
|
||||
const isAuto = row.source === 'auto_remboursement';
|
||||
const isCorrection = row.type === 'correction';
|
||||
const isRemb = row.type === 'remboursement' || row.type === 'bonus';
|
||||
const isBonus = row.type === 'bonus';
|
||||
const isSouscription = row.type === 'souscription';
|
||||
const isAuto = row.source === 'auto_remboursement';
|
||||
|
||||
const typeLabel = isCorrection ? 'Correction de solde'
|
||||
: isBonus ? 'Bonus / Cashback'
|
||||
: row.type === 'remboursement' ? 'Remboursement (porte-monnaie)'
|
||||
: isSouscription ? (row._isReinvest ? 'Réinvestissement' : 'Souscription à un investissement')
|
||||
: row.type === 'depot' ? 'Dépôt' : 'Retrait';
|
||||
|
||||
const editLabel = isCorrection ? 'Modifier la correction de solde'
|
||||
: row.type === 'depot' ? 'Modifier le dépôt' : 'Modifier le retrait';
|
||||
|
||||
const isPos = row.montant >= 0;
|
||||
// Débit (−) pour retrait/souscription, crédit (+) pour dépôt/remboursement/bonus ;
|
||||
// la correction est déjà signée en base.
|
||||
const isDebitType = row.type === 'retrait' || isSouscription;
|
||||
const isPos = isCorrection ? row.montant >= 0 : !isDebitType;
|
||||
const sign = isPos ? '+' : '−';
|
||||
const montantFmt = `${sign} ${fmtEUR(Math.abs(row.montant))}`;
|
||||
const montantColor = undefined;
|
||||
@@ -213,8 +239,14 @@ function DetailPanel({ row, onEdit, onDeleteCorrection }) {
|
||||
{ label: 'Type de mouvement', value: typeLabel },
|
||||
{ label: 'Montant', value: montantFmt, color: montantColor },
|
||||
{ label: 'Date', value: fmtDate(row.date_operation) },
|
||||
!isCorrection && row.libelle && { label: 'Libellé', value: row.libelle },
|
||||
!isCorrection && row.reference && { label: 'Référence', value: row.reference },
|
||||
isRemb && !isBonus && row.libelle && { label: 'Projet', value: row.libelle },
|
||||
isRemb && !isBonus && { label: 'Capital', value: fmtEUR(row._capital) },
|
||||
isRemb && !isBonus && row._cashback > 0 && { label: 'Cashback', value: fmtEUR(row._cashback) },
|
||||
isRemb && !isBonus && { label: 'Intérêts bruts', value: fmtEUR(row._interets_bruts) },
|
||||
isRemb && !isBonus && { label: 'Traitement fiscal', value: row._isFlatTax ? 'Flat tax France (PFU prélevé à la source)' : 'Étranger — indicatif, aucun prélèvement à la source ici' },
|
||||
isSouscription && row.libelle && { label: 'Projet', value: row.libelle },
|
||||
!isCorrection && !isRemb && !isSouscription && row.libelle && { label: 'Libellé', value: row.libelle },
|
||||
!isCorrection && !isRemb && row.reference && { label: 'Référence', value: row.reference },
|
||||
row.notes && { label: 'Notes', value: row.notes },
|
||||
].filter(Boolean);
|
||||
|
||||
@@ -244,6 +276,54 @@ function DetailPanel({ row, onEdit, onDeleteCorrection }) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isRemb && (
|
||||
<div style={{
|
||||
display: 'flex', alignItems: 'flex-start', gap: 8,
|
||||
margin: '5px 5px 12px',
|
||||
padding: '9px 12px', borderRadius: 6,
|
||||
background: 'color-mix(in srgb, var(--success) 10%, transparent)',
|
||||
border: '1px solid color-mix(in srgb, var(--success) 25%, transparent)',
|
||||
fontSize: 'var(--fs-sm)', color: 'var(--text-muted)',
|
||||
lineHeight: 1.4,
|
||||
}}>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
|
||||
style={{ marginTop: 1, flexShrink: 0, color: 'var(--success)' }}>
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
||||
</svg>
|
||||
<span>
|
||||
{isBonus
|
||||
? 'Bonus crédité directement au porte-monnaie de la plateforme.'
|
||||
: row._isAutoSweep
|
||||
? "Remboursement crédité sur le porte-monnaie puis reversé aussitôt sur le compte courant (retrait automatique associé, même montant). Effet net nul sur le solde. Modifiable depuis la page Remboursements."
|
||||
: "Remboursement versé sur le porte-monnaie de la plateforme (méthode « portefeuille »). Modifiable depuis la page Remboursements."}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isSouscription && (
|
||||
<div style={{
|
||||
display: 'flex', alignItems: 'flex-start', gap: 8,
|
||||
margin: '5px 5px 12px',
|
||||
padding: '9px 12px', borderRadius: 6,
|
||||
background: 'color-mix(in srgb, var(--danger) 10%, transparent)',
|
||||
border: '1px solid color-mix(in srgb, var(--danger) 25%, transparent)',
|
||||
fontSize: 'var(--fs-sm)', color: 'var(--text-muted)',
|
||||
lineHeight: 1.4,
|
||||
}}>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
|
||||
style={{ marginTop: 1, flexShrink: 0, color: 'var(--danger)' }}>
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
||||
</svg>
|
||||
<span>
|
||||
{row._isReinvest
|
||||
? "Réinvestissement automatique du capital d'intérêts dans un nouvel investissement — capital sorti du porte-monnaie de la plateforme."
|
||||
: "Souscription à un investissement — capital sorti du porte-monnaie de la plateforme."}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="dr-detail-fields">
|
||||
{fields.map(f => (
|
||||
<div className="dr-detail-field" key={f.label}>
|
||||
@@ -254,7 +334,15 @@ function DetailPanel({ row, onEdit, onDeleteCorrection }) {
|
||||
</div>
|
||||
|
||||
<div className="dr-detail-footer">
|
||||
{isAuto ? null : isCorrection ? (
|
||||
{isAuto ? null : isRemb ? (
|
||||
<button className="dr-detail-edit-btn" onClick={() => onViewRemb(row)}>
|
||||
Voir / modifier le remboursement
|
||||
</button>
|
||||
) : isSouscription ? (
|
||||
<button className="dr-detail-edit-btn" onClick={() => onViewInvestissement(row)}>
|
||||
Voir l'investissement
|
||||
</button>
|
||||
) : isCorrection ? (
|
||||
<button className="dr-detail-edit-btn" onClick={() => onDeleteCorrection(row._correctionId)}>
|
||||
Supprimer la correction
|
||||
</button>
|
||||
@@ -394,6 +482,7 @@ export default function DepotsRetraits() {
|
||||
const [drPlatYear, setDrPlatYear] = useState(currentYear);
|
||||
const [allRemb, setAllRemb] = useState([]);
|
||||
const [allInv, setAllInv] = useState([]);
|
||||
const [allReinvests, setAllReinvests] = useState([]);
|
||||
|
||||
const [allCorrections, setAllCorrections] = useState([]);
|
||||
const [deleteConfirm, setDeleteConfirm] = useState(null);
|
||||
@@ -444,15 +533,19 @@ export default function DepotsRetraits() {
|
||||
setDashData(null);
|
||||
const scopeParams = activeView === 'all' ? { scope: 'all' } : undefined;
|
||||
try {
|
||||
const [r, p, d, remb, inv, corr] = await Promise.all([
|
||||
const [r, p, d, remb, inv, corr, reinvests] = await Promise.all([
|
||||
api.get('/depots-retraits', scopeParams),
|
||||
api.get('/plateformes'),
|
||||
api.get('/dashboard', scopeParams),
|
||||
api.get('/remboursements', scopeParams),
|
||||
api.get('/investissements', scopeParams),
|
||||
api.get('/corrections', scopeParams),
|
||||
// Toujours scope 'all' — filtré côté client via allInv (déjà scopé) pour respecter
|
||||
// le même pattern que Plateformes.jsx (route /reinvestissements ne connaît que l'utilisateur).
|
||||
api.get('/reinvestissements', { scope: 'all' }),
|
||||
]);
|
||||
setAllRows(r); setPlats(p); setDashData(d); setAllRemb(remb); setAllInv(inv); setAllCorrections(corr);
|
||||
setAllReinvests(Array.isArray(reinvests) ? reinvests : []);
|
||||
} catch (e) {
|
||||
console.error('DepotsRetraits load error:', e);
|
||||
} finally { setLoading(false); }
|
||||
@@ -517,14 +610,140 @@ export default function DepotsRetraits() {
|
||||
return true;
|
||||
}), [normalizedCorrections, filter.plateforme_ids, filter.year, filter.month]);
|
||||
|
||||
// tableRows : dépôts/retraits + corrections, triés par date desc
|
||||
// Remboursements affichés comme mouvements du porte-monnaie de la plateforme —
|
||||
// règle fiscale : flat_tax (France) → net_recu (PFU déjà prélevé) ;
|
||||
// hors France → capital + cashback + intérêts bruts (pas de PFU prélevé à la source ici).
|
||||
// Les bonus (parrainage/plateforme) créditent le cashback, indépendamment de la fiscalité du prêt.
|
||||
// Note : les remboursements methode_remboursement='compte_courant' transitent bien par le
|
||||
// porte-monnaie (crédit) avant d'en être aussitôt sortis par le retrait automatique
|
||||
// (source='auto_remboursement', même montant) — on affiche donc les deux mouvements pour
|
||||
// que l'historique soit lisible, même si l'effet net sur le solde est nul (cf. calcul KPI
|
||||
// porte-monnaie ci-dessous, qui lui continue d'ignorer ces remboursements).
|
||||
const normalizedRembMouvements = useMemo(() => {
|
||||
const out = [];
|
||||
for (const r of allRemb) {
|
||||
const isBonus = r.type === 'bonus_parrainage' || r.type === 'bonus_plateforme';
|
||||
const plat = plats.find(p => p.id === r.plateforme_id);
|
||||
const montant = isBonus
|
||||
? (r.cashback || 0)
|
||||
: (plat?.fiscalite === 'flat_tax'
|
||||
? (r.net_recu || 0)
|
||||
: (r.capital || 0) + (r.cashback || 0) + (r.interets_bruts || 0));
|
||||
if (!montant) continue;
|
||||
out.push({
|
||||
id: `remb_${r.id}`,
|
||||
_rembId: r.id,
|
||||
plateforme_id: r.plateforme_id,
|
||||
plateforme_nom: r.plateforme_nom,
|
||||
plateforme_detenteur_nom: r.plateforme_detenteur_nom,
|
||||
date_operation: r.date_remb,
|
||||
type: isBonus ? 'bonus' : 'remboursement',
|
||||
montant,
|
||||
libelle: r.nom_projet || '',
|
||||
source: 'remboursement',
|
||||
investisseur_id: r.resolved_investisseur_id ?? null,
|
||||
_isFlatTax: !isBonus && plat?.fiscalite === 'flat_tax',
|
||||
_isAutoSweep: !isBonus && r.methode_remboursement === 'compte_courant',
|
||||
_capital: r.capital || 0,
|
||||
_cashback: r.cashback || 0,
|
||||
_interets_bruts: r.interets_bruts || 0,
|
||||
_interets_nets: r.interets_nets || 0,
|
||||
_net_recu: r.net_recu || 0,
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}, [allRemb, plats]);
|
||||
|
||||
// Filtrés (plateforme + année + mois, sans filtre type — appliqué au merge dans tableRows)
|
||||
const filteredRembMouvements = useMemo(() => normalizedRembMouvements.filter(r => {
|
||||
if (filter.plateforme_ids.length > 0 && !filter.plateforme_ids.includes(String(r.plateforme_id))) return false;
|
||||
const [y, m] = r.date_operation.split('-');
|
||||
if (filter.year && y !== filter.year) return false;
|
||||
if (filter.month && m !== filter.month.padStart(2,'0')) return false;
|
||||
return true;
|
||||
}), [normalizedRembMouvements, filter.plateforme_ids, filter.year, filter.month]);
|
||||
|
||||
// Projet lié à un remboursement (utilisé pour retrouver le nom du projet
|
||||
// sur les retraits AUTO, qui ne portent que remboursement_id)
|
||||
const rembProjetById = useMemo(() => new Map(allRemb.map(r => [r.id, r.nom_projet || ''])), [allRemb]);
|
||||
|
||||
// Investissements par id (déjà scopés) — sert à rattacher les réinvestissements
|
||||
// (qui ne portent que investissement_id) à leur plateforme/projet/détenteur.
|
||||
const invById = useMemo(() => new Map(allInv.map(i => [i.id, i])), [allInv]);
|
||||
|
||||
// Souscriptions à un investissement (débit du porte-monnaie) + réinvestissements —
|
||||
// les réinvestissements automatiques de capital d'intérêts sont aussi traités comme une
|
||||
// souscription (débit) : cohérent avec la formule du solde porte-monnaie, qui les
|
||||
// soustrait tous deux (capital_investi = montant_investi + Σ réinvestissements).
|
||||
const normalizedSouscriptionMouvements = useMemo(() => {
|
||||
const out = [];
|
||||
for (const inv of allInv) {
|
||||
if (!inv.montant_investi) continue;
|
||||
out.push({
|
||||
id: `sub_${inv.id}`,
|
||||
_invId: inv.id,
|
||||
plateforme_id: inv.plateforme_id,
|
||||
plateforme_nom: inv.plateforme_nom,
|
||||
plateforme_detenteur_nom: inv.plateforme_detenteur_nom,
|
||||
date_operation: inv.date_souscription,
|
||||
type: 'souscription',
|
||||
montant: inv.montant_investi,
|
||||
libelle: inv.nom_projet || '',
|
||||
source: 'souscription',
|
||||
investisseur_id: inv.investisseur_id ?? null,
|
||||
_isReinvest: false,
|
||||
});
|
||||
}
|
||||
for (const rv of allReinvests) {
|
||||
const inv = invById.get(rv.investissement_id);
|
||||
if (!inv || !rv.montant) continue;
|
||||
out.push({
|
||||
id: `reinv_${rv.id}`,
|
||||
_invId: inv.id,
|
||||
plateforme_id: inv.plateforme_id,
|
||||
plateforme_nom: inv.plateforme_nom,
|
||||
plateforme_detenteur_nom: inv.plateforme_detenteur_nom,
|
||||
date_operation: rv.date_reinvestissement,
|
||||
type: 'souscription',
|
||||
montant: rv.montant,
|
||||
libelle: inv.nom_projet || '',
|
||||
source: 'reinvestissement',
|
||||
investisseur_id: inv.investisseur_id ?? null,
|
||||
_isReinvest: true,
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}, [allInv, allReinvests, invById]);
|
||||
|
||||
const filteredSouscriptionMouvements = useMemo(() => normalizedSouscriptionMouvements.filter(r => {
|
||||
if (filter.plateforme_ids.length > 0 && !filter.plateforme_ids.includes(String(r.plateforme_id))) return false;
|
||||
const [y, m] = r.date_operation.split('-');
|
||||
if (filter.year && y !== filter.year) return false;
|
||||
if (filter.month && m !== filter.month.padStart(2,'0')) return false;
|
||||
return true;
|
||||
}), [normalizedSouscriptionMouvements, filter.plateforme_ids, filter.year, filter.month]);
|
||||
|
||||
// tableRows : dépôts/retraits + corrections + remboursements/bonus + souscriptions, triés par date desc.
|
||||
// _projet : nom du projet quand cela a du sens (remboursement, souscription/réinvestissement,
|
||||
// et retrait AUTO lié à un remboursement via remboursement_id) — vide pour dépôt/retrait
|
||||
// manuel, correction, bonus.
|
||||
const tableRows = useMemo(() => {
|
||||
const drPart = filter.type === 'correction' ? [] : rows;
|
||||
const corrPart = (filter.type === 'depot' || filter.type === 'retrait') ? [] : filteredCorrections;
|
||||
return [...drPart, ...corrPart].sort((a, b) =>
|
||||
const showDR = !filter.type || filter.type === 'depot' || filter.type === 'retrait';
|
||||
const showCorr = !filter.type || filter.type === 'correction';
|
||||
const drPart = showDR
|
||||
? rows.map(r => ({ ...r, _projet: r.remboursement_id ? (rembProjetById.get(r.remboursement_id) || '') : '' }))
|
||||
: [];
|
||||
const corrPart = showCorr ? filteredCorrections.map(c => ({ ...c, _projet: '' })) : [];
|
||||
const rembPart = filteredRembMouvements
|
||||
.filter(r => !filter.type || r.type === filter.type)
|
||||
.map(r => ({ ...r, _projet: r.type === 'bonus' ? '' : (r.libelle || '') }));
|
||||
const subPart = filteredSouscriptionMouvements
|
||||
.filter(r => !filter.type || r.type === filter.type)
|
||||
.map(r => ({ ...r, _projet: r.libelle || '' }));
|
||||
return [...drPart, ...corrPart, ...rembPart, ...subPart].sort((a, b) =>
|
||||
b.date_operation.localeCompare(a.date_operation) || String(b.id).localeCompare(String(a.id))
|
||||
);
|
||||
}, [rows, filteredCorrections, filter.type]);
|
||||
}, [rows, filteredCorrections, filteredRembMouvements, filteredSouscriptionMouvements, filter.type, rembProjetById]);
|
||||
|
||||
/* ── Pagination mouvements ── */
|
||||
const {
|
||||
@@ -1214,10 +1433,10 @@ export default function DepotsRetraits() {
|
||||
)}
|
||||
</button>
|
||||
<ExportDropdown
|
||||
disabled={rows.length === 0}
|
||||
onCSV={() => dlBlob(mouvToCSV(rows), `Mouvements Dépôts Retraits ${timestampSuffix()}.csv`, 'text/csv;charset=utf-8')}
|
||||
onXLS={() => dlBlob(mouvToXLS(rows, multiDetenteur), `Mouvements Dépôts Retraits ${timestampSuffix()}.xlsx`, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')}
|
||||
onJSON={() => dlBlob(mouvToJSON(rows), `Mouvements Dépôts Retraits ${timestampSuffix()}.json`, 'application/json')}
|
||||
disabled={tableRows.length === 0}
|
||||
onCSV={() => dlBlob(mouvToCSV(tableRows), `Mouvements Dépôts Retraits ${timestampSuffix()}.csv`, 'text/csv;charset=utf-8')}
|
||||
onXLS={() => dlBlob(mouvToXLS(tableRows, multiDetenteur), `Mouvements Dépôts Retraits ${timestampSuffix()}.xlsx`, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')}
|
||||
onJSON={() => dlBlob(mouvToJSON(tableRows), `Mouvements Dépôts Retraits ${timestampSuffix()}.json`, 'application/json')}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@@ -1241,6 +1460,9 @@ export default function DepotsRetraits() {
|
||||
<option value="depot">Dépôts</option>
|
||||
<option value="retrait">Retraits</option>
|
||||
<option value="correction">Corrections de solde</option>
|
||||
<option value="remboursement">Remboursements</option>
|
||||
<option value="bonus">Bonus / Cashback</option>
|
||||
<option value="souscription">Souscriptions</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style={{ flex: 2, minWidth: 160, position: 'relative' }} ref={platFilterRef}>
|
||||
@@ -1307,12 +1529,12 @@ export default function DepotsRetraits() {
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th><th>Plateforme</th>{multiDetenteur && <th>Détenteur</th>}<th>Type de mouvement</th><th className="num">Montant</th><th style={{ width: 36 }}></th>
|
||||
<th>Date</th><th>Plateforme</th>{multiDetenteur && <th>Détenteur</th>}<th>Type de mouvement</th><th>Projet</th><th className="num">Montant</th><th style={{ width: 36 }}></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tableRows.length === 0 && (
|
||||
<tr><td colSpan={multiDetenteur ? 6 : 5} className="text-muted" style={{ textAlign: 'center', padding: 24 }}>
|
||||
<tr><td colSpan={multiDetenteur ? 7 : 6} className="text-muted" style={{ textAlign: 'center', padding: 24 }}>
|
||||
{loading ? 'Chargement…' : 'Aucun mouvement'}
|
||||
</td></tr>
|
||||
)}
|
||||
@@ -1336,6 +1558,7 @@ export default function DepotsRetraits() {
|
||||
color: 'var(--text-muted)',
|
||||
}}>correction</span>
|
||||
</td>
|
||||
<td className="text-muted">—</td>
|
||||
<td className={`num ${isPos ? 'success' : 'danger'}`}>
|
||||
{isPos ? '+' : '−'} {fmtEUR(Math.abs(r.montant))}
|
||||
</td>
|
||||
@@ -1347,6 +1570,71 @@ export default function DepotsRetraits() {
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
if (r.type === 'remboursement' || r.type === 'bonus') {
|
||||
const isBonus = r.type === 'bonus';
|
||||
return (
|
||||
<tr key={r.id}
|
||||
className={`dr-row${selectedRow?.id === r.id ? ' dr-row-selected' : ''}`}
|
||||
onClick={() => setSelectedRow(selectedRow?.id === r.id ? null : r)}
|
||||
style={{ cursor: 'pointer' }}>
|
||||
<td>{fmtDate(r.date_operation)}</td>
|
||||
<td>{r.plateforme_nom}</td>
|
||||
{multiDetenteur && <td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>{r.plateforme_detenteur_nom || '—'}</td>}
|
||||
<td>
|
||||
<span style={{
|
||||
display: 'inline-block',
|
||||
fontSize: 11, fontWeight: 600, letterSpacing: '0.03em',
|
||||
padding: '2px 7px', borderRadius: 4,
|
||||
background: isBonus
|
||||
? 'color-mix(in srgb, var(--warning) 18%, transparent)'
|
||||
: 'color-mix(in srgb, var(--success) 15%, transparent)',
|
||||
color: isBonus ? 'var(--warning)' : 'var(--success)',
|
||||
}}>{isBonus ? 'bonus' : 'remboursement'}</span>
|
||||
</td>
|
||||
<td className="text-muted">{r._projet || '—'}</td>
|
||||
<td className="num success">
|
||||
+ {fmtEUR(r.montant)}
|
||||
</td>
|
||||
<td style={{ width: 36, textAlign: 'center' }} />
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
if (r.type === 'souscription') {
|
||||
return (
|
||||
<tr key={r.id}
|
||||
className={`dr-row${selectedRow?.id === r.id ? ' dr-row-selected' : ''}`}
|
||||
onClick={() => setSelectedRow(selectedRow?.id === r.id ? null : r)}
|
||||
style={{ cursor: 'pointer' }}>
|
||||
<td>{fmtDate(r.date_operation)}</td>
|
||||
<td>{r.plateforme_nom}</td>
|
||||
{multiDetenteur && <td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>{r.plateforme_detenteur_nom || '—'}</td>}
|
||||
<td>
|
||||
<span style={{
|
||||
display: 'inline-block',
|
||||
fontSize: 11, fontWeight: 600, letterSpacing: '0.03em',
|
||||
padding: '2px 7px', borderRadius: 4,
|
||||
background: 'color-mix(in srgb, #a855f7 15%, transparent)',
|
||||
color: '#a855f7',
|
||||
}}>souscription</span>
|
||||
{r._isReinvest && (
|
||||
<span style={{
|
||||
marginLeft: 6,
|
||||
fontSize: 10, fontWeight: 600, letterSpacing: '0.03em',
|
||||
padding: '1px 5px', borderRadius: 3,
|
||||
background: 'color-mix(in srgb, var(--primary) 15%, transparent)',
|
||||
color: 'var(--primary)',
|
||||
verticalAlign: 'middle',
|
||||
}}>RÉINVEST.</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="text-muted">{r._projet || '—'}</td>
|
||||
<td className="num danger">
|
||||
− {fmtEUR(r.montant)}
|
||||
</td>
|
||||
<td style={{ width: 36, textAlign: 'center' }} />
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
const isAuto = r.source === 'auto_remboursement';
|
||||
return (
|
||||
<tr key={r.id}
|
||||
@@ -1369,6 +1657,7 @@ export default function DepotsRetraits() {
|
||||
}}>AUTO</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="text-muted">{r._projet || '—'}</td>
|
||||
<td className={`num ${r.type === 'depot' ? 'success' : 'danger'}`}
|
||||
style={ isAuto ? { fontStyle: 'italic' } : undefined }>
|
||||
{r.type === 'depot' ? '+' : '−'} {fmtEUR(r.montant)}
|
||||
@@ -1402,6 +1691,8 @@ export default function DepotsRetraits() {
|
||||
row={selectedRow}
|
||||
onEdit={openEdit}
|
||||
onDeleteCorrection={deleteCorrection}
|
||||
onViewRemb={r => navigate(`/remboursements?edit-remb=${r._rembId}`)}
|
||||
onViewInvestissement={r => navigate(`/investissements/${r._invId}`)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user