diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 0bb56165..9e42d3f1 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -120,20 +120,20 @@ class AccountController extends Controller } } - // ponytail: load-all transactions for the account (server-side, replaces - // the old client-side sync-all-then-filter). Fine for a single account; - // switch to a deferred/cursor-paginated prop if accounts grow huge. - $transactions = $account->type->hasTransactionLedger() - ? $account->transactions() - ->with(['category', 'labels']) - ->orderBy('transaction_date', 'desc') - ->orderBy('id', 'desc') - ->get() - : []; - return Inertia::render('Accounts/Show', [ 'account' => $data, - 'transactions' => $transactions, + // Deferred so the page shell paints without blocking on the ledger + // query/serialization. It stays the whole set because search and + // filtering run client-side over decrypted rows. ponytail: window it + // server-side only if one account's history gets big enough that the + // transfer itself hurts. + 'transactions' => $account->type->hasTransactionLedger() + ? Inertia::defer(fn () => $account->transactions() + ->with(['category', 'labels']) + ->orderBy('transaction_date', 'desc') + ->orderBy('id', 'desc') + ->get()) + : [], ]); } diff --git a/resources/js/pages/Accounts/Show.test.tsx b/resources/js/pages/Accounts/Show.test.tsx index e1de798e..22d435bf 100644 --- a/resources/js/pages/Accounts/Show.test.tsx +++ b/resources/js/pages/Accounts/Show.test.tsx @@ -1,4 +1,5 @@ -import { fireEvent, render, screen } from '@testing-library/react'; +import { router } from '@inertiajs/react'; +import { act, fireEvent, render, screen } from '@testing-library/react'; import { type ReactNode } from 'react'; import { describe, expect, it, vi } from 'vitest'; @@ -7,6 +8,7 @@ import AccountShow from './Show'; vi.mock('@inertiajs/react', () => ({ Head: () => null, router: { reload: vi.fn() }, + Deferred: ({ children }: { children: ReactNode }) => <>{children}, })); vi.mock('@/actions/App/Http/Controllers/AccountController', () => ({ @@ -115,6 +117,17 @@ describe('AccountShow', () => { ); }); + it('reloads only the deferred transactions prop after a transaction is created', () => { + renderPage(); + + const { onSuccess } = editTransactionDialog.mock.calls.at(-1)![0] as { + onSuccess: () => void; + }; + act(() => onSuccess()); + + expect(router.reload).toHaveBeenCalledWith({ only: ['transactions'] }); + }); + it('hides transaction action for connected accounts', () => { renderPage({ ...baseAccount, diff --git a/resources/js/pages/Accounts/Show.tsx b/resources/js/pages/Accounts/Show.tsx index b1f0ed03..c8ded843 100644 --- a/resources/js/pages/Accounts/Show.tsx +++ b/resources/js/pages/Accounts/Show.tsx @@ -39,6 +39,7 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; +import { Skeleton } from '@/components/ui/skeleton'; import { Textarea } from '@/components/ui/textarea'; import { useChartColors } from '@/hooks/use-chart-color-scheme'; import AppSidebarLayout from '@/layouts/app/app-sidebar-layout'; @@ -63,7 +64,7 @@ import { Label as LabelType } from '@/types/label'; import { type Transaction } from '@/types/transaction'; import { formatDateMedium } from '@/utils/date'; import { __ } from '@/utils/i18n'; -import { Head, router } from '@inertiajs/react'; +import { Deferred, Head, router } from '@inertiajs/react'; import { ChevronDown, Pencil, Plus } from 'lucide-react'; import { useCallback, useMemo, useState } from 'react'; import { Line, LineChart, ResponsiveContainer, Tooltip } from 'recharts'; @@ -382,23 +383,34 @@ export default function AccountShow({ )} {isTransactionalAccount(account) && ( - - setChartRefreshKey((key) => key + 1) + + {Array.from({ length: 8 }).map((_, i) => ( + + ))} + } - /> + > + + setChartRefreshKey((key) => key + 1) + } + /> + )} diff --git a/tests/Feature/AccountControllerTest.php b/tests/Feature/AccountControllerTest.php index 68e5db3f..3220e2d1 100644 --- a/tests/Feature/AccountControllerTest.php +++ b/tests/Feature/AccountControllerTest.php @@ -238,7 +238,10 @@ test('account show includes the account transactions and excludes other accounts ->assertOk() ->assertInertia(fn ($page) => $page ->component('Accounts/Show') - ->has('transactions', 2) + ->missing('transactions') + ->loadDeferredProps(fn ($reload) => $reload + ->has('transactions', 2) + ) ); });