Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions frontend/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@
"ngen.auth.register.success": "Successful registration",
"ngen.cancel": "Cancel",
"ngen.case.addto": "Add to a case",
"ngen.case.close": "Close case",
"ngen.case.close.confirm": "Are you sure you want to close this case?",
"ngen.case.close.warning": "This case has {{count}} associated events. Closing it will affect all of them.",
"ngen.case.close.no_transition": "No transition available to close the case from its current state.",
"ngen.case.close.already_closed": "The case is already closed.",
"ngen.case.close.error": "An error occurred while closing the case.",
"ngen.case.create": "Create case",
"ngen.case.error": "Unable to retrieve cases information",
"ngen.case.info.error": "Unable to retrieve case information",
Expand Down Expand Up @@ -160,6 +166,8 @@
"ngen.contactcheck.public.send": "Send Confirmation",
"ngen.contact_one": "Contact",
"ngen.contact_other": "Contacts",
"ngen.close.case.error": "The case could not be closed",
"ngen.close.case.success": "The case was successfully closed",
"ngen.create.case.error": "The case could not be created",
"ngen.create.case.error.network": "Parent network is invalid",
"ngen.create.case.error.user": "Assigned user is invalid",
Expand Down
8 changes: 8 additions & 0 deletions frontend/public/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@
"ngen.auth.register.success": "Registro exitoso",
"ngen.cancel": "Cancelar",
"ngen.case.addto": "Agregar a un caso",
"ngen.case.close": "Cerrar caso",
"ngen.case.close.confirm": "¿Está seguro de que desea cerrar este caso?",
"ngen.case.close.warning": "Este caso tiene {{count}} eventos asociados. Al cerrarlo se afectarán todos.",
"ngen.case.close.no_transition": "No hay una transición disponible para cerrar el caso desde su estado actual.",
"ngen.case.close.already_closed": "El caso ya se encuentra cerrado.",
"ngen.case.close.error": "Ocurrió un error al cerrar el caso.",
"ngen.case.create": "Crear caso",
"ngen.case.error": "No se puede recuperar la información de los casos",
"ngen.case.info.error": "No se puede recuperar la información del caso",
Expand Down Expand Up @@ -160,6 +166,8 @@
"ngen.contactcheck.public.send": "Enviar confirmación",
"ngen.contact_one": "Contacto",
"ngen.contact_other": "Contactos",
"ngen.close.case.error": "No se pudo cerrar el caso",
"ngen.close.case.success": "El caso fue cerrado exitosamente",
"ngen.create.case.error": "No se pudo crear el caso",
"ngen.create.case.error.network": "La red principal no es válida",
"ngen.create.case.error.user": "El usuario asignado no es válido",
Expand Down
19 changes: 18 additions & 1 deletion frontend/src/api/services/cases.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,21 @@ const mergeCase = (urlParent, urlChildren) => {
});
};

export { getCases, getAllCases, getOrderingCases, getCase, postCase, putCase, deleteCase, mergeCase, patchCase, getMinifiedCase };
const patchCaseState = (url, stateUrl) => {
let messageSuccess = i18next.t("ngen.close.case.success");
let messageError = i18next.t("ngen.close.case.error") + " .";
return apiInstance
.patch(url, { state: stateUrl })
.then((response) => {
setAlert(messageSuccess, "success", "case");
return response;
})
.catch((error) => {
let statusText = error.response.statusText;
messageError += statusText;
setAlert(messageError, "error", "case");
return Promise.reject(error);
});
};
Comment thread
ucabrera marked this conversation as resolved.

export { getCases, getAllCases, getOrderingCases, getCase, postCase, putCase, deleteCase, mergeCase, patchCase, getMinifiedCase, patchCaseState };
130 changes: 127 additions & 3 deletions frontend/src/views/case/components/TableCase.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react";
import { Form, Row, Spinner, Table } from "react-bootstrap";
import { Alert, Button, Form, Modal, Row, Spinner, Table } from "react-bootstrap";
import CrudButton from "components/Button/CrudButton";
import { deleteCase } from "api/services/cases";
import { deleteCase, patchCaseState } from "api/services/cases";
import ModalConfirm from "components/Modal/ModalConfirm";
import Ordering from "components/Ordering/Ordering";
import LetterFormat from "components/LetterFormat";
Expand All @@ -15,6 +15,9 @@ import PriorityComponent from "../../tanstackquery/PriorityComponent";
import StateComponent from "../../tanstackquery/StateComponent";
import EventComponent from "views/tanstackquery/EventComponent";
import TaxonomyComponent from "views/tanstackquery/TaxonomyComponent";
import { getState } from "api/services/states";
import apiInstance from "api/api";
Comment thread
Copilot marked this conversation as resolved.
import setAlert from "utils/setAlert";



