// Goals screen — list + detail with steps generated by AI const GOAL_CAT_COLOR = { financeiro: '#5EE3A8', // mint saude: '#FF7A6B', // coral estudo: '#6FB8FF', // sky pessoal: '#FFB547', // amber viagem: '#FF8FB1', // rosa outros: '#9B98A8', // cinza neutro }; const getGoalCatColor = (cat) => GOAL_CAT_COLOR[cat] || '#6FB8FF'; const GoalsScreen = ({ state, setState, navigate, showToast }) => { const { goals } = state; const [selected, setSelected] = React.useState(null); const [showNew, setShowNew] = React.useState(false); if (selected) { return g.id === selected)} onBack={() => setSelected(null)} onToggleStep={(stepId) => { setState(s => ({ ...s, goals: s.goals.map(g => { if (g.id !== selected) return g; const etapas = (g.etapas || []).map(e => e.id === stepId ? { ...e, done: !e.done } : e); // Auto-calcula progresso baseado nas etapas (se houver pelo menos 1) const newProgress = etapas.length > 0 ? Math.round((etapas.filter(e => e.done).length / etapas.length) * 100) : g.progress; return { ...g, etapas, progress: newProgress }; }) })); showToast('Etapa atualizada'); }} onUpdateProgress={(patch) => { setState(s => ({ ...s, goals: s.goals.map(g => g.id !== selected ? g : { ...g, ...patch }) })); showToast('Meta atualizada'); }} onAddStep={(title) => { setState(s => ({ ...s, goals: s.goals.map(g => g.id !== selected ? g : { ...g, etapas: [...(g.etapas || []), { id: 's' + Date.now(), title, done: false }], }) })); showToast('Etapa adicionada'); }} onDelete={() => { if (window.confirm && !window.confirm('Excluir esta meta?')) return; setState(s => ({ ...s, goals: s.goals.filter(g => g.id !== selected) })); setSelected(null); showToast('Meta excluída'); }} />; } return (
navigate('dashboard')} right={ } />
{goals.map(g => setSelected(g.id)} />)}
{[ { title: 'Economizar R$ 5k para reserva', icon: 'wallet', color: CDV.mint }, { title: 'Ler 12 livros até dezembro', icon: 'book', color: CDV.amber }, { title: 'Treinar 4x por semana', icon: 'dumbbell', color: CDV.coral }, ].map((s, i) => (
{s.title}
))}
setShowNew(false)} title="Nova meta"> { setState(s => ({ ...s, goals: [...s.goals, { id: 'g' + Date.now(), title: g.title, category: g.cat, deadline: g.deadline, progress: 0, etapas: [], }] })); setShowNew(false); showToast('Meta criada, IA gerando etapas...'); }} />
); }; const StatBlock = ({ label, value, color }) => { const c = color || CDV.brand; return (
{/* Glow blob */}
{/* Barra lateral */}
{value}
{label}
); }; const GoalCard = ({ goal, onClick }) => { const catColor = getGoalCatColor(goal.category); const stepsDone = goal.etapas.filter(e => e.done).length; return (
{goal.category}
{goal.title}
Prazo: {goal.deadline}
{goal.progress}%
{stepsDone}/{goal.etapas.length} etapas
{goal.savedReais && (
R$ {goal.savedReais.toLocaleString('pt-BR')} {' '}/ R$ {goal.targetReais.toLocaleString('pt-BR')}
)}
); }; const GoalDetail = ({ goal, onBack, onToggleStep, onUpdateProgress, onAddStep, onDelete }) => { const catColor = getGoalCatColor(goal.category); const etapas = goal.etapas || []; const stepsDone = etapas.filter(e => e.done).length; const [openSheet, setOpenSheet] = React.useState(null); // 'progress' | 'money' | 'step' | null const isFinancial = goal.category === 'financeiro' || typeof goal.targetReais === 'number'; return (
} />
Progresso
{goal.progress || 0}%
{goal.deadline &&
Prazo: {goal.deadline}
}
{(goal.savedReais !== undefined || goal.targetReais !== undefined) && (
Acumulado
R$ {Number(goal.savedReais || 0).toLocaleString('pt-BR')} / R$ {Number(goal.targetReais || 0).toLocaleString('pt-BR')}
)} {/* Botões de atualização rápida */}
{isFinancial && ( )}
{/* AI insight */}
Análise da IA
No ritmo atual, você atinge essa meta 2 semanas antes do prazo. Considere aumentar a meta em 8% para um desafio maior?
setOpenSheet('step')} />
{etapas.length === 0 && (
Quebre essa meta em passos menores. Cada etapa concluída atualiza o % automaticamente.
)} {etapas.map((e, i) => (
onToggleStep(e.id)} style={{ padding: '12px 14px', borderRadius: 14, background: CDV.surface, border: `1px solid ${CDV.stroke}`, display: 'flex', alignItems: 'center', gap: 12, cursor: 'pointer', }}>
{e.done ? : i + 1}
{e.title}
{e.done &&
+50 XP
}
))}
{/* Sheet: ajustar progresso % */} setOpenSheet(null)} title="Atualizar progresso"> { onUpdateProgress && onUpdateProgress({ progress: p }); setOpenSheet(null); }} /> {/* Sheet: lançar valor financeiro */} setOpenSheet(null)} title="Lançar valor acumulado"> { const newProgress = targetReais > 0 ? Math.min(100, Math.round((savedReais / targetReais) * 100)) : goal.progress; onUpdateProgress && onUpdateProgress({ savedReais, targetReais, progress: newProgress }); setOpenSheet(null); }} /> {/* Sheet: adicionar etapa */} setOpenSheet(null)} title="Nova etapa"> { onAddStep && onAddStep(title); setOpenSheet(null); }} />
); }; // Editor de progresso (slider 0-100) const ProgressEditor = ({ initial, color, onSubmit }) => { const [val, setVal] = React.useState(initial); return (
Progresso
{val}%
setVal(Number(e.target.value))} style={{ width: '100%', marginBottom: 22, accentColor: color }} />
{[0, 25, 50, 75, 100].map(p => ( ))}
); }; // Editor financeiro (saved + target em R$) const MoneyEditor = ({ saved, target, color, onSubmit }) => { const [savedVal, setSavedVal] = React.useState(saved); const [targetVal, setTargetVal] = React.useState(target); const canSave = targetVal > 0 && savedVal >= 0; return (
Já acumulei (R$)
setSavedVal(Number(e.target.value) || 0)} autoFocus style={{ width: '100%', height: 54, borderRadius: 14, border: `1px solid ${CDV.stroke}`, background: CDV.surfaceHi, color: CDV.text, padding: '0 16px', fontSize: 18, fontWeight: 700, outline: 'none', fontFamily: 'inherit', marginBottom: 14, }} />
Objetivo total (R$)
setTargetVal(Number(e.target.value) || 0)} style={{ width: '100%', height: 54, borderRadius: 14, border: `1px solid ${CDV.stroke}`, background: CDV.surfaceHi, color: CDV.text, padding: '0 16px', fontSize: 18, fontWeight: 700, outline: 'none', fontFamily: 'inherit', marginBottom: 18, }} /> {targetVal > 0 && (
{Math.min(100, Math.round((savedVal / targetVal) * 100))}% concluído
)}
); }; // Editor de nova etapa const StepEditor = ({ color, onSubmit }) => { const [title, setTitle] = React.useState(''); return (
setTitle(e.target.value)} autoFocus placeholder="Ex: Juntar R$ 500 este mês" onKeyDown={e => { if (e.key === 'Enter' && title.trim()) onSubmit(title.trim()); }} style={{ width: '100%', height: 54, borderRadius: 16, border: `1px solid ${CDV.stroke}`, background: CDV.surfaceHi, color: CDV.text, padding: '0 18px', fontSize: 15, outline: 'none', fontFamily: 'inherit', marginBottom: 16, }} />
); }; const NewGoalForm = ({ onSubmit, onDone }) => { const [title, setTitle] = React.useState(''); const [cat, setCat] = React.useState('financeiro'); const [deadline, setDeadline] = React.useState('6 meses'); const canSave = title.trim().length > 0; const submit = () => { if (!canSave) return; if (typeof onSubmit === 'function') onSubmit({ title: title.trim(), cat, deadline }); else if (typeof onDone === 'function') onDone(); }; return (
setTitle(e.target.value)} placeholder="Ex: Comprar minha primeira moto" style={{ width: '100%', height: 54, borderRadius: 16, border: `1px solid ${CDV.stroke}`, background: CDV.surfaceHi, color: CDV.text, padding: '0 18px', fontSize: 15, outline: 'none', fontFamily: 'inherit', }} />
A IA vai dividir essa meta em etapas práticas
Categoria
{[ { id: 'financeiro', c: '#5EE3A8', label: 'Financeiro' }, { id: 'saude', c: '#FF7A6B', label: 'Saúde' }, { id: 'estudo', c: '#6FB8FF', label: 'Estudo' }, { id: 'pessoal', c: '#FFB547', label: 'Pessoal' }, { id: 'viagem', c: '#FF8FB1', label: 'Viagem' }, { id: 'outros', c: '#9B98A8', label: 'Outros' }, ].map(c => ( ))}
Prazo
{['1 mês', '3 meses', '6 meses', '1 ano'].map(d => ( ))}
); }; Object.assign(window, { GoalsScreen });