From 3f06fbeff1bc1d49f8fa80effc9240eb88a742bc Mon Sep 17 00:00:00 2001 From: Talha Ahmed Date: Wed, 2 Sep 2026 16:25:05 +0500 Subject: [PATCH] Inbox: All page-size option to show every application unpaged PageSizeField gains an opt-in All toggle beside the numeric input (sentinel value all; the input disables while active). Only the Recruitment Inbox opts in: with All active the fetch omits top/limit, which /inbox/all-applications and /sheet/form-data/fetch already read as unpaged (Query(None) -> no LIMIT), so the whole tab renders as one page for both the Email and Sheet Forms channels. Every other table keeps its existing paged-only control. Co-Authored-By: Claude Fable 5 --- frontend/src/screens/Inbox.jsx | 21 ++++++++++++++------- frontend/src/ui/DataTable.jsx | 33 +++++++++++++++++++++++++++------ 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/frontend/src/screens/Inbox.jsx b/frontend/src/screens/Inbox.jsx index 9657137..1156d92 100644 --- a/frontend/src/screens/Inbox.jsx +++ b/frontend/src/screens/Inbox.jsx @@ -731,7 +731,8 @@ export default function Inbox() { const formTabFilter = FORM_TAB_FILTERS[tab] ?? {} const listParams = useMemo(() => ({ ...tabFilter, - top: pageSize, + // 'all' drops the param entirely — the endpoint reads a missing top as unpaged. + top: pageSize === 'all' ? undefined : pageSize, skip, ...(q.trim() ? { search: q.trim() } : {}), }), [tabFilter, skip, pageSize, q]) @@ -739,7 +740,7 @@ export default function Inbox() { const formParams = useMemo(() => ({ sheet: formSheet || undefined, offset: skip, - limit: pageSize, + limit: pageSize === 'all' ? undefined : pageSize, ...formTabFilter, ...(q.trim() ? { search: q.trim() } : {}), }), [formSheet, skip, pageSize, q, formTabFilter]) @@ -840,15 +841,19 @@ export default function Inbox() { const total = q.trim() ? (activeQuery.data?.total ?? 0) : (countsReady ? tabTotal : (poolTotal || (activeQuery.data?.total ?? 0))) - const pages = Math.max(1, Math.ceil(total / pageSize)) + // '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 = Math.min(skip + pageSize, total) - const currentPage = Math.min(Math.floor(skip / pageSize) + 1, pages) + const to = showAll ? total : Math.min(skip + pageSize, total) + const currentPage = showAll ? 1 : Math.min(Math.floor(skip / pageSize) + 1, pages) useEffect(() => { + if (showAll) { if (skip !== 0) setSkip(0); return } if (total <= 0 || skip < total) return setSkip(Math.max(0, Math.floor((total - 1) / pageSize) * pageSize)) - }, [total, pageSize, skip]) + }, [total, pageSize, skip, showAll]) const list = inbox @@ -1207,12 +1212,14 @@ export default function Inbox() { total={total} page={currentPage} pages={pages} - setPage={(p) => { setSkip((p - 1) * pageSize); setSelectedId(null); selection.clear() }} + setPage={(p) => { if (showAll) return; setSkip((p - 1) * pageSize); setSelectedId(null); selection.clear() }} pageButtons={pageWindow(currentPage, pages)} pageSize={pageSize} pageSizeMax={PAGE_SIZE_MAX} + allowAll onPageSizeChange={(n) => { setPageSize(n) + if (n === 'all') setSkip(0) // numeric sizes keep their page (clamp effect above) setSelectedId(null) selection.clear() }} diff --git a/frontend/src/ui/DataTable.jsx b/frontend/src/ui/DataTable.jsx index 20c2c87..84cd457 100644 --- a/frontend/src/ui/DataTable.jsx +++ b/frontend/src/ui/DataTable.jsx @@ -103,15 +103,20 @@ export function pageAfterSizeChange(currentPage, total, nextSize) { return Math.min(Math.max(1, currentPage || 1), pages) } -/** Local draft so typing "50" does not fire a GET for 5, then 50. */ -export function PageSizeField({ value, onChange, max = 100, label = 'Per page', id }) { - const [draft, setDraft] = useState(String(value ?? DEFAULT_PAGE_SIZE)) - useEffect(() => setDraft(String(value ?? DEFAULT_PAGE_SIZE)), [value]) +/** Local draft so typing "50" does not fire a GET for 5, then 50. + With `allowAll`, an "All" toggle sits beside the input: active it reports + the sentinel 'all' (the caller drops its top/limit param — the endpoints + treat a missing page size as unpaged); committing a number exits it. */ +export function PageSizeField({ value, onChange, max = 100, label = 'Per page', id, allowAll = false }) { + const isAll = allowAll && value === 'all' + const numeric = isAll ? DEFAULT_PAGE_SIZE : (value ?? DEFAULT_PAGE_SIZE) + const [draft, setDraft] = useState(String(numeric)) + useEffect(() => setDraft(String(numeric)), [numeric]) function commit() { const next = clampPageSize(draft, max) setDraft(String(next)) - if (next !== value) onChange(next) + if (isAll || next !== value) onChange(next) } return ( @@ -124,6 +129,7 @@ export function PageSizeField({ value, onChange, max = 100, label = 'Per page', min={1} max={max} value={draft} + disabled={isAll} onChange={(e) => setDraft(e.target.value)} onBlur={commit} onKeyDown={(e) => { @@ -134,13 +140,27 @@ export function PageSizeField({ value, onChange, max = 100, label = 'Per page', }} aria-label={label} /> + {allowAll && ( + + )} ) } export function Pagination({ from, to, total, page, pages, setPage, pageButtons, - pageSize, onPageSizeChange, pageSizeMax = 100, + pageSize, onPageSizeChange, pageSizeMax = 100, allowAll = false, }) { return (
@@ -154,6 +174,7 @@ export function Pagination({ value={pageSize ?? DEFAULT_PAGE_SIZE} onChange={onPageSizeChange} max={pageSizeMax} + allowAll={allowAll} /> of {total}