// App principal: Login + Shell + roteamento de views + Tweaks const { useState: useSt, useEffect: useEf, useRef: useRf } = React; const ACCENT_PALETTES = { verde: { c: "oklch(55% 0.085 165)", c2: "oklch(70% 0.06 165)", cs: "oklch(95% 0.025 165)", ci: "oklch(35% 0.06 165)", warn: "oklch(58% 0.12 45)", warnSoft: "oklch(95% 0.04 50)" }, azul: { c: "oklch(55% 0.10 240)", c2: "oklch(70% 0.07 240)", cs: "oklch(95% 0.03 240)", ci: "oklch(35% 0.07 240)", warn: "oklch(58% 0.12 45)", warnSoft: "oklch(95% 0.04 50)" }, ambar: { c: "oklch(58% 0.12 65)", c2: "oklch(72% 0.08 65)", cs: "oklch(95% 0.04 70)", ci: "oklch(38% 0.08 60)", warn: "oklch(56% 0.16 28)", warnSoft: "oklch(96% 0.03 28)" }, indigo: { c: "oklch(52% 0.13 290)", c2: "oklch(68% 0.08 290)", cs: "oklch(95% 0.035 290)", ci: "oklch(34% 0.09 290)", warn: "oklch(58% 0.12 45)", warnSoft: "oklch(95% 0.04 50)" }, }; function detectMobileDevice() { const ua = navigator.userAgent || ""; const uaMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile/i.test(ua); const narrow = window.matchMedia("(max-width: 760px)").matches; const coarse = window.matchMedia("(pointer: coarse)").matches; return narrow || (uaMobile && coarse); } function useDeviceClass() { const [isMobile, setIsMobile] = useSt(false); useEf(() => { function update() { const next = detectMobileDevice(); document.body.dataset.device = next ? "mobile" : "desktop"; setIsMobile(next); } update(); window.addEventListener("resize", update); window.addEventListener("orientationchange", update); return () => { window.removeEventListener("resize", update); window.removeEventListener("orientationchange", update); }; }, []); return isMobile; } // ─── Loading screen ──────────────────────────────────────────── function LoadingScreen({ error, onRetry }) { return (
farma
{error ? ( <>

{error}

) : (

Carregando dados…

)}
); } // ─── Login ──────────────────────────────────────────────────── function LoginScreen({ onLogin }) { const [email, setEmail] = useSt("ana.marketing@farma.io"); const [pass, setPass] = useSt("••••••••"); const [busy, setBusy] = useSt(false); function submit(e) { e.preventDefault(); if (!email || !pass) return; setBusy(true); setTimeout(() => onLogin({ email }), 650); } const yyyy = new Date().getFullYear(); return (
farma

Dados que viram decisão.
Não relatório.

Plataforma de inteligência farmacêutica para times de marketing — mapeie demanda, antecipe rupturas e entenda a jornada de cada paciente.

{yyyy} · farma - aulerlabs product

Entrar

Acesse seu painel de inteligência farmacêutica.

