// Icons + shared UI atoms for BControl v0.5 const Icon = ({ name, size = 16, color = 'currentColor', strokeWidth = 1.75, style }) => { const common = { width: size, height: size, viewBox: '0 0 24 24', fill: 'none', stroke: color, strokeWidth, strokeLinecap: 'round', strokeLinejoin: 'round', style }; switch (name) { case 'fire': return ; case 'warning': return ; case 'shield': return ; case 'settings': return ; case 'refresh': return ; case 'plus': return ; case 'close': return ; case 'check': return ; case 'trash': return ; case 'search': return ; case 'filter': return ; case 'bell': return ; case 'logout': return ; case 'user': return ; case 'list': return ; case 'shield-admin': return ; case 'eye': return ; case 'power': return ; case 'send': return ; case 'dot': return ; case 'chev-down': return ; case 'chev-right': return ; case 'server': return ; case 'terminal': return ; case 'clock': return ; default: return null; } }; // ───────── Status pill ───────── const StatusPill = ({ status }) => { // status: connected | disconnected | pending const map = { connected: { bg: BC.ok.bg, fg: BC.ok.fg, dot: BC.ok.dot, label: 'Conectado' }, pending: { bg: BC.pending.bg, fg: BC.pending.fg, dot: BC.pending.dot, label: 'Pendiente' }, disconnected: { bg: BC.fire.bg, fg: BC.fire.fg, dot: BC.fire.dot, label: 'Desconectado' }, }; const s = map[status] || map.disconnected; return ( {s.label} ); }; // ───────── Role badge ───────── const RoleBadge = ({ role }) => { const map = { admin: { bg: '#F3E8FF', fg: '#6B21A8', label: 'ADMIN' }, operator: { bg: BC.brandTint, fg: BC.brandDeep, label: 'OPERADOR' }, viewer: { bg: BC.bgAlt, fg: BC.inkMuted, label: 'LECTOR' }, }; const s = map[role] || map.viewer; return ( {s.label} ); }; // ───────── App Header ───────── function AppHeader({ user, counts, animationGen, shouldAnimate, onLogout, onAdmin, onReconnect, onResetNotifications, liveState, onFilter, activeFilter, section, onSection, density, onDensity, onOpenSettings, onSidebarToggle }) { const [pillHover, setPillHover] = React.useState(false); const [menuOpen, setMenuOpen] = React.useState(false); const menuRef = React.useRef(null); React.useEffect(() => { if (!menuOpen) return; const handler = (e) => { if (!menuRef.current?.contains(e.target)) setMenuOpen(false); }; document.addEventListener('mousedown', handler); return () => document.removeEventListener('mousedown', handler); }, [menuOpen]); const NotifIcon = ({ iconName, count, cat, severity }) => { const sel = activeFilter === cat; return ( ); }; const tabs = [ { id: 'monitor', label: 'Monitor', icon: 'server' }, { id: 'stream', label: 'Eventos', icon: 'list' }, ]; if (user?.role === 'admin') tabs.push({ id: 'admin', label: 'Administración', icon: 'shield-admin' }); return (
{/* Logo block — centered absolutely */}
Logo
{/* Tabs */}
{tabs.map(t => ( ))}
{/* Live pill */} {/* Notification triad */}
{/* User chip with dropdown menu */}
{menuOpen && (
{/* Header */}
{(user?.username || '?').slice(0,1).toUpperCase()}
{user?.username}
{user?.role}
{/* Density settings */}
Densidad
{[{value:'compact',label:'Compacto'},{value:'comfortable',label:'Cómodo'},{value:'spacious',label:'Espacioso'}].map(opt => ( ))}
{/* Reset notifications */} {/* Logout */}
)}
); } // ───────── Status bar ───────── function StatusBar({ panels, connected, events, filter, onClearFilter, lastUpdate }) { return (
{panels} paneles {connected} conectados {panels - connected} sin conexión {events} eventos {filter && (<>
Filtro: {filter} )} {!filter &&
} Última actualización {formatTs(lastUpdate)} BControl {APP_VERSION} · America/Santiago
); } Object.assign(window, { Icon, StatusPill, RoleBadge, AppHeader, StatusBar });