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 <noreply@anthropic.com>
pull/50/head
Talha Ahmed 2026-09-02 16:25:05 +05:00
parent ea43b02b99
commit 3f06fbeff1
2 changed files with 41 additions and 13 deletions

View File

@ -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()
}}

View File

@ -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 && (
<button
type="button"
className={`page-btn${isAll ? ' active' : ''}`}
aria-pressed={isAll}
title={isAll ? 'Back to paged view' : 'Show all rows'}
onClick={(e) => {
e.preventDefault()
onChange(isAll ? clampPageSize(draft, max) : 'all')
}}
>
All
</button>
)}
</label>
)
}
export function Pagination({
from, to, total, page, pages, setPage, pageButtons,
pageSize, onPageSizeChange, pageSizeMax = 100,
pageSize, onPageSizeChange, pageSizeMax = 100, allowAll = false,
}) {
return (
<div className="pagination">
@ -154,6 +174,7 @@ export function Pagination({
value={pageSize ?? DEFAULT_PAGE_SIZE}
onChange={onPageSizeChange}
max={pageSizeMax}
allowAll={allowAll}
/>
<span className="page-size-total">of <b>{total}</b></span>
</>