From 1e79ada6d846decff9560130a6544c5f65c742fd Mon Sep 17 00:00:00 2001 From: siddharthd Date: Sun, 8 Mar 2026 17:24:04 +1100 Subject: [PATCH] feat(finance): implement Shared Expenses page Show split transactions with per-participant balance cards and settle buttons. --- src/app/shared/page.tsx | 145 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 3 deletions(-) diff --git a/src/app/shared/page.tsx b/src/app/shared/page.tsx index 607ec08..3850bbc 100644 --- a/src/app/shared/page.tsx +++ b/src/app/shared/page.tsx @@ -1,8 +1,147 @@ +"use client"; + +import { useState } from "react"; +import { + useSharedTransactions, + useParticipantBalances, + useSettleSplits, +} from "@/lib/hooks"; +import type { SharedTransactionRow } from "@/lib/queries"; + +function formatDate(d: string) { + return new Date(d).toLocaleDateString("en-AU", { day: "numeric", month: "short", year: "numeric" }); +} + +function formatAmount(n: number) { + return `$${Number(n).toFixed(2)}`; +} + export default function SharedPage() { + const { data: transactions = [], isLoading: txLoading } = useSharedTransactions(); + const { data: balances = [], isLoading: balLoading } = useParticipantBalances(); + const settle = useSettleSplits(); + const [settling, setSettling] = useState(null); + + async function handleSettleParticipant(participantId: number) { + setSettling(participantId); + await settle.mutateAsync({ participant_id: participantId }); + setSettling(null); + } + + const others = balances.filter((b) => b.name !== "Me"); + return ( -
-

Shared Expenses

-

Coming soon - track shared expenses and splits.

+
+

Shared Expenses

+ + {/* Balance summary */} +
+ {balLoading ? ( +

Loading balances...

+ ) : ( + others.map((b) => ( +
+
+
+

{b.name}

+

{b.unsettled_count} unsettled

+
+
+

{formatAmount(b.total_owed)}

+

owes you

+
+
+ {b.unsettled_count > 0 && ( + + )} +
+ )) + )} +
+ + {/* Transaction list */} +
+
+

Split Transactions

+
+ {txLoading ? ( +

Loading...

+ ) : transactions.length === 0 ? ( +

+ No split transactions yet. Use the Split button on any transaction. +

+ ) : ( + + + + + + + + + + + + {(transactions as SharedTransactionRow[]).map((tx) => { + const splits = Array.isArray(tx.splits) ? tx.splits : []; + const unsettled = splits.filter((s) => !s.settled && s.name !== "Me"); + return ( + + + + + + + + ); + })} + +
DateDescriptionAmountSplitsAction
+ {formatDate(tx.transaction_date)} + +

{tx.effective_merchant || tx.description}

+

{tx.description}

+
+ {formatAmount(tx.amount)} + +
+ {splits.map((s) => ( + + {s.name} {s.share_percent}% + {s.settled && } + + ))} +
+
+ {unsettled.length > 0 && ( + + )} +
+ )} +
); }