summary, activ eincident table
This commit is contained in:
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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 "-";
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user