// screen-transactions.jsx — Transaksi list + filters
function ScreenTransactions({ theme, accent, scenario, onOpenSheet, auth }) {
const [realTx, setRealTx] = React.useState(null);
const [currentMonth] = React.useState(new Date());
const familyId = auth && auth.family && auth.family.id;
React.useEffect(function() {
if (!familyId || !window.sbGetTransactions) return;
sbGetTransactions(familyId, currentMonth).then(function(r) {
if (!r.error) setRealTx(r.data||[]);
});
}, [familyId]);
function norm(t) {
// Map category name → CATEGORIES key
var rawCat = t.category ? (t.category.name||'').toLowerCase().replace(/\s+/g,'_') : '';
var kind = t.type||t.kind||'expense';
var catKey = rawCat;
if (rawCat && !CATEGORIES[catKey]) {
catKey = rawCat + '_ex';
if (!CATEGORIES[catKey]) catKey = rawCat + '_in';
}
if (!catKey || !CATEGORIES[catKey]) catKey = kind==='income' ? 'lainnya_in' : 'lainnya_ex';
return {id:t.id, kind:kind, amount:t.amount,
cat:catKey, catLabel:(t.category&&t.category.name)||catKey,
by:t.user_id||t.by, note:t.note, date:t.date,
account_id:t.account_id,
categoryObj:t.category, profileObj:t.profile};
}
const [filter, setFilter] = React.useState('all'); // all | income | expense
const [memberFilter, setMemberFilter] = React.useState('all');
const isAuth = !!(auth && auth.family);
const allTx = isAuth ? (realTx || []).map(norm) : scenario.transactions;
let txs = allTx;
if (filter !== 'all') txs = txs.filter(function(t){ return t.kind===filter; });
if (memberFilter !== 'all') txs = txs.filter(function(t){ return t.by===memberFilter; });
// group by relative day
const groups = {};
txs.forEach((t) => {
const key = fmtRelDay(t.date);
(groups[key] = groups[key] || []).push(t);
});
const totals = {
income: allTx.filter(function(t){return t.kind==='income';}).reduce(function(a,t){return a+t.amount;},0),
expense: allTx.filter(function(t){return t.kind==='expense';}).reduce(function(a,t){return a+t.amount;},0),
};
return (
{/* Header */}
Transaksi
{allTx.length} entri · Bulan ini
{/* Income / Expense totals */}
{/* Filter tabs */}
{[
{ id: 'all', label: 'Semua' },
{ id: 'income', label: 'Pemasukan' },
{ id: 'expense', label: 'Pengeluaran' },
].map((t) => (
setFilter(t.id)} theme={theme}>{t.label}
))}
{/* Member filter chips */}
{(isAuth
? (auth.profile ? [{ id: auth.profile.id, name: auth.profile.full_name||'Kamu', short: (auth.profile.full_name||'').split(' ')[0], color: '#10b981' }] : [])
: scenario.members
).map((m) => {
const on = memberFilter === m.id;
return (
);
})}
{/* Grouped transactions */}
{Object.entries(groups).map(([day, items]) => {
const dayTotal = items.reduce((a, t) => a + (t.kind === 'expense' ? -t.amount : t.amount), 0);
return (
{day}
{fmtRp(dayTotal, { sign: true, short: true })}
{items.map((tx, i) => (
))}
);
})}
{Object.keys(groups).length === 0 && (
)}
);
}
function iconBtn2(theme) {
return {
appearance: 'none', border: 0, cursor: 'pointer',
width: 38, height: 38, borderRadius: 12,
background: theme.surface,
display: 'flex', alignItems: 'center', justifyContent: 'center',
boxShadow: theme.shadow1,
border: `0.5px solid ${theme.border}`,
};
}
function TxSummaryPill({ theme, kind, label, amount }) {
const isIncome = kind === 'income';
return (
{label}
{fmtRp(amount, { short: true })}
);
}
Object.assign(window, { ScreenTransactions, iconBtn2, TxSummaryPill });