feat(trips): say which direction a trip balance points
ci / lint-test (push) Successful in 45s

A participant who has overpaid a trip showed as "$-816.16" under a column
headed "Outstanding on this trip". A negative outstanding reads as a bug
rather than as "they are ahead", so the sign is now spelled out: magnitude
plus one of all square / owes you / ahead — you owe them, coloured the same
way Shared colours the same three states.

Also corrects the footer, which had gone stale and was now simply false. It
said settlement could not be computed per trip because payments carried no
trip attribution. Migration 0022 added split_payments.trip_id and the figures
above it have been net of trip-scoped payments since. What a reader needs to
know is the opposite of what it said: household-tab payments are the ones NOT
counted here.
This commit is contained in:
2026-07-28 11:39:11 +10:00
parent e92fcb709f
commit 7a1acc32a9
+25 -11
View File
@@ -291,12 +291,24 @@ export default function TripDetailPage({ params }: { params: Promise<{ id: strin
</tr>
</thead>
<tbody>
{participant_splits.map((p) => (
{/* A negative outstanding means they have paid more towards this
trip than their share of it — which reads as a typo unless the
sign is spelled out. Shown as a magnitude plus a word, the same
way Shared does it, so the two pages agree on what a direction
means. */}
{participant_splits.map((p) => {
const owed = Number(p.owed);
const square = Math.abs(owed) < 0.005;
const theyOweMe = owed > 0;
return (
<tr key={p.participant_id} className="border-b border-zinc-800/50 last:border-0">
<td className="px-5 py-3 font-medium">{p.name}</td>
<td className="px-5 py-3 text-right tabular-nums font-mono">
<span className={Math.abs(Number(p.owed)) < 0.005 ? "text-zinc-500" : ""}>
${Number(p.owed).toFixed(2)}
<span className={square ? "text-zinc-500" : theyOweMe ? "text-amber-400" : "text-blue-400"}>
${Math.abs(owed).toFixed(2)}
</span>
<span className="block text-[11px] text-zinc-500 mt-0.5 font-sans">
{square ? "all square" : theyOweMe ? "owes you" : "ahead — you owe them"}
</span>
{p.unconverted_count > 0 && (
<span className="block text-[11px] text-amber-500/80 mt-0.5 font-sans">
@@ -305,18 +317,20 @@ export default function TripDetailPage({ params }: { params: Promise<{ id: strin
)}
</td>
</tr>
))}
);
})}
</tbody>
</table>
{/* Settled/unsettled was reported from transaction_splits.settled,
which nothing sets — so every trip showed 100% unsettled forever,
including ones already paid in full. Settlement is tracked across
the whole relationship, not per trip: payments carry no trip
attribution, so a per-trip figure cannot be computed. */}
{/* This note used to say a per-trip figure could not be computed,
because payments carried no trip attribution. Migration 0022 added
split_payments.trip_id, so it can and now does — the figures above
are net of payments scoped to this trip. What the note has to say
instead is which payments are NOT in them. */}
<p className="px-5 py-2.5 text-xs text-zinc-500 border-t border-zinc-800">
Settlement is tracked across all shared expenses, not per trip
Net of payments recorded against this trip. Payments on the ongoing
household tab are not counted here
see <Link href="/shared" className="text-zinc-400 hover:text-zinc-200 underline">Shared</Link> for
what is actually owed.
the overall balance.
</p>
</div>
)}