diff --git a/Client/src/Components/Inputs/Search/index.jsx b/Client/src/Components/Inputs/Search/index.jsx index b1e5d74ae..3de2cbe07 100644 --- a/Client/src/Components/Inputs/Search/index.jsx +++ b/Client/src/Components/Inputs/Search/index.jsx @@ -1,12 +1,5 @@ import PropTypes from "prop-types"; -import { - Box, - ListItem, - Autocomplete, - TextField, - Stack, - Typography, -} from "@mui/material"; +import { Box, ListItem, Autocomplete, TextField, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import SearchIcon from "../../../assets/icons/search.svg?react"; @@ -24,172 +17,172 @@ import SearchIcon from "../../../assets/icons/search.svg?react"; */ const SearchAdornment = () => { - const theme = useTheme(); - return ( - - - - ); + const theme = useTheme(); + return ( + + + + ); }; +//TODO keep search state inside of component const Search = ({ - id, - options, - filteredBy, - secondaryLabel, - value, - inputValue, - handleInputChange, - handleChange, - sx, - multiple = false, - isAdorned = true, - error, - disabled, + id, + options, + filteredBy, + secondaryLabel, + value, + inputValue, + handleInputChange, + handleChange, + sx, + multiple = false, + isAdorned = true, + error, + disabled, }) => { - const theme = useTheme(); + const theme = useTheme(); - return ( - { - handleInputChange(newValue); - }} - onChange={(_, newValue) => { - handleChange && handleChange(newValue); - }} - fullWidth - freeSolo - disabled={disabled} - disableClearable - options={options} - getOptionLabel={(option) => option[filteredBy]} - renderInput={(params) => ( - - }), - }} - sx={{ - "& fieldset": { - borderColor: theme.palette.border.light, - borderRadius: theme.shape.borderRadius, - }, - "& .MuiOutlinedInput-root:hover:not(:has(input:focus)):not(:has(textarea:focus)) fieldset": - { - borderColor: theme.palette.border.light, - }, - }} - /> - {error && ( - - {error} - - )} - - )} - filterOptions={(options, { inputValue }) => { - const filtered = options.filter((option) => - option[filteredBy].toLowerCase().includes(inputValue.toLowerCase()) - ); + return ( + { + handleInputChange(newValue); + }} + onChange={(_, newValue) => { + handleChange && handleChange(newValue); + }} + fullWidth + freeSolo + disabled={disabled} + disableClearable + options={options} + getOptionLabel={(option) => option[filteredBy]} + renderInput={(params) => ( + + }), + }} + sx={{ + "& fieldset": { + borderColor: theme.palette.border.light, + borderRadius: theme.shape.borderRadius, + }, + "& .MuiOutlinedInput-root:hover:not(:has(input:focus)):not(:has(textarea:focus)) fieldset": + { + borderColor: theme.palette.border.light, + }, + }} + /> + {error && ( + + {error} + + )} + + )} + filterOptions={(options, { inputValue }) => { + const filtered = options.filter((option) => + option[filteredBy].toLowerCase().includes(inputValue.toLowerCase()) + ); - if (filtered.length === 0) { - return [{ [filteredBy]: "No monitors found", noOptions: true }]; - } - return filtered; - }} - renderOption={(props, option) => { - const { key, ...optionProps } = props; - return ( - - {option[filteredBy] + - (secondaryLabel ? ` (${option[secondaryLabel]})` : "")} - - ); - }} - slotProps={{ - popper: { - keepMounted: true, - sx: { - "& ul": { p: 2 }, - "& li.MuiAutocomplete-option": { - color: theme.palette.text.secondary, - px: 4, - borderRadius: theme.shape.borderRadius, - }, - "& .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'], & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'].Mui-focused, & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true']:hover": - { - backgroundColor: theme.palette.background.fill, - }, - "& .MuiAutocomplete-noOptions": { - px: theme.spacing(6), - py: theme.spacing(5), - }, - }, - }, - }} - sx={{ - height: 34, - "&.MuiAutocomplete-root .MuiAutocomplete-input": { p: 0 }, - ...sx, - }} - /> - ); + if (filtered.length === 0) { + return [{ [filteredBy]: "No monitors found", noOptions: true }]; + } + return filtered; + }} + renderOption={(props, option) => { + const { key, ...optionProps } = props; + return ( + + {option[filteredBy] + (secondaryLabel ? ` (${option[secondaryLabel]})` : "")} + + ); + }} + slotProps={{ + popper: { + keepMounted: true, + sx: { + "& ul": { p: 2 }, + "& li.MuiAutocomplete-option": { + color: theme.palette.text.secondary, + px: 4, + borderRadius: theme.shape.borderRadius, + }, + "& .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'], & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'].Mui-focused, & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true']:hover": + { + backgroundColor: theme.palette.background.fill, + }, + "& .MuiAutocomplete-noOptions": { + px: theme.spacing(6), + py: theme.spacing(5), + }, + }, + }, + }} + sx={{ + height: 34, + "&.MuiAutocomplete-root .MuiAutocomplete-input": { p: 0 }, + ...sx, + }} + /> + ); }; Search.propTypes = { - id: PropTypes.string, - multiple: PropTypes.bool, - options: PropTypes.array.isRequired, - filteredBy: PropTypes.string.isRequired, - secondaryLabel: PropTypes.string, - value: PropTypes.array, - inputValue: PropTypes.string.isRequired, - handleInputChange: PropTypes.func.isRequired, - handleChange: PropTypes.func, - isAdorned: PropTypes.bool, - sx: PropTypes.object, - error: PropTypes.string, - disabled: PropTypes.bool, + id: PropTypes.string, + multiple: PropTypes.bool, + options: PropTypes.array.isRequired, + filteredBy: PropTypes.string.isRequired, + secondaryLabel: PropTypes.string, + value: PropTypes.array, + inputValue: PropTypes.string.isRequired, + handleInputChange: PropTypes.func.isRequired, + handleChange: PropTypes.func, + isAdorned: PropTypes.bool, + sx: PropTypes.object, + error: PropTypes.string, + disabled: PropTypes.bool, }; export default Search; diff --git a/Client/src/Pages/Monitors/Home/CurrentMonitoring/index.jsx b/Client/src/Pages/Monitors/Home/CurrentMonitoring/index.jsx new file mode 100644 index 000000000..613c280a4 --- /dev/null +++ b/Client/src/Pages/Monitors/Home/CurrentMonitoring/index.jsx @@ -0,0 +1,79 @@ +import { useTheme } from "@emotion/react"; +import { Box, Stack, Typography } from "@mui/material"; +import Search from "../../../../Components/Inputs/Search"; +import MemoizedMonitorTable from "../MonitorTable"; +import { useState } from "react"; +import useDebounce from "../../../../Utils/debounce"; +import PropTypes from "prop-types"; + +const CurrentMonitoring = ({ totalMonitors, monitors, isAdmin }) => { + const theme = useTheme(); + const [search, setSearch] = useState(""); + const [isSearching, setIsSearching] = useState(false); + const debouncedFilter = useDebounce(search, 500); + const handleSearch = (value) => { + setIsSearching(true); + setSearch(value); + }; + return ( + + + + Actively monitoring + + + {totalMonitors} + + + + + + + + ); +}; + +CurrentMonitoring.propTypes = { + totalMonitors: PropTypes.number, + monitors: PropTypes.array, + isAdmin: PropTypes.bool, +}; + +export { CurrentMonitoring }; diff --git a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx index b5e36f634..d9fbc34b2 100644 --- a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx +++ b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx @@ -109,7 +109,7 @@ TablePaginationActions.propTypes = { onPageChange: PropTypes.func.isRequired, }; -const MonitorTable = ({ isAdmin, filter, setLoading, isSearching }) => { +const MonitorTable = ({ isAdmin, filter, setIsSearching, isSearching }) => { const theme = useTheme(); const navigate = useNavigate(); const dispatch = useDispatch(); @@ -162,15 +162,25 @@ const MonitorTable = ({ isAdmin, filter, setLoading, isSearching }) => { }); setMonitors(res?.data?.data?.monitors ?? []); setMonitorCount(res?.data?.data?.monitorCount ?? 0); - setLoading(false); } catch (error) { logger.error(error); + } finally { + setIsSearching(false); } - }, [authState, page, rowsPerPage, filter, sort, setLoading]); + }, [authState, page, rowsPerPage, filter, sort, setIsSearching]); useEffect(() => { fetchPage(); - }, [updateTrigger, authState, page, rowsPerPage, filter, sort, setLoading, fetchPage]); + }, [ + updateTrigger, + authState, + page, + rowsPerPage, + filter, + sort, + setIsSearching, + fetchPage, + ]); // Listen for changes in filter, if new value reset the page useEffect(() => { @@ -459,7 +469,7 @@ const MonitorTable = ({ isAdmin, filter, setLoading, isSearching }) => { MonitorTable.propTypes = { isAdmin: PropTypes.bool, filter: PropTypes.string, - setLoading: PropTypes.func, + setIsSearching: PropTypes.func, isSearching: PropTypes.bool, }; diff --git a/Client/src/Pages/Monitors/Home/index.jsx b/Client/src/Pages/Monitors/Home/index.jsx index c2413f6cc..8bd1d7275 100644 --- a/Client/src/Pages/Monitors/Home/index.jsx +++ b/Client/src/Pages/Monitors/Home/index.jsx @@ -1,19 +1,17 @@ import "./index.css"; -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import { useSelector, useDispatch } from "react-redux"; import { getUptimeMonitorsByTeamId } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; -import { Box, Button, Stack, Typography } from "@mui/material"; +import { Box, Button, Stack } from "@mui/material"; import PropTypes from "prop-types"; import SkeletonLayout from "./skeleton"; import Fallback from "./fallback"; import StatusBox from "./StatusBox"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import Greeting from "../../../Utils/greeting"; -import MonitorTable from "./MonitorTable"; -import Search from "../../../Components/Inputs/Search"; -import useDebounce from "../../../Utils/debounce"; +import { CurrentMonitoring } from "./CurrentMonitoring"; const Monitors = ({ isAdmin }) => { const theme = useTheme(); @@ -22,20 +20,12 @@ const Monitors = ({ isAdmin }) => { const authState = useSelector((state) => state.auth); const dispatch = useDispatch({}); - //TODO create components, and lower these states. - const [search, setSearch] = useState(""); - const [isSearching, setIsSearching] = useState(false); - const debouncedFilter = useDebounce(search, 500); - - const handleSearch = (value) => { - setIsSearching(true); - setSearch(value); - }; - useEffect(() => { dispatch(getUptimeMonitorsByTeamId(authState.authToken)); }, [authState.authToken, dispatch]); + //TODO bring fetching to this component, like on pageSpeed + const loading = monitorState?.isLoading; const totalMonitors = monitorState?.monitorsSummary?.monitorCounts?.total; @@ -100,57 +90,11 @@ const Monitors = ({ isAdmin }) => { value={monitorState?.monitorsSummary?.monitorCounts?.paused ?? 0} /> - - - - Actively monitoring - - - {totalMonitors} - - - - - - - + )} diff --git a/Client/src/Pages/PageSpeed/index.jsx b/Client/src/Pages/PageSpeed/index.jsx index 3174013ef..2b5e60aad 100644 --- a/Client/src/Pages/PageSpeed/index.jsx +++ b/Client/src/Pages/PageSpeed/index.jsx @@ -14,114 +14,121 @@ import Card from "./card"; import { networkService } from "../../main"; const PageSpeed = ({ isAdmin }) => { - const theme = useTheme(); - const dispatch = useDispatch(); - const navigate = useNavigate(); + const theme = useTheme(); + const dispatch = useDispatch(); + const navigate = useNavigate(); - const { user, authToken } = useSelector((state) => state.auth); - const [isLoading, setIsLoading] = useState(false); - const [monitors, setMonitors] = useState([]); - useEffect(() => { - dispatch(getPageSpeedByTeamId(authToken)); - }, [authToken, dispatch]); + const { user, authToken } = useSelector((state) => state.auth); + const [isLoading, setIsLoading] = useState(true); + const [monitors, setMonitors] = useState([]); + useEffect(() => { + dispatch(getPageSpeedByTeamId(authToken)); + }, [authToken, dispatch]); - useEffect(() => { - const fetchMonitors = async () => { - try { - setIsLoading(true); - const res = await networkService.getMonitorsByTeamId({ - authToken: authToken, - teamId: user.teamId, - limit: 10, - types: ["pagespeed"], - status: null, - checkOrder: "desc", - normalize: true, - page: null, - rowsPerPage: null, - filter: null, - field: null, - order: null, - }); - if (res?.data?.data?.monitors) { - setMonitors(res.data.data.monitors); - } - } catch (error) { - console.log(error); - } finally { - setIsLoading(false); - } - }; + useEffect(() => { + const fetchMonitors = async () => { + try { + setIsLoading(true); + const res = await networkService.getMonitorsByTeamId({ + authToken: authToken, + teamId: user.teamId, + limit: 10, + types: ["pagespeed"], + status: null, + checkOrder: "desc", + normalize: true, + page: null, + rowsPerPage: null, + filter: null, + field: null, + order: null, + }); + if (res?.data?.data?.monitors) { + setMonitors(res.data.data.monitors); + } + } catch (error) { + console.log(error); + } finally { + setIsLoading(false); + } + }; - fetchMonitors(); - }, []); + fetchMonitors(); + }, []); - // will show skeletons only on initial load - // since monitor state is being added to redux persist, there's no reason to display skeletons on every render - let isActuallyLoading = isLoading && monitors?.length === 0; - return ( - [class*="fallback__"])': { - position: "relative", - border: 1, - borderColor: theme.palette.border.light, - borderRadius: theme.shape.borderRadius, - borderStyle: "dashed", - backgroundColor: theme.palette.background.main, - overflow: "hidden", - }, - }} - > - {isActuallyLoading ? ( - - ) : monitors?.length !== 0 ? ( - - - - - - {isAdmin && ( - - )} - - - - {monitors?.map((monitor) => ( - - ))} - - - ) : ( - - )} - - ); + // will show skeletons only on initial load + // since monitor state is being added to redux persist, there's no reason to display skeletons on every render + let isActuallyLoading = isLoading && monitors?.length === 0; + console.log({ isActuallyLoading }); + return ( + [class*="fallback__"])': { + position: "relative", + border: 1, + borderColor: theme.palette.border.light, + borderRadius: theme.shape.borderRadius, + borderStyle: "dashed", + backgroundColor: theme.palette.background.main, + overflow: "hidden", + }, + }} + > + + + + + {isAdmin && ( + + )} + + + {isActuallyLoading ? ( + + ) : monitors?.length !== 0 ? ( + + + {monitors?.map((monitor) => ( + + ))} + + + ) : ( + + )} + + ); }; PageSpeed.propTypes = { - isAdmin: PropTypes.bool, + isAdmin: PropTypes.bool, }; export default PageSpeed;