/* =========================================================== Forja 3D — Visão Geral =========================================================== */ function monthlySeries(items, dateKey, valFn) { const now = new Date(); const out = []; for (let i = 5; i >= 0; i--) { const d = new Date(now.getFullYear(), now.getMonth() - i, 1); const y = d.getFullYear(), m = d.getMonth() + 1; let sum = 0; items.forEach((it) => { const [iy, im] = (it[dateKey] || '').split('-').map(Number); if (iy === y && im === m) sum += valFn(it); }); out.push(sum); } return out; } function Overview({ go }) { const s = useStore(); const now = new Date(); const salesMonth = s.sales.filter((x) => isThisMonth(x.date, now)); const expMonth = s.expenses.filter((x) => isThisMonth(x.date, now)); const faturamento = salesMonth.reduce((a, x) => a + x.price * x.qty, 0); const lucro = salesMonth.reduce((a, x) => a + (x.price - x.cost) * x.qty, 0); const gastos = expMonth.reduce((a, x) => a + x.amount, 0); const faltaPagar = s.debts.reduce((a, d) => a + (d.total - d.paid), 0); const totalDebt = s.debts.reduce((a, d) => a + d.total, 0); const paidDebt = s.debts.reduce((a, d) => a + d.paid, 0); const sparkFat = monthlySeries(s.sales, 'date', (x) => x.price * x.qty); const sparkLucro = monthlySeries(s.sales, 'date', (x) => (x.price - x.cost) * x.qty); const sparkGasto = monthlySeries(s.expenses, 'date', (x) => x.amount); // estoque const estoqueG = s.filaments.reduce((a, f) => a + f.remaining, 0); const baixoEstoque = s.filaments.filter((f) => f.remaining < 200); // impressões const okPrints = s.prints.filter((p) => p.status === 'sucesso').length; const taxaSucesso = s.prints.length ? (okPrints / s.prints.length) * 100 : 0; const ticket = salesMonth.length ? faturamento / salesMonth.reduce((a, x) => a + x.qty, 0) : 0; // movimentações merged const mov = [ ...s.sales.map((x) => ({ ...x, _t: 'in', _label: x.product, _sub: `${fmt.date(x.date)} · ${x.channel}`, _v: x.price * x.qty })), ...s.expenses.map((x) => ({ ...x, _t: 'out', _label: x.description, _sub: `${fmt.date(x.date)} · ${x.category}`, _v: x.amount })), ].sort((a, b) => (b.date > a.date ? 1 : -1)).slice(0, 6); return (
go('vendas')}>Registrar venda} />
{/* Dívidas */}
Quitação de dívidas
{s.debts.length === 0 &&
Nenhuma dívida cadastrada. 🎉
} {s.debts.map((d) => { const pct = (d.paid / d.total) * 100; return (
{d.name}
{fmt.brl0(d.paid)} de {fmt.brl0(d.total)} · faltam {fmt.brl0(d.total - d.paid)}
= 100 ? 'var(--green)' : 'var(--orange)' }}>{fmt.pct(pct)}
); })}
{/* Movimentações */}
Movimentações
{mov.map((m, i) => (
{m._t === 'in' ? '+' : '−'}
{m._label}
{m._sub}
{m._t === 'in' ? '+' : '−'}{fmt.brl0(m._v)}
))} {mov.length === 0 &&
Sem movimentações ainda.
}
{/* insights */}
Estoque de filamento
{(estoqueG / 1000).toFixed(2).replace('.', ',')} kg
{baixoEstoque.length ? `⚠ ${baixoEstoque.length} filamento(s) com estoque baixo` : 'Estoque saudável'}
Taxa de sucesso
{fmt.pct(taxaSucesso)}
{okPrints}/{s.prints.length} impressões OK
Ticket médio
{fmt.brl0(ticket)}
{salesMonth.length} venda(s) no mês
); } window.Overview = Overview;