diff --git a/backend/src/controllers/taskController.js b/backend/src/controllers/taskController.js index d635dd8f..f3f4f6b1 100644 --- a/backend/src/controllers/taskController.js +++ b/backend/src/controllers/taskController.js @@ -89,6 +89,23 @@ export default class TaskController { }); } + updateTaskProject(req) { + return new Promise((resolve, reject) => { + const TaskModel = new Task(); + TaskModel.updateTaskProject( + req.params.taskId, + req.body.project_id, + (err, result) => { + if (err) { + reject({ error: err }); + } + this.isTaskLoaded = false; + resolve(result); + } + ); + }); + } + //add subtask given taskID saveSubtaskByTask(req) { return new Promise((resolve, reject) => { diff --git a/backend/src/models/task.js b/backend/src/models/task.js index 412c1344..f863caa5 100644 --- a/backend/src/models/task.js +++ b/backend/src/models/task.js @@ -90,7 +90,24 @@ export class Task { result(error, null); } else { result(null, { - result: `Task of ${taskId} Saved Successfully for Task ID: ${taskDescription}`, + result: `Task of ${taskId} Saved Successfully for Task ID: ${taskId}`, + }); + } + } + ); + } + + updateTaskProject(taskId, taskProject, result) { + con.query( + "CALL update_task_project(?, ?)", + [taskId, taskProject], + function (error, _) { + if (error) { + console.log("error: ", error); + result(error, null); + } else { + result(null, { + result: `Task of ${taskId} Saved Successfully for Task ID: ${taskId}`, }); } } diff --git a/backend/src/routes/taskRoute.js b/backend/src/routes/taskRoute.js index 31089518..7c1eb3db 100644 --- a/backend/src/routes/taskRoute.js +++ b/backend/src/routes/taskRoute.js @@ -55,6 +55,17 @@ router.post("/description/:taskId", authorize(), (req, res) => { }); }); +router.post("/project/:taskId", authorize(), (req, res) => { + taskController + .updateTaskProject(req) + .then((response) => { + res.status(200).json(response); + }) + .catch((err) => { + res.status(404).json(err); + }); +}); + //add subtask given task_id router.post("/addsubtask/:taskId", authorize(), (req, res) => { if (!req.body) { diff --git a/database/procedures/ticketManagement/saveProcedures.sql b/database/procedures/ticketManagement/saveProcedures.sql index b5a65f48..d75703b6 100644 --- a/database/procedures/ticketManagement/saveProcedures.sql +++ b/database/procedures/ticketManagement/saveProcedures.sql @@ -5,6 +5,7 @@ DROP procedure IF EXISTS `save_subtask`; DROP procedure IF EXISTS `update_task_status`; DROP procedure IF EXISTS `update_task_title`; DROP procedure IF EXISTS `update_task_description`; +DROP procedure IF EXISTS `update_task_project`; DROP procedure IF EXISTS `update_subtask_status`; DELIMITER $$ @@ -74,6 +75,16 @@ UPDATE `subtasks` SET `subtask_description`=`_task_description`, `subtask_update END $$ +CREATE PROCEDURE `update_task_project` ( + IN `_task_uuid` VARCHAR(50), + IN `_task_project` VARCHAR(250) + +) BEGIN +UPDATE `tasks` SET `fk_project_id`=`_task_project`, `task_updated` = NOW() WHERE `task_uuid`=`_task_uuid`; +UPDATE `billable` SET `fk_project_id`=`_task_project` WHERE `task_uuid`=`_task_uuid`; + +END $$ + CREATE PROCEDURE `save_subtask` ( IN `_subtask_uuid` VARCHAR(50), IN `_subtask_title` VARCHAR(100), diff --git a/frontend/src/components/TicketBoard/TicketInfo/index.js b/frontend/src/components/TicketBoard/TicketInfo/index.js index 1b59c844..1a688d25 100644 --- a/frontend/src/components/TicketBoard/TicketInfo/index.js +++ b/frontend/src/components/TicketBoard/TicketInfo/index.js @@ -10,6 +10,7 @@ import { SET_ACTIVE_TICKET, UNASSIGN_USER, UPDATE_TICKET_DESCRIPTION, + UPDATE_TICKET_PROJECT, UPDATE_TICKET_STATUS, UPDATE_TICKET_TITLE, } from "../../../redux/actions/ticketActions"; @@ -22,6 +23,7 @@ import Subtasks from "../Subtasks"; import ServiceList from "../ServiceList"; import { SuccessToast } from "../../Toasts"; import AWS from "aws-sdk"; +import { GET_PROJECT } from "../../../redux/actions/billingActions"; export const getColorNum = (id, colorArray) => { if (colorArray) { @@ -48,11 +50,15 @@ export const TicketInfo = () => { const currentTicketAttachments = useSelector( (state) => state.ticketReducer.currentTicketAttachments ); + const projectList = useSelector((state) => state.projectReducer.projectList); + const [currentProject, setCurrentProject] = useState(); const [assigneeAddModal, setAssigneeAddModal] = useState(false); + const [projectSwitchModal, setProjectSwitchModal] = useState(false); useEffect(() => { dispatch({ type: LOAD_EMPLOYEE }); + dispatch({ type: GET_PROJECT }); dispatch({ type: GET_TICKET_BOARD }); if (currentTicket?.id) { dispatch({ @@ -70,6 +76,13 @@ export const TicketInfo = () => { } }, [dispatch, currentTicket]); + useEffect(() => { + if (currentTicket?.id) { + const currentProj = projectList.find((proj) => proj.project_id === currentTicket.project_id); + setCurrentProject(currentProj); + } + }, [projectList, currentTicket]) + return (
{ onClick={() => { if (assigneeAddModal) { setAssigneeAddModal(false); + setProjectSwitchModal(false); } else { const inputs = document.querySelectorAll("input"); for (let i = 0; i < inputs.length; i++) { @@ -90,6 +104,7 @@ export const TicketInfo = () => { className="ticketDetail" onClick={(event) => { setAssigneeAddModal(false); + setProjectSwitchModal(false); event.stopPropagation(); }} > @@ -108,6 +123,49 @@ export const TicketInfo = () => { }); }} /> + + {projectSwitchModal ? ( +
+
Change project from {currentProject?.project_name} to:
+ {/*
NOTE: Changing projects will reset custom service costs to the default values set for the organization
*/} +
+ {projectList.map((project) => { + return ( +
{ + dispatch({ + type: UPDATE_TICKET_PROJECT, + payload: { + ticketId: currentTicket.task_uuid, + project_id: project.project_id, + }, + }); + setCurrentProject(project); + }}>{project.project_name}
+ ); + })} +
+
+ ) : null}