diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx index fd28b6e7e..023962cb0 100644 --- a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx +++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx @@ -6,10 +6,12 @@ import { Tooltip, CartesianGrid, ResponsiveContainer, + Text, } from "recharts"; import { Box, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useMemo } from "react"; +import { formatDate } from "../../../Utils/timeUtils"; import "./index.css"; const CustomToolTip = ({ active, payload, label }) => { @@ -87,20 +89,34 @@ const CustomToolTip = ({ active, payload, label }) => { return null; }; -const MonitorDetailsAreaChart = ({ checks }) => { - const formatDate = (timestamp) => { - const date = new Date(timestamp); - return date.toLocaleTimeString("en-US", { - hour: "numeric", - minute: "2-digit", - hour12: true, - }); - }; - - const memoizedChecks = useMemo(() => checks, [checks[0]]); - +const CustomTick = ({ x, y, payload, index }) => { const theme = useTheme(); + // Render nothing for the first tick + if (index === 0) return null; + + return ( + + {formatDate(new Date(payload.value), { + year: undefined, + month: undefined, + day: undefined, + })} + + ); +}; + +const MonitorDetailsAreaChart = ({ checks }) => { + const theme = useTheme(); + const memoizedChecks = useMemo(() => checks, [checks[0]]); + return ( { } + minTickGap={0} + axisLine={false} tickLine={false} - height={18} + height={20} + interval="equidistantPreserveStart" /> { - const keyToLabel = { - performance: "Performance", - seo: "SEO", - bestPractices: "Best practices", - accessibility: "Accessibility", - }; - - const colors = { - performance: "#2aa02b", - seo: "#9467bd", - bestPractices: "#ff7f0e", - accessibility: "#1f76b3", - }; - - const customize = { - legend: { position: { vertical: "bottom", horizontal: "middle" } }, - margin: { bottom: 75 }, - }; - - const xLabels = pageSpeedChecks.map((check) => { - return check.createdAt; - }); - - return ( - ({ - dataKey: key, - label: keyToLabel[key], - color: colors[key], - showMark: false, - }))} - yAxis={[{ min: 0, max: 100 }]} - xAxis={[ - { - scaleType: "point", - data: xLabels, - valueFormatter: (val) => new Date(val).toLocaleDateString(), - }, - ]} - dataset={pageSpeedChecks} - {...customize} - grid={{ vertical: true, horizontal: true }} - tooltip={{ trigger: "none" }} - slotProps={{ - legend: { - direction: "row", - position: { vertical: "bottom", horizontal: "middle" }, - padding: 2, - itemMarkWidth: 8, - itemMarkHeight: 8, - markGap: 5, - itemGap: 15, - labelStyle: { - fontSize: 13, - color: "#344054" - } - }, - }} - /> - ); -}; - -PageSpeedLineChart.propTypes = { - pageSpeedChecks: PropTypes.array, -}; - -export default PageSpeedLineChart; diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 41e529eb9..76e06b408 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -445,14 +445,8 @@ const DetailsPage = ({ isAdmin }) => { data={[{ response: monitor?.periodAvgResponseTime }]} /> - - + + diff --git a/Client/src/Pages/Monitors/Details/styled.jsx b/Client/src/Pages/Monitors/Details/styled.jsx index 7f580d7c7..728df4922 100644 --- a/Client/src/Pages/Monitors/Details/styled.jsx +++ b/Client/src/Pages/Monitors/Details/styled.jsx @@ -76,7 +76,7 @@ export const StatBox = styled(Box)(({ theme }) => ({ borderColor: theme.palette.border.light, borderRadius: 4, backgroundColor: theme.palette.background.main, - background: `linear-gradient(340deg, ${theme.palette.background.accent} 20%, ${theme.palette.background.main} 35%)`, + background: `linear-gradient(340deg, ${theme.palette.background.accent} 20%, ${theme.palette.background.main} 45%)`, "& h2": { fontSize: 13, fontWeight: 500, diff --git a/Client/src/Pages/PageSpeed/Details/Charts/AreaChart.jsx b/Client/src/Pages/PageSpeed/Details/Charts/AreaChart.jsx index 6c26c43bd..b040d87a5 100644 --- a/Client/src/Pages/PageSpeed/Details/Charts/AreaChart.jsx +++ b/Client/src/Pages/PageSpeed/Details/Charts/AreaChart.jsx @@ -1,3 +1,4 @@ +import PropTypes from "prop-types"; import { AreaChart, Area, @@ -5,6 +6,7 @@ import { Tooltip, CartesianGrid, ResponsiveContainer, + Text, } from "recharts"; import { useTheme } from "@emotion/react"; import { useMemo } from "react"; @@ -12,28 +14,37 @@ import { Box, Stack, Typography } from "@mui/material"; import { formatDate } from "../../../../Utils/timeUtils"; const config = { - accessibility: { - id: "accessibility", - text: "accessibility", - color: "primary", - }, - bestPractices: { - id: "bestPractices", - text: "best practices", - color: "warning", + seo: { + id: "seo", + text: "SEO", + color: "unresolved", }, performance: { id: "performance", text: "performance", color: "success", }, - seo: { - id: "seo", - text: "SEO", - color: "unresolved", + bestPractices: { + id: "bestPractices", + text: "best practices", + color: "warning", + }, + accessibility: { + id: "accessibility", + text: "accessibility", + color: "primary", }, }; +/** + * Custom tooltip for the area chart. + * @param {Object} props + * @param {boolean} props.active - Whether the tooltip is active. + * @param {Array} props.payload - The payload data for the tooltip. + * @param {string} props.label - The label for the tooltip. + * @returns {JSX.Element|null} The tooltip element or null if not active. + */ + const CustomToolTip = ({ active, payload, label }) => { const theme = useTheme(); @@ -58,50 +69,64 @@ const CustomToolTip = ({ active, payload, label }) => { > {formatDate(new Date(label))} - {Object.keys(config).map((key) => { - const { color } = config[key]; - const dotColor = theme.palette[color].main; + {Object.keys(config) + .reverse() + .map((key) => { + const { color } = config[key]; + const dotColor = theme.palette[color].main; - return ( - - - - {config[key].text} - {" "} - - {payload[0].payload[key]} - - - ); - })} + + + {config[key].text} + {" "} + + {payload[0].payload[key]} + + + ); + })} ); } return null; }; +CustomToolTip.propTypes = { + active: PropTypes.bool, + payload: PropTypes.array, + label: PropTypes.string, +}; + +/** + * Processes data to insert gaps with null values based on the interval. + * @param {Array} data + * @param {number} interval - The interval in milliseconds for gaps. + * @returns {Array} The formatted data with gaps. + */ const processDataWithGaps = (data, interval) => { if (data.length === 0) return []; let formattedData = []; @@ -137,6 +162,57 @@ const processDataWithGaps = (data, interval) => { return formattedData; }; +/** + * Custom tick component to render ticks on the XAxis. + * + * @param {Object} props + * @param {number} props.x - The x coordinate for the tick. + * @param {number} props.y - The y coordinate for the tick. + * @param {Object} props.payload - The data object containing the tick value. + * @param {number} props.index - The index of the tick in the array of ticks. + * + * @returns {JSX.Element|null} The tick element or null if the tick should be hidden. + */ +const CustomTick = ({ x, y, payload, index }) => { + const theme = useTheme(); + + // Render nothing for the first tick + if (index === 0) return null; + + return ( + + {formatDate(new Date(payload.value), { + year: undefined, + month: undefined, + day: undefined, + })} + + ); +}; +CustomTick.propTypes = { + x: PropTypes.number, + y: PropTypes.number, + payload: PropTypes.shape({ + value: PropTypes.string.isRequired, + }), + index: PropTypes.number, +}; + +/** + * A chart displaying pagespeed details over time. + * @param {Object} props + * @param {Array} props.data - The data to display in the chart. + * @param {number} props.interval - The interval in milliseconds for processing gaps. + * @returns {JSX.Element} The area chart component. + */ + const PagespeedDetailsAreaChart = ({ data, interval }) => { const theme = useTheme(); const memoizedData = useMemo( @@ -162,23 +238,12 @@ const PagespeedDetailsAreaChart = ({ data, interval }) => { - formatDate(new Date(timestamp), { - year: undefined, - month: undefined, - day: undefined, - }) - } - tick={{ - fontSize: 11, - fontWeight: 100, - opacity: 0.8, - stroke: theme.palette.text.tertiary, - }} + tick={} + axisLine={false} tickLine={false} - minTickGap={20} height={18} - interval="preserveEnd" + minTickGap={0} + interval="equidistantPreserveStart" /> { ); }; +PagespeedDetailsAreaChart.propTypes = { + data: PropTypes.arrayOf( + PropTypes.shape({ + createdAt: PropTypes.string.isRequired, + accessibility: PropTypes.oneOfType([PropTypes.string, PropTypes.number]) + .isRequired, + bestPractices: PropTypes.oneOfType([PropTypes.string, PropTypes.number]) + .isRequired, + performance: PropTypes.oneOfType([PropTypes.string, PropTypes.number]) + .isRequired, + seo: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + }) + ).isRequired, + interval: PropTypes.number.isRequired, +}; + export default PagespeedDetailsAreaChart; diff --git a/Client/src/Pages/PageSpeed/Details/index.jsx b/Client/src/Pages/PageSpeed/Details/index.jsx index 657ecc36d..07953609a 100644 --- a/Client/src/Pages/PageSpeed/Details/index.jsx +++ b/Client/src/Pages/PageSpeed/Details/index.jsx @@ -15,9 +15,9 @@ import { logger } from "../../../Utils/Logger"; import { networkService } from "../../../main"; import SkeletonLayout from "./skeleton"; import SettingsIcon from "../../../assets/icons/settings-bold.svg?react"; +import MetricsIcon from "../../../assets/icons/ruler-icon.svg?react"; import ScoreIcon from "../../../assets/icons/monitor-graph-line.svg?react"; import PerformanceIcon from "../../../assets/icons/performance-report.svg?react"; -import PageSpeedLineChart from "../../../Components/Charts/PagespeedLineChart"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import PulseDot from "../../../Components/Animated/PulseDot"; import PagespeedDetailsAreaChart from "./Charts/AreaChart"; @@ -402,23 +402,44 @@ const PageSpeedDetails = () => { - + Showing statistics for past 24 hours. + + + + + + Score history + + + + + + + + + + Metrics + + + - - - - - - Score history - - - - + @@ -536,16 +557,7 @@ const PageSpeedDetails = () => { - + ({ - gap: theme.spacing(8), + display: "grid", height: 300, minWidth: 250, - padding: theme.spacing(8), border: 1, borderStyle: "solid", borderColor: theme.palette.border.light, borderRadius: 4, + borderTopRightRadius: 16, + borderBottomRightRadius: 16, backgroundColor: theme.palette.background.main, "& h2": { color: theme.palette.text.secondary, fontSize: 15, fontWeight: 500, }, - "& p": { - color: theme.palette.text.secondary, + "& p": { color: theme.palette.text.secondary }, + "& > :nth-of-type(1)": { + gridColumn: 1, + gridRow: 1, + height: "fit-content", + paddingTop: theme.spacing(8), + paddingLeft: theme.spacing(8), + }, + "& > :nth-of-type(2)": { gridColumn: 1, gridRow: 2 }, + "& > :nth-of-type(3)": { + gridColumn: 2, + gridRow: "span 2", + padding: theme.spacing(8), + borderLeft: 1, + borderLeftStyle: "solid", + borderLeftColor: theme.palette.border.light, + borderRadius: 16, + backgroundColor: theme.palette.background.main, + background: `linear-gradient(325deg, ${theme.palette.background.accent} 20%, ${theme.palette.background.main} 45%)`, }, })); @@ -52,7 +70,7 @@ export const StatBox = styled(Box)(({ theme }) => ({ borderColor: theme.palette.border.light, borderRadius: 4, backgroundColor: theme.palette.background.main, - background: `linear-gradient(340deg, ${theme.palette.background.accent} 20%, ${theme.palette.background.main} 35%)`, + background: `linear-gradient(340deg, ${theme.palette.background.accent} 20%, ${theme.palette.background.main} 45%)`, "& h2": { fontSize: 13, fontWeight: 500, diff --git a/Client/src/Utils/Theme/darkTheme.js b/Client/src/Utils/Theme/darkTheme.js index 261f8135f..2d1f80635 100644 --- a/Client/src/Utils/Theme/darkTheme.js +++ b/Client/src/Utils/Theme/darkTheme.js @@ -63,7 +63,7 @@ const darkTheme = createTheme({ uptimeGood: "#ffd600", uptimeExcellent: "#079455", }, - unresolved: { main: "#4e5ba6", light: "#e2eaf7", bg: "#f2f4f7" }, + unresolved: { main: "#664eff", light: "#3a1bff", bg: "#f2f4f7" }, divider: border.light, other: { icon: "#e6e6e6", diff --git a/Client/src/assets/icons/ruler-icon.svg b/Client/src/assets/icons/ruler-icon.svg new file mode 100644 index 000000000..7266d2876 --- /dev/null +++ b/Client/src/assets/icons/ruler-icon.svg @@ -0,0 +1,3 @@ + + +