shared page: window the rendered rows — DOM no longer grows with history
ci / lint-test (push) Successful in 1m4s
ci / lint-test (push) Successful in 1m4s
The table rendered every split row (1,279 today, ~600/yr growth), so page weight was unbounded even after the re-render fix. Render the first 100 of the filtered/sorted set with Show more (+100) / Show all; any filter or sort change resets the window. Data stays fully client-side on purpose — search still counts matches across all history — only the DOM is capped. Headless-measured expand stall: 165ms -> 35ms.
This commit is contained in:
+31
-1
@@ -498,6 +498,17 @@ export default function SharedPage() {
|
|||||||
const [paymentModal, setPaymentModal] = useState<{ id: number; name: string; balance: number } | null>(null);
|
const [paymentModal, setPaymentModal] = useState<{ id: number; name: string; balance: number } | null>(null);
|
||||||
const [showHistory, setShowHistory] = useState<number | null>(null);
|
const [showHistory, setShowHistory] = useState<number | null>(null);
|
||||||
const [editModal, setEditModal] = useState<SharedTransactionRow | null>(null);
|
const [editModal, setEditModal] = useState<SharedTransactionRow | null>(null);
|
||||||
|
// Rendering is windowed even though the data is not: the query returns every
|
||||||
|
// split row (1,279 as of 2026-08, growing ~600/yr) so search and sort stay
|
||||||
|
// instant over full history, but the DOM stops at visibleCount — an unbounded
|
||||||
|
// table was what made expanding a receipt freeze the browser. "Show more"
|
||||||
|
// extends the window; changing any filter resets it.
|
||||||
|
const [visibleCount, setVisibleCount] = useState(100);
|
||||||
|
useEffect(() => {
|
||||||
|
setVisibleCount(100);
|
||||||
|
}, [search, sortCol, sortDir, tagIds, participantId]);
|
||||||
|
const visible = (transactions as SharedTransactionRow[]).slice(0, visibleCount);
|
||||||
|
|
||||||
// Receipt expansion, same pattern as the transactions page. The shared
|
// Receipt expansion, same pattern as the transactions page. The shared
|
||||||
// viewer is a split participant, so /api/transactions/[id]/order
|
// viewer is a split participant, so /api/transactions/[id]/order
|
||||||
// authorises them — the item list is part of what was shared.
|
// authorises them — the item list is part of what was shared.
|
||||||
@@ -681,7 +692,7 @@ export default function SharedPage() {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{(transactions as SharedTransactionRow[]).map((tx) => (
|
{visible.map((tx) => (
|
||||||
<SharedTxRow
|
<SharedTxRow
|
||||||
key={tx.id}
|
key={tx.id}
|
||||||
tx={tx}
|
tx={tx}
|
||||||
@@ -694,6 +705,25 @@ export default function SharedPage() {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
)}
|
)}
|
||||||
|
{!txLoading && transactions.length > visible.length && (
|
||||||
|
<div className="px-4 py-3 border-t border-zinc-800 flex items-center gap-3 text-xs">
|
||||||
|
<button
|
||||||
|
onClick={() => setVisibleCount((c) => c + 100)}
|
||||||
|
className="px-3 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded-lg"
|
||||||
|
>
|
||||||
|
Show more
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setVisibleCount(transactions.length)}
|
||||||
|
className="text-zinc-500 hover:text-zinc-300 underline underline-offset-2"
|
||||||
|
>
|
||||||
|
Show all
|
||||||
|
</button>
|
||||||
|
<span className="text-zinc-600 ml-auto tabular-nums">
|
||||||
|
showing {visible.length} of {transactions.length}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Payment modal */}
|
{/* Payment modal */}
|
||||||
|
|||||||
Reference in New Issue
Block a user