feat: simplify code and functionalities
This commit is contained in:
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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user