Checkpoint: Sélecteur d'année global (2025, 2026, 2027+) sur Renouvellement PC, CAPEX-Construction, CAPEX-Synthèse et DSI-OPEX. Données CAPEX isolées par année en localStorage. Titres dynamiques selon l'année sélectionnée.

This commit is contained in:
Manus
2026-06-03 09:49:14 +00:00
parent 858f0dc4d9
commit 787928c722
7 changed files with 189 additions and 55 deletions

View File

@@ -5,6 +5,7 @@ import { Route, Switch } from "wouter";
import ErrorBoundary from "./components/ErrorBoundary"; import ErrorBoundary from "./components/ErrorBoundary";
import { ThemeProvider } from "./contexts/ThemeContext"; import { ThemeProvider } from "./contexts/ThemeContext";
import { ParametresProvider } from "./contexts/ParametresContext"; import { ParametresProvider } from "./contexts/ParametresContext";
import { AnneeProvider } from "./contexts/AnneeContext";
import Home from "./pages/Home"; import Home from "./pages/Home";
import Parametres from "./pages/Parametres"; import Parametres from "./pages/Parametres";
import Budget2027 from "./pages/Budget2027"; import Budget2027 from "./pages/Budget2027";
@@ -35,12 +36,14 @@ function App() {
return ( return (
<ErrorBoundary> <ErrorBoundary>
<ParametresProvider> <ParametresProvider>
<ThemeProvider defaultTheme="light"> <AnneeProvider>
<TooltipProvider> <ThemeProvider defaultTheme="light">
<Toaster /> <TooltipProvider>
<Router /> <Toaster />
</TooltipProvider> <Router />
</ThemeProvider> </TooltipProvider>
</ThemeProvider>
</AnneeProvider>
</ParametresProvider> </ParametresProvider>
</ErrorBoundary> </ErrorBoundary>
); );

View File

@@ -0,0 +1,66 @@
// AnneeSelectorBar.tsx — Sélecteur d'année réutilisable
// Utilisé dans : Renouvellement PC, CAPEX-Construction, CAPEX-Synthèse, DSI-OPEX
import { Calendar, ChevronLeft, ChevronRight } from 'lucide-react';
import { useAnnee, ANNEES_DISPONIBLES } from '../contexts/AnneeContext';
interface AnneeSelectorBarProps {
label?: string;
className?: string;
}
export function AnneeSelectorBar({ label, className = '' }: AnneeSelectorBarProps) {
const { annee, setAnnee } = useAnnee();
const idx = ANNEES_DISPONIBLES.indexOf(annee);
const prev = () => { if (idx > 0) setAnnee(ANNEES_DISPONIBLES[idx - 1]); };
const next = () => { if (idx < ANNEES_DISPONIBLES.length - 1) setAnnee(ANNEES_DISPONIBLES[idx + 1]); };
return (
<div className={`flex items-center gap-2 ${className}`}>
{label && (
<span className="text-xs text-muted-foreground font-medium mr-1">{label}</span>
)}
<div className="flex items-center gap-1 bg-card border border-border rounded-lg px-1 py-0.5 shadow-sm">
<button
onClick={prev}
disabled={idx === 0}
className="p-1 rounded hover:bg-muted/60 transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
title="Année précédente"
>
<ChevronLeft className="w-3.5 h-3.5 text-muted-foreground" />
</button>
{/* Boutons d'année */}
<div className="flex items-center gap-0.5">
{ANNEES_DISPONIBLES.map(a => (
<button
key={a}
onClick={() => setAnnee(a)}
className={`px-2.5 py-1 rounded text-xs font-semibold transition-all duration-150 ${
a === annee
? 'bg-primary text-white shadow-sm'
: 'text-muted-foreground hover:bg-muted/60 hover:text-foreground'
}`}
>
{a}
</button>
))}
</div>
<button
onClick={next}
disabled={idx === ANNEES_DISPONIBLES.length - 1}
className="p-1 rounded hover:bg-muted/60 transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
title="Année suivante"
>
<ChevronRight className="w-3.5 h-3.5 text-muted-foreground" />
</button>
</div>
<div className="flex items-center gap-1 text-xs text-muted-foreground">
<Calendar className="w-3.5 h-3.5" />
<span>Exercice {annee}</span>
</div>
</div>
);
}

View File

@@ -0,0 +1,52 @@
// AnneeContext.tsx — Contexte global pour l'année sélectionnée
// Partagé entre toutes les pages : Renouvellement PC, CAPEX-Construction, CAPEX-Synthèse, DSI-OPEX
import { createContext, useContext, useState, ReactNode } from 'react';
export const ANNEES_DISPONIBLES = [2025, 2026, 2027, 2028, 2029, 2030];
export const ANNEE_DEFAUT = 2027;
interface AnneeContextType {
annee: number;
setAnnee: (a: number) => void;
}
const AnneeContext = createContext<AnneeContextType>({
annee: ANNEE_DEFAUT,
setAnnee: () => {},
});
export function AnneeProvider({ children }: { children: ReactNode }) {
const [annee, setAnneeState] = useState<number>(() => {
try {
const stored = localStorage.getItem('itinova_annee_selectionnee');
const parsed = stored ? parseInt(stored, 10) : ANNEE_DEFAUT;
return ANNEES_DISPONIBLES.includes(parsed) ? parsed : ANNEE_DEFAUT;
} catch { return ANNEE_DEFAUT; }
});
const setAnnee = (a: number) => {
setAnneeState(a);
try { localStorage.setItem('itinova_annee_selectionnee', String(a)); } catch {}
};
return (
<AnneeContext.Provider value={{ annee, setAnnee }}>
{children}
</AnneeContext.Provider>
);
}
export function useAnnee() {
return useContext(AnneeContext);
}
// Génère la clé localStorage pour les saisies CAPEX d'une année donnée
export function getCapexStorageKey(annee: number): string {
return `itinova_budget_capex_${annee}`;
}
// Génère la clé localStorage pour les saisies OPEX d'une année donnée
export function getOpexStorageKey(annee: number): string {
return `itinova_budget_opex_${annee}`;
}

View File

@@ -25,6 +25,8 @@ import {
AlertCircle, AlertCircle,
} from 'lucide-react'; } from 'lucide-react';
import { AppSidebar } from '../components/AppSidebar'; import { AppSidebar } from '../components/AppSidebar';
import { AnneeSelectorBar } from '../components/AnneeSelectorBar';
import { useAnnee, getCapexStorageKey } from '../contexts/AnneeContext';
import bp2027Raw from '../data_bp2027.json'; import bp2027Raw from '../data_bp2027.json';
import budgetRaw from '../data_budget.json'; import budgetRaw from '../data_budget.json';
import { useParametres } from '../contexts/ParametresContext'; import { useParametres } from '../contexts/ParametresContext';
@@ -35,7 +37,7 @@ import type { BudgetData } from '../types/budget';
const bp2027Data = bp2027Raw as BP2027Data; const bp2027Data = bp2027Raw as BP2027Data;
const budgetData = budgetRaw as BudgetData; const budgetData = budgetRaw as BudgetData;
const STORAGE_KEY = 'itinova_budget2027_saisies'; // La clé de stockage est désormais dynamique par année (voir getCapexStorageKey)
// Icônes par clé de champ // Icônes par clé de champ
const ICON_MAP: Record<string, React.ElementType> = { const ICON_MAP: Record<string, React.ElementType> = {
@@ -87,15 +89,15 @@ const CHAMPS: { key: keyof BudgetFormValues; label: string; description: string
{ key: 'autres', label: 'Autres investissements SI', description: 'Tout autre investissement informatique' }, { key: 'autres', label: 'Autres investissements SI', description: 'Tout autre investissement informatique' },
]; ];
function loadSaisies(): Record<string, Partial<BudgetFormValues>> { function loadSaisiesForAnnee(annee: number): Record<string, Partial<BudgetFormValues>> {
try { try {
const raw = localStorage.getItem(STORAGE_KEY); const raw = localStorage.getItem(getCapexStorageKey(annee));
return raw ? JSON.parse(raw) : {}; return raw ? JSON.parse(raw) : {};
} catch { return {}; } } catch { return {}; }
} }
function saveSaisies(data: Record<string, Partial<BudgetFormValues>>) { function saveSaisiesForAnnee(annee: number, data: Record<string, Partial<BudgetFormValues>>) {
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); } catch {} try { localStorage.setItem(getCapexStorageKey(annee), JSON.stringify(data)); } catch {}
} }
function getDefaultValues(etabCode: string, renouvellement2027: number): BudgetFormValues { function getDefaultValues(etabCode: string, renouvellement2027: number): BudgetFormValues {
@@ -167,8 +169,9 @@ function CurrencyInput({
export default function Budget2027() { export default function Budget2027() {
const [, navigate] = useLocation(); const [, navigate] = useLocation();
const { parametres } = useParametres(); const { parametres } = useParametres();
const { annee } = useAnnee();
const [selectedCode, setSelectedCode] = useState<string>(''); const [selectedCode, setSelectedCode] = useState<string>('');
const [saisies, setSaisies] = useState<Record<string, Partial<BudgetFormValues>>>(loadSaisies); const [saisies, setSaisies] = useState<Record<string, Partial<BudgetFormValues>>>(() => loadSaisiesForAnnee(annee));
const [saved, setSaved] = useState(false); const [saved, setSaved] = useState(false);
const [searchEtab, setSearchEtab] = useState(''); const [searchEtab, setSearchEtab] = useState('');
const [dropdownOpen, setDropdownOpen] = useState(false); const [dropdownOpen, setDropdownOpen] = useState(false);
@@ -213,6 +216,12 @@ export default function Budget2027() {
); );
}, [formValues]); }, [formValues]);
// Recharger les saisies quand l'année change
useEffect(() => {
setSaisies(loadSaisiesForAnnee(annee));
setSelectedCode('');
}, [annee]);
const updateField = (key: keyof BudgetFormValues, value: number | string) => { const updateField = (key: keyof BudgetFormValues, value: number | string) => {
setSaisies(prev => { setSaisies(prev => {
const next = { const next = {
@@ -222,14 +231,14 @@ export default function Budget2027() {
[key]: value, [key]: value,
}, },
}; };
saveSaisies(next); saveSaisiesForAnnee(annee, next);
return next; return next;
}); });
setSaved(false); setSaved(false);
}; };
const handleSave = () => { const handleSave = () => {
saveSaisies(saisies); saveSaisiesForAnnee(annee, saisies);
setSaved(true); setSaved(true);
setTimeout(() => setSaved(false), 2500); setTimeout(() => setSaved(false), 2500);
}; };
@@ -238,7 +247,7 @@ export default function Budget2027() {
setSaisies(prev => { setSaisies(prev => {
const next = { ...prev }; const next = { ...prev };
delete next[selectedCode]; delete next[selectedCode];
saveSaisies(next); saveSaisiesForAnnee(annee, next);
return next; return next;
}); });
}; };
@@ -265,12 +274,15 @@ export default function Budget2027() {
<div className="flex items-center justify-between gap-4"> <div className="flex items-center justify-between gap-4">
<div> <div>
<h1 className="text-xl font-bold text-foreground" style={{ fontFamily: 'Sora, sans-serif' }}> <h1 className="text-xl font-bold text-foreground" style={{ fontFamily: 'Sora, sans-serif' }}>
Construction Budget 2027 CAPEX - Construction {annee}
</h1> </h1>
<p className="text-sm text-muted-foreground mt-0.5"> <p className="text-sm text-muted-foreground mt-0.5">
Campagne budgétaire CAPEX DSI Prévisionnel 2027 Campagne budgétaire CAPEX DSI Prévisionnel {annee}
</p> </p>
</div> </div>
<div className="flex items-center gap-3">
<AnneeSelectorBar />
</div>
{selectedCode && ( {selectedCode && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button <button

View File

@@ -18,6 +18,8 @@ import {
Info, Info,
} from 'lucide-react'; } from 'lucide-react';
import { AppSidebar } from '../components/AppSidebar'; import { AppSidebar } from '../components/AppSidebar';
import { AnneeSelectorBar } from '../components/AnneeSelectorBar';
import { useAnnee, getCapexStorageKey } from '../contexts/AnneeContext';
import bp2027Raw from '../data_bp2027.json'; import bp2027Raw from '../data_bp2027.json';
import budgetRaw from '../data_budget.json'; import budgetRaw from '../data_budget.json';
import { useParametres } from '../contexts/ParametresContext'; import { useParametres } from '../contexts/ParametresContext';
@@ -27,7 +29,7 @@ import type { BudgetData } from '../types/budget';
const bp2027Data = bp2027Raw as BP2027Data; const bp2027Data = bp2027Raw as BP2027Data;
const budgetData = budgetRaw as BudgetData; const budgetData = budgetRaw as BudgetData;
const STORAGE_KEY = 'itinova_budget2027_saisies'; // Clé de stockage dynamique par année (voir getCapexStorageKey)
// Colonnes du tableau (correspondance exacte avec le fichier Excel BP2027) // Colonnes du tableau (correspondance exacte avec le fichier Excel BP2027)
const COLONNES = [ const COLONNES = [
@@ -56,9 +58,9 @@ const COL_COLORS: Record<string, { header: string; cell: string; total: string }
slate: { header: 'bg-slate-500 text-white', cell: 'text-slate-700', total: 'bg-slate-50 text-slate-800 font-semibold' }, slate: { header: 'bg-slate-500 text-white', cell: 'text-slate-700', total: 'bg-slate-50 text-slate-800 font-semibold' },
}; };
function loadSaisies(): Record<string, Partial<BudgetFormValues>> { function loadSaisiesForAnnee(annee: number): Record<string, Partial<BudgetFormValues>> {
try { try {
const raw = localStorage.getItem(STORAGE_KEY); const raw = localStorage.getItem(getCapexStorageKey(annee));
return raw ? JSON.parse(raw) : {}; return raw ? JSON.parse(raw) : {};
} catch { return {}; } } catch { return {}; }
} }
@@ -83,6 +85,7 @@ type SortDir = 'asc' | 'desc';
export default function DsiCapex() { export default function DsiCapex() {
const { parametres } = useParametres(); const { parametres } = useParametres();
const { annee } = useAnnee();
const [saisies, setSaisies] = useState<Record<string, Partial<BudgetFormValues>>>({}); const [saisies, setSaisies] = useState<Record<string, Partial<BudgetFormValues>>>({});
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const [sortCol, setSortCol] = useState<string>('nom'); const [sortCol, setSortCol] = useState<string>('nom');
@@ -91,8 +94,8 @@ export default function DsiCapex() {
const [lastRefresh, setLastRefresh] = useState(Date.now()); const [lastRefresh, setLastRefresh] = useState(Date.now());
useEffect(() => { useEffect(() => {
setSaisies(loadSaisies()); setSaisies(loadSaisiesForAnnee(annee));
}, [lastRefresh]); }, [lastRefresh, annee]);
const handleRefresh = () => setLastRefresh(Date.now()); const handleRefresh = () => setLastRefresh(Date.now());
@@ -191,19 +194,22 @@ export default function DsiCapex() {
<div className="flex items-center justify-between gap-4 flex-wrap"> <div className="flex items-center justify-between gap-4 flex-wrap">
<div> <div>
<h1 className="text-xl font-bold text-foreground" style={{ fontFamily: 'Sora, sans-serif' }}> <h1 className="text-xl font-bold text-foreground" style={{ fontFamily: 'Sora, sans-serif' }}>
DSI CAPEX Prévisionnel Budget 2027 CAPEX - Synthèse {annee}
</h1> </h1>
<p className="text-sm text-muted-foreground mt-0.5"> <p className="text-sm text-muted-foreground mt-0.5">
Tableau consolidé des investissements SI par établissement Tableau consolidé des investissements SI par établissement
</p> </p>
</div> </div>
<button <div className="flex items-center gap-3">
onClick={handleRefresh} <AnneeSelectorBar />
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-border text-sm text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors" <button
> onClick={handleRefresh}
<RefreshCw className="w-4 h-4" /> className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-border text-sm text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors"
Actualiser >
</button> <RefreshCw className="w-4 h-4" />
Actualiser
</button>
</div>
</div> </div>
</header> </header>
@@ -213,7 +219,7 @@ export default function DsiCapex() {
<div className="bg-card border border-border rounded-xl p-4"> <div className="bg-card border border-border rounded-xl p-4">
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<Euro className="w-4 h-4 text-orange-500" /> <Euro className="w-4 h-4 text-orange-500" />
<span className="text-xs text-muted-foreground uppercase tracking-wide font-medium">Total CAPEX 2027</span> <span className="text-xs text-muted-foreground uppercase tracking-wide font-medium">Total CAPEX {annee}</span>
</div> </div>
<p className="text-2xl font-bold text-orange-600 tabular-nums" style={{ fontFamily: 'Sora, sans-serif' }}> <p className="text-2xl font-bold text-orange-600 tabular-nums" style={{ fontFamily: 'Sora, sans-serif' }}>
{formatEuros(totalGeneral)} {formatEuros(totalGeneral)}

View File

@@ -18,6 +18,8 @@ import {
ArrowUpDown, ArrowUpDown,
} from 'lucide-react'; } from 'lucide-react';
import { AppSidebar } from '../components/AppSidebar'; import { AppSidebar } from '../components/AppSidebar';
import { AnneeSelectorBar } from '../components/AnneeSelectorBar';
import { useAnnee } from '../contexts/AnneeContext';
import opexRaw from '../data_opex.json'; import opexRaw from '../data_opex.json';
import { formatEuros } from '../lib/format'; import { formatEuros } from '../lib/format';
@@ -82,7 +84,7 @@ type ViewMode = 'etablissements' | 'postes';
type SortDir = 'asc' | 'desc'; type SortDir = 'asc' | 'desc';
export default function DsiOpex() { export default function DsiOpex() {
const [selectedYear, setSelectedYear] = useState<2026 | 2027>(2026); const { annee } = useAnnee();
const [viewMode, setViewMode] = useState<ViewMode>('postes'); const [viewMode, setViewMode] = useState<ViewMode>('postes');
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const [selectedCategorie, setSelectedCategorie] = useState('Toutes'); const [selectedCategorie, setSelectedCategorie] = useState('Toutes');
@@ -190,22 +192,11 @@ export default function DsiOpex() {
{opexData.meta.nb_postes} postes de charges · {opexData.meta.nb_etablissements} établissements {opexData.meta.nb_postes} postes de charges · {opexData.meta.nb_etablissements} établissements
</p> </p>
</div> </div>
{/* Sélecteur d'année */} {/* Sélecteur d'année remplacé par AnneeSelectorBar global */}
<div className="flex items-center gap-1 bg-muted rounded-lg p-1"> <div className="flex items-center gap-3">
{([2026, 2027] as const).map(year => ( <AnneeSelectorBar />
<button
key={year}
onClick={() => setSelectedYear(year)}
className={`px-4 py-1.5 rounded-md text-sm font-semibold transition-all ${
selectedYear === year
? 'bg-primary text-white shadow-sm'
: 'text-muted-foreground hover:text-foreground'
}`}
>
{year}
</button>
))}
</div> </div>
</div> </div>
</header> </header>
@@ -215,7 +206,7 @@ export default function DsiOpex() {
<div className="bg-card border border-border rounded-xl p-4"> <div className="bg-card border border-border rounded-xl p-4">
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<Euro className="w-4 h-4 text-orange-500" /> <Euro className="w-4 h-4 text-orange-500" />
<span className="text-xs text-muted-foreground uppercase tracking-wide font-medium">Total OPEX {selectedYear}</span> <span className="text-xs text-muted-foreground uppercase tracking-wide font-medium">Total OPEX {annee}</span>
</div> </div>
<p className="text-2xl font-bold text-orange-600 tabular-nums" style={{ fontFamily: 'Sora, sans-serif' }}> <p className="text-2xl font-bold text-orange-600 tabular-nums" style={{ fontFamily: 'Sora, sans-serif' }}>
{formatEuros(opexData.total_global)} {formatEuros(opexData.total_global)}
@@ -354,7 +345,7 @@ export default function DsiOpex() {
{showDetails && <th className="text-left px-4 py-3 font-medium text-muted-foreground hidden xl:table-cell">Compte</th>} {showDetails && <th className="text-left px-4 py-3 font-medium text-muted-foreground hidden xl:table-cell">Compte</th>}
<th className="text-right px-4 py-3 font-medium text-muted-foreground">Budget N-1</th> <th className="text-right px-4 py-3 font-medium text-muted-foreground">Budget N-1</th>
<th className="text-right px-4 py-3 font-medium text-muted-foreground"> <th className="text-right px-4 py-3 font-medium text-muted-foreground">
<SortBtn col="montant" label={`Prév. ${selectedYear}`} /> <SortBtn col="montant" label={`Prév. ${annee}`} />
</th> </th>
<th className="text-right px-4 py-3 font-medium text-muted-foreground w-24">Répartition</th> <th className="text-right px-4 py-3 font-medium text-muted-foreground w-24">Répartition</th>
</tr> </tr>
@@ -441,7 +432,7 @@ export default function DsiOpex() {
<tfoot> <tfoot>
<tr className="bg-muted/40 border-t-2 border-border"> <tr className="bg-muted/40 border-t-2 border-border">
<td colSpan={showDetails ? 7 : 6} className="px-4 py-3 font-bold text-foreground"> <td colSpan={showDetails ? 7 : 6} className="px-4 py-3 font-bold text-foreground">
TOTAL {selectedCategorie !== 'Toutes' ? selectedCategorie : 'OPEX DSI'} {selectedYear} TOTAL {selectedCategorie !== 'Toutes' ? selectedCategorie : 'OPEX DSI'} {annee}
</td> </td>
<td className="px-4 py-3 text-right font-bold text-orange-600 text-base tabular-nums"> <td className="px-4 py-3 text-right font-bold text-orange-600 text-base tabular-nums">
{formatEuros(totalFiltre)} {formatEuros(totalFiltre)}
@@ -460,7 +451,7 @@ export default function DsiOpex() {
<div className="mt-6 bg-card border border-border rounded-xl p-5"> <div className="mt-6 bg-card border border-border rounded-xl p-5">
<h3 className="font-semibold text-foreground mb-4 flex items-center gap-2" style={{ fontFamily: 'Sora, sans-serif' }}> <h3 className="font-semibold text-foreground mb-4 flex items-center gap-2" style={{ fontFamily: 'Sora, sans-serif' }}>
<BarChart3 className="w-4 h-4 text-muted-foreground" /> <BarChart3 className="w-4 h-4 text-muted-foreground" />
Répartition par catégorie {selectedYear} Répartition par catégorie {annee}
</h3> </h3>
<div className="space-y-3"> <div className="space-y-3">
{Object.entries(totalParCategorie) {Object.entries(totalParCategorie)
@@ -518,7 +509,7 @@ export default function DsiOpex() {
</th> </th>
))} ))}
<th className="text-right px-4 py-3 font-medium text-muted-foreground"> <th className="text-right px-4 py-3 font-medium text-muted-foreground">
<SortBtn col="total" label={`Total ${selectedYear}`} /> <SortBtn col="total" label={`Total ${annee}`} />
</th> </th>
</tr> </tr>
</thead> </thead>

View File

@@ -18,6 +18,8 @@ import {
List, List,
} from 'lucide-react'; } from 'lucide-react';
import { AppSidebar } from '../components/AppSidebar'; import { AppSidebar } from '../components/AppSidebar';
import { AnneeSelectorBar } from '../components/AnneeSelectorBar';
import { useAnnee } from '../contexts/AnneeContext';
import { useBudgetData } from '../hooks/useBudgetData'; import { useBudgetData } from '../hooks/useBudgetData';
import { formatEuros, formatNumber } from '../lib/format'; import { formatEuros, formatNumber } from '../lib/format';
import { EtablissementCard } from '../components/EtablissementCard'; import { EtablissementCard } from '../components/EtablissementCard';
@@ -42,6 +44,7 @@ export default function Home() {
setFilterType, setFilterType,
} = useBudgetData(); } = useBudgetData();
const { annee } = useAnnee();
const [selectedEtab, setSelectedEtab] = useState<Etablissement | null>(null); const [selectedEtab, setSelectedEtab] = useState<Etablissement | null>(null);
const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const [viewMode, setViewMode] = useState<ViewMode>('grid'); const [viewMode, setViewMode] = useState<ViewMode>('grid');
@@ -71,13 +74,14 @@ export default function Home() {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<h1 className="text-xl font-bold text-foreground" style={{ fontFamily: 'Sora, sans-serif' }}> <h1 className="text-xl font-bold text-foreground" style={{ fontFamily: 'Sora, sans-serif' }}>
Renouvellement PC 2027 Renouvellement PC {annee}
</h1> </h1>
<p className="text-sm text-muted-foreground mt-0.5"> <p className="text-sm text-muted-foreground mt-0.5">
{totalEtablissements} établissements Itinova Vétusté du parc PC {totalEtablissements} établissements Itinova Vétusté du parc PC
</p> </p>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-3">
<AnneeSelectorBar />
<span className="text-xs text-muted-foreground bg-muted px-2.5 py-1 rounded-full"> <span className="text-xs text-muted-foreground bg-muted px-2.5 py-1 rounded-full">
{etablissements.length} affiché{etablissements.length > 1 ? 's' : ''} {etablissements.length} affiché{etablissements.length > 1 ? 's' : ''}
</span> </span>
@@ -92,7 +96,7 @@ export default function Home() {
icon={Euro} icon={Euro}
label="Budget global" label="Budget global"
value={formatEuros(meta.budget_global)} value={formatEuros(meta.budget_global)}
subtitle="TTC — exercice 2027" subtitle={`TTC — exercice ${annee}`}
accentColor="text-orange-600" accentColor="text-orange-600"
delay={0} delay={0}
/> />