feat(ui): add bulk edit notifications modal and checkmark selection for uptime monitors
This commit is contained in:
1 parent
c5f0316990
commit
27dd565bc6
5 files changed
+263
-81
No files matched your search
Generated
+35
-65
@@ -18,6 +18,7 @@
|
||||
"@mui/x-date-pickers": "7.29.4",
|
||||
"@reduxjs/toolkit": "2.7.0",
|
||||
"@types/maplibre-gl": "^1.13.2",
|
||||
"@types/react-map-gl": "^6.1.8",
|
||||
"axios": "^1.7.4",
|
||||
"dayjs": "1.11.13",
|
||||
"flag-icons": "7.3.2",
|
||||
@@ -434,7 +435,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -451,7 +451,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -468,7 +467,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -485,7 +483,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -502,7 +499,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -519,7 +515,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -536,7 +531,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -553,7 +547,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -570,7 +563,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -587,7 +579,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -604,7 +595,6 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -621,7 +611,6 @@
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -638,7 +627,6 @@
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -655,7 +643,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -672,7 +659,6 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -689,7 +675,6 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -704,7 +689,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -721,7 +705,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -738,7 +721,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -755,7 +737,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -772,7 +753,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -789,7 +769,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -806,7 +785,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -823,7 +801,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -840,7 +817,6 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -857,7 +833,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1666,7 +1641,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1680,7 +1654,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1694,7 +1667,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1708,7 +1680,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1722,7 +1693,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1736,7 +1706,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1750,7 +1719,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1764,7 +1732,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1778,7 +1745,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1792,7 +1758,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1806,7 +1771,6 @@
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1820,7 +1784,6 @@
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1834,7 +1797,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1848,7 +1810,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1862,7 +1823,6 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1876,7 +1836,6 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1890,7 +1849,6 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1902,7 +1860,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1916,7 +1873,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1930,7 +1886,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1944,7 +1899,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1958,7 +1912,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1972,7 +1925,6 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1986,7 +1938,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2000,7 +1951,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2339,6 +2289,15 @@
|
||||
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/mapbox-gl": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-3.4.1.tgz",
|
||||
"integrity": "sha512-NsGKKtgW93B+UaLPti6B7NwlxYlES5DpV5Gzj9F75rK5ALKsqSk15CiEHbOnTr09RGbr6ZYiCdI+59NNNcAImg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/geojson": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/maplibre-gl": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/maplibre-gl/-/maplibre-gl-1.13.2.tgz",
|
||||
@@ -2350,7 +2309,7 @@
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.5.2",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.12.0"
|
||||
@@ -2366,7 +2325,6 @@
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.27",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
@@ -2381,6 +2339,18 @@
|
||||
"@types/react": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-map-gl": {
|
||||
"version": "6.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-map-gl/-/react-map-gl-6.1.8.tgz",
|
||||
"integrity": "sha512-M0WLRx7Q7m47aFoNRu7NxwaboY/K12J+zgSesJnwisIq5EgqkGUdl9AUb/yRd0cJtx6oOTeOMVPb7emEdlXpQg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/geojson": "*",
|
||||
"@types/mapbox-gl": "<3.5",
|
||||
"@types/react": "*",
|
||||
"@types/viewport-mercator-project": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-transition-group": {
|
||||
"version": "4.4.12",
|
||||
"license": "MIT",
|
||||
@@ -2401,6 +2371,15 @@
|
||||
"version": "0.0.6",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/viewport-mercator-project": {
|
||||
"version": "6.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/viewport-mercator-project/-/viewport-mercator-project-6.1.6.tgz",
|
||||
"integrity": "sha512-uWrbqhRXFeiT6CAvRjf0BkQKRkKED+ofrPhglKpUktQML3463dEPiA4iwe7cZQs6m49Zo/g03rL7ChMLiE5Z8w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"gl-matrix": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ungap/structured-clone": {
|
||||
"version": "1.3.0",
|
||||
"dev": true,
|
||||
@@ -3566,7 +3545,6 @@
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.12",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@@ -3884,7 +3862,6 @@
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
@@ -4018,7 +3995,6 @@
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -5317,7 +5293,6 @@
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -5650,7 +5625,6 @@
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.6",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -6158,7 +6132,6 @@
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.57.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
@@ -6530,7 +6503,6 @@
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -6818,7 +6790,6 @@
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.15",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fdir": "^6.5.0",
|
||||
@@ -6948,7 +6919,7 @@
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@@ -6992,7 +6963,7 @@
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.12.0",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/union-value": {
|
||||
@@ -7082,7 +7053,6 @@
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.3.6",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
@@ -7366,7 +7336,7 @@
|
||||
"version": "2.8.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
|
||||
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"@mui/x-date-pickers": "7.29.4",
|
||||
"@reduxjs/toolkit": "2.7.0",
|
||||
"@types/maplibre-gl": "^1.13.2",
|
||||
"@types/react-map-gl": "^6.1.8",
|
||||
"axios": "^1.7.4",
|
||||
"dayjs": "1.11.13",
|
||||
"flag-icons": "7.3.2",
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import { Dialog, Select, Autocomplete } from "@/Components/inputs";
|
||||
import { useState, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useGet, usePatch } from "@/Hooks/UseApi";
|
||||
import type { Notification } from "@/Types/Notification";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import { Trash2 } from "lucide-react";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { LAYOUT } from "@/Utils/Theme/constants";
|
||||
|
||||
type ActionType = "add" | "remove" | "set";
|
||||
|
||||
export const BulkEditNotificationsModal = ({
|
||||
open,
|
||||
monitorIds,
|
||||
onClose,
|
||||
onSuccess,
|
||||
}: {
|
||||
open: boolean;
|
||||
monitorIds: string[];
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const [action, setAction] = useState<ActionType>("add");
|
||||
const [selectedNotificationIds, setSelectedNotificationIds] = useState<string[]>([]);
|
||||
|
||||
const { data: notifications } = useGet<Notification[]>("/notifications/team");
|
||||
const { patch, loading } = usePatch();
|
||||
|
||||
const handleConfirm = async () => {
|
||||
if (selectedNotificationIds.length === 0 && action !== "set") return;
|
||||
|
||||
const result = await patch("/notifications", {
|
||||
monitorIds,
|
||||
notificationIds: selectedNotificationIds,
|
||||
action,
|
||||
});
|
||||
|
||||
if (result?.success) {
|
||||
onSuccess();
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const notificationOptions = useMemo(() => {
|
||||
return (notifications ?? []).map((n) => ({
|
||||
...n,
|
||||
name: n.notificationName,
|
||||
}));
|
||||
}, [notifications]);
|
||||
|
||||
const selectedItems = useMemo(() => {
|
||||
return notificationOptions.filter((n) => selectedNotificationIds.includes(n.id));
|
||||
}, [notificationOptions, selectedNotificationIds]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
title={t("pages.uptime.bulkEdit.title", { defaultValue: "Bulk Edit Notifications" })}
|
||||
content={t("pages.uptime.bulkEdit.description", {
|
||||
defaultValue: `Apply notification changes to ${monitorIds.length} selected monitors.`,
|
||||
})}
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={onClose}
|
||||
loading={loading}
|
||||
confirmText={t("common.buttons.save")}
|
||||
>
|
||||
<Stack spacing={theme.spacing(LAYOUT.MD)} sx={{ mt: 2 }}>
|
||||
<Select
|
||||
value={action}
|
||||
onChange={(e) => setAction(e.target.value as ActionType)}
|
||||
fieldLabel={t("pages.uptime.bulkEdit.actionLabel", { defaultValue: "Action" })}
|
||||
>
|
||||
<MenuItem value="add">
|
||||
{t("pages.uptime.bulkEdit.actionAdd", { defaultValue: "Add Notifications" })}
|
||||
</MenuItem>
|
||||
<MenuItem value="remove">
|
||||
{t("pages.uptime.bulkEdit.actionRemove", { defaultValue: "Remove Notifications" })}
|
||||
</MenuItem>
|
||||
<MenuItem value="set">
|
||||
{t("pages.uptime.bulkEdit.actionSet", { defaultValue: "Set Notifications (Replace All)" })}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
|
||||
<Autocomplete
|
||||
multiple
|
||||
options={notificationOptions}
|
||||
value={selectedItems}
|
||||
getOptionLabel={(option) => option.name}
|
||||
onChange={(_: unknown, newValue: typeof notificationOptions) => {
|
||||
setSelectedNotificationIds(newValue.map((n) => n.id));
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||
fieldLabel={t("pages.uptime.bulkEdit.notificationsLabel", {
|
||||
defaultValue: "Notifications",
|
||||
})}
|
||||
/>
|
||||
|
||||
{selectedItems.length > 0 && (
|
||||
<Stack flex={1} width="100%">
|
||||
{selectedItems.map((notification, index) => (
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
key={notification.id}
|
||||
width="100%"
|
||||
>
|
||||
<Typography flexGrow={1}>{notification.notificationName}</Typography>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setSelectedNotificationIds((prev) =>
|
||||
prev.filter((id) => id !== notification.id)
|
||||
);
|
||||
}}
|
||||
aria-label="Remove notification"
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</IconButton>
|
||||
{index < selectedItems.length - 1 && <Divider />}
|
||||
</Stack>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -16,6 +16,7 @@ import { useTheme } from "@mui/material/styles";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { usePost } from "@/Hooks/UseApi";
|
||||
import { useSelector } from "react-redux";
|
||||
import { Checkbox } from "@/Components/inputs";
|
||||
|
||||
import type { Monitor } from "@/Types/Monitor";
|
||||
import type { ActionMenuItem } from "@/Components/actions-menu";
|
||||
@@ -34,6 +35,8 @@ export const MonitorTable = ({
|
||||
setPage,
|
||||
rowsPerPage,
|
||||
setRowsPerPage,
|
||||
selectedMonitors,
|
||||
setSelectedMonitors,
|
||||
}: {
|
||||
monitors: Monitor[];
|
||||
refetch: Function;
|
||||
@@ -47,6 +50,8 @@ export const MonitorTable = ({
|
||||
setPage: (page: number) => void;
|
||||
rowsPerPage: number;
|
||||
setRowsPerPage: (rowsPerPage: number) => void;
|
||||
selectedMonitors: string[];
|
||||
setSelectedMonitors: React.Dispatch<React.SetStateAction<string[]>>;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
@@ -151,6 +156,37 @@ export const MonitorTable = ({
|
||||
</Box>
|
||||
);
|
||||
const headers: Header<Monitor>[] = [
|
||||
{
|
||||
id: "select",
|
||||
content: (
|
||||
<Checkbox
|
||||
checked={monitors.length > 0 && selectedMonitors.length === monitors.length}
|
||||
indeterminate={selectedMonitors.length > 0 && selectedMonitors.length < monitors.length}
|
||||
onChange={(e) => {
|
||||
e.stopPropagation();
|
||||
if (e.target.checked) {
|
||||
setSelectedMonitors(monitors.map((m) => m.id));
|
||||
} else {
|
||||
setSelectedMonitors([]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
),
|
||||
onClick: (e) => e.stopPropagation(),
|
||||
render: (row) => (
|
||||
<Checkbox
|
||||
checked={selectedMonitors.includes(row.id)}
|
||||
onChange={(e) => {
|
||||
e.stopPropagation();
|
||||
if (e.target.checked) {
|
||||
setSelectedMonitors((prev) => [...prev, row.id]);
|
||||
} else {
|
||||
setSelectedMonitors((prev) => prev.filter((id) => id !== row.id));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "name",
|
||||
content: (
|
||||
|
||||
@@ -2,8 +2,11 @@ import { ControlsFilter, HeaderMonitorsSummary } from "@/Components/monitors";
|
||||
import { MonitorBasePageWithStates } from "@/Components/design-elements";
|
||||
import { TextField, Dialog } from "@/Components/inputs";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { MonitorTable } from "@/Pages/Uptime/Monitors/Components/UptimeMonitorsTable";
|
||||
import { HeaderCreate } from "@/Components/common";
|
||||
import { Button } from "@/Components/inputs";
|
||||
import { BulkEditNotificationsModal } from "./Components/BulkEditNotificationsModal";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
@@ -39,6 +42,9 @@ const UptimeMonitorsPage = () => {
|
||||
const [selectedMonitor, setSelectedMonitor] = useState<Monitor | null>(null);
|
||||
const isDialogOpen = Boolean(selectedMonitor);
|
||||
|
||||
const [selectedMonitors, setSelectedMonitors] = useState<string[]>([]);
|
||||
const [isBulkEditOpen, setIsBulkEditOpen] = useState(false);
|
||||
|
||||
const debouncedSearch = useDebounce<string>(search, 300);
|
||||
|
||||
// Convert filter selections to API filter values
|
||||
@@ -159,22 +165,46 @@ const UptimeMonitorsPage = () => {
|
||||
justifyContent={isSmall ? "flex-start" : "space-between"}
|
||||
gap={theme.spacing(4)}
|
||||
>
|
||||
<ControlsFilter
|
||||
selectedTypes={selectedTypes}
|
||||
setSelectedTypes={setSelectedTypes}
|
||||
selectedStatus={selectedStatus}
|
||||
setSelectedStatus={setSelectedStatus}
|
||||
selectedState={selectedState}
|
||||
setSelectedState={setSelectedState}
|
||||
onClearFilters={handleClearFilters}
|
||||
/>
|
||||
<TextField
|
||||
placeholder={t("pages.uptime.filters.search.placeholder")}
|
||||
value={search}
|
||||
onChange={(event) => {
|
||||
setSearch(event.target.value);
|
||||
}}
|
||||
/>
|
||||
{selectedMonitors.length > 0 ? (
|
||||
<Stack direction="row" alignItems="center" spacing={2} width="100%">
|
||||
<Typography variant="body1" fontWeight="medium">
|
||||
{selectedMonitors.length} monitor(s) selected
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => setIsBulkEditOpen(true)}
|
||||
>
|
||||
Bulk Edit Notifications
|
||||
</Button>
|
||||
<Button
|
||||
variant="text"
|
||||
color="inherit"
|
||||
onClick={() => setSelectedMonitors([])}
|
||||
>
|
||||
Cancel Selection
|
||||
</Button>
|
||||
</Stack>
|
||||
) : (
|
||||
<>
|
||||
<ControlsFilter
|
||||
selectedTypes={selectedTypes}
|
||||
setSelectedTypes={setSelectedTypes}
|
||||
selectedStatus={selectedStatus}
|
||||
setSelectedStatus={setSelectedStatus}
|
||||
selectedState={selectedState}
|
||||
setSelectedState={setSelectedState}
|
||||
onClearFilters={handleClearFilters}
|
||||
/>
|
||||
<TextField
|
||||
placeholder={t("pages.uptime.filters.search.placeholder")}
|
||||
value={search}
|
||||
onChange={(event) => {
|
||||
setSearch(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
<MonitorTable
|
||||
monitors={monitorsWithChecks || []}
|
||||
@@ -188,6 +218,8 @@ const UptimeMonitorsPage = () => {
|
||||
setSortField={setSortField}
|
||||
sortOrder={sortOrder}
|
||||
setSortOrder={setSortOrder}
|
||||
selectedMonitors={selectedMonitors}
|
||||
setSelectedMonitors={setSelectedMonitors}
|
||||
setRowsPerPage={(rowsPerPage: number) => {
|
||||
dispatch(
|
||||
setRowsPerPage({
|
||||
@@ -206,6 +238,15 @@ const UptimeMonitorsPage = () => {
|
||||
onCancel={handleCancel}
|
||||
loading={isDeleting}
|
||||
/>
|
||||
<BulkEditNotificationsModal
|
||||
open={isBulkEditOpen}
|
||||
monitorIds={selectedMonitors}
|
||||
onClose={() => setIsBulkEditOpen(false)}
|
||||
onSuccess={() => {
|
||||
setSelectedMonitors([]);
|
||||
refetch();
|
||||
}}
|
||||
/>
|
||||
</MonitorBasePageWithStates>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user