diff --git a/Client/src/Pages/Monitors/Home/MonitorTable/Skeleton/index.jsx b/Client/src/Pages/Monitors/Home/MonitorTable/Skeleton/index.jsx new file mode 100644 index 000000000..186b89725 --- /dev/null +++ b/Client/src/Pages/Monitors/Home/MonitorTable/Skeleton/index.jsx @@ -0,0 +1,31 @@ +import { Skeleton, TableBody, TableCell, TableRow } from "@mui/material"; +const ROWS_NUMBER = 7; +const ROWS_ARRAY = Array.from({ length: ROWS_NUMBER }, (_, i) => i); + +const TableBodySkeleton = () => { + return ( + + {ROWS_ARRAY.map((row) => ( + + + + + + + + + + + + + + + + + + ))} + + ); +}; + +export { TableBodySkeleton }; diff --git a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx index b86a739e0..4e8f5100b 100644 --- a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx +++ b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx @@ -1,17 +1,17 @@ import PropTypes from "prop-types"; import { - TableContainer, - Table, - TableHead, - TableRow, - TableCell, - TableBody, - Paper, - Box, - TablePagination, - Stack, - Typography, - Button, + TableContainer, + Table, + TableHead, + TableRow, + TableCell, + TableBody, + Paper, + Box, + TablePagination, + Stack, + Typography, + Button, } from "@mui/material"; import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded"; @@ -34,6 +34,7 @@ import RightArrow from "../../../../assets/icons/right-arrow.svg?react"; import SelectorVertical from "../../../../assets/icons/selector-vertical.svg?react"; import ActionsMenu from "../actionsMenu"; import useUtils from "../../utils"; +import { TableBodySkeleton } from "./Skeleton"; /** * Component for pagination actions (first, previous, next, last). @@ -48,386 +49,385 @@ import useUtils from "../../utils"; * @returns {JSX.Element} Pagination actions component. */ const TablePaginationActions = (props) => { - const { count, page, rowsPerPage, onPageChange } = props; - const handleFirstPageButtonClick = (event) => { - onPageChange(event, 0); - }; - const handleBackButtonClick = (event) => { - onPageChange(event, page - 1); - }; - const handleNextButtonClick = (event) => { - onPageChange(event, page + 1); - }; - const handleLastPageButtonClick = (event) => { - onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1)); - }; + const { count, page, rowsPerPage, onPageChange } = props; + const handleFirstPageButtonClick = (event) => { + onPageChange(event, 0); + }; + const handleBackButtonClick = (event) => { + onPageChange(event, page - 1); + }; + const handleNextButtonClick = (event) => { + onPageChange(event, page + 1); + }; + const handleLastPageButtonClick = (event) => { + onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1)); + }; - return ( - - - - - - - ); + return ( + + + + + + + ); }; TablePaginationActions.propTypes = { - count: PropTypes.number.isRequired, - page: PropTypes.number.isRequired, - rowsPerPage: PropTypes.number.isRequired, - onPageChange: PropTypes.func.isRequired, + count: PropTypes.number.isRequired, + page: PropTypes.number.isRequired, + rowsPerPage: PropTypes.number.isRequired, + onPageChange: PropTypes.func.isRequired, }; const MonitorTable = ({ isAdmin, filter, setLoading }) => { - const theme = useTheme(); - const navigate = useNavigate(); - const dispatch = useDispatch(); - const { determineState } = useUtils(); + const theme = useTheme(); + const navigate = useNavigate(); + const dispatch = useDispatch(); + const { determineState } = useUtils(); - const { rowsPerPage } = useSelector((state) => state.ui.monitors); - const [page, setPage] = useState(0); - const [monitors, setMonitors] = useState([]); - const [monitorCount, setMonitorCount] = useState(0); - const authState = useSelector((state) => state.auth); - const [updateTrigger, setUpdateTrigger] = useState(false); - const [sort, setSort] = useState({}); - const prevFilter = useRef(filter); + const { rowsPerPage } = useSelector((state) => state.ui.monitors); + const authState = useSelector((state) => state.auth); + const [page, setPage] = useState(0); + const [monitors, setMonitors] = useState([]); + const [monitorCount, setMonitorCount] = useState(0); + const [updateTrigger, setUpdateTrigger] = useState(false); + const [sort, setSort] = useState({}); + const prevFilter = useRef(filter); - const handleActionMenuDelete = () => { - setUpdateTrigger((prev) => !prev); - }; + const handleActionMenuDelete = () => { + setUpdateTrigger((prev) => !prev); + }; - const handleChangePage = (event, newPage) => { - setPage(newPage); - }; + const handleChangePage = (event, newPage) => { + setPage(newPage); + }; - const handleChangeRowsPerPage = (event) => { - dispatch( - setRowsPerPage({ - value: parseInt(event.target.value, 10), - table: "monitors", - }) - ); - setPage(0); - }; + const handleChangeRowsPerPage = (event) => { + dispatch( + setRowsPerPage({ + value: parseInt(event.target.value, 10), + table: "monitors", + }) + ); + setPage(0); + }; - const fetchPage = useCallback(async () => { - try { - const { authToken } = authState; - const user = jwtDecode(authToken); - const res = await networkService.getMonitorsByTeamId({ - authToken, - teamId: user.teamId, - limit: 25, - types: ["http", "ping"], - status: null, - checkOrder: "desc", - normalize: true, - page: page, - rowsPerPage: rowsPerPage, - filter: filter, - field: sort.field, - order: sort.order, - }); - setMonitors(res?.data?.data?.monitors ?? []); - setMonitorCount(res?.data?.data?.monitorCount ?? 0); - setLoading(false); - } catch (error) { - logger.error(error); - } - }, [authState, page, rowsPerPage, filter, sort, setLoading]); + const fetchPage = useCallback(async () => { + try { + const { authToken } = authState; + const user = jwtDecode(authToken); + const res = await networkService.getMonitorsByTeamId({ + authToken, + teamId: user.teamId, + limit: 25, + types: ["http", "ping"], + status: null, + checkOrder: "desc", + normalize: true, + page: page, + rowsPerPage: rowsPerPage, + filter: filter, + field: sort.field, + order: sort.order, + }); + setMonitors(res?.data?.data?.monitors ?? []); + setMonitorCount(res?.data?.data?.monitorCount ?? 0); + setLoading(false); + } catch (error) { + logger.error(error); + } + }, [authState, page, rowsPerPage, filter, sort, setLoading]); - useEffect(() => { - fetchPage(); - }, [ - updateTrigger, - authState, - page, - rowsPerPage, - filter, - sort, - setLoading, - fetchPage, - ]); + useEffect(() => { + fetchPage(); + }, [updateTrigger, authState, page, rowsPerPage, filter, sort, setLoading, fetchPage]); - // Listen for changes in filter, if new value reset the page - useEffect(() => { - if (prevFilter.current !== filter) { - setPage(0); - fetchPage(); - } - prevFilter.current = filter; - }, [filter, fetchPage]); + // Listen for changes in filter, if new value reset the page + useEffect(() => { + if (prevFilter.current !== filter) { + setPage(0); + fetchPage(); + } + prevFilter.current = filter; + }, [filter, fetchPage]); - /** - * Helper function to calculate the range of displayed rows. - * @returns {string} - */ - const getRange = () => { - let start = page * rowsPerPage + 1; - let end = Math.min(page * rowsPerPage + rowsPerPage, monitorCount); - return `${start} - ${end}`; - }; + /** + * Helper function to calculate the range of displayed rows. + * @returns {string} + */ + const getRange = () => { + let start = page * rowsPerPage + 1; + let end = Math.min(page * rowsPerPage + rowsPerPage, monitorCount); + return `${start} - ${end}`; + }; - const handleSort = async (field) => { - let order = ""; - if (sort.field !== field) { - order = "desc"; - } else { - order = sort.order === "asc" ? "desc" : "asc"; - } - setSort({ field, order }); + const handleSort = async (field) => { + let order = ""; + if (sort.field !== field) { + order = "desc"; + } else { + order = sort.order === "asc" ? "desc" : "asc"; + } + setSort({ field, order }); - const { authToken } = authState; - const user = jwtDecode(authToken); + const { authToken } = authState; + const user = jwtDecode(authToken); - const res = await networkService.getMonitorsByTeamId({ - authToken, - teamId: user.teamId, - limit: 25, - types: ["http", "ping"], - status: null, - checkOrder: "desc", - normalize: true, - page: page, - rowsPerPage: rowsPerPage, - filter: null, - field: field, - order: order, - }); - setMonitors(res?.data?.data?.monitors ?? []); - setMonitorCount(res?.data?.data?.monitorCount ?? 0); - }; + const res = await networkService.getMonitorsByTeamId({ + authToken, + teamId: user.teamId, + limit: 25, + types: ["http", "ping"], + status: null, + checkOrder: "desc", + normalize: true, + page: page, + rowsPerPage: rowsPerPage, + filter: null, + field: field, + order: order, + }); + setMonitors(res?.data?.data?.monitors ?? []); + setMonitorCount(res?.data?.data?.monitorCount ?? 0); + }; - return ( - <> - - - - - handleSort("name")} - > - - Host - - {sort.order === "asc" ? ( - - ) : ( - - )} - - - - handleSort("status")} - > - {" "} - - {" "} - Status - - {sort.order === "asc" ? ( - - ) : ( - - )} - - - - Response Time - Type - Actions - - - - {monitors.map((monitor) => { - let uptimePercentage = ""; - let percentageColor = theme.palette.percentage.uptimeExcellent; + return ( + <> + +
+ + + handleSort("name")} + > + + Host + + {sort.order === "asc" ? ( + + ) : ( + + )} + + + + handleSort("status")} + > + {" "} + + {" "} + Status + + {sort.order === "asc" ? ( + + ) : ( + + )} + + + + Response Time + Type + Actions + + + {monitors.length > 0 ? ( + + {monitors.map((monitor) => { + let uptimePercentage = ""; + let percentageColor = theme.palette.percentage.uptimeExcellent; - // Determine uptime percentage and color based on the monitor's uptimePercentage value - if (monitor.uptimePercentage !== undefined) { - uptimePercentage = - monitor.uptimePercentage === 0 - ? "0" - : (monitor.uptimePercentage * 100).toFixed(2); + // Determine uptime percentage and color based on the monitor's uptimePercentage value + if (monitor.uptimePercentage !== undefined) { + uptimePercentage = + monitor.uptimePercentage === 0 + ? "0" + : (monitor.uptimePercentage * 100).toFixed(2); - percentageColor = - monitor.uptimePercentage < 0.25 - ? theme.palette.percentage.uptimePoor - : monitor.uptimePercentage < 0.5 - ? theme.palette.percentage.uptimeFair - : monitor.uptimePercentage < 0.75 - ? theme.palette.percentage.uptimeGood - : theme.palette.percentage.uptimeExcellent; - } + percentageColor = + monitor.uptimePercentage < 0.25 + ? theme.palette.percentage.uptimePoor + : monitor.uptimePercentage < 0.5 + ? theme.palette.percentage.uptimeFair + : monitor.uptimePercentage < 0.75 + ? theme.palette.percentage.uptimeGood + : theme.palette.percentage.uptimeExcellent; + } - const params = { - url: monitor.url, - title: monitor.name, - percentage: uptimePercentage, - percentageColor, - status: determineState(monitor), - }; + const params = { + url: monitor.url, + title: monitor.name, + percentage: uptimePercentage, + percentageColor, + status: determineState(monitor), + }; - return ( - { - navigate(`/monitors/${monitor._id}`); - }} - > - - - - - - - - - - - - {monitor.type} - - - - - - - ); - })} - -
-
- - - Showing {getRange()} of {monitorCount} monitor(s) - - - `Page ${page + 1} of ${Math.max(0, Math.ceil(count / rowsPerPage))}` - } - slotProps={{ - select: { - MenuProps: { - keepMounted: true, - disableScrollLock: true, - PaperProps: { - className: "pagination-dropdown", - sx: { - mt: 0, - mb: theme.spacing(2), - }, - }, - transformOrigin: { vertical: "bottom", horizontal: "left" }, - anchorOrigin: { vertical: "top", horizontal: "left" }, - sx: { mt: theme.spacing(-2) }, - }, - inputProps: { id: "pagination-dropdown" }, - IconComponent: SelectorVertical, - sx: { - ml: theme.spacing(4), - mr: theme.spacing(12), - minWidth: theme.spacing(20), - textAlign: "left", - "&.Mui-focused > div": { - backgroundColor: theme.palette.background.main, - }, - }, - }, - }} - sx={{ - mt: theme.spacing(6), - color: theme.palette.text.secondary, - "& svg path": { - stroke: theme.palette.text.tertiary, - strokeWidth: 1.3, - }, - "& .MuiSelect-select": { - border: 1, - borderColor: theme.palette.border.light, - borderRadius: theme.shape.borderRadius, - }, - }} - /> - - - ); + return ( + { + navigate(`/monitors/${monitor._id}`); + }} + > + + + + + + + + + + + {monitor.type} + + + + + + ); + })} + + ) : ( + + )} + + + + + Showing {getRange()} of {monitorCount} monitor(s) + + + `Page ${page + 1} of ${Math.max(0, Math.ceil(count / rowsPerPage))}` + } + slotProps={{ + select: { + MenuProps: { + keepMounted: true, + disableScrollLock: true, + PaperProps: { + className: "pagination-dropdown", + sx: { + mt: 0, + mb: theme.spacing(2), + }, + }, + transformOrigin: { vertical: "bottom", horizontal: "left" }, + anchorOrigin: { vertical: "top", horizontal: "left" }, + sx: { mt: theme.spacing(-2) }, + }, + inputProps: { id: "pagination-dropdown" }, + IconComponent: SelectorVertical, + sx: { + ml: theme.spacing(4), + mr: theme.spacing(12), + minWidth: theme.spacing(20), + textAlign: "left", + "&.Mui-focused > div": { + backgroundColor: theme.palette.background.main, + }, + }, + }, + }} + sx={{ + mt: theme.spacing(6), + color: theme.palette.text.secondary, + "& svg path": { + stroke: theme.palette.text.tertiary, + strokeWidth: 1.3, + }, + "& .MuiSelect-select": { + border: 1, + borderColor: theme.palette.border.light, + borderRadius: theme.shape.borderRadius, + }, + }} + /> + + + ); }; MonitorTable.propTypes = { - isAdmin: PropTypes.bool, - filter: PropTypes.string, - setLoading: PropTypes.func, + isAdmin: PropTypes.bool, + filter: PropTypes.string, + setLoading: PropTypes.func, }; const MemoizedMonitorTable = memo(MonitorTable); diff --git a/Client/src/Pages/Monitors/Home/index.jsx b/Client/src/Pages/Monitors/Home/index.jsx index d02d046ab..e52556bb9 100644 --- a/Client/src/Pages/Monitors/Home/index.jsx +++ b/Client/src/Pages/Monitors/Home/index.jsx @@ -20,9 +20,11 @@ const Monitors = ({ isAdmin }) => { const navigate = useNavigate(); const monitorState = useSelector((state) => state.uptimeMonitors); 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 dispatch = useDispatch({}); const debouncedFilter = useDebounce(search, 500); const handleSearch = (value) => { @@ -34,13 +36,17 @@ const Monitors = ({ isAdmin }) => { dispatch(getUptimeMonitorsByTeamId(authState.authToken)); }, [authState.authToken, dispatch]); - console.log(monitorState); - const loading = true; - /* monitorState?.isLoading && - monitorState?.monitorsSummary?.monitors?.length === 0; */ - const canCreateMonitor = - isAdmin && monitorState?.monitorsSummary?.monitors?.length !== 0; + //Why are we tying loading to monitors length? + const loading = + monitorState?.isLoading; /* && monitorState?.monitorsSummary?.monitors?.length === 0 */ + const hasMonitors = monitorState?.monitorsSummary?.monitors?.length !== 0; + const noMonitors = monitorState?.monitorsSummary?.monitors?.length === 0; + const canAddMonitor = isAdmin && hasMonitors; + const needsAdmin = !isAdmin && noMonitors; + + /* console.log({ loading }); + console.log({ monitorState }); */ return ( { gap={theme.spacing(6)} > - {canCreateMonitor && ( + {canAddMonitor && (