diff --git a/.gitignore b/.gitignore index c2c15de..9504316 100644 --- a/.gitignore +++ b/.gitignore @@ -110,3 +110,4 @@ temp/ # Manus version file (auto-generated, not part of source) client/public/__manus__/version.json +.project-config.json diff --git a/client/src/App.tsx b/client/src/App.tsx index 6950c14..171b31f 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,6 +1,3 @@ -// App.tsx — Budget Renouvellement SI 2027 - Itinova -// Design: Corporate Modernism — Thème clair institutionnel - import { Toaster } from "@/components/ui/sonner"; import { TooltipProvider } from "@/components/ui/tooltip"; import NotFound from "@/pages/NotFound"; @@ -11,13 +8,23 @@ import { ParametresProvider } from "./contexts/ParametresContext"; import Home from "./pages/Home"; import Parametres from "./pages/Parametres"; import Budget2027 from "./pages/Budget2027"; +import DsiCapex from "./pages/DsiCapex"; +import DsiOpex from "./pages/DsiOpex"; +import Santinova from "./pages/Santinova"; +import SoinsSante from "./pages/SoinsSante"; +import StExupery from "./pages/StExupery"; function Router() { return ( - + + + + + + @@ -27,14 +34,14 @@ function Router() { function App() { return ( - - + + - - + + ); } diff --git a/client/src/components/AppSidebar.tsx b/client/src/components/AppSidebar.tsx new file mode 100644 index 0000000..afb307c --- /dev/null +++ b/client/src/components/AppSidebar.tsx @@ -0,0 +1,252 @@ +// AppSidebar.tsx — Sidebar partagée avec navigation hiérarchique +// Structure : +// ├── ITINOVA (expandable) +// │ ├── Établissements (expandable) +// │ │ ├── Renouvellement PC → / +// │ │ └── Construction BP CAPEX → /budget2027 +// │ └── DSI (expandable) +// │ ├── CAPEX (Investissement) → /dsi-capex +// │ └── OPEX (Charges) → /dsi-opex +// ├── SANTINOVA → /santinova +// ├── SOINS ET SANTÉ → /soins-sante +// ├── ST EXUPÉRY → /st-exupery +// └── Paramètres → /parametres + +import { useState } from 'react'; +import { useLocation } from 'wouter'; +import { + BarChart3, + Building2, + Settings, + ChevronDown, + ChevronRight, + Monitor, + FileText, + TrendingUp, + TrendingDown, + Heart, + GraduationCap, + Stethoscope, + Layers, + PanelLeftClose, + PanelLeftOpen, +} from 'lucide-react'; + +interface NavItem { + id: string; + label: string; + icon: React.ElementType; + path?: string; + children?: NavItem[]; + badge?: string; +} + +const NAV_STRUCTURE: NavItem[] = [ + { + id: 'itinova', + label: 'Itinova', + icon: Building2, + children: [ + { + id: 'etablissements', + label: 'Établissements', + icon: Layers, + children: [ + { id: 'renouvellement-pc', label: 'Renouvellement PC', icon: Monitor, path: '/' }, + { id: 'budget2027', label: 'Construction BP CAPEX', icon: FileText, path: '/budget2027' }, + ], + }, + { + id: 'dsi', + label: 'DSI', + icon: BarChart3, + children: [ + { id: 'dsi-capex', label: 'CAPEX (Investissement)', icon: TrendingUp, path: '/dsi-capex' }, + { id: 'dsi-opex', label: 'OPEX (Charges)', icon: TrendingDown, path: '/dsi-opex' }, + ], + }, + ], + }, + { + id: 'santinova', + label: 'Santinova', + icon: Heart, + path: '/santinova', + }, + { + id: 'soins-sante', + label: 'Soins et Santé', + icon: Stethoscope, + path: '/soins-sante', + }, + { + id: 'st-exupery', + label: 'St Exupéry', + icon: GraduationCap, + path: '/st-exupery', + }, +]; + +const SETTINGS_ITEM: NavItem = { + id: 'parametres', + label: 'Paramètres', + icon: Settings, + path: '/parametres', +}; + +function isPathActive(path: string | undefined, currentPath: string): boolean { + if (!path) return false; + if (path === '/') return currentPath === '/'; + return currentPath.startsWith(path); +} + +function hasActiveChild(item: NavItem, currentPath: string): boolean { + if (isPathActive(item.path, currentPath)) return true; + if (item.children) { + return item.children.some(child => hasActiveChild(child, currentPath)); + } + return false; +} + +// Initialise les menus ouverts selon la route active +function getInitialOpen(currentPath: string): Set { + const open = new Set(); + // Toujours ouvrir itinova et son sous-menu actif + NAV_STRUCTURE.forEach(item => { + if (hasActiveChild(item, currentPath)) { + open.add(item.id); + item.children?.forEach(child => { + if (hasActiveChild(child, currentPath)) { + open.add(child.id); + } + }); + } + }); + return open; +} + +interface AppSidebarProps { + collapsed?: boolean; + onToggle?: () => void; +} + +export function AppSidebar({ collapsed = false, onToggle }: AppSidebarProps) { + const [location, navigate] = useLocation(); + const [openMenus, setOpenMenus] = useState>(() => getInitialOpen(location)); + + const toggleMenu = (id: string) => { + setOpenMenus(prev => { + const next = new Set(prev); + if (next.has(id)) next.delete(id); + else next.add(id); + return next; + }); + }; + + const renderItem = (item: NavItem, depth: number = 0) => { + const isActive = isPathActive(item.path, location); + const hasChildren = !!item.children?.length; + const isOpen = openMenus.has(item.id); + const isChildActive = hasActiveChild(item, location); + const Icon = item.icon; + + const paddingLeft = depth === 0 ? 'pl-3' : depth === 1 ? 'pl-6' : 'pl-10'; + + if (hasChildren) { + return ( +
+ + {!collapsed && isOpen && ( +
+ {item.children!.map(child => renderItem(child, depth + 1))} +
+ )} +
+ ); + } + + // Feuille (lien direct) + return ( + + ); + }; + + return ( + + ); +} diff --git a/client/src/components/ComingSoon.tsx b/client/src/components/ComingSoon.tsx new file mode 100644 index 0000000..2b01bcf --- /dev/null +++ b/client/src/components/ComingSoon.tsx @@ -0,0 +1,28 @@ +// ComingSoon.tsx — Page placeholder pour les sections en cours de développement +import { LucideIcon, Construction } from 'lucide-react'; + +interface ComingSoonProps { + title: string; + subtitle?: string; + icon?: LucideIcon; +} + +export function ComingSoon({ title, subtitle, icon: Icon = Construction }: ComingSoonProps) { + return ( +
+
+ +
+

