// Main App Shell — Sidebar, Topbar, Routing function App() { const [session, setSession] = React.useState(null); const [authLoading, setAuthLoading] = React.useState(true); React.useEffect(() => { sb.auth.getSession().then(({ data: { session } }) => { setSession(session); setAuthLoading(false); }); const { data: { subscription } } = sb.auth.onAuthStateChange((_event, session) => { setSession(session); }); return () => subscription.unsubscribe(); }, []); if (authLoading) { return (
); } if (!session) { return ; } return ; } function AppShell({ session }) { const [page, setPage] = React.useState(() => localStorage.getItem('ef_page') || 'dashboard'); const [sidebarCollapsed, setSidebarCollapsed] = React.useState(false); const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false); const [isMobile, setIsMobile] = React.useState(window.innerWidth < 768); const [notifOpen, setNotifOpen] = React.useState(false); const [searchVal, setSearchVal] = React.useState(''); const [notifs, setNotifs] = React.useState([]); React.useEffect(() => { const onResize = () => setIsMobile(window.innerWidth < 768); window.addEventListener('resize', onResize); return () => window.removeEventListener('resize', onResize); }, []); React.useEffect(() => { const today = new Date().toISOString().split('T')[0]; const inWeek = new Date(Date.now() + 7 * 86400000).toISOString().split('T')[0]; Promise.all([ sb.from('events').select('id, name, event_date').gte('event_date', today).lte('event_date', inWeek).order('event_date'), sb.from('budgets').select('id').in('status', ['Pendente', 'Em análise']), ]).then(([evRes, budRes]) => { const items = []; (evRes.data || []).slice(0, 3).forEach(e => { const days = Math.ceil((new Date(e.event_date) - new Date(today)) / 86400000); items.push({ icon: 'calendar', color: '#3B82F6', text: `${e.name} em ${days === 0 ? 'hoje' : days + ' dia(s)'}`, time: 'Agenda' }); }); const pending = (budRes.data || []).length; if (pending > 0) items.push({ icon: 'budget', color: '#F59E0B', text: `${pending} orçamento(s) aguardando aprovação`, time: 'Orçamentos' }); setNotifs(items); }); }, []); const userName = session?.user?.email?.split('@')[0] || 'Usuário'; const displayName = userName.charAt(0).toUpperCase() + userName.slice(1); const navigate = (p) => { setPage(p); localStorage.setItem('ef_page', p); if (isMobile) setMobileMenuOpen(false); // auto-close no mobile }; const handleLogout = async () => { await sb.auth.signOut(); }; const nav = [ { id: 'dashboard', label: 'Dashboard', icon: 'dashboard' }, { id: 'budgets', label: 'Orçamentos', icon: 'budget' }, { id: 'events', label: 'Agenda', icon: 'calendar' }, { id: 'freelancers', label: 'Freelancers', icon: 'users' }, { id: 'crm', label: 'Clientes', icon: 'crm' }, { id: 'financeiro', label: 'Financeiro', icon: 'wallet' }, { id: 'tasks', label: 'Tarefas', icon: 'tasks' }, { id: 'portal', label: 'Portal Cliente', icon: 'portal' }, { id: 'reports', label: 'Relatórios', icon: 'reports' }, { id: 'usuarios', label: 'Usuários', icon: 'users' }, ]; const SIDEBAR_W = isMobile ? 260 : (sidebarCollapsed ? 60 : 200); // Safe area top (notch) const safeTop = 'env(safe-area-inset-top, 0px)'; const SidebarItem = ({ item }) => { const active = page === item.id; const collapsed = !isMobile && sidebarCollapsed; const [hov, setHov] = React.useState(false); return (
navigate(item.id)} onMouseEnter={() => setHov(true)} onMouseLeave={() => setHov(false)} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: collapsed ? '10px 0' : '10px 14px', justifyContent: collapsed ? 'center' : 'flex-start', borderRadius: DS.radius.sm, cursor: 'pointer', transition: 'all 0.15s', background: active ? DS.colors.primary : hov ? DS.colors.sidebarHover : 'transparent', color: active ? '#fff' : hov ? '#CBD5E1' : '#94A3B8', position: 'relative', minHeight: 44, }}> {!collapsed && {item.label}} {active && !collapsed &&
} {collapsed && hov && (
{item.label}
)}
); }; const pageMap = { dashboard: Dashboard, budgets: Budgets, events: Events, freelancers: Freelancers, crm: CRM, financeiro: Financeiro, tasks: Tasks, portal: ClientPortal, reports: Reports, usuarios: Usuarios }; const PageComp = pageMap[page] || Dashboard; const collapsed = !isMobile && sidebarCollapsed; // ── Sidebar content (reutilizado em desktop e mobile) ── const SidebarContent = () => ( <> {/* Logo */}
{collapsed ? ( Promoup ) : ( Promoup 360° )}
{/* Nav */}
{!collapsed &&
Principal
} {nav.slice(0, 3).map(item => )} {!collapsed &&
Gestão
} {collapsed &&
} {nav.slice(3).map(item => )}
{/* Bottom */}
{!isMobile && (
setSidebarCollapsed(c => !c)} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 10px', borderRadius: DS.radius.sm, cursor: 'pointer', color: '#64748B', justifyContent: collapsed ? 'center' : 'flex-start', marginBottom: 4 }} onMouseEnter={e => e.currentTarget.style.color = '#94A3B8'} onMouseLeave={e => e.currentTarget.style.color = '#64748B'}> {!collapsed && Recolher menu}
)} {!collapsed && (
{displayName}
Administrador
)} {collapsed && (
e.currentTarget.style.color = '#EF4444'} onMouseLeave={e => e.currentTarget.style.color = '#475569'}>
)}
); return (
{/* ── DESKTOP Sidebar ── */} {!isMobile && (
)} {/* ── MOBILE Sidebar overlay ── */} {isMobile && mobileMenuOpen && (
setMobileMenuOpen(false)}>
)} {isMobile && (
)} {/* ── Main ── */}
{/* Topbar */}
{/* Hamburguer mobile */} {isMobile && ( )} {/* Search — oculto no mobile pequeno */} {!isMobile && (
setSearchVal(e.target.value)} placeholder="Buscar eventos, clientes, tarefas…" style={{ width: '100%', border: `1px solid ${DS.colors.border}`, borderRadius: DS.radius.sm, padding: '8px 12px 8px 36px', fontSize: 13, outline: 'none', background: '#F8FAFC', fontFamily: 'inherit', color: DS.colors.text, boxSizing: 'border-box' }} />
)} {isMobile && (
{nav.find(n => n.id === page)?.label || 'Dashboard'}
)}
{!isMobile && navigate('events')}>Novo evento} {/* Notificações */}
{notifOpen && (
Notificações {notifs.length > 0 && {notifs.length} novas}
{notifs.length === 0 ? (
Sem notificações no momento
) : notifs.map((n, i) => (
setNotifOpen(false)} style={{ display: 'flex', gap: 12, padding: '12px 16px', borderBottom: i < notifs.length - 1 ? `1px solid ${DS.colors.border}` : 'none', cursor: 'pointer' }} onMouseEnter={e => e.currentTarget.style.background = '#F8FAFC'} onMouseLeave={e => e.currentTarget.style.background = '#fff'}>
{n.text}
{n.time}
))}
)}
{/* Usuário — só desktop */} {!isMobile && (
{displayName}
)} {/* Avatar mobile */} {isMobile && (
)}
{/* Page content */}
{/* Overlay notificações */} {notifOpen &&
setNotifOpen(false)} />}
); } Object.assign(window, { App });