// 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 ? (

) : (

)}
{/* 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'}>
))}
)}
{/* Usuário — só desktop */}
{!isMobile && (
)}
{/* Avatar mobile */}
{isMobile && (
)}
{/* Page content */}
{/* Overlay notificações */}
{notifOpen &&
setNotifOpen(false)} />}
);
}
Object.assign(window, { App });