diff --git a/backend/src/controllers/taskController.js b/backend/src/controllers/taskController.js
index b72de45b..f3f4f6b1 100644
--- a/backend/src/controllers/taskController.js
+++ b/backend/src/controllers/taskController.js
@@ -8,8 +8,7 @@ export default class TaskController {
return new Promise((resolve, reject) => {
if (
!req.body.task_title ||
- !req.body.task_state ||
- !req.body.task_description
+ !req.body.task_state
) {
return reject({ error: "Error with request body." });
}
@@ -90,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/backend/src/routes/userRoute.js b/backend/src/routes/userRoute.js
index 2fc61fcb..981e3ecb 100644
--- a/backend/src/routes/userRoute.js
+++ b/backend/src/routes/userRoute.js
@@ -159,6 +159,18 @@ router.post("/resetpassword", (req, res) => {
}
});
+router.post("/adminReset", authorize(true), (req, res) => {
+ // Reset Password
+ userController
+ .updateUserPassword(req, req.body.email)
+ .then((response) => {
+ res.status(200).json(response);
+ })
+ .catch((err) => {
+ res.status(404).json(err);
+ });
+});
+
router.delete("/:userId", authorize(), (req, res) => {
userController
.deleteUser(req.params.userId)
diff --git a/database/procedures/billableProc.sql b/database/procedures/billableProc.sql
index e68799ed..060bddde 100644
--- a/database/procedures/billableProc.sql
+++ b/database/procedures/billableProc.sql
@@ -76,9 +76,11 @@ END $$
CREATE PROCEDURE `load_billable` ()
BEGIN
- SELECT billable.*, t.task_id, st.fk_task_id, st.subtask_id, t.task_state, st.subtask_state FROM billable
+ SELECT billable.*, u.username, o.organization_name, t.task_id, st.fk_task_id, st.subtask_id, t.task_state, st.subtask_state FROM billable
LEFT JOIN tasks t on t.task_uuid = billable.task_uuid
LEFT JOIN subtasks st on st.subtask_uuid = billable.task_uuid
+ LEFT JOIN users u on u.user_id = billable.created_by
+ LEFT JOIN organizations o on o.organization_id = u.fk_organization_id
WHERE billable.billed = 0
AND (t.task_state != "archived" OR t.task_state IS NULL)
AND (st.subtask_state != "archived" OR st.subtask_state IS NULL);
@@ -98,11 +100,13 @@ CREATE PROCEDURE `load_billable_with_filter` (
)
BEGIN
- SELECT billable.*, t.task_id, st.fk_task_id, st.subtask_id, t.task_state, st.subtask_state FROM billable
+ SELECT billable.*, u.username, o.organization_name, t.task_id, st.fk_task_id, st.subtask_id, t.task_state, st.subtask_state FROM billable
LEFT JOIN tasks t on t.task_uuid = billable.task_uuid
LEFT JOIN subtasks st on st.subtask_uuid = billable.task_uuid
LEFT JOIN costcenter_assignments ca on ca.fk_project_id = billable.fk_project_id
LEFT JOIN project_assignments pa on pa.fk_project_id = billable.fk_project_id
+ LEFT JOIN users u on u.user_id = billable.created_by
+ LEFT JOIN organizations o on o.organization_id = u.fk_organization_id
WHERE (name = _service_name OR _service_name IS NULL OR _service_name = '')
AND (ca.fk_cost_center_id = _costcenter_id OR _costcenter_id IS NULL OR _costcenter_id = '')
AND (billable.fk_project_id = _project_id OR _project_id IS NULL OR _project_id = '')
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/.example.env b/frontend/.example.env
index 79fc323b..5703a83a 100644
--- a/frontend/.example.env
+++ b/frontend/.example.env
@@ -1,4 +1,6 @@
REACT_APP_S3_ACCESS_KEY_ID = REPLACE_VALUE_WITH_NO_QUOTATIONS
REACT_APP_S3_SECRET_ACCESS_KEY = REPLACE_VALUE_WITH_NO_QUOTATIONS
REACT_APP_S3_BUCKET = REPLACE_VALUE_WITH_NO_QUOTATIONS
-REACT_APP_BACKEND = REPLACE_VALUE_WITH_NO_QUOTATIONS
\ No newline at end of file
+REACT_APP_BACKEND = REPLACE_VALUE_WITH_NO_QUOTATIONS
+REACT_APP_FRONTEND = REPLACE_VALUE_WITH_NO_QUOTATIONS
+REACT_APP_SUMMARY_FORMAT = png
\ No newline at end of file
diff --git a/frontend/package.json b/frontend/package.json
index a2301a56..0e51fa5d 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -23,6 +23,7 @@
"date-fns": "^2.29.3",
"html2canvas": "^1.4.1",
"html2pdf.js": "^0.10.1",
+ "jspdf": "^2.5.1",
"react": "^18.2.0",
"react-apexcharts": "^1.4.0",
"react-beautiful-dnd": "^13.1.1",
diff --git a/frontend/src/components/CostEstimate/index.js b/frontend/src/components/CostEstimate/index.js
index 9ffc0f4d..9be59658 100644
--- a/frontend/src/components/CostEstimate/index.js
+++ b/frontend/src/components/CostEstimate/index.js
@@ -46,7 +46,7 @@ export const CostEstimateFull = () => {
{formResponses.map((response) => {
- if (!costEstimateMap) {
+ if (!costEstimateMap || costEstimateMap.length === 0) {
return null;
}
const cost = costEstimateMap.get(response.response) ?? 0;
diff --git a/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.css b/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.css
index 9434048f..ce487bcb 100644
--- a/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.css
+++ b/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.css
@@ -1,7 +1,7 @@
#invoice-template {
color: #000;
margin: 10px auto;
- border: 1px solid #ddd;
+ padding: 10px;
}
#invoice-header {
font-size: 12px;
diff --git a/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.js b/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.js
index 57b8cef1..e07b7dc3 100644
--- a/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.js
+++ b/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.js
@@ -102,7 +102,7 @@ const InvoiceTemplate = ({ customer, costcenterMap, invoicNum }) => {
/>
- */}
@@ -147,17 +147,22 @@ function InvoiceDetails({ billingData }) {
Description |
+ SOW |
+ User |
Quantity |
Unit Price |
Total(CAD) |
{billingData.map((invoiceItem, index) => {
+ console.log(invoiceItem);
return (
{index + 1}. {invoiceItem.name}
|
+ {`SOW-${invoiceItem.task_id ?? (invoiceItem.subtask_id ?? "?")}`} |
+ {invoiceItem.username} |
{invoiceItem.quantity} |
${(invoiceItem.cost / invoiceItem.quantity).toFixed(2)} |
${invoiceItem.cost} |
@@ -175,10 +180,6 @@ function InvoiceDetails({ billingData }) {
Total Due: |
${subtotal} |
-
- Amount Paid: |
- $0 |
-
diff --git a/frontend/src/components/InvoiceTable/index.js b/frontend/src/components/InvoiceTable/index.js
index 7c7b6edc..f8a2b14c 100644
--- a/frontend/src/components/InvoiceTable/index.js
+++ b/frontend/src/components/InvoiceTable/index.js
@@ -38,6 +38,18 @@ const InvoiceTable = () => {
key: "fk_project_id",
editable: false,
},
+ {
+ title: "Organization",
+ dataIndex: "organization_name",
+ key: "organization_name",
+ editable: false,
+ },
+ {
+ title: "User",
+ dataIndex: "username",
+ key: "username",
+ editable: false,
+ },
{
title: "Created Date",
dataIndex: "createdDate",
diff --git a/frontend/src/components/ProjectSelector/ProjectSelectorEditor/index.jsx b/frontend/src/components/ProjectSelector/ProjectSelectorEditor/index.jsx
index b08c485c..50aeb30e 100644
--- a/frontend/src/components/ProjectSelector/ProjectSelectorEditor/index.jsx
+++ b/frontend/src/components/ProjectSelector/ProjectSelectorEditor/index.jsx
@@ -26,7 +26,7 @@ function ProjectSelectorEditor({ question }) {
useEffect(() => {
console.log(question);
setQuestionNum(`Q${question.position_index}`);
- setTitle("User will select a project here...");
+ setTitle(question.question ?? "");
setNote(question.question_note);
}, [question]);
diff --git a/frontend/src/components/ProjectSelector/index.jsx b/frontend/src/components/ProjectSelector/index.jsx
index 78238376..739b4b25 100644
--- a/frontend/src/components/ProjectSelector/index.jsx
+++ b/frontend/src/components/ProjectSelector/index.jsx
@@ -9,13 +9,16 @@ import {
REMOVE_PROJECT_RESPONSE,
} from "../../redux/actions/formActions";
import { GET_PROJECT } from "../../redux/actions/billingActions";
+import { LOAD_QUESTION } from "../../redux/actions/questionActions";
function ProjectSelector({ question }) {
const dispatch = useDispatch();
const projectList = useSelector((state) => state.projectReducer.projectList);
const currentUser = useSelector((state) => state.userReducer.currentUser);
const draftList = useSelector((state) => state.formReducer.draftList);
+ const formResponses = useSelector((state) => state.formReducer.formResponses);
const [selectedValue, setSelectedValue] = useState(null);
+ const [draftDone, setDraftDone] = useState(false);
const handleChange = (event) => {
const selected = JSON.parse(event.target.value);
@@ -34,7 +37,6 @@ function ProjectSelector({ question }) {
question_info: question,
},
});
- setSelectedValue(selected);
const draftObj = {
draft_id: question.question_id + "_" + currentUser.user_id,
@@ -47,6 +49,9 @@ function ProjectSelector({ question }) {
type: ADD_DRAFT,
payload: draftObj,
});
+ setDraftDone(true);
+ dispatch({ type: LOAD_QUESTION, payload: question.fk_form_id });
+ setSelectedValue(selected);
};
useEffect(() => {
@@ -63,7 +68,6 @@ function ProjectSelector({ question }) {
(option) => option.project_id === draft.answer
);
if (value) {
- setSelectedValue(value);
dispatch({
type: ADD_RESPONSE,
payload: {
@@ -73,10 +77,20 @@ function ProjectSelector({ question }) {
question_info: question,
},
});
+ setSelectedValue(value);
+ setDraftDone(true);
}
}
}, [dispatch, draftList, projectList, question]);
+ useEffect(() => {
+ dispatch({ type: LOAD_QUESTION, payload: question.fk_form_id });
+ }, [dispatch, question.fk_form_id, draftDone]);
+
+ useEffect(() => {
+ const project = formResponses.find((response) => response?.question_info?.question_type === "project");
+ setSelectedValue(project?.question);
+ }, [formResponses, question]);
return (
@@ -86,7 +100,7 @@ function ProjectSelector({ question }) {
{question.question_note}