From 88a91022c34e5950a9dc418a46f2bad3e48fe365 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 2 Feb 2026 17:28:22 +0000 Subject: [PATCH] summary, activ eincident table --- .../Incidents/Components/CardSummary.tsx | 104 ++++++++++++++---- .../Incidents/Components/IncidentTable.tsx | 10 ++ client/src/Pages/Incidents/index.tsx | 52 ++++++++- client/src/Pages/Incidents/utils.ts | 7 +- 4 files changed, 143 insertions(+), 30 deletions(-) diff --git a/client/src/Pages/Incidents/Components/CardSummary.tsx b/client/src/Pages/Incidents/Components/CardSummary.tsx index f5ba2619b..12d1f87cb 100644 --- a/client/src/Pages/Incidents/Components/CardSummary.tsx +++ b/client/src/Pages/Incidents/Components/CardSummary.tsx @@ -1,22 +1,23 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; import Divider from "@mui/material/Divider"; -import { BaseBox } from "@/Components/v2/design-elements"; -import { CircleCheck, TriangleAlert, Bell, Wrench } from "lucide-react"; +import Grid from "@mui/material/Grid"; +import { BaseBox, ValueLabel } from "@/Components/v2/design-elements"; +import { CircleCheck, TriangleAlert, Bell, Wrench, Globe } from "lucide-react"; import Box from "@mui/material/Box"; -import { IncidentItem, SummaryItem } from "@/Pages/Incidents/Components/IncidentItem"; +import { SummaryItem } from "@/Pages/Incidents/Components/IncidentItem"; import { useTranslation } from "react-i18next"; import { useTheme } from "@mui/material"; -import type { Incident, IncidentSummary } from "@/Types/Incident"; -import type { Monitor } from "@/Types/Monitor"; +import type { IncidentSummary, IncidentSummaryItem } from "@/Types/Incident"; +import { getIncidentsDuration } from "@/Pages/Incidents/utils"; interface SummaryCardProps { title: string; sx?: React.CSSProperties; } -const SummaryCard = ({ +export const SummaryCard = ({ title, sx, children, @@ -49,18 +50,17 @@ const SummaryCard = ({ ); }; -interface SummaryCardIncidentsProps { - incidents?: Incident[]; - monitors?: Monitor[] | null; +interface SummaryCardActiveIncidentsProps { + summary?: IncidentSummary | null; } -export const SummaryCardActiveIncidents = ({ incidents }: SummaryCardIncidentsProps) => { +export const SummaryCardActiveIncidents = ({ summary }: SummaryCardActiveIncidentsProps) => { const { t } = useTranslation(); const theme = useTheme(); - if (!incidents) return null; + if (!summary) return null; - const activeCount = incidents.filter((incident) => incident.status === true).length; + const activeCount = summary.totalActive; const hasActive = activeCount > 0; const color = hasActive ? theme.palette.error.main : theme.palette.success.main; const icon = hasActive ? ( @@ -90,22 +90,82 @@ export const SummaryCardActiveIncidents = ({ incidents }: SummaryCardIncidentsPr ); }; -export const SummaryCardLatestIncidents = ({ - incidents, - monitors, -}: SummaryCardIncidentsProps) => { +const SummaryIncidentItem = ({ incident }: { incident: IncidentSummaryItem }) => { + const theme = useTheme(); + const duration = getIncidentsDuration(incident); + return ( + + + + + {incident.monitorName ?? "N/A"} + + + + + + + + + {duration} + + + ); +}; + +interface SummaryCardLatestIncidentsProps { + summary?: IncidentSummary | null; +} + +export const SummaryCardLatestIncidents = ({ summary }: SummaryCardLatestIncidentsProps) => { const { t } = useTranslation(); const theme = useTheme(); + + const latestIncidents = summary?.latestIncidents ?? []; + return ( - {incidents?.slice(0, 3).map((incident, index) => ( + {latestIncidents.slice(0, 3).map((incident, index) => ( - m.id === incident.monitorId)} - /> - {index < incidents.length - 1 && } + + {index < latestIncidents.length - 1 && ( + + )} ))} diff --git a/client/src/Pages/Incidents/Components/IncidentTable.tsx b/client/src/Pages/Incidents/Components/IncidentTable.tsx index 957f30dfc..df82764d9 100644 --- a/client/src/Pages/Incidents/Components/IncidentTable.tsx +++ b/client/src/Pages/Incidents/Components/IncidentTable.tsx @@ -18,6 +18,7 @@ import { useTheme } from "@mui/material/styles"; import Box from "@mui/material/Box"; interface IncidentsTableProps { + title?: string; incidents?: Incident[]; monitors?: Monitor[]; incidentsCount: number; @@ -30,6 +31,7 @@ interface IncidentsTableProps { } export const IncidentsTable = ({ + title, incidents, monitors, incidentsCount, @@ -200,6 +202,14 @@ export const IncidentsTable = ({ return ( + {title && ( + + {title} + + )} { + const { t } = useTranslation(); const theme = useTheme(); const { monitorId } = useParams(); @@ -27,6 +29,8 @@ const IncidentsPage = () => { const [dateRange, setDateRange] = useState("recent"); const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(10); + const [activeIncidentsPage, setActiveIncidentsPage] = useState(0); + const [activeIncidentsRowsPerPage, setActiveIncidentsRowsPerPage] = useState(5); // Resolve dialog state const [isResolveDialogOpen, setIsResolveDialogOpen] = useState(false); @@ -73,6 +77,27 @@ const IncidentsPage = () => { error: monitorsError, } = useGet("/monitors/team"); + // Build active incidents URL (always fetch all active, no date filter) + const activeIncidentsUrl = useMemo(() => { + const params = new URLSearchParams(); + if (selectedMonitor !== "0") params.append("monitorId", selectedMonitor); + params.append("status", "true"); + params.append("sortOrder", "desc"); + params.append("page", String(activeIncidentsPage)); + params.append("rowsPerPage", String(activeIncidentsRowsPerPage)); + return `/incidents/team?${params.toString()}`; + }, [selectedMonitor, activeIncidentsPage, activeIncidentsRowsPerPage]); + + // Fetch active incidents + const { + data: activeIncidentsData, + refetch: refetchActiveIncidents, + } = useGet( + activeIncidentsUrl, + {}, + { keepPreviousData: true, refreshInterval: 10000 } + ); + // Fetch incident summary const { data: summaryData, @@ -89,6 +114,8 @@ const IncidentsPage = () => { // Derived state const incidents = incidentsData?.incidents ?? []; const incidentsCount = incidentsData?.count ?? 0; + const activeIncidents = activeIncidentsData?.incidents ?? []; + const activeIncidentsCount = activeIncidentsData?.count ?? 0; const networkError = !!incidentsError || !!monitorsError || !!summaryError; // Expose state and handlers for future UI use @@ -102,7 +129,7 @@ const IncidentsPage = () => { void networkError; const handleOpenDetails = (incidentId: string) => { - const incident = incidents.find((i) => i.id === incidentId) ?? null; + const incident = incidents.find((i) => i.id === incidentId) ?? activeIncidents.find((i) => i.id === incidentId) ?? null; const monitor = monitorsData?.find((m) => m.id === incident?.monitorId) ?? null; setIsDetailsDialogOpen(true); setSelectedIncident(incident); @@ -132,6 +159,7 @@ const IncidentsPage = () => { const handleResolved = () => { refetchIncidents(); + refetchActiveIncidents(); refetchSummary(); }; @@ -141,11 +169,8 @@ const IncidentsPage = () => { direction={{ xs: "column", md: "row" }} gap={theme.spacing(8)} > - - + + { setDateRange={setDateRange} isLoading={isLoadingIncidents} /> + {activeIncidentsCount > 0 && ( + + )} { + +type IncidentLike = Pick; + +export const getIncidentsDuration = (incident: IncidentLike | IncidentSummaryItem) => { if (!incident?.startTime) { return "-"; }