// ============================================================ // FINAFAMILY — Layout: Sidebar, Header, BottomNav // ============================================================ const { useState, useEffect, useRef } = React; // Ícone e rótulo de CADA tema (não do próximo) — o botão de troca rápida // cicla light → dark → nubank-light → light ao ser clicado. const THEME_META = { 'light': { icon: 'Sun', label: 'Finafamily Claro' }, 'dark': { icon: 'Moon', label: 'Finafamily Escuro' }, 'nubank-light': { icon: 'Zap', label: 'Finafamily Roxo' }, }; const NAV_ITEMS = [ { id: 'dashboard', label: 'Dashboard', icon: 'LayoutDashboard' }, { id: 'despesas', label: 'Despesas', icon: 'Wallet' }, { id: 'receitas', label: 'Receitas', icon: 'DollarSign' }, { id: 'entre-membros', label: 'Entre membros', icon: 'Users' }, { id: 'cartoes', label: 'Pagamentos', icon: 'CreditCard' }, { id: 'investimentos', label: 'Investimentos', icon: 'TrendingUp' }, { id: 'orcamento', label: 'Orçamento', icon: 'Wallet' }, { id: 'relatorios', label: 'Relatórios', icon: 'BarChart2' }, { id: 'importar', label: 'Importar Fatura', icon: 'Upload' }, { id: 'configuracoes', label: 'Configurações', icon: 'Settings' }, ]; const SCREEN_LABELS = { dashboard: 'Dashboard', despesas: 'Despesas', receitas: 'Receitas', 'entre-membros': 'Valores entre membros', cartoes: 'Meios de Pagamento', investimentos: 'Investimentos', orcamento: 'Orçamento mensal', relatorios: 'Relatórios', importar: 'Importar Fatura', notificacoes: 'Notificações', configuracoes: 'Configurações', }; function Logo({ collapsed = false }) { return (
{!collapsed && ( Fina family . )}
); } function Sidebar({ currentScreen, onNavigate }) { const { theme, toggle } = useTheme(); const { currentMember, logout } = useApp(); const [collapsed, setCollapsed] = useState(false); return ( ); } // Pra onde a notificação leva o usuário quando ele clica nela — link vem do // backend (ex.: 'despesas', 'cartoes', 'dashboard'); sem link, cai no dashboard. function notificationTargetScreen(n) { return n.link === 'member-operations' ? 'entre-membros' : (n.link || 'dashboard'); } function NotificationPopup({ onNavigate, onClose }) { const { notifications, markNotificationRead } = useApp(); const ref = useRef(null); useEffect(() => { function handleClickOutside(e) { if (ref.current && !ref.current.contains(e.target)) onClose(); } document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, [onClose]); function handleClick(n) { if (!n.read) markNotificationRead(n.id); onClose(); onNavigate(notificationTargetScreen(n)); } const TYPE_META = { warning: { icon: 'AlertTriangle', color: '#F59E0B' }, alert: { icon: 'Bell', color: '#EF4444' }, info: { icon: 'Info', color: '#3B82F6' }, }; return (
Notificações
{notifications.length === 0 && (
Nenhuma notificação por aqui.
)} {notifications.slice(0, 20).map(n => { const meta = TYPE_META[n.type] || TYPE_META.info; return ( ); })}
); } function Header({ currentScreen, onNavigate }) { const { currentMonth, setCurrentMonth, currentMember, unreadCount, notifAlert, markAllRead } = useApp(); const { theme, toggle } = useTheme(); const [showNotifPopup, setShowNotifPopup] = useState(false); // O sino pisca com a contagem até o usuário abrir o popup e ver a lista — // ao fechar (clicando fora, num item, ou no próprio sino de novo) tudo é // dado como visto e a contagem zera. function closeNotifPopup() { setShowNotifPopup(false); markAllRead(); } function toggleNotifPopup() { if (showNotifPopup) closeNotifPopup(); else setShowNotifPopup(true); } function prevMonth() { setCurrentMonth(m => { if (m.month === 1) return { year: m.year - 1, month: 12 }; return { ...m, month: m.month - 1 }; }); } function nextMonth() { setCurrentMonth(m => { if (m.month === 12) return { year: m.year + 1, month: 1 }; return { ...m, month: m.month + 1 }; }); } const monthNames = ['Janeiro','Fevereiro','Março','Abril','Maio','Junho','Julho','Agosto','Setembro','Outubro','Novembro','Dezembro']; const MONTHS_SHORT = ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez']; return (
{/* Logo — só aparece no mobile (sidebar oculta) */}
{/* Título da tela — oculto no mobile, substituído pelo logo */} {SCREEN_LABELS[currentScreen]} {/* Month nav */} {['dashboard','despesas','receitas'].includes(currentScreen) && (
{/* Desktop: mês completo / Mobile: abreviado */} {monthNames[currentMonth.month - 1]} {currentMonth.year} {MONTHS_SHORT[currentMonth.month - 1]}/{String(currentMonth.year).slice(-2)}
)}
{/* Dark mode — oculto no mobile (está na sidebar / settings) */}
{showNotifPopup && }
{currentMember.name.split(' ')[0]}
); } function BottomNav({ currentScreen, onNavigate }) { const { currentMember, logout } = useApp(); const [menuOpen, setMenuOpen] = useState(false); const MOBILE_NAV = [ { id: '__menu__', icon: 'Menu', label: 'Menu' }, { id: 'dashboard', icon: 'LayoutDashboard', label: 'Início' }, { id: 'despesas', icon: 'Wallet', label: 'Despesas' }, { id: 'receitas', icon: 'DollarSign', label: 'Receitas' }, { id: 'cartoes', icon: 'CreditCard', label: 'Cartões' }, ]; // Itens do drawer — o que não está na barra principal const DRAWER_ITEMS = [ { id: 'entre-membros', icon: 'Users', label: 'Entre membros', desc: 'Valores a pagar e receber', color: '#5264de' }, { id: 'investimentos', icon: 'TrendingUp', label: 'Investimentos', desc: 'Portfólio e rendimentos', color: '#7C3AED' }, { id: 'orcamento', icon: 'Wallet', label: 'Orçamento', desc: 'Plano mensal e divisões', color: 'var(--accent)' }, { id: 'relatorios', icon: 'BarChart2', label: 'Relatórios', desc: 'Análise e tendências', color: '#0284C7' }, { id: 'importar', icon: 'Upload', label: 'Importar Fatura', desc: 'Lançar despesas do cartão', color: '#059669' }, { id: 'configuracoes', icon: 'Settings', label: 'Configurações', desc: 'Perfil e preferências', color: '#64748B' }, ]; function handleNav(id) { onNavigate(id); setMenuOpen(false); } return ( <> {/* ── Bottom Navigation Bar ── */} {/* ── Menu Drawer (Bottom Sheet) ── */} {menuOpen && (
setMenuOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 450, // acima do botão "+" (400), abaixo dos modais background: 'var(--overlay-bg)', backdropFilter: 'blur(2px)', animation: 'fadeIn 180ms ease-out', }} /> )}
{/* Handle + cabeçalho */}
{/* Perfil do membro */} {currentMember && (
{currentMember.initials || currentMember.name?.slice(0,2).toUpperCase()}
{currentMember.name}
{currentMember.email}
)}
{/* Grid de opções */}
{DRAWER_ITEMS.map(item => { const active = currentScreen === item.id; return ( ); })}
{/* Separador + Sair */}
); } // ── Popup diário: alertas de vencimento + situação do dia a dia ────────── // Aparece uma vez por dia, no primeiro acesso (ver useEffect em data.jsx). function DailyDigestModal({ onNavigate }) { const { dailyDigest, dismissDailyDigest, markNotificationRead } = useApp(); if (!dailyDigest || dailyDigest.length === 0) return null; const TYPE_META = { warning: { icon: 'AlertTriangle', color: '#F59E0B' }, alert: { icon: 'Bell', color: '#EF4444' }, info: { icon: 'Info', color: '#3B82F6' }, }; function handleClick(n) { if (!n.read) markNotificationRead(n.id); dismissDailyDigest(); onNavigate(n.link || 'dashboard'); } return (
Vencimentos próximos e o ritmo dos seus gastos do dia a dia:
{dailyDigest.map(n => { const meta = TYPE_META[n.type] || TYPE_META.info; return ( ); })}
); } Object.assign(window, { Sidebar, Header, BottomNav, DailyDigestModal, NAV_ITEMS, SCREEN_LABELS });