mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 12:58:13 +00:00
feat(data-table): typeable rows-per-page control
The footer offered a fixed <select> of 5/10/25/50, so a table could not be paged in anything larger without a code change. Replace it with a Mantine Autocomplete: the presets go up to 500, and any other size can be typed. A typed value is clamped to [1, MAX_PAGE_SIZE] on commit, with MAX_PAGE_SIZE set to 500 to match @Max on the freight API's PaginationQueryDto and MAX_PAGE_SIZE in its pagination.util. Raising it here alone would turn the top preset into a 400. Changing the size also resets to the first page -- page 12 of 50-row pages does not exist once the page holds 500, and a manual-pagination consumer would happily request it. Done as one setPagination call so consumers see a single fetch rather than two.
This commit is contained in:
@@ -1,6 +1,24 @@
|
|||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { Autocomplete } from "@mantine/core";
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import { DataTableFooterProps } from "./types";
|
import { DataTableFooterProps } from "./types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upper bound on a hand-typed page size. Not a free choice — the server has to
|
||||||
|
* accept it. It is kept in step with `@Max` on the freight API's
|
||||||
|
* `PaginationQueryDto.pageSize` and with `MAX_PAGE_SIZE` in its
|
||||||
|
* `common/utils/pagination.util.ts`; raising it here alone turns the top preset
|
||||||
|
* into a 400 on every endpoint that validates against those.
|
||||||
|
*/
|
||||||
|
export const MAX_PAGE_SIZE = 500;
|
||||||
|
|
||||||
|
/** Clamp a typed page size to a whole number in [1, MAX_PAGE_SIZE]; null if unusable. */
|
||||||
|
export function clampPageSize(raw: string | number): number | null {
|
||||||
|
const parsed = Math.trunc(Number(raw));
|
||||||
|
if (!Number.isFinite(parsed) || parsed < 1) return null;
|
||||||
|
return Math.min(parsed, MAX_PAGE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
export interface DataTableFooterOptions {
|
export interface DataTableFooterOptions {
|
||||||
pageSizeOptions?: number[];
|
pageSizeOptions?: number[];
|
||||||
showPageSizeSelector?: boolean;
|
showPageSizeSelector?: boolean;
|
||||||
@@ -25,7 +43,7 @@ interface DataTableFooterComponentProps<
|
|||||||
}
|
}
|
||||||
|
|
||||||
const defaultOptions: DataTableFooterOptions = {
|
const defaultOptions: DataTableFooterOptions = {
|
||||||
pageSizeOptions: [5, 10, 25, 50],
|
pageSizeOptions: [5, 10, 25, 50, 100, 200, 500],
|
||||||
showPageSizeSelector: true,
|
showPageSizeSelector: true,
|
||||||
showRowCount: true,
|
showRowCount: true,
|
||||||
showPagination: true,
|
showPagination: true,
|
||||||
@@ -56,8 +74,36 @@ export function DataTableFooter<TData>({
|
|||||||
const start = totalCount === 0 ? 0 : pageIndex * pageSize + 1;
|
const start = totalCount === 0 ? 0 : pageIndex * pageSize + 1;
|
||||||
const end = Math.min((pageIndex + 1) * pageSize, totalCount);
|
const end = Math.min((pageIndex + 1) * pageSize, totalCount);
|
||||||
|
|
||||||
|
// Back to the first page: page 12 of 50-row pages does not exist once the
|
||||||
|
// rows per page becomes 500, and a manual-pagination consumer would happily
|
||||||
|
// request it. One state update, so consumers see a single fetch.
|
||||||
const handlePageSizeChange = (newPageSize: number) => {
|
const handlePageSizeChange = (newPageSize: number) => {
|
||||||
table?.setPageSize(newPageSize);
|
table?.setPagination({ pageIndex: 0, pageSize: newPageSize });
|
||||||
|
};
|
||||||
|
|
||||||
|
// The presets, with the active size folded in when it was typed by hand, so
|
||||||
|
// the dropdown always contains what the input shows.
|
||||||
|
const sizeOptions = useMemo(() => {
|
||||||
|
const presets = opts.pageSizeOptions ?? [];
|
||||||
|
const all = presets.includes(pageSize) ? presets : [...presets, pageSize];
|
||||||
|
return [...all].sort((a, b) => a - b).map(String);
|
||||||
|
}, [opts.pageSizeOptions, pageSize]);
|
||||||
|
|
||||||
|
const [sizeDraft, setSizeDraft] = useState(`${pageSize}`);
|
||||||
|
|
||||||
|
// Keep the input honest when the page size changes from anywhere else.
|
||||||
|
useEffect(() => {
|
||||||
|
setSizeDraft(`${pageSize}`);
|
||||||
|
}, [pageSize]);
|
||||||
|
|
||||||
|
const commitPageSize = (raw: string) => {
|
||||||
|
const size = clampPageSize(raw);
|
||||||
|
if (size === null) {
|
||||||
|
setSizeDraft(`${pageSize}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSizeDraft(`${size}`);
|
||||||
|
if (size !== pageSize) handlePageSizeChange(size);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -69,18 +115,23 @@ export function DataTableFooter<TData>({
|
|||||||
<label htmlFor="page-size" className="font-medium text-slate-700">
|
<label htmlFor="page-size" className="font-medium text-slate-700">
|
||||||
{labels.rowsPerPage}
|
{labels.rowsPerPage}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<Autocomplete
|
||||||
id="page-size"
|
id="page-size"
|
||||||
value={pageSize}
|
aria-label={labels.rowsPerPage}
|
||||||
onChange={(e) => handlePageSizeChange(Number(e.target.value))}
|
size="sm"
|
||||||
className="h-9 rounded-xl border border-slate-200 bg-white px-3 text-sm text-slate-700 outline-none transition focus:border-[#33578D]/50 focus:ring-2 focus:ring-[#33578D]/20"
|
w={96}
|
||||||
>
|
inputMode="numeric"
|
||||||
{opts.pageSizeOptions?.map((size) => (
|
value={sizeDraft}
|
||||||
<option key={size} value={size}>
|
data={sizeOptions}
|
||||||
{size}
|
comboboxProps={{ position: "top", withinPortal: true }}
|
||||||
</option>
|
onChange={setSizeDraft}
|
||||||
))}
|
onOptionSubmit={commitPageSize}
|
||||||
</select>
|
onBlur={() => commitPageSize(sizeDraft)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") commitPageSize(sizeDraft);
|
||||||
|
if (e.key === "Escape") setSizeDraft(`${pageSize}`);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{opts.showRowCount && (
|
{opts.showRowCount && (
|
||||||
|
|||||||
Reference in New Issue
Block a user