// Hotel Walks — Invoice dashboard. // Listing (sending) vs Accepts (receiving) rows. No-show toggle ONLY appears // on receiving lines. Period switcher, status filter, totals. (function() { const { useState, useMemo } = React; const { Caps, Btn, Pill, Toggle, Hr, money } = window.UI; const { INVOICE_ROWS, INVOICE_PERIOD, HOTEL_BY_ID, ME } = window.HW_DATA; function Invoices() { const [rows, setRows] = useState(INVOICE_ROWS); const [filter, setFilter] = useState("all"); // all | listing | accepts | open const filtered = useMemo(() => { if (filter === "all") return rows; if (filter === "listing") return rows.filter(r => r.side === "Listing"); if (filter === "accepts") return rows.filter(r => r.side === "Accepts"); if (filter === "open") return rows.filter(r => r.status === "open"); return rows; }, [rows, filter]); const totals = useMemo(() => { let listed = 0, accepted = 0, fees = 0, commission = 0; rows.forEach(r => { const due = r.fee + (r.noShow ? 0 : r.commission); // no-show waives commission, not the $4 fee if (r.side === "Listing") listed += due; else accepted += due; fees += r.fee; if (!r.noShow) commission += r.commission; }); return { listed, accepted, fees, commission, gross: listed + accepted }; }, [rows]); const toggleNoShow = (id) => { setRows(prev => prev.map(r => r.id === id ? { ...r, noShow: !r.noShow } : r)); }; return (
Invoices · period {INVOICE_PERIOD.id}
{INVOICE_PERIOD.label}

$4 + 10% per side. No-shows can only be marked on the receiving line — sending hotels cannot dispute attendance.

Export CSV Settle period
{/* Stat strip — content inset to page padding so cells align with the head */}
{/* Filters */}
{[ ["all", "All"], ["listing", "Listing (sent)"], ["accepts", "Accepts (received)"], ["open", "Open only"], ].map(([k, l]) => ( ))}
{filtered.length} rows
{/* Table */}
{["Date","Invoice","Side","Guest","Partner","Rate","Fee","Commission","Status / No-show"].map((h, i) => (
{h}
))}
{filtered.map(r => { const partner = HOTEL_BY_ID[r.to || r.from]; const isAccepts = r.side === "Accepts"; const commission = r.noShow ? 0 : r.commission; return (
{r.date}
{r.id.replace("INV-","")}
{r.side}
{r.guest}
{r.serial}
{partner.name}
{isAccepts ? "from" : "to"} · {partner.area}
{money(r.rate)}
{money(r.fee)}
{money(commission)}
{r.status === "paid" ? Paid : Open} {isAccepts && ( toggleNoShow(r.id)} label={{r.noShow ? "No-show" : "Mark no-show"}} /> )}
); })} {/* Footer totals row */}
Period gross
{money(totals.fees)}
{money(totals.commission)}
{money(totals.gross)}
); } function StatCell({ label, value, positive, idx = 0, count = 1 }) { const isFirst = idx === 0; const isLast = idx === count - 1; return (
{label}
{value}
); } const responsiveCSS = ` @media (max-width: 1100px) { .invoice-stats { grid-template-columns: 1fr 1fr !important; } .invoice-stats > div:nth-child(2) { border-right: none !important; } .invoice-stats > div:nth-child(n+3) { border-top: 1px solid var(--rule); } } @media (max-width: 900px) { .inv-grid { grid-template-columns: 1fr !important; gap: 8px !important; padding: 18px 0 !important; } } @media (max-width: 720px) { .inv-head { grid-template-columns: 1fr !important; gap: 16px !important; } .inv-head .page-title { font-size: 36px !important; } .inv-head-ctas { width: 100%; } .inv-head-ctas > * { flex: 1; } .invoice-stats { padding: 0 20px !important; grid-template-columns: 1fr 1fr !important; } .invoice-stats > div { padding: 18px 0 !important; border-right: none !important; } .invoice-stats > div:nth-child(odd) { padding-right: 12px !important; border-right: 1px solid var(--rule) !important; } .invoice-stats > div:nth-child(even) { padding-left: 12px !important; } .invoice-stats > div .serif-i { font-size: 24px !important; } .inv-filters { padding: 16px 20px !important; } } `; function Wrapped() { return (<>); } window.Screens = window.Screens || {}; window.Screens.Invoices = Wrapped; })();