perf(bookkeeping): fiscal-year pickers and cash accounts read the session cache (#1934)
First consumer migration onto lib/reference-data. FyPicker and
FiscalYearSelector (14 consumer surfaces, 47 fiscal-period fetch sites
before this series) now read useFiscalPeriods(); with the layout seed the
restore of the persisted scope runs in the first effect tick and onReady
fires on mount instead of after a round trip. Their restore rules are
extracted into a pure resolveInitialFiscalScope() (lib/reference-data/
fiscal-scope.ts) so the two pickers cannot drift apart again, and the
restore runs once per company load, not on every background revalidation.
- /reports: the static catalog renders immediately; only the "no fiscal
year" empty state waits for the picker (previously six skeleton bars
until /api/bookkeeping/fiscal-periods resolved).
- JournalEntryList (/bookkeeping): resolves its initial scope from the
cached list instead of its own fetch; the saved-scope shortcut still
unblocks the entries fetch first when nothing is cached, and resolution
is guarded to once per company so a revalidation can never snap a
deep-link "all years" visit back to the stored year.
- /transactions: the account chooser reads useCashAccounts({ enabledOnly })
(seeded) instead of fetching /api/cash-accounts on every visit; the bank
sync button invalidates that entry after a sync.
- STORAGE_KEY_PREFIX / ALL_YEARS_VALUE move to a dependency-free
fiscal-year-storage.ts (re-exported from FiscalYearSelector) so lib/ code
can import them without a React component.
raw-reference-fetch ratchet: 55 -> 51 files.
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Jakob Wennberg
Claude Fable 5
parent
b436d47b64
commit
9a56b7aff9
@@ -3,7 +3,6 @@
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { PageHeader } from '@/components/ui/page-header'
|
||||
import { HelpPopover } from '@/components/ui/help-popover'
|
||||
import { EmptyState } from '@/components/ui/empty-state'
|
||||
@@ -25,7 +24,10 @@ import { getReport } from '@/lib/reports/catalog'
|
||||
export default function ReportsPage() {
|
||||
const router = useRouter()
|
||||
const [selectedPeriod, setSelectedPeriod] = useState('')
|
||||
const [isLoadingInit, setIsLoadingInit] = useState(true)
|
||||
// The catalog itself is static; only the "no fiscal year" empty state has
|
||||
// to wait for the picker to finish restoring its scope (one effect tick
|
||||
// when the periods are seeded, see FyPicker).
|
||||
const [fyReady, setFyReady] = useState(false)
|
||||
const { company } = useCompany()
|
||||
const { settings } = useCompanySettings()
|
||||
const t = useTranslations('reports')
|
||||
@@ -62,18 +64,12 @@ export default function ReportsPage() {
|
||||
onChange={(id) => setSelectedPeriod(id || '')}
|
||||
includeAllOption={false}
|
||||
hideFuturePeriods
|
||||
onReady={() => setIsLoadingInit(false)}
|
||||
onReady={() => setFyReady(true)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
{isLoadingInit ? (
|
||||
<div className="space-y-3">
|
||||
{[1, 2, 3, 4, 5, 6].map((i) => (
|
||||
<Skeleton key={i} className="h-9 w-full" />
|
||||
))}
|
||||
</div>
|
||||
) : !selectedPeriod ? (
|
||||
{fyReady && !selectedPeriod ? (
|
||||
<EmptyState
|
||||
title="Inget räkenskapsår valt"
|
||||
description="Skapa ett räkenskapsår för att kunna se rapporter."
|
||||
|
||||
@@ -68,12 +68,13 @@ import {
|
||||
MATCHABLE_SUPPLIER_INVOICE_STATUSES,
|
||||
} from '@/lib/invoices/matchable-statuses'
|
||||
import { useCompany } from '@/contexts/CompanyContext'
|
||||
import { useCashAccounts } from '@/lib/reference-data/hooks'
|
||||
import { fetchAllRows } from '@/lib/supabase/fetch-all'
|
||||
import { useRealtimeSupabase } from '@/lib/hooks/use-realtime-supabase'
|
||||
import { getErrorMessage } from '@/lib/errors/get-error-message'
|
||||
import { cn, formatCurrency, formatDate } from '@/lib/utils'
|
||||
import { roundOre } from '@/lib/money'
|
||||
import type { TransactionCategory, CreateTransactionInput, Invoice, Customer, SupplierInvoice, Supplier, VatTreatment, EntityType, LinePatternEntry, BookingTemplateLibrary, CashAccount } from '@/types'
|
||||
import type { TransactionCategory, CreateTransactionInput, Invoice, Customer, SupplierInvoice, Supplier, VatTreatment, EntityType, LinePatternEntry, BookingTemplateLibrary } from '@/types'
|
||||
import type { SuggestedTemplate } from '@/lib/transactions/category-suggestions'
|
||||
import { isImportedTransaction } from '@/lib/transactions/origin'
|
||||
import { computeJeUnderlagStatus, type JeUnderlagStatus } from '@/lib/transactions/underlag-status'
|
||||
@@ -534,7 +535,10 @@ export default function TransactionsPage() {
|
||||
}, [companyId])
|
||||
// Registered cash accounts (cash_accounts): the account chooser's rows,
|
||||
// with PSD2 balances when the bank reports them.
|
||||
const [cashAccounts, setCashAccounts] = useState<CashAccount[]>([])
|
||||
// Registered, enabled cash accounts for the account chooser: session-cached
|
||||
// and seeded by the dashboard layout (lib/reference-data), so the chooser
|
||||
// renders populated on the first paint. Bank sync invalidates the entry.
|
||||
const { cashAccounts } = useCashAccounts({ enabledOnly: true })
|
||||
|
||||
const { toast } = useToast()
|
||||
const { dialogProps: confirmDialogProps, confirm } = useDestructiveConfirm()
|
||||
@@ -882,24 +886,6 @@ export default function TransactionsPage() {
|
||||
|
||||
const PAGE_SIZE = 200
|
||||
|
||||
// Account chooser rows: the registered, enabled cash accounts. One fetch
|
||||
// per company; balances refresh with the page (bank sync triggers a
|
||||
// router.refresh via the sync toast flow).
|
||||
useEffect(() => {
|
||||
if (!companyId) return
|
||||
let cancelled = false
|
||||
fetch('/api/cash-accounts?enabled_only=true')
|
||||
.then((res) => (res.ok ? res.json() : { data: [] }))
|
||||
.then((json: { data?: CashAccount[] }) => {
|
||||
if (!cancelled) setCashAccounts(json.data ?? [])
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setCashAccounts([])
|
||||
})
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [companyId])
|
||||
|
||||
// Same sequence-guard pattern as fetchGenerationRef: only the newest
|
||||
// skattekonto fetch may write rows. A company switch bumps the sequence so a
|
||||
|
||||
Reference in New Issue
Block a user