From eb236f4b8f8e2408c98d6a5d76af6f00d466f3db Mon Sep 17 00:00:00 2001 From: Manus Date: Wed, 10 Jun 2026 07:43:25 +0000 Subject: [PATCH] =?UTF-8?q?Checkpoint:=20Suppression=20du=20bloc=20R=C3=A9?= =?UTF-8?q?partition=20par=20cat=C3=A9gorie=20en=20bas=20de=20la=20vue=20P?= =?UTF-8?q?ostes=20(doublon=20des=20vignettes=20KPI).=20Refonte=20de=20la?= =?UTF-8?q?=20vue=20=C3=89tablissements=20:=20tableau=20scrollable=20horiz?= =?UTF-8?q?ontalement=20avec=20tous=20les=2035=20postes=20en=20colonnes,?= =?UTF-8?q?=20code=20couleur=20vert=20(bordure=20emerald=20=3D=20calcul=20?= =?UTF-8?q?prorata=20automatique)=20/=20bleu=20(bordure=20bleue=20=3D=20sa?= =?UTF-8?q?isie=20manuelle),=20colonnes=20Code=20et=20=C3=89tablissement?= =?UTF-8?q?=20sticky=20=C3=A0=20gauche,=20colonne=20Total=20sticky=20?= =?UTF-8?q?=C3=A0=20droite,=20l=C3=A9gende=20en=20haut=20du=20tableau.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/DsiOpex.tsx | 202 ++++++++++++++++++++--------------- 1 file changed, 113 insertions(+), 89 deletions(-) diff --git a/client/src/pages/DsiOpex.tsx b/client/src/pages/DsiOpex.tsx index d5daa77..9f4f3fd 100644 --- a/client/src/pages/DsiOpex.tsx +++ b/client/src/pages/DsiOpex.tsx @@ -1159,101 +1159,125 @@ export default function DsiOpex() { - {/* Répartition par catégorie */} -
-

- - Répartition par catégorie — {annee} -

-
- {Object.entries(totalParCategorie) - .filter(([, v]) => v > 0) - .sort(([, a], [, b]) => b - a) - .map(([cat, montant]) => { - const pct = totalGlobal > 0 ? (montant / totalGlobal) * 100 : 0; - const colors = getCatColors(cat); - return ( -
- - {cat} - -
-
-
- - {formatEuros(montant)} - - - {pct.toFixed(1)}% - -
- ); - })} -
-
+
)} {/* === VUE PAR ÉTABLISSEMENT === */} {viewMode === 'etablissements' && ( -
- - - - - - - {opexData2026.postes.slice(0, showDetails ? 8 : 4).map(p => ( - - ))} - - - - - {sortedEtabs.map((etab, idx) => ( - - - - - {opexData2026.postes.slice(0, showDetails ? 8 : 4).map(p => ( - +
+ {/* Légende code couleur */} +
+
+ + Calcul automatique (prorata classe 6) +
+
+ + Saisie manuelle +
+
+ + {/* Tableau scrollable horizontalement avec tous les postes */} +
+
+
Code - - Base répartition - {p.libelle.split(' ').slice(0, 3).join(' ')} - - -
- {etab.code} - {etab.nom} - {etab.base_repartition > 0 - ? new Intl.NumberFormat('fr-FR', { maximumFractionDigits: 0 }).format(etab.base_repartition) + ' €' - : '—'} - - {etab.montants[p.libelle] > 0 - ? new Intl.NumberFormat('fr-FR', { maximumFractionDigits: 0 }).format(Math.round(etab.montants[p.libelle])) + ' €' - : '—'} -
+ + + + + {/* Colonnes postes — vert = prorata auto, bleu = manuel */} + {opexState.lignes.map(ligne => { + const srcPoste = getOpexDataForAnnee(annee).postes.find(p => p.libelle === ligne.libelle); + const isManuel = srcPoste?.mode_ventilation === 'Manuel' || ligne.isCustom; + return ( + + ); + })} + + + + + {sortedEtabs.map((etab, idx) => ( + + + + {opexState.lignes.map(ligne => { + const srcPoste = getOpexDataForAnnee(annee).postes.find(p => p.libelle === ligne.libelle); + const isManuel = srcPoste?.mode_ventilation === 'Manuel' || ligne.isCustom; + const montant = etab.montants[ligne.libelle] ?? 0; + return ( + + ); + })} + + ))} - - - ))} - - - - - - - - -
Code + + + + {ligne.libelle.length > 18 ? ligne.libelle.slice(0, 16) + '…' : ligne.libelle} + + {formatEuros(ligne.montant)} + + +
+ {etab.code} + {etab.nom} + {montant > 0 + ? new Intl.NumberFormat('fr-FR', { maximumFractionDigits: 0 }).format(montant) + ' €' + : } + + 0 ? 'text-orange-600' : 'text-muted-foreground'}> + {etab.total > 0 ? formatEuros(etab.total) : '—'} + +
- 0 ? 'text-orange-600' : 'text-muted-foreground'}> - {etab.total > 0 ? formatEuros(etab.total) : '—'} - -
- TOTAL — {sortedEtabs.length} établissements - TOTAL - {formatEuros(sortedEtabs.reduce((s, e) => s + e.total, 0))} -
+ + + + + TOTAL — {sortedEtabs.length} établissements + + {opexState.lignes.map(ligne => { + const srcPoste = getOpexDataForAnnee(annee).postes.find(p => p.libelle === ligne.libelle); + const isManuel = srcPoste?.mode_ventilation === 'Manuel' || ligne.isCustom; + const totalColonne = sortedEtabs.reduce((s, e) => s + (e.montants[ligne.libelle] ?? 0), 0); + return ( + + {totalColonne > 0 + ? new Intl.NumberFormat('fr-FR', { maximumFractionDigits: 0 }).format(totalColonne) + ' €' + : '—'} + + ); + })} + + {formatEuros(sortedEtabs.reduce((s, e) => s + e.total, 0))} + + + + +
+ )}