// Panels: spending overview (donut), AI insight, quick actions, recommendations, transactions, debts
const { useState: useStateP, useMemo: useMemoP } = React;
// ---------- DONUT (SVG) ----------
function Donut({ data, size = 220, thickness = 32 }) {
const total = data.reduce((s, d) => s + d.value, 0) || 1;
const r = size / 2 - thickness / 2;
const cx = size / 2, cy = size / 2;
const C = 2 * Math.PI * r;
let offset = 0;
// shades of green from dark to light to match reference
const colors = ["#0e4a40", "#177a63", "#2a9d84", "#f0b84d", "#f6cd7c", "#e5d9b8", "#c4d6cf", "#d9e6e0", "#f3eedf"];
return (
{data.map((d, i) => {
const len = (d.value / total) * C;
const seg = (
);
offset += len;
return seg;
})}
Top Categoria
{data[0]?.category || "—"}
);
}
// ---------- SPENDING OVERVIEW ----------
function SpendingOverview({ catData, total, period, setPeriod, onGoTo }) {
const colors = ["#0e4a40", "#177a63", "#2a9d84", "#f0b84d", "#f6cd7c", "#e5d9b8", "#c4d6cf", "#d9e6e0", "#f3eedf"];
const top = catData.slice(0, 6);
return (
onGoTo("analise"))} title="Ver Análise completa">
Visão de Gastos
setPeriod(e.target.value)}>
Este Mês
Este Ano
{top.map((c, i) => {
const pct = total ? Math.round((c.value / total) * 100) : 0;
return (
{c.category}
{window.fmtBRLString(c.value)}
{pct}%
);
})}
{top.length === 0 && (
Sem despesas neste período.
)}
);
}
// ---------- AI INSIGHT ----------
function AIInsight({ catData, monthIncome, monthExpense, monthLabel, onGoTo }) {
const [insight, setInsight] = useStateP(null);
const [loading, setLoading] = useStateP(false);
async function generate() {
setLoading(true);
try {
const top3 = catData.slice(0, 3).map(c => `${c.category}: R$ ${c.value.toFixed(2)}`).join(", ");
const prompt = `Você é um consultor financeiro. Em PORTUGUÊS, em no máximo 2 frases curtas, analise estes dados de ${monthLabel}:
Receita: R$ ${monthIncome.toFixed(2)}
Despesa: R$ ${monthExpense.toFixed(2)}
Top 3 categorias: ${top3}
Identifique 1 padrão preocupante ou oportunidade e SUGIRA uma ação concreta. Não use cumprimentos. Comece direto.`;
const res = await window.claude.complete(prompt);
setInsight(res.trim());
} catch (e) {
setInsight("Não consegui gerar agora.");
} finally {
setLoading(false);
}
}
// Auto-generate a default insight based on the data
const defaultInsight = useMemoP(() => {
if (!catData[0]) return "Adicione lançamentos para receber análises.";
const top = catData[0];
const pct = monthExpense ? Math.round((top.value / monthExpense) * 100) : 0;
if (monthExpense > monthIncome) {
const deficit = monthExpense - monthIncome;
return `Seus gastos ultrapassaram a receita em ${window.fmtBRLString(deficit)} neste mês. ${top.category} representa ${pct}% — vale revisar.`;
}
return `${top.category} concentra ${pct}% dos seus gastos do mês. Quer dicas para reduzir?`;
}, [catData, monthIncome, monthExpense]);
return (
onGoTo("insights"))} title="Ver Insights">
{loading ? "Analisando seus números…" : (insight || defaultInsight)}
{loading ? "Pensando…" : (insight ? "Gerar novo insight" : "Ver sugestões")}
);
}
// ---------- QUICK ACTIONS ----------
function QuickActions({ onAdd }) {
const actions = [
{ id: "receita", label: "Nova Receita", icon: , color: "bg-green" },
{ id: "despesa", label: "Novo Gasto", icon: , color: "bg-pink" },
{ id: "divida", label: "Nova Dívida", icon: , color: "bg-yellow" },
{ id: "fatura", label: "Pagar Fatura", icon: , color: "bg-green-soft" },
];
return (
Ações Rápidas
{actions.map(a => (
onAdd(a.id)}>
{a.icon}
{a.label}
))}
);
}
// ---------- AI RECOMMENDATIONS ----------
function Recommendations({ catData, dividas, monthIncome, monthExpense, onGoTo }) {
const recs = useMemoP(() => {
const items = [];
const topCat = catData[0];
if (topCat) {
items.push({
icon: "🍽️",
title: `Reduzir ${topCat.category.replace(/[^\w\sÀ-ÿ]/g, "").trim()}`,
sub: "Definir teto semanal pode economizar até 20%",
type: "card-light",
});
}
if (dividas.length) {
const total = dividas.reduce((s, d) => s + d.saldoDevedor, 0);
items.push({
icon: "💰",
title: `Quitar ${window.fmtBRLString(total).replace(",00","")}`,
sub: `${dividas.length} dívidas pendentes`,
type: "card-dark",
});
}
const subsCat = catData.find(c => c.category.includes("Assinatura"));
if (subsCat) {
items.push({
icon: "📺",
title: "Auditar assinaturas",
sub: `Você gasta ${window.fmtBRLString(subsCat.value).replace(",00","")}/mês`,
type: "card-light",
});
}
if (monthExpense > monthIncome) {
items.push({
icon: "⚠️",
title: "Mês no vermelho",
sub: `Déficit de ${window.fmtBRLString(monthExpense - monthIncome).replace(",00","")}`,
type: "card-alert",
});
} else {
items.push({
icon: "✨",
title: "Reserva de Emergência",
sub: "Meta: 6× despesas mensais",
type: "card-light",
});
}
return items.slice(0, 3);
}, [catData, dividas, monthIncome, monthExpense]);
return (
onGoTo("lancamentos"))} title="Agir no Checklist do mês">
Recomendações IA
Ver todas
{recs.map((r, i) => (
{r.icon}
{r.title}
{r.sub}
))}
);
}
// ---------- RECENT TRANSACTIONS ----------
function RecentTransactions({ txs, onDelete, onViewAll }) {
const recent = [...txs].sort((a, b) => b.date.localeCompare(a.date)).slice(0, 5);
function fmtDate(d) {
const [y, m, dd] = d.split("-");
return `${dd}/${m}/${y.slice(2)}`;
}
return (
Lançamentos Recentes
Ver todos
{recent.map(t => {
const isIncome = t.type === "Receita";
return (
{(t.category || "•").split(" ")[0]}
{t.description}
{fmtDate(t.date)} · {t.account}
{isIncome ? "+" : "-"}{window.fmtBRLString(t.amount).replace("R$ ", "R$ ").replace("-","")}
onDelete(t.id)} title="Excluir">
);
})}
{recent.length === 0 &&
Nenhum lançamento. Adicione um na barra de ações.
}
);
}
Object.assign(window, { SpendingOverview, AIInsight, QuickActions, Recommendations, RecentTransactions, Donut, PlanoIndependencia });
// ---------- PLANO 70 / 10 / 10 / 10 (Jim Rohn) ----------
function PlanoIndependencia({ plano, monthLabel, onGoToLancamentos }) {
const { income, buckets, gastos } = plano;
function money(v) { return window.fmtBRLString(Math.max(0, v)).replace(",00", ""); }
return (
Independência Financeira · Jim Rohn
Plano 70 · 10 · 10 · 10
Receita base · {monthLabel}
{income <= 0 ? (
Sem receita lançada neste mês. As metas do plano aparecem assim que houver receita.
) : (
<>
{buckets.map(b => {
const fill = Math.min(b.ratio, 1) * 100;
const isGastos = b.key === "gastos";
return (
{b.icon}
{Math.round(b.pct * 100)}%
{b.label}
{isGastos ? "máximo de gastos" : b.sub}
{money(b.used)}
de {money(b.target)}
{b.over
? `${money(b.used - b.target)} acima`
: `falta ${money(b.remaining)}`}
);
})}
{/* Lever analysis — fitting gastos into 70% */}
{gastos.over ? (
Gastos {money(gastos.overBy)} acima do teto de 70%. Para caber, corte gastos marcados como redutíveis — você tem {money(gastos.redutivelPendente)} ainda não pagos que podem ser reduzidos.
) : (
Gastos dentro do teto de 70%, com folga de {money(gastos.target - gastos.used)} . Mantenha o ritmo.
)}
Pago{money(gastos.gastosPago)}
A pagar{money(gastos.gastosPendente)}
Redutível{money(gastos.gastosRedutivel)}
Revisar lançamentos →
>
)}
);
}