Amélioration des frais
This commit is contained in:
1 parent
981ddf5830
commit
3e8508d48b
12 files changed
+1083
-206
No files matched your search
@@ -10,8 +10,6 @@ import { useUi } from '../context/UiContext.jsx';
|
||||
import ConfirmModal from '../components/ConfirmModal.jsx';
|
||||
import InteretsChart from '../components/InteretsChart.jsx';
|
||||
import InteretsDistributionChart from '../components/InteretsDistributionChart.jsx';
|
||||
import FraisDistributionChart from '../components/FraisDistributionChart.jsx';
|
||||
import { FRAIS_CATEGORIE_LABELS } from '../components/MouvementsShared.jsx';
|
||||
import TableauInteretsPlateforme from '../components/TableauInteretsPlateforme.jsx';
|
||||
import DrillCellPanel from '../components/DrillCellPanel.jsx';
|
||||
import RembFormModal from '../components/RembFormModal.jsx';
|
||||
@@ -420,39 +418,6 @@ export default function Remboursements() {
|
||||
[byPlatform]
|
||||
);
|
||||
|
||||
/* ── Étape 5 : reporting "Frais payés" — filtré comme le reste de la page par
|
||||
filterPlatId (plateforme) et rembPlatYear (année), sur date_operation cette fois. ── */
|
||||
const fraisFiltered = useMemo(() =>
|
||||
allFraisOps.filter(f => {
|
||||
if (filterPlatId && String(f.plateforme_id) !== filterPlatId) return false;
|
||||
if (rembPlatYear && (f.date_operation || '').slice(0, 4) !== rembPlatYear) return false;
|
||||
return true;
|
||||
}),
|
||||
[allFraisOps, filterPlatId, rembPlatYear]
|
||||
);
|
||||
|
||||
const fraisPrevFiltered = useMemo(() =>
|
||||
allFraisOps.filter(f => {
|
||||
if (filterPlatId && String(f.plateforme_id) !== filterPlatId) return false;
|
||||
return (f.date_operation || '').slice(0, 4) === rembPrevYear;
|
||||
}),
|
||||
[allFraisOps, filterPlatId, rembPrevYear]
|
||||
);
|
||||
|
||||
const fraisTotal = useMemo(() => fraisFiltered.reduce((s, f) => s + (f.montant || 0), 0), [fraisFiltered]);
|
||||
const fraisPrevTotal = useMemo(() => fraisPrevFiltered.reduce((s, f) => s + (f.montant || 0), 0), [fraisPrevFiltered]);
|
||||
|
||||
const byCategorie = useMemo(() => {
|
||||
const map = {};
|
||||
for (const f of fraisFiltered) {
|
||||
const key = f.categorie || 'autre';
|
||||
if (!map[key]) map[key] = { categorie: key, montant: 0, count: 0 };
|
||||
map[key].montant += f.montant || 0;
|
||||
map[key].count += 1;
|
||||
}
|
||||
return Object.values(map).sort((a, b) => b.montant - a.montant);
|
||||
}, [fraisFiltered]);
|
||||
|
||||
/* ── Tableau remboursements : filtrage client-side ── */
|
||||
const tableRows = useMemo(() => {
|
||||
// Remboursements filtrés
|
||||
@@ -941,8 +906,6 @@ export default function Remboursements() {
|
||||
onClick={() => setActiveTab('remboursements')}>Remboursements</button>
|
||||
<button className={`dr-tab${activeTab === 'projections' ? ' active' : ''}`}
|
||||
onClick={() => setActiveTab('projections')}>Projections Échéances</button>
|
||||
<button className={`dr-tab${activeTab === 'frais' ? ' active' : ''}`}
|
||||
onClick={() => setActiveTab('frais')}>Frais</button>
|
||||
</div>}
|
||||
|
||||
{/* ══ ONGLET PLATEFORMES ══ */}
|
||||
@@ -1561,96 +1524,6 @@ export default function Remboursements() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ══ ONGLET FRAIS (Étape 5 — reporting consolidé) ══ */}
|
||||
{activeTab === 'frais' && (
|
||||
<div style={{ padding: '0 24px' }}>
|
||||
{/* ── KPI ── */}
|
||||
<div className="dr-kpi-row" style={{ marginBottom: 16 }}>
|
||||
<div className="kpi">
|
||||
<div className="label">
|
||||
Frais payés{rembPlatYear && ` en ${rembPlatYear}`}
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, margin: '4px 0 0' }}>
|
||||
<span style={{ fontSize: '1.35rem', fontWeight: 700 }}>{fmtEUR(fraisTotal)}</span>
|
||||
{rembPlatYear && <TrendBadge current={fraisTotal} prev={fraisPrevTotal} invert />}
|
||||
</div>
|
||||
{rembPlatYear && (
|
||||
<div style={{ fontSize: '0.8em', color: 'var(--text-muted)', marginTop: 5 }}>
|
||||
{fmtEUR(fraisPrevTotal)} en {rembPrevYear}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Graphique ── */}
|
||||
<div className="charts-row" style={{ marginBottom: 16 }}>
|
||||
<FraisDistributionChart rows={fraisFiltered} />
|
||||
</div>
|
||||
|
||||
{/* ── Tableau par catégorie ── */}
|
||||
<div className="card" style={{ marginBottom: 16 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14 }}>
|
||||
<h3 style={{ margin: 0 }}>
|
||||
Répartition par catégorie
|
||||
{rembPlatYear && <span style={{ marginLeft: 8 }}>pour l'année {rembPlatYear}</span>}
|
||||
</h3>
|
||||
{filterPlatId && (
|
||||
<span style={{ fontSize: 12, color: 'var(--text-muted)', fontStyle: 'italic' }}>
|
||||
Filtré sur cette plateforme
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Catégorie</th>
|
||||
<th className="num">Montant</th>
|
||||
<th className="num">Nombre</th>
|
||||
<th className="num" style={{ minWidth: 120 }}>Poids</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{byCategorie.length === 0 && (
|
||||
<tr><td colSpan={4} className="text-muted" style={{ textAlign: 'center', padding: 24 }}>
|
||||
{loading ? 'Chargement…' : 'Aucun frais enregistré pour ces critères'}
|
||||
</td></tr>
|
||||
)}
|
||||
{byCategorie.map(c => {
|
||||
const poids = fraisTotal !== 0 ? (c.montant / fraisTotal) * 100 : 0;
|
||||
return (
|
||||
<tr key={c.categorie}>
|
||||
<td>{FRAIS_CATEGORIE_LABELS[c.categorie] || c.categorie}</td>
|
||||
<td className="num">{fmtEUR(c.montant)}</td>
|
||||
<td className="num" style={{ color: 'var(--text-muted)', fontSize: 'var(--fs-sm)' }}>{c.count}</td>
|
||||
<td className="num">
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, justifyContent: 'flex-end' }}>
|
||||
<div style={{ width: 48, height: 5, borderRadius: 3, background: 'var(--surface-2)', overflow: 'hidden' }}>
|
||||
<div style={{ width: `${Math.max(0, poids)}%`, height: '100%', background: 'var(--primary)', borderRadius: 3 }} />
|
||||
</div>
|
||||
<span style={{ minWidth: 36, textAlign: 'right', fontSize: 'var(--fs-sm)' }}>
|
||||
{poids.toFixed(1)} %
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
{byCategorie.length > 1 && (
|
||||
<tfoot>
|
||||
<tr style={{ borderTop: '2px solid var(--border)', fontWeight: 700, background: 'var(--surface-2)' }}>
|
||||
<td>Total — {byCategorie.length} catégories</td>
|
||||
<td className="num">{fmtEUR(fraisTotal)}</td>
|
||||
<td className="num" style={{ color: 'var(--text-muted)', fontSize: 'var(--fs-sm)' }}>{fraisFiltered.length}</td>
|
||||
<td className="num"><span style={{ fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>100 %</span></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
)}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ══ MODAL ══ */}
|
||||
{(() => {
|
||||
|
||||
Reference in new issue
Block a user