/* JRN — App shell, navegação, controle de acesso e Tweaks */ const { useState: uS, useEffect } = React; const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "companyInteraction": "click", "sidebar": "navy", "showBrandBar": true }/*EDITMODE-END*/; /* Itens de navegação administrativos e a função que cada um exige */ const ADMIN_NAV = [ { id: 'admin-dashboards', label: 'Dashboards', icon: 'chart', fn: 'dash_cadastro' }, { id: 'admin-users', label: 'Usuários', icon: 'users', fn: 'user_edicao' }, { id: 'admin-companies', label: 'Empresas', icon: 'building', fn: null }, { id: 'admin-audit', label: 'Auditoria', icon: 'history', fn: 'auditoria' }, ]; function canSee(user, fn) { if (!user) return false; if (user.perfil === 'admin') return true; if (!fn) return false; const fns = user.funcoes || []; return fns.includes('*') || fns.includes(fn); } function Sidebar({ user, view, onNav, tweaks, collapsed, onToggle, onLogout, mobile, mobileOpen }) { const dark = tweaks.sidebar === 'navy'; const [userOpen, setUserOpen] = useState(false); const adminItems = ADMIN_NAV.filter(i => canSee(user, i.fn)); const showExpanded = mobile ? true : !collapsed; // no mobile sempre expandida const C = { bg: dark ? 'linear-gradient(180deg,#15233f,#0d1730)' : '#fff', fg: dark ? '#fff' : 'var(--ink)', muted: dark ? '#8ea0c4' : 'var(--muted)', line: dark ? 'rgba(255,255,255,.1)' : 'var(--line)', activeBg: dark ? 'rgba(255,255,255,.12)' : 'var(--navy-100)', activeFg: dark ? '#fff' : 'var(--navy-800)', hover: dark ? 'rgba(255,255,255,.06)' : 'var(--navy-50)', }; const NavItem = ({ id, label, icon }) => { const I = Icons[icon]; const active = view === id || (id === 'portal' && (view === 'portal' || view === 'dashboard')); return ( ); }; const mobileStyle = mobile ? { position: 'fixed', top: 0, left: 0, height: '100vh', zIndex: 55, overflowY: 'auto', width: 260, transform: mobileOpen ? 'translateX(0)' : 'translateX(-270px)', transition: 'transform .25s cubic-bezier(.4,0,.2,1)', boxShadow: mobileOpen ? 'var(--sh-lg)' : 'none', } : { width: collapsed ? 76 : 248, position: 'sticky', top: 0, height: '100vh', transition: 'width .22s ease, padding .22s ease', flexShrink: 0, }; return ( ); } function Topbar({ user, onLogout, onOpenMenu, isMobile }) { return (
{isMobile && ( )}
); } /* Tela de carregamento enquanto verifica a sessão */ function LoadingScreen() { return (
JRN
); } function App() { const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); const [session, setSession] = useState(null); const [loadingSession, setLoadingSession] = useState(true); const [empresas, setEmpresas] = useState([]); const [view, setView] = useState('portal'); const isMobile = useMediaQuery('(max-width: 640px)'); const isTablet = useMediaQuery('(max-width: 1100px)'); const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); const [collapsed, setCollapsed] = useState(() => { const saved = localStorage.getItem('jrn-sidebar'); if (saved !== null) return saved === '1'; return window.innerWidth <= 1100; // recolhido por padrão em telas menores }); const toggleSidebar = () => setCollapsed(c => { localStorage.setItem('jrn-sidebar', c ? '0' : '1'); return !c; }); const [openDash, setOpenDash] = useState(null); const [toastNode, toast] = useToast(); // Verifica sessão existente ao carregar a página useEffect(() => { apiMe() .then(({ user, empresas: emp }) => { setSession(user); setEmpresas(emp); }) .catch(() => {}) .finally(() => setLoadingSession(false)); }, []); // Navega e fecha sidebar no mobile const handleNav = (v) => { setView(v); setOpenDash(null); if (isMobile) setMobileSidebarOpen(false); }; // Chamado após apiLogin() bem-sucedido const doLogin = async () => { const { user, empresas: emp } = await apiMe(); setSession(user); setEmpresas(emp); setView('portal'); setOpenDash(null); }; const logout = async () => { try { await apiLogout(); } catch (_) {} setSession(null); setEmpresas([]); setView('portal'); setOpenDash(null); }; if (loadingSession) return ; if (!session) return ( ); let content; if (view === 'dashboard' && openDash) { content = { setView('portal'); setOpenDash(null); }} />; } else if (view === 'admin-users' && canSee(session, 'user_edicao')) { content = ; } else if (view === 'admin-dashboards' && canSee(session, 'dash_cadastro')) { content = ; } else if (view === 'admin-companies' && session.perfil === 'admin') { content = ; } else if (view === 'admin-audit' && canSee(session, 'auditoria')) { content = ; } else { content = { setOpenDash(d); setView('dashboard'); }} />; } const mainPad = isMobile ? '18px 16px 40px' : isTablet ? '26px 28px 56px' : '34px 40px 60px'; return (
{/* Backdrop para fechar sidebar no mobile */} {isMobile && mobileSidebarOpen && (
setMobileSidebarOpen(false)} /> )}
setMobileSidebarOpen(true)} />
{content}
{toastNode}
); } function TweaksHost({ t, setTweak }) { return ( setTweak('companyInteraction', v)} /> setTweak('sidebar', v)} /> ); } ReactDOM.createRoot(document.getElementById('root')).render();