From 4667c59573aa8a246bf8beaedb69407c9e878a57 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Dec 2025 19:26:07 +0000 Subject: [PATCH] improve checks fetch and paginaiton --- .../src/components/design-elements/Table.tsx | 111 +++++++++++++++++- client/src/pages/checks/ChecksPage.tsx | 6 +- client/src/pages/checks/ChecksTable.tsx | 9 +- server/src/services/business/CheckService.ts | 26 ++-- 4 files changed, 131 insertions(+), 21 deletions(-) diff --git a/client/src/components/design-elements/Table.tsx b/client/src/components/design-elements/Table.tsx index 2d2b7544e..4dff95fe2 100644 --- a/client/src/components/design-elements/Table.tsx +++ b/client/src/components/design-elements/Table.tsx @@ -10,6 +10,7 @@ import TableContainer from "@mui/material/TableContainer"; import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; import Collapse from "@mui/material/Collapse"; +import Tooltip from "@mui/material/Tooltip"; import IconButton from "@mui/material/IconButton"; import { @@ -18,6 +19,7 @@ import { ChevronLeft, ChevronRight, Coffee, + Ellipsis, PartyPopper, } from "lucide-react"; @@ -331,13 +333,118 @@ function TablePaginationActions(props: TablePaginationActionsProps) { ); } -export const Pagination = ({ ...props }: TablePaginationProps) => { +interface HasMoreTablePaginationActionsProps { + hasMore?: boolean; + page: number; + onPageChange: ( + event: React.MouseEvent, + newPage: number + ) => void; +} +function HasMoreTablePaginationActions( + props: HasMoreTablePaginationActionsProps +) { + const theme = useTheme(); + const { hasMore, page, onPageChange } = props; + console.log(JSON.stringify(props, null, 2)); + + const handleFirstPageButtonClick = ( + event: React.MouseEvent + ) => { + onPageChange(event, 0); + }; + + const handleBackButtonClick = ( + event: React.MouseEvent + ) => { + onPageChange(event, page - 1); + }; + + const handleNextButtonClick = ( + event: React.MouseEvent + ) => { + onPageChange(event, page + 1); + }; + + return ( + + + {theme.direction === "rtl" ? ( + + ) : ( + + )} + + + {theme.direction === "rtl" ? ( + + ) : ( + + )} + + + {theme.direction === "rtl" ? ( + + ) : ( + + )} + + + + + + + + ); +} + +interface PaginationProps extends TablePaginationProps { + hasMore?: boolean; +} + +export const Pagination = ({ ...props }: PaginationProps) => { + const hasMore = props.hasMore; const isSmall = useMediaQuery((theme: any) => theme.breakpoints.down("sm")); const theme = useTheme(); + const labelDisplayedRows = ({ + from, + to, + count, + }: { + from: number; + to: number; + count: number; + }) => { + if (hasMore) { + return to === 0 ? "" : `${from}–${to}`; + } + return `${from}–${to} of ${count}`; + }; + return ( { const monitors: IMonitor[] = monitorResponse?.data || []; const { response: checksResponse, loading: checksLoading } = useGet< - ApiResponse<{ count: number; checks: ICheck[] }> + ApiResponse<{ hasMore: boolean; checks: ICheck[] }> >( `/checks?page=${page}&rowsPerPage=${rowsPerPage}&range=${range}&status=${status}${ selectedMonitorId !== "all" ? `&monitorId=${selectedMonitorId}` : "" @@ -44,7 +44,7 @@ const ChecksPage = () => { ); const checks = checksResponse?.data?.checks || []; - const checksCount = checksResponse?.data?.count || 0; + const hasMore = checksResponse?.data?.hasMore; return ( @@ -97,7 +97,7 @@ const ChecksPage = () => { void; rowsPerPage: number; @@ -76,7 +76,7 @@ export const ChecksTable = ({ }, { id: "message", - content: t("checks.table.headers.message"), + content: t("common.table.headers.message"), render: (row) => { return row.message || "N/A"; }, @@ -114,7 +114,8 @@ export const ChecksTable = ({ /> Promise<{ checks: ICheck[]; count: number }>; + ) => Promise<{ checks: ICheck[]; hasMore: boolean }>; getCheckById: (checkId: string, teamId: string) => Promise; @@ -242,24 +242,26 @@ class CheckService implements ICheckService { status, "metadata.teamId": new mongoose.Types.ObjectId(teamId), "metadata.monitorId": new mongoose.Types.ObjectId(monitorId), - createdAt: { $gte: startDate }, + createdAt: { $gte: startDate, $lte: new Date() }, }; } else { match = { status, "metadata.teamId": new mongoose.Types.ObjectId(teamId), - createdAt: { $gte: startDate }, + createdAt: { $gte: startDate, $lte: new Date() }, }; } - const [count, checks] = await Promise.all([ - Check.countDocuments(match), - Check.find(match) - .populate({ path: "metadata.monitorId", select: "name" }) - .sort({ createdAt: -1 }) - .skip(page * rowsPerPage) - .limit(rowsPerPage), - ]); - return { checks, count }; + // Has-more pagination: over-fetch by 1 to infer existence of a next page + const pageSizePlusOne = rowsPerPage + 1; + const docs = await Check.find(match) + .populate({ path: "metadata.monitorId", select: "name" }) + .sort({ createdAt: -1 }) + .skip(page * rowsPerPage) + .limit(pageSizePlusOne); + + const hasMore = docs.length > rowsPerPage; + const checks = hasMore ? docs.slice(0, rowsPerPage) : docs; + return { checks, hasMore }; }; getCheckById = async (checkId: string, teamId: string) => {