feat(shared): replace settle buttons with payment ledger

- New split_payments table records actual payments between participants
- Balance = total split obligations - total payments (splits never marked settled)
- Record Payment modal per participant: direction toggle, amount pre-filled with balance, date, notes
- Payment history inline on each balance card with +/- display and delete
- Per-transaction Settle button removed; Action column removed from shared table
- Splits always show the true cost breakdown regardless of payment state
This commit is contained in:
2026-03-14 21:09:00 +11:00
parent 5206388958
commit 85e7801407
6 changed files with 383 additions and 94 deletions
+50 -4
View File
@@ -257,11 +257,43 @@ export function useSetSplits() {
});
}
export function useSettleSplits() {
export interface SplitPayment {
id: number;
from_participant_id: number;
from_name: string;
to_participant_id: number;
to_name: string;
amount: number;
payment_date: string;
notes: string | null;
linked_transaction_id: number | null;
created_at: string;
}
export function usePaymentHistory(participantId: number | null) {
return useQuery<SplitPayment[]>({
queryKey: ["split-payments", participantId],
queryFn: async () => {
if (!participantId) return [];
const res = await fetch(`/api/split-payments?participant_id=${participantId}`);
return res.json();
},
enabled: !!participantId,
});
}
export function useRecordPayment() {
const qc = useQueryClient();
return useMutation({
mutationFn: async (body: { participant_id?: number; split_ids?: number[] }) => {
const res = await fetch("/api/splits/settle", {
mutationFn: async (body: {
from_participant_id: number;
to_participant_id: number;
amount: number;
payment_date: string;
notes?: string;
linked_transaction_id?: number;
}) => {
const res = await fetch("/api/split-payments", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
@@ -269,8 +301,22 @@ export function useSettleSplits() {
return res.json();
},
onSuccess: () => {
qc.invalidateQueries({ queryKey: ["shared-transactions"] });
qc.invalidateQueries({ queryKey: ["participant-balances"] });
qc.invalidateQueries({ queryKey: ["split-payments"] });
},
});
}
export function useDeletePayment() {
const qc = useQueryClient();
return useMutation({
mutationFn: async (id: number) => {
const res = await fetch(`/api/split-payments?id=${id}`, { method: "DELETE" });
return res.json();
},
onSuccess: () => {
qc.invalidateQueries({ queryKey: ["participant-balances"] });
qc.invalidateQueries({ queryKey: ["split-payments"] });
},
});
}