+ {title} +

+ {subtitle && ( +

{subtitle}

+ )} +
+ + Section en cours de développement +
+
+ ); +} diff --git a/client/src/pages/Budget2027.tsx b/client/src/pages/Budget2027.tsx index 4e8a5ff..bb7b9b4 100644 --- a/client/src/pages/Budget2027.tsx +++ b/client/src/pages/Budget2027.tsx @@ -7,8 +7,6 @@ import { useState, useMemo, useEffect } from 'react'; import { useLocation } from 'wouter'; import { Building2, - BarChart3, - Settings, ChevronDown, Monitor, PlusCircle, @@ -26,6 +24,7 @@ import { TrendingUp, AlertCircle, } from 'lucide-react'; +import { AppSidebar } from '../components/AppSidebar'; import bp2027Raw from '../data_bp2027.json'; import budgetRaw from '../data_budget.json'; import { useParametres } from '../contexts/ParametresContext'; @@ -257,50 +256,7 @@ export default function Budget2027() { return (
- {/* Sidebar */} - + {/* Contenu principal */}
diff --git a/client/src/pages/DsiCapex.tsx b/client/src/pages/DsiCapex.tsx new file mode 100644 index 0000000..5fafced --- /dev/null +++ b/client/src/pages/DsiCapex.tsx @@ -0,0 +1,27 @@ +// DsiCapex.tsx — CAPEX DSI (Investissement) +import { useState } from 'react'; +import { AppSidebar } from '../components/AppSidebar'; +import { ComingSoon } from '../components/ComingSoon'; +import { TrendingUp } from 'lucide-react'; + +export default function DsiCapex() { + const [collapsed, setCollapsed] = useState(false); + return ( +
+ setCollapsed(c => !c)} /> +
+
+

+ CAPEX — Investissements DSI +

+

Budget d'investissement du Système d'Information

+
+ +
+
+ ); +} diff --git a/client/src/pages/DsiOpex.tsx b/client/src/pages/DsiOpex.tsx new file mode 100644 index 0000000..d56bfef --- /dev/null +++ b/client/src/pages/DsiOpex.tsx @@ -0,0 +1,27 @@ +// DsiOpex.tsx — OPEX DSI (Charges établissement) +import { useState } from 'react'; +import { AppSidebar } from '../components/AppSidebar'; +import { ComingSoon } from '../components/ComingSoon'; +import { TrendingDown } from 'lucide-react'; + +export default function DsiOpex() { + const [collapsed, setCollapsed] = useState(false); + return ( +
+ setCollapsed(c => !c)} /> +
+
+

