// 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 });