// Receitas and Despesas pages - editable monthly grids matching the spreadsheet structure
const { useState: useStateG, useMemo: useMemoG } = React;
// ---------- YEAR SWITCH ----------
function YearSwitch({ year, onChange, years = [2025, 2026, 2027] }) {
return (
{years.map(y => (
))}
);
}
// ---------- EDITABLE CELL ----------
function EditCell({ value, onChange, dim }) {
const [editing, setEditing] = useStateG(false);
const [draft, setDraft] = useStateG("");
function start() {
setDraft(value > 0 ? String(value).replace(".", ",") : "");
setEditing(true);
}
function commit() {
const parsed = window.parseValor(draft);
onChange(isNaN(parsed) ? 0 : parsed);
setEditing(false);
}
function onKey(e) {
if (e.key === "Enter") { e.preventDefault(); commit(); e.target.blur(); }
else if (e.key === "Escape") { setEditing(false); }
else if (e.key === "Tab") { commit(); /* tab default behavior moves focus */ }
}
if (editing) {
return (
setDraft(e.target.value)}
onBlur={commit}
onKeyDown={onKey}
/>
);
}
return (
);
}
// ---------- GRID TABLE ----------
function GridTable({ headers, rows, summary, hint }) {
return (
{headers.map((h, i) => | {h} | )}
{rows}
{summary && {summary}}
{hint &&
{hint}
}
);
}
// ---------- RECEITAS PAGE ----------
function ReceitasPage({ aggregates, txs, year, onUpdate, onChangeYear }) {
const data = (aggregates[year] || aggregates[2026] || { receitas: {}, despesas: { cards: {}, fixos: {} } });
const sources = Object.keys(data.receitas);
const [newSource, setNewSource] = useStateG("");
// Add detailed transactions per source per month for context
const txBySourceMonth = useMemoG(() => {
const map = {};
for (const t of txs) {
if (t.type !== "Receita" || !t.date.startsWith(String(year))) continue;
const m = Number(t.date.slice(5, 7)) - 1;
const key = `${t.description.split(" — ")[0]}|${m}`;
map[key] = (map[key] || 0) + Number(t.amount || 0);
}
return map;
}, [txs, year]);
function setCell(src, monthIdx, val) {
onUpdate(prev => {
const aggs = window.ensureYear(prev.aggregates, year);
const arr = aggs[year].receitas[src].slice();
arr[monthIdx] = val;
return {
...prev,
aggregates: { ...aggs, [year]: { ...aggs[year], receitas: { ...aggs[year].receitas, [src]: arr } } }
};
});
}
function addSource() {
const name = newSource.trim();
if (!name || data.receitas[name]) return;
onUpdate(prev => {
const aggs = window.ensureYear(prev.aggregates, year);
return {
...prev,
aggregates: { ...aggs, [year]: { ...aggs[year], receitas: { ...aggs[year].receitas, [name]: new Array(12).fill(0) } } }
};
});
setNewSource("");
}
function delSource(src) {
if (!confirm(`Remover linha "${src}"?`)) return;
onUpdate(prev => {
const aggs = window.ensureYear(prev.aggregates, year);
const { [src]: _, ...rest } = aggs[year].receitas;
return { ...prev, aggregates: { ...aggs, [year]: { ...aggs[year], receitas: rest } } };
});
}
const monthTotals = new Array(12).fill(0);
const sourceTotals = {};
for (const src of sources) {
sourceTotals[src] = 0;
for (let m = 0; m < 12; m++) {
const v = data.receitas[src][m] || 0;
monthTotals[m] += v;
sourceTotals[src] += v;
}
}
const grandTotal = monthTotals.reduce((s, v) => s + v, 0);
const headers = ["Origem", ...window.MESES, "Total"];
const rows = sources.map(src => (
|
{src}
|
{window.MESES.map((_, m) => (
setCell(src, m, v)}/>
{txBySourceMonth[`${src}|${m}`] > 0 && (
+ R$ {txBySourceMonth[`${src}|${m}`].toFixed(0)}
)}
|
))}
{window.fmtBRLString(sourceTotals[src]).replace("R$ ", "")} |
));
const summary = (
| Total |
{monthTotals.map((v, i) => (
{v > 0 ? window.fmtBRLString(v).replace("R$ ", "") : "—"} |
))}
{window.fmtBRLString(grandTotal).replace("R$ ", "")} |
);
return (
💰 Receitas
Clique numa célula para editar
);
}
// ---------- DESPESAS PAGE ----------
function DespesasPage({ aggregates, year, onUpdate, onChangeYear }) {
const data = (aggregates[year] || aggregates[2026] || { receitas: {}, despesas: { cards: {}, fixos: {} } });
const cards = Object.keys(data.despesas.cards);
const fixos = Object.keys(data.despesas.fixos);
const [newCard, setNewCard] = useStateG("");
const [newFixo, setNewFixo] = useStateG("");
function setCardCell(card, pessoa, m, v) {
onUpdate(prev => {
const aggs = window.ensureYear(prev.aggregates, year);
const arr = aggs[year].despesas.cards[card][pessoa].slice();
arr[m] = v;
return {
...prev,
aggregates: { ...aggs, [year]: { ...aggs[year], despesas: { ...aggs[year].despesas, cards: { ...aggs[year].despesas.cards, [card]: { ...aggs[year].despesas.cards[card], [pessoa]: arr } } } } }
};
});
}
function setFixoCell(name, m, v) {
onUpdate(prev => {
const aggs = window.ensureYear(prev.aggregates, year);
const arr = aggs[year].despesas.fixos[name].slice();
arr[m] = v;
return {
...prev,
aggregates: { ...aggs, [year]: { ...aggs[year], despesas: { ...aggs[year].despesas, fixos: { ...aggs[year].despesas.fixos, [name]: arr } } } }
};
});
}
function addCard() {
const name = newCard.trim();
if (!name || data.despesas.cards[name]) return;
onUpdate(prev => {
const aggs = window.ensureYear(prev.aggregates, year);
return {
...prev,
aggregates: { ...aggs, [year]: { ...aggs[year], despesas: { ...aggs[year].despesas, cards: { ...aggs[year].despesas.cards, [name]: { Lucas: new Array(12).fill(0), Larissa: new Array(12).fill(0) } } } } }
};
});
setNewCard("");
}
function delCard(card) {
if (!confirm(`Remover cartão "${card}"?`)) return;
onUpdate(prev => {
const aggs = window.ensureYear(prev.aggregates, year);
const { [card]: _, ...rest } = aggs[year].despesas.cards;
return { ...prev, aggregates: { ...aggs, [year]: { ...aggs[year], despesas: { ...aggs[year].despesas, cards: rest } } } };
});
}
function addFixo() {
const name = newFixo.trim();
if (!name || data.despesas.fixos[name]) return;
onUpdate(prev => {
const aggs = window.ensureYear(prev.aggregates, year);
return {
...prev,
aggregates: { ...aggs, [year]: { ...aggs[year], despesas: { ...aggs[year].despesas, fixos: { ...aggs[year].despesas.fixos, [name]: new Array(12).fill(0) } } } }
};
});
setNewFixo("");
}
function delFixo(name) {
if (!confirm(`Remover linha "${name}"?`)) return;
onUpdate(prev => {
const aggs = window.ensureYear(prev.aggregates, year);
const { [name]: _, ...rest } = aggs[year].despesas.fixos;
return { ...prev, aggregates: { ...aggs, [year]: { ...aggs[year], despesas: { ...aggs[year].despesas, fixos: rest } } } };
});
}
// monthly grand totals
const monthTotals = new Array(12).fill(0);
for (const card of cards) {
for (const pessoa of Object.keys(data.despesas.cards[card])) {
for (let m = 0; m < 12; m++) monthTotals[m] += data.despesas.cards[card][pessoa][m] || 0;
}
}
for (const name of fixos) {
for (let m = 0; m < 12; m++) monthTotals[m] += data.despesas.fixos[name][m] || 0;
}
const grandTotal = monthTotals.reduce((s, v) => s + v, 0);
const headers = ["", ...window.MESES, "Total"];
// Build rows: per card, Lucas/Larissa/Total; then a section of fixos
const cardRows = [];
for (const card of cards) {
const pessoas = Object.keys(data.despesas.cards[card]);
const cardTotalByMonth = new Array(12).fill(0);
let cardTotalAll = 0;
cardRows.push(
|
💳 {card}
|
|
);
for (const pessoa of pessoas) {
let total = 0;
cardRows.push(
| {pessoa} |
{window.MESES.map((_, m) => {
const v = data.despesas.cards[card][pessoa][m] || 0;
total += v;
cardTotalByMonth[m] += v;
return (
setCardCell(card, pessoa, m, nv)}/>
|
);
})}
{total > 0 ? window.fmtBRLString(total).replace("R$ ", "") : "—"} |
);
cardTotalAll += total;
}
cardRows.push(
| Total {card} |
{cardTotalByMonth.map((v, m) => (
{v > 0 ? window.fmtBRLString(v).replace("R$ ", "") : "—"} |
))}
{window.fmtBRLString(cardTotalAll).replace("R$ ", "")} |
);
}
// Fixos rows
const fixoRows = [];
if (fixos.length > 0) {
fixoRows.push(
| 📌 Despesas Fixas |
);
for (const name of fixos) {
let total = 0;
fixoRows.push(
|
{name}
|
{window.MESES.map((_, m) => {
const v = data.despesas.fixos[name][m] || 0;
total += v;
return (
setFixoCell(name, m, nv)}/>
|
);
})}
{total > 0 ? window.fmtBRLString(total).replace("R$ ", "") : "—"} |
);
}
}
const summary = (
| Total Geral |
{monthTotals.map((v, i) => (
{v > 0 ? window.fmtBRLString(v).replace("R$ ", "") : "—"} |
))}
{window.fmtBRLString(grandTotal).replace("R$ ", "")} |
);
return (
💸 Despesas
Clique numa célula para editar
Despesas {year}
Total do ano: {window.fmtBRLString(grandTotal)}
);
}
Object.assign(window, { ReceitasPage, DespesasPage });