Correction de l'affichage en tant que mono détenteur

This commit is contained in:
2026-07-04 18:32:51 +02:00
parent 69d006d358
commit 11790c63fe
6 changed files with 109 additions and 37 deletions
+9 -9
View File
@@ -889,7 +889,7 @@ export default function DepotsRetraits() {
<EmptyState />
</>
);
if (!loading && !modalOpen && allRows.length === 0) return (
if (!loading && !modalOpen && !corrModal.open && allRows.length === 0) return (
<>
<div className="topbar"><h2>Dépôts / Retraits</h2></div>
<EmptyState
@@ -1029,11 +1029,11 @@ export default function DepotsRetraits() {
) : (
<table>
<thead>
<tr><th>Plateforme</th><th>Détenteur</th><th className="num">Dépôts</th><th className="num">Retraits</th><th className="num">Diff. Dépôts vs Retraits</th><th className="num">Porte-monnaie</th><th className="num">Poids</th><th style={{ width: 28 }}></th></tr>
<tr><th>Plateforme</th>{multiDetenteur && <th>Détenteur</th>}<th className="num">Dépôts</th><th className="num">Retraits</th><th className="num">Diff. Dépôts vs Retraits</th><th className="num">Porte-monnaie</th><th className="num">Poids</th><th style={{ width: 28 }}></th></tr>
</thead>
<tbody>
{drPlatData.length === 0 && (
<tr><td colSpan={8} className="text-muted" style={{ textAlign: 'center' }}>Aucune plateforme</td></tr>
<tr><td colSpan={multiDetenteur ? 8 : 7} className="text-muted" style={{ textAlign: 'center' }}>Aucune plateforme</td></tr>
)}
{(() => {
const totalSolde = drPlatTotals.solde_net;
@@ -1053,7 +1053,7 @@ export default function DepotsRetraits() {
<td>
<span style={{ fontWeight: isActive ? 600 : undefined }}>{p.nom}</span>
</td>
<td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>{p.detenteur_nom || ''}</td>
{multiDetenteur && <td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>{p.detenteur_nom || ''}</td>}
<td className="num">{fmtEUR(p.depots)}</td>
<td className="num">{fmtEUR(p.retraits)}</td>
<td className="num">{fmtEUR(p.solde_net)}</td>
@@ -1080,7 +1080,7 @@ export default function DepotsRetraits() {
<tfoot>
<tr style={{ borderTop: '2px solid var(--border)', fontWeight: 700, background: 'var(--surface-2)' }}>
<td>Total — {drPlatData.length} plateformes</td>
<td />
{multiDetenteur && <td />}
<td className="num">{fmtEUR(drPlatTotals.depots)}</td>
<td className="num">{fmtEUR(drPlatTotals.retraits)}</td>
<td className="num">{fmtEUR(drPlatTotals.solde_net)}</td>
@@ -1236,12 +1236,12 @@ export default function DepotsRetraits() {
<table>
<thead>
<tr>
<th>Date</th><th>Plateforme</th><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 className="num">Montant</th><th style={{ width: 36 }}></th>
</tr>
</thead>
<tbody>
{tableRows.length === 0 && (
<tr><td colSpan={5} className="text-muted" style={{ textAlign: 'center', padding: 24 }}>
<tr><td colSpan={multiDetenteur ? 6 : 5} className="text-muted" style={{ textAlign: 'center', padding: 24 }}>
{loading ? 'Chargement' : 'Aucun mouvement'}
</td></tr>
)}
@@ -1255,7 +1255,7 @@ export default function DepotsRetraits() {
style={{ cursor: 'pointer' }}>
<td>{fmtDate(r.date_operation)}</td>
<td>{r.plateforme_nom}</td>
<td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>{r.plateforme_detenteur_nom || ''}</td>
{multiDetenteur && <td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>{r.plateforme_detenteur_nom || ''}</td>}
<td>
<span style={{
display: 'inline-block',
@@ -1284,7 +1284,7 @@ export default function DepotsRetraits() {
style={{ cursor: 'pointer', opacity: isAuto ? 0.8 : 1 }}>
<td>{fmtDate(r.date_operation)}</td>
<td>{r.plateforme_nom}</td>
<td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>{r.plateforme_detenteur_nom || ''}</td>
{multiDetenteur && <td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>{r.plateforme_detenteur_nom || ''}</td>}
<td>
<span className={`badge ${r.type}`}>{r.type}</span>
{isAuto && (
+7 -7
View File
@@ -962,7 +962,7 @@ export default function Investissements() {
<thead>
<tr>
<th>Plateforme</th>
<th>Détenteur</th>
{multiDetenteur && <th>Détenteur</th>}
<th className="num">Projets</th>
<th className="num">Montant investi</th>
<th className="num">Capital investi</th>
@@ -974,7 +974,7 @@ export default function Investissements() {
</thead>
<tbody>
{platData.length === 0 && (
<tr><td colSpan={9} className="text-muted" style={{ textAlign: 'center', padding: 24 }}>
<tr><td colSpan={multiDetenteur ? 9 : 8} className="text-muted" style={{ textAlign: 'center', padding: 24 }}>
Aucun investissement
</td></tr>
)}
@@ -999,7 +999,7 @@ export default function Investissements() {
}}>!</span>
)}
</td>
<td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>{p.detenteur_nom || '—'}</td>
{multiDetenteur && <td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>{p.detenteur_nom || '—'}</td>}
<td className="num">{p.count}</td>
<td className="num">{fmtEUR(p.investi)}</td>
<td className="num">{fmtEUR(p.encours)}</td>
@@ -1032,7 +1032,7 @@ export default function Investissements() {
<td style={{ padding: '10px 12px', color: 'var(--text-muted)', fontSize: 'var(--fs-sm)', fontWeight: 600 }}>
Total {platData.length} plateformes
</td>
<td />
{multiDetenteur && <td />}
<td className="num" style={{ padding: '10px 12px' }}>{platTotals.count}</td>
<td className="num" style={{ padding: '10px 12px' }}>{fmtEUR(platTotals.investi)}</td>
<td className="num" style={{ padding: '10px 12px' }}>{fmtEUR(platTotals.encours)}</td>
@@ -1202,7 +1202,7 @@ export default function Investissements() {
<table style={{ cursor: 'pointer' }}>
<thead>
<tr>
<th>Date</th><th>Projet</th><th>Plateforme</th><th>Détenteur</th>
<th>Date</th><th>Projet</th><th>Plateforme</th>{multiDetenteur && <th>Détenteur</th>}
<th className="num">Montant</th><th className="num">Taux</th>
<th className="num">Durée</th><th>Type remb.</th>
<th className="num">Inv. remboursés</th><th className="num">Cap. restant </th>
@@ -1213,7 +1213,7 @@ export default function Investissements() {
</thead>
<tbody>
{rows.length === 0 && (
<tr><td colSpan={13} className="text-muted" style={{ textAlign: 'center', padding: 24 }}>
<tr><td colSpan={multiDetenteur ? 13 : 12} className="text-muted" style={{ textAlign: 'center', padding: 24 }}>
{loading ? 'Chargement…' : 'Aucun investissement'}
</td></tr>
)}
@@ -1233,7 +1233,7 @@ export default function Investissements() {
{r.emetteur && <div className="text-muted" style={{ fontSize: 11 }}>{r.emetteur}</div>}
</td>
<td>{r.plateforme_nom}</td>
<td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>{r.plateforme_detenteur_nom || '—'}</td>
{multiDetenteur && <td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>{r.plateforme_detenteur_nom || '—'}</td>}
<td className="num">{fmtEUR(capInv)}</td>
<td className="num">{r.taux_interet != null ? fmtPct(r.taux_interet) : '—'}</td>
<td className="num">{r.duree_mois ? `${r.duree_mois} m.` : '—'}</td>
+5 -2
View File
@@ -424,6 +424,9 @@ export default function Plateformes() {
];
}, [platRows, investisseurs]);
/* ── Vrai/faux multi-détenteur global (indépendant de la plateforme sélectionnée) ── */
const multiDetenteur = new Set(plats.map(p => p.investisseur_id)).size > 1;
/* ── Reset détenteur quand plateforme change (skip restauration initiale) ── */
const isFirstPlatChange = useRef(true);
useEffect(() => {
@@ -1534,7 +1537,7 @@ export default function Plateformes() {
<thead>
<tr>
<th>Projet</th>
<th>Détenteur</th>
{multiDetenteur && <th>Détenteur</th>}
<th className="num">Date de souscription</th>
<th className="num">Date cible</th>
<th className="num">Montant</th>
@@ -1555,7 +1558,7 @@ export default function Plateformes() {
<tr key={r.id} style={{ cursor: 'pointer' }}
onClick={() => navigate(`/investissements/${r.id}`, { state: { from: { path: location.pathname, search: location.search, label: 'Plateformes' } } })}>
<td style={{ fontWeight: 500 }}>{r.nom_projet}</td>
<td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>{(() => { const inv = investisseurs.find(i => i.id === r.investisseur_id); return inv ? memberLabel(inv) : '—'; })()}</td>
{multiDetenteur && <td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>{(() => { const inv = investisseurs.find(i => i.id === r.investisseur_id); return inv ? memberLabel(inv) : '—'; })()}</td>}
<td className="num" style={{ fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>{fmtDate(r.date_souscription)}</td>
<td className="num" style={{ fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>{fmtDate(r.date_cible)}</td>
<td className="num">{fmtEUR(r.montant_investi)}</td>
+11 -9
View File
@@ -899,7 +899,7 @@ export default function Remboursements() {
<thead>
<tr>
<th>Plateforme</th>
<th>Détenteur</th>
{multiDetenteur && <th>Détenteur</th>}
<th className="num">Capital remboursé</th>
<th className="num">Cashback</th>
<th className="num">Intérêts ({netMode ? 'Net' : 'Brut'})</th>
@@ -911,7 +911,7 @@ export default function Remboursements() {
</thead>
<tbody>
{byPlatform.length === 0 && (
<tr><td colSpan={9} className="text-muted" style={{ textAlign: 'center', padding: 24 }}>
<tr><td colSpan={multiDetenteur ? 9 : 8} className="text-muted" style={{ textAlign: 'center', padding: 24 }}>
{loading ? 'Chargement' : 'Aucun remboursement'}
</td></tr>
)}
@@ -927,7 +927,7 @@ export default function Remboursements() {
title={isActive ? 'Cliquer pour retirer le filtre' : 'Cliquer pour filtrer sur cette plateforme'}
>
<td><span style={{ fontWeight: isActive ? 600 : undefined }}>{p.nom}</span></td>
<td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>{p.detenteur_nom || ''}</td>
{multiDetenteur && <td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>{p.detenteur_nom || ''}</td>}
<td className="num">{fmtEUR(p.capital)}</td>
<td className="num">{p.cashback > 0 ? fmtEUR(p.cashback) : ''}</td>
<td className="num">{fmtEUR(val)}</td>
@@ -954,7 +954,7 @@ export default function Remboursements() {
<tfoot>
<tr style={{ borderTop: '2px solid var(--border)', fontWeight: 700, background: 'var(--surface-2)' }}>
<td>Total — {byPlatform.length} plateformes</td>
<td />
{multiDetenteur && <td />}
<td className="num">{fmtEUR(byPlatformTotals.capital)}</td>
<td className="num">{byPlatformTotals.cashback > 0 ? fmtEUR(byPlatformTotals.cashback) : ''}</td>
<td className="num">{fmtEUR(netMode ? byPlatformTotals.interets_nets : byPlatformTotals.interets_bruts)}</td>
@@ -1131,7 +1131,7 @@ export default function Remboursements() {
<table>
<thead>
<tr>
<th>Date</th><th>Plateforme</th><th>Détenteur</th><th>Nom du projet</th><th>Versement</th>
<th>Date</th><th>Plateforme</th>{multiDetenteur && <th>Détenteur</th>}<th>Nom du projet</th><th>Versement</th>
<th className="num">Capital Remboursé</th><th className="num">Capital Restant dû</th>
<th className="num">Cashback</th>
<th className="num">Intérêts Bruts</th>
@@ -1143,7 +1143,7 @@ export default function Remboursements() {
</thead>
<tbody>
{tableRows.length === 0 && (
<tr><td colSpan={14} className="text-muted" style={{ textAlign: 'center', padding: 24 }}>
<tr><td colSpan={multiDetenteur ? 14 : 13} className="text-muted" style={{ textAlign: 'center', padding: 24 }}>
{loading ? 'Chargement' : 'Aucun remboursement'}
</td></tr>
)}
@@ -1169,9 +1169,11 @@ export default function Remboursements() {
}}>
<td>{fmtDate(r.date_remb)}</td>
<td>{r.plateforme_nom}</td>
<td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>
{isCorrection ? (r.investisseur_nom || '') : (r.plateforme_detenteur_nom || '')}
</td>
{multiDetenteur && (
<td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>
{isCorrection ? (r.investisseur_nom || '') : (r.plateforme_detenteur_nom || '')}
</td>
)}
<td>
{isCorrection
? <span style={{ color: 'var(--text-muted)', fontStyle: 'italic' }}>{r.nom_projet}</span>
@@ -132,6 +132,8 @@ export default function ComptesSection() {
});
};
const multiDetenteur = investisseurs.length > 1;
return (
<>
<div className="card">
@@ -154,7 +156,7 @@ export default function ComptesSection() {
<tr>
<th>Nom</th>
<th style={{ width: '14%' }}>Type</th>
<th style={{ width: '22%' }}>Détenteur</th>
{multiDetenteur && <th style={{ width: '22%' }}>Détenteur</th>}
<th style={{ width: '16%' }}>Banque</th>
<th style={{ width: '10%' }}>Exonération</th>
<th style={{ width: 80 }} />
@@ -162,7 +164,7 @@ export default function ComptesSection() {
</thead>
<tbody>
{comptes.length === 0 && (
<tr><td colSpan={6} className="text-muted" style={{ textAlign: 'center', fontStyle: 'italic' }}>
<tr><td colSpan={multiDetenteur ? 6 : 5} className="text-muted" style={{ textAlign: 'center', fontStyle: 'italic' }}>
Aucun compte défini.
</td></tr>
)}
@@ -172,7 +174,7 @@ export default function ComptesSection() {
<tr key={c.id}>
<td style={{ fontWeight: 500 }}>{c.nom}</td>
<td><span className="badge">{TYPE_COMPTE_LABELS[c.type] ?? c.type}</span></td>
<td>{inv ? memberLabel(inv) : <span className="text-muted"></span>}</td>
{multiDetenteur && <td>{inv ? memberLabel(inv) : <span className="text-muted"></span>}</td>}
<td>{c.banque || <span className="text-muted"></span>}</td>
<td>
{c.exoneration_fiscale && c.exoneration_fiscale !== 'aucune' ? (
@@ -17,6 +17,12 @@ function dlBlob(content, filename, type) {
URL.revokeObjectURL(url);
}
/** Date du jour au format YYYY-MM-DD, en heure locale (ne pas utiliser toISOString ici, décale d'un jour selon le fuseau). */
function localToday() {
const d = new Date();
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
}
const countryLabel = code => COUNTRIES.find(c => c.code === code)?.name ?? code ?? '—';
const FISCALITE_LABELS = {
flat_tax: 'Flat Tax',
@@ -283,6 +289,13 @@ function PlatDetailPanel({ plat, onEdit }) {
function PlatPickerModal({ open, onClose, referentiel, onSelect, onManual, err }) {
const [search, setSearch] = useState('');
const [domFilter, setDomFilter] = useState('all'); // 'all' | 'fr' | 'etr'
const [confirming, setConfirming] = useState(null); // ref en cours de confirmation
const [dateOuverture, setDateOuverture] = useState('');
useEffect(() => {
if (open) { setConfirming(null); setDateOuverture(''); }
}, [open]);
if (!open) return null;
const q = search.trim().toLowerCase();
@@ -298,6 +311,53 @@ function PlatPickerModal({ open, onClose, referentiel, onSelect, onManual, err }
return f ? (LOGO_BASE + f) : null;
};
/* ── Étape de confirmation : date d'ouverture du compte ── */
if (confirming) {
const img = imgFor(confirming);
return (
<Modal
open={open}
title="Ajouter une plateforme"
onClose={onClose}
width={480}
footer={
<div style={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}>
<button type="button" className="ghost" onClick={() => setConfirming(null)}> Retour</button>
<button type="button" className="primary" onClick={() => onSelect(confirming, dateOuverture)}>
Ajouter
</button>
</div>
}
>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
{err && <div className="error">{err}</div>}
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
{img
? <img src={img} alt={confirming.nom} style={{ width: 44, height: 44, objectFit: 'contain', borderRadius: 8, flexShrink: 0 }} />
: <div style={{ width: 44, height: 44, borderRadius: 8, flexShrink: 0, background: 'var(--surface-2)', border: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 18, color: 'var(--text-muted)' }}>🏦</div>
}
<div style={{ fontWeight: 600, fontSize: 'var(--fs-md)' }}>{confirming.nom}</div>
</div>
<p style={{ margin: 0, color: 'var(--text-muted)', fontSize: 'var(--fs-sm)', lineHeight: 1.5 }}>
Pouvez-vous SVP préciser la date d'ouverture de votre compte sur cette plateforme ?
</p>
<div>
<label style={{ display: 'block', fontWeight: 600, marginBottom: 4, fontSize: 'var(--fs-sm)' }}>
Date d'ouverture du compte
</label>
<input
type="date"
autoFocus
value={dateOuverture}
onChange={e => setDateOuverture(e.target.value)}
style={{ width: '100%', boxSizing: 'border-box' }}
/>
</div>
</div>
</Modal>
);
}
return (
<Modal
open={open}
@@ -372,7 +432,7 @@ function PlatPickerModal({ open, onClose, referentiel, onSelect, onManual, err }
<button
key={r.id}
type="button"
onClick={() => onSelect(r)}
onClick={() => { setConfirming(r); setDateOuverture(localToday()); }}
style={{
display: 'flex', flexDirection: 'column', alignItems: 'center',
gap: 8, padding: '16px 10px 12px',
@@ -779,7 +839,7 @@ export default function PlateformesSection() {
};
/* ── Ajouter depuis le référentiel ──────────────────────────── */
const addPlatFromRef = async (ref) => {
const addPlatFromRef = async (ref, dateOuverture) => {
try {
await api.post('/plateformes', {
nom: ref.nom,
@@ -792,6 +852,7 @@ export default function PlateformesSection() {
type_pret_defaut: ref.type_pret_defaut || null,
freq_interets_defaut: ref.freq_interets_defaut || null,
referentiel_id: ref.id,
date_ouverture: dateOuverture || null,
});
setShowAddPicker(false);
await load();
@@ -942,6 +1003,8 @@ export default function PlateformesSection() {
} catch (e) { setErr(e.message); }
};
const multiDetenteur = new Set(plats.map(p => p.investisseur_id)).size > 1;
/* ── PFU CRUD ────────────────────────────────────────────────── */
@@ -1000,14 +1063,14 @@ export default function PlateformesSection() {
<th style={{ padding: '10px 8px', textAlign: 'left' }}>Domiciliation</th>
<th style={{ padding: '10px 8px', textAlign: 'left' }}>Catégories</th>
<th style={{ padding: '10px 8px', textAlign: 'left' }}>Secteurs</th>
<th style={{ padding: '10px 8px', textAlign: 'left' }}>Détenteur</th>
{multiDetenteur && <th style={{ padding: '10px 8px', textAlign: 'left' }}>Détenteur</th>}
<th style={{ padding: '10px 16px', textAlign: 'center', width: 60 }}>Invest.</th>
<th style={{ padding: '10px 8px', width: 40 }}></th>
</tr>
</thead>
<tbody>
{plats.length === 0 && (
<tr><td colSpan={7} className="text-muted" style={{ textAlign: 'center', padding: 24 }}>Aucune plateforme</td></tr>
<tr><td colSpan={multiDetenteur ? 8 : 7} className="text-muted" style={{ textAlign: 'center', padding: 24 }}>Aucune plateforme</td></tr>
)}
{plats.map((p, i) => {
const imgSrc = p.icone_filename ? logoUrl(p.icone_filename) : p.logo_filename ? logoUrl(p.logo_filename) : null;
@@ -1054,9 +1117,11 @@ export default function PlateformesSection() {
{sects.length === 0 && <span style={{ fontSize: 11, color: 'var(--text-muted)' }}></span>}
</div>
</td>
<td style={{ padding: '10px 8px', fontSize: 12, color: 'var(--text-muted)' }}>
{inv ? memberLabel(inv) : ''}
</td>
{multiDetenteur && (
<td style={{ padding: '10px 8px', fontSize: 12, color: 'var(--text-muted)' }}>
{inv ? memberLabel(inv) : '—'}
</td>
)}
<td style={{ padding: '10px 16px', textAlign: 'center', fontWeight: 600,
color: (p.nb_investissements ?? 0) > 0 ? 'var(--text)' : 'var(--text-muted)' }}>
{p.nb_investissements ?? 0}