diff --git a/client/src/App.tsx b/client/src/App.tsx
index 171b31f..095879c 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -5,6 +5,7 @@ import { Route, Switch } from "wouter";
import ErrorBoundary from "./components/ErrorBoundary";
import { ThemeProvider } from "./contexts/ThemeContext";
import { ParametresProvider } from "./contexts/ParametresContext";
+import { AnneeProvider } from "./contexts/AnneeContext";
import Home from "./pages/Home";
import Parametres from "./pages/Parametres";
import Budget2027 from "./pages/Budget2027";
@@ -35,12 +36,14 @@ function App() {
return (
-
-
-
-
-
-
+
+
+
+
+
+
+
+
);
diff --git a/client/src/components/AnneeSelectorBar.tsx b/client/src/components/AnneeSelectorBar.tsx
new file mode 100644
index 0000000..4be89b3
--- /dev/null
+++ b/client/src/components/AnneeSelectorBar.tsx
@@ -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 (
+
+ {label && (
+
{label}
+ )}
+
+
+
+ {/* Boutons d'année */}
+
+ {ANNEES_DISPONIBLES.map(a => (
+
+ ))}
+
+
+
+
+
+
+ Exercice {annee}
+
+
+ );
+}
diff --git a/client/src/contexts/AnneeContext.tsx b/client/src/contexts/AnneeContext.tsx
new file mode 100644
index 0000000..3c4bebd
--- /dev/null
+++ b/client/src/contexts/AnneeContext.tsx
@@ -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({
+ annee: ANNEE_DEFAUT,
+ setAnnee: () => {},
+});
+
+export function AnneeProvider({ children }: { children: ReactNode }) {
+ const [annee, setAnneeState] = useState(() => {
+ 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 (
+
+ {children}
+
+ );
+}
+
+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}`;
+}
diff --git a/client/src/pages/Budget2027.tsx b/client/src/pages/Budget2027.tsx
index bb7b9b4..8ab376b 100644
--- a/client/src/pages/Budget2027.tsx
+++ b/client/src/pages/Budget2027.tsx
@@ -25,6 +25,8 @@ import {
AlertCircle,
} from 'lucide-react';
import { AppSidebar } from '../components/AppSidebar';
+import { AnneeSelectorBar } from '../components/AnneeSelectorBar';
+import { useAnnee, getCapexStorageKey } from '../contexts/AnneeContext';
import bp2027Raw from '../data_bp2027.json';
import budgetRaw from '../data_budget.json';
import { useParametres } from '../contexts/ParametresContext';
@@ -35,7 +37,7 @@ import type { BudgetData } from '../types/budget';
const bp2027Data = bp2027Raw as BP2027Data;
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
const ICON_MAP: Record = {
@@ -87,15 +89,15 @@ const CHAMPS: { key: keyof BudgetFormValues; label: string; description: string
{ key: 'autres', label: 'Autres investissements SI', description: 'Tout autre investissement informatique' },
];
-function loadSaisies(): Record> {
+function loadSaisiesForAnnee(annee: number): Record> {
try {
- const raw = localStorage.getItem(STORAGE_KEY);
+ const raw = localStorage.getItem(getCapexStorageKey(annee));
return raw ? JSON.parse(raw) : {};
} catch { return {}; }
}
-function saveSaisies(data: Record>) {
- try { localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); } catch {}
+function saveSaisiesForAnnee(annee: number, data: Record>) {
+ try { localStorage.setItem(getCapexStorageKey(annee), JSON.stringify(data)); } catch {}
}
function getDefaultValues(etabCode: string, renouvellement2027: number): BudgetFormValues {
@@ -167,8 +169,9 @@ function CurrencyInput({
export default function Budget2027() {
const [, navigate] = useLocation();
const { parametres } = useParametres();
+ const { annee } = useAnnee();
const [selectedCode, setSelectedCode] = useState('');
- const [saisies, setSaisies] = useState>>(loadSaisies);
+ const [saisies, setSaisies] = useState>>(() => loadSaisiesForAnnee(annee));
const [saved, setSaved] = useState(false);
const [searchEtab, setSearchEtab] = useState('');
const [dropdownOpen, setDropdownOpen] = useState(false);
@@ -213,6 +216,12 @@ export default function Budget2027() {
);
}, [formValues]);
+ // Recharger les saisies quand l'année change
+ useEffect(() => {
+ setSaisies(loadSaisiesForAnnee(annee));
+ setSelectedCode('');
+ }, [annee]);
+
const updateField = (key: keyof BudgetFormValues, value: number | string) => {
setSaisies(prev => {
const next = {
@@ -222,14 +231,14 @@ export default function Budget2027() {
[key]: value,
},
};
- saveSaisies(next);
+ saveSaisiesForAnnee(annee, next);
return next;
});
setSaved(false);
};
const handleSave = () => {
- saveSaisies(saisies);
+ saveSaisiesForAnnee(annee, saisies);
setSaved(true);
setTimeout(() => setSaved(false), 2500);
};
@@ -238,7 +247,7 @@ export default function Budget2027() {
setSaisies(prev => {
const next = { ...prev };
delete next[selectedCode];
- saveSaisies(next);
+ saveSaisiesForAnnee(annee, next);
return next;
});
};
@@ -265,12 +274,15 @@ export default function Budget2027() {
- Construction Budget 2027
+ CAPEX - Construction {annee}
- Campagne budgétaire CAPEX DSI — Prévisionnel 2027
+ Campagne budgétaire CAPEX DSI — Prévisionnel {annee}
+
{selectedCode && (