// ════════════════════════════════════════════════════════════════ // Header — sticky шапка с wordmark + live pill + 4 stats со sparklines // ════════════════════════════════════════════════════════════════ const { useState: useStateHeader, useEffect: useEffectHeader } = React; function useWindowWidth() { const [w, setW] = useStateHeader(typeof window !== 'undefined' ? window.innerWidth : 1280); useEffectHeader(() => { function onR() { setW(window.innerWidth); } window.addEventListener('resize', onR); return () => window.removeEventListener('resize', onR); }, []); return w; } function Header({ route, onRoute, onToggleTheme, theme, megaOpen, onToggleMega, currentUser, onLogout }) { const D = window.SDH_DATA; const W = useWindowWidth(); const isAdmin = currentUser && (currentUser.role === 'super_admin' || currentUser.role === 'admin'); // Progressive collapse logic (different budget when admin chrome is present) // Header container is now max-1760px so we have more room than before const showLabels = W >= (isAdmin ? 1780 : 1280); const showSparks = W >= (isAdmin ? 1480 : 980); const visibleStatsCount = isAdmin ? (W >= 1780 ? 4 : W >= 1560 ? 2 : W >= 1300 ? 1 : 0) : (W >= 1280 ? 4 : W >= 1180 ? 4 : W >= 1020 ? 2 : W >= 880 ? 1 : 0); const showLivePillDate = W >= (isAdmin ? 1480 : 1100); const showUserName = W >= (isAdmin ? 1700 : 1180); const adminBarLevel = W >= 1700 ? 'full' : W >= 1080 ? 'compact' : 'icon'; const showThemeToggle = W >= 760; const allStats = [ { key: 'startups', label: 'стартапов', value: D.series.startups[D.series.startups.length - 1].value, series: D.series.startups }, { key: 'classified', label: 'классифицировано', value: D.series.classified[D.series.classified.length - 1].value, series: D.series.classified }, { key: 'sources', label: 'источников', value: D.series.sources[D.series.sources.length - 1].value, series: D.series.sources }, { key: 'ideas', label: 'идей', value: D.series.ideas[D.series.ideas.length - 1].value, series: D.series.ideas }, ]; // Priority order for narrow widths: startups + ideas first const priorityOrder = ['startups', 'ideas', 'classified', 'sources']; const stats = priorityOrder.slice(0, visibleStatsCount) .map(k => allStats.find(s => s.key === k)) .filter(Boolean); // Primary nav: 3 ежедневных маршрута. Остальное — в мега-меню "Инструменты". const primary = [ { id: 'corpus', label: 'Реестр' }, { id: 'ideas', label: 'Идеи' }, { id: 'trends', label: 'Тренды' }, ]; const toolRoutes = ['markets', 'methods', 'costs', 'pricing']; const inTools = toolRoutes.includes(route); return (
{/* wordmark — clickable home link, collapses to just .DataHub on narrow */} {/* live pill */} live {showLivePillDate && 23 мая} {/* nav: shrinkable, overflow clips when too narrow */} {/* stats with sparklines */} {stats.length > 0 && (
{stats.map((s, i) => { const m = window.METRIC_DICT[s.key]; const labelEl = showLabels ? ( m ? ( {s.label} ) : {s.label} ) : null; return ( {i > 0 && ·}
{s.value.toLocaleString('ru-RU')} {showSparks && } {labelEl}
); })}
)} {/* Admin Quick-Bar */} {currentUser && } {/* User chip */} {currentUser && ( )} {/* theme toggle */} {showThemeToggle && ( )}
); } window.Header = Header;