diff --git a/Client/package-lock.json b/Client/package-lock.json index ffe529997..145d2783b 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -1725,9 +1725,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.20.0.tgz", - "integrity": "sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.21.0.tgz", + "integrity": "sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -5442,12 +5442,12 @@ } }, "node_modules/react-router": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.27.0.tgz", - "integrity": "sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==", + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.28.0.tgz", + "integrity": "sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.20.0" + "@remix-run/router": "1.21.0" }, "engines": { "node": ">=14.0.0" @@ -5457,13 +5457,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.27.0.tgz", - "integrity": "sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==", + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.28.0.tgz", + "integrity": "sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.20.0", - "react-router": "6.27.0" + "@remix-run/router": "1.21.0", + "react-router": "6.28.0" }, "engines": { "node": ">=14.0.0" diff --git a/Client/src/Components/Charts/Gauge/index.jsx b/Client/src/Components/Charts/Gauge/index.jsx new file mode 100644 index 000000000..0ae872e3f --- /dev/null +++ b/Client/src/Components/Charts/Gauge/index.jsx @@ -0,0 +1,124 @@ +import { RadialBarChart, RadialBar, ResponsiveContainer } from "recharts"; +import PropTypes from "prop-types"; +import { useTheme } from "@emotion/react"; + +const MINIMUM_VALUE = 0; +const MAXIMUM_VALUE = 100; +const DATA = [{ value: MAXIMUM_VALUE }]; +const PROGRESS_THRESHOLD = 50; +const DEFAULT_CONTAINER_HEIGHT = 160; +const TEXT_POSITIONS = { + value: { x: "50%", y: "45%" }, + label: { x: "50%", y: "55%" }, +}; +const COMMON_RADIAL_BAR_PROPS = { + minAngle: 15, + clockWise: true, + dataKey: "value", + cornerRadius: 0, +}; + +const RADIAL_BAR_CHART_PROPS = { + cx: "50%", + cy: "50%", + innerRadius: "45%", + outerRadius: "100%", + startAngle: 90, + endAngle: -270, +}; + +const RADIAL_BAR_OVERLAY_PROPS = { + transform: "rotate(180deg)", + position: "absolute", + top: "0", + left: "0", +}; + +const COMMON_HEADER_PROPS = { + textAnchor: "middle", + dominantBaseline: "middle", +}; + +/** + * A circular gauge component that displays a progress value and text. + * The gauge fills based on the progress value and changes color at a 50% threshold. + * + * @component + * @param {Object} props - Component props + * @param {number} props.progressValue - The value to display in the gauge (0-100) + * @param {string} props.displayText - The text to display below the progress value + * @param {number} [props.containerHeight=160] - Height of the gauge container in pixels + * @param {Object} [props.gaugeHeader] - Custom styles to apply to the progress value text + * @param {Object} [props.gaugeSubheader] - Custom styles to apply to the display text + * + * @returns {JSX.Element} A circular gauge chart with progress value and text + * + * @example + * + */ +const Gauge = ({ + progressValue, + displayText, + containerHeight, + gaugeHeader, + gaugeSubheader, +}) => { + const theme = useTheme(); + const myProgressValue = Math.max(MINIMUM_VALUE, Math.min(progressValue, MAXIMUM_VALUE)); + + return ( + + + PROGRESS_THRESHOLD + ? theme.palette.primary.main + : theme.palette.percentage.uptimePoor + } + background={{ fill: theme.palette.background.fill }} + data={[{ value: myProgressValue }]} + /> + + + {`${myProgressValue}%`} + + + {`${displayText}`} + + + + ); +}; + +Gauge.propTypes = { + progressValue: PropTypes.number.isRequired, + displayText: PropTypes.string.isRequired, + containerHeight: PropTypes.number, + gaugeHeader: PropTypes.object, + gaugeSubheader: PropTypes.object, +}; +export default Gauge; diff --git a/Client/src/Utils/Theme/darkTheme.js b/Client/src/Utils/Theme/darkTheme.js index d182f0496..6eca20aa3 100644 --- a/Client/src/Utils/Theme/darkTheme.js +++ b/Client/src/Utils/Theme/darkTheme.js @@ -29,6 +29,18 @@ const darkTheme = createTheme({ body1: { fontSize: 13, color: text.tertiary, fontWeight: 400 }, body2: { fontSize: 12, color: text.tertiary, fontWeight: 400 }, }, + chart: { + header: { + fontWeight: 400, + fill: text.tertiary, + fontSize: 11, + }, + subheader: { + fontWeight: 400, + fill: text.tertiary, + fontSize: 9, + }, + }, palette: { mode: "dark", primary: { main: "#1570ef" }, diff --git a/Client/src/Utils/Theme/lightTheme.js b/Client/src/Utils/Theme/lightTheme.js index d2bfafaf4..e6335a062 100644 --- a/Client/src/Utils/Theme/lightTheme.js +++ b/Client/src/Utils/Theme/lightTheme.js @@ -12,6 +12,7 @@ const background = { fill: "#F4F4F4", accent: "#f9fafb", }; + const border = { light: "#eaecf0", dark: "#d0d5dd" }; const fontFamilyDefault = @@ -28,6 +29,18 @@ const lightTheme = createTheme({ body1: { fontSize: 13, color: text.tertiary, fontWeight: 400 }, body2: { fontSize: 12, color: text.tertiary, fontWeight: 400 }, }, + chart: { + header: { + fontWeight: 400, + fill: text.tertiary, + fontSize: 11, + }, + subheader: { + fontWeight: 400, + fill: text.tertiary, + fontSize: 9, + }, + }, palette: { primary: { main: "#1570EF" }, secondary: { main: "#F4F4F4", dark: "#e3e3e3", contrastText: "#475467" },