Mias à jour des KPI
This commit is contained in:
1 parent
6c96e023a9
commit
b48731ec50
2 files changed
+72
-12
No files matched your search
@@ -152,6 +152,12 @@ router.get('/', (req, res) => {
|
|||||||
cashByPlatformEnriched.reduce((s, p) => s + p.solde_portefeuille, 0) * 100
|
cashByPlatformEnriched.reduce((s, p) => s + p.solde_portefeuille, 0) * 100
|
||||||
) / 100;
|
) / 100;
|
||||||
|
|
||||||
|
// Total des corrections de solde, toutes plateformes confondues (cumul complet
|
||||||
|
// quand asOf est absent) — utilisé par le KPI "Gain net depuis le début" du Dashboard.
|
||||||
|
const corrections_total = Math.round(
|
||||||
|
correctionsPerPlat.reduce((s, r) => s + r.total_correction, 0) * 100
|
||||||
|
) / 100;
|
||||||
|
|
||||||
/* ── Portfolio ───────────────────────────────────────────────── */
|
/* ── Portfolio ───────────────────────────────────────────────── */
|
||||||
const portfolio = db.prepare(`
|
const portfolio = db.prepare(`
|
||||||
SELECT
|
SELECT
|
||||||
@@ -242,7 +248,7 @@ router.get('/', (req, res) => {
|
|||||||
ORDER BY i.date_souscription DESC
|
ORDER BY i.date_souscription DESC
|
||||||
`).all(...invParams);
|
`).all(...invParams);
|
||||||
|
|
||||||
res.json({ cash, cashByPlatform: cashByPlatformEnriched, solde_portefeuille_total, portfolio, interets, interetsParAnnee, upcoming, enDefaut });
|
res.json({ cash, cashByPlatform: cashByPlatformEnriched, solde_portefeuille_total, corrections_total, portfolio, interets, interetsParAnnee, upcoming, enDefaut });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ function KpiCard({ title, value, badge, refValue, onClick, hint }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ── KPI filtrés par année ── */
|
/* ── KPI filtrés par année ── */
|
||||||
function DashboardKpis({ portfolio, netMode, pfuRates, capitalMensuelData, activeId, activeView, soldePortefeuilleLive }) {
|
function DashboardKpis({ portfolio, netMode, pfuRates, capitalMensuelData, activeId, activeView, soldePortefeuilleLive, correctionsTotal }) {
|
||||||
const {
|
const {
|
||||||
annee, modeGlobal, rawDataGlobal, rawData, currentYear, currentMonth,
|
annee, modeGlobal, rawDataGlobal, rawData, currentYear, currentMonth,
|
||||||
setInclureInterets, setInclureCapital, setInclureCashback,
|
setInclureInterets, setInclureCapital, setInclureCashback,
|
||||||
@@ -192,26 +192,46 @@ function DashboardKpis({ portfolio, netMode, pfuRates, capitalMensuelData, activ
|
|||||||
// (GET /dashboard[?asOf=]), formule unique cf. mémoire
|
// (GET /dashboard[?asOf=]), formule unique cf. mémoire
|
||||||
// project_taxreport_interets_plateforme. "Depuis le début" → valeur live
|
// project_taxreport_interets_plateforme. "Depuis le début" → valeur live
|
||||||
// déjà chargée par le parent ; année précise → snapshot fin d'année.
|
// déjà chargée par le parent ; année précise → snapshot fin d'année.
|
||||||
|
//
|
||||||
|
// Comparaison : en vue live (mode global ou année courante), on compare au
|
||||||
|
// solde de fin du mois calendaire précédent (comme le KPI "Gain net depuis
|
||||||
|
// le début"). En vue d'une année passée, on garde la comparaison fin
|
||||||
|
// d'année vs fin d'année précédente (seule comparaison qui a du sens pour
|
||||||
|
// un snapshot historique figé au 31/12).
|
||||||
|
const prevMonthDatePM = new Date(currentYear, currentMonth - 2, 1);
|
||||||
|
const prevMonthEndDatePM = new Date(prevMonthDatePM.getFullYear(), prevMonthDatePM.getMonth() + 1, 0);
|
||||||
|
const walletPrevMonthKey = `${prevMonthEndDatePM.getFullYear()}-${String(prevMonthEndDatePM.getMonth() + 1).padStart(2, '0')}-${String(prevMonthEndDatePM.getDate()).padStart(2, '0')}`;
|
||||||
|
const walletPrevMonthLabel = `${['jan.','fév.','mar.','avr.','mai','juin','juil.','août','sep.','oct.','nov.','déc.'][prevMonthDatePM.getMonth()]} ${prevMonthDatePM.getFullYear()}`;
|
||||||
|
|
||||||
const [walletAsOf, setWalletAsOf] = useState({});
|
const [walletAsOf, setWalletAsOf] = useState({});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (modeGlobal) return;
|
|
||||||
if (!activeId && activeView !== 'all') return;
|
if (!activeId && activeView !== 'all') return;
|
||||||
const scopeParams = activeView === 'all' ? { scope: 'all' } : undefined;
|
const scopeParams = activeView === 'all' ? { scope: 'all' } : undefined;
|
||||||
const prevYear = Number(annee) - 1;
|
const keys = [];
|
||||||
const keys = [`${annee}-12-31`, `${prevYear}-12-31`];
|
if (modeGlobal || isCurrentYear) {
|
||||||
|
keys.push(walletPrevMonthKey);
|
||||||
|
}
|
||||||
|
if (!modeGlobal && !isCurrentYear) {
|
||||||
|
const prevYear = Number(annee) - 1;
|
||||||
|
keys.push(`${annee}-12-31`, `${prevYear}-12-31`);
|
||||||
|
}
|
||||||
|
if (!keys.length) return;
|
||||||
Promise.all(keys.map(asOf =>
|
Promise.all(keys.map(asOf =>
|
||||||
api.get('/dashboard', { ...scopeParams, asOf }).then(d => [asOf, d])
|
api.get('/dashboard', { ...scopeParams, asOf }).then(d => [asOf, d])
|
||||||
)).then(entries => {
|
)).then(entries => {
|
||||||
setWalletAsOf(prev => ({ ...prev, ...Object.fromEntries(entries) }));
|
setWalletAsOf(prev => ({ ...prev, ...Object.fromEntries(entries) }));
|
||||||
}).catch(e => console.error('walletAsOf load error:', e));
|
}).catch(e => console.error('walletAsOf load error:', e));
|
||||||
}, [annee, modeGlobal, activeId, activeView]); // eslint-disable-line
|
}, [annee, modeGlobal, isCurrentYear, walletPrevMonthKey, activeId, activeView]); // eslint-disable-line
|
||||||
|
|
||||||
const soldePortefeuilleValue = modeGlobal
|
const soldePortefeuilleValue = (modeGlobal || isCurrentYear)
|
||||||
? soldePortefeuilleLive
|
? soldePortefeuilleLive
|
||||||
: (walletAsOf[`${annee}-12-31`]?.solde_portefeuille_total ?? null);
|
: (walletAsOf[`${annee}-12-31`]?.solde_portefeuille_total ?? null);
|
||||||
const soldePortefeuillePrev = modeGlobal
|
const soldePortefeuillePrev = (modeGlobal || isCurrentYear)
|
||||||
? null
|
? (walletAsOf[walletPrevMonthKey]?.solde_portefeuille_total ?? null)
|
||||||
: (walletAsOf[`${Number(annee) - 1}-12-31`]?.solde_portefeuille_total ?? null);
|
: (walletAsOf[`${Number(annee) - 1}-12-31`]?.solde_portefeuille_total ?? null);
|
||||||
|
const soldePortefeuillePrevLabel = (modeGlobal || isCurrentYear)
|
||||||
|
? walletPrevMonthLabel
|
||||||
|
: String(Number(annee) - 1);
|
||||||
|
|
||||||
// Capital de référence pour l'année sélectionnée
|
// Capital de référence pour l'année sélectionnée
|
||||||
// Pour les années passées : dernier mois de capitalMensuel (= capital déployé fin décembre)
|
// Pour les années passées : dernier mois de capitalMensuel (= capital déployé fin décembre)
|
||||||
@@ -317,6 +337,32 @@ function DashboardKpis({ portfolio, netMode, pfuRates, capitalMensuelData, activ
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [rawDataGlobal, annee, modeGlobal, pfuRates]);
|
}, [rawDataGlobal, annee, modeGlobal, pfuRates]);
|
||||||
|
|
||||||
|
// ── Gain net depuis le début ──────────────────────────────────
|
||||||
|
// Toujours net (indépendant du toggle Brut/Net), toujours cumulé sur tout
|
||||||
|
// l'historique (indépendant du sélecteur d'année) : intérêts nets + cashback
|
||||||
|
// (investissement + bonus parrainage/plateforme, déjà inclus dans le champ
|
||||||
|
// "cashback" de interets-annuels) + corrections de solde. Volontairement
|
||||||
|
// indépendant du capital encore investi et du solde porte-monnaie courant :
|
||||||
|
// reste juste même si tout l'argent est retiré des plateformes.
|
||||||
|
// Voir mémoire project_gain_net_depuis_debut pour la dérivation complète.
|
||||||
|
const gainNetTotal = useMemo(() => {
|
||||||
|
const totals = (rawDataGlobal.rembourses ?? []).reduce((acc, r) => ({
|
||||||
|
interets_nets: acc.interets_nets + (r.interets_nets || 0),
|
||||||
|
cashback: acc.cashback + (r.cashback || 0),
|
||||||
|
}), { interets_nets: 0, cashback: 0 });
|
||||||
|
return totals.interets_nets + totals.cashback + (correctionsTotal || 0);
|
||||||
|
}, [rawDataGlobal, correctionsTotal]);
|
||||||
|
|
||||||
|
// Valeur "Gain net" telle qu'elle aurait été affichée fin du mois précédent —
|
||||||
|
// on retire du cumul total la part perçue ce mois-ci (intérêts nets + cashback,
|
||||||
|
// via thisMonthRow, déjà chargé pour l'année courante). Les corrections de solde
|
||||||
|
// n'ont pas de granularité mensuelle côté backend ; elles restent donc dans le
|
||||||
|
// cumul "avant ce mois", ce qui est sans incidence pratique (montants marginaux).
|
||||||
|
const gainNetPrevMonthEnd = useMemo(() => {
|
||||||
|
const deltaCeMois = (thisMonthRow?.interets_nets || 0) + (thisMonthRow?.cashback || 0);
|
||||||
|
return gainNetTotal - deltaCeMois;
|
||||||
|
}, [gainNetTotal, thisMonthRow]);
|
||||||
|
|
||||||
// ── Valeur affichée ──
|
// ── Valeur affichée ──
|
||||||
const getValue = (field) => {
|
const getValue = (field) => {
|
||||||
if (isCurrentYear) return thisMonthRow?.[field] || 0;
|
if (isCurrentYear) return thisMonthRow?.[field] || 0;
|
||||||
@@ -410,11 +456,11 @@ function DashboardKpis({ portfolio, netMode, pfuRates, capitalMensuelData, activ
|
|||||||
title="Porte-monnaie"
|
title="Porte-monnaie"
|
||||||
hint="Cash disponible sur les porte-monnaie des plateformes (hors compte courant)"
|
hint="Cash disponible sur les porte-monnaie des plateformes (hors compte courant)"
|
||||||
value={soldePortefeuilleValue != null ? fmtEUR(soldePortefeuilleValue) : '…'}
|
value={soldePortefeuilleValue != null ? fmtEUR(soldePortefeuilleValue) : '…'}
|
||||||
badge={!modeGlobal && soldePortefeuilleValue != null && soldePortefeuillePrev != null
|
badge={soldePortefeuilleValue != null && soldePortefeuillePrev != null
|
||||||
? <TrendBadge current={soldePortefeuilleValue} prev={soldePortefeuillePrev} />
|
? <TrendBadge current={soldePortefeuilleValue} prev={soldePortefeuillePrev} />
|
||||||
: null}
|
: null}
|
||||||
refValue={!modeGlobal && soldePortefeuillePrev != null
|
refValue={soldePortefeuillePrev != null
|
||||||
? fmtEUR(soldePortefeuillePrev) + ' en ' + (Number(annee) - 1)
|
? fmtEUR(soldePortefeuillePrev) + ' en ' + soldePortefeuillePrevLabel
|
||||||
: null}
|
: null}
|
||||||
/>
|
/>
|
||||||
<KpiCard
|
<KpiCard
|
||||||
@@ -446,6 +492,13 @@ function DashboardKpis({ portfolio, netMode, pfuRates, capitalMensuelData, activ
|
|||||||
refValue={mkRef('cashback')}
|
refValue={mkRef('cashback')}
|
||||||
onClick={() => { setInclureInterets(false); setInclureCapital(false); setInclureCashback(true); }}
|
onClick={() => { setInclureInterets(false); setInclureCapital(false); setInclureCashback(true); }}
|
||||||
/>
|
/>
|
||||||
|
<KpiCard
|
||||||
|
title="Gain net depuis le début"
|
||||||
|
hint="Intérêts nets + cashback + bonus parrainage/plateforme + corrections de solde, cumulés depuis le début — indépendant du capital encore investi ou du solde actuel sur les plateformes (reste juste même en cas de retrait complet)."
|
||||||
|
value={fmtEUR(gainNetTotal)}
|
||||||
|
badge={<TrendBadge current={gainNetTotal} prev={gainNetPrevMonthEnd} />}
|
||||||
|
refValue={fmtEUR(gainNetPrevMonthEnd) + ' en ' + prevMonthLabel}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -569,6 +622,7 @@ export default function Dashboard() {
|
|||||||
<DashboardKpis
|
<DashboardKpis
|
||||||
portfolio={portfolio} netMode={netMode} pfuRates={pfuRates} capitalMensuelData={capitalMensuelData}
|
portfolio={portfolio} netMode={netMode} pfuRates={pfuRates} capitalMensuelData={capitalMensuelData}
|
||||||
activeId={activeId} activeView={activeView} soldePortefeuilleLive={data?.solde_portefeuille_total ?? null}
|
activeId={activeId} activeView={activeView} soldePortefeuilleLive={data?.solde_portefeuille_total ?? null}
|
||||||
|
correctionsTotal={data?.corrections_total ?? 0}
|
||||||
/>
|
/>
|
||||||
<YearSelectorKpi />
|
<YearSelectorKpi />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user