/* ============================================================================
MOWDEW — Homeowner screens, part 2
h_ledger (payment STATEMENT · distinct artifact: year control, filters,
ledger-style entries) · h_summary (monthly recap)
========================================================================= */
/* ============================== PAYMENT LEDGER / STATEMENT ============= */
function HomeLedger({ go, openFab }) {
const [q, setQ] = React.useState('');
const [filter, setFilter] = React.useState('all'); // all | paid | due
const [year, setYear] = React.useState(2026);
const paidYTD = LEDGER.filter(r => r.status === 'paid').reduce((s, r) => s + r.cents, 0);
const due = LEDGER.filter(r => r.status === 'unpaid').reduce((s, r) => s + r.cents, 0);
const billed = paidYTD + due;
const paidCount = LEDGER.filter(r => r.status === 'paid').length;
const paidVisits = LEDGER.filter(r => r.status === 'paid').reduce((s, r) => s + r.visits, 0);
const counts = { all: LEDGER.length, paid: paidCount, due: LEDGER.length - paidCount };
const rows = LEDGER
.filter(r => filter === 'all' || (filter === 'paid' ? r.status === 'paid' : r.status === 'unpaid'))
.filter(r => r.month.toLowerCase().includes(q.toLowerCase()));
const YearBtn = ({ dir, label }) => (
);
return (
}>
{/* statement header + year control */}
PAYMENT STATEMENT
Your ledger
{year}
{/* statement summary band — distinct dark artifact */}
CLEARED · {year}
{fmtMoney(paidYTD)}
{paidCount} months · {paidVisits} visits
due && go('h_inbox')} style={{ flex: 1, paddingLeft: 16, borderLeft: '1px solid rgba(255,255,255,.16)', cursor: due ? 'pointer' : 'default' }}>
STILL DUE
{fmtMoney(due)}
{counts.due} invoice{counts.due === 1 ? '' : 's'} open
{/* collected vs billed */}
{Math.round((paidYTD / billed) * 100)}% CLEAREDBILLED {fmtMoney(billed)}
{/* controls: filter + search */}
{[['all', 'All'], ['paid', 'Cleared'], ['due', 'Due']].map(([id, lb]) => {
const on = filter === id;
return (
);
})}
setQ(e.target.value)} className="input" placeholder="Find a month — e.g. March" style={{ paddingLeft: 42 }} />
{/* ledger entries */}
{rows.map(r => {
const unpaid = r.status === 'unpaid';
const [mo, yr] = r.month.split(' ');
return (
go(unpaid ? 'h_inbox' : 'h_summary', { id: r.id })} className="card" style={{ padding: '12px 13px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 12 }}>
{/* date column */}
{r.visits} visits
{unpaid ? Due : Cleared}
{unpaid
? {r.invoice}
: <>Paid {r.paidOn} · {RAIL[r.rail].label}>}
{unpaid ? 'UNPAID' : 'CLEARED'}
);
})}
{rows.length === 0 && (
{q ? `No months match "${q}".` : 'Nothing here for this filter.'}
)}
Cleared months lock — Mowdew won't let you pay one twice.
);
}
/* ============================== MONTHLY SUMMARY ======================= */
function HomeSummary({ go, params }) {
const r = LEDGER.find(x => x.id === params.id) || LEDGER[1];
const visitDates = ['Apr 28', 'Apr 21', 'Apr 14', 'Apr 7'];
return (
}>
go('h_ledger')} />
{/* hero recap card */}
Month, on the record
{r.month.split(' ')[0]}: {r.visits} visits, {fmtMoney(r.cents).replace('.00', '')}, all paid.
{[['Visits', r.visits], ['Total', fmtMoney(r.cents).replace('.00', '')], ['Avg', fmtMoney(Math.round(r.cents / r.visits)).replace('.00', '')]].map(([k, v], i) => (
))}
{/* paid via */}
Paid in full
{r.paidOn} · {r.invoice}
{RAIL[r.rail].label}
{/* visit list */}
The {r.visits} verified visits
{visitDates.slice(0, r.visits).map((d, i) => (
go('h_visit', { id: 'v2' })} style={{ display: 'flex', alignItems: 'center', gap: 11, padding: '11px 14px', borderTop: i ? '1px solid var(--ink-100)' : 'none', cursor: 'pointer' }}>
Pool service
{d} · Verified
))}
);
}
Object.assign(window, { HomeLedger, HomeSummary });