From 4e284c146893e9a1bfdd4b87eaa7deeee565d028 Mon Sep 17 00:00:00 2001 From: Retr01-sudo Date: Fri, 1 May 2026 19:44:07 +0530 Subject: [PATCH] feat(monitors): integrate bulk actions into uptime and infrastructure tables - Mounts selection checkboxes in UptimeMonitorsTable and MonitorsTable - Enhances global Table.tsx to cleanly render checkboxes on mobile cards via mobileLabel - Injects BulkActionsBar into main list views Fixes #3602 --- .../src/Components/design-elements/Table.tsx | 23 ++++- .../Monitors/Components/MonitorsTable.tsx | 91 ++++++++++++------ .../Pages/Infrastructure/Monitors/index.tsx | 29 +++++- .../Components/UptimeMonitorsTable.tsx | 95 +++++++++++++------ client/src/Pages/Uptime/Monitors/index.tsx | 29 +++++- 5 files changed, 208 insertions(+), 59 deletions(-) diff --git a/client/src/Components/design-elements/Table.tsx b/client/src/Components/design-elements/Table.tsx index 91bb69615..121d890d1 100644 --- a/client/src/Components/design-elements/Table.tsx +++ b/client/src/Components/design-elements/Table.tsx @@ -35,6 +35,7 @@ import useMediaQuery from "@mui/material/useMediaQuery"; export type Header = { id: number | string; content: React.ReactNode; + mobileLabel?: React.ReactNode; onClick?: (event: React.MouseEvent, row: T) => void; render: (row: T) => React.ReactNode; }; @@ -109,6 +110,24 @@ export function DataTable< key={key} > {headers.map((header) => { + if (header.mobileLabel === null) { + return ( + + + {header.render(row)} + + + ); + } + return ( - {header.content} + {header.mobileLabel !== undefined + ? header.mobileLabel + : header.content} void; + setSelectedMonitor: (monitor: Monitor | null) => void; + sortField: string; + setSortField: (field: string) => void; + sortOrder: "asc" | "desc"; + setSortOrder: (order: "asc" | "desc") => void; + count: number; + page: number; + setPage: (page: number) => void; + rowsPerPage: number; + setRowsPerPage: (rowsPerPage: number) => void; + selectedRows?: string[]; + onSelectionChange?: (selected: string[]) => void; +} export const InfraMonitorsTable = ({ monitors, @@ -26,29 +45,22 @@ export const InfraMonitorsTable = ({ setPage, rowsPerPage, setRowsPerPage, -}: { - monitors: Monitor[]; - refetch: Function; - setSelectedMonitor: Function; - sortField: string; - setSortField: (field: string) => void; - sortOrder: "asc" | "desc"; - setSortOrder: (order: "asc" | "desc") => void; - count: number; - page: number; - setPage: (page: number) => void; - rowsPerPage: number; - setRowsPerPage: (rowsPerPage: number) => void; -}) => { + selectedRows = [], + onSelectionChange, +}: InfraMonitorsTableProps) => { const { t } = useTranslation(); const theme = useTheme(); const isSmall = useMediaQuery(theme.breakpoints.down("md")); const navigate = useNavigate(); + const { post } = usePost, Monitor>(); + const { - post, - // loading: isPatching, - // error: postError, - } = usePost(); + isAllSelected, + isSomeSelected, + handleSelectAll, + handleSelectRow, + isRowSelected, + } = useTableSelection(monitors, selectedRows, onSelectionChange); const handlePageChange = ( _e: React.MouseEvent | null, @@ -65,7 +77,7 @@ export const InfraMonitorsTable = ({ setRowsPerPage(value); }; - const handleSort = (e: any, field: string) => { + const handleSort = (e: React.MouseEvent, field: string) => { e.preventDefault(); e.stopPropagation(); if (sortField === field) { @@ -101,13 +113,6 @@ export const InfraMonitorsTable = ({ navigate(`/infrastructure/configure/${monitor.id}`); }, }, - // { - // id: 5, - // label: "Clone", - // action: () => { - - // }, - // }, { id: 6, label: @@ -115,7 +120,7 @@ export const InfraMonitorsTable = ({ ? t("common.buttons.resume") : t("common.buttons.pause"), action: async () => { - await post(`/monitors/pause/${monitor.id}`, {}); + await post(`/monitors/pause/${monitor.id}`, {} as Record); refetch(); }, closeMenu: true, @@ -152,8 +157,34 @@ export const InfraMonitorsTable = ({ ); const headers: Header[] = [ + { + id: "selection", + content: ( + handleSelectAll(e.target.checked)} + /> + ), + mobileLabel: null, + render: (row) => ( + { + handleSelectRow(row.id, e.target.checked); + }} + onClick={(e) => e.stopPropagation()} + /> + ), + onClick: (e) => e?.stopPropagation(), + }, { id: "name", + mobileLabel: t("common.table.headers.name"), content: ( { navigate(`/infrastructure/${row.id}`); }} + getRowSx={(row) => ({ + backgroundColor: isRowSelected(row.id) + ? theme.palette.action.selected + : "inherit", + })} /> { const { t } = useTranslation(); @@ -37,6 +42,7 @@ const InfrastructureMonitors = () => { const [sortField, setSortField] = useState(""); const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc"); const [selectedMonitor, setSelectedMonitor] = useState(null); + const isDialogOpen = Boolean(selectedMonitor); const debouncedSearch = useDebounce(search, 300); @@ -97,6 +103,15 @@ const InfrastructureMonitors = () => { const { summary, count } = monitorsWithChecksData ?? { summary: null, count: 0 }; const isLoading = monitorsWithChecksLoading; + // Bulk actions + const { + selectedRows, + setSelectedRows, + handleBulkPause, + handleBulkResume, + handleCancelSelection, + } = useBulkMonitorActions(refetch, page); + // Check if any filters are active const hasActiveFilters = Boolean(selectedStatus || selectedState || search); @@ -159,6 +174,16 @@ const InfrastructureMonitors = () => { }} /> + + {!isLoading && ( + + )} + { ); setPage(0); }} + selectedRows={selectedRows} + onSelectionChange={setSelectedRows} /> void; + setSelectedMonitor: (monitor: Monitor | null) => void; + sortField: string; + setSortField: (field: string) => void; + sortOrder: "asc" | "desc"; + setSortOrder: (order: "asc" | "desc") => void; + count: number; + page: number; + setPage: (page: number) => void; + rowsPerPage: number; + setRowsPerPage: (rowsPerPage: number) => void; + selectedRows?: string[]; + onSelectionChange?: (selected: string[]) => void; +} export const MonitorTable = ({ monitors, @@ -34,31 +53,24 @@ export const MonitorTable = ({ setPage, rowsPerPage, setRowsPerPage, -}: { - monitors: Monitor[]; - refetch: Function; - setSelectedMonitor: (monitor: Monitor | null) => void; - sortField: string; - setSortField: (field: string) => void; - sortOrder: "asc" | "desc"; - setSortOrder: (order: "asc" | "desc") => void; - count: number; - page: number; - setPage: (page: number) => void; - rowsPerPage: number; - setRowsPerPage: (rowsPerPage: number) => void; -}) => { + selectedRows = [], + onSelectionChange, +}: MonitorTableProps) => { const { t } = useTranslation(); const theme = useTheme(); const navigate = useNavigate(); const chartType = useSelector((state: RootState) => state.ui?.chartType ?? "histogram"); - const { - post, - // loading: isPosting, - // error: postError, - } = usePost(); + const { post } = usePost, Monitor>(); - const handleSort = (e: any, field: string) => { + const { + isAllSelected, + isSomeSelected, + handleSelectAll, + handleSelectRow, + isRowSelected, + } = useTableSelection(monitors, selectedRows, onSelectionChange); + + const handleSort = (e: React.MouseEvent, field: string) => { e.preventDefault(); e.stopPropagation(); if (sortField === field) { @@ -110,13 +122,6 @@ export const MonitorTable = ({ navigate(`/uptime/configure/${monitor.id}`); }, }, - // { - // id: 5, - // label: "Clone", - // action: () => { - - // }, - // }, { id: 6, label: @@ -124,7 +129,7 @@ export const MonitorTable = ({ ? t("pages.common.monitors.actions.resume") : t("pages.common.monitors.actions.pause"), action: async () => { - await post(`/monitors/pause/${monitor.id}`, {}); + await post(`/monitors/pause/${monitor.id}`, {} as Record); refetch(); }, closeMenu: true, @@ -160,8 +165,34 @@ export const MonitorTable = ({ ); const headers: Header[] = [ + { + id: "selection", + content: ( + handleSelectAll(e.target.checked)} + /> + ), + mobileLabel: null, + render: (row) => ( + { + handleSelectRow(row.id, e.target.checked); + }} + onClick={(e) => e.stopPropagation()} + /> + ), + onClick: (e) => e?.stopPropagation(), + }, { id: "name", + mobileLabel: t("common.table.headers.name"), content: ( handleSort(e, "status")} sx={{ cursor: "pointer" }} @@ -212,6 +243,7 @@ export const MonitorTable = ({ }, { id: "type", + mobileLabel: t("common.table.headers.type"), content: ( { navigate(`/uptime/${row.id}`); }} + getRowSx={(row) => ({ + backgroundColor: isRowSelected(row.id) + ? theme.palette.action.selected + : "inherit", + })} /> { const { t } = useTranslation(); @@ -37,6 +42,7 @@ const UptimeMonitorsPage = () => { const [sortField, setSortField] = useState(""); const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc"); const [selectedMonitor, setSelectedMonitor] = useState(null); + const isDialogOpen = Boolean(selectedMonitor); const debouncedSearch = useDebounce(search, 300); @@ -102,6 +108,15 @@ const UptimeMonitorsPage = () => { count, } = monitorsWithChecksData ?? { monitors: null, summary: null, count: 0 }; + // Bulk actions + const { + selectedRows, + setSelectedRows, + handleBulkPause, + handleBulkResume, + handleCancelSelection, + } = useBulkMonitorActions(refetch, page); + // Delete hook const { deleteFn, loading: isDeleting } = useDelete(); @@ -177,6 +192,16 @@ const UptimeMonitorsPage = () => { }} /> + + {!isLoading && ( + + )} + { ); setPage(0); }} + selectedRows={selectedRows} + onSelectionChange={setSelectedRows} />