Refactor reusable components out of Uptime Details

This commit is contained in:
Alex Holliday
2025-01-28 15:42:09 -08:00
parent fe0f861e98
commit 784dc4da63
12 changed files with 80 additions and 257 deletions
@@ -0,0 +1,28 @@
// Components
import { Stack } from "@mui/material";
import SkeletonLayout from "./skeleton";
// Utils
import { useTheme } from "@mui/material/styles";
import PropTypes from "prop-types";
const StatusBoxes = ({ shouldRender, children }) => {
const theme = useTheme();
if (!shouldRender) {
return <SkeletonLayout numBoxes={children?.length ?? 1} />;
}
return (
<Stack
direction="row"
gap={theme.spacing(8)}
>
{children}
</Stack>
);
};
StatusBoxes.propTypes = {
shouldRender: PropTypes.bool,
children: PropTypes.node,
};
export default StatusBoxes;
@@ -0,0 +1,31 @@
import { Stack, Skeleton } from "@mui/material";
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
const SkeletonLayout = ({ numBoxes }) => {
const theme = useTheme();
return (
<Stack
direction="row"
gap={theme.spacing(4)}
mt={theme.spacing(4)}
>
{Array.from({ length: numBoxes }).map((_, index) => {
const width = `${100 / numBoxes}%`;
return (
<Skeleton
variant="rounded"
width={width}
height={50}
key={index}
/>
);
})}
</Stack>
);
};
SkeletonLayout.propTypes = {
numBoxes: PropTypes.number,
};
export default SkeletonLayout;
@@ -1,6 +1,6 @@
// Components
import { Stack, Typography, Box } from "@mui/material";
import ChartBox from "../Charts/ChartBox";
import ChartBox from "../../../../../Components/Charts/ChartBox";
import UptimeIcon from "../../../../../assets/icons/uptime-icon.svg?react";
import IncidentsIcon from "../../../../../assets/icons/incidents.svg?react";
import AverageResponseIcon from "../../../../../assets/icons/average-response-icon.svg?react";
@@ -40,7 +40,10 @@ const ChartBoxes = ({
icon={<UptimeIcon />}
header="Uptime"
>
<Stack justifyContent="space-between">
<Stack
justifyContent="space-between"
direction="row"
>
<Box position="relative">
<Typography>Total Checks</Typography>
<Typography component="span">
@@ -129,6 +132,7 @@ const ChartBoxes = ({
export default ChartBoxes;
ChartBoxes.propTypes = {
shouldRender: PropTypes.bool,
monitor: PropTypes.object.isRequired,
dateRange: PropTypes.string.isRequired,
uiTimezone: PropTypes.string.isRequired,
@@ -1,75 +0,0 @@
import { Stack, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import IconBox from "../../../../../Components/IconBox";
import PropTypes from "prop-types";
const ChartBox = ({ children, icon, header, height = "300px" }) => {
const theme = useTheme();
return (
<Stack
sx={{
justifyContent: "space-between",
flex: "1 30%",
gap: theme.spacing(8),
height,
minWidth: 250,
padding: theme.spacing(8),
border: 1,
borderStyle: "solid",
borderColor: theme.palette.primary.lowContrast,
borderRadius: 4,
backgroundColor: theme.palette.primary.main,
"& h2": {
color: theme.palette.primary.contrastTextSecondary,
fontSize: 15,
fontWeight: 500,
},
"& .MuiBox-root:not(.area-tooltip) p": {
color: theme.palette.primary.contrastTextTertiary,
fontSize: 13,
},
"& .MuiBox-root > span": {
color: theme.palette.primary.contrastText,
fontSize: 20,
"& span": {
opacity: 0.8,
marginLeft: 2,
fontSize: 15,
},
},
"& .MuiStack-root": {
flexDirection: "row",
gap: theme.spacing(6),
},
"& .MuiStack-root:first-of-type": {
alignItems: "center",
},
"& tspan, & text": {
fill: theme.palette.primary.contrastTextTertiary,
},
"& path": {
transition: "fill 300ms ease, stroke-width 400ms ease",
},
}}
>
<Stack
direction="row"
alignItems="center"
gap={theme.spacing(2)}
>
<IconBox>{icon}</IconBox>
<Typography component="h2">{header}</Typography>
</Stack>
{children}
</Stack>
);
};
export default ChartBox;
ChartBox.propTypes = {
children: PropTypes.node,
icon: PropTypes.node.isRequired,
header: PropTypes.string.isRequired,
height: PropTypes.string,
};
@@ -1,4 +1,4 @@
import ChartBox from "./ChartBox";
import ChartBox from "../../../../../Components/Charts/ChartBox";
import MonitorDetailsAreaChart from "../../../../../Components/Charts/MonitorDetailsAreaChart";
import ResponseTimeIcon from "../../../../../assets/icons/response-time-icon.svg?react";
import SkeletonLayout from "./ResponseTimeChartSkeleton";
@@ -1,40 +0,0 @@
import { Button, Box } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
import SettingsIcon from "../../../../../assets/icons/settings-bold.svg?react";
import PropTypes from "prop-types";
const ConfigButton = ({ shouldRender, monitorId }) => {
const theme = useTheme();
const navigate = useNavigate();
if (!shouldRender) return null;
return (
<Box alignSelf="flex-end">
<Button
variant="contained"
color="secondary"
onClick={() => navigate(`/uptime/configure/${monitorId}`)}
sx={{
px: theme.spacing(5),
"& svg": {
mr: theme.spacing(3),
"& path": {
/* Should always be contrastText for the button color */
stroke: theme.palette.secondary.contrastText,
},
},
}}
>
<SettingsIcon /> Configure
</Button>
</Box>
);
};
ConfigButton.propTypes = {
shouldRender: PropTypes.bool,
monitorId: PropTypes.string,
};
export default ConfigButton;
@@ -1,55 +0,0 @@
import { Stack, Typography } from "@mui/material";
import PulseDot from "../../../../../Components/Animated/PulseDot";
import Dot from "../../../../../Components/Dot";
import { useTheme } from "@emotion/react";
import useUtils from "../../../Monitors/Hooks/useUtils";
import { formatDurationRounded } from "../../../../../Utils/timeUtils";
import ConfigButton from "../ConfigButton";
import SkeletonLayout from "./skeleton";
import PropTypes from "prop-types";
const MonitorHeader = ({ shouldRender = true, isAdmin, monitor }) => {
const theme = useTheme();
const { statusColor, statusMsg, determineState } = useUtils();
console.log(shouldRender);
if (!shouldRender) {
return <SkeletonLayout />;
}
return (
<Stack
direction="row"
justifyContent="space-between"
>
<Stack>
<Typography variant="h1">{monitor.name}</Typography>
<Stack
direction="row"
alignItems={"center"}
gap={theme.spacing(2)}
>
<PulseDot color={statusColor[determineState(monitor)]} />
<Typography variant="h2">
{monitor?.url?.replace(/^https?:\/\//, "") || "..."}
</Typography>
<Dot />
<Typography>
Checking every {formatDurationRounded(monitor?.interval)}.
</Typography>
</Stack>
</Stack>
<ConfigButton
shouldRender={isAdmin}
monitorId={monitor._id}
/>
</Stack>
);
};
MonitorHeader.propTypes = {
shouldRender: PropTypes.bool,
isAdmin: PropTypes.bool,
monitor: PropTypes.object,
};
export default MonitorHeader;
@@ -1,23 +0,0 @@
import { Stack, Skeleton } from "@mui/material";
const SkeletonLayout = () => {
return (
<Stack
direction="row"
justifyContent="space-between"
>
<Skeleton
height={40}
variant="rounded"
width="15%"
/>
<Skeleton
height={40}
variant="rounded"
width="15%"
/>
</Stack>
);
};
export default SkeletonLayout;
@@ -1,4 +1,4 @@
import ChartBox from "../Charts/ChartBox";
import ChartBox from "../../../../../Components/Charts/ChartBox";
import PropTypes from "prop-types";
import HistoryIcon from "../../../../../assets/icons/history-icon.svg?react";
import Table from "../../../../../Components/Table";
@@ -1,30 +0,0 @@
import { Stack, Skeleton } from "@mui/material";
import { useTheme } from "@emotion/react";
const SkeletonLayout = () => {
const theme = useTheme();
return (
<Stack
direction="row"
gap={theme.spacing(4)}
mt={theme.spacing(4)}
>
<Skeleton
variant="rounded"
width="33%"
height={50}
/>
<Skeleton
variant="rounded"
width="33%"
height={50}
/>
<Skeleton
variant="rounded"
width="33%"
height={50}
/>
</Stack>
);
};
export default SkeletonLayout;
@@ -1,20 +1,13 @@
// Components
import { Stack, Typography } from "@mui/material";
import StatusBoxes from "../../../../../Components/StatusBoxes";
import StatBox from "../../../../../Components/StatBox";
import SkeletonLayout from "./skeleton";
// Utils
import { useTheme } from "@mui/material/styles";
import useUtils from "../../../Monitors/Hooks/useUtils";
import { getHumanReadableDuration } from "../../../../../Utils/timeUtils";
import PropTypes from "prop-types";
const StatusBoxes = ({ shouldRender, monitor, certificateExpiry }) => {
// Utils
import { useTheme } from "@mui/material/styles";
import { Typography } from "@mui/material";
import useUtils from "../../../Monitors/Hooks/useUtils";
const UptimeStatusBoxes = ({ shouldRender, monitor, certificateExpiry }) => {
const theme = useTheme();
const { determineState } = useUtils();
if (!shouldRender) {
return <SkeletonLayout />;
}
const { time: streakTime, units: streakUnits } = getHumanReadableDuration(
monitor?.uptimeStreak
);
@@ -22,12 +15,8 @@ const StatusBoxes = ({ shouldRender, monitor, certificateExpiry }) => {
const { time: lastCheckTime, units: lastCheckUnits } = getHumanReadableDuration(
monitor?.timeSinceLastCheck
);
return (
<Stack
direction="row"
gap={theme.spacing(8)}
>
<StatusBoxes shouldRender={shouldRender}>
<StatBox
gradient={true}
status={determineState(monitor)}
@@ -70,14 +59,8 @@ const StatusBoxes = ({ shouldRender, monitor, certificateExpiry }) => {
</Typography>
}
/>
</Stack>
</StatusBoxes>
);
};
StatusBoxes.propTypes = {
shouldRender: PropTypes.bool,
monitor: PropTypes.object,
certificateExpiry: PropTypes.string,
};
export default StatusBoxes;
export default UptimeStatusBoxes;
+4 -4
View File
@@ -1,11 +1,11 @@
// Components
import Breadcrumbs from "../../../Components/Breadcrumbs";
import MonitorHeader from "./Components/MonitorHeader";
import StatusBoxes from "./Components/StatusBoxes";
import MonitorStatusHeader from "../../../Components/MonitorStatusHeader";
import TimeFramePicker from "./Components/TimeFramePicker";
import ChartBoxes from "./Components/ChartBoxes";
import ResponseTimeChart from "./Components/Charts/ResponseTimeChart";
import ResponseTable from "./Components/ResponseTable";
import UptimeStatusBoxes from "./Components/UptimeStatusBoxes";
// MUI Components
import { Stack } from "@mui/material";
@@ -80,12 +80,12 @@ const UptimeDetails = () => {
return (
<Stack gap={theme.spacing(10)}>
<Breadcrumbs list={BREADCRUMBS} />
<MonitorHeader
<MonitorStatusHeader
isAdmin={isAdmin}
shouldRender={!monitorIsLoading}
monitor={monitor}
/>
<StatusBoxes
<UptimeStatusBoxes
shouldRender={!monitorIsLoading}
monitor={monitor}
certificateExpiry={certificateExpiry}