const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "density": "regular", "formLayout": "sections", "headerStyle": "navy" }/*EDITMODE-END*/; function initialTheme() { try { const saved = localStorage.getItem("nz_theme"); if (saved === "dark" || saved === "light") return saved; } catch (e) {} try { if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) return "dark"; } catch (e) {} return "light"; } const DENSITY_SCALE = { compact: 0.92, regular: 1, comfy: 1.1 }; // Nomes de negócio dos três perfis de RF-USUARIOS-01 (ESP-0001). A chave é o // valor que a API devolve em `perfil`; o texto é o que a pessoa lê. const NOME_DO_PERFIL = { administrador: "Administração", editor: "Editoração", operador: "Operação", }; // Rotas privativas de `administrador` (RF-USUARIOS-05, RF-AUDITORIA-07, // RF-CONFIG-01). const AREA_ADMINISTRATIVA = ["users", "audit", "devices", "settings"]; function NavItem({ icon, label, count, active, onClick }) { return ( ); } function App() { const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); const [route, setRoute] = React.useState(() => { try { return JSON.parse(localStorage.getItem("nz_route")) || { view: "dashboard" }; } catch (e) { return { view: "dashboard" }; } }); const [toast, setToast] = React.useState(null); const [theme, setTheme] = React.useState(initialTheme); const [navOpen, setNavOpen] = React.useState(false); const [auth, setAuth] = React.useState({ loggedIn: false, user: null, loading: true }); const [loginError, setLoginError] = React.useState(null); const [loginBusy, setLoginBusy] = React.useState(false); React.useEffect(() => { if (window.API && window.API.isAuthenticated()) { window.API.getMe().then(u => { setAuth({ loggedIn: true, user: u, loading: false }); }).catch(() => { window.API.logout(); setAuth({ loggedIn: false, user: null, loading: false }); }); } else { setAuth(p => ({ ...p, loading: false })); } }, []); React.useEffect(() => { document.documentElement.style.setProperty("--ui-scale", DENSITY_SCALE[t.density] || 1); }, [t.density]); React.useEffect(() => { document.documentElement.setAttribute("data-theme", theme); try { localStorage.setItem("nz_theme", theme); } catch (e) {} }, [theme]); // RF-UI-01: `nz_route` é persistida em localStorage. Uma rota salva apontando // para a área de administração faria um editor que já foi administrador abrir // o portal numa tela proibida — que chamaria a API e receberia 403. Cair para // o painel corrige o estado guardado; a proteção continua sendo do backend. // // Fica aqui, junto dos demais hooks, e **antes** dos retornos antecipados // abaixo: um `useEffect` depois deles só é chamado em parte das renderizações, // e o React aborta com "rendered more hooks than during the previous render". const rotaProibida = auth.user ? auth.user.perfil !== "administrador" && AREA_ADMINISTRATIVA.includes(route.view) : false; React.useEffect(() => { if (rotaProibida) go("dashboard"); }, [rotaProibida]); const toggleTheme = () => setTheme(p => (p === "dark" ? "light" : "dark")); const go = (view, extra = {}) => { const nr = { view, ...extra }; setRoute(nr); setNavOpen(false); try { localStorage.setItem("nz_route", JSON.stringify(nr)); } catch (e) {} document.querySelector(".scroll")?.scrollTo(0, 0); }; const handleLogin = (email, pwd) => { setLoginBusy(true); setLoginError(null); window.API.login(email, pwd).then(() => { return window.API.getMe(); }).then(user => { setAuth({ loggedIn: true, user: user, loading: false }); setLoginBusy(false); go("dashboard"); }).catch(err => { setLoginError(err.message || "Erro ao autenticar"); setLoginBusy(false); }); }; const handleLogout = () => { window.API.logout(); setAuth({ loggedIn: false, user: null, loading: false }); go("login"); }; if (auth.loading) { return (