From 1e7b8f08cf7e4b6ec86d7e6198cefcda09527d04 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Mon, 29 Jun 2026 22:26:39 +0000 Subject: [PATCH] Address review: tidy pagination handling and comments Let useTableQueryState accept a full TablePaginationConfig (only defaultPageSize is read) so UsersTable passes its pagination prop straight through instead of extracting and casting it. Drop the redundant pagination: false from the tables that just want it disabled (that is already the default). Clarify the comment on the Last Seen online-only default to mention the onlyOnline prop, and explain why onChange only uses the first sorter. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/web_ui/src/components/HostsPage.tsx | 2 +- packages/web_ui/src/components/InstanceList.tsx | 2 +- packages/web_ui/src/components/ModsPage.tsx | 4 ++-- packages/web_ui/src/components/PluginsPage.tsx | 2 +- packages/web_ui/src/components/RolesPage.tsx | 2 +- packages/web_ui/src/components/SavesList.tsx | 2 +- packages/web_ui/src/components/UserViewPage.tsx | 2 +- packages/web_ui/src/components/UsersTable.tsx | 14 +++++++------- packages/web_ui/src/util/useTableQueryState.ts | 6 ++++-- 9 files changed, 19 insertions(+), 17 deletions(-) diff --git a/packages/web_ui/src/components/HostsPage.tsx b/packages/web_ui/src/components/HostsPage.tsx index 41ecc9a2..9219218e 100644 --- a/packages/web_ui/src/components/HostsPage.tsx +++ b/packages/web_ui/src/components/HostsPage.tsx @@ -208,7 +208,7 @@ export default function HostsPage() { const [showNumbers, setShowNumbers] = useState(false); const [showCpuModel, setShowCpuModel] = useState(false); const tableState = useTableQueryState({ - namespace: "host", defaultSortKey: "name", pagination: false, + namespace: "host", defaultSortKey: "name", }); const nameSearch = useColumnSearch(host => host.name, "Search hosts"); diff --git a/packages/web_ui/src/components/InstanceList.tsx b/packages/web_ui/src/components/InstanceList.tsx index 492e21eb..478d66f2 100644 --- a/packages/web_ui/src/components/InstanceList.tsx +++ b/packages/web_ui/src/components/InstanceList.tsx @@ -31,7 +31,7 @@ export default function InstanceList(props: InstanceListProps) { let [hosts] = useHosts(); const [systems] = useSystems(); const tableState = useTableQueryState({ - namespace: "instance", defaultSortKey: "name", pagination: false, + namespace: "instance", defaultSortKey: "name", }); const nameSearch = useColumnSearch(instance => instance.name, "Search instances"); diff --git a/packages/web_ui/src/components/ModsPage.tsx b/packages/web_ui/src/components/ModsPage.tsx index 99c22d95..8ca6b83b 100644 --- a/packages/web_ui/src/components/ModsPage.tsx +++ b/packages/web_ui/src/components/ModsPage.tsx @@ -511,10 +511,10 @@ export default function ModsPage() { let [mods] = useMods(); let [modPacks] = useModPacks(); const modPackTable = useTableQueryState({ - namespace: "modPack", defaultSortKey: "name", pagination: false, + namespace: "modPack", defaultSortKey: "name", }); const modTable = useTableQueryState({ - namespace: "mod", defaultSortKey: "title", pagination: false, + namespace: "mod", defaultSortKey: "title", }); function actions(mod: lib.ModInfo) { diff --git a/packages/web_ui/src/components/PluginsPage.tsx b/packages/web_ui/src/components/PluginsPage.tsx index ffe95355..13ee636d 100644 --- a/packages/web_ui/src/components/PluginsPage.tsx +++ b/packages/web_ui/src/components/PluginsPage.tsx @@ -27,7 +27,7 @@ export default function PluginsPage() { let navigate = useNavigate(); let [pluginList, setPluginList] = useState([]); const tableState = useTableQueryState({ - namespace: "plugin", defaultSortKey: "name", pagination: false, + namespace: "plugin", defaultSortKey: "name", }); const nameSearch = useColumnSearch( plugin => (plugin.info ? plugin.info.title : plugin.meta.name), "Search plugins" diff --git a/packages/web_ui/src/components/RolesPage.tsx b/packages/web_ui/src/components/RolesPage.tsx index faedd8c8..44b34e07 100644 --- a/packages/web_ui/src/components/RolesPage.tsx +++ b/packages/web_ui/src/components/RolesPage.tsx @@ -65,7 +65,7 @@ export default function RolesPage() { let account = useAccount(); let navigate = useNavigate(); const [roles] = useRoles(); - const tableState = useTableQueryState({ namespace: "role", pagination: false }); + const tableState = useTableQueryState({ namespace: "role" }); const nameSearch = useColumnSearch(role => role.name, "Search roles"); return diff --git a/packages/web_ui/src/components/SavesList.tsx b/packages/web_ui/src/components/SavesList.tsx index b949c3a4..6219899e 100644 --- a/packages/web_ui/src/components/SavesList.tsx +++ b/packages/web_ui/src/components/SavesList.tsx @@ -206,7 +206,7 @@ export default function SavesList(props: { instance: lib.InstanceDetails }) { let [starting, setStarting] = useState(false); let [uploadingFiles, setUploadingFiles] = useState([]); const tableState = useTableQueryState({ - namespace: "save", defaultSortKey: "mtimeMs", defaultSortOrder: "descend", pagination: false, + namespace: "save", defaultSortKey: "mtimeMs", defaultSortOrder: "descend", }); let hostOffline = ["unassigned", "unknown"].includes(props.instance.status!); diff --git a/packages/web_ui/src/components/UserViewPage.tsx b/packages/web_ui/src/components/UserViewPage.tsx index 491044f3..fd0c2eea 100644 --- a/packages/web_ui/src/components/UserViewPage.tsx +++ b/packages/web_ui/src/components/UserViewPage.tsx @@ -40,7 +40,7 @@ export default function UserViewPage() { let [roles] = useRoles(); let [form] = Form.useForm(); const statsTable = useTableQueryState<[number, lib.PlayerStats]>({ - namespace: "stats", defaultSortKey: "instance", pagination: false, + namespace: "stats", defaultSortKey: "instance", }); let [rolesDirty, setRolesDirty] = useState(false); let [banReasonDirty, setBanReasonDirty] = useState(false); diff --git a/packages/web_ui/src/components/UsersTable.tsx b/packages/web_ui/src/components/UsersTable.tsx index fa5816c6..11d17163 100644 --- a/packages/web_ui/src/components/UsersTable.tsx +++ b/packages/web_ui/src/components/UsersTable.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Table, Tag } from "antd"; +import { Table, Tag, type TablePaginationConfig } from "antd"; import { SearchOutlined } from "@ant-design/icons"; import { useNavigate } from "react-router-dom"; @@ -23,7 +23,7 @@ export interface UsersTableProps { /** Show only players that are currently online, works with instanceId to show online of a particular instance */ onlyOnline?: boolean; /** Ant Design pagination prop. Pass `false` to disable pagination. */ - pagination?: false | object; + pagination?: false | TablePaginationConfig; /** Ant Design size prop */ size?: "small" | "middle" | "large"; } @@ -36,12 +36,11 @@ export default function UsersTable( const [roles, rolesSynced] = useRoles(); const [users, usersSynced] = useUsers(); const navigate = useNavigate(); - const callerPagination = pagination && typeof pagination === "object" ? pagination : undefined; - const defaultPageSize = (callerPagination as { defaultPageSize?: number })?.defaultPageSize ?? 50; const tableState = useTableQueryState({ namespace: "user", defaultSortKey: "name", - pagination: pagination === false ? false : { defaultPageSize }, + // Default to 50 per page when the caller does not specify a pagination config. + pagination: pagination === false ? false : (pagination ?? { defaultPageSize: 50 }), filterCodecs: { name: userFilterCodec }, }); @@ -168,7 +167,8 @@ export default function UsersTable( key: "lastSeen", filterMultiple: false, filters: durationFilters, - // Default to online-only when nothing for this column is in the URL yet. + // With the onlyOnline prop set, seed this column's filter to "online" until the + // URL provides one, so the table starts out showing only online players. filteredValue: tableState.filteredValue("lastSeen") ?? (onlyOnline ? ["online"] : null), onFilter: (value: any, record: lib.UserDetails) => onFilterDuration(value, record, calculateLastSeen), sorter: (a: lib.UserDetails, b: lib.UserDetails) => sortLastSeen(a, b, instanceId, instanceId), @@ -187,7 +187,7 @@ export default function UsersTable( tablePagination = false; } else { // Controlled current/pageSize from the URL win over the caller's display props. - tablePagination = { ...displayPagination, ...callerPagination, ...(tableState.pagination || {}) }; + tablePagination = { ...displayPagination, ...pagination, ...(tableState.pagination || {}) }; } return ( diff --git a/packages/web_ui/src/util/useTableQueryState.ts b/packages/web_ui/src/util/useTableQueryState.ts index f4b92002..dad0be56 100644 --- a/packages/web_ui/src/util/useTableQueryState.ts +++ b/packages/web_ui/src/util/useTableQueryState.ts @@ -15,8 +15,8 @@ export interface TableQueryStateOptions { defaultSortKey?: string; /** Direction of the default sort. Default "ascend". */ defaultSortOrder?: SortOrder; - /** Pass an object to persist pagination; omit/false for `pagination={false}` tables. */ - pagination?: false | { defaultPageSize?: number }; + /** Pagination config to persist pagination; only `defaultPageSize` is read (omit for `pagination={false}`). */ + pagination?: false | TablePaginationConfig; /** Per-column-key codecs to persist a bundled filter value as separate readable params. */ filterCodecs?: Record; } @@ -59,6 +59,8 @@ export default function useTableQueryState(options: TableQueryStateOptions): return { onChange(pagination, tableFilters, sorter) { + // Only single-column sort is persisted; antd passes an array when several + // columns are sorted, so take the first (primary) sorter. const single = Array.isArray(sorter) ? sorter[0] : sorter; const order = single && single.order ? single.order : undefined; const columnKey = order ? String(single.columnKey ?? single.field ?? "") : undefined;