Checkpoint: Inventaire PC distinct par année : clé budgetsi_inventaire_data_YYYY dans localStorage, ImportModal utilise l'année active (useAnnee), indicateur visuel des imports par année dans la modale, useBudgetData + Budget2027 + DsiCapex rechargent l'inventaire à chaque changement d'année

This commit is contained in:
Manus
2026-06-10 08:47:32 +00:00
parent f64a7db3f3
commit 3145e9c84f
5 changed files with 109 additions and 35 deletions

View File

@@ -2,6 +2,7 @@
// Design: Corporate Modernism — Itinova Budget SI
import { useState, useRef, useEffect } from 'react';
import { useAnnee, getInventaireStorageKey } from '../contexts/AnneeContext';
import * as XLSX from 'xlsx';
import {
Upload,
@@ -251,6 +252,7 @@ interface ImportModalProps {
}
export function ImportModal({ open, onClose }: ImportModalProps) {
const { annee } = useAnnee();
const [tab, setTab] = useState<ImportTab>('inventaire');
const [inventaireStatus, setInventaireStatus] = useState<'idle' | 'success' | 'error'>('idle');
const [inventaireMsg, setInventaireMsg] = useState('');
@@ -535,7 +537,7 @@ export function ImportModal({ open, onClose }: ImportModalProps) {
const inventaireData = {
meta: {
date_calcul: new Date().toISOString(),
date_reference: '2027-01-01',
date_reference: `${annee}-01-01`,
nb_etablissements: etabData.size,
total_fixes: nbFixes,
total_portables: nbPortables,
@@ -543,7 +545,7 @@ export function ImportModal({ open, onClose }: ImportModalProps) {
},
etablissements: etablissementsArray,
};
localStorage.setItem('budgetsi_inventaire_data', JSON.stringify(inventaireData));
localStorage.setItem(getInventaireStorageKey(annee), JSON.stringify(inventaireData));
// ── Mettre à jour aussi la liste des établissements (code + nom) ────────────
const existing = loadEtablissements();
@@ -567,7 +569,7 @@ export function ImportModal({ open, onClose }: ImportModalProps) {
// ── Métadonnées d'import ────────────────────────────────────────────────────────
const nbLignes = rows.length - 1;
localStorage.setItem('budgetsi_inventaire_import', JSON.stringify({
localStorage.setItem(`budgetsi_inventaire_import_${annee}`, JSON.stringify({
filename: file.name,
date: new Date().toISOString(),
size: file.size,
@@ -587,7 +589,7 @@ export function ImportModal({ open, onClose }: ImportModalProps) {
].filter(Boolean).join(' — ');
// Notifier les autres composants (useBudgetData, DsiCapex, Budget2027)
window.dispatchEvent(new Event('budgetsi_inventaire_updated'));
window.dispatchEvent(new CustomEvent('budgetsi_inventaire_updated', { detail: { annee } }));
setInventaireStatus('success');
setInventaireMsg(`"${file.name}" importé — ${details}`);
@@ -850,6 +852,38 @@ export function ImportModal({ open, onClose }: ImportModalProps) {
)}
</div>
{/* ── Indicateur imports par année ── */}
<div className="border border-border rounded-xl overflow-hidden">
<div className="px-4 py-2.5 bg-muted/30 border-b border-border">
<p className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">État des inventaires par année</p>
</div>
<div className="flex flex-wrap gap-2 p-3">
{[2025, 2026, 2027, 2028, 2029, 2030].map(yr => {
const hasData = !!localStorage.getItem(`budgetsi_inventaire_data_${yr}`);
const meta = (() => { try { const d = localStorage.getItem(`budgetsi_inventaire_import_${yr}`); return d ? JSON.parse(d) : null; } catch { return null; } })();
return (
<div
key={yr}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg border text-xs font-medium ${
yr === annee
? hasData
? 'bg-emerald-100 border-emerald-400 text-emerald-800'
: 'bg-blue-100 border-blue-400 text-blue-800'
: hasData
? 'bg-emerald-50 border-emerald-200 text-emerald-700'
: 'bg-muted/40 border-border text-muted-foreground'
}`}
title={meta ? `${meta.filename} — ${meta.nbLignes} lignes — ${new Date(meta.date).toLocaleDateString('fr-FR')}` : 'Aucun inventaire importé'}
>
<span className={`w-1.5 h-1.5 rounded-full flex-shrink-0 ${hasData ? 'bg-emerald-500' : 'bg-muted-foreground/40'}`} />
<span>{yr}</span>
{yr === annee && <span className="text-[10px] opacity-70">(actif)</span>}
</div>
);
})}
</div>
</div>
{/* ── Info résumée ── */}
<div className="flex items-start gap-3 p-3 bg-blue-50 border border-blue-200 rounded-xl">
<Info className="w-4 h-4 text-blue-600 flex-shrink-0 mt-0.5" />