// Parametres.tsx — Page de paramétrage du calcul de renouvellement
// Design: Corporate Modernism — Itinova Budget SI 2027
import { useState, useEffect } from 'react';
import { useLocation } from 'wouter';
import {
Settings,
Monitor,
Laptop,
Euro,
RotateCcw,
Save,
CheckCircle,
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';
import { toast } from 'sonner';
interface FieldState {
seuilFixesAns: number;
seuilPortablesAns: number;
coutFixe: number;
coutPortable: number;
anneeDefaut: number;
}
function SliderInput({
label,
icon: Icon,
value,
min,
max,
step,
unit,
description,
onChange,
accentClass,
}: {
label: string;
icon: React.ElementType;
value: number;
min: number;
max: number;
step: number;
unit: string;
description: string;
onChange: (v: number) => void;
accentClass: string;
}) {
return (
{/* Slider */}
{/* Input numérique direct */}
Valeur exacte :
{
const v = Number(e.target.value);
if (!isNaN(v) && v >= min && v <= max) onChange(v);
}}
className="w-24 px-3 py-1.5 text-sm border border-border rounded-lg bg-muted/50 focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary tabular-nums"
/>
{unit}
);
}
export default function Parametres() {
const [, navigate] = useLocation();
const { parametres, setParametres, resetParametres } = useParametres();
const [draft, setDraft] = useState({ ...parametres });
const [saved, setSaved] = useState(false);
// Sync si les paramètres changent depuis l'extérieur
useEffect(() => {
setDraft({ ...parametres });
}, [parametres]);
const hasChanges =
draft.seuilFixesAns !== parametres.seuilFixesAns ||
draft.seuilPortablesAns !== parametres.seuilPortablesAns ||
draft.coutFixe !== parametres.coutFixe ||
draft.coutPortable !== parametres.coutPortable ||
draft.anneeDefaut !== parametres.anneeDefaut;
const handleSave = () => {
setParametres(draft);
setSaved(true);
toast.success('Paramètres enregistrés', {
description: 'Les budgets ont été recalculés avec les nouveaux seuils.',
});
setTimeout(() => setSaved(false), 2500);
};
const handleReset = () => {
setDraft({ ...PARAMETRES_DEFAULTS });
resetParametres();
toast.info('Paramètres réinitialisés', {
description: 'Les valeurs par défaut ont été restaurées.',
});
};
const update = (key: keyof FieldState) => (v: number) => {
setDraft((d) => ({ ...d, [key]: v }));
};
return (
{/* Contenu principal */}
{/* En-tête */}
{/* Contenu */}
{/* Bandeau info */}
Les modifications sont appliquées immédiatement après enregistrement.
Tous les budgets des 60 établissements sont recalculés automatiquement.
Les valeurs sont sauvegardées dans votre navigateur.
{/* Section vétusté */}
Seuils de vétusté
Un équipement est considéré à renouveler si son âge (calculé au 01/01/2027) est
supérieur ou égal au seuil défini ci-dessous.
{/* Section coûts */}
Coûts unitaires TTC
Prix d'achat unitaire estimé utilisé pour le calcul du budget de renouvellement.
{/* Section année par défaut */}
Année d'ouverture par défaut
L'année sélectionnée au démarrage de l'application lorsqu'aucune session précédente n'est mémorisée.
Année par défaut
Exercice ouvert au premier lancement
{ANNEES_DISPONIBLES.map(a => (
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}
))}
{/* Aperçu de l'impact */}
Paramètres en cours d'édition
{[
{ label: 'Seuil PC Fixes', value: `${draft.seuilFixesAns} ans`, changed: draft.seuilFixesAns !== parametres.seuilFixesAns },
{ 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) => (
{item.label}
{item.value}
{item.changed && (
modifié
)}
))}
{hasChanges && (
Des modifications non enregistrées sont en attente. Cliquez sur Enregistrer pour les appliquer.
)}
);
}