payout-clarity
side project · React, TypeScript, Vite, Tailwind · Live prototype + source
Payout Clarity
A short-term-rental owner opens a booking and asks one question: what am I getting paid, and why is it less than the guest paid? This is a prototype that answers it in a single screen — and then a second version that answers it when the stay runs forty-five nights instead of six.
act one · six nights
Four pots of money, one of them yours
The guest pays one number. The owner receives a different one. Everything hard about this screen lives in the gap between them.
Pending state showing the hierarchy — payout dominant, reconciliation directly under it, accommodation collapsed, reference last.
Both subtractions reduce the number, and they are not the same kind of fact. The tax was never the owner's money — it passed through them on the way to a tax authority. The management fee was their money, and the platform took it.
Listing both as "deductions" is arithmetically correct and tells the owner a lie: that the platform took $258 from them. So the page separates what the guest paid from what belongs to the owner from what the platform deducted, and says which is which in words rather than leaving it to a minus sign.
Answer first
Payout amount, status and expected date sit together in one block at the top, as the largest thing on the page. Everything below is evidence for that number: the reconciliation directly under it, nightly rates progressively disclosed beneath that, booking reference last.
An owner opening this screen has a question, not a curiosity. The reconciliation exists to make the answer trustworthy, not to be read.
Reconciliation rows
Five states, one component
A payout can be pending, paid, scheduled, canceled — or absent entirely, when the owner blocked the dates themselves.
The interesting pair is the last two. A canceled booking has a payout of $0.00. Owner-blocked dates have no payout object at all. Both render as "nothing," and they mean completely different things — one is a financial event that resolved to zero, the other is the absence of a financial event. Collapsing them into a dash costs the owner the ability to tell "this fell through" from "this was never a booking."
So the state is read from the explicit status field rather than inferred from whether the amount is falsy, and each state has its own sentence:
- Pending · Expected May 19,
- Paid · Deposited May 19,
- Canceled, Owner-blocked dates.
act two · forty-five nights
The question that broke it
The prototype above assumes a short stay. Six nights is a list you glance at. What happens at forty-five?
The naive answer is that the table gets longer. The real answer is that the owner's job changes. At six nights they are confirming — they scan the nights and they're done. At forty-five they are reconciling: they have a specific question, usually why is this figure not forty-five times my nightly rate, and forty-five rows do not answer it.
“Stay length doesn’t change the amount of data. It changes the task.”
chunk by what changed
Grouping by week is the obvious move and the wrong one — calendar weeks mean nothing to a rate. A long stay isn't forty-five independent prices; it's three or four pricing regimes with boundaries between them: a monthly rate, a promotion covering part of the stay, a peak-season stretch.
So nights are grouped into contiguous runs sharing the same listed rate and the same set of adjustments, breaking wherever either one changes.
Four rows instead of forty-five, and each one names the reason it differs from its neighbour. The question the owner arrived with is answered by the fourth column.
It degrades honestly, too. If every night genuinely has a different price you get forty-five runs and you're back to a list — but that is the truthful shape of that data, not a failure of the component.
One function, one invariant
Grouping is a pure function over the nightly data — no component knows about it, which means the same runs feed the table, the CSV export and the payout schedule.
And one property test carries the whole feature:
Generate the nights randomly, assert both. That test is what lets the grouped view be described as the same money rearranged, rather than a summary of it.
What forty-five nights breaks that isn't the table
The rate list is the visible half of the problem. Two more sit behind it, and the second is a larger change than the grouping.
Payout stops being singular. A stay that long likely pays in installments. Payout clarity was defined as amount plus status plus timing — at forty-five nights all three go plural, and the summary becomes next payout, then a schedule.
Tax treatment can change mid-stay. Many jurisdictions exempt stays past thirty days from transient occupancy tax. If that applies, the reconciliation has a line that stops partway through the booking — which is exactly the kind of thing that makes an owner think the math is broken. It needs a sentence, not a silently changing number.