+ OPEX — Charges établissement +

+

Budget de fonctionnement du Système d'Information

+
+ +
+
+ ); +} diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx index 505a489..a30474a 100644 --- a/client/src/pages/Home.tsx +++ b/client/src/pages/Home.tsx @@ -2,9 +2,7 @@ // Design: Corporate Modernism — Sidebar bleu marine + grille de cards + vue liste import { useState } from 'react'; -import { useLocation } from 'wouter'; import { - Building2, Monitor, Laptop, Euro, @@ -12,15 +10,14 @@ import { SortAsc, SortDesc, Filter, - BarChart3, + Building2, AlertTriangle, CheckCircle, ChevronDown, LayoutGrid, List, - Settings, - FileText, } from 'lucide-react'; +import { AppSidebar } from '../components/AppSidebar'; import { useBudgetData } from '../hooks/useBudgetData'; import { formatEuros, formatNumber } from '../lib/format'; import { EtablissementCard } from '../components/EtablissementCard'; @@ -45,9 +42,8 @@ export default function Home() { setFilterType, } = useBudgetData(); - const [, navigate] = useLocation(); const [selectedEtab, setSelectedEtab] = useState(null); - const [sidebarOpen, setSidebarOpen] = useState(true); + const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [viewMode, setViewMode] = useState('grid'); const sortOptions: { value: SortField; label: string }[] = [ @@ -66,90 +62,7 @@ export default function Home() { return (
- {/* Sidebar */} - + setSidebarCollapsed(c => !c)} /> {/* Contenu principal */}
diff --git a/client/src/pages/Parametres.tsx b/client/src/pages/Parametres.tsx index 244dffe..a66b8df 100644 --- a/client/src/pages/Parametres.tsx +++ b/client/src/pages/Parametres.tsx @@ -14,9 +14,8 @@ import { ChevronLeft, Info, BarChart3, - Building2, - FileText, } from 'lucide-react'; +import { AppSidebar } from '../components/AppSidebar'; import { useParametres, PARAMETRES_DEFAULTS } from '../contexts/ParametresContext'; import { formatEuros } from '../lib/format'; import { toast } from 'sonner'; @@ -149,69 +148,7 @@ export default function Parametres() { return (
- {/* Sidebar identique à Home */} - + {/* Contenu principal */}
diff --git a/client/src/pages/Santinova.tsx b/client/src/pages/Santinova.tsx new file mode 100644 index 0000000..03475a9 --- /dev/null +++ b/client/src/pages/Santinova.tsx @@ -0,0 +1,21 @@ +// Santinova.tsx +import { useState } from 'react'; +import { AppSidebar } from '../components/AppSidebar'; +import { ComingSoon } from '../components/ComingSoon'; +import { Heart } from 'lucide-react'; + +export default function Santinova() { + const [collapsed, setCollapsed] = useState(false); + return ( +
+ setCollapsed(c => !c)} /> +
+
+

Santinova

+

Budget SI 2027 — Périmètre Santinova

+
+ +
+
+ ); +} diff --git a/client/src/pages/SoinsSante.tsx b/client/src/pages/SoinsSante.tsx new file mode 100644 index 0000000..371d329 --- /dev/null +++ b/client/src/pages/SoinsSante.tsx @@ -0,0 +1,21 @@ +// SoinsSante.tsx +import { useState } from 'react'; +import { AppSidebar } from '../components/AppSidebar'; +import { ComingSoon } from '../components/ComingSoon'; +import { Stethoscope } from 'lucide-react'; + +export default function SoinsSante() { + const [collapsed, setCollapsed] = useState(false); + return ( +
+ setCollapsed(c => !c)} /> +
+
+

Soins et Santé

+

Budget SI 2027 — Périmètre Soins et Santé

+
+ +
+
+ ); +} diff --git a/client/src/pages/StExupery.tsx b/client/src/pages/StExupery.tsx new file mode 100644 index 0000000..57d3fec --- /dev/null +++ b/client/src/pages/StExupery.tsx @@ -0,0 +1,21 @@ +// StExupery.tsx +import { useState } from 'react'; +import { AppSidebar } from '../components/AppSidebar'; +import { ComingSoon } from '../components/ComingSoon'; +import { GraduationCap } from 'lucide-react'; + +export default function StExupery() { + const [collapsed, setCollapsed] = useState(false); + return ( +
+ setCollapsed(c => !c)} /> +
+
+

St Exupéry

+

Budget SI 2027 — Périmètre St Exupéry

+
+ +
+
+ ); +}