Merge branch 'develop' into feat/infrastructure-network-empty-state
This commit is contained in:
commit
91bdc662ca
16 files changed
+269
-230
No files matched your search
@@ -38,9 +38,9 @@ export const PageSpeedKeyPriorityFallback = () => {
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to="/settings"
|
||||
color={theme.palette.primary.main}
|
||||
fontWeight={600}
|
||||
sx={{
|
||||
color: theme.palette.primary.main,
|
||||
fontWeight: 600,
|
||||
textDecoration: "underline",
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -17,7 +17,7 @@ export const MonitorStatus = ({ monitor }: { monitor: Monitor }) => {
|
||||
return (
|
||||
<Stack>
|
||||
<Typography
|
||||
fontSize={typographyLevels.xl}
|
||||
fontSize={typographyLevels.xxl}
|
||||
fontWeight={500}
|
||||
overflow={"hidden"}
|
||||
textOverflow={"ellipsis"}
|
||||
|
||||
@@ -3,8 +3,10 @@ import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { useTheme, alpha } from "@mui/material/styles";
|
||||
import { LAYOUT } from "@/Utils/Theme/constants";
|
||||
import { typographyLevels } from "@/Utils/Theme/Palette";
|
||||
|
||||
type Severity = "info" | "warning" | "error";
|
||||
export const Severities = ["info", "warning", "error"] as const;
|
||||
export type Severity = (typeof Severities)[number];
|
||||
|
||||
const SEVERITY_GLYPH: Record<Severity, string> = {
|
||||
info: "ⓘ",
|
||||
@@ -25,29 +27,26 @@ export const NoticeBanner = ({ severity = "info", children }: NoticeBannerProps)
|
||||
direction="row"
|
||||
alignItems="flex-start"
|
||||
gap={theme.spacing(LAYOUT.SM)}
|
||||
sx={{
|
||||
width: "100%",
|
||||
p: theme.spacing(LAYOUT.MD),
|
||||
borderRadius: 1,
|
||||
border: `1px solid ${alpha(tone, 0.45)}`,
|
||||
backgroundColor: alpha(tone, 0.08),
|
||||
textAlign: "left",
|
||||
}}
|
||||
width={"100%"}
|
||||
p={theme.spacing(LAYOUT.MD)}
|
||||
borderRadius={theme.shape.borderRadius}
|
||||
border={`1px solid ${alpha(tone, 0.45)}`}
|
||||
bgcolor={alpha(tone, 0.08)}
|
||||
textAlign={"left"}
|
||||
>
|
||||
<Box
|
||||
component="span"
|
||||
sx={{
|
||||
color: tone,
|
||||
fontSize: 18,
|
||||
lineHeight: 1,
|
||||
mt: "2px",
|
||||
}}
|
||||
color={tone}
|
||||
fontSize={typographyLevels.xl}
|
||||
lineHeight={1}
|
||||
mt={LAYOUT.XXS}
|
||||
aria-hidden
|
||||
>
|
||||
{SEVERITY_GLYPH[severity]}
|
||||
</Box>
|
||||
<Typography
|
||||
sx={{ color: theme.palette.text.primary, lineHeight: 1.55, fontSize: 13 }}
|
||||
color={theme.palette.text.primary}
|
||||
lineHeight={1.55}
|
||||
>
|
||||
{children}
|
||||
</Typography>
|
||||
|
||||
@@ -1,19 +1,43 @@
|
||||
import Button from "@mui/material/Button";
|
||||
import type { ButtonProps } from "@mui/material/Button";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useTheme, darken } from "@mui/material/styles";
|
||||
import { HOVER } from "@/Utils/Theme/constants";
|
||||
|
||||
const PALETTE_COLORS = [
|
||||
"primary",
|
||||
"secondary",
|
||||
"error",
|
||||
"warning",
|
||||
"info",
|
||||
"success",
|
||||
] as const;
|
||||
type PaletteColor = (typeof PALETTE_COLORS)[number];
|
||||
|
||||
export const ButtonInput = ({ sx, ...props }: ButtonProps) => {
|
||||
const theme = useTheme();
|
||||
const outlinedSx =
|
||||
|
||||
const hoverOverrides: Record<string, unknown> = { boxShadow: "none" };
|
||||
|
||||
if (props.variant === "outlined") {
|
||||
hoverOverrides.borderColor = theme.palette.text.secondary;
|
||||
}
|
||||
|
||||
if (props.variant === "contained") {
|
||||
const colorKey = (props.color ?? "primary") as PaletteColor;
|
||||
const palette = theme.palette[colorKey];
|
||||
if (palette && typeof palette === "object" && "main" in palette) {
|
||||
hoverOverrides.backgroundColor = darken(palette.main, HOVER.DARKEN);
|
||||
}
|
||||
}
|
||||
|
||||
const variantSx =
|
||||
props.variant === "outlined"
|
||||
? {
|
||||
color: theme.palette.text.primary,
|
||||
borderColor: theme.palette.divider,
|
||||
"&:hover": {
|
||||
borderColor: theme.palette.text.secondary,
|
||||
},
|
||||
}
|
||||
: {};
|
||||
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
@@ -26,10 +50,8 @@ export const ButtonInput = ({ sx, ...props }: ButtonProps) => {
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
boxShadow: "none",
|
||||
"&:hover": {
|
||||
boxShadow: "none",
|
||||
},
|
||||
...outlinedSx,
|
||||
"&:hover": hoverOverrides,
|
||||
...variantSx,
|
||||
...sx,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -53,24 +53,24 @@ export const DialogInput = ({
|
||||
>
|
||||
{title && (
|
||||
<DialogTitle
|
||||
sx={{
|
||||
pb: theme.spacing(LAYOUT.XS),
|
||||
backgroundColor: theme.palette.action.hover,
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
}}
|
||||
pb={theme.spacing(LAYOUT.XS)}
|
||||
bgcolor={theme.palette.action.hover}
|
||||
borderBottom={`1px solid ${theme.palette.divider}`}
|
||||
>
|
||||
<Typography
|
||||
component="span"
|
||||
variant="h2"
|
||||
sx={{ fontWeight: 600, display: "block" }}
|
||||
fontWeight={600}
|
||||
display={"block"}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
{content && (
|
||||
<Typography
|
||||
variant="body1"
|
||||
color="text.secondary"
|
||||
sx={{ mt: theme.spacing(LAYOUT.XS), display: "block" }}
|
||||
color={theme.palette.text.secondary}
|
||||
mt={theme.spacing(LAYOUT.XS)}
|
||||
display={"block"}
|
||||
>
|
||||
{content}
|
||||
</Typography>
|
||||
@@ -79,7 +79,7 @@ export const DialogInput = ({
|
||||
)}
|
||||
{children && (
|
||||
<DialogContent sx={{ pt: theme.spacing(LAYOUT.MD) }}>
|
||||
<Box sx={{ pt: theme.spacing(LAYOUT.XS) }}>{children}</Box>
|
||||
<Box pt={theme.spacing(LAYOUT.XS)}>{children}</Box>
|
||||
</DialogContent>
|
||||
)}
|
||||
<DialogActions
|
||||
|
||||
@@ -15,9 +15,9 @@ export const FieldLabel = ({ children, required = false, htmlFor }: FieldLabelPr
|
||||
return (
|
||||
<Typography
|
||||
component="label"
|
||||
variant="body1"
|
||||
htmlFor={htmlFor}
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
fontWeight: 500,
|
||||
color: theme.palette.text.secondary,
|
||||
marginBottom: theme.spacing(2),
|
||||
|
||||
@@ -5,6 +5,7 @@ import { typographyLevels } from "@/Utils/Theme/Palette";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { FieldLabel } from "./FieldLabel";
|
||||
import { LAYOUT } from "@/Utils/Theme/constants";
|
||||
|
||||
interface TextInputProps extends Omit<TextFieldProps, "label"> {
|
||||
fieldLabel?: string;
|
||||
@@ -12,47 +13,53 @@ interface TextInputProps extends Omit<TextFieldProps, "label"> {
|
||||
}
|
||||
|
||||
export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(function TextInput(
|
||||
{ fieldLabel, required, ...props },
|
||||
{ fieldLabel, required, sx, ...props },
|
||||
ref
|
||||
) {
|
||||
const theme = useTheme();
|
||||
|
||||
const innerSx = {
|
||||
width: "100%",
|
||||
"& .MuiOutlinedInput-root": {
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
height: 34,
|
||||
fontSize: typographyLevels.base,
|
||||
overflow: "hidden",
|
||||
},
|
||||
"& .MuiOutlinedInput-notchedOutline": {
|
||||
borderColor: theme.palette.divider,
|
||||
},
|
||||
"&:hover .MuiOutlinedInput-notchedOutline": {
|
||||
borderColor: theme.palette.divider,
|
||||
},
|
||||
"& .MuiFormHelperText-root": {
|
||||
marginLeft: 0,
|
||||
marginRight: 0,
|
||||
marginTop: theme.spacing(1),
|
||||
},
|
||||
"& input:-webkit-autofill, & input:-webkit-autofill:hover, & input:-webkit-autofill:focus, & input:-webkit-autofill:active":
|
||||
{
|
||||
WebkitBoxShadow: `0 0 0 100px ${theme.palette.background.default} inset !important`,
|
||||
WebkitTextFillColor: `${theme.palette.text.primary} !important`,
|
||||
caretColor: theme.palette.text.primary,
|
||||
transition: "background-color 5000s ease-in-out 0s",
|
||||
},
|
||||
};
|
||||
|
||||
const input = (
|
||||
<TextField
|
||||
{...props}
|
||||
inputRef={ref}
|
||||
sx={{
|
||||
"& .MuiOutlinedInput-root": {
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
height: 34,
|
||||
fontSize: typographyLevels.base,
|
||||
overflow: "hidden",
|
||||
},
|
||||
"& .MuiOutlinedInput-notchedOutline": {
|
||||
borderColor: theme.palette.divider,
|
||||
},
|
||||
"&:hover .MuiOutlinedInput-notchedOutline": {
|
||||
borderColor: theme.palette.divider,
|
||||
},
|
||||
"& .MuiFormHelperText-root": {
|
||||
marginLeft: 0,
|
||||
marginRight: 0,
|
||||
marginTop: theme.spacing(1),
|
||||
},
|
||||
"& input:-webkit-autofill, & input:-webkit-autofill:hover, & input:-webkit-autofill:focus, & input:-webkit-autofill:active":
|
||||
{
|
||||
WebkitBoxShadow: `0 0 0 100px ${theme.palette.background.default} inset !important`,
|
||||
WebkitTextFillColor: `${theme.palette.text.primary} !important`,
|
||||
caretColor: theme.palette.text.primary,
|
||||
transition: "background-color 5000s ease-in-out 0s",
|
||||
},
|
||||
}}
|
||||
sx={fieldLabel ? innerSx : { ...innerSx, ...sx }}
|
||||
/>
|
||||
);
|
||||
|
||||
if (fieldLabel) {
|
||||
return (
|
||||
<Stack spacing={theme.spacing(2)}>
|
||||
<Stack
|
||||
spacing={theme.spacing(LAYOUT.XXS)}
|
||||
sx={sx}
|
||||
>
|
||||
<FieldLabel required={required}>{fieldLabel}</FieldLabel>
|
||||
{input}
|
||||
</Stack>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useAddTeamMemberForm } from "@/Hooks/useAddTeamMemberForm";
|
||||
import type { AddTeamMemberFormData } from "@/Validation/addTeamMember";
|
||||
import type { UserRole, User } from "@/Types/User";
|
||||
import { usePost } from "@/Hooks/UseApi";
|
||||
import { LAYOUT } from "@/Utils/Theme/constants";
|
||||
|
||||
interface AddTeamMemberDialogProps {
|
||||
open: boolean;
|
||||
@@ -82,38 +83,40 @@ export const AddTeamMemberDialog = ({
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
>
|
||||
<Stack
|
||||
gap={theme.spacing(4)}
|
||||
mt={theme.spacing(4)}
|
||||
>
|
||||
<Controller
|
||||
name="firstName"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fieldLabel={t("common.form.name.option.firstName.label")}
|
||||
placeholder={t("common.form.name.option.firstName.placeholder")}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message ?? ""}
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="lastName"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fieldLabel={t("common.form.name.option.lastName.label")}
|
||||
placeholder={t("common.form.name.option.lastName.placeholder")}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message ?? ""}
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Stack gap={theme.spacing(LAYOUT.XS)}>
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(LAYOUT.XS)}
|
||||
>
|
||||
<Controller
|
||||
name="firstName"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fieldLabel={t("common.form.name.option.firstName.label")}
|
||||
placeholder={t("common.form.name.option.firstName.placeholder")}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message ?? ""}
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="lastName"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fieldLabel={t("common.form.name.option.lastName.label")}
|
||||
placeholder={t("common.form.name.option.lastName.placeholder")}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message ?? ""}
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Stack>
|
||||
<Controller
|
||||
name="email"
|
||||
control={control}
|
||||
@@ -151,36 +154,41 @@ export const AddTeamMemberDialog = ({
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="password"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fieldLabel={t("pages.auth.common.form.option.password.label")}
|
||||
placeholder={t("pages.auth.common.form.option.password.placeholder")}
|
||||
type="password"
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message ?? ""}
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="confirm"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fieldLabel={t("pages.auth.common.form.option.confirmPassword.label")}
|
||||
placeholder={t("pages.auth.common.form.option.password.placeholder")}
|
||||
type="password"
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message ?? ""}
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(LAYOUT.XS)}
|
||||
>
|
||||
<Controller
|
||||
name="password"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fieldLabel={t("pages.auth.common.form.option.password.label")}
|
||||
placeholder={t("pages.auth.common.form.option.password.placeholder")}
|
||||
type="password"
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message ?? ""}
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="confirm"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fieldLabel={t("pages.auth.common.form.option.confirmPassword.label")}
|
||||
placeholder={t("pages.auth.common.form.option.password.placeholder")}
|
||||
type="password"
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message ?? ""}
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { UserRole } from "@/Types/User";
|
||||
import { useInviteForm } from "@/Hooks/useInviteForm";
|
||||
import type { InviteFormData } from "@/Validation/invite";
|
||||
import { usePost } from "@/Hooks/UseApi";
|
||||
import { LAYOUT } from "@/Utils/Theme/constants";
|
||||
|
||||
const CLIENT_HOST = import.meta.env.VITE_APP_CLIENT_HOST;
|
||||
|
||||
@@ -95,10 +96,7 @@ export const InviteTeamMemberDialog = ({
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
gap={theme.spacing(4)}
|
||||
mt={theme.spacing(4)}
|
||||
>
|
||||
<Stack gap={theme.spacing(LAYOUT.XS)}>
|
||||
<Controller
|
||||
name="email"
|
||||
control={control}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
const typographyBase = 13;
|
||||
|
||||
export const typographyLevels = {
|
||||
base: typographyBase,
|
||||
xs: `${(typographyBase - 4) / 16}rem`,
|
||||
s: `${(typographyBase - 2) / 16}rem`,
|
||||
m: `${typographyBase / 16}rem`,
|
||||
l: `${(typographyBase + 2) / 16}rem`,
|
||||
xl: `${(typographyBase + 10) / 16}rem`,
|
||||
base: typographyBase, // 13px
|
||||
xs: `${(typographyBase - 4) / 16}rem`, // 9px
|
||||
s: `${(typographyBase - 2) / 16}rem`, // 11px
|
||||
m: `${typographyBase / 16}rem`, // 13px
|
||||
l: `${(typographyBase + 2) / 16}rem`, // 15px
|
||||
xl: `${(typographyBase + 5) / 16}rem`, // 18px
|
||||
xxl: `${(typographyBase + 10) / 16}rem`, // 23px
|
||||
};
|
||||
|
||||
export const colors = {
|
||||
|
||||
@@ -58,7 +58,7 @@ export const theme = (mode: string, palette: any) =>
|
||||
fontFamilyMonospace,
|
||||
fontSize: typographyLevels.base,
|
||||
h1: {
|
||||
fontSize: typographyLevels.xl,
|
||||
fontSize: typographyLevels.xxl,
|
||||
fontWeight: 500,
|
||||
},
|
||||
h2: {
|
||||
|
||||
@@ -8,6 +8,7 @@ export const SPACING = {
|
||||
} as const;
|
||||
|
||||
export const LAYOUT = {
|
||||
XXS: 2,
|
||||
XS: 4,
|
||||
SM: 6,
|
||||
MD: 8,
|
||||
@@ -15,3 +16,7 @@ export const LAYOUT = {
|
||||
XL: 12,
|
||||
XXL: 16,
|
||||
} as const;
|
||||
|
||||
export const HOVER = {
|
||||
DARKEN: 0.06, // This is a coefficient for darkening function
|
||||
} as const;
|
||||
Reference in new issue
Block a user