diff --git a/client/src/Pages/Dashboard/components/DashboardCard.tsx b/client/src/Pages/Dashboard/components/DashboardCard.tsx index 49f7f4395..f638e110b 100644 --- a/client/src/Pages/Dashboard/components/DashboardCard.tsx +++ b/client/src/Pages/Dashboard/components/DashboardCard.tsx @@ -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} - - {topRight && ( - - {topRight} - - )} - - - - + {topRight} + + )} {renderContent()} diff --git a/client/src/Pages/Dashboard/components/EditCardsModal.tsx b/client/src/Pages/Dashboard/components/EditCardsModal.tsx deleted file mode 100644 index a2487bf6d..000000000 --- a/client/src/Pages/Dashboard/components/EditCardsModal.tsx +++ /dev/null @@ -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]) => ( - - - {t(category.labelKey)} - - {category.cards.map((id) => ( - - {t(`pages.dashboard.cards.${id}`)} - - } - control={ - handleToggleCard(id, e.target.checked)} - /> - } - sx={{ m: 0, gap: theme.spacing(LAYOUT.SM) }} - /> - ))} - - ); - - return ( - - - - {t("pages.dashboard.modal.title")} - - - - - - - {leftCategories.map(renderCategory)} - - - {rightCategories.map(renderCategory)} - - - - - - - - - - {t("pages.dashboard.modal.selectAll")} - - } - control={ - - } - sx={{ m: 0, gap: theme.spacing(LAYOUT.SM) }} - /> - - - - - ); -}; diff --git a/client/src/Pages/Dashboard/components/SortableCard.tsx b/client/src/Pages/Dashboard/components/SortableCard.tsx deleted file mode 100644 index 5e8848117..000000000 --- a/client/src/Pages/Dashboard/components/SortableCard.tsx +++ /dev/null @@ -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 ( - - {children(listeners)} - - ); -}; diff --git a/client/src/Pages/Dashboard/components/cards/CurrentlyDownCard.tsx b/client/src/Pages/Dashboard/components/cards/CurrentlyDownCard.tsx deleted file mode 100644 index 4f7b9a672..000000000 --- a/client/src/Pages/Dashboard/components/cards/CurrentlyDownCard.tsx +++ /dev/null @@ -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 ( - - {downMonitors.length === 0 ? ( - - {t("pages.dashboard.emptyStates.allMonitorsUp")} - - ) : ( - - {downMonitors.map((m) => ( - - {m.name} - - ))} - - )} - - ); -}; diff --git a/client/src/Pages/Dashboard/components/cards/LowestUptimeCard.tsx b/client/src/Pages/Dashboard/components/cards/LowestUptimeCard.tsx deleted file mode 100644 index fb3658dee..000000000 --- a/client/src/Pages/Dashboard/components/cards/LowestUptimeCard.tsx +++ /dev/null @@ -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 ( - 0 - ? t("pages.dashboard.emptyStates.topOf", { - count: Math.min(TOP_N, total), - total, - }) - : undefined - } - isLoading={isLoading} - error={error} - > - - {top.map((monitor) => { - const uptime = monitor.uptimePercentage; - const downtime = 100 - uptime; - return ( - - ); - })} - - - ); -}; diff --git a/client/src/Pages/Dashboard/components/cards/MonitorStatusCard.tsx b/client/src/Pages/Dashboard/components/cards/MonitorStatusCard.tsx index 451f7d948..16eb9bceb 100644 --- a/client/src/Pages/Dashboard/components/cards/MonitorStatusCard.tsx +++ b/client/src/Pages/Dashboard/components/cards/MonitorStatusCard.tsx @@ -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} > { - const theme = useTheme(); - const { t } = useTranslation(); - - const maxCount = monitorsByGroup[0]?.count ?? 1; - - return ( - - {monitorsByGroup.length === 0 ? ( - - {t("pages.dashboard.emptyStates.noGroups")} - - ) : ( - - {monitorsByGroup.map(({ group, count }) => ( - - ))} - - )} - - ); -}; diff --git a/client/src/Pages/Dashboard/components/cards/MonitorsByTypeCard.tsx b/client/src/Pages/Dashboard/components/cards/MonitorsByTypeCard.tsx index 70430f96e..46709aa50 100644 --- a/client/src/Pages/Dashboard/components/cards/MonitorsByTypeCard.tsx +++ b/client/src/Pages/Dashboard/components/cards/MonitorsByTypeCard.tsx @@ -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} > {monitorsByType.map(({ type, count }) => ( diff --git a/client/src/Pages/Dashboard/components/cards/SlowestMonitorsCard.tsx b/client/src/Pages/Dashboard/components/cards/SlowestMonitorsCard.tsx deleted file mode 100644 index a149189be..000000000 --- a/client/src/Pages/Dashboard/components/cards/SlowestMonitorsCard.tsx +++ /dev/null @@ -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 ( - 0 - ? t("pages.dashboard.emptyStates.topOf", { - count: Math.min(TOP_N, total), - total, - }) - : undefined - } - isLoading={isLoading} - error={error} - > - - {top.map((m) => ( - - ))} - - - ); -}; diff --git a/client/src/Pages/Dashboard/components/cards/StubCard.tsx b/client/src/Pages/Dashboard/components/cards/StubCard.tsx deleted file mode 100644 index c853bb853..000000000 --- a/client/src/Pages/Dashboard/components/cards/StubCard.tsx +++ /dev/null @@ -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 ( - - - {t("pages.dashboard.comingSoon")} - - - ); -}; diff --git a/client/src/Pages/Dashboard/index.tsx b/client/src/Pages/Dashboard/index.tsx index c68ca9fae..4cb9045ab 100644 --- a/client/src/Pages/Dashboard/index.tsx +++ b/client/src/Pages/Dashboard/index.tsx @@ -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(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 ( - - ); - case "currentlyDown": - return ( - - ); - case "slowestMonitors": - return ( - - ); - case "lowestUptime": - return ( - - ); - case "monitorsByType": - return ( - - ); - case "monitorsByGroup": - return ( - - ); - default: - return ( - - ); - } - }; + const { isLoading, error, summary, monitorsByType } = useDashboardData(); return ( - - - - - - - - {visibleCards.map((id) => ( - - {(listeners) => renderCardContent(id, listeners)} - - ))} - - - - - {activeId ? ( - - {renderCardContent(activeId)} - - ) : null} - - - - setModalOpen(false)} - /> + + + ); };