Checkpoint: Ajout du champ anneeDefaut dans ParametresContext (avec persistance localStorage). Nouvelle section "Année d'ouverture par défaut" dans la page Paramètres avec sélecteur de boutons (2025-2030). AnneeContext mis à jour pour utiliser cette valeur au premier lancement (quand aucune session n'est mémorisée). La page Paramètres reste accessible via l'icône engrenage en bas de la sidebar.
This commit is contained in:
@@ -6,6 +6,21 @@ import { createContext, useContext, useState, ReactNode } from 'react';
|
||||
export const ANNEES_DISPONIBLES = [2025, 2026, 2027, 2028, 2029, 2030];
|
||||
export const ANNEE_DEFAUT = 2027;
|
||||
|
||||
const PARAMETRES_STORAGE_KEY = 'itinova_budget_parametres';
|
||||
|
||||
function getAnneeDefautFromParametres(): number {
|
||||
try {
|
||||
const raw = localStorage.getItem(PARAMETRES_STORAGE_KEY);
|
||||
if (raw) {
|
||||
const parsed = JSON.parse(raw);
|
||||
if (typeof parsed.anneeDefaut === 'number' && ANNEES_DISPONIBLES.includes(parsed.anneeDefaut)) {
|
||||
return parsed.anneeDefaut;
|
||||
}
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
return ANNEE_DEFAUT;
|
||||
}
|
||||
|
||||
interface AnneeContextType {
|
||||
annee: number;
|
||||
setAnnee: (a: number) => void;
|
||||
@@ -20,8 +35,12 @@ 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;
|
||||
if (stored) {
|
||||
const parsed = parseInt(stored, 10);
|
||||
if (ANNEES_DISPONIBLES.includes(parsed)) return parsed;
|
||||
}
|
||||
// Pas de session mémorisée → utiliser l'année par défaut des paramètres
|
||||
return getAnneeDefautFromParametres();
|
||||
} catch { return ANNEE_DEFAUT; }
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface Parametres {
|
||||
seuilPortablesAns: number;
|
||||
coutFixe: number;
|
||||
coutPortable: number;
|
||||
anneeDefaut: number;
|
||||
}
|
||||
|
||||
const DEFAULTS: Parametres = {
|
||||
@@ -15,6 +16,7 @@ const DEFAULTS: Parametres = {
|
||||
seuilPortablesAns: 4,
|
||||
coutFixe: 450,
|
||||
coutPortable: 1000,
|
||||
anneeDefaut: 2027,
|
||||
};
|
||||
|
||||
const STORAGE_KEY = 'itinova_budget_parametres';
|
||||
@@ -29,6 +31,7 @@ function loadFromStorage(): Parametres {
|
||||
seuilPortablesAns: typeof parsed.seuilPortablesAns === 'number' ? parsed.seuilPortablesAns : DEFAULTS.seuilPortablesAns,
|
||||
coutFixe: typeof parsed.coutFixe === 'number' ? parsed.coutFixe : DEFAULTS.coutFixe,
|
||||
coutPortable: typeof parsed.coutPortable === 'number' ? parsed.coutPortable : DEFAULTS.coutPortable,
|
||||
anneeDefaut: typeof parsed.anneeDefaut === 'number' ? parsed.anneeDefaut : DEFAULTS.anneeDefaut,
|
||||
};
|
||||
} catch {
|
||||
return DEFAULTS;
|
||||
|
||||
@@ -14,7 +14,9 @@ import {
|
||||
ChevronLeft,
|
||||
Info,
|
||||
BarChart3,
|
||||
Calendar,
|
||||
} from 'lucide-react';
|
||||
import { ANNEES_DISPONIBLES } from '../contexts/AnneeContext';
|
||||
import { AppSidebar } from '../components/AppSidebar';
|
||||
import { useParametres, PARAMETRES_DEFAULTS } from '../contexts/ParametresContext';
|
||||
import { formatEuros } from '../lib/format';
|
||||
@@ -25,6 +27,7 @@ interface FieldState {
|
||||
seuilPortablesAns: number;
|
||||
coutFixe: number;
|
||||
coutPortable: number;
|
||||
anneeDefaut: number;
|
||||
}
|
||||
|
||||
function SliderInput({
|
||||
@@ -123,7 +126,8 @@ export default function Parametres() {
|
||||
draft.seuilFixesAns !== parametres.seuilFixesAns ||
|
||||
draft.seuilPortablesAns !== parametres.seuilPortablesAns ||
|
||||
draft.coutFixe !== parametres.coutFixe ||
|
||||
draft.coutPortable !== parametres.coutPortable;
|
||||
draft.coutPortable !== parametres.coutPortable ||
|
||||
draft.anneeDefaut !== parametres.anneeDefaut;
|
||||
|
||||
const handleSave = () => {
|
||||
setParametres(draft);
|
||||
@@ -300,6 +304,45 @@ export default function Parametres() {
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Section année par défaut */}
|
||||
<section className="space-y-4">
|
||||
<div className="flex items-center gap-2 pb-2 border-b border-border">
|
||||
<Calendar className="w-4 h-4 text-muted-foreground" />
|
||||
<h2 className="font-semibold text-foreground" style={{ fontFamily: 'Sora, sans-serif' }}>
|
||||
Année d'ouverture par défaut
|
||||
</h2>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
L'année sélectionnée au démarrage de l'application lorsqu'aucune session précédente n'est mémorisée.
|
||||
</p>
|
||||
<div className="bg-card border border-border rounded-xl p-5">
|
||||
<div className="flex items-center gap-4 flex-wrap">
|
||||
<div className="w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0 bg-indigo-100 text-indigo-700">
|
||||
<Calendar className="w-5 h-5" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="font-semibold text-foreground" style={{ fontFamily: 'Sora, sans-serif' }}>Année par défaut</p>
|
||||
<p className="text-sm text-muted-foreground mt-0.5">Exercice ouvert au premier lancement</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{ANNEES_DISPONIBLES.map(a => (
|
||||
<button
|
||||
key={a}
|
||||
onClick={() => setDraft(d => ({ ...d, anneeDefaut: a }))}
|
||||
className={`px-4 py-2 rounded-lg text-sm font-semibold transition-all ${
|
||||
draft.anneeDefaut === a
|
||||
? 'bg-indigo-600 text-white shadow-sm'
|
||||
: 'bg-muted text-muted-foreground hover:bg-muted/70'
|
||||
}`}
|
||||
>
|
||||
{a}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Aperçu de l'impact */}
|
||||
<section className="space-y-4">
|
||||
<div className="flex items-center gap-2 pb-2 border-b border-border">
|
||||
@@ -314,6 +357,7 @@ export default function Parametres() {
|
||||
{ label: 'Seuil PC Portables', value: `${draft.seuilPortablesAns} ans`, changed: draft.seuilPortablesAns !== parametres.seuilPortablesAns },
|
||||
{ label: 'Coût fixe unitaire', value: formatEuros(draft.coutFixe), changed: draft.coutFixe !== parametres.coutFixe },
|
||||
{ label: 'Coût portable unitaire', value: formatEuros(draft.coutPortable), changed: draft.coutPortable !== parametres.coutPortable },
|
||||
{ label: 'Année par défaut', value: `${draft.anneeDefaut}`, changed: draft.anneeDefaut !== parametres.anneeDefaut },
|
||||
].map((item) => (
|
||||
<div
|
||||
key={item.label}
|
||||
|
||||
Reference in New Issue
Block a user