Améliorations diverses
This commit is contained in:
@@ -805,16 +805,23 @@ router.get('/interets-par-plateforme', (req, res) => {
|
|||||||
* Query params : plateforme_id (int), annee (4 chiffres), mois (01-12), scope=all (opt)
|
* Query params : plateforme_id (int), annee (4 chiffres), mois (01-12), scope=all (opt)
|
||||||
*/
|
*/
|
||||||
router.get('/detail-cellule', (req, res) => {
|
router.get('/detail-cellule', (req, res) => {
|
||||||
const { plateforme_id, annee, mois } = req.query;
|
const { plateforme_id, plateforme_ids, annee, mois } = req.query;
|
||||||
const scopeAll = req.query.scope === 'all';
|
const scopeAll = req.query.scope === 'all';
|
||||||
const userId = req.user.id;
|
const userId = req.user.id;
|
||||||
|
|
||||||
if (!annee || !mois)
|
if (!annee || !mois)
|
||||||
return res.status(400).json({ error: 'annee et mois sont requis' });
|
return res.status(400).json({ error: 'annee et mois sont requis' });
|
||||||
|
|
||||||
const moisStr = `${annee}-${String(mois).padStart(2, '0')}`;
|
const moisStr = `${annee}-${String(mois).padStart(2, '0')}`;
|
||||||
const platId = plateforme_id ? Number(plateforme_id) : null;
|
|
||||||
const platFilter = platId ? 'AND i.plateforme_id = ?' : '';
|
/* Support filtre simple (platId) OU multiple (platIds, vue consolidée) */
|
||||||
|
const platIds = plateforme_ids
|
||||||
|
? plateforme_ids.split(',').map(Number).filter(Boolean)
|
||||||
|
: plateforme_id ? [Number(plateforme_id)].filter(Boolean) : [];
|
||||||
|
|
||||||
|
const platFilter = platIds.length
|
||||||
|
? `AND i.plateforme_id IN (${platIds.map(() => '?').join(',')})`
|
||||||
|
: '';
|
||||||
|
|
||||||
let invWhere, invParams;
|
let invWhere, invParams;
|
||||||
if (scopeAll) {
|
if (scopeAll) {
|
||||||
@@ -830,8 +837,8 @@ router.get('/detail-cellule', (req, res) => {
|
|||||||
invParams = [invId];
|
invParams = [invId];
|
||||||
}
|
}
|
||||||
|
|
||||||
const recusParams = platId ? [...invParams, platId, moisStr] : [...invParams, moisStr];
|
const recusParams = [...invParams, ...platIds, moisStr];
|
||||||
const projetesParams = platId ? [...invParams, platId, moisStr, moisStr] : [...invParams, moisStr, moisStr];
|
const projetesParams = [...invParams, ...platIds, moisStr, moisStr];
|
||||||
|
|
||||||
/* ── Remboursements reçus ─────────────────────────────────── */
|
/* ── Remboursements reçus ─────────────────────────────────── */
|
||||||
const recus = db.prepare(`
|
const recus = db.prepare(`
|
||||||
|
|||||||
@@ -72,10 +72,20 @@ export default function DrillCellPanel({
|
|||||||
if (!cell) { setData(null); return; }
|
if (!cell) { setData(null); return; }
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setData(null);
|
setData(null);
|
||||||
|
|
||||||
|
/* En mode consolidé, cell.platIds contient plusieurs IDs numériques ;
|
||||||
|
si l'utilisateur a choisi un filtre manuel dans le select, on l'utilise tel quel */
|
||||||
|
const isInitialFilter = cell.platId ? filterPlatId === String(cell.platId) : !filterPlatId;
|
||||||
|
const consolidatedIds = isInitialFilter && cell.platIds?.length > 1
|
||||||
|
? cell.platIds.join(',')
|
||||||
|
: null;
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
annee: cell.annee,
|
annee: cell.annee,
|
||||||
mois: cell.mois,
|
mois: cell.mois,
|
||||||
...(filterPlatId ? { plateforme_id: filterPlatId } : {}),
|
...(consolidatedIds
|
||||||
|
? { plateforme_ids: consolidatedIds }
|
||||||
|
: filterPlatId ? { plateforme_id: filterPlatId } : {}),
|
||||||
...(activeView === 'all' ? { scope: 'all' } : {}),
|
...(activeView === 'all' ? { scope: 'all' } : {}),
|
||||||
};
|
};
|
||||||
api.get('/dashboard/detail-cellule', params)
|
api.get('/dashboard/detail-cellule', params)
|
||||||
|
|||||||
@@ -12,20 +12,6 @@ function startOfMonth(Y, M) {
|
|||||||
return `${Y}-${String(M).padStart(2,'0')}-01`;
|
return `${Y}-${String(M).padStart(2,'0')}-01`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const STATUT_BG = {
|
|
||||||
en_cours: 'var(--b-en_cours-bg)',
|
|
||||||
rembourse: 'var(--b-rembourse-bg)',
|
|
||||||
en_retard: 'var(--b-en_retard-bg)',
|
|
||||||
procedure: 'var(--b-procedure-bg)',
|
|
||||||
cloture: 'var(--surface-2)',
|
|
||||||
};
|
|
||||||
const STATUT_FG = {
|
|
||||||
en_cours: 'var(--b-en_cours-fg)',
|
|
||||||
rembourse: 'var(--b-rembourse-fg)',
|
|
||||||
en_retard: 'var(--b-en_retard-fg)',
|
|
||||||
procedure: 'var(--b-procedure-fg)',
|
|
||||||
cloture: 'var(--text-muted)',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function InvMensuelTable({ rows, allRembs, allReinvests, year, originFrom }) {
|
export default function InvMensuelTable({ rows, allRembs, allReinvests, year, originFrom }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -125,11 +111,7 @@ export default function InvMensuelTable({ rows, allRembs, allReinvests, year, or
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th className="tip-th-name tip-th-name-amber" style={{ minWidth: '22ch', maxWidth: '40ch' }}>Investissement</th>
|
<th className="tip-th-name tip-th-name-amber" style={{ minWidth: '22ch', maxWidth: '40ch' }}>Investissement</th>
|
||||||
<th style={{
|
<th className="tip-th-name" style={{ minWidth: 'unset', position: 'static', fontSize: 'var(--fs-xs)', textAlign: 'left' }}>Statut</th>
|
||||||
padding: '7px 10px', background: 'var(--surface-2)', color: 'var(--text)',
|
|
||||||
fontWeight: 600, fontSize: 'var(--fs-xs)', textAlign: 'left',
|
|
||||||
borderRight: '1px solid var(--border)', whiteSpace: 'nowrap',
|
|
||||||
}}>Statut</th>
|
|
||||||
{MOIS_LONG.map((m, i) => (
|
{MOIS_LONG.map((m, i) => (
|
||||||
<th key={m}
|
<th key={m}
|
||||||
className={`tip-th-month${displayYear === currentYear && i === currentMonth - 1 ? ' tip-th-month-current' : ''}`}>
|
className={`tip-th-month${displayYear === currentYear && i === currentMonth - 1 ? ' tip-th-month-current' : ''}`}>
|
||||||
@@ -148,14 +130,7 @@ export default function InvMensuelTable({ rows, allRembs, allReinvests, year, or
|
|||||||
{inv.nom_projet || '—'}
|
{inv.nom_projet || '—'}
|
||||||
</td>
|
</td>
|
||||||
<td style={{ padding: '8px 10px', whiteSpace: 'nowrap', borderRight: '1px solid var(--border)' }}>
|
<td style={{ padding: '8px 10px', whiteSpace: 'nowrap', borderRight: '1px solid var(--border)' }}>
|
||||||
<span style={{
|
<span className={`badge ${inv.statut}`}>{fmtStatut(inv.statut)}</span>
|
||||||
display: 'inline-block', padding: '2px 8px', borderRadius: 4,
|
|
||||||
fontSize: 'var(--fs-xs)', fontWeight: 600,
|
|
||||||
background: STATUT_BG[inv.statut] || 'var(--surface-2)',
|
|
||||||
color: STATUT_FG[inv.statut] || 'var(--text-muted)',
|
|
||||||
}}>
|
|
||||||
{fmtStatut(inv.statut)}
|
|
||||||
</span>
|
|
||||||
</td>
|
</td>
|
||||||
{months.map((v, mi) => {
|
{months.map((v, mi) => {
|
||||||
const curClass = displayYear === currentYear && mi === currentMonth - 1 ? ' tip-col-current' : '';
|
const curClass = displayYear === currentYear && mi === currentMonth - 1 ? ' tip-col-current' : '';
|
||||||
|
|||||||
@@ -136,12 +136,14 @@ export default function TableauInteretsPlateforme({ activeView, activeId, pfuRat
|
|||||||
if (!byNom[plat.nom]) {
|
if (!byNom[plat.nom]) {
|
||||||
byNom[plat.nom] = {
|
byNom[plat.nom] = {
|
||||||
...plat,
|
...plat,
|
||||||
id: plat.nom,
|
id: plat.nom,
|
||||||
|
_ids: [plat.id], // ← tous les IDs numériques fusionnés
|
||||||
detenteur_nom: null,
|
detenteur_nom: null,
|
||||||
rembourses: { ...plat.rembourses },
|
rembourses: { ...plat.rembourses },
|
||||||
projections: { ...plat.projections },
|
projections: { ...plat.projections },
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
byNom[plat.nom]._ids.push(plat.id);
|
||||||
byNom[plat.nom].rembourses = mergeMaps(byNom[plat.nom].rembourses, plat.rembourses);
|
byNom[plat.nom].rembourses = mergeMaps(byNom[plat.nom].rembourses, plat.rembourses);
|
||||||
byNom[plat.nom].projections = mergeMaps(byNom[plat.nom].projections, plat.projections);
|
byNom[plat.nom].projections = mergeMaps(byNom[plat.nom].projections, plat.projections);
|
||||||
}
|
}
|
||||||
@@ -397,7 +399,8 @@ export default function TableauInteretsPlateforme({ activeView, activeId, pfuRat
|
|||||||
className={`tip-td-num${v?.projected ? ' tip-projected' : ''}${isCurrent ? ' tip-col-current' : ''}${isActive ? ' tip-td-active' : ''}${clickable ? ' tip-td-clickable' : ''}`}
|
className={`tip-td-num${v?.projected ? ' tip-projected' : ''}${isCurrent ? ' tip-col-current' : ''}${isActive ? ' tip-td-active' : ''}${clickable ? ' tip-td-clickable' : ''}`}
|
||||||
onClick={() => clickable && onCellClick && onCellClick({
|
onClick={() => clickable && onCellClick && onCellClick({
|
||||||
key: cellKey,
|
key: cellKey,
|
||||||
platId: plat.id,
|
platId: plat._ids ? plat._ids[0] : plat.id, // toujours numérique
|
||||||
|
platIds: plat._ids ?? [plat.id], // tous les IDs (consolidé)
|
||||||
platNom: plat.nom,
|
platNom: plat.nom,
|
||||||
annee,
|
annee,
|
||||||
mois: String(mi + 1).padStart(2, '0'),
|
mois: String(mi + 1).padStart(2, '0'),
|
||||||
|
|||||||
@@ -518,8 +518,8 @@ export default function Dashboard() {
|
|||||||
activeId={activeId}
|
activeId={activeId}
|
||||||
pfuRates={pfuRates}
|
pfuRates={pfuRates}
|
||||||
onCapitalMensuel={setCapitalMensuelData}
|
onCapitalMensuel={setCapitalMensuelData}
|
||||||
onCellClick={({ platId, platNom, annee, mois, moisLabel }) =>
|
onCellClick={({ platId, platIds, platNom, annee, mois, moisLabel }) =>
|
||||||
setDrillCell({ platId, platNom, annee, mois, moisLabel })
|
setDrillCell({ platId, platIds, platNom, annee, mois, moisLabel })
|
||||||
}
|
}
|
||||||
activeCell={drillCell}
|
activeCell={drillCell}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import EmptyState from '../components/EmptyState.jsx';
|
|||||||
import InvChart from '../components/InvChart.jsx';
|
import InvChart from '../components/InvChart.jsx';
|
||||||
import InvMensuelTable from '../components/InvMensuelTable.jsx';
|
import InvMensuelTable from '../components/InvMensuelTable.jsx';
|
||||||
import { fmtEUR, fmtDate, fmtStatut, memberLabel } from '../utils/format.js';
|
import { fmtEUR, fmtDate, fmtStatut, memberLabel } from '../utils/format.js';
|
||||||
|
import { usePagination } from '../hooks/usePagination.js';
|
||||||
|
import Pagination from '../components/Pagination.jsx';
|
||||||
|
|
||||||
const LOGOS_BASE = '/api/logos/';
|
const LOGOS_BASE = '/api/logos/';
|
||||||
|
|
||||||
@@ -324,7 +326,7 @@ export default function Plateformes() {
|
|||||||
const [pfuRates, setPfuRates] = useState([]);
|
const [pfuRates, setPfuRates] = useState([]);
|
||||||
|
|
||||||
/* ── État UI ── */
|
/* ── État UI ── */
|
||||||
const activeTab = searchParams.get('tab') || 'depots-retraits';
|
const activeTab = searchParams.get('tab') || 'remboursements';
|
||||||
const setActiveTab = (tab) => setSearchParams(p => { const n = new URLSearchParams(p); n.set('tab', tab); return n; }, { replace: true });
|
const setActiveTab = (tab) => setSearchParams(p => { const n = new URLSearchParams(p); n.set('tab', tab); return n; }, { replace: true });
|
||||||
const [listFocused, setListFocused] = useState(false);
|
const [listFocused, setListFocused] = useState(false);
|
||||||
|
|
||||||
@@ -526,6 +528,17 @@ export default function Plateformes() {
|
|||||||
}, { investi: 0, cap_remb: 0, int_perc: 0, int_perc_net: 0, encours: 0, defaut: 0 }),
|
}, { investi: 0, cap_remb: 0, int_perc: 0, int_perc_net: 0, encours: 0, defaut: 0 }),
|
||||||
[chartRows, capRembParInv, rembParInv, reinvestCumulParInv]);
|
[chartRows, capRembParInv, rembParInv, reinvestCumulParInv]);
|
||||||
|
|
||||||
|
/* ── Pagination onglet Investissements ── */
|
||||||
|
const sortedChartRows = useMemo(() =>
|
||||||
|
chartRows.slice().sort((a, b) => (a.date_souscription || '') < (b.date_souscription || '') ? -1 : 1),
|
||||||
|
[chartRows]
|
||||||
|
);
|
||||||
|
const {
|
||||||
|
pagedItems: pagedChartRows, page: platInvPage, setPage: setPlatInvPage,
|
||||||
|
pageSize: platInvPageSize, setPageSize: setPlatInvPageSize,
|
||||||
|
totalPages: platInvTotalPages, totalItems: platInvTotalItems, PAGE_SIZES: platInvPageSizes,
|
||||||
|
} = usePagination(sortedChartRows, 'cl_pagesize_plat_inv', [selectedPlatName, selectedYear]);
|
||||||
|
|
||||||
/* ── KPI N-1 (pour TrendBadge) ── */
|
/* ── KPI N-1 (pour TrendBadge) ── */
|
||||||
const prevTotals = useMemo(() => {
|
const prevTotals = useMemo(() => {
|
||||||
const effectiveYear = selectedYear || String(new Date().getFullYear());
|
const effectiveYear = selectedYear || String(new Date().getFullYear());
|
||||||
@@ -1004,7 +1017,7 @@ export default function Plateformes() {
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</th>
|
</th>
|
||||||
<th style={{ padding: '7px 10px', background: 'var(--surface-2)', color: 'var(--text)', fontWeight: 600, fontSize: 'var(--fs-xs)', textAlign: 'left', borderRight: '1px solid var(--border)', whiteSpace: 'nowrap' }}>Statut</th>
|
<th className="tip-th-name" style={{ minWidth: 'unset', position: 'static', fontSize: 'var(--fs-xs)', textAlign: 'left' }}>Statut</th>
|
||||||
{MOIS_LONG.map((m, i) => (
|
{MOIS_LONG.map((m, i) => (
|
||||||
<th key={m} className={`tip-th-month${displayYear === currentYear && i === currentMonth - 1 ? ' tip-th-month-current' : ''}`}>{m}</th>
|
<th key={m} className={`tip-th-month${displayYear === currentYear && i === currentMonth - 1 ? ' tip-th-month-current' : ''}`}>{m}</th>
|
||||||
))}
|
))}
|
||||||
@@ -1030,14 +1043,7 @@ export default function Plateformes() {
|
|||||||
{inv.nom_projet || '—'}
|
{inv.nom_projet || '—'}
|
||||||
</td>
|
</td>
|
||||||
<td style={{ padding: '8px 10px', whiteSpace: 'nowrap', borderRight: '1px solid var(--border)' }}>
|
<td style={{ padding: '8px 10px', whiteSpace: 'nowrap', borderRight: '1px solid var(--border)' }}>
|
||||||
<span style={{
|
<span className={`badge ${inv.statut}`}>{fmtStatut(inv.statut)}</span>
|
||||||
display: 'inline-block', padding: '2px 8px', borderRadius: 4,
|
|
||||||
fontSize: 'var(--fs-xs)', fontWeight: 600,
|
|
||||||
background: `var(--b-${inv.statut}-bg, var(--surface-2))`,
|
|
||||||
color: `var(--b-${inv.statut}-fg, var(--text-muted))`,
|
|
||||||
}}>
|
|
||||||
{fmtStatut(inv.statut)}
|
|
||||||
</span>
|
|
||||||
</td>
|
</td>
|
||||||
{(() => {
|
{(() => {
|
||||||
const firstNonNull = months.findIndex(v => v !== null);
|
const firstNonNull = months.findIndex(v => v !== null);
|
||||||
@@ -1163,9 +1169,10 @@ export default function Plateformes() {
|
|||||||
<div className="solde-chart-wrap" style={{ padding: '24px 24px 16px', marginBottom: 24 }}>
|
<div className="solde-chart-wrap" style={{ padding: '24px 24px 16px', marginBottom: 24 }}>
|
||||||
<div className="solde-chart-header" style={{ marginBottom: 16 }}>
|
<div className="solde-chart-header" style={{ marginBottom: 16 }}>
|
||||||
<div className="solde-chart-info">
|
<div className="solde-chart-info">
|
||||||
<div style={{ fontSize: 13, color: 'var(--text-muted)' }}>
|
<h3 style={{ margin: 0 }}>
|
||||||
Mouvements de trésorerie · {selectedYear || 'Toutes les années'}
|
Mouvements de trésorerie
|
||||||
</div>
|
<span style={{ marginLeft: 8, fontWeight: 400, color: 'var(--text-muted)', fontSize: '0.85em' }}>— {selectedYear || 'Toutes les années'}</span>
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="solde-chart-controls">
|
<div className="solde-chart-controls">
|
||||||
<div className="solde-chart-ranges">
|
<div className="solde-chart-ranges">
|
||||||
@@ -1187,6 +1194,22 @@ export default function Plateformes() {
|
|||||||
onClick={() => setSelectedYear('')}>
|
onClick={() => setSelectedYear('')}>
|
||||||
TOUT
|
TOUT
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" className="icon-btn"
|
||||||
|
title={listFocused ? 'Réduire' : 'Agrandir'}
|
||||||
|
style={{ marginLeft: 4 }}
|
||||||
|
onClick={() => setListFocused(v => !v)}>
|
||||||
|
{listFocused ? (
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<polyline points="4 14 10 14 10 20"/><polyline points="20 10 14 10 14 4"/>
|
||||||
|
<line x1="10" y1="14" x2="3" y2="21"/><line x1="21" y1="3" x2="14" y2="10"/>
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/>
|
||||||
|
<line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1380,9 +1403,10 @@ export default function Plateformes() {
|
|||||||
<div className="solde-chart-wrap" style={{ padding: '24px 24px 16px', marginBottom: 24 }}>
|
<div className="solde-chart-wrap" style={{ padding: '24px 24px 16px', marginBottom: 24 }}>
|
||||||
<div className="solde-chart-header">
|
<div className="solde-chart-header">
|
||||||
<div className="solde-chart-info">
|
<div className="solde-chart-info">
|
||||||
<div style={{ fontSize: 13, color: 'var(--text-muted)', marginBottom: 2 }}>
|
<h3 style={{ margin: 0 }}>
|
||||||
Capital investi par investissement · {selectedYear || 'Toutes les années'}
|
Capital investi par investissement
|
||||||
</div>
|
<span style={{ marginLeft: 8, fontWeight: 400, color: 'var(--text-muted)', fontSize: '0.85em' }}>— {selectedYear || 'Toutes les années'}</span>
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="solde-chart-controls">
|
<div className="solde-chart-controls">
|
||||||
<div className="solde-chart-ranges">
|
<div className="solde-chart-ranges">
|
||||||
@@ -1444,9 +1468,26 @@ export default function Plateformes() {
|
|||||||
Investissements
|
Investissements
|
||||||
{selectedYear && <span style={{ marginLeft: 8, fontWeight: 400, color: 'var(--text-muted)', fontSize: '0.85em' }}>— {selectedYear}</span>}
|
{selectedYear && <span style={{ marginLeft: 8, fontWeight: 400, color: 'var(--text-muted)', fontSize: '0.85em' }}>— {selectedYear}</span>}
|
||||||
</h3>
|
</h3>
|
||||||
<span style={{ fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
{chartRows.length} investissement{chartRows.length !== 1 ? 's' : ''}
|
<span style={{ fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
|
||||||
</span>
|
{chartRows.length} investissement{chartRows.length !== 1 ? 's' : ''}
|
||||||
|
</span>
|
||||||
|
<button type="button" className="icon-btn"
|
||||||
|
title={listFocused ? 'Réduire' : 'Agrandir'}
|
||||||
|
onClick={() => setListFocused(v => !v)}>
|
||||||
|
{listFocused ? (
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<polyline points="4 14 10 14 10 20"/><polyline points="20 10 14 10 14 4"/>
|
||||||
|
<line x1="10" y1="14" x2="3" y2="21"/><line x1="21" y1="3" x2="14" y2="10"/>
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/>
|
||||||
|
<line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!chartRows.length ? (
|
{!chartRows.length ? (
|
||||||
@@ -1454,6 +1495,7 @@ export default function Plateformes() {
|
|||||||
{loading ? 'Chargement…' : 'Aucun investissement'}
|
{loading ? 'Chargement…' : 'Aucun investissement'}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
<>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -1467,10 +1509,7 @@ export default function Plateformes() {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{chartRows
|
{pagedChartRows.map(r => {
|
||||||
.slice()
|
|
||||||
.sort((a, b) => (a.date_souscription || '') < (b.date_souscription || '') ? -1 : 1)
|
|
||||||
.map(r => {
|
|
||||||
const capInv = r.montant_investi + (reinvestCumulParInv[r.id] || 0);
|
const capInv = r.montant_investi + (reinvestCumulParInv[r.id] || 0);
|
||||||
const capRemb = capRembParInv[r.id] || 0;
|
const capRemb = capRembParInv[r.id] || 0;
|
||||||
const capRestant = Math.max(0, capInv - capRemb);
|
const capRestant = Math.max(0, capInv - capRemb);
|
||||||
@@ -1492,6 +1531,13 @@ export default function Plateformes() {
|
|||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<Pagination
|
||||||
|
page={platInvPage} setPage={setPlatInvPage}
|
||||||
|
pageSize={platInvPageSize} setPageSize={setPlatInvPageSize}
|
||||||
|
totalPages={platInvTotalPages} totalItems={platInvTotalItems}
|
||||||
|
PAGE_SIZES={platInvPageSizes}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1716,17 +1716,18 @@ tr:hover td { background: var(--surface-2); }
|
|||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
}
|
}
|
||||||
.tip-th-name {
|
.tip-th-name {
|
||||||
background: var(--surface-2);
|
background: linear-gradient(135deg, #7c3aed 0%, #4f46e5 100%);
|
||||||
|
color: #fff;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
font-size: var(--fs-sm);
|
font-size: var(--fs-sm);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
min-width: 160px;
|
min-width: 160px;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
border-right: 1px solid var(--border);
|
border-right: 1px solid rgba(255,255,255,.2);
|
||||||
}
|
}
|
||||||
.tip-th-month {
|
.tip-th-month {
|
||||||
background: var(--surface-2);
|
background: var(--surface-2);
|
||||||
@@ -1777,6 +1778,8 @@ tr:hover td { background: var(--surface-2); }
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
font-size: var(--fs-xs);
|
font-size: var(--fs-xs);
|
||||||
|
background: rgba(109,40,217,.04);
|
||||||
|
border-left: 1px solid rgba(109,40,217,.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Valeurs projetées */
|
/* Valeurs projetées */
|
||||||
@@ -1854,6 +1857,8 @@ tr:hover td { background: var(--surface-2); }
|
|||||||
/* Dark mode ajustements */
|
/* Dark mode ajustements */
|
||||||
[data-theme="dark"] .tip-footer-total { background: rgba(217,119,6,.12); }
|
[data-theme="dark"] .tip-footer-total { background: rgba(217,119,6,.12); }
|
||||||
[data-theme="dark"] .tip-td-total { background: rgba(217,119,6,.08); }
|
[data-theme="dark"] .tip-td-total { background: rgba(217,119,6,.08); }
|
||||||
|
[data-theme="dark"] .tip-td-avg { background: rgba(217,119,6,.04); border-left-color: rgba(217,119,6,.15); }
|
||||||
|
[data-theme="dark"] .tip-th-name { background: linear-gradient(135deg, #7c3aed 0%, #4f46e5 100%); }
|
||||||
|
|
||||||
/* Colonne mois courant */
|
/* Colonne mois courant */
|
||||||
.tip-th-month-current {
|
.tip-th-month-current {
|
||||||
|
|||||||
Reference in New Issue
Block a user