From 0e5422919e1e7b4a81bf67ee35053dc795d58999 Mon Sep 17 00:00:00 2001 From: siddharthd Date: Mon, 10 Aug 2026 18:27:16 +1000 Subject: [PATCH] shared page: expandable receipt details on split transactions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The item list existed only on the owner's transactions page — the person a meal or order was split WITH could see the amount but never what was in it. getSharedTransactions now carries the same order_platform LATERAL the transactions page uses, and rows with a receipt get the same disclosure arrow + OrderDetails expansion. The shared viewer is a split participant, so canAccessTransactions already authorises the order API for them; meal rows keep the verdict UI (the partner records their own), bridge rows stay review-free. --- src/app/shared/page.tsx | 36 +++++++++++++++++++++++++++++++++--- src/lib/queries.ts | 13 ++++++++++++- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/app/shared/page.tsx b/src/app/shared/page.tsx index 186447e..3d7787c 100644 --- a/src/app/shared/page.tsx +++ b/src/app/shared/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useRef, useEffect } from "react"; +import { Fragment, useState, useRef, useEffect } from "react"; import { useSharedTransactions, useParticipantBalances, @@ -16,6 +16,7 @@ import { } from "@/lib/hooks"; import type { SharedTransactionRow } from "@/lib/queries"; import { EditTransactionModal } from "@/components/edit-transaction-modal"; +import { OrderDetails } from "@/components/order-details"; import { formatCategory } from "@/lib/categories"; import { CATEGORY_COLORS } from "@/lib/category-colors"; @@ -370,6 +371,10 @@ export default function SharedPage() { const [paymentModal, setPaymentModal] = useState<{ id: number; name: string; balance: number } | null>(null); const [showHistory, setShowHistory] = useState(null); const [editModal, setEditModal] = useState(null); + // Receipt expansion, same pattern as the transactions page. The shared + // viewer is a split participant, so /api/transactions/[id]/order + // authorises them — the item list is part of what was shared. + const [expanded, setExpanded] = useState>(new Set()); return (
@@ -543,11 +548,28 @@ export default function SharedPage() { {(transactions as SharedTransactionRow[]).map((tx) => { const splits = Array.isArray(tx.splits) ? tx.splits : []; return ( - + + {formatDate(tx.transaction_date)} {formatDate(tx.created_at)} -

{tx.effective_merchant || tx.description}

+
+ {tx.order_platform && ( + + )} +

{tx.effective_merchant || tx.description}

+
{tx.effective_merchant && (

{tx.description}

)} @@ -615,6 +637,14 @@ export default function SharedPage() { + {expanded.has(tx.id) && ( + + + + + + )} +
); })} diff --git a/src/lib/queries.ts b/src/lib/queries.ts index d137b76..52a67af 100644 --- a/src/lib/queries.ts +++ b/src/lib/queries.ts @@ -866,6 +866,11 @@ export async function getSharedTransactions(ownerId: number, tagIds?: number[], COALESCE(t.owner_id, s.owner_id) as owner_id, p_owner.name as owner_name, COALESCE(src.created_at, t.created_at) as created_at, + -- Receipt flag, same LATERAL the transactions page uses: the shared + -- viewer is a split participant, so the order API authorises them — + -- without the flag here the disclosure arrow (and the item list behind + -- it) existed only on the owner's page. + order_ctx.platform as order_platform, json_agg(json_build_object( 'split_id', ts.id, 'participant_id', ts.participant_id, @@ -879,6 +884,12 @@ export async function getSharedTransactions(ownerId: number, tagIds?: number[], LEFT JOIN transaction_overrides o ON o.transaction_id = t.id LEFT JOIN statements s ON s.id = t.statement_id LEFT JOIN participants p_owner ON p_owner.id = COALESCE(t.owner_id, s.owner_id) + LEFT JOIN LATERAL ( + SELECT em.platform + FROM expense_metadata em + WHERE em.transaction_id = t.id OR em.matched_transaction_id = t.id + LIMIT 1 + ) order_ctx ON true LEFT JOIN transactions src ON src.reconciled_with_id = t.id AND src.statement_id IS NULL WHERE ( ( @@ -892,7 +903,7 @@ export async function getSharedTransactions(ownerId: number, tagIds?: number[], AND ${EXCLUDE_RECONCILED_SOURCE} ${tagClause} ${participantClause} - GROUP BY t.id, o.category_override, o.merchant_normalized, o.notes, s.bank_name, s.currency, s.owner_id, p_owner.name, src.created_at + GROUP BY t.id, o.category_override, o.merchant_normalized, o.notes, s.bank_name, s.currency, s.owner_id, p_owner.name, src.created_at, order_ctx.platform ORDER BY t.transaction_date DESC `, params);