// ============================================================ // JuFlow — Sidebar + Header Layout (responsive + safe-area) // ============================================================ const { useState: useSt, useEffect: useEf } = React; const U2 = window.JFUtils; const NAV_ITEMS = [ { id: 'dashboard', label: 'Dashboard', icon: 'LayoutDashboard' }, { id: 'agenda', label: 'Agenda', icon: 'Calendar' }, { id: 'clientes', label: 'Clientes', icon: 'Users' }, { id: 'atendimentos', label: 'Atendimentos', icon: 'ClipboardList' }, { id: 'servicos', label: 'Serviços', icon: 'Scissors' }, { id: 'profissionais', label: 'Profissionais', icon: 'UserCheck' }, { id: 'financeiro', label: 'Financeiro', icon: 'DollarSign' }, { id: 'marketing', label: 'Marketing', icon: 'Megaphone' }, { id: 'relatorios', label: 'Relatórios', icon: 'BarChart3' }, { id: 'configuracoes', label: 'Configurações', icon: 'Settings' }, ]; // ─── useIsMobile hook ───────────────────────────────────── const useIsMobile = (breakpoint = 900) => { const [isMobile, setIsMobile] = useSt(typeof window !== 'undefined' && window.matchMedia(`(max-width: ${breakpoint}px)`).matches); useEf(() => { const mq = window.matchMedia(`(max-width: ${breakpoint}px)`); const handler = e => setIsMobile(e.matches); if (mq.addEventListener) mq.addEventListener('change', handler); else mq.addListener(handler); return () => { if (mq.removeEventListener) mq.removeEventListener('change', handler); else mq.removeListener(handler); }; }, [breakpoint]); return isMobile; }; const SidebarLogo = ({ collapsed }) => ( <> {!collapsed && (
Juflow
Digital
)} ); // ─── SIDEBAR ────────────────────────────────────────────── const Sidebar = ({ page, onNavigate, collapsed, onToggle, isMobile, mobileOpen, onMobileClose }) => { const W = isMobile ? 280 : (collapsed ? 68 : 240); const showText = isMobile ? true : !collapsed; const showCollapseToggle = !isMobile; const showMobileClose = isMobile; // Mobile: drawer behavior (off-canvas) const mobileTransform = isMobile && !mobileOpen ? 'translateX(-100%)' : 'translateX(0)'; return ( <> {/* Mobile backdrop */} {isMobile && mobileOpen && (
)}
{/* Logo */}
{showMobileClose && ( )}
{/* Toggle button (desktop only) */} {showCollapseToggle && ( )} {/* Nav */} {/* User avatar at bottom */}
A
{showText && (
Admin Demo
demo@juflow.com
)}
); }; // ─── NOTIFICATION PANEL ─────────────────────────────────── const NotificationPanel = ({ onClose, appointments }) => { const today = window.JF.CLIENTS.filter(c => U2.isBirthdayThisMonth(c.birthdate)).slice(0, 3); const upcoming = appointments .filter(a => ['confirmed','pending'].includes(a.status) && new Date(a.datetime) >= window.JF.TODAY) .sort((a,b) => new Date(a.datetime) - new Date(b.datetime)) .slice(0, 4); return (
Notificações
{today.length > 0 && (
🎂 Aniversariantes
{today.map(c => (
{c.name} — {U2.formatDate(c.birthdate, 'dd/MM')}
))}
)} {upcoming.map(a => { const client = U2.getClient(a.clientId); const service = U2.getService(a.serviceId); return (
{client?.name}
{service?.name} · {U2.formatDateTime(a.datetime)}
); })}
); }; // ─── HEADER ─────────────────────────────────────────────── const Header = ({ page, appointments, onNavigate, onLogout, onMenuOpen, isMobile }) => { const [notifOpen, setNotifOpen] = useSt(false); const [searchVal, setSearchVal] = useSt(''); const [searchResults, setSearchResults] = useSt([]); const [searchFocus, setSearchFocus] = useSt(false); const pageLabels = { dashboard:'Dashboard', agenda:'Agenda', clientes:'Clientes', atendimentos:'Atendimentos', servicos:'Serviços', profissionais:'Profissionais', financeiro:'Financeiro', marketing:'Marketing', relatorios:'Relatórios', configuracoes:'Configurações', }; const todayCount = appointments.filter(a => U2.isSameDay(a.datetime, window.JF.TODAY) && ['confirmed','pending'].includes(a.status) ).length; useEf(() => { if (searchVal.length < 2) { setSearchResults([]); return; } const q = searchVal.toLowerCase(); const clients = window.JF.CLIENTS.filter(c => c.name.toLowerCase().includes(q)).slice(0,3) .map(c => ({ type:'client', label:c.name, sub:'Cliente', id:c.id, icon:'User' })); const services = window.JF.SERVICES.filter(s => s.name.toLowerCase().includes(q)).slice(0,2) .map(s => ({ type:'service', label:s.name, sub:s.category, id:s.id, icon:'Scissors' })); setSearchResults([...clients, ...services]); }, [searchVal]); return (
{/* Hamburger (mobile) */} {isMobile && ( )} {/* Page title */}

{pageLabels[page] || page}

{!isMobile &&

{U2.formatDateLong(window.JF.TODAY)}

}
{/* Search - hidden on mobile */} {!isMobile && (
{searchFocus && searchResults.length > 0 && (
{searchResults.map((r, i) => (
{ setSearchVal(''); setSearchResults([]); if(r.type==='client') onNavigate('clientes'); }} style={{ display:'flex', alignItems:'center', gap:10, padding:'10px 14px', cursor:'pointer', fontSize:13, }} onMouseEnter={e => e.currentTarget.style.background='#F9FAFB'} onMouseLeave={e => e.currentTarget.style.background='#fff'} >
{r.label}
{r.sub}
))}
)}
)} {/* Today badge */} {todayCount > 0 && !isMobile && (
{todayCount} hoje
)} {/* Notifications */}
{notifOpen && ( setNotifOpen(false)} appointments={appointments} /> )}
{/* User menu */} A
) : (
A
Admin
) } items={[ { label:'Perfil', icon:'User', onClick:() => {} }, { label:'Configurações', icon:'Settings', onClick:() => onNavigate('configuracoes') }, 'divider', { label:'Sair', icon:'LogOut', onClick:onLogout, danger:true }, ]} />
); }; Object.assign(window, { Sidebar, Header, useIsMobile });