diff --git a/Clients/src/presentation/components/Modals/Controlpane/NewControlPane.tsx b/Clients/src/presentation/components/Modals/Controlpane/NewControlPane.tsx index 63b6d41245..d289fcce83 100644 --- a/Clients/src/presentation/components/Modals/Controlpane/NewControlPane.tsx +++ b/Clients/src/presentation/components/Modals/Controlpane/NewControlPane.tsx @@ -11,9 +11,9 @@ import { } from "@mui/material"; import { ReactComponent as CloseIcon } from "../../../assets/icons/close.svg"; import DropDowns from "../../Inputs/Dropdowns"; -import { useState, useContext, useEffect } from "react"; +import { useState, useContext } from "react"; import AuditorFeedback from "../ComplianceFeedback/ComplianceFeedback"; -import { getEntityById, updateEntityById } from "../../../../application/repository/entity.repository"; +import { updateEntityById } from "../../../../application/repository/entity.repository"; import { Subcontrol } from "../../../../domain/types/Subcontrol"; import { Control } from "../../../../domain/types/Control"; import { FileData } from "../../../../domain/types/File"; @@ -22,7 +22,6 @@ import VWToast from "../../../vw-v2-components/Toast"; import SaveIcon from "@mui/icons-material/Save"; import VWButton from "../../../vw-v2-components/Buttons"; import { VerifyWiseContext } from "../../../../application/contexts/VerifyWise.context"; -import VWSkeleton from "../../../vw-v2-components/Skeletons"; const tabStyle = { textTransform: "none", @@ -37,7 +36,7 @@ const tabStyle = { }; const NewControlPane = ({ - _data, + data, isOpen, handleClose, controlCategoryId, @@ -45,9 +44,8 @@ const NewControlPane = ({ OnError, onComplianceUpdate, projectId, - projectFrameworkId, }: { - _data: Control; + data: Control; isOpen: boolean; handleClose: () => void; controlCategoryId?: string; @@ -55,11 +53,8 @@ const NewControlPane = ({ OnError?: () => void; onComplianceUpdate?: () => void; projectId: number; - projectFrameworkId: number; }) => { const theme = useTheme(); - const [data, setData] = useState(_data); - const [loading, setLoading] = useState(true); const [selectedTab, setSelectedTab] = useState(0); const [activeSection, setActiveSection] = useState("Overview"); const [alert, setAlert] = useState<{ @@ -77,35 +72,6 @@ const NewControlPane = ({ }>({}); const context = useContext(VerifyWiseContext); - useEffect(() => { - const fetchControls = async () => { - setLoading(true); - const response = await getEntityById({ - routeUrl: `eu-ai-act/controlById?controlId=${_data.id}&projectFrameworkId=${projectFrameworkId}`, - }); - setData(response.data); - setLoading(false); - setState({ - order_no: response.data.order_no, - id: response.data.id, - title: response.data.title, - description: response.data.description, - status: response.data.status, - approver: response.data.approver, - risk_review: response.data.risk_review, - owner: response.data.owner, - reviewer: response.data.reviewer, - implementation_details: response.data.implementation_details, - due_date: response.data.due_date, - control_category_id: response.data.control_category_id, // Added missing property - - subControls: response.data.subControls, - }) - }; - - fetchControls(); - }, [isOpen]) - const sanitizeField = (value: string | undefined | null): string => { if (!value || value === "undefined") { return ""; @@ -113,7 +79,7 @@ const NewControlPane = ({ return value; }; - const initialSubControlState = (data.subControls || []).length > 0 && data + const initialSubControlState = data .subControls!.slice() .sort((a, b) => a.order_no! - b.order_no!) .map((subControl: Subcontrol) => ({ @@ -366,16 +332,6 @@ const NewControlPane = ({ handleClose(); }; - if (loading) { - return ( - - - - - - ); - } - return ( <> {alert && ( diff --git a/Clients/src/presentation/pages/ComplianceTracker/1.0ComplianceTracker/ControlsTable.tsx b/Clients/src/presentation/pages/ComplianceTracker/1.0ComplianceTracker/ControlsTable.tsx index c9c69df0bf..4098ee9dda 100644 --- a/Clients/src/presentation/pages/ComplianceTracker/1.0ComplianceTracker/ControlsTable.tsx +++ b/Clients/src/presentation/pages/ComplianceTracker/1.0ComplianceTracker/ControlsTable.tsx @@ -50,6 +50,7 @@ const ControlsTable: React.FC = ({ const { users } = dashboardValues; const currentProjectId = projectId; const [controls, setControls] = useState([]); + const [selectedControl, setSelectedControl] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [selectedRow, setSelectedRow] = useState(null); @@ -72,7 +73,11 @@ const ControlsTable: React.FC = ({ setAlert(null); }, [currentProjectId]); - const handleRowClick = (id: number) => { + const handleRowClick = async (id: number) => { + const subControlsResponse = await getEntityById({ + routeUrl: `eu-ai-act/controlById?controlId=${id}&projectFrameworkId=${projectFrameworkId}`, + }); + setSelectedControl(subControlsResponse.data); setSelectedRow(id); setModalOpen(true); }; @@ -125,7 +130,7 @@ const ControlsTable: React.FC = ({ setLoading(true); try { const response = await getEntityById({ - routeUrl: `/eu-ai-act/controls/byControlCategoryId/${controlCategoryId}`, + routeUrl: `/eu-ai-act/controls/byControlCategoryId/${controlCategoryId}?projectFrameworkId=${projectFrameworkId}`, }); setControls(response); } catch (err) { @@ -278,7 +283,7 @@ const ControlsTable: React.FC = ({ {modalOpen && selectedRow !== null && ( c.id === selectedRow)!} + data={selectedControl!} isOpen={modalOpen} handleClose={handleCloseModal} OnSave={handleSaveSuccess} @@ -286,7 +291,6 @@ const ControlsTable: React.FC = ({ controlCategoryId={controlCategoryIndex?.toString()} onComplianceUpdate={onComplianceUpdate} projectId={currentProjectId} - projectFrameworkId={projectFrameworkId} /> )} diff --git a/Servers/controllers/eu.ctrl.ts b/Servers/controllers/eu.ctrl.ts index 5b9bc9bacc..f03528cd69 100644 --- a/Servers/controllers/eu.ctrl.ts +++ b/Servers/controllers/eu.ctrl.ts @@ -6,7 +6,7 @@ import { getAllProjectsQuery, updateProjectUpdatedByIdQuery } from "../utils/pro import { RequestWithFile, UploadedFile } from "../utils/question.utils"; import { STATUS_CODE } from "../utils/statusCode.utils"; import { QuestionStructEU } from "../models/EU/questionStructEU.model"; -import { countAnswersEUByProjectId, countSubControlsEUByProjectId, deleteAssessmentEUByProjectIdQuery, deleteComplianeEUByProjectIdQuery, getAllControlCategoriesQuery, getAllTopicsQuery, getAssessmentsEUByProjectIdQuery, getComplianceEUByProjectIdQuery, getControlByIdForProjectQuery, getControlStructByControlCategoryIdQuery, getTopicByIdForProjectQuery, updateControlEUByIdQuery, updateQuestionEUByIdQuery, updateSubcontrolEUByIdQuery } from "../utils/eu.utils"; +import { countAnswersEUByProjectId, countSubControlsEUByProjectId, deleteAssessmentEUByProjectIdQuery, deleteComplianeEUByProjectIdQuery, getAllControlCategoriesQuery, getAllTopicsQuery, getAssessmentsEUByProjectIdQuery, getComplianceEUByProjectIdQuery, getControlByIdForProjectQuery, getControlStructByControlCategoryIdForAProjectQuery, getControlStructByControlCategoryIdQuery, getTopicByIdForProjectQuery, updateControlEUByIdQuery, updateQuestionEUByIdQuery, updateSubcontrolEUByIdQuery } from "../utils/eu.utils"; import { AnswerEU } from "../models/EU/answerEU.model"; import { sequelize } from "../database/db"; import { Project, ProjectModel } from "../models/project.model"; @@ -292,8 +292,8 @@ export async function getProjectAssessmentProgress(req: Request, res: Response) const { totalAssessments, answeredAssessments } = await countAnswersEUByProjectId(projectFrameworkId); return res.status(200).json( STATUS_CODE[200]({ - totalQuestions: totalAssessments, - answeredQuestions: answeredAssessments, + totalQuestions: parseInt(totalAssessments), + answeredQuestions: parseInt(answeredAssessments), }) ); } catch (error) { @@ -313,8 +313,8 @@ export async function getProjectComplianceProgress(req: Request, res: Response) const { totalSubcontrols, doneSubcontrols } = await countSubControlsEUByProjectId(projectFrameworkId); return res.status(200).json( STATUS_CODE[200]({ - allsubControls: totalSubcontrols, - allDonesubControls: doneSubcontrols, + allsubControls: parseInt(totalSubcontrols), + allDonesubControls: parseInt(doneSubcontrols), }) ); } catch (error) { @@ -412,7 +412,8 @@ export async function getControlsByControlCategoryId( ): Promise { try { const controlCategoryId = parseInt(req.params.id); - const controls = await getControlStructByControlCategoryIdQuery(controlCategoryId); + const projectFrameworkId = parseInt(req.query.projectFrameworkId as string); + const controls = await getControlStructByControlCategoryIdForAProjectQuery(controlCategoryId, projectFrameworkId); return res.status(200).json(controls); } catch (error) { return res.status(500).json(STATUS_CODE[500]((error as Error).message)); diff --git a/Servers/utils/eu.utils.ts b/Servers/utils/eu.utils.ts index a42916646c..80b487d52a 100644 --- a/Servers/utils/eu.utils.ts +++ b/Servers/utils/eu.utils.ts @@ -122,6 +122,22 @@ export const getTopicByIdForProjectQuery = async ( return topic; } +const getSubControlsCalculations = async ( + controlId: number, +) => { + const result = await sequelize.query( + `SELECT COUNT(*) AS "numberOfSubcontrols", COUNT(CASE WHEN sc.status = 'Done' THEN 1 END) AS "numberOfDoneSubcontrols" FROM + controls_eu c JOIN subcontrols_eu sc ON c.id = sc.control_id WHERE c.id = :control_id;`, + { + replacements: { control_id: controlId }, + } + ) as [{ numberOfSubcontrols: string; numberOfDoneSubcontrols: string }[], number]; + return result[0][0] as { + numberOfSubcontrols: string; + numberOfDoneSubcontrols: string; + }; +} + export const getControlByIdForProjectQuery = async ( controlStructId: number, projectFrameworkId: number @@ -139,6 +155,9 @@ export const getControlByIdForProjectQuery = async ( for (let subControl of subControls) { (control as any).subControls.push({ ...subControl }); } + const subControlsCalculations = await getSubControlsCalculations(control.id!); + (control as any).numberOfSubcontrols = parseInt(subControlsCalculations.numberOfSubcontrols); + (control as any).numberOfDoneSubcontrols = parseInt(subControlsCalculations.numberOfDoneSubcontrols); return control; } @@ -251,6 +270,27 @@ export const getControlStructByControlCategoryIdQuery = async ( return controlsStruct; } +export const getControlStructByControlCategoryIdForAProjectQuery = async ( + controlCategoryId: number, + projectFrameworkId: number, +) => { + const controlsStruct = await sequelize.query( + `SELECT cs.*, c.owner FROM controls_struct_eu cs JOIN controls_eu c ON cs.id = c.control_meta_id + WHERE cs.control_category_id = :control_category_id AND c.projects_frameworks_id = :projects_frameworks_id;`, + { + replacements: { + control_category_id: controlCategoryId, projects_frameworks_id: projectFrameworkId + } + } + ) as [Partial[], number]; + for (let control of controlsStruct[0]) { + const subControlsCalculations = await getSubControlsCalculations(control.id!); + (control as any).numberOfSubcontrols = parseInt(subControlsCalculations.numberOfSubcontrols); + (control as any).numberOfDoneSubcontrols = parseInt(subControlsCalculations.numberOfDoneSubcontrols); + } + return controlsStruct[0]; +} + export const getControlByIdQuery = async ( controlId: number, transaction: Transaction | null = null @@ -571,6 +611,9 @@ export const updateControlEUByIdQuery = async ( return true } }).map(f => `${f} = :${f}`).join(", "); + if (!setClause) { + return control as ControlEU; + } const query = `UPDATE controls_eu SET ${setClause} WHERE id = :id RETURNING *;`; @@ -593,7 +636,7 @@ export const updateSubcontrolEUByIdQuery = async ( feedbackUploadedFiles: { id: string; fileName: string, project_id: number, uploaded_by: number, uploaded_time: Date }[] = [], deletedFiles: number[] = [], transaction: Transaction -): Promise => { +): Promise => { const files = await sequelize.query( `SELECT evidence_files, feedback_files FROM subcontrols_eu WHERE id = :id`, { @@ -649,7 +692,7 @@ export const updateSubcontrolEUByIdQuery = async ( }).join(", "); if (setClause.length === 0) { - return null; + return subcontrol as SubcontrolEU; } const query = `UPDATE subcontrols_eu SET ${setClause} WHERE id = :id RETURNING *;`;