feat: simplify code and functionalities

This commit is contained in:
Malena Aguiar committed 2026-09-12 15:41:03 -06:00
1 parent 896d73f12e
commit 71dd998390
11 files changed
+23 -735

No files matched your search

@@ -3,9 +3,7 @@ import Skeleton from "@mui/material/Skeleton";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { useTheme } from "@mui/material/styles";
import { GripVertical } from "lucide-react";
import { useTranslation } from "react-i18next";
import type { DraggableSyntheticListeners } from "@dnd-kit/core";
import { LAYOUT } from "@/Utils/Theme/constants";
import { typographyLevels } from "@/Utils/Theme/Palette";
@@ -14,7 +12,6 @@ interface DashboardCardProps {
topRight?: React.ReactNode;
isLoading?: boolean;
error?: unknown;
dragHandleProps?: DraggableSyntheticListeners;
children: React.ReactNode;
}
@@ -23,7 +20,6 @@ export const DashboardCard = ({
topRight,
isLoading,
error,
dragHandleProps,
children,
}: DashboardCardProps) => {
const theme = useTheme();
@@ -82,33 +78,14 @@ export const DashboardCard = ({
>
{title}
</Typography>
<Stack
direction="row"
alignItems="center"
gap={theme.spacing(LAYOUT.SM)}
>
{topRight && (
<Typography
fontSize={typographyLevels.s}
color={theme.palette.text.secondary}
>
{topRight}
</Typography>
)}
<Box
color={theme.palette.text.disabled}
display="flex"
alignItems="center"
{...(dragHandleProps ?? {})}
sx={{
cursor: dragHandleProps ? "grab" : "default",
"&:active": { cursor: dragHandleProps ? "grabbing" : "default" },
outline: "none",
}}
{topRight && (
<Typography
fontSize={typographyLevels.s}
color={theme.palette.text.secondary}
>
<GripVertical size={LAYOUT.XXL} />
</Box>
</Stack>
{topRight}
</Typography>
)}
</Stack>
{renderContent()}
</Box>
@@ -1,181 +0,0 @@
import Dialog from "@mui/material/Dialog";
import DialogTitle from "@mui/material/DialogTitle";
import DialogContent from "@mui/material/DialogContent";
import DialogActions from "@mui/material/DialogActions";
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import FormControlLabel from "@mui/material/FormControlLabel";
import { useTheme } from "@mui/material/styles";
import { useDispatch, useSelector } from "react-redux";
import { useTranslation } from "react-i18next";
import { Button, Checkbox } from "@/Components/inputs";
import { setDashboardCardsVisibility, resetDashboardCards } from "@/Features/UI/uiSlice";
import {
cardCategories,
defaultVisibleCards,
DASHBOARD_CARD_IDS,
} from "@/Pages/Dashboard/dashboardCards";
import type { DashboardCardId } from "@/Pages/Dashboard/dashboardCards";
import type { RootState } from "@/Types/state";
import { LAYOUT } from "@/Utils/Theme/constants";
import { typographyLevels } from "@/Utils/Theme/Palette";
interface EditCardsModalProps {
open: boolean;
onClose: () => void;
}
const leftCategories = cardCategories.slice(0, Math.ceil(cardCategories.length / 2));
const rightCategories = cardCategories.slice(Math.ceil(cardCategories.length / 2));
export const EditCardsModal = ({ open, onClose }: EditCardsModalProps) => {
const theme = useTheme();
const dispatch = useDispatch();
const { t } = useTranslation();
const visibility = useSelector(
(state: RootState) => state.ui.dashboardCards ?? defaultVisibleCards
);
const allVisible = DASHBOARD_CARD_IDS.every((id) => visibility[id]);
const someVisible = DASHBOARD_CARD_IDS.some((id) => visibility[id]);
const selectAllIndeterminate = someVisible && !allVisible;
const handleToggleCard = (id: DashboardCardId, visible: boolean) => {
dispatch(setDashboardCardsVisibility({ id, visible }));
};
const handleSelectAll = () => {
const next = !allVisible;
DASHBOARD_CARD_IDS.forEach((id) => {
dispatch(setDashboardCardsVisibility({ id, visible: next }));
});
};
const handleReset = () => {
dispatch(resetDashboardCards());
};
const renderCategory = (category: (typeof cardCategories)[number]) => (
<Stack
key={category.labelKey}
gap={theme.spacing(LAYOUT.XXS)}
>
<Typography
fontSize={typographyLevels.s}
fontWeight={600}
letterSpacing="0.08em"
color={theme.palette.text.disabled}
textTransform="uppercase"
mb={theme.spacing(LAYOUT.XXS)}
>
{t(category.labelKey)}
</Typography>
{category.cards.map((id) => (
<FormControlLabel
key={id}
label={
<Typography fontSize={typographyLevels.m}>
{t(`pages.dashboard.cards.${id}`)}
</Typography>
}
control={
<Checkbox
size="small"
checked={visibility[id] ?? false}
onChange={(e) => handleToggleCard(id, e.target.checked)}
/>
}
sx={{ m: 0, gap: theme.spacing(LAYOUT.SM) }}
/>
))}
</Stack>
);
return (
<Dialog
open={open}
onClose={onClose}
maxWidth="sm"
fullWidth
>
<DialogTitle
pb={theme.spacing(LAYOUT.XS)}
bgcolor={theme.palette.action.hover}
borderBottom={`1px solid ${theme.palette.divider}`}
>
<Typography
component="span"
fontWeight={600}
fontSize={typographyLevels.l}
display="block"
>
{t("pages.dashboard.modal.title")}
</Typography>
</DialogTitle>
<DialogContent sx={{ pt: theme.spacing(LAYOUT.MD) }}>
<Box
display="grid"
gridTemplateColumns="1fr 1fr"
gap={theme.spacing(LAYOUT.XL)}
pt={theme.spacing(LAYOUT.XS)}
>
<Stack gap={theme.spacing(LAYOUT.LG)}>
{leftCategories.map(renderCategory)}
</Stack>
<Stack gap={theme.spacing(LAYOUT.LG)}>
{rightCategories.map(renderCategory)}
</Stack>
</Box>
</DialogContent>
<DialogActions
sx={{
p: theme.spacing(LAYOUT.MD),
pt: theme.spacing(LAYOUT.SM),
bgcolor: theme.palette.action.hover,
borderTop: `1px solid ${theme.palette.divider}`,
justifyContent: "space-between",
}}
>
<Button
variant="outlined"
onClick={handleReset}
>
{t("pages.dashboard.modal.resetToDefaults")}
</Button>
<Stack
direction="row"
alignItems="center"
gap={theme.spacing(LAYOUT.SM)}
>
<FormControlLabel
label={
<Typography fontSize={typographyLevels.m}>
{t("pages.dashboard.modal.selectAll")}
</Typography>
}
control={
<Checkbox
size="small"
checked={allVisible}
indeterminate={selectAllIndeterminate}
onChange={handleSelectAll}
/>
}
sx={{ m: 0, gap: theme.spacing(LAYOUT.SM) }}
/>
<Button
variant="contained"
onClick={onClose}
>
{t("pages.dashboard.modal.done")}
</Button>
</Stack>
</DialogActions>
</Dialog>
);
};
@@ -1,31 +0,0 @@
import Box from "@mui/material/Box";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import type { DraggableSyntheticListeners } from "@dnd-kit/core";
interface SortableCardProps {
id: string;
isDragging?: boolean;
children: (listeners: DraggableSyntheticListeners) => React.ReactNode;
}
export const SortableCard = ({ id, isDragging, children }: SortableCardProps) => {
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
id,
});
return (
<Box
ref={setNodeRef}
{...attributes}
height="100%"
style={{
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragging ? 0 : 1,
}}
>
{children(listeners)}
</Box>
);
};
@@ -1,55 +0,0 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { useTheme } from "@mui/material/styles";
import { DashboardCard } from "@/Pages/Dashboard/components/DashboardCard";
import { typographyLevels } from "@/Utils/Theme/Palette";
import { useTranslation } from "react-i18next";
import type { DashboardMonitorRow } from "@/Pages/Dashboard/types";
import type { DraggableSyntheticListeners } from "@dnd-kit/core";
interface CurrentlyDownCardProps {
downMonitors: DashboardMonitorRow[];
isLoading?: boolean;
error?: unknown;
dragHandleProps?: DraggableSyntheticListeners;
}
export const CurrentlyDownCard = ({
downMonitors,
isLoading,
error,
dragHandleProps,
}: CurrentlyDownCardProps) => {
const theme = useTheme();
const { t } = useTranslation();
return (
<DashboardCard
title={t("pages.dashboard.cards.currentlyDown")}
isLoading={isLoading}
error={error}
dragHandleProps={dragHandleProps}
>
{downMonitors.length === 0 ? (
<Typography
fontSize={typographyLevels.m}
color={theme.palette.text.secondary}
>
{t("pages.dashboard.emptyStates.allMonitorsUp")}
</Typography>
) : (
<Stack gap={theme.spacing(1)}>
{downMonitors.map((m) => (
<Typography
key={m.id}
fontSize={typographyLevels.m}
color={theme.palette.error.main}
>
{m.name}
</Typography>
))}
</Stack>
)}
</DashboardCard>
);
};
@@ -1,61 +0,0 @@
import Stack from "@mui/material/Stack";
import { DashboardCard } from "@/Pages/Dashboard/components/DashboardCard";
import { BarRow } from "@/Pages/Dashboard/components/cards/BarRow";
import { useTranslation } from "react-i18next";
import { formatPercentageFromWhole } from "@/Utils/FormatUtils";
import type { DashboardMonitorRow } from "@/Pages/Dashboard/types";
import type { DraggableSyntheticListeners } from "@dnd-kit/core";
import { LAYOUT } from "@/Utils/Theme/constants";
const TOP_N = 5;
interface LowestUptimeCardProps {
lowestUptimeMonitors: DashboardMonitorRow[];
isLoading?: boolean;
error?: unknown;
dragHandleProps?: DraggableSyntheticListeners;
}
export const LowestUptimeCard = ({
lowestUptimeMonitors,
isLoading,
error,
dragHandleProps,
}: LowestUptimeCardProps) => {
const { t } = useTranslation();
const top = lowestUptimeMonitors.slice(0, TOP_N);
const total = lowestUptimeMonitors.length;
return (
<DashboardCard
title={t("pages.dashboard.cards.lowestUptime")}
dragHandleProps={dragHandleProps}
topRight={
total > 0
? t("pages.dashboard.emptyStates.topOf", {
count: Math.min(TOP_N, total),
total,
})
: undefined
}
isLoading={isLoading}
error={error}
>
<Stack gap={LAYOUT.SM}>
{top.map((monitor) => {
const uptime = monitor.uptimePercentage;
const downtime = 100 - uptime;
return (
<BarRow
key={monitor.id}
label={monitor.name}
value={formatPercentageFromWhole(uptime)}
fillRatio={downtime / 100}
/>
);
})}
</Stack>
</DashboardCard>
);
};
@@ -7,20 +7,16 @@ import { typographyLevels } from "@/Utils/Theme/Palette";
import { SPACING } from "@/Utils/Theme/constants";
import { useTranslation } from "react-i18next";
import type { MonitorsSummary } from "@/Types/Monitor";
import type { DraggableSyntheticListeners } from "@dnd-kit/core";
interface MonitorStatusCardProps {
summary: MonitorsSummary;
isLoading?: boolean;
error?: unknown;
dragHandleProps?: DraggableSyntheticListeners;
}
export const MonitorStatusCard = ({
summary,
isLoading,
error,
dragHandleProps,
}: MonitorStatusCardProps) => {
const theme = useTheme();
const { t } = useTranslation();
@@ -34,7 +30,6 @@ export const MonitorStatusCard = ({
title={t("pages.dashboard.cards.monitorStatus")}
isLoading={isLoading}
error={error}
dragHandleProps={dragHandleProps}
>
<Stack gap={theme.spacing(SPACING.SM)}>
<Stack
@@ -1,59 +0,0 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { useTheme } from "@mui/material/styles";
import { DashboardCard } from "@/Pages/Dashboard/components/DashboardCard";
import { BarRow } from "@/Pages/Dashboard/components/cards/BarRow";
import { typographyLevels } from "@/Utils/Theme/Palette";
import { useTranslation } from "react-i18next";
import type { DashboardGroupCount } from "@/Pages/Dashboard/types";
import type { DraggableSyntheticListeners } from "@dnd-kit/core";
import { LAYOUT } from "@/Utils/Theme/constants";
interface MonitorsByGroupCardProps {
monitorsByGroup: DashboardGroupCount[];
isLoading?: boolean;
error?: unknown;
dragHandleProps?: DraggableSyntheticListeners;
}
export const MonitorsByGroupCard = ({
monitorsByGroup,
isLoading,
error,
dragHandleProps,
}: MonitorsByGroupCardProps) => {
const theme = useTheme();
const { t } = useTranslation();
const maxCount = monitorsByGroup[0]?.count ?? 1;
return (
<DashboardCard
title={t("pages.dashboard.cards.monitorsByGroup")}
isLoading={isLoading}
error={error}
dragHandleProps={dragHandleProps}
>
{monitorsByGroup.length === 0 ? (
<Typography
fontSize={typographyLevels.m}
color={theme.palette.text.secondary}
>
{t("pages.dashboard.emptyStates.noGroups")}
</Typography>
) : (
<Stack gap={LAYOUT.SM}>
{monitorsByGroup.map(({ group, count }) => (
<BarRow
key={group}
label={group}
value={String(count)}
fillRatio={count / maxCount}
valueColor={theme.palette.text.primary}
/>
))}
</Stack>
)}
</DashboardCard>
);
};
@@ -4,21 +4,18 @@ import { BarRow } from "@/Pages/Dashboard/components/cards/BarRow";
import { useTranslation } from "react-i18next";
import { useTheme } from "@mui/material/styles";
import type { DashboardTypeCount } from "@/Pages/Dashboard/types";
import type { DraggableSyntheticListeners } from "@dnd-kit/core";
import { LAYOUT } from "@/Utils/Theme/constants";
interface MonitorsByTypeCardProps {
monitorsByType: DashboardTypeCount[];
isLoading?: boolean;
error?: unknown;
dragHandleProps?: DraggableSyntheticListeners;
}
export const MonitorsByTypeCard = ({
monitorsByType,
isLoading,
error,
dragHandleProps,
}: MonitorsByTypeCardProps) => {
const { t } = useTranslation();
const theme = useTheme();
@@ -30,7 +27,6 @@ export const MonitorsByTypeCard = ({
title={t("pages.dashboard.cards.monitorsByType")}
isLoading={isLoading}
error={error}
dragHandleProps={dragHandleProps}
>
<Stack gap={LAYOUT.SM}>
{monitorsByType.map(({ type, count }) => (
@@ -1,58 +0,0 @@
import Stack from "@mui/material/Stack";
import { DashboardCard } from "@/Pages/Dashboard/components/DashboardCard";
import { BarRow } from "@/Pages/Dashboard/components/cards/BarRow";
import { useTranslation } from "react-i18next";
import { formatMs } from "@/Utils/TimeUtils";
import type { DashboardMonitorRow } from "@/Pages/Dashboard/types";
import type { DraggableSyntheticListeners } from "@dnd-kit/core";
import { LAYOUT } from "@/Utils/Theme/constants";
const TOP_N = 5;
interface SlowestMonitorsCardProps {
slowestMonitors: DashboardMonitorRow[];
isLoading?: boolean;
error?: unknown;
dragHandleProps?: DraggableSyntheticListeners;
}
export const SlowestMonitorsCard = ({
slowestMonitors,
isLoading,
error,
dragHandleProps,
}: SlowestMonitorsCardProps) => {
const { t } = useTranslation();
const top = slowestMonitors.slice(0, TOP_N);
const maxAvg = top[0]?.avgResponseTime ?? 1;
const total = slowestMonitors.length;
return (
<DashboardCard
title={t("pages.dashboard.cards.slowestMonitors")}
dragHandleProps={dragHandleProps}
topRight={
total > 0
? t("pages.dashboard.emptyStates.topOf", {
count: Math.min(TOP_N, total),
total,
})
: undefined
}
isLoading={isLoading}
error={error}
>
<Stack gap={LAYOUT.SM}>
{top.map((m) => (
<BarRow
key={m.id}
label={m.name}
value={formatMs(m.avgResponseTime)}
fillRatio={m.avgResponseTime / maxAvg}
/>
))}
</Stack>
</DashboardCard>
);
};
@@ -1,41 +0,0 @@
import Typography from "@mui/material/Typography";
import { useTheme } from "@mui/material/styles";
import { DashboardCard } from "@/Pages/Dashboard/components/DashboardCard";
import { typographyLevels } from "@/Utils/Theme/Palette";
import { useTranslation } from "react-i18next";
import type { DashboardCardId } from "@/Pages/Dashboard/dashboardCards";
import type { DraggableSyntheticListeners } from "@dnd-kit/core";
interface StubCardProps {
cardId: DashboardCardId;
isLoading?: boolean;
error?: unknown;
dragHandleProps?: DraggableSyntheticListeners;
}
export const StubCard = ({
cardId,
isLoading,
error,
dragHandleProps,
}: StubCardProps) => {
const theme = useTheme();
const { t } = useTranslation();
return (
<DashboardCard
title={t(`pages.dashboard.cards.${cardId}`)}
isLoading={isLoading}
error={error}
dragHandleProps={dragHandleProps}
>
<Typography
fontSize={typographyLevels.m}
color={theme.palette.text.disabled}
fontStyle="italic"
>
{t("pages.dashboard.comingSoon")}
</Typography>
</DashboardCard>
);
};
+16 -210
View File
@@ -1,232 +1,38 @@
import { useState } from "react";
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import { useTheme } from "@mui/material/styles";
import useMediaQuery from "@mui/material/useMediaQuery";
import { LayoutGrid } from "lucide-react";
import { useDispatch, useSelector } from "react-redux";
import { useTranslation } from "react-i18next";
import {
DndContext,
DragOverlay,
PointerSensor,
useSensor,
useSensors,
closestCenter,
} from "@dnd-kit/core";
import type {
DragStartEvent,
DragEndEvent,
DraggableSyntheticListeners,
} from "@dnd-kit/core";
import { SortableContext, arrayMove } from "@dnd-kit/sortable";
import type { SortingStrategy } from "@dnd-kit/sortable";
import { BasePage } from "@/Components/design-elements/BasePage";
import { Button } from "@/Components/inputs";
import { LAYOUT } from "@/Utils/Theme/constants";
import type { RootState } from "@/Types/state";
import { setDashboardCardOrder } from "@/Features/UI/uiSlice";
import {
DASHBOARD_CARD_IDS,
defaultVisibleCards,
type DashboardCardId,
} from "@/Pages/Dashboard/dashboardCards";
import { EditCardsModal } from "@/Pages/Dashboard/components/EditCardsModal";
import { useDashboardData } from "@/Pages/Dashboard/hooks/useDashboardData";
import { SortableCard } from "@/Pages/Dashboard/components/SortableCard";
import { MonitorStatusCard } from "@/Pages/Dashboard/components/cards/MonitorStatusCard";
import { CurrentlyDownCard } from "@/Pages/Dashboard/components/cards/CurrentlyDownCard";
import { SlowestMonitorsCard } from "@/Pages/Dashboard/components/cards/SlowestMonitorsCard";
import { LowestUptimeCard } from "@/Pages/Dashboard/components/cards/LowestUptimeCard";
import { MonitorsByTypeCard } from "@/Pages/Dashboard/components/cards/MonitorsByTypeCard";
import { MonitorsByGroupCard } from "@/Pages/Dashboard/components/cards/MonitorsByGroupCard";
import { StubCard } from "@/Pages/Dashboard/components/cards/StubCard";
// No displacement transforms during drag — only the DragOverlay moves
const noAnimationStrategy: SortingStrategy = () => null;
const Dashboard = () => {
const theme = useTheme();
const { t } = useTranslation();
const dispatch = useDispatch();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
const [modalOpen, setModalOpen] = useState(false);
const [activeId, setActiveId] = useState<DashboardCardId | null>(null);
const visibility = useSelector(
(state: RootState) => state.ui.dashboardCards ?? defaultVisibleCards
);
const cardOrder = useSelector(
(state: RootState) => state.ui.dashboardCardOrder ?? [...DASHBOARD_CARD_IDS]
);
const {
isLoading,
error,
summary,
downMonitors,
slowestMonitors,
lowestUptimeMonitors,
monitorsByType,
monitorsByGroup,
} = useDashboardData();
const visibleCards = cardOrder.filter((id) => visibility[id]);
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: { distance: 8 },
})
);
const handleDragStart = (event: DragStartEvent) => {
setActiveId(event.active.id as DashboardCardId);
};
const handleDragEnd = (event: DragEndEvent) => {
setActiveId(null);
const { active, over } = event;
if (!over || active.id === over.id) return;
const oldIndex = visibleCards.indexOf(active.id as DashboardCardId);
const newIndex = visibleCards.indexOf(over.id as DashboardCardId);
if (oldIndex === -1 || newIndex === -1) return;
const newVisible = arrayMove(visibleCards, oldIndex, newIndex);
let vi = 0;
const newFullOrder = cardOrder.map((id) => (visibility[id] ? newVisible[vi++] : id));
dispatch(setDashboardCardOrder(newFullOrder));
};
const renderCardContent = (
id: DashboardCardId,
dragHandleProps?: DraggableSyntheticListeners
) => {
switch (id) {
case "monitorStatus":
return (
<MonitorStatusCard
summary={summary}
isLoading={isLoading}
error={error}
dragHandleProps={dragHandleProps}
/>
);
case "currentlyDown":
return (
<CurrentlyDownCard
downMonitors={downMonitors}
isLoading={isLoading}
error={error}
dragHandleProps={dragHandleProps}
/>
);
case "slowestMonitors":
return (
<SlowestMonitorsCard
slowestMonitors={slowestMonitors}
isLoading={isLoading}
error={error}
dragHandleProps={dragHandleProps}
/>
);
case "lowestUptime":
return (
<LowestUptimeCard
lowestUptimeMonitors={lowestUptimeMonitors}
isLoading={isLoading}
error={error}
dragHandleProps={dragHandleProps}
/>
);
case "monitorsByType":
return (
<MonitorsByTypeCard
monitorsByType={monitorsByType}
isLoading={isLoading}
error={error}
dragHandleProps={dragHandleProps}
/>
);
case "monitorsByGroup":
return (
<MonitorsByGroupCard
monitorsByGroup={monitorsByGroup}
isLoading={isLoading}
error={error}
dragHandleProps={dragHandleProps}
/>
);
default:
return (
<StubCard
cardId={id}
isLoading={isLoading}
error={error}
dragHandleProps={dragHandleProps}
/>
);
}
};
const { isLoading, error, summary, monitorsByType } = useDashboardData();
return (
<BasePage headerKey="dashboard">
<Stack
direction="row"
justifyContent="flex-end"
<Box
display="grid"
gridTemplateColumns={isSmall ? "1fr" : "1fr 1fr"}
gap={theme.spacing(LAYOUT.MD)}
>
<Button
variant="outlined"
startIcon={<LayoutGrid size={16} />}
onClick={() => setModalOpen(true)}
>
{t("pages.dashboard.editCards")}
</Button>
</Stack>
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
<SortableContext
items={visibleCards}
strategy={noAnimationStrategy}
>
<Box
display="grid"
gridTemplateColumns={isSmall ? "1fr" : "1fr 1fr"}
gap={theme.spacing(LAYOUT.MD)}
>
{visibleCards.map((id) => (
<SortableCard
key={id}
id={id}
isDragging={activeId === id}
>
{(listeners) => renderCardContent(id, listeners)}
</SortableCard>
))}
</Box>
</SortableContext>
<DragOverlay>
{activeId ? (
<Box sx={{ opacity: 0.85, pointerEvents: "none" }}>
{renderCardContent(activeId)}
</Box>
) : null}
</DragOverlay>
</DndContext>
<EditCardsModal
open={modalOpen}
onClose={() => setModalOpen(false)}
/>
<MonitorStatusCard
summary={summary}
isLoading={isLoading}
error={error}
/>
<MonitorsByTypeCard
monitorsByType={monitorsByType}
isLoading={isLoading}
error={error}
/>
</Box>
</BasePage>
);
};