detail modal fixes and filters options
This commit is contained in:
1 parent
90eee474d2
commit
09513696ee
12 files changed
+49
-685
No files matched your search
@@ -168,7 +168,6 @@ const IncidentDetailsModal = ({ open, incidentId, onClose, onResolved }) => {
|
||||
>
|
||||
<Typography
|
||||
variant="body1"
|
||||
color="text.secondary"
|
||||
fontWeight={500}
|
||||
sx={{ minWidth: 70 }}
|
||||
>
|
||||
@@ -188,7 +187,6 @@ const IncidentDetailsModal = ({ open, incidentId, onClose, onResolved }) => {
|
||||
>
|
||||
<Typography
|
||||
variant="body1"
|
||||
color="text.secondary"
|
||||
fontWeight={500}
|
||||
sx={{ minWidth: 70 }}
|
||||
>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Menu from "@mui/material/Menu";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
//import Settings from "../../../../assets/icons/settings-bold.svg?react";
|
||||
import Icon from "@/Components/v1/Icon/index.jsx";
|
||||
import ResolveIncidentDialog from "../ResolveIncidentDialog/index.jsx";
|
||||
import useFetchIncidents from "../../hooks/useFetchIncidents";
|
||||
|
||||
@@ -51,7 +51,7 @@ const IncidentActionsMenu = ({ incident, onResolve, onOpenDetails }) => {
|
||||
e.stopPropagation();
|
||||
closeMenu(e);
|
||||
if (onOpenDetails) {
|
||||
onOpenDetails(incident._id);
|
||||
onOpenDetails(incident.id);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -59,8 +59,7 @@ const IncidentActionsMenu = ({ incident, onResolve, onOpenDetails }) => {
|
||||
e.stopPropagation();
|
||||
closeMenu(e);
|
||||
try {
|
||||
const incidentData = await fetchIncidentById(incident._id);
|
||||
console.log(incidentData);
|
||||
const incidentData = await fetchIncidentById(incident.id);
|
||||
if (incidentData) {
|
||||
const { type, _id: monitorId } = incidentData.monitorId;
|
||||
const path = TypeToPathMap[type];
|
||||
@@ -94,7 +93,10 @@ const IncidentActionsMenu = ({ incident, onResolve, onOpenDetails }) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* <Settings /> */}M
|
||||
<Icon
|
||||
name="Settings"
|
||||
size={20}
|
||||
/>
|
||||
</IconButton>
|
||||
|
||||
<Menu
|
||||
@@ -134,7 +136,7 @@ const IncidentActionsMenu = ({ incident, onResolve, onOpenDetails }) => {
|
||||
</Menu>
|
||||
<ResolveIncidentDialog
|
||||
open={isDialogOpen}
|
||||
incidentId={incident._id}
|
||||
incidentId={incident.id}
|
||||
onClose={closeDialog}
|
||||
onResolve={onResolve}
|
||||
/>
|
||||
@@ -144,7 +146,7 @@ const IncidentActionsMenu = ({ incident, onResolve, onOpenDetails }) => {
|
||||
|
||||
IncidentActionsMenu.propTypes = {
|
||||
incident: PropTypes.shape({
|
||||
_id: PropTypes.string.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
status: PropTypes.bool.isRequired,
|
||||
}).isRequired,
|
||||
onResolve: PropTypes.func.isRequired,
|
||||
|
||||
@@ -13,7 +13,7 @@ import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
|
||||
import { useSelector } from "react-redux";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, Typography, useTheme } from "@mui/material";
|
||||
import { Typography, useTheme } from "@mui/material";
|
||||
|
||||
const IncidentTable = ({
|
||||
monitors = [],
|
||||
@@ -36,7 +36,7 @@ const IncidentTable = ({
|
||||
|
||||
const handleResolveIncident = async (incidentId, options = {}) => {
|
||||
try {
|
||||
await resolveIncident(incidentId);
|
||||
await resolveIncident(incidentId, options);
|
||||
handleUpdateTrigger();
|
||||
} catch (error) {
|
||||
console.error(t("incidentsPage.errorResolvingIncident"), error);
|
||||
@@ -48,7 +48,6 @@ const IncidentTable = ({
|
||||
id: "monitorName",
|
||||
content: t("incidentsTableMonitorName"),
|
||||
render: (row) => {
|
||||
console.log(monitors, row);
|
||||
const monitor = monitors.find((monitor) => monitor.id === row.monitorId);
|
||||
return monitor ? monitor.name : "N/A";
|
||||
},
|
||||
|
||||
@@ -84,7 +84,7 @@ const LatestIncidentsPanel = ({ incidents = [], isLoading = false, error = null
|
||||
) : (
|
||||
<Stack gap={theme.spacing(4)}>
|
||||
{incidents.map((incident, index) => (
|
||||
<Box key={incident._id}>
|
||||
<Box key={incident.id}>
|
||||
<IncidentItem incident={incident} />
|
||||
{index < incidents.length - 1 && <Divider sx={{ mt: theme.spacing(2) }} />}
|
||||
</Box>
|
||||
|
||||
@@ -26,6 +26,7 @@ const OptionsHeader = ({
|
||||
{ _id: "active", name: t("incidentsPage.incidentsOptionsHeaderFilterActive") },
|
||||
{ _id: "resolved", name: t("incidentsPage.incidentsOptionsHeaderFilterResolved") },
|
||||
{ _id: "manual", name: t("incidentsPage.incidentsOptionsHeaderFilterManual") },
|
||||
{ _id: "automatic", name: t("incidentsPage.incidentsOptionsHeaderFilterAutomatic") },
|
||||
];
|
||||
|
||||
const stackStyles = {
|
||||
|
||||
@@ -13,7 +13,7 @@ const ResolveIncidentDialog = ({
|
||||
onResolve,
|
||||
onAfterResolve,
|
||||
onResolved,
|
||||
isLoading,
|
||||
isLoading = false,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
@@ -90,7 +90,7 @@ const ResolveIncidentDialog = ({
|
||||
|
||||
ResolveIncidentDialog.propTypes = {
|
||||
open: PropTypes.bool.isRequired,
|
||||
incidentId: PropTypes.string.isRequired,
|
||||
incidentId: PropTypes.string,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onResolve: PropTypes.func.isRequired,
|
||||
onAfterResolve: PropTypes.func,
|
||||
@@ -98,8 +98,4 @@ ResolveIncidentDialog.propTypes = {
|
||||
onResolved: PropTypes.func,
|
||||
};
|
||||
|
||||
ResolveIncidentDialog.defaultProps = {
|
||||
isLoading: false,
|
||||
};
|
||||
|
||||
export default ResolveIncidentDialog;
|
||||
@@ -71,7 +71,28 @@ const useFetchIncidents = () => {
|
||||
},
|
||||
[fetchIncidentsByTeam]
|
||||
);
|
||||
|
||||
const fetchManualResolvedIncidents = useCallback(
|
||||
async (config = {}) => {
|
||||
await fetchIncidentsByTeam({
|
||||
status: false,
|
||||
resolutionType: "manual",
|
||||
sortOrder: "desc",
|
||||
...config,
|
||||
});
|
||||
},
|
||||
[fetchIncidentsByTeam]
|
||||
);
|
||||
const fetchAutomaticResolvedIncidents = useCallback(
|
||||
async (config = {}) => {
|
||||
await fetchIncidentsByTeam({
|
||||
status: false,
|
||||
resolutionType: "automatic",
|
||||
sortOrder: "desc",
|
||||
...config,
|
||||
});
|
||||
},
|
||||
[fetchIncidentsByTeam]
|
||||
);
|
||||
/**
|
||||
* Fetch resolved incidents
|
||||
*
|
||||
@@ -154,6 +175,7 @@ const useFetchIncidents = () => {
|
||||
createToast({ body: t("incidentsPage.errorResolvingIncident") });
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("cayo a error");
|
||||
setNetworkError(true);
|
||||
console.error(t("incidentsPage.errorResolvingIncident"), error);
|
||||
const errorMessage =
|
||||
@@ -198,6 +220,8 @@ const useFetchIncidents = () => {
|
||||
fetchIncidentById,
|
||||
resolveIncident,
|
||||
fetchIncidentSummary,
|
||||
fetchManualResolvedIncidents,
|
||||
fetchAutomaticResolvedIncidents,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -49,6 +49,8 @@ const Incidents2 = () => {
|
||||
fetchActiveIncidents,
|
||||
fetchResolvedIncidents,
|
||||
resolveIncident,
|
||||
fetchManualResolvedIncidents,
|
||||
fetchAutomaticResolvedIncidents,
|
||||
} = useFetchIncidents();
|
||||
|
||||
const networkError = monitorsNetworkError || incidentsNetworkError;
|
||||
@@ -72,6 +74,10 @@ const Incidents2 = () => {
|
||||
fetchActiveIncidents(config);
|
||||
} else if (filter === "resolved") {
|
||||
fetchResolvedIncidents(config);
|
||||
} else if (filter === "manual") {
|
||||
fetchManualResolvedIncidents(config);
|
||||
} else if (filter === "automatic") {
|
||||
fetchAutomaticResolvedIncidents(config);
|
||||
} else {
|
||||
fetchIncidents(config);
|
||||
}
|
||||
@@ -85,6 +91,8 @@ const Incidents2 = () => {
|
||||
fetchActiveIncidents,
|
||||
fetchResolvedIncidents,
|
||||
fetchIncidents,
|
||||
fetchManualResolvedIncidents,
|
||||
fetchAutomaticResolvedIncidents,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,406 +0,0 @@
|
||||
// Components
|
||||
import { Stack, Typography, Grid, Box, Button, Divider } from "@mui/material";
|
||||
import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx";
|
||||
import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
|
||||
import SummaryCard from "@/Pages/Incidents2/Components/SummaryCard";
|
||||
import ResolveIncidentDialog from "../ResolveIncidentDialog/index.jsx";
|
||||
|
||||
// Utils
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { formatDateWithTz } from "@/Utils/timeUtils";
|
||||
import { useSelector } from "react-redux";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
// Hooks
|
||||
import useFetchIncidents from "@/Pages/Incidents2/hooks/useFetchIncidents";
|
||||
import useGetIncidentsDuration from "@/Pages/Incidents2/hooks/useGetIncidentsDuration";
|
||||
|
||||
const IncidentDetailsModal = ({ open, incidentId, onClose, onResolved }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const { fetchIncidentById, resolveIncident } = useFetchIncidents();
|
||||
const [incident, setIncident] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isResolveDialogOpen, setIsResolveDialogOpen] = useState(false);
|
||||
const uiTimezone = useSelector((state) => state.ui.timezone);
|
||||
const isActive = incident?.status === true;
|
||||
const duration = useGetIncidentsDuration(incident || null, isActive);
|
||||
|
||||
const statusColor = isActive ? theme.palette.error.main : theme.palette.success.main;
|
||||
const toCapitalLetter = (text) =>
|
||||
typeof text === "string" && text.length > 0
|
||||
? text.charAt(0).toUpperCase() + text.slice(1)
|
||||
: text;
|
||||
|
||||
const KeyValueRow = ({ label, value, sx = {} }) => (
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
alignItems="flex-start"
|
||||
sx={{ ...sx }}
|
||||
>
|
||||
<Grid
|
||||
item
|
||||
xs={4}
|
||||
>
|
||||
<Typography variant="body1">{label}</Typography>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs
|
||||
>
|
||||
<Typography
|
||||
variant="body1"
|
||||
textAlign="right"
|
||||
>
|
||||
{value ?? "-"}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const loadIncident = async () => {
|
||||
if (!incidentId || !open) {
|
||||
setIncident(null);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const incidentData = await fetchIncidentById(incidentId);
|
||||
setIncident(incidentData);
|
||||
} catch (error) {
|
||||
console.error("Error fetching incident:", error);
|
||||
setIncident(null);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadIncident();
|
||||
}, [incidentId, open, fetchIncidentById]);
|
||||
|
||||
const openResolveDialog = () => {
|
||||
setIsResolveDialogOpen(true);
|
||||
};
|
||||
|
||||
const closeResolveDialog = () => {
|
||||
setIsResolveDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleAfterResolve = async () => {
|
||||
if (!incidentId) return;
|
||||
const updatedIncident = await fetchIncidentById(incidentId);
|
||||
if (updatedIncident) {
|
||||
setIncident(updatedIncident);
|
||||
}
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<GenericFallback
|
||||
isLoading={true}
|
||||
message={t("incidentsPage.loadingIncidentDetails")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!incident) {
|
||||
return (
|
||||
<GenericFallback
|
||||
isLoading={false}
|
||||
message={t("incidentsPage.incidentNotFound")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(8)}>
|
||||
<Grid
|
||||
container
|
||||
spacing={3}
|
||||
>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
md={12}
|
||||
>
|
||||
<SummaryCard title={t("incidentsPage.overview")}>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(2)}
|
||||
sx={{ mb: theme.spacing(3) }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: "50%",
|
||||
bgcolor: statusColor,
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
color: statusColor,
|
||||
fontWeight: 700,
|
||||
letterSpacing: 0.4,
|
||||
}}
|
||||
>
|
||||
{isActive ? t("incidentsPage.active") : t("incidentsPage.resolved")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="column"
|
||||
alignItems="start"
|
||||
gap={theme.spacing(3)}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={2}
|
||||
sx={{ alignItems: "baseline" }}
|
||||
>
|
||||
<Typography
|
||||
variant="body1"
|
||||
color="text.secondary"
|
||||
fontWeight={500}
|
||||
sx={{ minWidth: 70 }}
|
||||
>
|
||||
{t("incidentsPage.incidentItemMonitor")}:
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{ flex: 1 }}
|
||||
>
|
||||
{incident?.monitorId?.name || t("incidentsPage.unknownMonitor")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={2}
|
||||
sx={{ alignItems: "baseline" }}
|
||||
>
|
||||
<Typography
|
||||
variant="body1"
|
||||
color="text.secondary"
|
||||
fontWeight={500}
|
||||
sx={{ minWidth: 70 }}
|
||||
>
|
||||
{t("incidentsPage.URL")}:
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{ flex: 1, wordBreak: "break-word", fontFamily: "monospace" }}
|
||||
>
|
||||
{incident?.monitorId?.url || "-"}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</SummaryCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
container
|
||||
spacing={3}
|
||||
>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
sm={12}
|
||||
>
|
||||
<SummaryCard title={t("incidentsPage.incidentAnalysis")}>
|
||||
<Grid
|
||||
container
|
||||
spacing={3}
|
||||
alignItems="stretch"
|
||||
>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
sm={6}
|
||||
>
|
||||
<Stack gap={theme.spacing(2)}>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
fontWeight={700}
|
||||
>
|
||||
{t("incidentsPage.timeline")}
|
||||
</Typography>
|
||||
<Divider sx={{ mt: 2 }} />
|
||||
|
||||
<KeyValueRow
|
||||
label={t("incidentsPage.startedAt")}
|
||||
value={
|
||||
formatDateWithTz(
|
||||
incident?.startTime,
|
||||
"D MMM YYYY, h:mm A",
|
||||
uiTimezone
|
||||
) || "-"
|
||||
}
|
||||
sx={{ paddingBottom: theme.spacing(3) }}
|
||||
/>
|
||||
{!isActive && (
|
||||
<KeyValueRow
|
||||
label={t("incidentsPage.endedAt")}
|
||||
value={
|
||||
formatDateWithTz(
|
||||
incident?.endTime,
|
||||
"D MMM YYYY, h:mm A",
|
||||
uiTimezone
|
||||
) || "-"
|
||||
}
|
||||
sx={{ paddingBottom: theme.spacing(3) }}
|
||||
/>
|
||||
)}
|
||||
|
||||
<KeyValueRow
|
||||
label={t("incidentsPage.downtime")}
|
||||
value={duration || "-"}
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
sm="auto"
|
||||
sx={{ display: { xs: "none", sm: "flex" } }}
|
||||
>
|
||||
<Divider
|
||||
orientation="vertical"
|
||||
flexItem
|
||||
/>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
sm
|
||||
>
|
||||
<Stack gap={theme.spacing(2)}>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
sx={{ letterSpacing: 0.6, fontWeight: 700 }}
|
||||
>
|
||||
{isActive
|
||||
? t("incidentsPage.errorDetails")
|
||||
: t("incidentsPage.resolutionDetails")}
|
||||
</Typography>
|
||||
|
||||
<Divider sx={{ mt: 2 }} />
|
||||
|
||||
<KeyValueRow
|
||||
label={t("incidentsPage.statusCode")}
|
||||
value={incident?.statusCode ?? "-"}
|
||||
/>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
fontFamily: "monospace",
|
||||
|
||||
wordBreak: "break-word",
|
||||
lineHeight: 1.6,
|
||||
}}
|
||||
>
|
||||
{incident?.message || "-"}
|
||||
</Typography>
|
||||
|
||||
{!isActive && (
|
||||
<>
|
||||
<Divider sx={{ mb: theme.spacing(2) }} />
|
||||
|
||||
<KeyValueRow
|
||||
label={t("incidentsPage.resolutionMethod")}
|
||||
value={toCapitalLetter(incident?.resolutionType) || "-"}
|
||||
/>
|
||||
{incident?.resolutionType === "manual" && (
|
||||
<>
|
||||
<KeyValueRow
|
||||
label={t("incidentsPage.resolvedBy")}
|
||||
value={incident?.resolvedBy?.email || "Unknown"}
|
||||
/>
|
||||
|
||||
<KeyValueRow
|
||||
label={t("incidentsPage.comment")}
|
||||
value={
|
||||
incident?.comment?.trim()
|
||||
? incident.comment
|
||||
: t("incidentsPage.noCommentProvided")
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</SummaryCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<GenericDialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
theme={theme}
|
||||
title={t("incidentsPage.incidentDetails")}
|
||||
width={{ xs: "95%", md: "45%", lg: "40%", xl: "35%" }}
|
||||
>
|
||||
<Stack
|
||||
gap={theme.spacing(6)}
|
||||
sx={{ maxHeight: "70vh", overflowY: "auto", pr: 1 }}
|
||||
>
|
||||
{renderContent()}
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="flex-end"
|
||||
gap={2}
|
||||
sx={{ pt: theme.spacing(3) }}
|
||||
>
|
||||
{isActive && (
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="error"
|
||||
onClick={openResolveDialog}
|
||||
>
|
||||
{t("incidentsPage.incidentsTableActionResolveManually")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={onClose}
|
||||
>
|
||||
{t("close")}
|
||||
</Button>
|
||||
</Stack>
|
||||
<ResolveIncidentDialog
|
||||
open={isResolveDialogOpen}
|
||||
incidentId={incidentId}
|
||||
onClose={closeResolveDialog}
|
||||
onResolve={resolveIncident}
|
||||
onAfterResolve={handleAfterResolve}
|
||||
onResolved={onResolved}
|
||||
/>
|
||||
</Stack>
|
||||
</GenericDialog>
|
||||
);
|
||||
};
|
||||
|
||||
IncidentDetailsModal.propTypes = {
|
||||
open: PropTypes.bool.isRequired,
|
||||
incidentId: PropTypes.string,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onResolved: PropTypes.func,
|
||||
};
|
||||
|
||||
export default IncidentDetailsModal;
|
||||
@@ -1,154 +0,0 @@
|
||||
// Components
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Menu from "@mui/material/Menu";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Settings from "../../../../assets/icons/settings-bold.svg?react";
|
||||
import ResolveIncidentDialog from "../ResolveIncidentDialog/index.jsx";
|
||||
import useFetchIncidents from "../../hooks/useFetchIncidents";
|
||||
|
||||
// Utils
|
||||
import { useState } from "react";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TypeToPathMap } from "@/Utils/monitorUtils.js";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const IncidentActionsMenu = ({ incident, onResolve, onOpenDetails }) => {
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const { fetchIncidentById } = useFetchIncidents();
|
||||
const navigate = useNavigate();
|
||||
const openMenu = (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const closeMenu = (e) => {
|
||||
if (e) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const openResolveDialog = (e) => {
|
||||
closeMenu(e);
|
||||
setIsDialogOpen(true);
|
||||
};
|
||||
|
||||
const closeDialog = (e) => {
|
||||
if (e) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
setIsDialogOpen(false);
|
||||
};
|
||||
|
||||
const isActive = incident.status === true;
|
||||
const handleGoToDetails = (e) => {
|
||||
e.stopPropagation();
|
||||
closeMenu(e);
|
||||
if (onOpenDetails) {
|
||||
onOpenDetails(incident._id);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoToMonitor = async (e) => {
|
||||
e.stopPropagation();
|
||||
closeMenu(e);
|
||||
try {
|
||||
const incidentData = await fetchIncidentById(incident._id);
|
||||
console.log(incidentData);
|
||||
if (incidentData) {
|
||||
const { type, _id: monitorId } = incidentData.monitorId;
|
||||
const path = TypeToPathMap[type];
|
||||
if (path && monitorId) {
|
||||
navigate(`/${path}/${monitorId}`);
|
||||
} else {
|
||||
console.error(`Monitor type not found`);
|
||||
}
|
||||
} else {
|
||||
console.error("Monitor information not found in incident data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching incident details:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton
|
||||
aria-label="incident actions"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
openMenu(event);
|
||||
}}
|
||||
sx={{
|
||||
"&:focus": {
|
||||
outline: "none",
|
||||
},
|
||||
"& svg path": {
|
||||
stroke: theme.palette.primary.contrastTextTertiary,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Settings />
|
||||
</IconButton>
|
||||
|
||||
<Menu
|
||||
className="incident-actions-menu"
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={closeMenu}
|
||||
disableScrollLock
|
||||
slotProps={{
|
||||
paper: {
|
||||
sx: {
|
||||
"& ul": {
|
||||
p: theme.spacing(2.5),
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
},
|
||||
"& li": { m: 0, color: theme.palette.primary.contrastTextSecondary },
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<MenuItem onClick={handleGoToDetails}>
|
||||
{t("incidentsPage.incidentsTableActionDetails", "Details")}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleGoToMonitor}>
|
||||
{t("incidentsPage.incidentsTableActionGoToMonitor", "Go to monitor")}
|
||||
</MenuItem>
|
||||
{isActive && (
|
||||
<MenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
openResolveDialog(e);
|
||||
}}
|
||||
>
|
||||
{t("incidentsPage.incidentsTableActionResolveManually")}
|
||||
</MenuItem>
|
||||
)}
|
||||
</Menu>
|
||||
<ResolveIncidentDialog
|
||||
open={isDialogOpen}
|
||||
incidentId={incident._id}
|
||||
onClose={closeDialog}
|
||||
onResolve={onResolve}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
IncidentActionsMenu.propTypes = {
|
||||
incident: PropTypes.shape({
|
||||
_id: PropTypes.string.isRequired,
|
||||
status: PropTypes.bool.isRequired,
|
||||
}).isRequired,
|
||||
onResolve: PropTypes.func.isRequired,
|
||||
onOpenDetails: PropTypes.func,
|
||||
};
|
||||
|
||||
export default IncidentActionsMenu;
|
||||
@@ -1,105 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Button, Stack } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx";
|
||||
import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
|
||||
|
||||
const ResolveIncidentDialog = ({
|
||||
open,
|
||||
incidentId,
|
||||
onClose,
|
||||
onResolve,
|
||||
onAfterResolve,
|
||||
onResolved,
|
||||
isLoading,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const [comment, setComment] = useState("");
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setComment("");
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const handleConfirm = async () => {
|
||||
if (!incidentId) return;
|
||||
const trimmed = comment.trim();
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
await onResolve(incidentId, trimmed ? { comment: trimmed } : {});
|
||||
if (onAfterResolve) {
|
||||
await onAfterResolve();
|
||||
}
|
||||
onResolved?.();
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error("Error resolving incident:", error);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<GenericDialog
|
||||
open={open}
|
||||
theme={theme}
|
||||
title={t("incidentsPage.resolveIncidentDialogTitle")}
|
||||
description={t("incidentsPage.resolveIncidentDialogDescription")}
|
||||
onClose={onClose}
|
||||
>
|
||||
<Stack gap={theme.spacing(4)}>
|
||||
<TextInput
|
||||
label={t("incidentsPage.resolveIncidentDialogCommentLabel")}
|
||||
placeholder={t("incidentsPage.resolveIncidentDialogCommentPlaceholder")}
|
||||
value={comment}
|
||||
onChange={(e) => setComment(e.target.value)}
|
||||
maxWidth="100%"
|
||||
/>
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(4)}
|
||||
mt={theme.spacing(4)}
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={onClose}
|
||||
disabled={isLoading || isSubmitting}
|
||||
>
|
||||
{t("cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={handleConfirm}
|
||||
disabled={isLoading || isSubmitting}
|
||||
>
|
||||
{t("incidentsPage.resolveIncidentDialogConfirm")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</GenericDialog>
|
||||
);
|
||||
};
|
||||
|
||||
ResolveIncidentDialog.propTypes = {
|
||||
open: PropTypes.bool.isRequired,
|
||||
incidentId: PropTypes.string.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onResolve: PropTypes.func.isRequired,
|
||||
onAfterResolve: PropTypes.func,
|
||||
isLoading: PropTypes.bool,
|
||||
onResolved: PropTypes.func,
|
||||
};
|
||||
|
||||
ResolveIncidentDialog.defaultProps = {
|
||||
isLoading: false,
|
||||
};
|
||||
|
||||
export default ResolveIncidentDialog;
|
||||
@@ -1202,6 +1202,7 @@
|
||||
"noIncidentIdProvided": "No incident ID provided",
|
||||
"errorFetchingIncidentSummary": "Error fetching incident summary",
|
||||
"incidentsOptionsHeaderFilterManual": "Manual",
|
||||
"incidentsOptionsHeaderFilterAutomatic": "Automatic",
|
||||
"incidentsOptionsHeaderFilterAll": "All",
|
||||
"incidentsOptionsHeaderFilterActive": "Active",
|
||||
"incidentsOptionsHeaderFilterResolved": "Resolved",
|
||||
|
||||
Reference in new issue
Block a user