diff --git a/client/src/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable.tsx b/client/src/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable.tsx index 9e9c6e89b..9d1789264 100644 --- a/client/src/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable.tsx +++ b/client/src/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable.tsx @@ -6,8 +6,9 @@ import { Pagination, StatusLabel, StrategyBadge, + ColoredLabel, } from "@/Components/design-elements"; -import { LAYOUT } from "@/Utils/Theme/constants"; +import { LAYOUT, SPACING } from "@/Utils/Theme/constants"; import { HistogramPageSpeed } from "@/Components/monitors"; import { ArrowDown, ArrowUp } from "lucide-react"; import type { Header } from "@/Components/design-elements/Table"; @@ -20,10 +21,12 @@ import { useNavigate } from "react-router-dom"; import { usePatch } from "@/Hooks/UseApi"; import type { Monitor, MonitorWithChecks } from "@/Types/Monitor"; +import type { Tag } from "@/Types/Tag"; import type { ActionMenuItem } from "@/Components/actions-menu"; export const PageSpeedMonitorsTable = ({ monitors, + tags, refetch, setSelectedMonitor, sortField, @@ -37,6 +40,7 @@ export const PageSpeedMonitorsTable = ({ setRowsPerPage, }: { monitors: MonitorWithChecks[]; + tags: Tag[]; refetch: Function; setSelectedMonitor: Function; sortField: string; @@ -233,6 +237,32 @@ export const PageSpeedMonitorsTable = ({ ); }, }, + { + id: "tags", + content: t("common.table.headers.tags"), + render: (row) => { + if (row.tags.length === 0) return "-"; + return ( + + {row.tags.map((tagId) => { + const fullTag = tags.find((tag) => tag.id === tagId); + if (!fullTag) return null; + return ( + + ); + })} + + ); + }, + }, { id: "actions", diff --git a/client/src/Pages/PageSpeed/Monitors/index.tsx b/client/src/Pages/PageSpeed/Monitors/index.tsx index 4cb28b51e..b9d0b49b1 100644 --- a/client/src/Pages/PageSpeed/Monitors/index.tsx +++ b/client/src/Pages/PageSpeed/Monitors/index.tsx @@ -1,22 +1,32 @@ import { MonitorBasePageWithStates, PageSpeedKeyPriorityFallback, + ColoredLabel, } from "@/Components/design-elements"; -import { Dialog } from "@/Components/inputs"; +import { Dialog, TextField } from "@/Components/inputs"; import { HeaderCreate } from "@/Components/common"; import { PageSpeedMonitorsTable } from "@/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable"; import type { Monitor } from "@/Types/Monitor"; +import type { Tag } from "@/Types/Tag"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import { useTheme } from "@mui/material"; +import { SPACING } from "@/Utils/Theme/constants"; import { useTranslation } from "react-i18next"; -import { useMemo, useState } from "react"; +import { useCallback, useMemo, useState } from "react"; import { useIsAdmin } from "@/Hooks/useIsAdmin"; import { useGet, useDelete } from "@/Hooks/UseApi"; +import useDebounce from "@/Hooks/useDebounce"; import type { MonitorsWithChecksResponse } from "@/Types/Monitor"; import type { AppSettingsResponse } from "@/Types/Settings"; -import { HeaderMonitorsSummary } from "@/Components/monitors"; +import { ControlsFilter, HeaderMonitorsSummary } from "@/Components/monitors"; const PageSpeedMonitorsPage = () => { const { t } = useTranslation(); + const theme = useTheme(); + const isSmall = useMediaQuery(theme.breakpoints.down("md")); const isAdmin = useIsAdmin(); const { deleteFn, loading: isDeleting } = useDelete(); @@ -33,17 +43,50 @@ const PageSpeedMonitorsPage = () => { const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc"); const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(10); + const [selectedTags, setSelectedTags] = useState([]); + const [selectedStatus, setSelectedStatus] = useState(""); + const [selectedState, setSelectedState] = useState(""); + const [search, setSearch] = useState(""); + + const debouncedSearch = useDebounce(search, 300); + + // Status: pass "up"/"down" directly to the API + // State: "active" -> true, "paused" -> false + const toFilterStatus = useMemo(() => { + if (selectedStatus === "up") return "up"; + if (selectedStatus === "down") return "down"; + return undefined; + }, [selectedStatus]); + + const toFilterActive = useMemo(() => { + if (selectedState === "active") return "true"; + if (selectedState === "paused") return "false"; + return undefined; + }, [selectedState]); + + // Priority: status > isActive > search > sort + const filterLookup = new Map([ + [toFilterStatus, "status"], + [toFilterActive, "isActive"], + ]); + const activeFilter = [...filterLookup].find(([key]) => key !== undefined); + const field = activeFilter?.[1] || (debouncedSearch ? "name" : sortField || undefined); + const filter = activeFilter?.[0] || debouncedSearch; + + const { data: tags } = useGet("/tags/team"); const monitorsWithChecksUrl = useMemo(() => { const params = new URLSearchParams(); params.append("type", "pagespeed"); + selectedTags.forEach((tagId) => params.append("tags", tagId)); params.append("limit", "25"); if (page !== undefined) params.append("page", String(page)); if (rowsPerPage) params.append("rowsPerPage", String(rowsPerPage)); - if (sortField) params.append("field", sortField); + if (filter) params.append("filter", filter); + if (field) params.append("field", field); if (sortOrder) params.append("order", sortOrder); return `/monitors/team/with-checks?${params.toString()}`; - }, [page, rowsPerPage, sortField, sortOrder]); + }, [page, rowsPerPage, sortOrder, selectedTags, filter, field]); const { data: monitorsData, @@ -75,12 +118,28 @@ const PageSpeedMonitorsPage = () => { setSelectedMonitor(null); }; + const handleClearFilters = useCallback(() => { + setSelectedStatus(""); + setSelectedState(""); + setSelectedTags([]); + setSearch(""); + }, []); + + const hasActiveFilters = Boolean( + selectedStatus || selectedState || selectedTags.length > 0 || search + ); + + const effectiveTotalCount = + hasActiveFilters && (summary?.totalMonitors ?? 0) === 0 + ? 1 + : (summary?.totalMonitors ?? 0); + return ( : undefined} @@ -91,8 +150,59 @@ const PageSpeedMonitorsPage = () => { isAdmin={isAdmin} /> + + + + { + setSearch(event.target.value); + }} + /> + + + {selectedTags.length > 0 && ( + + + {t("pages.uptime.filters.activeTags")} + + {selectedTags.map((tagId) => { + const tag = tags?.find((t) => t.id === tagId); + if (!tag) return null; + return ( + + ); + })} + + )} +