setEmail(e.target.value)} autoFocus />
setPass(e.target.value)} />
ou continue com SSO corporativo
); } // ─── Drug picker ───────────────────────────────────────────── function DrugPicker({ drug, onChange }) { const [open, setOpen] = useSt(false); const ref = useRf(null); useEf(() => { function click(e) { if (ref.current && !ref.current.contains(e.target)) setOpen(false); } document.addEventListener("mousedown", click); return () => document.removeEventListener("mousedown", click); }, []); const drugs = window.FARMA_DATA.drugs; return (
{open && (
{drugs.map(d => ( ))}
)}
); } // ─── Dashboard shell ───────────────────────────────────────── function Dashboard({ user, onLogout, initialView = "demanda", showInsight = true, isMobile = false }) { const [drug, setDrug] = useSt(window.FARMA_DATA.drugs[0]); const [view, setView] = useSt(initialView); const [market, setMarket] = useSt("Todos"); const [period, setPeriod] = useSt("M"); // Sync date from data const syncDate = window.FARMA_DATA.kpis?.competencia || "—"; const syncShort = syncDate.split("/").map(s => s.trim()).reverse().join("/").slice(0, 7); const initials = user.email.split("@")[0].split(".").map(s => s[0]).join("").slice(0, 2).toUpperCase(); const navItems = [ { id: "demanda", label: "Demanda", icon: NavIconMap, count: window.FARMA_DATA.kpis?.ufsAtendidas + " UFs" }, { id: "pacientes", label: "Pacientes", icon: NavIconUsers, count: (window.FARMA_DATA.kpis?.pacientesAtivos / 1000).toFixed(1).replace(".", ",") + "k" }, { id: "hospitais", label: isMobile ? "Hosp." : "Hospitais", icon: NavIconHospital, count: window.FARMA_DATA.kpis?.estabsAtivos }, { id: "exames", label: "Exames", icon: NavIconFlask, count: "20k" }, { id: "ied", label: isMobile ? "Intern." : "Internações IEP", icon: NavIconHeart, count: (window.FARMA_DATA.internacoes?.total / 1000).toFixed(1).replace(".", ",") + "k" }, ]; const periodOptions = isMobile ? [{ id: "M", label: "Mês" }, { id: "3M", label: "3m" }, { id: "12M", label: "12m" }, { id: "YTD", label: "YTD" }] : [{ id: "M", label: "Mês corrente" }, { id: "3M", label: "3 meses" }, { id: "12M", label: "12 meses" }, { id: "YTD", label: "YTD" }]; return (
farma
Painéis/ {drug.name}/ {{ demanda: "Demanda", pacientes: "Pacientes", hospitais: "Hospitais", exames: "Exames", ied: "Internações IEP" }[view] || "—"}
{initials}
FILTRAR {["Todos", "Fibrose Cística", "IEP"].map(m => ( ))}
{periodOptions.map(p => ( ))}
{view === "demanda" && } {view === "pacientes" && } {view === "hospitais" && } {view === "exames" && } {view === "ied" && }
); } // ─── Ícones ─────────────────────────────────────────────────── function NavIconMap() { return ; } function NavIconUsers() { return ; } function NavIconHospital(){ return ; } function NavIconFlask() { return ; } function NavIconHeart() { return ; } function NavIconCog() { return ; } function NavIconLogout() { return ; } // ─── App root ───────────────────────────────────────────────── function App() { const [t, setTweak] = useTweaks(window.TWEAK_DEFAULTS); const [user, setUser] = useSt(null); const [toast, setToast] = useSt(null); const [ready, setReady] = useSt(!window.__FARMA_LOADING); const [loadError, setLoadError] = useSt(null); const isMobile = useDeviceClass(); // Aguarda dados da API useEf(() => { if (ready) return; function onReady() { setReady(true); } window.addEventListener("farma:ready", onReady); return () => window.removeEventListener("farma:ready", onReady); }, [ready]); // Aplica tweaks como CSS variables useEf(() => { const root = document.documentElement.style; const p = ACCENT_PALETTES[t.accent] || ACCENT_PALETTES.verde; root.setProperty("--accent", p.c); root.setProperty("--accent-2", p.c2); root.setProperty("--accent-soft", p.cs); root.setProperty("--accent-ink", p.ci); root.setProperty("--r-lg", t.radius + "px"); root.setProperty("--r-md", Math.max(6, t.radius - 4) + "px"); root.setProperty("--r-sm", Math.max(4, t.radius - 8) + "px"); root.setProperty("--display-weight", String(t.displayWeight)); document.body.dataset.density = t.density; }, [t.accent, t.radius, t.displayWeight, t.density]); useEf(() => { if (toast) { const tm = setTimeout(() => setToast(null), 2200); return () => clearTimeout(tm); } }, [toast]); if (!ready) { return { window.location.reload(); }} />; } if (!user) return ( <> { setUser(u); setToast("Bem-vinda, " + u.email.split("@")[0].split(".")[0]); }} /> ); return ( <> setUser(null)} initialView={t.defaultView} showInsight={t.showInsight} isMobile={isMobile} /> {toast &&
{toast}
} ); } function FarmaTweaks({ t, setTweak }) { return ( setTweak("accent", v)} /> setTweak("density", v)} /> setTweak("radius", v)} /> setTweak("displayWeight", v)} /> setTweak("showInsight", v)} /> setTweak("defaultView", v)} /> ); } const root = ReactDOM.createRoot(document.getElementById("root")); root.render();