Expand Down Expand Up @@ -52,6 +55,7 @@ const TableCase = ({
disableUuid,
disableDateModified,
disableEvents,
disableCloseCase,
basePath = ""
}) => {
const [url, setUrl] = useState(null);
Expand All @@ -62,14 +66,100 @@ const TableCase = ({
const [isCheckAll, setIsCheckAll] = useState(false);
const [showFullUuid, setShowFullUuid] = useState(false);
const [list, setList] = useState([]);
const [showCloseCaseModal, setShowCloseCaseModal] = useState(false);
const [closeCaseInfo, setCloseCaseInfo] = useState(null);
const [closingCase, setClosingCase] = useState(false);
const [solvedCases, setSolvedCases] = useState(new Set());

const { t } = useTranslation();

//ORDER
useEffect(() => {
setList(cases);
const stateUrlToCases = {};
cases.forEach((c) => {
if (c?.state) {
if (!stateUrlToCases[c.state]) stateUrlToCases[c.state] = [];
stateUrlToCases[c.state].push(c.url);
}
});
const stateUrls = Object.keys(stateUrlToCases);
if (!stateUrls.length) { setSolvedCases(new Set()); return; }
let cancelled = false;
Promise.all(stateUrls.map((url) => apiInstance.get(url).catch(() => null)))
.then((responses) => {
if (!cancelled) {
const solved = new Set();
responses.forEach((r, i) => {
if (r?.data?.solved) {
stateUrlToCases[stateUrls[i]].forEach((caseUrl) => solved.add(caseUrl));
}
});
setSolvedCases(solved);
}
})
.catch(() => {});
return () => { cancelled = true; };
}, [cases]);

const handleCloseCaseClick = async (caseItem) => {
setClosingCase(true);
try {
const stateResponse = await getState(caseItem.state);
const stateData = stateResponse.data;

if (stateData.solved) {
setAlert(t("ngen.case.close.already_closed"), "error", "case");
return;
}
Comment thread
Copilot marked this conversation as resolved.

let targetStateUrl = null;
for (const childUrl of (stateData.children || [])) {
const childResponse = await getState(childUrl);
if (childResponse.data.solved) {
targetStateUrl = childResponse.data.url;
break;
}
}

if (!targetStateUrl) {
setAlert(t("ngen.case.close.no_transition"), "error", "case");
return;
}
Comment thread
Copilot marked this conversation as resolved.

setCloseCaseInfo({
caseUrl: caseItem.url,
eventCount: caseItem.events?.length ?? caseItem.events_count ?? 0,
targetStateUrl
});
setShowCloseCaseModal(true);
} catch (error) {
console.error(error);
} finally {
setClosingCase(false);
}
};

const handleCloseCaseConfirm = () => {
if (!closeCaseInfo || closingCase) return;
setClosingCase(true);
patchCaseState(closeCaseInfo.caseUrl, closeCaseInfo.targetStateUrl)
.then((response) => {
setIfModify(response);
setShowCloseCaseModal(false);
setCloseCaseInfo(null);
})
.catch((error) => {
console.error(error);
const msg = error.response?.data?.detail
|| error.response?.data?.state?.[0]
|| t("ngen.case.close.error");
setAlert(msg, "error", "case");
})
.finally(() => {
setClosingCase(false);
});
};

const storageCaseUrl = (url) => {
localStorage.removeItem("case");
localStorage.removeItem("navigation");
Expand Down Expand Up @@ -326,6 +416,19 @@ const TableCase = ({
) : (
<CrudButton type="delete" onClick={() => Delete(caseItem.url)} permissions="delete_case" />
))}
{!disableCloseCase && (
<Button
type="button"
className="btn-icon btn-rounded"
variant="outline-success"
title={t("ngen.case.close")}
aria-label={t("ngen.case.close")}
disabled={closingCase || solvedCases.has(caseItem.url)}
onClick={() => handleCloseCaseClick(caseItem)}
>
Comment thread
Copilot marked this conversation as resolved.
<i className="fas fa-lock" />
</Button>
)}
</td>
</tr>
);
Expand All @@ -340,6 +443,27 @@ const TableCase = ({
onHide={() => setModalDelete(false)}
ifConfirm={() => removeCase(url)}
/>
<Modal show={showCloseCaseModal} onHide={() => setShowCloseCaseModal(false)} centered>
<Modal.Header closeButton>
<Modal.Title>{t("ngen.case.close")}</Modal.Title>
</Modal.Header>
<Modal.Body>
{closeCaseInfo?.eventCount > 1 && (
<Alert variant="warning">
{t("ngen.case.close.warning", { count: closeCaseInfo.eventCount })}
</Alert>
)}
<p>{t("ngen.case.close.confirm")}</p>
</Modal.Body>
<Modal.Footer>
<Button variant="outline-secondary" onClick={() => setShowCloseCaseModal(false)} disabled={closingCase}>
{t("ngen.cancel")}
</Button>
<Button variant="outline-danger" onClick={handleCloseCaseConfirm} disabled={closingCase}>
{t("ngen.case.close")}
</Button>
</Modal.Footer>
</Modal>
</React.Fragment>
);
};
Expand Down
Loading