import { useCallback, useEffect, useRef, useState } from 'react'; import { NavLink, Outlet, useNavigate } from 'react-router-dom'; import { api } from '../api.js'; import { useInvestisseur } from '../context/InvestisseurContext.jsx'; import { useUi } from '../context/UiContext.jsx'; import Logo from './Logo.jsx'; import UserMenu from './UserMenu.jsx'; /* ── Icônes nav ─────────────────────────────────────────────── */ const ICONS_BASE = '/api/icons-files/'; const I = ({ children }) => ( ); const IconDashboard = () => ; const IconDeposits = () => ; const IconInvestments = () => ; const IconRepayments = () => ; const IconFlatTax = () => ; /* Icône nav hybride : bibliothèque si dispo, sinon fallback SVG inline */ function NavIcon({ libFilename, Fallback }) { if (libFilename) { return ( ); } return ; } /* Bouton « réduire » (visible dans la sidebar étendue) */ const IconPanelCollapse = () => ( ); /* Bouton « étendre » (apparaît au hover du logo en mode réduit) */ const IconPanelExpand = () => ( ); /* ── Recherche rapide de projet ─────────────────────────────── */ function ProjectSearch() { const navigate = useNavigate(); const { activeId, activeView } = useInvestisseur(); const [query, setQuery] = useState(''); const [allInv, setAllInv] = useState([]); const [open, setOpen] = useState(false); const [activeIdx, setActiveIdx] = useState(-1); const inputRef = useRef(null); const wrapRef = useRef(null); /* Chargement (ou rechargement) des investissements */ const loadInv = useCallback(async () => { try { const scopeParams = activeView === 'all' ? { scope: 'all' } : {}; const rows = await api.get('/investissements', scopeParams); setAllInv(rows); } catch {} }, [activeView]); useEffect(() => { loadInv(); }, [loadInv, activeId]); /* Raccourci clavier global Ctrl+K / Cmd+K */ useEffect(() => { const h = e => { if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); inputRef.current?.focus(); inputRef.current?.select(); } }; document.addEventListener('keydown', h); return () => document.removeEventListener('keydown', h); }, []); /* Fermeture au clic extérieur */ useEffect(() => { const h = e => { if (!wrapRef.current?.contains(e.target)) setOpen(false); }; document.addEventListener('mousedown', h); return () => document.removeEventListener('mousedown', h); }, []); /* Résultats filtrés */ const results = (() => { const q = query.trim().toLowerCase(); if (!q) return []; return allInv .filter(r => r.nom_projet?.toLowerCase().includes(q) || r.plateforme_nom?.toLowerCase().includes(q)) .slice(0, 8); })(); /* Synchronise l'ouverture du dropdown */ useEffect(() => { setOpen(results.length > 0 && query.trim().length > 0); setActiveIdx(-1); }, [results.length, query]); /* eslint-disable-line */ const goTo = (inv) => { setQuery(''); setOpen(false); navigate(`/investissements/${inv.id}`); }; const handleKeyDown = (e) => { if (e.key === 'ArrowDown') { e.preventDefault(); setActiveIdx(i => Math.min(i + 1, results.length - 1)); } if (e.key === 'ArrowUp') { e.preventDefault(); setActiveIdx(i => Math.max(i - 1, -1)); } if (e.key === 'Enter') { e.preventDefault(); if (activeIdx >= 0) goTo(results[activeIdx]); else if (results.length === 1) goTo(results[0]); } if (e.key === 'Escape') { setOpen(false); setQuery(''); inputRef.current?.blur(); } }; const STATUT_LABELS = { en_cours: 'en cours', rembourse: 'remboursé', en_retard: 'en retard', procedure: 'procédure', cloture: 'clôturé', }; const statutColor = (s) => { if (s === 'en_cours') return 'var(--b-en_cours-fg)'; if (s === 'rembourse') return 'var(--b-rembourse-fg)'; if (s === 'en_retard') return 'var(--b-en_retard-fg)'; if (s === 'procedure') return 'var(--b-procedure-fg)'; return 'var(--text-muted)'; }; return (
setQuery(e.target.value)} onKeyDown={handleKeyDown} placeholder="Rechercher un projet…" autoComplete="off" spellCheck="false" /> {query ? ( ) : ( ⌘K )}
{open && (
{results.map((inv, i) => (
goTo(inv)} onMouseEnter={() => setActiveIdx(i)} >
{inv.nom_projet}
{inv.plateforme_nom} · {STATUT_LABELS[inv.statut] ?? inv.statut?.replace('_', ' ')} {inv.montant_investi != null && ( <> · {inv.montant_investi.toLocaleString('fr-FR')} € )}
))}
)}
); } /* ── Layout ─────────────────────────────────────────────────── */ function IconPlusCircle() { return ( ); } export default function Layout() { const { sidebarCollapsed, toggleSidebar, displayMode, setDisplayMode } = useUi(); const navigate = useNavigate(); const [navIcons, setNavIcons] = useState({}); useEffect(() => { api.get('/icons').then(rows => { const m = {}; rows.forEach(r => { m[r.name] = r.filename; }); setNavIcons(m); }).catch(() => {}); }, []); return (
); }