a8980a3a41
Hem was one ~33-query render behind a single fallback: the greeting waited for the slowest worklist scan, and the request also paid a sequential bank_file_imports read after the batch, a second scan of the suggested matches (getWorklistCounts counted the same 200 rows the pane listed) and an awaited stale-dismissal delete on the read path. - page.tsx awaits only what the greeting shell and the redirects need (settings, profile, agent profile, the Skatteverket flag); the notice line, the setup checklist and the Att göra + Fortsätt panes are async server components behind their own Suspense (hem-sections.tsx). RSC streaming applies to client navigations too, so the greeting paints first on every visit and each block fills in as its queries land. - DashboardContent becomes the shell with three slots; HemNotices keeps the one client-side action (the wrong-account sign-out); HemSkeletons are the two fallbacks. - getWorklistCounts accepts the suggested matches the caller is already fetching (a promise, so it stays parallel); listSuggestedMatches runs once at the scan cap and the pane shows the first five. - countInboxDocuments runs its id chunks in one wave instead of N sequential round trips. - getCompanyNotices takes deferReap; Hem passes Next's after() so the stale-dismissal delete runs after the response. - bank_file_imports joins the checklist section's batch. Tests: worklist aggregate (precomputed matches skip the rescan), notices aggregate (deferReap receives the reap; the delete does not run inline and runs when the task does). Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
88 lines
2.9 KiB
TypeScript
88 lines
2.9 KiB
TypeScript
import type { SupabaseClient } from '@supabase/supabase-js'
|
|
import type { SuggestedMatch } from './types'
|
|
import type { WorklistCounts } from './types'
|
|
import {
|
|
countDeadlinesNeedingAction,
|
|
countInboxDocuments,
|
|
countOverdueInvoices,
|
|
countPendingOperations,
|
|
countReconciliationDue,
|
|
countSuggestedMatches,
|
|
countSupplierInvoicesAwaitingApproval,
|
|
countUnbookedTransactions,
|
|
countVerifikatMissingDocument,
|
|
} from './categories'
|
|
|
|
/**
|
|
* All worklist counts in one round-trip burst. Each count is a bounded query
|
|
* (mostly head-only; suggested_match revalidates its candidates, see
|
|
* categories.ts) and individually soft-fails to 0, so this is safe to call
|
|
* from layouts and server components on every render.
|
|
*
|
|
* `total` is the number of distinct actionable items: suggested_match is a
|
|
* fast path over transactions already counted in book_transaction, so it is
|
|
* excluded to avoid double-counting (see lib/worklist/types.ts).
|
|
*/
|
|
export interface GetWorklistCountsOptions {
|
|
/**
|
|
* Suggested matches the caller is already fetching (Hem renders them in
|
|
* the Att göra pane): the count is taken from this list instead of a
|
|
* second scan of the same rows. A promise is accepted so it can run in
|
|
* parallel with the other counts.
|
|
*/
|
|
suggestedMatches?: SuggestedMatch[] | Promise<SuggestedMatch[]>
|
|
}
|
|
|
|
export async function getWorklistCounts(
|
|
supabase: SupabaseClient,
|
|
companyId: string,
|
|
options: GetWorklistCountsOptions = {},
|
|
): Promise<WorklistCounts> {
|
|
const [
|
|
bookTransaction,
|
|
inboxDocument,
|
|
suggestedMatch,
|
|
supplierInvoiceApproval,
|
|
verifikatMissingDocument,
|
|
overdueInvoice,
|
|
deadlineAction,
|
|
pendingOperations,
|
|
reconciliationDue,
|
|
] = await Promise.all([
|
|
countUnbookedTransactions(supabase, companyId),
|
|
countInboxDocuments(supabase, companyId),
|
|
options.suggestedMatches
|
|
? Promise.resolve(options.suggestedMatches).then((m) => m.length)
|
|
: countSuggestedMatches(supabase, companyId),
|
|
countSupplierInvoicesAwaitingApproval(supabase, companyId),
|
|
countVerifikatMissingDocument(supabase, companyId),
|
|
countOverdueInvoices(supabase, companyId),
|
|
countDeadlinesNeedingAction(supabase, companyId),
|
|
countPendingOperations(supabase, companyId),
|
|
countReconciliationDue(supabase, companyId),
|
|
])
|
|
|
|
return {
|
|
counts: {
|
|
book_transaction: bookTransaction,
|
|
inbox_document: inboxDocument,
|
|
suggested_match: suggestedMatch,
|
|
supplier_invoice_approval: supplierInvoiceApproval,
|
|
verifikat_missing_document: verifikatMissingDocument,
|
|
overdue_invoice: overdueInvoice,
|
|
deadline_action: deadlineAction,
|
|
pending_operations: pendingOperations,
|
|
reconciliation_due: reconciliationDue,
|
|
},
|
|
total:
|
|
bookTransaction +
|
|
inboxDocument +
|
|
supplierInvoiceApproval +
|
|
verifikatMissingDocument +
|
|
overdueInvoice +
|
|
deadlineAction +
|
|
pendingOperations +
|
|
reconciliationDue,
|
|
}
|
|
}
|