Mise en place des objectifs
This commit is contained in:
@@ -12,6 +12,7 @@ import SoldeChart from '../components/SoldeChart.jsx';
|
||||
import DistributionChart from '../components/DistributionChart.jsx';
|
||||
import { fmtEUR, fmtDate, today } from '../utils/format.js';
|
||||
import DepotsMensuelTable from '../components/DepotsMensuelTable.jsx';
|
||||
import SuiviObjectifs from '../components/SuiviObjectifs.jsx';
|
||||
import * as XLSX from 'xlsx';
|
||||
|
||||
/* ── Helpers export ──────────────────────────────────────────── */
|
||||
@@ -397,6 +398,14 @@ export default function DepotsRetraits() {
|
||||
const [allCorrections, setAllCorrections] = useState([]);
|
||||
const [deleteConfirm, setDeleteConfirm] = useState(null);
|
||||
|
||||
/* Objectifs de versement annuel — pour enrichir le KPI "Diff. Dépôts vs Retraits" */
|
||||
const [objectifsKpi, setObjectifsKpi] = useState([]);
|
||||
useEffect(() => {
|
||||
api.get('/objectifs', { type: 'versement_annuel' })
|
||||
.then(r => setObjectifsKpi(Array.isArray(r) ? r : []))
|
||||
.catch(() => setObjectifsKpi([]));
|
||||
}, []);
|
||||
|
||||
/* Lecture des params de navigation depuis la page Plateformes */
|
||||
useEffect(() => {
|
||||
const p = new URLSearchParams(search);
|
||||
@@ -681,6 +690,26 @@ export default function DepotsRetraits() {
|
||||
return balance;
|
||||
})();
|
||||
|
||||
/* ── Enrichissement du KPI "Diff. Dépôts vs Retraits" avec l'objectif de l'année ──
|
||||
Toujours calculé sur le portefeuille entier (ignore le filtre plateforme),
|
||||
pour l'année sélectionnée (drPlatYear, ou l'année en cours si "Toutes les années"). */
|
||||
const scopeInvestisseurIdsKpi = activeView === 'all' ? investisseurs.map(i => i.id) : [activeId];
|
||||
const kpiObjectifYear = Number(drPlatYear || new Date().getFullYear());
|
||||
const kpiObjectifTotal = (() => {
|
||||
const list = objectifsKpi.filter(o => o.annee === kpiObjectifYear && scopeInvestisseurIdsKpi.includes(o.investisseur_id));
|
||||
return list.length ? list.reduce((s, o) => s + o.montant, 0) : null;
|
||||
})();
|
||||
const kpiDiffGlobalYear = (() => {
|
||||
let d = 0, r = 0;
|
||||
for (const row of allRows) {
|
||||
if (!scopeInvestisseurIdsKpi.includes(row.investisseur_id)) continue;
|
||||
if (row.date_operation?.slice(0, 4) !== String(kpiObjectifYear)) continue;
|
||||
if (row.type === 'depot') d += row.montant; else r += row.montant;
|
||||
}
|
||||
return d - r;
|
||||
})();
|
||||
const kpiEcartObjectif = kpiObjectifTotal != null ? kpiDiffGlobalYear - kpiObjectifTotal : null;
|
||||
|
||||
const multiDetenteur = new Set(plats.map(p => p.investisseur_id)).size > 1;
|
||||
|
||||
/** Investisseur par défaut selon la vue active */
|
||||
@@ -966,6 +995,16 @@ export default function DepotsRetraits() {
|
||||
<TrendBadge current={totals.depots - totals.retraits} prev={prevTotals.depots - prevTotals.retraits} />
|
||||
</div>
|
||||
<div style={{ fontSize: '0.8em', color: 'var(--text-muted)', marginTop: 5 }}>{fmtEUR(prevTotals.depots - prevTotals.retraits)} en {prevYear}</div>
|
||||
{kpiEcartObjectif != null && (
|
||||
<div style={{
|
||||
fontSize: '0.8em', marginTop: 4, fontWeight: 600,
|
||||
color: kpiEcartObjectif >= 0 ? 'var(--success)' : 'var(--warning)',
|
||||
}}>
|
||||
{kpiEcartObjectif >= 0
|
||||
? `+${fmtEUR(kpiEcartObjectif)} au-delà de l'objectif ${kpiObjectifYear}`
|
||||
: `Reste ${fmtEUR(-kpiEcartObjectif)} pour l'objectif ${kpiObjectifYear}`}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="kpi" title="Cash disponible sur les porte-monnaie des plateformes (hors compte courant)">
|
||||
<div className="label">Porte-monnaie</div>
|
||||
@@ -981,12 +1020,25 @@ export default function DepotsRetraits() {
|
||||
{!listFocused && <div className="dr-tabs">
|
||||
<button className={`dr-tab${activeTab === 'plateformes' ? ' active' : ''}`}
|
||||
onClick={() => setActiveTab('plateformes')}>Plateformes</button>
|
||||
<button className={`dr-tab${activeTab === 'vision-annuelle' ? ' active' : ''}`}
|
||||
onClick={() => setActiveTab('vision-annuelle')}>Vision annuelle</button>
|
||||
<button className={`dr-tab${activeTab === 'vision-mensuelle' ? ' active' : ''}`}
|
||||
onClick={() => setActiveTab('vision-mensuelle')}>Vision mensuelle</button>
|
||||
<button className={`dr-tab${activeTab === 'mouvements' ? ' active' : ''}`}
|
||||
onClick={() => { setActiveTab('mouvements'); setSelectedRow(r => r ?? (rows[0] || null)); }}>Mouvements</button>
|
||||
</div>}
|
||||
|
||||
{/* ====== ONGLET VISION ANNUELLE ====== */}
|
||||
{activeTab === 'vision-annuelle' && (
|
||||
<div style={{ padding: '0 24px' }}>
|
||||
<SuiviObjectifs
|
||||
rows={allRows}
|
||||
investisseurs={investisseurs}
|
||||
scopeInvestisseurIds={scopeInvestisseurIdsKpi}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ====== ONGLET PLATEFORMES ====== */}
|
||||
{activeTab === 'plateformes' && (
|
||||
<div style={{ padding: '0 24px' }}>
|
||||
|
||||
@@ -7,6 +7,7 @@ import PageIcon from '../components/PageIcon.jsx';
|
||||
import EmptyState from '../components/EmptyState.jsx';
|
||||
import InvChart from '../components/InvChart.jsx';
|
||||
import InvMensuelTable from '../components/InvMensuelTable.jsx';
|
||||
import SuiviObjectifs from '../components/SuiviObjectifs.jsx';
|
||||
import { fmtEUR, fmtDate, fmtStatut, memberLabel, today } from '../utils/format.js';
|
||||
import { xirr } from '../utils/xirr.js';
|
||||
import { usePagination } from '../hooks/usePagination.js';
|
||||
@@ -1510,6 +1511,14 @@ export default function Plateformes() {
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* Suivi des objectifs de versement — toujours sur le portefeuille entier,
|
||||
indépendamment du filtre plateforme de cette page (cf. décision produit). */}
|
||||
<SuiviObjectifs
|
||||
rows={allDepots}
|
||||
investisseurs={investisseurs}
|
||||
scopeInvestisseurIds={activeView === 'all' ? investisseurs.map(i => i.id) : [activeId]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user