sewt all limit #77
|
|
@ -215,6 +215,18 @@ try {
|
|||
hasSkeleton(view.html()) && rowCount(view.html()) === 0,
|
||||
`skeleton=${hasSkeleton(view.html())} rows=${rowCount(view.html())}`,
|
||||
)
|
||||
const allFormFetch = requestsMatching('/sheet/form-data/fetch')
|
||||
const allEmailFetch = requestsMatching('/inbox/all-applications').filter((u) => !u.includes('/count'))
|
||||
check(
|
||||
'All channel sends the UI page size, not the whole form_data table',
|
||||
allFormFetch.some((u) => u.includes('limit=10')),
|
||||
allFormFetch.slice(-1)[0] || 'no form-data fetch on All',
|
||||
)
|
||||
check(
|
||||
'All channel emails send the same page size',
|
||||
allEmailFetch.some((u) => u.includes('top=10')),
|
||||
allEmailFetch.slice(-1)[0] || 'no email fetch on All',
|
||||
)
|
||||
|
||||
// ---- frame 2: email home, sheet still travelling ------------------------
|
||||
// THE REGRESSION: this frame used to render six placeholders on top of two
|
||||
|
|
|
|||
|
|
@ -49,10 +49,10 @@ const PAGE_SIZE_MAX = 500
|
|||
* This list is a local table, not a live feed. Applications only appear when
|
||||
* the Sync worker writes them, and that path already invalidates
|
||||
* qk.mailbox.all() the moment a run completes — so refetching on every visit
|
||||
* bought nothing and cost a full-width skeleton each time. The All channel
|
||||
* makes that worse: it fetches BOTH sources unpaged, so the "loading" state a
|
||||
* recruiter saw on re-entry was thousands of rows being re-downloaded to
|
||||
* render the same ten.
|
||||
* bought nothing and cost a full-width skeleton each time.
|
||||
*
|
||||
* List fetches always send the UI page size (10 / 50 / 100), including All —
|
||||
* omitting limit used to dump the whole form_data table into the browser.
|
||||
*
|
||||
* staleTime therefore covers a normal working stretch, and keepPreviousData
|
||||
* means a tab switch, a page turn or a keystroke re-renders the rows already
|
||||
|
|
@ -1010,22 +1010,22 @@ export default function Inbox() {
|
|||
}, [deepOpen, deepKind, setSearchParams])
|
||||
|
||||
const isForms = channel === 'forms'
|
||||
// Combined channel: both sources fetched UNPAGED (each endpoint reads a
|
||||
// missing top/limit as no LIMIT), merged by date, and paged client-side —
|
||||
// per-source skip/top cannot compose into a correct global page.
|
||||
// Combined channel: each source is fetched with the UI page size, then
|
||||
// merged newest-first. Per-source pages are not a perfect global timeline,
|
||||
// but omitting limit dumped the whole form_data table on open.
|
||||
const isAllChannel = channel === 'all'
|
||||
const channelTabs = isForms || isAllChannel ? FORM_TABS : TABS
|
||||
const pageLimit = pageSize === 'all' ? undefined : pageSize
|
||||
|
||||
const tabFilter = TAB_FILTERS[tab] ?? {}
|
||||
const formTabFilter = FORM_TAB_FILTERS[tab] ?? {}
|
||||
const listParams = useMemo(() => ({
|
||||
...tabFilter,
|
||||
// 'all' page size and the All channel drop the param entirely — the
|
||||
// endpoint reads a missing top as unpaged.
|
||||
top: pageSize === 'all' || isAllChannel ? undefined : pageSize,
|
||||
skip: isAllChannel ? 0 : skip,
|
||||
// Show-all omits top (no LIMIT). Otherwise send the pager size — 10, 50, 100.
|
||||
top: pageLimit,
|
||||
skip: pageLimit == null ? 0 : skip,
|
||||
...(search ? { search } : {}),
|
||||
}), [tabFilter, skip, pageSize, search, isAllChannel])
|
||||
}), [tabFilter, skip, pageLimit, search])
|
||||
|
||||
/**
|
||||
* Sheet Forms only. On the All channel these rows are merged with email ones,
|
||||
|
|
@ -1042,12 +1042,12 @@ export default function Inbox() {
|
|||
const formParams = useMemo(() => ({
|
||||
// All channel spans every sheet tab, not just the selected one.
|
||||
sheet: isAllChannel ? undefined : (formSheet || undefined),
|
||||
offset: isAllChannel ? 0 : skip,
|
||||
limit: pageSize === 'all' || isAllChannel ? undefined : pageSize,
|
||||
offset: pageLimit == null ? 0 : skip,
|
||||
limit: pageLimit,
|
||||
...formTabFilter,
|
||||
...(search ? { search } : {}),
|
||||
...linkFilters,
|
||||
}), [formSheet, skip, pageSize, search, formTabFilter, isAllChannel, linkFilters])
|
||||
}), [formSheet, skip, pageLimit, search, formTabFilter, isAllChannel, linkFilters])
|
||||
|
||||
const applicationsQuery = useQuery({
|
||||
queryKey: qk.mailbox.applications(listParams),
|
||||
|
|
@ -1135,8 +1135,7 @@ export default function Inbox() {
|
|||
}
|
||||
}, [isForms, formSheetsQuery.data, formSheet])
|
||||
|
||||
// All channel: both sources arrive unpaged; merge newest-first and let the
|
||||
// pager slice the merged array below.
|
||||
// All channel: one page from each source, merged newest-first.
|
||||
const mergedRows = useMemo(() => {
|
||||
if (!isAllChannel) return null
|
||||
const emails = Array.isArray(applicationsQuery.data?.rows) ? applicationsQuery.data.rows : []
|
||||
|
|
@ -1198,15 +1197,17 @@ export default function Inbox() {
|
|||
const countsReady = isAllChannel
|
||||
? countsQuery.isSuccess && formCountsQuery.isSuccess
|
||||
: (isForms ? formCountsQuery.isSuccess : countsQuery.isSuccess)
|
||||
const searchTotal = isAllChannel
|
||||
? (Number(applicationsQuery.data?.total ?? 0) + Number(formQuery.data?.total ?? 0))
|
||||
: (activeQuery.data?.total ?? 0)
|
||||
const total = search
|
||||
? (activeQuery.data?.total ?? 0)
|
||||
? searchTotal
|
||||
: (countsReady ? tabTotal : (poolTotal || (activeQuery.data?.total ?? 0)))
|
||||
// 'all' = unpaged: the fetch omits top/limit (the endpoints treat a missing
|
||||
// page size as no LIMIT), so the whole tab is one page.
|
||||
const showAll = pageSize === 'all'
|
||||
const pages = showAll ? 1 : Math.max(1, Math.ceil(total / pageSize))
|
||||
const from = total ? skip + 1 : 0
|
||||
const to = showAll ? total : Math.min(skip + pageSize, total)
|
||||
const currentPage = showAll ? 1 : Math.min(Math.floor(skip / pageSize) + 1, pages)
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -1215,8 +1216,9 @@ export default function Inbox() {
|
|||
setSkip(Math.max(0, Math.floor((total - 1) / pageSize) * pageSize))
|
||||
}, [total, pageSize, skip, showAll])
|
||||
|
||||
// All channel pages the merged array client-side; single channels page on the server.
|
||||
const list = isAllChannel && !showAll ? inbox.slice(skip, skip + pageSize) : inbox
|
||||
// Server already applied skip/limit (or the whole tab when Show all).
|
||||
const list = inbox
|
||||
const to = showAll ? total : Math.min(skip + (isAllChannel ? list.length : pageSize), total)
|
||||
|
||||
// Mixed rows: the row's own kind picks the detail endpoint, not the channel.
|
||||
const selectedKind = inbox.find((i) => sameInboxId(i.id, selectedId))?.kind
|
||||
|
|
@ -1399,8 +1401,7 @@ export default function Inbox() {
|
|||
|
||||
/** Styled XLSX of the rows already loaded in this view — the DB filtered
|
||||
them when the list was fetched (channel, tab, search); no extra request.
|
||||
Paged channels export the loaded page; the All channel and the "All"
|
||||
page size hold the whole view, so those export everything. */
|
||||
Paged channels export the loaded page; Show all exports the whole view. */
|
||||
async function exportRows() {
|
||||
const rows = inbox
|
||||
if (!rows.length) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue