Message lorsqu'aucun enregistrement présent

This commit is contained in:
2026-06-16 08:38:23 +02:00
parent 032a370e7d
commit 9058bc7d22
8 changed files with 156 additions and 6 deletions
+67
View File
@@ -0,0 +1,67 @@
import { useNavigate } from 'react-router-dom';
/**
* État vide — affiché quand aucune plateforme n'est configurée.
* Props :
* icon — emoji ou caractère affiché en grand (défaut : '🏦')
* title — titre du message (défaut : 'Aucune plateforme configurée')
* message — texte explicatif (défaut : message générique plateformes)
* cta — libellé du bouton (défaut : 'Configurer mes plateformes')
* to — route cible du bouton (défaut : '/settings?section=plateformes')
*/
export default function EmptyState({
icon = '🏦',
title = 'Aucune plateforme configurée',
message = "Vous n'avez pas encore configuré de plateforme de crowdfunding sur votre compte. Associez au moins une plateforme pour commencer à suivre vos investissements.",
cta = 'Configurer mes plateformes',
to = '/settings?section=plateformes',
}) {
const navigate = useNavigate();
return (
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
padding: '72px 24px',
textAlign: 'center',
gap: 16,
}}>
<div style={{ fontSize: 56, lineHeight: 1 }}>{icon}</div>
<h2 style={{
margin: 0,
fontSize: '1.35rem',
fontWeight: 700,
color: 'var(--text)',
}}>
{title}
</h2>
<p style={{
margin: 0,
maxWidth: 480,
color: 'var(--text-muted)',
lineHeight: 1.6,
fontSize: '0.97rem',
}}>
{message}
</p>
<button
onClick={() => navigate(to)}
style={{
marginTop: 8,
padding: '10px 24px',
background: 'var(--primary)',
color: '#fff',
border: 'none',
borderRadius: 8,
fontSize: '0.95rem',
fontWeight: 600,
cursor: 'pointer',
}}
>
{cta}
</button>
</div>
);
}
+9 -1
View File
@@ -10,6 +10,7 @@ import InteretsDonutChart from '../components/InteretsDonutChart.jsx';
import { InteretsChartProvider, useInteretsChart } from '../context/InteretsChartContext.jsx';
import TableauInteretsPlateforme from '../components/TableauInteretsPlateforme.jsx';
import DrillCellPanel from '../components/DrillCellPanel.jsx';
import EmptyState from '../components/EmptyState.jsx';
/* ── Sélecteur d'année — doit être enfant de InteretsChartProvider ── */
function YearSelectorKpi() {
@@ -422,6 +423,7 @@ export default function Dashboard() {
const [loading, setLoading] = useState(false);
const [capitalMensuelData, setCapitalMensuelData] = useState([]);
const [plateformes, setPlateformes] = useState([]);
const [platLoaded, setPlatLoaded] = useState(false);
/* ── drillCell : cellule sélectionnée dans le TIP — par défaut mois courant toutes plateformes ── */
const _now = new Date();
@@ -461,7 +463,7 @@ export default function Dashboard() {
useEffect(() => {
api.get('/pfu').then(setPfuRates).catch(() => {});
api.get('/plateformes').then(setPlateformes).catch(() => {});
api.get('/plateformes').then(d => { setPlateformes(d); setPlatLoaded(true); }).catch(() => { setPlatLoaded(true); });
}, []);
useEffect(() => {
@@ -475,6 +477,12 @@ export default function Dashboard() {
const ready = activeView === 'all' ? investisseurs.length > 0 : !!activeId;
if (!ready) return <div className="card text-muted">Sélectionnez un compte investisseur.</div>;
if (loading || !data) return <div className="card text-muted">Chargement</div>;
if (platLoaded && plateformes.length === 0) return (
<>
<div className="topbar"><h2>Tableau de bord</h2></div>
<EmptyState />
</>
);
const { cash, cashByPlatform, portfolio, interets, interetsParAnnee } = data;
const viewTitle = activeView === 'all'
+7
View File
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
import { usePagination } from '../hooks/usePagination.js';
import Pagination from '../components/Pagination.jsx';
import PageIcon from '../components/PageIcon.jsx';
import EmptyState from '../components/EmptyState.jsx';
import { useLocation, useNavigate } from 'react-router-dom';
import { api } from '../api.js';
import { useInvestisseur } from '../context/InvestisseurContext.jsx';
@@ -882,6 +883,12 @@ export default function DepotsRetraits() {
});
};
if (!loading && plats.length === 0) return (
<>
<div className="topbar"><h2>Dépôts / Retraits</h2></div>
<EmptyState />
</>
);
return (
<>
<div className="topbar">
+7
View File
@@ -15,6 +15,7 @@ import DistributionChart from '../components/DistributionChart.jsx';
import CapitalMensuelTable from '../components/CapitalMensuelTable.jsx';
import { fmtEUR, fmtPct, fmtDate, fmtStatut, today } from '../utils/format.js';
import * as XLSX from 'xlsx';
import EmptyState from '../components/EmptyState.jsx';
/* ── Constantes ──────────────────────────────────────────────── */
const MOIS_FR = ['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre'];
@@ -742,6 +743,12 @@ export default function Investissements() {
const clearFilter = () => setFilter({ statut: '', plateforme_id: '', categorie_inv_id: '', secteur_inv_id: '', year: '', month: '' });
const multiDetenteur = new Set(plats.map(p => p.investisseur_id)).size > 1;
if (!loading && plats.length === 0) return (
<>
<div className="topbar"><h2>Investissements</h2></div>
<EmptyState />
</>
);
return (
<>
<div className="topbar">
+2 -3
View File
@@ -4,6 +4,7 @@ import { api } from '../api.js';
import { useInvestisseur } from '../context/InvestisseurContext.jsx';
import { useUi } from '../context/UiContext.jsx';
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 { fmtEUR, fmtDate, fmtStatut, memberLabel } from '../utils/format.js';
@@ -567,9 +568,7 @@ export default function Plateformes() {
<div className="topbar">
<h2><PageIcon name="plateforme" />Plateformes</h2>
</div>
<div style={{ padding: '48px 24px', textAlign: 'center', color: 'var(--text-muted)' }}>
Aucun investissement trouvé.
</div>
<EmptyState />
</>
);
}
+9 -1
View File
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
import { usePagination } from '../hooks/usePagination.js';
import Pagination from '../components/Pagination.jsx';
import PageIcon from '../components/PageIcon.jsx';
import EmptyState from '../components/EmptyState.jsx';
import { useLocation, useNavigate } from 'react-router-dom';
import { api } from '../api.js';
import { useInvestisseur } from '../context/InvestisseurContext.jsx';
@@ -177,6 +178,7 @@ export default function Remboursements() {
const [corrections, setCorrections] = useState([]);
const [investissements, setInvestissements] = useState([]);
const [plateformes, setPlateformes] = useState([]);
const [platLoaded, setPlatLoaded] = useState(false);
const [pfuRates, setPfuRates] = useState([]);
const [loading, setLoading] = useState(false);
@@ -263,7 +265,7 @@ export default function Remboursements() {
useEffect(() => {
api.get('/pfu').then(setPfuRates).catch(() => {});
api.get('/plateformes').then(setPlateformes).catch(() => {});
api.get('/plateformes').then(d => { setPlateformes(d); setPlatLoaded(true); }).catch(() => { setPlatLoaded(true); });
api.get('/icons').then(rows => { const m = {}; rows.forEach(r => { m[r.name] = r.filename; }); setLibIcons(m); }).catch(() => {});
}, []);
@@ -760,6 +762,12 @@ export default function Remboursements() {
const multiDetenteur = new Set(plateformes.map(p => p.investisseur_id)).size > 1;
/* ── Rendu ── */
if (platLoaded && !loading && plateformes.length === 0) return (
<>
<div className="topbar"><h2>Remboursements</h2></div>
<EmptyState />
</>
);
return (
<>
<div className="topbar">
+11
View File
@@ -1,5 +1,6 @@
import { useEffect, useRef, useState } from 'react';
import PageIcon from '../components/PageIcon.jsx';
import EmptyState from '../components/EmptyState.jsx';
import Pagination from '../components/Pagination.jsx';
import { usePagination } from '../hooks/usePagination.js';
import { api } from '../api.js';
@@ -249,6 +250,16 @@ export default function TaxReport() {
dlBlob(JSON.stringify(payload, null, 2), `2778-SD-${annee}.json`, 'application/json');
};
if (!loading && data && availableYears.length === 0) return (
<>
<div className="topbar"><h2>Fiscalité</h2></div>
<EmptyState
icon="📋"
title="Aucune donnée fiscale disponible"
message="Vous n'avez pas encore de données fiscales à déclarer. Ajoutez des plateformes et des remboursements pour voir apparaître votre récapitulatif fiscal."
/>
</>
);
return (
<>
<div className="topbar">