Checkpoint: Ajout de la page Paramètres : seuils de vétusté (PC fixes et portables) et coûts unitaires configurables via sliders + inputs numériques. Recalcul automatique de tous les budgets à l'enregistrement. Persistance dans localStorage. Navigation sidebar Établissements ↔ Paramètres.
This commit is contained in:
@@ -1,20 +1,102 @@
|
||||
// Hook pour charger et gérer les données budget
|
||||
// Hook pour charger et gérer les données budget avec recalcul dynamique
|
||||
// Design: Corporate Modernism — Itinova Budget SI 2027
|
||||
|
||||
import { useState, useMemo } from 'react';
|
||||
import budgetRaw from '../data_budget.json';
|
||||
import type { BudgetData, Etablissement, SortField, SortOrder, FilterType } from '../types/budget';
|
||||
import type { BudgetData, Etablissement, EtablissementStats, SortField, SortOrder, FilterType } from '../types/budget';
|
||||
import { useParametres } from '../contexts/ParametresContext';
|
||||
|
||||
const budgetData = budgetRaw as BudgetData;
|
||||
|
||||
// Date de référence fixe : 01/01/2027
|
||||
const DATE_REFERENCE = new Date('2027-01-01');
|
||||
|
||||
/**
|
||||
* Recalcule les stats d'un établissement selon les paramètres courants.
|
||||
* On repart des données brutes (age_ans stocké dans chaque Materiel)
|
||||
* et on applique les nouveaux seuils.
|
||||
*/
|
||||
function recalculerStats(
|
||||
etab: Etablissement,
|
||||
seuilFixesAns: number,
|
||||
seuilPortablesAns: number,
|
||||
coutFixe: number,
|
||||
coutPortable: number
|
||||
): EtablissementStats {
|
||||
const fixes = etab.fixes ?? [];
|
||||
const portables = etab.portables ?? [];
|
||||
|
||||
const nbFixesTotal = fixes.length;
|
||||
const nbPortablesTotal = portables.length;
|
||||
|
||||
// Renouveler si age_ans connu ET >= seuil
|
||||
const nbFixesRenouveler = fixes.filter(
|
||||
(f) => f.age_ans !== null && f.age_ans >= seuilFixesAns
|
||||
).length;
|
||||
const nbPortablesRenouveler = portables.filter(
|
||||
(p) => p.age_ans !== null && p.age_ans >= seuilPortablesAns
|
||||
).length;
|
||||
|
||||
const nbFixesSansDate = fixes.filter((f) => f.age_ans === null).length;
|
||||
const nbPortablesSansDate = portables.filter((p) => p.age_ans === null).length;
|
||||
|
||||
const budgetFixes = nbFixesRenouveler * coutFixe;
|
||||
const budgetPortables = nbPortablesRenouveler * coutPortable;
|
||||
const budgetTotal = budgetFixes + budgetPortables;
|
||||
|
||||
return {
|
||||
nb_fixes_total: nbFixesTotal,
|
||||
nb_portables_total: nbPortablesTotal,
|
||||
nb_fixes_renouveler: nbFixesRenouveler,
|
||||
nb_portables_renouveler: nbPortablesRenouveler,
|
||||
nb_fixes_sans_date: nbFixesSansDate,
|
||||
nb_portables_sans_date: nbPortablesSansDate,
|
||||
budget_fixes: budgetFixes,
|
||||
budget_portables: budgetPortables,
|
||||
budget_total: budgetTotal,
|
||||
};
|
||||
}
|
||||
|
||||
export function useBudgetData() {
|
||||
const { parametres } = useParametres();
|
||||
const [search, setSearch] = useState('');
|
||||
const [sortField, setSortField] = useState<SortField>('budget_total');
|
||||
const [sortOrder, setSortOrder] = useState<SortOrder>('desc');
|
||||
const [filterType, setFilterType] = useState<FilterType>('all');
|
||||
|
||||
// Recalcul de tous les établissements selon les paramètres courants
|
||||
const etablissementsRecalcules = useMemo<Etablissement[]>(() => {
|
||||
return budgetData.etablissements.map((etab) => ({
|
||||
...etab,
|
||||
stats: recalculerStats(
|
||||
etab,
|
||||
parametres.seuilFixesAns,
|
||||
parametres.seuilPortablesAns,
|
||||
parametres.coutFixe,
|
||||
parametres.coutPortable
|
||||
),
|
||||
}));
|
||||
}, [parametres]);
|
||||
|
||||
// Méta recalculée (totaux globaux)
|
||||
const metaRecalculee = useMemo(() => {
|
||||
const totalFixesR = etablissementsRecalcules.reduce((s, e) => s + e.stats.nb_fixes_renouveler, 0);
|
||||
const totalPortablesR = etablissementsRecalcules.reduce((s, e) => s + e.stats.nb_portables_renouveler, 0);
|
||||
const budgetGlobal = etablissementsRecalcules.reduce((s, e) => s + e.stats.budget_total, 0);
|
||||
return {
|
||||
...budgetData.meta,
|
||||
seuil_fixes_ans: parametres.seuilFixesAns,
|
||||
seuil_portables_ans: parametres.seuilPortablesAns,
|
||||
cout_fixe: parametres.coutFixe,
|
||||
cout_portable: parametres.coutPortable,
|
||||
total_fixes_renouveler: totalFixesR,
|
||||
total_portables_renouveler: totalPortablesR,
|
||||
budget_global: budgetGlobal,
|
||||
};
|
||||
}, [etablissementsRecalcules, parametres]);
|
||||
|
||||
const filteredAndSorted = useMemo(() => {
|
||||
let list: Etablissement[] = [...budgetData.etablissements];
|
||||
let list: Etablissement[] = [...etablissementsRecalcules];
|
||||
|
||||
// Filtre par recherche
|
||||
if (search.trim()) {
|
||||
@@ -76,7 +158,7 @@ export function useBudgetData() {
|
||||
});
|
||||
|
||||
return list;
|
||||
}, [search, sortField, sortOrder, filterType]);
|
||||
}, [etablissementsRecalcules, search, sortField, sortOrder, filterType]);
|
||||
|
||||
const toggleSort = (field: SortField) => {
|
||||
if (sortField === field) {
|
||||
@@ -88,7 +170,7 @@ export function useBudgetData() {
|
||||
};
|
||||
|
||||
return {
|
||||
meta: budgetData.meta,
|
||||
meta: metaRecalculee,
|
||||
etablissements: filteredAndSorted,
|
||||
totalEtablissements: budgetData.etablissements.length,
|
||||
search,
|
||||
|
||||
Reference in New Issue
Block a user