/* global React, Icon */ const { useState: uS, useEffect: uE, useMemo: uM, Fragment: Fr } = React; // ============== SIDEBAR ============== function Sidebar({ active, onNav, onLogout, user }) { return ( ); } function NavItem({ m, active, onClick }) { return (
{m.name} {m.n} {m.badge && {m.badge}} {m.badgeRed && {m.badgeRed}}
); } // ============== TOPBAR ============== function Topbar({ moduleId, crumbs, right, onNav, onMenuClick }) { const mod = MODULES.find(m => m.id === moduleId); const [showNotif, setShowNotif] = uS(false); return (
{onMenuClick && ( )}
{mod && M{mod.n}} {mod?.name || ''} {crumbs && crumbs.map((c, i) => ( / {c} ))}
{right}
onNav?.('config')} onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onNav?.('config')} >PNCP · ONLINE
onNav?.('integracoes')} onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onNav?.('integracoes')} >Gmail · 24 não lidos
{showNotif && ( <>
setShowNotif(false)} style={{ position: 'fixed', inset: 0, zIndex: 90 }}>
Notificações
{ setShowNotif(false); onNav?.('auditoria'); }} /> { setShowNotif(false); onNav?.('auditoria'); }} /> { setShowNotif(false); onNav?.('disputa'); }} /> setShowNotif(false)} />
)}
); } function NotifItem({ tone, title, sub, onClick }) { const cols = { red: 'var(--red)', amber: 'var(--amber)', cyan: 'var(--cyan)', lime: 'var(--lime)' }; return (
(e.key === 'Enter' || e.key === ' ') && onClick()} style={{ padding: '10px 14px', borderBottom: '1px solid var(--line-1)', display: 'flex', gap: 10, alignItems: 'flex-start' }}>
{title}
{sub}
); } // ============== TICKER ============== let _tickerCache = null; let _tickerCacheAt = 0; const TICKER_TTL_MS = 60000; function Ticker() { const [stats, setStats] = uS(_tickerCache); uE(() => { let mounted = true; const load = async () => { const now = Date.now(); if (_tickerCache && (now - _tickerCacheAt) < TICKER_TTL_MS) { setStats(_tickerCache); return; } try { const data = await window.dataApi.getTickerStats(); if (!mounted) return; _tickerCache = data; _tickerCacheAt = Date.now(); setStats(data); } catch (_) { /* silencioso — ticker é decorativo */ } }; load(); const intv = setInterval(load, TICKER_TTL_MS); return () => { mounted = false; clearInterval(intv); }; }, []); const items = stats ? [ { l: 'Editais ativos', v: String(stats.editaisAtivos), cls: '' }, { l: 'Empenhos ativos', v: String(stats.empenhosAtivos), cls: '' }, { l: 'A receber', v: BRL(stats.aReceber), cls: 'up' }, ...(stats.cndDias !== null ? [{ l: 'Próx. CND vence em', v: `${stats.cndDias}d`, cls: stats.cndDias <= 10 ? 'dn' : '' }] : []), { l: 'Lances hoje', v: String(stats.lancesHoje), cls: '' }, ] : []; return (
LIVE {items.map((t, i) => ( {t.l} {t.v} ))}
); } // ============== SHARED COMPONENTS ============== function Sparkline({ data, color = 'lime', w = 90, h = 26, filled = true }) { const max = Math.max(...data); const min = Math.min(...data); const r = max - min || 1; const step = w / (data.length - 1); const pts = data.map((v, i) => [i * step, h - 2 - ((v - min) / r) * (h - 4)]); const path = pts.map((p, i) => `${i === 0 ? 'M' : 'L'}${p[0].toFixed(1)},${p[1].toFixed(1)}`).join(' '); const fill = `${path} L ${w},${h} L 0,${h} Z`; const cls = color === 'red' ? 'red' : color === 'cyan' ? 'cyan' : ''; return ( {filled && } ); } function Avatar({ initials, color, size = 22 }) { return (
{initials}
); } function StatusPill({ status }) { const map = { novo: { l: 'NOVO', c: 'var(--fg-2)' }, analise: { l: 'ANÁLISE', c: 'var(--cyan)' }, precificacao: { l: 'PRECIF.', c: 'var(--amber)' }, disputa: { l: 'DISPUTA', c: 'var(--red)' }, ganho: { l: 'GANHO', c: 'var(--lime)' }, descartado: { l: 'DESCART.', c: 'var(--fg-4)' }, }; const s = map[status] || map.novo; return {s.l}; } function ScoreDial({ score }) { const color = score >= 85 ? 'var(--lime)' : score >= 70 ? 'var(--amber)' : 'var(--fg-3)'; const r = 10; const c = 2 * Math.PI * r; const off = c - (score / 100) * c; return (
{score}
); } function Toggle({ on, onToggle }) { return
; } function Section({ title, sub, right, children, padTop }) { return (
{sub}
{title}
{right}
{children}
); } // ============== DRAWER ============== function Drawer({ open, onClose, title, subtitle, eyebrow, width, headRight, tabs, activeTab, onTabChange, footer, children }) { uE(() => { if (!open) return; const onKey = e => { if (e.key === 'Escape') onClose(); }; document.addEventListener('keydown', onKey); const prevOverflow = document.body.style.overflow; document.body.style.overflow = 'hidden'; return () => { document.removeEventListener('keydown', onKey); document.body.style.overflow = prevOverflow; }; }, [open, onClose]); if (!open) return null; return (
{eyebrow &&
{eyebrow}
}
{title}
{subtitle &&
{subtitle}
}
{headRight}
{tabs && tabs.length > 0 && (
{tabs.map(t => (
onTabChange(t.id)}> {t.label} {t.count !== undefined && {t.count}}
))}
)}
{children}
{footer &&
{footer}
}
); } Object.assign(window, { Sidebar, Topbar, Ticker, Sparkline, Avatar, StatusPill, ScoreDial, Toggle, Section, Drawer });