"use client"; import { useState } from "react"; import { useSharedTransactions, useParticipantBalances, useSettleSplits, useCreateParticipant, } 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" }); } const SPEND_TYPES = new Set(["debit", "fee", "interest"]); function formatAmount(n: number, type?: string) { const formatted = `$${Number(n).toFixed(2)}`; return type && !SPEND_TYPES.has(type) ? `+${formatted}` : formatted; } function AddParticipantForm({ onDone }: { onDone: () => void }) { const [name, setName] = useState(""); const [email, setEmail] = useState(""); const [error, setError] = useState(""); const create = useCreateParticipant(); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setError(""); if (!name.trim()) { setError("Name is required"); return; } try { await create.mutateAsync({ name: name.trim(), email: email.trim() || undefined }); onDone(); } catch (err) { setError(err instanceof Error ? err.message : "Failed to create"); } } return (

Add Participant

setName(e.target.value)} className="flex-1 bg-zinc-800 border border-zinc-700 rounded-lg px-3 py-1.5 text-sm focus:outline-none focus:border-zinc-500" /> setEmail(e.target.value)} className="flex-1 bg-zinc-800 border border-zinc-700 rounded-lg px-3 py-1.5 text-sm focus:outline-none focus:border-zinc-500" />
{error &&

{error}

}
); } export default function SharedPage() { const { data: transactions = [], isLoading: txLoading } = useSharedTransactions(); const { data: balances = [], isLoading: balLoading } = useParticipantBalances(); const settle = useSettleSplits(); const [settling, setSettling] = useState(null); const [addingParticipant, setAddingParticipant] = useState(false); async function handleSettleParticipant(participantId: number) { setSettling(participantId); await settle.mutateAsync({ participant_id: participantId }); setSettling(null); } return (

Shared Expenses

{!addingParticipant && ( )}
{addingParticipant && ( setAddingParticipant(false)} /> )} {/* Balance summary */}
{balLoading ? (

Loading balances...

) : ( balances.map((b) => { const theyOweMe = b.total_owed > 0; const net = Math.abs(b.total_owed); return (

{b.name}

{theyOweMe ? `${b.unsettled_count} unsettled` : "you owe"}

${net.toFixed(2)}

{theyOweMe ? "owes you" : "you owe"}

{theyOweMe && 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 ( ); })}
Date Description Amount Splits Action
{formatDate(tx.transaction_date)}

{tx.effective_merchant || tx.description}

{tx.description}

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