diff --git a/client/src/Pages/Dashboard/components/EditCardsModal.tsx b/client/src/Pages/Dashboard/components/EditCardsModal.tsx index 84257c1ec..faf13c407 100644 --- a/client/src/Pages/Dashboard/components/EditCardsModal.tsx +++ b/client/src/Pages/Dashboard/components/EditCardsModal.tsx @@ -1,8 +1,11 @@ -import { useState } from "react"; +import { useMemo, useState } from "react"; import Stack from "@mui/material/Stack"; import FormControlLabel from "@mui/material/FormControlLabel"; +import Typography from "@mui/material/Typography"; +import { useTheme } from "@mui/material/styles"; import { useTranslation } from "react-i18next"; import { Checkbox, Dialog } from "@/Components/inputs"; +import { LAYOUT } from "@/Utils/Theme/constants"; import { type DashboardCardKey, dashboardCardKeys } from "@/Types/Dashboard"; interface EditCardsModalProps { @@ -18,16 +21,19 @@ export const EditCardsModal = ({ onClose, onSave, }: EditCardsModalProps) => { + const theme = useTheme(); const { t } = useTranslation(); - const [draft, setDraft] = useState>(new Set(visibleCards)); + const [cardsSelection, setCardsSelection] = useState>( + new Set(visibleCards) + ); const handleClose = () => { - setDraft(new Set(visibleCards)); + setCardsSelection(new Set(visibleCards)); onClose(); }; const handleToggle = (key: DashboardCardKey) => { - setDraft((prev) => { + setCardsSelection((prev) => { const next = new Set(prev); if (next.has(key)) { next.delete(key); @@ -38,26 +44,40 @@ export const EditCardsModal = ({ }); }; + const sortedCardKeys = useMemo( + () => + [...dashboardCardKeys].sort((a, b) => + t(`pages.dashboard.cards.${a}`).localeCompare(t(`pages.dashboard.cards.${b}`)) + ), + [t] + ); + return ( onSave(draft)} + onConfirm={() => onSave(cardsSelection)} confirmText={t("pages.dashboard.editCardsModal.done")} maxWidth="xs" fullWidth > - - {dashboardCardKeys.map((key: DashboardCardKey) => ( + + + {t("pages.dashboard.editCardsModal.groups.uptime")} + + {sortedCardKeys.map((key: DashboardCardKey) => ( handleToggle(key)} /> } diff --git a/client/src/Pages/Dashboard/components/cards/CurrentlyDownCard.tsx b/client/src/Pages/Dashboard/components/cards/CurrentlyDownCard.tsx new file mode 100644 index 000000000..478b2efa3 --- /dev/null +++ b/client/src/Pages/Dashboard/components/cards/CurrentlyDownCard.tsx @@ -0,0 +1,59 @@ +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import { useTheme } from "@mui/material/styles"; +import { useTranslation } from "react-i18next"; +import { DashboardCard } from "@/Pages/Dashboard/components/DashboardCard"; +import { Dot } from "@/Components/design-elements"; +import { getStatusColor } from "@/Utils/MonitorUtils"; +import { typographyLevels } from "@/Utils/Theme/Palette"; +import { LAYOUT, SPACING } from "@/Utils/Theme/constants"; +import type { Monitor } from "@/Types/Monitor"; + +interface CurrentlyDownCardProps { + monitors: Monitor[]; +} + +export const CurrentlyDownCard = ({ monitors }: CurrentlyDownCardProps) => { + const theme = useTheme(); + const { t } = useTranslation(); + + const downMonitors = monitors.filter((monitor) => monitor.status === "down"); + + return ( + 0 ? downMonitors.length : undefined} + > + {downMonitors.length === 0 ? ( + + {t("pages.dashboard.currentlyDown.empty")} + + ) : ( + + {downMonitors.map((monitor) => ( + + + + {monitor.name} + + + ))} + + )} + + ); +}; diff --git a/client/src/Pages/Dashboard/components/cards/LowestUptimeCard.tsx b/client/src/Pages/Dashboard/components/cards/LowestUptimeCard.tsx new file mode 100644 index 000000000..f8840081e --- /dev/null +++ b/client/src/Pages/Dashboard/components/cards/LowestUptimeCard.tsx @@ -0,0 +1,60 @@ +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import { useTheme } from "@mui/material/styles"; +import { useTranslation } from "react-i18next"; +import { DashboardCard } from "@/Pages/Dashboard/components/DashboardCard"; +import { BarRow } from "@/Pages/Dashboard/components/cards/BarRow"; +import { getUptimePercentageColor } from "@/Utils/MonitorUtils"; +import { formatPercentage } from "@/Utils/FormatUtils"; +import { typographyLevels } from "@/Utils/Theme/Palette"; +import { SPACING } from "@/Utils/Theme/constants"; +import type { Monitor } from "@/Types/Monitor"; + +const TOP_N = 5; + +interface LowestUptimeCardProps { + monitors: Monitor[]; +} + +export const LowestUptimeCard = ({ monitors }: LowestUptimeCardProps) => { + const theme = useTheme(); + const { t } = useTranslation(); + + const ranked = monitors + .filter((monitor) => monitor.status !== "paused") + .filter( + (monitor): monitor is Monitor & { uptimePercentage: number } => + typeof monitor.uptimePercentage === "number" + ) + .sort((a, b) => a.uptimePercentage - b.uptimePercentage) + .slice(0, TOP_N); + + return ( + + {ranked.length === 0 ? ( + + {t("pages.dashboard.noData")} + + ) : ( + + {ranked.map((monitor) => { + const paletteKey = getUptimePercentageColor(monitor.uptimePercentage); + const color = theme.palette[paletteKey].main; + return ( + + ); + })} + + )} + + ); +}; diff --git a/client/src/Pages/Dashboard/index.tsx b/client/src/Pages/Dashboard/index.tsx index f86b224f1..1656b9709 100644 --- a/client/src/Pages/Dashboard/index.tsx +++ b/client/src/Pages/Dashboard/index.tsx @@ -14,6 +14,8 @@ import { setDashboardVisibleCards } from "@/Features/UI/uiSlice"; import type { RootState, AppDispatch } from "@/store"; import { MonitorStatusCard } from "@/Pages/Dashboard/components/cards/MonitorStatusCard"; +import { CurrentlyDownCard } from "@/Pages/Dashboard/components/cards/CurrentlyDownCard"; +import { LowestUptimeCard } from "@/Pages/Dashboard/components/cards/LowestUptimeCard"; import { MonitorsByTypeCard } from "@/Pages/Dashboard/components/cards/MonitorsByTypeCard"; import { EditCardsModal } from "@/Pages/Dashboard/components/EditCardsModal"; import { type DashboardCardKey, dashboardCardKeys } from "@/Types/Dashboard"; @@ -48,6 +50,8 @@ const Dashboard = () => { const cardComponents: Record = { monitorStatus: , + currentlyDown: , + lowestUptime: , monitorsByType: , }; diff --git a/client/src/Types/Dashboard.ts b/client/src/Types/Dashboard.ts index a397ec807..6e418f5ed 100644 --- a/client/src/Types/Dashboard.ts +++ b/client/src/Types/Dashboard.ts @@ -1,2 +1,7 @@ -export const dashboardCardKeys = ["monitorStatus", "monitorsByType"] as const; +export const dashboardCardKeys = [ + "monitorStatus", + "currentlyDown", + "lowestUptime", + "monitorsByType", +] as const; export type DashboardCardKey = (typeof dashboardCardKeys)[number]; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 1b1337dd2..ba0e3e897 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -1924,16 +1924,24 @@ }, "cards": { "monitorStatus": "Monitor status", + "currentlyDown": "Currently down", + "lowestUptime": "Lowest uptime", "monitorsByType": "Monitors by type" }, + "currentlyDown": { + "empty": "No monitors are down at this moment." + }, "header": { "title": "Dashboard", "description": "See every monitor type in one place and spot what needs attention." }, "editCards": "Edit cards", "editCardsModal": { - "title": "Edit cards", + "title": "Dashboard cards", "description": "Select the cards to display on your dashboard.", + "groups": { + "uptime": "Uptime" + }, "done": "Done" } }