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" },