diff --git a/Client/src/App.jsx b/Client/src/App.jsx index 3b28e285a..05822a5f5 100644 --- a/Client/src/App.jsx +++ b/Client/src/App.jsx @@ -40,6 +40,7 @@ function App() { return { body: { backgroundImage: `radial-gradient(circle, ${palette.gradient.color1}, ${palette.gradient.color2}, ${palette.gradient.color3}, ${palette.gradient.color4}, ${palette.gradient.color5})`, + color: palette.primary.contrastText, }, }; }} diff --git a/Client/src/Components/Alert/index.jsx b/Client/src/Components/Alert/index.jsx index bf941368a..a52761ca4 100644 --- a/Client/src/Components/Alert/index.jsx +++ b/Client/src/Components/Alert/index.jsx @@ -32,9 +32,12 @@ const icons = { const Alert = ({ variant, title, body, isToast, hasIcon = true, onClick }) => { const theme = useTheme(); /* TODO - This needs fixing. text bg and border not necessarilly exist. Probably text becomes contrastText. border becomes contrastText. bg becomes dark. - Check possible variants, see implementation in light and dark theme, and adjust */ - const { text, bg, border } = theme.palette[variant]; + Do we need other variants for alert? + */ + + const text = theme.palette.secondary.contrastText; + const bg = theme.palette.secondary.main; + const border = theme.palette.secondary.contrastText; const icon = icons[variant]; return ( diff --git a/Client/src/Components/Animated/PulseDot.jsx b/Client/src/Components/Animated/PulseDot.jsx index fc38ba7d9..83513d56e 100644 --- a/Client/src/Components/Animated/PulseDot.jsx +++ b/Client/src/Components/Animated/PulseDot.jsx @@ -1,5 +1,6 @@ import PropTypes from "prop-types"; -import { Box, Stack } from "@mui/material"; +import { Box, Stack, useTheme } from "@mui/material"; +// import useUtils from "../../Pages/Uptime/utils"; /** * A component that renders a pulsating dot with a specified color. @@ -15,6 +16,11 @@ import { Box, Stack } from "@mui/material"; */ const PulseDot = ({ color }) => { + const theme = useTheme(); + // const { statusToTheme } = useUtils(); + /* TODO refactor so it gets status and gets theme color. Then uses theme.palette.[themeColor].lowContrast */ + /* const themeColor = statusToTheme[status]; */ + return ( { width: "7px", height: "7px", borderRadius: "50%", - backgroundColor: "white", + backgroundColor: theme.palette.accent.contrastText, top: "50%", left: "50%", transform: "translate(-50%, -50%)", diff --git a/Client/src/Components/Avatar/index.jsx b/Client/src/Components/Avatar/index.jsx index 39e8a3f42..aa2050827 100644 --- a/Client/src/Components/Avatar/index.jsx +++ b/Client/src/Components/Avatar/index.jsx @@ -32,13 +32,16 @@ const Avatar = ({ src, small, sx }) => { return ( { }} > {user.firstName?.charAt(0)} - {user.lastName?.charAt(0) || ''} + {user.lastName?.charAt(0) || ""} ); }; diff --git a/Client/src/Components/BasicTable/index.jsx b/Client/src/Components/BasicTable/index.jsx new file mode 100644 index 000000000..b93c584da --- /dev/null +++ b/Client/src/Components/BasicTable/index.jsx @@ -0,0 +1,360 @@ +/* TODO + +Should we delete this? +*/ + +import PropTypes from "prop-types"; +import { useState, useEffect } from "react"; +import { useTheme } from "@emotion/react"; +import { + TableContainer, + Paper, + Table, + TableHead, + TableRow, + TableCell, + TableBody, + TablePagination, + Box, + Typography, + Stack, + Button, +} from "@mui/material"; +import { useDispatch, useSelector } from "react-redux"; +import { setRowsPerPage } from "../../Features/UI/uiSlice"; +import LeftArrowDouble from "../../assets/icons/left-arrow-double.svg?react"; +import RightArrowDouble from "../../assets/icons/right-arrow-double.svg?react"; +import LeftArrow from "../../assets/icons/left-arrow.svg?react"; +import RightArrow from "../../assets/icons/right-arrow.svg?react"; +import SelectorVertical from "../../assets/icons/selector-vertical.svg?react"; +import "./index.css"; +/** + * Component for pagination actions (first, previous, next, last). + * + * @component + * @param {Object} props + * @param {number} props.count - Total number of items. + * @param {number} props.page - Current page number. + * @param {number} props.rowsPerPage - Number of rows per page. + * @param {function} props.onPageChange - Callback function to handle page change. + * + * @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)); + }; + + return ( + + + + + + + ); +}; + +TablePaginationActions.propTypes = { + count: PropTypes.number.isRequired, + page: PropTypes.number.isRequired, + rowsPerPage: PropTypes.number.isRequired, + onPageChange: PropTypes.func.isRequired, +}; + +/** + * BasicTable Component + * Renders a table with optional pagination. + * + * @component + * @param {Object} props - Component props. + * @param {Object} props.data - Data for the table including columns and rows. + * @param {Array} props.data.cols - Array of objects for column headers. + * @param {number} props.data.cols[].id - Unique identifier for the column. + * @param {string} props.data.cols[].name - Name of the column to display as header. + * @param {Array} props.data.rows - Array of row objects. + * @param {number} props.data.rows[].id - Unique identifier for the row. + * @param {Array} props.data.rows[].data - Array of cell data objects for the row. + * @param {number} props.data.rows[].data[].id - Unique identifier for the cell. + * @param {JSX.Element} props.data.rows[].data[].data - The content to display in the cell. + * @param {function} props.data.rows.data.handleClick - Function to call when the row is clicked. + * @param {boolean} [props.paginated=false] - Flag to enable pagination. + * @param {boolean} [props.reversed=false] - Flag to enable reverse order. + * @param {number} props.rowsPerPage- Number of rows per page (table). + * @param {string} props.emptyMessage - Message to display when there is no data. + * @example + * const data = { + * cols: [ + * { id: 1, name: "First Col" }, + * { id: 2, name: "Second Col" }, + * { id: 3, name: "Third Col" }, + * { id: 4, name: "Fourth Col" }, + * ], + * rows: [ + * { + * id: 1, + * data: [ + * { id: 1, data:
Data for Row 1 Col 1
}, + * { id: 2, data:
Data for Row 1 Col 2
}, + * { id: 3, data:
Data for Row 1 Col 3
}, + * { id: 4, data:
Data for Row 1 Col 4
}, + * ], + * }, + * { + * id: 2, + * data: [ + * { id: 5, data:
Data for Row 2 Col 1
}, + * { id: 6, data:
Data for Row 2 Col 2
}, + * { id: 7, data:
Data for Row 2 Col 3
}, + * { id: 8, data:
Data for Row 2 Col 4
}, + * ], + * }, + * ], + * }; + * + * + */ + +const BasicTable = ({ data, paginated, reversed, table, emptyMessage = "No data" }) => { + const DEFAULT_ROWS_PER_PAGE = 5; + const theme = useTheme(); + const dispatch = useDispatch(); + const uiState = useSelector((state) => state.ui); + let rowsPerPage = uiState?.[table]?.rowsPerPage ?? DEFAULT_ROWS_PER_PAGE; + const [page, setPage] = useState(0); + + useEffect(() => { + setPage(0); + }, [data]); + + const handleChangePage = (event, newPage) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = (event) => { + dispatch( + setRowsPerPage({ + value: parseInt(event.target.value, 10), + table: table, + }) + ); + setPage(0); + }; + + let displayData = []; + + if (data && data.rows) { + let rows = reversed ? [...data.rows].reverse() : data.rows; + displayData = paginated + ? rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) + : rows; + } + + if (!data || !data.cols || !data.rows) { + return
No data
; + } + + /** + * 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, data.rows.length); + return `${start} - ${end}`; + }; + + return ( + <> + + + + + {data.cols.map((col) => ( + {col.name} + ))} + + + + {displayData.map((row) => { + return ( + + {row.data.map((cell) => { + return {cell.data}; + })} + + ); + })} + {displayData.length === 0 && ( + + + {emptyMessage} + + + )} + +
+
+ {paginated && ( + + + Showing {getRange()} of {data.rows.length} monitor(s) + + + `Page ${page + 1} of ${Math.max(0, Math.ceil(count / rowsPerPage))}` + } + slotProps={{ + select: { + MenuProps: { + keepMounted: 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.primary.main, + }, + }, + }, + }} + sx={{ + mt: theme.spacing(6), + color: theme.palette.primary.contrastTextSecondary, + "& svg path": { + stroke: theme.palette.primary.contrastTextTertiary, + strokeWidth: 1.3, + }, + "& .MuiSelect-select": { + border: 1, + borderColor: theme.palette.primary.lowContrast, + borderRadius: theme.shape.borderRadius, + }, + }} + /> + + )} + + ); +}; + +BasicTable.propTypes = { + data: PropTypes.object.isRequired, + paginated: PropTypes.bool, + reversed: PropTypes.bool, + rowPerPage: PropTypes.number, + table: PropTypes.string, + emptyMessage: PropTypes.string, +}; + +export default BasicTable; diff --git a/Client/src/Components/Breadcrumbs/index.css b/Client/src/Components/Breadcrumbs/index.css index b089f8366..abb3c0a28 100644 --- a/Client/src/Components/Breadcrumbs/index.css +++ b/Client/src/Components/Breadcrumbs/index.css @@ -1,9 +1,9 @@ .MuiBreadcrumbs-root { - height: 34px; + min-height: 34px; } .MuiBreadcrumbs-root svg { width: 16px; - height: 16px; + min-height: 16px; } .MuiBreadcrumbs-root .MuiBreadcrumbs-li a { font-size: var(--env-var-font-size-medium); diff --git a/Client/src/Components/Breadcrumbs/index.jsx b/Client/src/Components/Breadcrumbs/index.jsx index d2c8e4019..0ebd8dab1 100644 --- a/Client/src/Components/Breadcrumbs/index.jsx +++ b/Client/src/Components/Breadcrumbs/index.jsx @@ -28,13 +28,16 @@ const Breadcrumbs = ({ list }) => { px={theme.spacing(2)} py={theme.spacing(3.5)} width="fit-content" - backgroundColor={theme.palette.background.fill} + backgroundColor={theme.palette.secondary.main} borderRadius={theme.shape.borderRadius} lineHeight="18px" sx={{ + "& .MuiBreadcrumbs-li a": { + transition: "background-color 0.2s ease-in-out, color 0.2s ease-in-out", + }, "& .MuiBreadcrumbs-li:not(:last-of-type):hover a": { - backgroundColor: theme.palette.other.fill, - opacity: 1, + backgroundColor: theme.palette.secondary.contrastText, + color: theme.palette.secondary.main, }, }} > @@ -52,7 +55,7 @@ const Breadcrumbs = ({ list }) => { opacity: 0.8, textTransform: "capitalize", "&, &:hover": { - color: theme.palette.text.tertiary, + color: theme.palette.secondary.contrastText, }, }} > diff --git a/Client/src/Components/Charts/AreaChart/index.jsx b/Client/src/Components/Charts/AreaChart/index.jsx index 5e239996b..e1f1832e5 100644 --- a/Client/src/Components/Charts/AreaChart/index.jsx +++ b/Client/src/Components/Charts/AreaChart/index.jsx @@ -149,7 +149,7 @@ const CustomAreaChart = ({ /> diff --git a/Client/src/Components/Charts/BarChart/index.jsx b/Client/src/Components/Charts/BarChart/index.jsx index a49fbc37d..101afe648 100644 --- a/Client/src/Components/Charts/BarChart/index.jsx +++ b/Client/src/Components/Charts/BarChart/index.jsx @@ -39,12 +39,15 @@ const BarChart = ({ checks = [] }) => { > {checks.map((check, index) => check === "placeholder" ? ( + /* TODO what is the purpose of this box? */ + // CAIO_REVIEW the purpose of this box is to make sure there are always at least 25 bars + // even if there are less than 25 checks { height={theme.spacing(4)} backgroundColor={ check.status - ? theme.palette.success.main - : theme.palette.error.contrastText + ? theme.palette.success.lowContrast + : theme.palette.error.lowContrast } sx={{ borderRadius: "50%" }} /> @@ -116,20 +119,22 @@ const BarChart = ({ checks = [] }) => { "& .MuiTooltip-tooltip": { backgroundColor: theme.palette.secondary.main, border: 1, - borderColor: theme.palette.border.dark, + borderColor: theme.palette.primary.lowContrast, borderRadius: theme.shape.borderRadius, boxShadow: theme.shape.boxShadow, px: theme.spacing(4), - py: theme.spacing(2), + py: theme.spacing(3), }, "& .MuiTooltip-tooltip p": { + /* TODO Font size should point to theme */ fontSize: 12, - color: theme.palette.text.tertiary, + color: theme.palette.secondary.contrastText, fontWeight: 500, }, "& .MuiTooltip-tooltip span": { + /* TODO Font size should point to theme */ fontSize: 11, - color: theme.palette.text.tertiary, + color: theme.palette.secondary.contrastText, fontWeight: 600, }, }, @@ -140,16 +145,14 @@ const BarChart = ({ checks = [] }) => { position="relative" width="9px" height="100%" - backgroundColor={ - check.status - ? theme.palette.success.contrastText - : theme.palette.error.dark - } + backgroundColor={theme.palette.primary.lowContrast} // CAIO_REVIEW sx={{ borderRadius: theme.spacing(1.5), + /* + TODO this doesn't seem to be used "&:hover > .MuiBox-root": { filter: "brightness(0.8)", - }, + }, */ }} > { width="100%" height={`${animate ? check.responseTime : 0}%`} backgroundColor={ - check.status ? theme.palette.success.main : theme.palette.error.main + check.status + ? theme.palette.success.lowContrast + : theme.palette.error.lowContrast } sx={{ borderRadius: theme.spacing(1.5), diff --git a/Client/src/Components/Charts/CustomGauge/index.jsx b/Client/src/Components/Charts/CustomGauge/index.jsx index 44512aa60..d29663004 100644 --- a/Client/src/Components/Charts/CustomGauge/index.jsx +++ b/Client/src/Components/Charts/CustomGauge/index.jsx @@ -54,8 +54,8 @@ const CustomGauge = ({ progress = 0, radius = 70, strokeWidth = 15, threshold = const fillColor = progressWithinRange > threshold - ? theme.palette.percentage.uptimePoor - : theme.palette.primary.main; + ? theme.palette.error.lowContrast // CAIO_REVIEW + : theme.palette.accent.main; // CAIO_REVIEW return ( - */ - -function Gauge({ progressValue, width = DEFAULT_WIDTH }) { - const theme = useTheme(); - const myProgressValue = Math.max(MINIMUM_VALUE, Math.min(progressValue, MAXIMUM_VALUE)); - const chartColor = - progressValue > PROGRESS_THRESHOLD - ? theme.palette.primary.main - : theme.palette.percentage.uptimePoor; - const chartData = [ - CHART_MAXIMUM_DATA, - { - value: myProgressValue, - fill: chartColor, - }, - ]; - - return ( - - - PROGRESS_THRESHOLD - ? theme.palette.primary.main - : theme.palette.percentage.uptimePoor - } - background={{ fill: theme.palette.background.fill }} - label={{ - position: "center", - content: () => ( - - {`${myProgressValue}%`} - - ), - }} - /> - - - ); -} - -export { Gauge }; diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx index 91cb1bf22..0b959a392 100644 --- a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx +++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx @@ -31,9 +31,9 @@ const CustomToolTip = ({ active, payload, label, dateRange }) => { { > { ml={theme.spacing(3)} sx={{ "& span": { - color: theme.palette.text.tertiary, + color: theme.palette.primary.contrastTextTertiary, fontSize: 11, fontWeight: 500, }, @@ -109,7 +109,7 @@ CustomToolTip.propTypes = { label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), dateRange: PropTypes.string, }; -const CustomTick = ({ x, y, payload, index, dateRange }) => { +const CustomTick = ({ x, y, payload, dateRange }) => { const format = tickDateFormatLookup(dateRange); const theme = useTheme(); const uiTimezone = useSelector((state) => state.ui.timezone); @@ -118,7 +118,7 @@ const CustomTick = ({ x, y, payload, index, dateRange }) => { x={x} y={y + 10} textAnchor="middle" - fill={theme.palette.text.tertiary} + fill={theme.palette.primary.contrastTextTertiary} fontSize={11} fontWeight={400} > @@ -139,7 +139,6 @@ const MonitorDetailsAreaChart = ({ checks, dateRange }) => { const theme = useTheme(); const memoizedChecks = useMemo(() => checks, [checks[0]]); const [isHovered, setIsHovered] = useState(false); - return ( { onMouseLeave={() => setIsHovered(false)} > { > } axisLine={false} @@ -195,17 +194,17 @@ const MonitorDetailsAreaChart = ({ checks, dateRange }) => { height={20} /> } wrapperStyle={{ pointerEvents: "none" }} /> diff --git a/Client/src/Components/Charts/Utils/chartUtils.jsx b/Client/src/Components/Charts/Utils/chartUtils.jsx index 1776c75d9..414e1ff9c 100644 --- a/Client/src/Components/Charts/Utils/chartUtils.jsx +++ b/Client/src/Components/Charts/Utils/chartUtils.jsx @@ -24,7 +24,7 @@ export const TzTick = ({ x, y, payload, index, dateRange }) => { x={x} y={y + 10} textAnchor="middle" - fill={theme.palette.text.tertiary} + fill={theme.palette.primary.contrastTextTertiary} fontSize={11} fontWeight={400} > @@ -59,7 +59,7 @@ export const PercentTick = ({ x, y, payload, index }) => { x={x - 20} y={y} textAnchor="middle" - fill={theme.palette.text.tertiary} + fill={theme.palette.primary.contrastTextTertiary} fontSize={11} fontWeight={400} > @@ -123,9 +123,9 @@ export const InfrastructureTooltip = ({ diff --git a/Client/src/Components/ConfigBox/index.jsx b/Client/src/Components/ConfigBox/index.jsx index 7650dd47f..8f7eb7b24 100644 --- a/Client/src/Components/ConfigBox/index.jsx +++ b/Client/src/Components/ConfigBox/index.jsx @@ -5,10 +5,10 @@ const ConfigBox = styled(Stack)(({ theme }) => ({ flexDirection: "row", justifyContent: "space-between", gap: theme.spacing(20), - backgroundColor: theme.palette.background.main, + backgroundColor: theme.palette.primary.main, border: 1, borderStyle: "solid", - borderColor: theme.palette.border.light, + borderColor: theme.palette.primary.lowContrast, borderRadius: theme.spacing(2), "& > *": { paddingTop: theme.spacing(12), @@ -18,7 +18,7 @@ const ConfigBox = styled(Stack)(({ theme }) => ({ flex: 0.7, borderRight: 1, borderRightStyle: "solid", - borderRightColor: theme.palette.border.light, + borderRightColor: theme.palette.primary.lowContrast, paddingRight: theme.spacing(15), paddingLeft: theme.spacing(15), }, @@ -28,10 +28,10 @@ const ConfigBox = styled(Stack)(({ theme }) => ({ paddingLeft: theme.spacing(18), }, "& h1, & h2": { - color: theme.palette.text.secondary, + color: theme.palette.primary.contrastTextSecondary, }, "& p": { - color: theme.palette.text.tertiary, + color: theme.palette.primary.contrastTextTertiary, }, })); diff --git a/Client/src/Components/Dialog/genericDialog.jsx b/Client/src/Components/Dialog/genericDialog.jsx index e3ebd51b1..871b3f329 100644 --- a/Client/src/Components/Dialog/genericDialog.jsx +++ b/Client/src/Components/Dialog/genericDialog.jsx @@ -22,9 +22,9 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) = left: "50%", transform: "translate(-50%, -50%)", width: 400, - bgcolor: theme.palette.background.main, + bgcolor: theme.palette.primary.main, border: 1, - borderColor: theme.palette.border.light, + borderColor: theme.palette.primary.lowContrast, borderRadius: theme.shape.borderRadius, boxShadow: 24, p: theme.spacing(15), @@ -37,7 +37,7 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) = id={titleId} component="h2" fontSize={16} - color={theme.palette.text.primary} + color={theme.palette.primary.contrastText} fontWeight={600} > {title} @@ -45,7 +45,7 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) = {description && ( {description} diff --git a/Client/src/Components/Fallback/index.css b/Client/src/Components/Fallback/index.css index f00fc1b09..9b4d6f653 100644 --- a/Client/src/Components/Fallback/index.css +++ b/Client/src/Components/Fallback/index.css @@ -12,7 +12,7 @@ width: max-content; } [class*="fallback__"] button.MuiButtonBase-root { - height: 34px; + min-height: 34px; } [class*="fallback__"] .check span.MuiTypography-root, [class*="fallback__"] button.MuiButtonBase-root { diff --git a/Client/src/Components/Fallback/index.jsx b/Client/src/Components/Fallback/index.jsx index 1af38c052..61840452f 100644 --- a/Client/src/Components/Fallback/index.jsx +++ b/Client/src/Components/Fallback/index.jsx @@ -40,7 +40,7 @@ const Fallback = ({ title, checks, link = "/", isAdmin, vowelStart = false }) => className="background-pattern-svg" sx={{ "& svg g g:last-of-type path": { - stroke: theme.palette.border.light, + stroke: theme.palette.primary.lowContrast, }, }} > @@ -54,7 +54,7 @@ const Fallback = ({ title, checks, link = "/", isAdmin, vowelStart = false }) => {vowelStart ? "An" : "A"} {title} is used to: @@ -70,7 +70,7 @@ const Fallback = ({ title, checks, link = "/", isAdmin, vowelStart = false }) => {isAdmin && (