// ============================================================ // FINAFAMILY — Relatórios, Notificações, Configurações // ============================================================ const { useState, useMemo, useEffect } = React; // ── Relatórios ───────────────────────────────────────────── function Relatorios({ onNavigate }) { const { expenses, incomes, members, categories, balancete, exportToCSV, currentMonth, currentMember, paymentMethods, fetchMonthExpenses, fetchMonthIncomes } = useApp(); const [toast, setToast] = useState(null); // Garante que os últimos 6 meses (incluindo o atual) estejam carregados — // o resto do app só pré-carrega meses vizinhos. useEffect(() => { for (let i = 0; i < 6; i++) { const d = new Date(currentMonth.year, currentMonth.month - 1 - i, 1); fetchMonthExpenses(d.getFullYear(), d.getMonth() + 1); fetchMonthIncomes(d.getFullYear(), d.getMonth() + 1); } }, [currentMonth.year, currentMonth.month]); // Histórico real dos últimos 6 meses do membro logado (antes eram números // fixos de exemplo, com meses fixos "Dez…Mai"). Despesa = regra canônica do // mês (o que sai do bolso dele), receita = receitas dele no mês. const MONTHS_SHORT_PT = ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez']; const history = useMemo(() => Array.from({ length: 6 }, (_, idx) => { const d = new Date(currentMonth.year, currentMonth.month - 1 - (5 - idx), 1); const y = d.getFullYear(), m = d.getMonth() + 1; const exp = monthExpensesTotal(expenses, currentMember?.id, paymentMethods, y, m, 0); const inc = incomes.filter(i => { const di = parseLocalDate(i.date); return i.memberId === currentMember?.id && di.getFullYear() === y && di.getMonth() + 1 === m; }).reduce((s, i) => s + (Number(i.value) || 0), 0); return { label: MONTHS_SHORT_PT[m - 1], exp, inc }; }), [expenses, incomes, currentMonth.year, currentMonth.month, currentMember?.id, paymentMethods]); const last6Months = history.map(h => h.label); const expenseHistory = history.map(h => h.exp); const incomeHistory = history.map(h => h.inc); const balanceHistory = incomeHistory.map((v, i) => v - expenseHistory[i]); const maxBar = Math.max(...expenseHistory, ...incomeHistory, 1); // Janela dos últimos 6 meses (incluindo o mês em exibição), início/fim inclusive. const sixMonthWindow = useMemo(() => { const start = new Date(currentMonth.year, currentMonth.month - 1 - 5, 1); const end = new Date(currentMonth.year, currentMonth.month, 0); // último dia do mês atual return { start, end }; }, [currentMonth.year, currentMonth.month]); const expensesInWindow = useMemo(() => expenses.filter(e => { const d = parseLocalDate(e.date); return d >= sixMonthWindow.start && d <= sixMonthWindow.end; }), [expenses, sixMonthWindow]); // Ranking por categoria: total do mês em exibição + média mensal dos últimos 6 meses. const catRanking = categories.map(cat => { const inMonth = expenses.filter(e => e.categoryId === cat.id && parseLocalDate(e.date).getFullYear() === currentMonth.year && parseLocalDate(e.date).getMonth() + 1 === currentMonth.month ); const inWindow = expensesInWindow.filter(e => e.categoryId === cat.id); return { ...cat, total: inMonth.reduce((s, e) => s + e.value, 0), avg6m: inWindow.reduce((s, e) => s + e.value, 0) / 6, }; }).filter(c => c.total > 0 || c.avg6m > 0).sort((a, b) => b.total - a.total); const maxCat = catRanking[0]?.total || 1; return (
{toast && setToast(null)} />} {/* Export header */}
{/* Income vs Expense comparison */}
Receitas vs Despesas
Receita
Despesa
{last6Months.map((month, i) => (
{month}
))}
{/* Balance evolution */}
Saldo Familiar Mensal
{(() => { const W2 = 300, H2 = 100; const minV = Math.min(...balanceHistory), maxV = Math.max(...balanceHistory); const range = maxV - minV || 1; const pts = balanceHistory.map((v, i) => { const x = (i / (balanceHistory.length - 1)) * W2; const y = H2 - ((v - minV) / range) * (H2 - 16) - 8; return `${x},${y}`; }).join(' '); const areaPoints = `0,${H2} ` + pts + ` ${W2},${H2}`; return ( {balanceHistory.map((v, i) => { const x = (i / (balanceHistory.length - 1)) * W2; const y = H2 - ((v - minV) / range) * (H2 - 16) - 8; return ; })} {last6Months.map((m, i) => { const x = (i / (last6Months.length - 1)) * W2; return {m}; })} ); })()}
{/* Category ranking */}
Ranking de Gastos por Categoria
Total no mês em exibição · média mensal nos últimos 6 meses
{catRanking.map((cat, i) => (
#{i + 1} {cat.name}
{fmtBRL(cat.total)}
média 6m: {fmtBRL(cat.avg6m)}
))}
{/* Shared expenses report */}
Quem Deve a Quem (Balancete)
{balancete.length === 0 ? ( ) : ( balancete.map((item, i) => { const fromM = members.find(m => m.id === item.from); const toM = members.find(m => m.id === item.to); if (!fromM || !toM) return null; return (
{fromM.name.split(' ')[0]} deve a {toM.name.split(' ')[0]}
Saldo líquido consolidado
{fmtBRL(item.amount)}
); }) )}
); } // ── Notificações ─────────────────────────────────────────── function Notificacoes() { const { notifications, markNotificationRead, markAllRead, unreadCount, notifPrefs, setNotifPrefs } = useApp(); const typeIcon = { warning: 'AlertTriangle', alert: 'Bell', info: 'Info' }; const typeColor = { warning: '#F59E0B', alert: '#EF4444', info: '#3B82F6' }; const [showPrefs, setShowPrefs] = useState(false); const ALERT_TYPES = [ { key: 'cardDue', icon: 'CreditCard', label: 'Cartão próximo ao vencimento', desc: 'Aviso 3 dias antes do vencimento da fatura' }, { key: 'installmentDue', icon: 'CalendarClock', label: 'Parcelas vencendo em 7 dias', desc: 'Alerta de parcelas a vencer na semana' }, { key: 'diaDia80', icon: 'Zap', label: 'Dia a Dia acima de 80%', desc: 'Quando o limite mensal estiver quase esgotado' }, { key: 'investmentStale', icon: 'TrendingUp', label: 'Investimento sem atualização', desc: 'Investimento parado há mais de 30 dias' }, ]; function togglePref(key) { setNotifPrefs({ ...notifPrefs, [key]: !notifPrefs[key] }); } function Toggle({ on, onToggle }) { return ( ); } return (
{/* Header */}
{unreadCount} não lida{unreadCount !== 1 ? 's' : ''}
{unreadCount > 0 && ( )}
{/* Alert preferences panel */} {showPrefs && (
Quais alertas você quer receber
{ALERT_TYPES.map((a, i) => (
{a.label}
{a.desc}
togglePref(a.key)} />
))}
Preferências salvas automaticamente para este dispositivo
)} {/* Notification list */}
{notifications.length === 0 && } {notifications.map(n => (
markNotificationRead(n.id)} style={{ display: 'flex', gap: 14, padding: '16px 18px', background: 'var(--surface-1)', border: `1px solid ${n.read ? 'var(--border)' : typeColor[n.type] + '44'}`, borderRadius: 14, cursor: 'pointer', opacity: n.read ? 0.6 : 1, transition: 'opacity 200ms, border-color 200ms', }}>
{n.title} {!n.read &&
}
{n.desc}
{new Date(n.date).toLocaleDateString('pt-BR')}
))}
); } // ── Shared helpers for config lists ─────────────────────── const PRESET_COLORS = ['#EF4444','#F97316','#F59E0B','#10B981','#0D9488','#3B82F6','#6366F1','#8B5CF6','#EC4899','#94A3B8']; const PRESET_ICONS = ['ShoppingCart','Home','Car','Utensils','Heart','Briefcase','GraduationCap','Plane','Music','Smartphone','Shirt','Baby','Gamepad2','Coffee','Dumbbell','PawPrint','Book','Gift']; function ColorDot({ color, selected, onClick }) { return (
); } function IconGrid({ selected, onSelect }) { return (
{PRESET_ICONS.map(ic => (
onSelect(ic)} style={{ width: 34, height: 34, borderRadius: 9, cursor: 'pointer', background: selected === ic ? 'var(--action-bg)' : 'var(--surface-3)', border: selected === ic ? '2px solid var(--accent)' : '1.5px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: ffReadableColor(selected === ic ? '#FFFFFF' : 'var(--text-2)'), transition: 'all 120ms', boxShadow: selected === ic ? '0 0 0 3px var(--accent-tint)' : 'none', }} onMouseEnter={e => { if (selected !== ic) { e.currentTarget.style.background = 'var(--accent-tint)'; e.currentTarget.style.color = 'var(--accent)'; e.currentTarget.style.borderColor = 'var(--accent)'; } }} onMouseLeave={e => { if (selected !== ic) { e.currentTarget.style.background = 'var(--surface-3)'; e.currentTarget.style.color = 'var(--text-2)'; e.currentTarget.style.borderColor = 'var(--border)'; } }} >
))}
); } // ── Configurações ───────────────────────────────────────── function Configuracoes() { const { currentMember, members, categories, addCategory, updateCategory, deleteCategory, paymentMethods, addPaymentMethod, updatePaymentMethod, deletePaymentMethod, investmentTypes, setInvestmentTypes, investmentPurposes, setInvestmentPurposes, incomeSources, setIncomeSources, updateEmail, updatePassword, deleteAccount, addMember, updateMember, } = useApp(); const { theme, setTheme } = useTheme(); const [activeTab, setActiveTab] = useState('perfil'); const [diaDiaVal, setDiaDiaVal] = useState(currentMember.diaDiaLimit); const [notifyCardDueDaysVal, setNotifyCardDueDaysVal] = useState(currentMember.notifyCardDueDays ?? 3); const [notifyInstallmentDueDaysVal, setNotifyInstallmentDueDaysVal] = useState(currentMember.notifyInstallmentDueDays ?? 7); const [profileName, setProfileName] = useState(currentMember.name); const [profileSaving, setProfileSaving] = useState(false); const [toast, setToast] = useState(null); const [toastType, setToastType] = useState('success'); function showToast(msg, type = 'success') { setToast(msg); setToastType(type); } async function handleSaveProfile() { setProfileSaving(true); try { await updateMember(currentMember.id, { name: profileName.trim() || currentMember.name, diaDiaLimit: parseFloat(diaDiaVal) || currentMember.diaDiaLimit, notifyCardDueDays: parseInt(notifyCardDueDaysVal, 10) >= 0 ? parseInt(notifyCardDueDaysVal, 10) : currentMember.notifyCardDueDays, notifyInstallmentDueDays: parseInt(notifyInstallmentDueDaysVal, 10) >= 0 ? parseInt(notifyInstallmentDueDaysVal, 10) : currentMember.notifyInstallmentDueDays, }); showToast('Perfil salvo com sucesso!'); } catch { showToast('Erro ao salvar perfil. Tente novamente.', 'error'); } finally { setProfileSaving(false); } } // ── Category state ────────────────────────────────────── const [editingCatId, setEditingCatId] = useState(null); const [editCatForm, setEditCatForm] = useState({}); const [showNewCat, setShowNewCat] = useState(false); const [newCat, setNewCat] = useState({ name: '', color: PRESET_COLORS[0], icon: 'ShoppingCart' }); // ── Investment types state ────────────────────────────── const [newInvType, setNewInvType] = useState(''); // ── Investment purposes state ─────────────────────────── const [newPurpose, setNewPurpose] = useState({ label: '', color: PRESET_COLORS[0] }); const [showNewPurpose, setShowNewPurpose] = useState(false); // ── Income sources state ──────────────────────────────── const [newSource, setNewSource] = useState(''); // ── Payment method state ──────────────────────────────── const [showNewPM, setShowNewPM] = useState(false); const [newPM, setNewPM] = useState({ name: '', type: 'credit', bank: '', limit: '', closingDay: '', dueDay: '', color: '#0D9488' }); const [editingPM, setEditingPM] = useState(null); // id do cartão em edição const [editPMForm, setEditPMForm] = useState({}); const [pmDeleteConfirm, setPmDeleteConfirm] = useState(null); // ── Members state ─────────────────────────────────────── const [showNewMember, setShowNewMember] = useState(false); const [newMember, setNewMember] = useState({ name: '', email: '', password: '', role: 'MASTER2' }); const [memberSaving, setMemberSaving] = useState(false); const [memberErr, setMemberErr] = useState(null); const [editingMemberId, setEditingMemberId] = useState(null); const [editMemberForm, setEditMemberForm] = useState({}); // ── Security state ────────────────────────────────────── const [emailForm, setEmailForm] = useState({ newEmail: '', password: '' }); const [emailErr, setEmailErr] = useState(null); const [emailLoading, setEmailLoading] = useState(false); const [passForm, setPassForm] = useState({ current: '', next: '', confirm: '' }); const [passErr, setPassErr] = useState(null); const [passLoading, setPassLoading] = useState(false); const [deleteAccountConfirm, setDeleteAccountConfirm] = useState(false); async function handleChangeEmail() { setEmailErr(null); if (!emailForm.newEmail.trim() || !/\S+@\S+\.\S+/.test(emailForm.newEmail)) { setEmailErr('Digite um e-mail válido.'); return; } if (!emailForm.password) { setEmailErr('Digite sua senha atual para confirmar.'); return; } setEmailLoading(true); try { await updateEmail(emailForm.newEmail.trim(), emailForm.password); setEmailForm({ newEmail: '', password: '' }); showToast('E-mail atualizado com sucesso!'); } catch (err) { setEmailErr(err.message || 'Não foi possível alterar o e-mail. Verifique sua senha.'); } finally { setEmailLoading(false); } } async function handleChangePassword() { setPassErr(null); if (!passForm.current) { setPassErr('Digite sua senha atual.'); return; } if (passForm.next.length < 8) { setPassErr('A nova senha deve ter no mínimo 8 caracteres.'); return; } if (passForm.next !== passForm.confirm) { setPassErr('As senhas não coincidem.'); return; } setPassLoading(true); try { await updatePassword(passForm.current, passForm.next); setPassForm({ current: '', next: '', confirm: '' }); showToast('Senha alterada com sucesso!'); } catch (err) { setPassErr(err.message || 'Senha atual incorreta. Tente novamente.'); } finally { setPassLoading(false); } } const tabs = [ { id: 'perfil', label: 'Perfil', icon: 'User' }, { id: 'seguranca', label: 'Segurança', icon: 'ShieldCheck' }, { id: 'membros', label: 'Membros', icon: 'Users' }, { id: 'categorias', label: 'Categorias', icon: 'Tag' }, { id: 'pagamentos', label: 'Pagamentos', icon: 'CreditCard' }, { id: 'investimentos', label: 'Investimentos', icon: 'TrendingUp' }, { id: 'receitas', label: 'Fontes de Receita', icon: 'DollarSign' }, { id: 'aparencia', label: 'Aparência', icon: 'Sun' }, ]; // ── Section title helper ──────────────────────────────── function SectionTitle({ children }) { return
{children}
; } function Divider() { return
; } // ── Inline list item ──────────────────────────────────── function ListItem({ left, right }) { return (
{left} {right}
); } // ── Delete button ─────────────────────────────────────── function DelBtn({ onClick }) { return ( ); } return (
{toast && setToast(null)} />}
{/* Sidebar */}
{tabs.map(tab => ( ))}
{/* Content */}
{/* ── PERFIL ── */} {activeTab === 'perfil' && ( Meu Perfil {currentMember.isLocalAccount && (
Conta local — os dados são salvos apenas neste dispositivo. O registro de novas famílias via app está sendo implantado no servidor.
)}
{currentMember.name}
{currentMember.role}
{currentMember.email}
setProfileName(e.target.value)} placeholder="Seu nome completo" /> setDiaDiaVal(e.target.value)} />
setNotifyCardDueDaysVal(e.target.value)} /> setNotifyInstallmentDueDaysVal(e.target.value)} />
)} {/* ── MEMBROS ── */} {activeTab === 'membros' && (
Membros da Família
{/* Formulário para novo membro */} {showNewMember && (
Novo Membro
{memberErr && (
{memberErr}
)}
setNewMember(f => ({ ...f, name: e.target.value }))} placeholder="Nome do membro" />
setNewMember(f => ({ ...f, email: e.target.value }))} placeholder="email@exemplo.com" /> {newMember.email.trim() && ( setNewMember(f => ({ ...f, password: e.target.value }))} placeholder="Mínimo 8 caracteres" /> )}
)} {/* Lista de membros */}
{members.map(m => { const isMe = m.id === currentMember.id; const isEditing = editingMemberId === m.id; return (
{m.name}
{m.role} {m.email && <>•{m.email}}
{isMe && Você}
{/* Edição inline */} {isEditing && (
setEditMemberForm(f => ({ ...f, name: e.target.value }))} /> {isMe && ( setEditMemberForm(f => ({ ...f, diaDiaLimit: e.target.value }))} /> )}
)}
); })}
{/* Info sobre login dos membros */}
Acesso dos membros: a credencial é definida somente na criação e depois administrada pelo próprio titular. Um master nunca redefine a senha de outra pessoa.
)} {/* ── CATEGORIAS ── */} {activeTab === 'categorias' && (
Categorias de Despesa
{/* New category form */} {showNewCat && (
Nova Categoria
setNewCat(c => ({ ...c, name: e.target.value }))} placeholder="Ex: Saúde" />
Cor
{PRESET_COLORS.map(c => setNewCat(f => ({ ...f, color: c }))} />)}
Ícone
setNewCat(f => ({ ...f, icon: ic }))} />
)} {/* Category list */} {categories.map(cat => (
{editingCatId === cat.id ? (
setEditCatForm(f => ({ ...f, name: e.target.value }))} />
Cor
{PRESET_COLORS.map(c => setEditCatForm(f => ({ ...f, color: c }))} />)}
Ícone
setEditCatForm(f => ({ ...f, icon: ic }))} />
) : (
{cat.name} { deleteCategory(cat.id); setToast('Categoria removida!'); }} />
)}
))}
)} {/* ── PAGAMENTOS ── */} {activeTab === 'pagamentos' && (
Meios de Pagamento
{/* Formulário de criação */} {showNewPM && (
Novo Meio de Pagamento
setNewPM(f => ({ ...f, name: e.target.value }))} placeholder="Ex: Nubank" /> setNewPM(f => ({ ...f, bank: e.target.value }))} placeholder="Ex: Itaú Visa" /> {newPM.type === 'credit' && ( setNewPM(f => ({ ...f, limit: e.target.value }))} placeholder="0,00" /> )}
{newPM.type === 'credit' && (
setNewPM(f => ({ ...f, closingDay: e.target.value }))} placeholder="Ex: 5" /> setNewPM(f => ({ ...f, dueDay: e.target.value }))} placeholder="Ex: 15" />
)} {/* Cor */}
setNewPM(f => ({ ...f, color: e.target.value }))} style={{ width: 40, height: 38, padding: 2, borderRadius: 8, border: '1px solid var(--border)', cursor: 'pointer', background: 'var(--surface-1)' }} /> { const v = e.target.value; setNewPM(f => ({ ...f, color: v })); }} placeholder="#0D9488" style={{ fontFamily: 'monospace', textTransform: 'uppercase' }} />
)} {/* Grid de cards */}
{paymentMethods.map(pm => { const TYPE_ICON = { credit: 'CreditCard', debit: 'CreditCard', account: 'Building2', pix: 'Zap', cash: 'Banknote' }; const TYPE_LABEL = { credit: 'Crédito', debit: 'Débito', account: 'Conta', pix: 'Pix', cash: 'Dinheiro' }; const member = members.find(m => m.id === pm.memberId); const isEditing = editingPM === pm.id; const color = pm.color || '#0D9488'; return (
{/* Cabeçalho colorido estilo cartão */}
{!pmDeleteConfirm && !isEditing && (
)} {pmDeleteConfirm === pm.id && (
Excluir?
)}
{pm.name}
{TYPE_LABEL[pm.type] || pm.type} {pm.bank && <>·{pm.bank}} {member && <>·{member.name.split(' ')[0]}}
{/* Rodapé do card */}
{pm.limit ? (
Limite
{fmtBRL(pm.limit)}
) :
}
{pm.closingDay &&
Fecha dia {pm.closingDay}
} {pm.dueDay &&
Vence dia {pm.dueDay}
}
{/* Formulário de edição inline */} {isEditing && (
setEditPMForm(f => ({ ...f, name: e.target.value }))} /> setEditPMForm(f => ({ ...f, bank: e.target.value }))} placeholder="Ex: Itaú Visa" /> {editPMForm.type === 'credit' && ( setEditPMForm(f => ({ ...f, limit: e.target.value }))} placeholder="0,00" /> )}
{editPMForm.type === 'credit' && (
setEditPMForm(f => ({ ...f, closingDay: e.target.value }))} placeholder="Ex: 5" /> setEditPMForm(f => ({ ...f, dueDay: e.target.value }))} placeholder="Ex: 15" />
)} {/* Cor na edição */}
setEditPMForm(f => ({ ...f, color: e.target.value }))} style={{ width: 40, height: 38, padding: 2, borderRadius: 8, border: '1px solid var(--border)', cursor: 'pointer', background: 'var(--surface-1)' }} /> setEditPMForm(f => ({ ...f, color: e.target.value }))} placeholder="#0D9488" style={{ fontFamily: 'monospace', textTransform: 'uppercase' }} />
)}
); })}
)} {/* ── INVESTIMENTOS config ── */} {activeTab === 'investimentos' && (
{/* Types */} Tipos de Investimento
{investmentTypes.map((t, i) => (
{t}
))}
setNewInvType(e.target.value)} placeholder="Novo tipo (Ex: Debêntures)" onKeyDown={e => { if (e.key === 'Enter' && newInvType.trim()) { setInvestmentTypes([...investmentTypes, newInvType.trim()]); setNewInvType(''); setToast('Tipo adicionado!'); } }} />
{/* Purposes */}
Finalidades
{showNewPurpose && (
setNewPurpose(f => ({ ...f, label: e.target.value }))} placeholder="Ex: Fundo Casamento" />
Cor
{PRESET_COLORS.map(c => setNewPurpose(f => ({ ...f, color: c }))} />)}
)}
{investmentPurposes.map((p, i) => (
{p.label} { setInvestmentPurposes(investmentPurposes.filter((_, j) => j !== i)); setToast('Finalidade removida!'); }} />
))}
)} {/* ── RECEITAS (fontes) ── */} {activeTab === 'receitas' && ( Fontes de Receita
{incomeSources.map((s, i) => (
{s.label} { setIncomeSources(incomeSources.filter((_, j) => j !== i)); setToast('Fonte removida!'); }} />
))}
setNewSource(e.target.value)} placeholder="Nova fonte (Ex: Pensão)" onKeyDown={e => { if (e.key === 'Enter' && newSource.trim()) { const key = newSource.toLowerCase().replace(/\s+/g, '_').replace(/[^a-z0-9_]/g, ''); setIncomeSources([...incomeSources, { key, label: newSource.trim() }]); setNewSource(''); setToast('Fonte adicionada!'); } }} />
)} {/* ── SEGURANÇA ── */} {activeTab === 'seguranca' && (
{/* Alterar E-mail */}
Alterar E-mail
Atual: {currentMember.email}
{emailErr && (
{emailErr}
)} setEmailForm(f => ({ ...f, newEmail: e.target.value }))} placeholder="novo@email.com" /> setEmailForm(f => ({ ...f, password: e.target.value }))} placeholder="••••••••" />
{/* Alterar Senha */}
Alterar Senha
Use no mínimo 8 caracteres com letras e números
{passErr && (
{passErr}
)} setPassForm(f => ({ ...f, current: e.target.value }))} placeholder="••••••••" /> setPassForm(f => ({ ...f, next: e.target.value }))} placeholder="Mínimo 8 caracteres" /> setPassForm(f => ({ ...f, confirm: e.target.value }))} placeholder="Repita a nova senha" /> {passForm.next.length > 0 && (
{[ { ok: passForm.next.length >= 8, label: '8+ chars' }, { ok: /[A-Z]/.test(passForm.next), label: 'Maiúscula' }, { ok: /[0-9]/.test(passForm.next), label: 'Número' }, { ok: passForm.next === passForm.confirm && passForm.confirm.length > 0, label: 'Coincidem' }, ].map(r => (
{r.label}
))}
)}
{/* Informações LGPD */}
Privacidade & LGPD
Lei 13.709/2018 — Lei Geral de Proteção de Dados
{[ { icon: 'Database', title: 'Dados coletados', text: 'Nome, e-mail, dados financeiros inseridos por você. Nenhum dado bancário real é acessado automaticamente.' }, { icon: 'Lock', title: 'Segurança', text: 'Senhas protegidas com criptografia. Tokens JWT com expiração. Toda comunicação via HTTPS.' }, { icon: 'Eye', title: 'Sem compartilhamento', text: 'Seus dados NÃO são vendidos, compartilhados ou usados para publicidade.' }, { icon: 'Settings', title: 'Seus direitos', text: 'Você pode corrigir seus dados (aba Perfil), exportar via CSV (Relatórios) ou excluir permanentemente sua conta abaixo (LGPD art. 18).' }, ].map(item => (
{item.title}
{item.text}
))}
{/* Exclusão de Conta */}
Excluir Conta
Direito à eliminação de dados — LGPD art. 18, VI
Ao excluir sua conta, todos os seus dados financeiros serão permanentemente removidos. Esta ação não pode ser desfeita.
{!deleteAccountConfirm ? ( ) : (
⚠️ Tem certeza? Esta ação é irreversível.
)}
)} {/* ── APARÊNCIA ── */} {activeTab === 'aparencia' && ( Aparência
Tema visual
Escolha o design system do app
{[ { value: 'light', label: 'Finafamily Claro', swatch: 'linear-gradient(135deg, #EEF2F8, #2167D5)' }, { value: 'dark', label: 'Finafamily Escuro', swatch: 'linear-gradient(135deg, #101827, #9EC8FF)' }, { value: 'nubank-light', label: 'Finafamily Roxo', swatch: 'linear-gradient(135deg, #FFFFFF, #8A05BE)' }, ].map(opt => ( ))}
Dados e Privacidade
)}
); } Object.assign(window, { Relatorios, Notificacoes, Configuracoes });