summary, activ eincident table

This commit is contained in:
Alex Holliday committed 2026-02-02 17:28:22 +00:00
1 parent 5822a363a9
commit 88a91022c3
4 files changed
+143 -30

No files matched your search

@@ -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 (
<Grid
container
alignItems="center"
spacing={2}
sx={{
width: "100%",
py: theme.spacing(0.5),
}}
>
<Grid
size={{ xs: 12, lg: 5 }}
sx={{
display: "flex",
alignItems: "center",
justifyContent: "flex-start",
gap: theme.spacing(2),
}}
>
<Globe />
<Typography
variant="body1"
fontWeight={500}
noWrap
>
{incident.monitorName ?? "N/A"}
</Typography>
</Grid>
<Grid
size={{ xs: 12, md: 6, lg: 3 }}
sx={{
display: "flex",
}}
>
<ValueLabel
value={incident.status ? "negative" : "positive"}
text={incident.status ? "Active" : "Resolved"}
/>
</Grid>
<Grid
size={{ xs: 12, md: 6, lg: 4 }}
sx={{
textAlign: { xs: "left", md: "right" },
fontWeight: 500,
}}
>
<Typography variant="body1">{duration}</Typography>
</Grid>
</Grid>
);
};
interface SummaryCardLatestIncidentsProps {
summary?: IncidentSummary | null;
}
export const SummaryCardLatestIncidents = ({ summary }: SummaryCardLatestIncidentsProps) => {
const { t } = useTranslation();
const theme = useTheme();
const latestIncidents = summary?.latestIncidents ?? [];
return (
<SummaryCard title={t("pages.incidents.summaryCard.latestIncidents.title")}>
<Stack gap={theme.spacing(4)}>
{incidents?.slice(0, 3).map((incident, index) => (
{latestIncidents.slice(0, 3).map((incident, index) => (
<Box key={incident.id}>
<IncidentItem
incident={incident}
monitor={monitors?.find((m) => m.id === incident.monitorId)}
/>
{index < incidents.length - 1 && <Divider sx={{ mt: theme.spacing(2) }} />}
<SummaryIncidentItem incident={incident} />
{index < latestIncidents.length - 1 && (
<Divider sx={{ mt: theme.spacing(2) }} />
)}
</Box>
))}
</Stack>
@@ -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 (
<Box>
{title && (
<Typography
variant="h6"
sx={{ mb: theme.spacing(4), textTransform: "uppercase" }}
>
{title}
</Typography>
)}
<Table
headers={getHeaders()}
data={incidents}
+46 -6
View File
@@ -1,5 +1,6 @@
import { BasePage } from "@/Components/v2/design-elements";
import Stack from "@mui/material/Stack";
import { useTranslation } from "react-i18next";
import {
SummaryCardActiveIncidents,
SummaryCardLatestIncidents,
@@ -18,6 +19,7 @@ import type { Monitor } from "@/Types/Monitor";
import { useTheme } from "@mui/material";
const IncidentsPage = () => {
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<Monitor[]>("/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<IncidentsResponse>(
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)}
>
<SummaryCardActiveIncidents incidents={incidents} />
<SummaryCardLatestIncidents
incidents={incidents}
monitors={monitorsData}
/>
<SummaryCardActiveIncidents summary={summaryData} />
<SummaryCardLatestIncidents summary={summaryData} />
<SummaryCardStats summary={summaryData} />
</Stack>
<HeaderTimeRange
@@ -153,7 +178,22 @@ const IncidentsPage = () => {
setDateRange={setDateRange}
isLoading={isLoadingIncidents}
/>
{activeIncidentsCount > 0 && (
<IncidentsTable
title={t("pages.incidents.table.activeIncidents")}
incidents={activeIncidents}
monitors={monitorsData ?? undefined}
incidentsCount={activeIncidentsCount}
page={activeIncidentsPage}
setPage={setActiveIncidentsPage}
rowsPerPage={activeIncidentsRowsPerPage}
setRowsPerPage={setActiveIncidentsRowsPerPage}
onOpenDetails={handleOpenDetails}
onResolve={handleResolve}
/>
)}
<IncidentsTable
title={t("pages.incidents.table.allIncidents")}
incidents={incidents}
monitors={monitorsData ?? undefined}
incidentsCount={incidentsCount}
+5 -2
View File
@@ -1,6 +1,9 @@
import type { Incident } from "@/Types/Incident";
import type { Incident, IncidentSummaryItem } from "@/Types/Incident";
import { getHumanReadableDuration } from "@/Utils/timeUtilsLegacy.js";
export const getIncidentsDuration = (incident: Incident) => {
type IncidentLike = Pick<Incident, "startTime" | "endTime" | "status">;
export const getIncidentsDuration = (incident: IncidentLike | IncidentSummaryItem) => {
if (!incident?.startTime) {
return "-";
}