Merge pull request #3594 from bluewave-labs/feat/incidents-page-polish
feat(incidents): consistent typography, icons, and layout across the page
This commit is contained in:
4 files changed
+90
-91
No files matched your search
@@ -13,6 +13,7 @@ import { useSelector } from "react-redux";
|
||||
import type { RootState } from "@/Types/state";
|
||||
import { formatDateWithTz } from "@/Utils/TimeUtils";
|
||||
import { getIncidentsDuration } from "@/Pages/Incidents/utils";
|
||||
import { theme } from "@/Utils/Theme/Theme";
|
||||
|
||||
interface CardDetailsProps {
|
||||
incident: Incident | null;
|
||||
@@ -20,6 +21,24 @@ interface CardDetailsProps {
|
||||
sx?: object;
|
||||
}
|
||||
|
||||
const SectionHeading = ({ children }: { children: React.ReactNode }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="eyebrow"
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
{children}
|
||||
</Typography>
|
||||
);
|
||||
};
|
||||
|
||||
const Cell = ({ children }: { children: React.ReactNode }) => (
|
||||
<Typography variant="body1">{children}</Typography>
|
||||
);
|
||||
|
||||
export const CardDetails = ({ incident, monitor, sx }: CardDetailsProps) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
@@ -33,22 +52,18 @@ export const CardDetails = ({ incident, monitor, sx }: CardDetailsProps) => {
|
||||
gap={theme.spacing(LAYOUT.MD)}
|
||||
sx={sx}
|
||||
>
|
||||
<Typography textTransform={"uppercase"}>
|
||||
{t("pages.incidents.dialog.details.title")}
|
||||
</Typography>
|
||||
<SectionHeading>{t("pages.incidents.dialog.details.title")}</SectionHeading>
|
||||
<BaseBox padding={LAYOUT.MD}>
|
||||
<Stack gap={theme.spacing(LAYOUT.MD)}>
|
||||
<Typography textTransform={"uppercase"}>
|
||||
{t("pages.incidents.dialog.details.overview")}
|
||||
</Typography>
|
||||
<Divider />
|
||||
|
||||
<SectionHeading>{t("pages.incidents.dialog.details.overview")}</SectionHeading>
|
||||
<Grid
|
||||
container
|
||||
spacing={theme.spacing(LAYOUT.MD)}
|
||||
alignItems="center"
|
||||
>
|
||||
<Grid size={2}>{t("pages.incidents.dialog.details.status")}</Grid>
|
||||
<Grid size={2}>
|
||||
<Cell>{t("pages.incidents.dialog.details.status")}</Cell>
|
||||
</Grid>
|
||||
<Grid size={10}>
|
||||
<ValueLabel
|
||||
value={incident.status ? "negative" : "positive"}
|
||||
@@ -61,15 +76,17 @@ export const CardDetails = ({ incident, monitor, sx }: CardDetailsProps) => {
|
||||
</Grid>
|
||||
{monitor && (
|
||||
<>
|
||||
<Grid size={2}>{t("pages.incidents.dialog.details.monitor")}</Grid>
|
||||
<Grid size={2}>
|
||||
<Cell>{t("pages.incidents.dialog.details.monitor")}</Cell>
|
||||
</Grid>
|
||||
<Grid size={10}>
|
||||
<Typography>{monitor.name ?? "N/A"}</Typography>
|
||||
<Cell>{monitor.name ?? "N/A"}</Cell>
|
||||
</Grid>
|
||||
<Grid size={2}>
|
||||
<Typography>{t("pages.incidents.dialog.details.url")}</Typography>
|
||||
<Cell>{t("pages.incidents.dialog.details.url")}</Cell>
|
||||
</Grid>
|
||||
<Grid size={10}>
|
||||
<Typography>{monitor.url ?? "N/A"}</Typography>
|
||||
<Cell>{monitor.url ?? "N/A"}</Cell>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
@@ -78,51 +95,48 @@ export const CardDetails = ({ incident, monitor, sx }: CardDetailsProps) => {
|
||||
</BaseBox>
|
||||
<BaseBox padding={LAYOUT.MD}>
|
||||
<Stack gap={theme.spacing(LAYOUT.MD)}>
|
||||
<Typography textTransform={"uppercase"}>
|
||||
{t("pages.incidents.dialog.details.analysis")}
|
||||
</Typography>
|
||||
<Divider />
|
||||
<SectionHeading>{t("pages.incidents.dialog.details.analysis")}</SectionHeading>
|
||||
<Grid
|
||||
container
|
||||
spacing={theme.spacing(LAYOUT.MD)}
|
||||
>
|
||||
<Grid size={6}>
|
||||
<Typography>{t("pages.incidents.dialog.details.timeline")}</Typography>
|
||||
<Cell>{t("pages.incidents.dialog.details.timeline")}</Cell>
|
||||
</Grid>
|
||||
<Grid size={6}>
|
||||
<Typography>{t("pages.incidents.dialog.details.detailsLabel")}</Typography>
|
||||
<Cell>{t("pages.incidents.dialog.details.detailsLabel")}</Cell>
|
||||
</Grid>
|
||||
<Grid size={6}>
|
||||
<Divider></Divider>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid size={6}>
|
||||
<Divider></Divider>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid size={2}>
|
||||
<Typography>{t("pages.incidents.dialog.details.startedAt")}</Typography>
|
||||
<Cell>{t("pages.incidents.dialog.details.startedAt")}</Cell>
|
||||
</Grid>
|
||||
<Grid size={4}>
|
||||
<Typography>
|
||||
<Cell>
|
||||
{formatDateWithTz(incident.startTime, "D MMM YYYY, h:mm A", uiTimezone)}
|
||||
</Typography>
|
||||
</Cell>
|
||||
</Grid>
|
||||
<Grid size={2}>
|
||||
<Typography>{t("pages.incidents.dialog.details.statusCode")}</Typography>
|
||||
<Cell>{t("pages.incidents.dialog.details.statusCode")}</Cell>
|
||||
</Grid>
|
||||
<Grid size={4}>
|
||||
<Typography>{incident.statusCode ?? "N/A"}</Typography>
|
||||
<Cell>{incident.statusCode ?? "N/A"}</Cell>
|
||||
</Grid>
|
||||
<Grid size={2}>
|
||||
<Typography>{t("pages.incidents.dialog.details.downtime")}</Typography>
|
||||
<Cell>{t("pages.incidents.dialog.details.downtime")}</Cell>
|
||||
</Grid>
|
||||
<Grid size={4}>
|
||||
<Typography>{getIncidentsDuration(incident)}</Typography>
|
||||
<Cell>{getIncidentsDuration(incident)}</Cell>
|
||||
</Grid>
|
||||
<Grid size={2}>
|
||||
<Typography>{t("pages.incidents.dialog.details.message")}</Typography>
|
||||
<Cell>{t("pages.incidents.dialog.details.message")}</Cell>
|
||||
</Grid>
|
||||
<Grid size={4}>
|
||||
<Typography>{incident.message ?? "N/A"}</Typography>
|
||||
<Cell>{incident.message ?? "N/A"}</Cell>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Stack>
|
||||
@@ -130,60 +144,53 @@ export const CardDetails = ({ incident, monitor, sx }: CardDetailsProps) => {
|
||||
{!incident.status && (
|
||||
<BaseBox padding={LAYOUT.MD}>
|
||||
<Stack gap={theme.spacing(LAYOUT.XS)}>
|
||||
<Typography textTransform={"uppercase"}>
|
||||
<SectionHeading>
|
||||
{t("pages.incidents.dialog.details.resolutionDetails")}
|
||||
</Typography>
|
||||
<Divider />
|
||||
</SectionHeading>
|
||||
<Grid
|
||||
container
|
||||
spacing={theme.spacing(LAYOUT.MD)}
|
||||
alignItems="center"
|
||||
>
|
||||
<Grid size={2}>
|
||||
<Typography>{t("pages.incidents.dialog.details.resolvedAt")}</Typography>
|
||||
<Cell>{t("pages.incidents.dialog.details.resolvedAt")}</Cell>
|
||||
</Grid>
|
||||
<Grid size={10}>
|
||||
<Typography>
|
||||
<Cell>
|
||||
{incident.endTime
|
||||
? formatDateWithTz(incident.endTime, "D MMM YYYY, h:mm A", uiTimezone)
|
||||
: "N/A"}
|
||||
</Typography>
|
||||
</Cell>
|
||||
</Grid>
|
||||
<Grid size={2}>
|
||||
<Typography>
|
||||
{t("pages.incidents.dialog.details.resolutionType")}
|
||||
</Typography>
|
||||
<Cell>{t("pages.incidents.dialog.details.resolutionType")}</Cell>
|
||||
</Grid>
|
||||
<Grid size={10}>
|
||||
<Typography>
|
||||
<Cell>
|
||||
{incident.resolutionType
|
||||
? t(
|
||||
`pages.incidents.dialog.details.resolutionTypes.${incident.resolutionType}`
|
||||
)
|
||||
: "N/A"}
|
||||
</Typography>
|
||||
</Cell>
|
||||
</Grid>
|
||||
{incident.resolvedBy && (
|
||||
<>
|
||||
<Grid size={2}>
|
||||
<Typography>
|
||||
{t("pages.incidents.dialog.details.resolvedBy")}
|
||||
</Typography>
|
||||
<Cell>{t("pages.incidents.dialog.details.resolvedBy")}</Cell>
|
||||
</Grid>
|
||||
<Grid size={10}>
|
||||
<Typography>
|
||||
{incident.resolvedByEmail ?? incident.resolvedBy}
|
||||
</Typography>
|
||||
<Cell>{incident.resolvedByEmail ?? incident.resolvedBy}</Cell>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
{incident.comment && (
|
||||
<>
|
||||
<Grid size={2}>
|
||||
<Typography>{t("pages.incidents.dialog.details.comment")}</Typography>
|
||||
<Cell>{t("pages.incidents.dialog.details.comment")}</Cell>
|
||||
</Grid>
|
||||
<Grid size={10}>
|
||||
<Typography>{incident.comment}</Typography>
|
||||
<Cell>{incident.comment}</Cell>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { BaseBox, ValueLabel } from "@/Components/design-elements";
|
||||
import { BaseBox, Icon, ValueLabel } from "@/Components/design-elements";
|
||||
import { CircleCheck, TriangleAlert, Bell, Wrench, Globe } from "lucide-react";
|
||||
import Box from "@mui/material/Box";
|
||||
|
||||
@@ -25,6 +24,7 @@ const SummaryItem = ({ icon, label, value }: SummaryItemProps) => {
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
gap={theme.spacing(2)}
|
||||
minHeight={32}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
@@ -32,10 +32,10 @@ const SummaryItem = ({ icon, label, value }: SummaryItemProps) => {
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
{icon}
|
||||
<Typography variant="body2">{label}</Typography>
|
||||
<Typography variant="body1">{label}</Typography>
|
||||
</Stack>
|
||||
<Typography
|
||||
variant="body2"
|
||||
variant="body1"
|
||||
fontWeight={600}
|
||||
>
|
||||
{value}
|
||||
@@ -67,15 +67,11 @@ export const SummaryCard = ({
|
||||
>
|
||||
<Typography
|
||||
component="h2"
|
||||
sx={{
|
||||
textTransform: "uppercase",
|
||||
fontWeight: 500,
|
||||
fontSize: 13,
|
||||
}}
|
||||
variant="eyebrow"
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
<Divider />
|
||||
{children}
|
||||
</Stack>
|
||||
</BaseBox>
|
||||
@@ -97,10 +93,13 @@ export const SummaryCardActiveIncidents = ({
|
||||
const activeCount = summary.totalActive;
|
||||
const hasActive = activeCount > 0;
|
||||
const color = hasActive ? theme.palette.error.main : theme.palette.success.main;
|
||||
const icon = hasActive ? (
|
||||
<TriangleAlert color={color} />
|
||||
) : (
|
||||
<CircleCheck color={color} />
|
||||
const icon = (
|
||||
<Box sx={{ color, display: "inline-flex" }}>
|
||||
<Icon
|
||||
icon={hasActive ? TriangleAlert : CircleCheck}
|
||||
size={32}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
const msg = t("pages.incidents.summaryCard.activeIncidents.active", {
|
||||
count: activeCount,
|
||||
@@ -133,10 +132,7 @@ const SummaryIncidentItem = ({ incident }: { incident: IncidentSummaryItem }) =>
|
||||
container
|
||||
alignItems="center"
|
||||
spacing={2}
|
||||
sx={{
|
||||
width: "100%",
|
||||
py: theme.spacing(0.5),
|
||||
}}
|
||||
sx={{ width: "100%", minHeight: 32 }}
|
||||
>
|
||||
<Grid
|
||||
size={{ xs: 12, lg: 5 }}
|
||||
@@ -147,7 +143,7 @@ const SummaryIncidentItem = ({ incident }: { incident: IncidentSummaryItem }) =>
|
||||
gap: theme.spacing(2),
|
||||
}}
|
||||
>
|
||||
<Globe />
|
||||
<Icon icon={Globe} />
|
||||
<Typography
|
||||
variant="body1"
|
||||
fontWeight={500}
|
||||
@@ -159,9 +155,7 @@ const SummaryIncidentItem = ({ incident }: { incident: IncidentSummaryItem }) =>
|
||||
|
||||
<Grid
|
||||
size={{ xs: 12, md: 6, lg: 3 }}
|
||||
sx={{
|
||||
display: "flex",
|
||||
}}
|
||||
sx={{ display: "flex" }}
|
||||
>
|
||||
<ValueLabel
|
||||
value={incident.status ? "negative" : "positive"}
|
||||
@@ -173,7 +167,7 @@ const SummaryIncidentItem = ({ incident }: { incident: IncidentSummaryItem }) =>
|
||||
size={{ xs: 12, md: 6, lg: 4 }}
|
||||
sx={{
|
||||
textAlign: { xs: "left", md: "right" },
|
||||
fontWeight: 500,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
<Typography variant="body1">{duration}</Typography>
|
||||
@@ -190,22 +184,17 @@ 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)}>
|
||||
{latestIncidents.slice(0, 3).map((incident, index) => (
|
||||
<Box key={incident.id}>
|
||||
<SummaryIncidentItem incident={incident} />
|
||||
{index < latestIncidents.length - 1 && (
|
||||
<Divider sx={{ mt: theme.spacing(2) }} />
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
{latestIncidents.slice(0, 3).map((incident) => (
|
||||
<SummaryIncidentItem
|
||||
key={incident.id}
|
||||
incident={incident}
|
||||
/>
|
||||
))}
|
||||
</SummaryCard>
|
||||
);
|
||||
};
|
||||
@@ -224,17 +213,17 @@ export const SummaryCardStats = ({ summary }: SummaryCardStatsProps) => {
|
||||
return (
|
||||
<SummaryCard title={t("pages.incidents.summaryCard.incidentStats.title")}>
|
||||
<SummaryItem
|
||||
icon={<Bell size={18} />}
|
||||
icon={<Icon icon={Bell} />}
|
||||
label={t("pages.incidents.summaryCard.incidentStats.totalIncidents")}
|
||||
value={summary?.total || 0}
|
||||
/>
|
||||
<SummaryItem
|
||||
icon={<TriangleAlert size={18} />}
|
||||
icon={<Icon icon={TriangleAlert} />}
|
||||
label={t("pages.incidents.summaryCard.incidentStats.mostAffectedMonitor")}
|
||||
value={mostAffected}
|
||||
/>
|
||||
<SummaryItem
|
||||
icon={<Wrench size={18} />}
|
||||
icon={<Icon icon={Wrench} />}
|
||||
label={t("pages.incidents.summaryCard.incidentStats.avgResolutionTime")}
|
||||
value={
|
||||
summary.total > 0
|
||||
|
||||
@@ -75,7 +75,7 @@ export const ControlsIncidentFilter = ({
|
||||
variant="contained"
|
||||
onClick={onClearFilters}
|
||||
>
|
||||
{t("pages.incidents.filters.clearFilters")}
|
||||
{t("common.buttons.clearFilters")}
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -15,6 +15,7 @@ import { HeaderTimeRange } from "@/Components/common";
|
||||
import { ControlsIncidentFilter } from "@/Pages/Incidents/Components/ControlsIncidentFilter";
|
||||
|
||||
import { useGet } from "@/Hooks/UseApi";
|
||||
import { LAYOUT } from "@/Utils/Theme/constants";
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import type { Incident, IncidentsResponse, IncidentSummary } from "@/Types/Incident";
|
||||
@@ -186,8 +187,9 @@ const IncidentsPage = () => {
|
||||
{activeIncidentsCount > 0 && (
|
||||
<>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{ mb: theme.spacing(4), textTransform: "uppercase" }}
|
||||
variant="eyebrow"
|
||||
color={theme.palette.text.secondary}
|
||||
mb={theme.spacing(LAYOUT.XS)}
|
||||
>
|
||||
{t("pages.incidents.table.activeIncidents")}
|
||||
</Typography>
|
||||
@@ -208,8 +210,9 @@ const IncidentsPage = () => {
|
||||
)}
|
||||
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{ mb: theme.spacing(4), textTransform: "uppercase" }}
|
||||
variant="eyebrow"
|
||||
color={theme.palette.text.secondary}
|
||||
mb={theme.spacing(LAYOUT.XS)}
|
||||
>
|
||||
{t("pages.incidents.table.resolvedIncidents")}
|
||||
</Typography>
|
||||
|
||||
Reference in new issue
Block a user