From 1c01dcaf31e0fcdc7cfeba5a3f11ff52e3be7b21 Mon Sep 17 00:00:00 2001 From: karenvicent Date: Wed, 14 Jan 2026 22:12:26 -0500 Subject: [PATCH 1/7] create incidents action menu and detail page --- .../IncidentTable/IncidentActionsMenu.jsx | 204 ++++++++++++++++++ .../Components/IncidentTable/index.jsx | 39 +--- .../LatestIncidentsPanel/IncidentItem.jsx | 28 +-- .../src/Pages/v1/Incidents2/Details/index.jsx | 171 +++++++++++++++ .../hooks/useGetIncidentsDuration.js | 25 +++ client/src/Routes/index.jsx | 5 + client/src/locales/en.json | 12 +- 7 files changed, 428 insertions(+), 56 deletions(-) create mode 100644 client/src/Pages/v1/Incidents2/Components/IncidentTable/IncidentActionsMenu.jsx create mode 100644 client/src/Pages/v1/Incidents2/Details/index.jsx create mode 100644 client/src/Pages/v1/Incidents2/hooks/useGetIncidentsDuration.js diff --git a/client/src/Pages/v1/Incidents2/Components/IncidentTable/IncidentActionsMenu.jsx b/client/src/Pages/v1/Incidents2/Components/IncidentTable/IncidentActionsMenu.jsx new file mode 100644 index 000000000..59749fca7 --- /dev/null +++ b/client/src/Pages/v1/Incidents2/Components/IncidentTable/IncidentActionsMenu.jsx @@ -0,0 +1,204 @@ +// 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 { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; +import { Button, Stack } from "@mui/material"; +import useFetchIncidents from "../../hooks/useFetchIncidents"; + +// Utils +import { useState } from "react"; +import { useTheme } from "@emotion/react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import { TypeToPathMap } from "@/Utils/monitorUtils.js"; +import PropTypes from "prop-types"; + +const IncidentActionsMenu = ({ incident, onResolve }) => { + const [anchorEl, setAnchorEl] = useState(null); + const [isDialogOpen, setIsDialogOpen] = useState(false); + const [comment, setComment] = useState(""); + const theme = useTheme(); + const { t } = useTranslation(); + const navigate = useNavigate(); + const { fetchIncidentById } = useFetchIncidents(); + const openMenu = (event) => { + event.preventDefault(); + event.stopPropagation(); + setAnchorEl(event.currentTarget); + }; + + const closeMenu = (e) => { + if (e) { + e.stopPropagation(); + } + setAnchorEl(null); + }; + + const openResolveDialog = (e) => { + closeMenu(e); + setComment(""); + setIsDialogOpen(true); + }; + + const closeDialog = (e) => { + if (e) { + e.stopPropagation(); + } + setComment(""); + setIsDialogOpen(false); + }; + + const handleConfirmResolve = (e) => { + e.stopPropagation(); + if (onResolve) { + const options = comment.trim() ? { comment: comment.trim() } : {}; + onResolve(incident._id, options); + } + setComment(""); + setIsDialogOpen(false); + }; + + const isActive = incident.status === true; + const handleGoToDetails = (e) => { + e.stopPropagation(); + closeMenu(e); + navigate(`/incidents/details/${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 ( + <> + { + event.stopPropagation(); + openMenu(event); + }} + sx={{ + "&:focus": { + outline: "none", + }, + "& svg path": { + stroke: theme.palette.primary.contrastTextTertiary, + }, + }} + > + + + + + {isActive && ( + { + e.stopPropagation(); + openResolveDialog(e); + }} + > + {t("incidentsPage.incidentsTableActionResolveManually")} + + )} + + {t("incidentsPage.incidentsTableActionDetails", "Details")} + + + {t("incidentsPage.incidentsTableActionGoToMonitor", "Go to monitor")} + + + Add comment + + {/* Future options will be added here */} + + + + setComment(e.target.value)} + maxWidth="100%" + /> + + + + + + + + ); +}; + +IncidentActionsMenu.propTypes = { + incident: PropTypes.shape({ + _id: PropTypes.string.isRequired, + status: PropTypes.bool.isRequired, + }).isRequired, + onResolve: PropTypes.func.isRequired, +}; + +export default IncidentActionsMenu; diff --git a/client/src/Pages/v1/Incidents2/Components/IncidentTable/index.jsx b/client/src/Pages/v1/Incidents2/Components/IncidentTable/index.jsx index 8cac4e07b..5456d55f2 100644 --- a/client/src/Pages/v1/Incidents2/Components/IncidentTable/index.jsx +++ b/client/src/Pages/v1/Incidents2/Components/IncidentTable/index.jsx @@ -6,13 +6,14 @@ import { StatusLabel } from "@/Components/v1/Label/index.jsx"; import { HttpStatusLabel } from "@/Components/v1/HttpStatusLabel/index.jsx"; import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx"; +import IncidentActionsMenu from "./IncidentActionsMenu.jsx"; //Utils 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 = ({ incidents = [], @@ -31,9 +32,9 @@ const IncidentTable = ({ const { t } = useTranslation(); const theme = useTheme(); - const handleResolveIncident = async (incidentId) => { + const handleResolveIncident = async (incidentId, options = {}) => { try { - await resolveIncident(incidentId); + await resolveIncident(incidentId, options); handleUpdateTrigger(); } catch (error) { console.error(t("incidentsPage.errorResolvingIncident"), error); @@ -120,32 +121,12 @@ const IncidentTable = ({ id: "action", content: t("actions"), render: (row) => { - if (row.status === true) { - return ( - - ); - } else { - return ( - - {t("incidentsPage.incidentsTableResolved")} - - ); - } + return ( + + ); }, }, ]; diff --git a/client/src/Pages/v1/Incidents2/Components/LatestIncidentsPanel/IncidentItem.jsx b/client/src/Pages/v1/Incidents2/Components/LatestIncidentsPanel/IncidentItem.jsx index c212c8e52..dff710597 100644 --- a/client/src/Pages/v1/Incidents2/Components/LatestIncidentsPanel/IncidentItem.jsx +++ b/client/src/Pages/v1/Incidents2/Components/LatestIncidentsPanel/IncidentItem.jsx @@ -2,40 +2,16 @@ import PropTypes from "prop-types"; import { Box, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import { StatusLabel } from "@/Components/v1/Label/index.jsx"; -import { getHumanReadableDuration } from "@/Utils/timeUtils.js"; import Monitors from "@/assets/icons/monitors.svg?react"; import AverageResponseIcon from "@/assets/icons/status-pages.svg?react"; import { useTranslation } from "react-i18next"; +import useGetIncidentsDuration from "../../hooks/useGetIncidentsDuration"; const IncidentItem = ({ incident }) => { const { t } = useTranslation(); const theme = useTheme(); const isActive = incident.status === true; - const calculateDuration = () => { - if (!incident.startTime) { - return "-"; - } - const startTime = new Date(incident.startTime); - const endTime = isActive - ? new Date() - : incident.endTime - ? new Date(incident.endTime) - : null; - - if (!endTime) { - return "-"; - } - - const durationMs = endTime.getTime() - startTime.getTime(); - - if (durationMs < 0) { - return "-"; - } - - return getHumanReadableDuration(durationMs); - }; - - const duration = calculateDuration(); + const duration = useGetIncidentsDuration(incident, isActive); const iconWrapperStyle = { px: theme.spacing(2), display: "flex", diff --git a/client/src/Pages/v1/Incidents2/Details/index.jsx b/client/src/Pages/v1/Incidents2/Details/index.jsx new file mode 100644 index 000000000..67388899d --- /dev/null +++ b/client/src/Pages/v1/Incidents2/Details/index.jsx @@ -0,0 +1,171 @@ +// Components +import { Stack, Typography, Grid, Box } from "@mui/material"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; +import SummaryCard from "../Components/SummaryCard"; +import useGetIncidentsDuration from "../hooks/useGetIncidentsDuration"; + +// Utils +import { useParams } from "react-router-dom"; +import { useTheme } from "@emotion/react"; +import { useTranslation } from "react-i18next"; +import useFetchIncidents from "../hooks/useFetchIncidents"; +import { useEffect, useState } from "react"; + +// Constants +const BREADCRUMBS = [ + { name: "incidents", path: "/incidents" }, + { name: "details", path: "" }, +]; + +const IncidentDetails = () => { + const theme = useTheme(); + const { t } = useTranslation(); + const { incidentId } = useParams(); + const { fetchIncidentById } = useFetchIncidents(); + const [incident, setIncident] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const isActive = incident?.status === true; + const duration = useGetIncidentsDuration(incident || null, isActive); + useEffect(() => { + const loadIncident = async () => { + if (incidentId) { + setIsLoading(true); + try { + const incidentData = await fetchIncidentById(incidentId); + console.log(incidentData); + setIncident(incidentData); + } catch (error) { + console.error("Error fetching incident:", error); + } finally { + setIsLoading(false); + } + } + }; + + loadIncident(); + }, [incidentId, fetchIncidentById]); + + if (isLoading) { + return ( + + + + + ); + } + + if (!incident) { + return ( + + + + + ); + } + + return ( + + + + {t("incidentsPage.incidentDetails", "Incident Details")} + + + + + + {duration} + + + + + {incident?.startTime} + + + {!isActive && ( + + + {incident?.endTime} + + + )} + + + + + {incident?.statusCode} + {incident?.message} + + + + + {incident?.checks.length} + + + {!isActive && ( + + + {incident?.resolutionType} + {incident?.resolutionType === "manual" && ( + + + Resolved by {incident?.resolvedBy?.email} + + {incident?.comment && ( + Comment: {incident?.comment} + )} + + )} + + + )} + + + + ); +}; + +export default IncidentDetails; diff --git a/client/src/Pages/v1/Incidents2/hooks/useGetIncidentsDuration.js b/client/src/Pages/v1/Incidents2/hooks/useGetIncidentsDuration.js new file mode 100644 index 000000000..e9b1da6d1 --- /dev/null +++ b/client/src/Pages/v1/Incidents2/hooks/useGetIncidentsDuration.js @@ -0,0 +1,25 @@ +import { getHumanReadableDuration } from "@/Utils/timeUtils.js"; +const useGetIncidentsDuration = (incident, isActive) => { + if (!incident?.startTime) { + return "-"; + } + const startTime = new Date(incident?.startTime); + const endTime = isActive + ? new Date() + : incident?.endTime + ? new Date(incident.endTime) + : null; + + if (!endTime) { + return "-"; + } + + const durationMs = endTime.getTime() - startTime.getTime(); + + if (durationMs < 0) { + return "-"; + } + + return getHumanReadableDuration(durationMs); +}; +export default useGetIncidentsDuration; diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index 255d913c1..1af600e09 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -31,6 +31,7 @@ import ServerUnreachable from "../Pages/v1/ServerUnreachable.jsx"; // Incidents import Checks from "../Pages/v1/Incidents"; import Incidents2 from "../Pages/v1/Incidents2"; +import IncidentDetails from "../Pages/v1/Incidents2/Details"; // Status pages import CreateStatus from "../Pages/v1/StatusPage/Create"; @@ -134,6 +135,10 @@ const Routes = () => { path="checks/:monitorId?" element={} /> + } + /> } diff --git a/client/src/locales/en.json b/client/src/locales/en.json index ed07fd8d2..ad68322ff 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -1193,6 +1193,16 @@ "incidentsOptionsHeaderFilterResolved": "Resolved", "incidentsTableResolved": "Closed", "incidentsTableActionResolveManually": "Resolve Manually", - "hours": "hours" + "incidentsTableActionDetails": "Details", + "incidentsTableActionGoToMonitor": "Go to monitor", + "resolveIncidentDialogTitle": "Resolve Incident", + "resolveIncidentDialogDescription": "Are you sure you want to resolve this incident manually?", + "resolveIncidentDialogCommentLabel": "Comment (optional)", + "resolveIncidentDialogCommentPlaceholder": "Add a comment about the resolution...", + "resolveIncidentDialogConfirm": "Resolve", + "hours": "hours", + "loading": "Loading incident details...", + "incidentNotFound": "Incident not found", + "incidentDetails": "Incident Details" } } From 82584d85c7babab655fb855852e78a83f9ebbb82 Mon Sep 17 00:00:00 2001 From: karenvicent Date: Tue, 20 Jan 2026 17:49:54 -0500 Subject: [PATCH 2/7] detail modal --- .../Components/IncidentDetailsModal/index.jsx | 406 ++++++++++++++++++ .../IncidentTable/IncidentActionsMenu.jsx | 88 +--- .../Components/IncidentTable/index.jsx | 3 + .../ResolveIncidentDialog/index.jsx | 105 +++++ client/src/Pages/Incidents2/index.jsx | 21 + client/src/Routes/index.jsx | 8 +- client/src/locales/en.json | 20 +- 7 files changed, 574 insertions(+), 77 deletions(-) create mode 100644 client/src/Pages/Incidents2/Components/IncidentDetailsModal/index.jsx create mode 100644 client/src/Pages/Incidents2/Components/ResolveIncidentDialog/index.jsx diff --git a/client/src/Pages/Incidents2/Components/IncidentDetailsModal/index.jsx b/client/src/Pages/Incidents2/Components/IncidentDetailsModal/index.jsx new file mode 100644 index 000000000..25af835b9 --- /dev/null +++ b/client/src/Pages/Incidents2/Components/IncidentDetailsModal/index.jsx @@ -0,0 +1,406 @@ +// 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 = {} }) => ( + + + {label} + + + + {value ?? "-"} + + + + ); + + 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 ( + + ); + } + + if (!incident) { + return ( + + ); + } + + return ( + + + + + + + + {isActive ? t("incidentsPage.active") : t("incidentsPage.resolved")} + + + + + + {t("incidentsPage.incidentItemMonitor")}: + + + {incident?.monitorId?.name || t("incidentsPage.unknownMonitor")} + + + + + {t("incidentsPage.URL")}: + + + {incident?.monitorId?.url || "-"} + + + + + + + + + + + + + + {t("incidentsPage.timeline")} + + + + + {!isActive && ( + + )} + + + + + + + + + + + {isActive + ? t("incidentsPage.errorDetails") + : t("incidentsPage.resolutionDetails")} + + + + + + + + {incident?.message || "-"} + + + {!isActive && ( + <> + + + + {incident?.resolutionType === "manual" && ( + <> + + + + + )} + + )} + + + + + + + + ); + }; + + return ( + + + {renderContent()} + + {isActive && ( + + )} + + + + + + + ); +}; + +IncidentDetailsModal.propTypes = { + open: PropTypes.bool.isRequired, + incidentId: PropTypes.string, + onClose: PropTypes.func.isRequired, + onResolved: PropTypes.func, +}; + +export default IncidentDetailsModal; diff --git a/client/src/Pages/Incidents2/Components/IncidentTable/IncidentActionsMenu.jsx b/client/src/Pages/Incidents2/Components/IncidentTable/IncidentActionsMenu.jsx index 59749fca7..2902da049 100644 --- a/client/src/Pages/Incidents2/Components/IncidentTable/IncidentActionsMenu.jsx +++ b/client/src/Pages/Incidents2/Components/IncidentTable/IncidentActionsMenu.jsx @@ -2,28 +2,25 @@ 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 { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx"; -import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; -import { Button, Stack } from "@mui/material"; +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 { useNavigate } from "react-router-dom"; import { TypeToPathMap } from "@/Utils/monitorUtils.js"; +import { useNavigate } from "react-router-dom"; import PropTypes from "prop-types"; -const IncidentActionsMenu = ({ incident, onResolve }) => { +const IncidentActionsMenu = ({ incident, onResolve, onOpenDetails }) => { const [anchorEl, setAnchorEl] = useState(null); const [isDialogOpen, setIsDialogOpen] = useState(false); - const [comment, setComment] = useState(""); const theme = useTheme(); const { t } = useTranslation(); - const navigate = useNavigate(); const { fetchIncidentById } = useFetchIncidents(); + const navigate = useNavigate(); const openMenu = (event) => { event.preventDefault(); event.stopPropagation(); @@ -39,7 +36,6 @@ const IncidentActionsMenu = ({ incident, onResolve }) => { const openResolveDialog = (e) => { closeMenu(e); - setComment(""); setIsDialogOpen(true); }; @@ -47,17 +43,6 @@ const IncidentActionsMenu = ({ incident, onResolve }) => { if (e) { e.stopPropagation(); } - setComment(""); - setIsDialogOpen(false); - }; - - const handleConfirmResolve = (e) => { - e.stopPropagation(); - if (onResolve) { - const options = comment.trim() ? { comment: comment.trim() } : {}; - onResolve(incident._id, options); - } - setComment(""); setIsDialogOpen(false); }; @@ -65,7 +50,9 @@ const IncidentActionsMenu = ({ incident, onResolve }) => { const handleGoToDetails = (e) => { e.stopPropagation(); closeMenu(e); - navigate(`/incidents/details/${incident._id}`); + if (onOpenDetails) { + onOpenDetails(incident._id); + } }; const handleGoToMonitor = async (e) => { @@ -128,6 +115,12 @@ const IncidentActionsMenu = ({ incident, onResolve }) => { }, }} > + + {t("incidentsPage.incidentsTableActionDetails", "Details")} + + + {t("incidentsPage.incidentsTableActionGoToMonitor", "Go to monitor")} + {isActive && ( { @@ -138,57 +131,13 @@ const IncidentActionsMenu = ({ incident, onResolve }) => { {t("incidentsPage.incidentsTableActionResolveManually")} )} - - {t("incidentsPage.incidentsTableActionDetails", "Details")} - - - {t("incidentsPage.incidentsTableActionGoToMonitor", "Go to monitor")} - - - Add comment - - {/* Future options will be added here */} - - - setComment(e.target.value)} - maxWidth="100%" - /> - - - - - - + onResolve={onResolve} + /> ); }; @@ -199,6 +148,7 @@ IncidentActionsMenu.propTypes = { status: PropTypes.bool.isRequired, }).isRequired, onResolve: PropTypes.func.isRequired, + onOpenDetails: PropTypes.func, }; export default IncidentActionsMenu; diff --git a/client/src/Pages/Incidents2/Components/IncidentTable/index.jsx b/client/src/Pages/Incidents2/Components/IncidentTable/index.jsx index 05d73f755..157cf5103 100644 --- a/client/src/Pages/Incidents2/Components/IncidentTable/index.jsx +++ b/client/src/Pages/Incidents2/Components/IncidentTable/index.jsx @@ -26,6 +26,7 @@ const IncidentTable = ({ handleChangeRowsPerPage, resolveIncident, handleUpdateTrigger, + onOpenDetails, }) => { const uiTimezone = useSelector((state) => state.ui.timezone); @@ -125,6 +126,7 @@ const IncidentTable = ({ ); }, @@ -180,6 +182,7 @@ IncidentTable.propTypes = { handleChangeRowsPerPage: PropTypes.func.isRequired, // Handler for rows per page change resolveIncident: PropTypes.func.isRequired, handleUpdateTrigger: PropTypes.func.isRequired, + onOpenDetails: PropTypes.func, }; export default IncidentTable; diff --git a/client/src/Pages/Incidents2/Components/ResolveIncidentDialog/index.jsx b/client/src/Pages/Incidents2/Components/ResolveIncidentDialog/index.jsx new file mode 100644 index 000000000..e2be5fed2 --- /dev/null +++ b/client/src/Pages/Incidents2/Components/ResolveIncidentDialog/index.jsx @@ -0,0 +1,105 @@ +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 ( + + + setComment(e.target.value)} + maxWidth="100%" + /> + + + + + + + ); +}; + +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; diff --git a/client/src/Pages/Incidents2/index.jsx b/client/src/Pages/Incidents2/index.jsx index 966388b51..e3bd53ab4 100644 --- a/client/src/Pages/Incidents2/index.jsx +++ b/client/src/Pages/Incidents2/index.jsx @@ -5,6 +5,7 @@ import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; import IncidentTable from "./Components/IncidentTable/index.jsx"; import OptionsHeader from "./Components/OptionsHeader/index.jsx"; import IncidentsSummaryPanel from "./Components/IncidentsSummaryPanel/index.jsx"; +import IncidentDetailsModal from "./Components/IncidentDetailsModal/index.jsx"; //Utils import { useTheme } from "@emotion/react"; @@ -30,6 +31,8 @@ const Incidents2 = () => { const [rowsPerPage, setRowsPerPage] = useState(10); const [monitorLookup, setMonitorLookup] = useState(undefined); const [updateTrigger, setUpdateTrigger] = useState(false); + const [isDetailsOpen, setIsDetailsOpen] = useState(false); + const [selectedIncidentId, setSelectedIncidentId] = useState(null); const handleUpdateTrigger = () => { setUpdateTrigger((prev) => !prev); }; @@ -114,6 +117,17 @@ const Incidents2 = () => { setPage(0); }; + const handleOpenDetails = (id) => { + if (!id) return; + setSelectedIncidentId(id); + setIsDetailsOpen(true); + }; + + const handleCloseDetails = () => { + setIsDetailsOpen(false); + setSelectedIncidentId(null); + }; + return ( @@ -142,6 +156,13 @@ const Incidents2 = () => { handleChangeRowsPerPage={handleChangeRowsPerPage} resolveIncident={resolveIncident} handleUpdateTrigger={handleUpdateTrigger} + onOpenDetails={handleOpenDetails} + /> + ); diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index 38c29fa4a..ad74b692e 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -29,10 +29,8 @@ import InfrastructureDetails from "../Pages/Infrastructure/Details/index.jsx"; import ServerUnreachable from "../Pages/ServerUnreachable.jsx"; // Incidents -import Checks from "../Pages/v1/Incidents"; +import Checks from "../Pages/Incidents/index.jsx"; import Incidents2 from "../Pages/Incidents2/index.jsx"; -import IncidentDetails from "../Pages/v1/Incidents2/Details"; - // Status pages import CreateStatus from "../Pages/StatusPage/Create/index.jsx"; @@ -136,10 +134,6 @@ const Routes = () => { path="checks/:monitorId?" element={} /> - } - /> } diff --git a/client/src/locales/en.json b/client/src/locales/en.json index ad68322ff..c543eb7e5 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -247,6 +247,7 @@ "addMonitors": "Add monitors", "window": "window", "cancel": "Cancel", + "close": "Close", "message": "Message", "low": "low", "high": "high", @@ -1203,6 +1204,23 @@ "hours": "hours", "loading": "Loading incident details...", "incidentNotFound": "Incident not found", - "incidentDetails": "Incident Details" + "incidentDetails": "INCIDENT DETAILS", + "activeIncident": "ACTIVE INCIDENT", + "resolvedIncident": "RESOLVED INCIDENT", + "overview": "OVERVIEW", + "URL": "URL", + "incidentAnalysis": "INCIDENT ANALYSIS", + "timeline": "Timeline", + "startedAt": "Started at", + "endedAt": "Ended at", + "downtime": "Downtime", + "errorDetails": "Error details", + "resolutionDetails": "Resolution details", + "statusCode": "Status code", + "resolutionMethod": "Resolution method", + "resolvedBy": "Resolved by", + "comment": "Comment", + "noCommentProvided": "No comment provided", + "loadingIncidentDetails": "Loading incident details..." } } From 63416ceb4bc85b4b577021f2556832a0631b6b49 Mon Sep 17 00:00:00 2001 From: karenvicent Date: Tue, 20 Jan 2026 20:58:35 -0500 Subject: [PATCH 3/7] fix import routes --- .../Components/IncidentDetailsModal/index.jsx | 6 +- .../IncidentTable/IncidentActionsMenu.jsx | 4 +- .../Components/IncidentTable/index.jsx | 2 +- .../ResolveIncidentDialog/index.jsx | 105 ++++++++++++++++++ client/src/Pages/Incidents/index.jsx | 2 +- 5 files changed, 112 insertions(+), 7 deletions(-) create mode 100644 client/src/Pages/Incidents/Components/ResolveIncidentDialog/index.jsx diff --git a/client/src/Pages/Incidents/Components/IncidentDetailsModal/index.jsx b/client/src/Pages/Incidents/Components/IncidentDetailsModal/index.jsx index 25af835b9..1290c69ff 100644 --- a/client/src/Pages/Incidents/Components/IncidentDetailsModal/index.jsx +++ b/client/src/Pages/Incidents/Components/IncidentDetailsModal/index.jsx @@ -2,7 +2,7 @@ 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 SummaryCard from "../SummaryCard/index.jsx"; import ResolveIncidentDialog from "../ResolveIncidentDialog/index.jsx"; // Utils @@ -14,8 +14,8 @@ 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"; +import useFetchIncidents from "../../hooks/useFetchIncidents.js"; +import useGetIncidentsDuration from "../../hooks/useGetIncidentsDuration.js"; const IncidentDetailsModal = ({ open, incidentId, onClose, onResolved }) => { const theme = useTheme(); diff --git a/client/src/Pages/Incidents/Components/IncidentTable/IncidentActionsMenu.jsx b/client/src/Pages/Incidents/Components/IncidentTable/IncidentActionsMenu.jsx index 2902da049..1e9a3be85 100644 --- a/client/src/Pages/Incidents/Components/IncidentTable/IncidentActionsMenu.jsx +++ b/client/src/Pages/Incidents/Components/IncidentTable/IncidentActionsMenu.jsx @@ -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 Settings from "../../../../assets/icons/settings-bold.svg?react"; import ResolveIncidentDialog from "../ResolveIncidentDialog/index.jsx"; import useFetchIncidents from "../../hooks/useFetchIncidents"; @@ -94,7 +94,7 @@ const IncidentActionsMenu = ({ incident, onResolve, onOpenDetails }) => { }, }} > - + {/* */}M { const uiTimezone = useSelector((state) => state.ui.timezone); diff --git a/client/src/Pages/Incidents/Components/ResolveIncidentDialog/index.jsx b/client/src/Pages/Incidents/Components/ResolveIncidentDialog/index.jsx new file mode 100644 index 000000000..e2be5fed2 --- /dev/null +++ b/client/src/Pages/Incidents/Components/ResolveIncidentDialog/index.jsx @@ -0,0 +1,105 @@ +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 ( + + + setComment(e.target.value)} + maxWidth="100%" + /> + + + + + + + ); +}; + +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; diff --git a/client/src/Pages/Incidents/index.jsx b/client/src/Pages/Incidents/index.jsx index 2721f42ad..abf24989b 100644 --- a/client/src/Pages/Incidents/index.jsx +++ b/client/src/Pages/Incidents/index.jsx @@ -157,7 +157,7 @@ const Incidents2 = () => { handleUpdateTrigger={handleUpdateTrigger} onOpenDetails={handleOpenDetails} /> - Date: Wed, 21 Jan 2026 15:33:00 -0500 Subject: [PATCH 4/7] detail modal fixes and filters options --- .../Components/IncidentDetailsModal/index.jsx | 2 - .../IncidentTable/IncidentActionsMenu.jsx | 16 +- .../Components/IncidentTable/index.jsx | 5 +- .../Components/LatestIncidentsPanel/index.jsx | 2 +- .../Components/OptionsHeader/index.jsx | 1 + .../ResolveIncidentDialog/index.jsx | 8 +- .../Incidents/hooks/useFetchIncidents.js | 26 +- client/src/Pages/Incidents/index.jsx | 8 + .../Components/IncidentDetailsModal/index.jsx | 406 ------------------ .../IncidentTable/IncidentActionsMenu.jsx | 154 ------- .../ResolveIncidentDialog/index.jsx | 105 ----- client/src/locales/en.json | 1 + 12 files changed, 49 insertions(+), 685 deletions(-) delete mode 100644 client/src/Pages/Incidents2/Components/IncidentDetailsModal/index.jsx delete mode 100644 client/src/Pages/Incidents2/Components/IncidentTable/IncidentActionsMenu.jsx delete mode 100644 client/src/Pages/Incidents2/Components/ResolveIncidentDialog/index.jsx diff --git a/client/src/Pages/Incidents/Components/IncidentDetailsModal/index.jsx b/client/src/Pages/Incidents/Components/IncidentDetailsModal/index.jsx index 1290c69ff..e4dad7c3e 100644 --- a/client/src/Pages/Incidents/Components/IncidentDetailsModal/index.jsx +++ b/client/src/Pages/Incidents/Components/IncidentDetailsModal/index.jsx @@ -168,7 +168,6 @@ const IncidentDetailsModal = ({ open, incidentId, onClose, onResolved }) => { > @@ -188,7 +187,6 @@ const IncidentDetailsModal = ({ open, incidentId, onClose, onResolved }) => { > diff --git a/client/src/Pages/Incidents/Components/IncidentTable/IncidentActionsMenu.jsx b/client/src/Pages/Incidents/Components/IncidentTable/IncidentActionsMenu.jsx index 1e9a3be85..28b3a4f52 100644 --- a/client/src/Pages/Incidents/Components/IncidentTable/IncidentActionsMenu.jsx +++ b/client/src/Pages/Incidents/Components/IncidentTable/IncidentActionsMenu.jsx @@ -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 }) => { }, }} > - {/* */}M + { @@ -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, diff --git a/client/src/Pages/Incidents/Components/IncidentTable/index.jsx b/client/src/Pages/Incidents/Components/IncidentTable/index.jsx index 699ae59c4..7d23d8ebd 100644 --- a/client/src/Pages/Incidents/Components/IncidentTable/index.jsx +++ b/client/src/Pages/Incidents/Components/IncidentTable/index.jsx @@ -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"; }, diff --git a/client/src/Pages/Incidents/Components/LatestIncidentsPanel/index.jsx b/client/src/Pages/Incidents/Components/LatestIncidentsPanel/index.jsx index d24bcd0ef..ff1ef9417 100644 --- a/client/src/Pages/Incidents/Components/LatestIncidentsPanel/index.jsx +++ b/client/src/Pages/Incidents/Components/LatestIncidentsPanel/index.jsx @@ -84,7 +84,7 @@ const LatestIncidentsPanel = ({ incidents = [], isLoading = false, error = null ) : ( {incidents.map((incident, index) => ( - + {index < incidents.length - 1 && } diff --git a/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx b/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx index 3a0ca1985..8d7439576 100644 --- a/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx +++ b/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx @@ -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 = { diff --git a/client/src/Pages/Incidents/Components/ResolveIncidentDialog/index.jsx b/client/src/Pages/Incidents/Components/ResolveIncidentDialog/index.jsx index e2be5fed2..3e868b2dc 100644 --- a/client/src/Pages/Incidents/Components/ResolveIncidentDialog/index.jsx +++ b/client/src/Pages/Incidents/Components/ResolveIncidentDialog/index.jsx @@ -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; diff --git a/client/src/Pages/Incidents/hooks/useFetchIncidents.js b/client/src/Pages/Incidents/hooks/useFetchIncidents.js index 54874bae7..3de495f8a 100644 --- a/client/src/Pages/Incidents/hooks/useFetchIncidents.js +++ b/client/src/Pages/Incidents/hooks/useFetchIncidents.js @@ -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, }; }; diff --git a/client/src/Pages/Incidents/index.jsx b/client/src/Pages/Incidents/index.jsx index abf24989b..179002e18 100644 --- a/client/src/Pages/Incidents/index.jsx +++ b/client/src/Pages/Incidents/index.jsx @@ -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(() => { diff --git a/client/src/Pages/Incidents2/Components/IncidentDetailsModal/index.jsx b/client/src/Pages/Incidents2/Components/IncidentDetailsModal/index.jsx deleted file mode 100644 index 25af835b9..000000000 --- a/client/src/Pages/Incidents2/Components/IncidentDetailsModal/index.jsx +++ /dev/null @@ -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 = {} }) => ( - - - {label} - - - - {value ?? "-"} - - - - ); - - 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 ( - - ); - } - - if (!incident) { - return ( - - ); - } - - return ( - - - - - - - - {isActive ? t("incidentsPage.active") : t("incidentsPage.resolved")} - - - - - - {t("incidentsPage.incidentItemMonitor")}: - - - {incident?.monitorId?.name || t("incidentsPage.unknownMonitor")} - - - - - {t("incidentsPage.URL")}: - - - {incident?.monitorId?.url || "-"} - - - - - - - - - - - - - - {t("incidentsPage.timeline")} - - - - - {!isActive && ( - - )} - - - - - - - - - - - {isActive - ? t("incidentsPage.errorDetails") - : t("incidentsPage.resolutionDetails")} - - - - - - - - {incident?.message || "-"} - - - {!isActive && ( - <> - - - - {incident?.resolutionType === "manual" && ( - <> - - - - - )} - - )} - - - - - - - - ); - }; - - return ( - - - {renderContent()} - - {isActive && ( - - )} - - - - - - - ); -}; - -IncidentDetailsModal.propTypes = { - open: PropTypes.bool.isRequired, - incidentId: PropTypes.string, - onClose: PropTypes.func.isRequired, - onResolved: PropTypes.func, -}; - -export default IncidentDetailsModal; diff --git a/client/src/Pages/Incidents2/Components/IncidentTable/IncidentActionsMenu.jsx b/client/src/Pages/Incidents2/Components/IncidentTable/IncidentActionsMenu.jsx deleted file mode 100644 index 2902da049..000000000 --- a/client/src/Pages/Incidents2/Components/IncidentTable/IncidentActionsMenu.jsx +++ /dev/null @@ -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 ( - <> - { - event.stopPropagation(); - openMenu(event); - }} - sx={{ - "&:focus": { - outline: "none", - }, - "& svg path": { - stroke: theme.palette.primary.contrastTextTertiary, - }, - }} - > - - - - - - {t("incidentsPage.incidentsTableActionDetails", "Details")} - - - {t("incidentsPage.incidentsTableActionGoToMonitor", "Go to monitor")} - - {isActive && ( - { - e.stopPropagation(); - openResolveDialog(e); - }} - > - {t("incidentsPage.incidentsTableActionResolveManually")} - - )} - - - - ); -}; - -IncidentActionsMenu.propTypes = { - incident: PropTypes.shape({ - _id: PropTypes.string.isRequired, - status: PropTypes.bool.isRequired, - }).isRequired, - onResolve: PropTypes.func.isRequired, - onOpenDetails: PropTypes.func, -}; - -export default IncidentActionsMenu; diff --git a/client/src/Pages/Incidents2/Components/ResolveIncidentDialog/index.jsx b/client/src/Pages/Incidents2/Components/ResolveIncidentDialog/index.jsx deleted file mode 100644 index e2be5fed2..000000000 --- a/client/src/Pages/Incidents2/Components/ResolveIncidentDialog/index.jsx +++ /dev/null @@ -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 ( - - - setComment(e.target.value)} - maxWidth="100%" - /> - - - - - - - ); -}; - -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; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 9b5ac626f..cc5789e7f 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -1202,6 +1202,7 @@ "noIncidentIdProvided": "No incident ID provided", "errorFetchingIncidentSummary": "Error fetching incident summary", "incidentsOptionsHeaderFilterManual": "Manual", + "incidentsOptionsHeaderFilterAutomatic": "Automatic", "incidentsOptionsHeaderFilterAll": "All", "incidentsOptionsHeaderFilterActive": "Active", "incidentsOptionsHeaderFilterResolved": "Resolved", From 0cf85f8b3b49e5673e01a77843bdcce849bc9227 Mon Sep 17 00:00:00 2001 From: karenvicent Date: Wed, 21 Jan 2026 15:50:46 -0500 Subject: [PATCH 5/7] add traslation --- client/src/locales/en.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/locales/en.json b/client/src/locales/en.json index cc5789e7f..4f4447b86 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -1235,6 +1235,7 @@ "resolvedBy": "Resolved by", "comment": "Comment", "noCommentProvided": "No comment provided", - "loadingIncidentDetails": "Loading incident details..." + "loadingIncidentDetails": "Loading incident details...", + "none": "None" } } From b8a5af9c10f183d84b3efc8550f5f01806f117de Mon Sep 17 00:00:00 2001 From: karenvicent Date: Wed, 21 Jan 2026 16:12:42 -0500 Subject: [PATCH 6/7] delete detail incident page --- .../src/Pages/v1/Incidents2/Details/index.jsx | 171 ------------------ 1 file changed, 171 deletions(-) delete mode 100644 client/src/Pages/v1/Incidents2/Details/index.jsx diff --git a/client/src/Pages/v1/Incidents2/Details/index.jsx b/client/src/Pages/v1/Incidents2/Details/index.jsx deleted file mode 100644 index 67388899d..000000000 --- a/client/src/Pages/v1/Incidents2/Details/index.jsx +++ /dev/null @@ -1,171 +0,0 @@ -// Components -import { Stack, Typography, Grid, Box } from "@mui/material"; -import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; -import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; -import SummaryCard from "../Components/SummaryCard"; -import useGetIncidentsDuration from "../hooks/useGetIncidentsDuration"; - -// Utils -import { useParams } from "react-router-dom"; -import { useTheme } from "@emotion/react"; -import { useTranslation } from "react-i18next"; -import useFetchIncidents from "../hooks/useFetchIncidents"; -import { useEffect, useState } from "react"; - -// Constants -const BREADCRUMBS = [ - { name: "incidents", path: "/incidents" }, - { name: "details", path: "" }, -]; - -const IncidentDetails = () => { - const theme = useTheme(); - const { t } = useTranslation(); - const { incidentId } = useParams(); - const { fetchIncidentById } = useFetchIncidents(); - const [incident, setIncident] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const isActive = incident?.status === true; - const duration = useGetIncidentsDuration(incident || null, isActive); - useEffect(() => { - const loadIncident = async () => { - if (incidentId) { - setIsLoading(true); - try { - const incidentData = await fetchIncidentById(incidentId); - console.log(incidentData); - setIncident(incidentData); - } catch (error) { - console.error("Error fetching incident:", error); - } finally { - setIsLoading(false); - } - } - }; - - loadIncident(); - }, [incidentId, fetchIncidentById]); - - if (isLoading) { - return ( - - - - - ); - } - - if (!incident) { - return ( - - - - - ); - } - - return ( - - - - {t("incidentsPage.incidentDetails", "Incident Details")} - - - - - - {duration} - - - - - {incident?.startTime} - - - {!isActive && ( - - - {incident?.endTime} - - - )} - - - - - {incident?.statusCode} - {incident?.message} - - - - - {incident?.checks.length} - - - {!isActive && ( - - - {incident?.resolutionType} - {incident?.resolutionType === "manual" && ( - - - Resolved by {incident?.resolvedBy?.email} - - {incident?.comment && ( - Comment: {incident?.comment} - )} - - )} - - - )} - - - - ); -}; - -export default IncidentDetails; From 476659441387ff12af7da0c63942626c20281c71 Mon Sep 17 00:00:00 2001 From: karenvicent Date: Thu, 22 Jan 2026 10:58:47 -0500 Subject: [PATCH 7/7] fix function duplicated and log --- .../Incidents/hooks/useFetchIncidents.js | 23 ++++--------------- client/src/Pages/Incidents/index.jsx | 10 ++++---- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/client/src/Pages/Incidents/hooks/useFetchIncidents.js b/client/src/Pages/Incidents/hooks/useFetchIncidents.js index 3de495f8a..e308c9319 100644 --- a/client/src/Pages/Incidents/hooks/useFetchIncidents.js +++ b/client/src/Pages/Incidents/hooks/useFetchIncidents.js @@ -71,22 +71,11 @@ const useFetchIncidents = () => { }, [fetchIncidentsByTeam] ); - const fetchManualResolvedIncidents = useCallback( - async (config = {}) => { + + const fetchIncidentsByResolutionType = useCallback( + async (resolutionType, config = {}) => { await fetchIncidentsByTeam({ - status: false, - resolutionType: "manual", - sortOrder: "desc", - ...config, - }); - }, - [fetchIncidentsByTeam] - ); - const fetchAutomaticResolvedIncidents = useCallback( - async (config = {}) => { - await fetchIncidentsByTeam({ - status: false, - resolutionType: "automatic", + resolutionType, sortOrder: "desc", ...config, }); @@ -175,7 +164,6 @@ const useFetchIncidents = () => { createToast({ body: t("incidentsPage.errorResolvingIncident") }); } } catch (error) { - console.log("cayo a error"); setNetworkError(true); console.error(t("incidentsPage.errorResolvingIncident"), error); const errorMessage = @@ -220,8 +208,7 @@ const useFetchIncidents = () => { fetchIncidentById, resolveIncident, fetchIncidentSummary, - fetchManualResolvedIncidents, - fetchAutomaticResolvedIncidents, + fetchIncidentsByResolutionType, }; }; diff --git a/client/src/Pages/Incidents/index.jsx b/client/src/Pages/Incidents/index.jsx index 179002e18..3937512b6 100644 --- a/client/src/Pages/Incidents/index.jsx +++ b/client/src/Pages/Incidents/index.jsx @@ -49,8 +49,7 @@ const Incidents2 = () => { fetchActiveIncidents, fetchResolvedIncidents, resolveIncident, - fetchManualResolvedIncidents, - fetchAutomaticResolvedIncidents, + fetchIncidentsByResolutionType, } = useFetchIncidents(); const networkError = monitorsNetworkError || incidentsNetworkError; @@ -75,9 +74,9 @@ const Incidents2 = () => { } else if (filter === "resolved") { fetchResolvedIncidents(config); } else if (filter === "manual") { - fetchManualResolvedIncidents(config); + fetchIncidentsByResolutionType("manual", config); } else if (filter === "automatic") { - fetchAutomaticResolvedIncidents(config); + fetchIncidentsByResolutionType("automatic", config); } else { fetchIncidents(config); } @@ -91,8 +90,7 @@ const Incidents2 = () => { fetchActiveIncidents, fetchResolvedIncidents, fetchIncidents, - fetchManualResolvedIncidents, - fetchAutomaticResolvedIncidents, + fetchIncidentsByResolutionType, ]); useEffect(() => {