diff --git a/frontend/src/components/ds/grid/useTableChrome.js b/frontend/src/components/ds/grid/useTableChrome.js index 17d15358..ec98acc6 100644 --- a/frontend/src/components/ds/grid/useTableChrome.js +++ b/frontend/src/components/ds/grid/useTableChrome.js @@ -1,6 +1,6 @@ import { useCallback, useMemo, useState } from 'react'; import useGridViews from './useGridViews'; -import { OPS, emptyValueFor, isFilterable, ruleId, withInferredTypes } from './fields'; +import { OPS, applyFilters, emptyValueFor, isFilterable, ruleId, withInferredTypes } from './fields'; const NO_FILTERS = { match: 'all', rules: [] }; @@ -149,12 +149,24 @@ export function useTableChrome({ resetToView: api.resetToView, }); + // What the table will ACTUALLY render. `rows` is the page's own already + // searched/filtered array; the column rules are applied by + // itself, so a page counting `rows.length` for its view bar reported "4 of + // 4" while the table showed 2. Computed here, once, from the same engine + // the table uses, so the two can never disagree. + const shownRows = useMemo( + () => applyFilters(rows, filters, orderedColumns), + [rows, filters, orderedColumns], + ); + return { cfg, api, views, columns: orderedColumns, noun, + shownRows, + shownCount: shownRows.length, filterCount: filters.rules.length, drawerOpen, setDrawerOpen, diff --git a/frontend/src/components/layouts/ResourceListPage.jsx b/frontend/src/components/layouts/ResourceListPage.jsx index b452dbd7..d579bc61 100644 --- a/frontend/src/components/layouts/ResourceListPage.jsx +++ b/frontend/src/components/layouts/ResourceListPage.jsx @@ -173,11 +173,6 @@ export default function ResourceListPage({ } }; - const pagedItems = useMemo( - () => (pageSize === 'all' ? items : items.slice(0, pageSize)), - [items, pageSize], - ); - // The chrome — view picker, chip bar, filter drawer, tools menu, shareable // links — comes from the shared hook rather than being adapted again here. // This wrapper used to carry its own copy of that adapter, which is how it @@ -218,6 +213,15 @@ export default function ResourceListPage({ const orderedColumns = chrome.columns; const tableViews = chrome.views; + // Paged AFTER the column rules, not before. Slicing `items` meant a page + // size of 25 took 25 unfiltered rows and THEN filtered them, so "show 25" + // could render three. `chrome.shownRows` is the same set the table renders, + // so the two agree; DataTable re-applying the rules to it is a no-op. + const pagedItems = useMemo( + () => (pageSize === 'all' ? chrome.shownRows : chrome.shownRows.slice(0, pageSize)), + [chrome.shownRows, pageSize], + ); + if (loading) { // Same wrapper as the loaded state below. A skeleton that renders // outside the page's padded container occupies a different box than the @@ -260,7 +264,7 @@ export default function ResourceListPage({ )} @@ -365,7 +369,7 @@ export default function ResourceListPage({ footer={( { { { { { { rowClassName={(job) => (job.enabled ? undefined : 'is-disabled')} footer={( diff --git a/frontend/src/pages/DeliveryLog.jsx b/frontend/src/pages/DeliveryLog.jsx index 91d11a54..e689b9a6 100644 --- a/frontend/src/pages/DeliveryLog.jsx +++ b/frontend/src/pages/DeliveryLog.jsx @@ -268,7 +268,7 @@ export default function DeliveryLog() { { diff --git a/frontend/src/pages/Jobs.jsx b/frontend/src/pages/Jobs.jsx index bca51ac5..0380e28f 100644 --- a/frontend/src/pages/Jobs.jsx +++ b/frontend/src/pages/Jobs.jsx @@ -489,8 +489,8 @@ export default function Jobs() { views={chrome.views} label="jobs" total={hasFilters - ? `${jobs.length} of ${total} matching · ${allJobs} jobs total` - : `${jobs.length} of ${allJobs} jobs`} + ? `${chrome.shownCount} of ${total} matching · ${allJobs} jobs total` + : `${chrome.shownCount} of ${allJobs} jobs`} onCreate={chrome.createView} /> - {monitors.length} monitor{monitors.length === 1 ? '' : 's'} + {chrome.shownCount} of {monitors.length} monitor{monitors.length === 1 ? '' : 's'} {stats?.overall_uptime_30d != null && ( <> · {stats.overall_uptime_30d}% uptime (30d) )} diff --git a/frontend/src/pages/QueueDetail.jsx b/frontend/src/pages/QueueDetail.jsx index 94713b8d..4602a827 100644 --- a/frontend/src/pages/QueueDetail.jsx +++ b/frontend/src/pages/QueueDetail.jsx @@ -389,7 +389,7 @@ const QueueDetail = () => { diff --git a/frontend/src/pages/QueueOperations.jsx b/frontend/src/pages/QueueOperations.jsx index 030dc04c..71843b09 100644 --- a/frontend/src/pages/QueueOperations.jsx +++ b/frontend/src/pages/QueueOperations.jsx @@ -565,7 +565,7 @@ const QueueOperations = () => { diff --git a/frontend/src/pages/Servers.jsx b/frontend/src/pages/Servers.jsx index 93e973e9..4951b247 100644 --- a/frontend/src/pages/Servers.jsx +++ b/frontend/src/pages/Servers.jsx @@ -371,7 +371,7 @@ const Servers = () => { { tableClassName="servers-table" footer={( diff --git a/frontend/src/pages/Telemetry.jsx b/frontend/src/pages/Telemetry.jsx index 9544f168..ceb0f833 100644 --- a/frontend/src/pages/Telemetry.jsx +++ b/frontend/src/pages/Telemetry.jsx @@ -377,7 +377,7 @@ export default function Telemetry() {