diff --git a/frontend/src/components/CostEstimate/index.js b/frontend/src/components/CostEstimate/index.js index 11141fda..59b5e437 100644 --- a/frontend/src/components/CostEstimate/index.js +++ b/frontend/src/components/CostEstimate/index.js @@ -23,6 +23,8 @@ export const CostEstimateCollapsed = () => { }; export const CostEstimateFull = () => { + var isUrgent = false; + var urgentMultiplier = 1.5; const dispatch = useDispatch(); const costEstimateMap = useSelector( (state) => state.costEstimateReducer.costEstimateList @@ -30,6 +32,13 @@ export const CostEstimateFull = () => { const formResponses = useSelector((state) => state.formReducer.formResponses); let costSum = 0; + formResponses.some((response) => { + if (response.question.question_type === "urgent") { + isUrgent = response.question.answer === "Yes" ? true : false; + } + return null; + }); + return (
{
{formResponses.map((response) => { - const cost = costEstimateMap.get(response.question.answer_id); + const cost = isUrgent ? urgentMultiplier * costEstimateMap.get(response.question.answer_id) : costEstimateMap.get(response.question.answer_id); let quantity = response.quantity ?? 1; costSum += cost * quantity; return ( diff --git a/frontend/src/components/DragAndDrop/component-sideview-dnd-data.js b/frontend/src/components/DragAndDrop/component-sideview-dnd-data.js index 73393aed..f9ba12ef 100644 --- a/frontend/src/components/DragAndDrop/component-sideview-dnd-data.js +++ b/frontend/src/components/DragAndDrop/component-sideview-dnd-data.js @@ -105,6 +105,15 @@ export const ProjectSelectorCard = () => { ); }; +export const UrgentCard = () => { + return ( + + Contact Information + Urgent Request + + ); +}; + export const componentsSideViewData = { components: { multi: { @@ -147,12 +156,16 @@ export const componentsSideViewData = { id: "project", component: ProjectSelectorCard, }, + urgent: { + id: "urgent", + component: UrgentCard, + }, }, sections: { "question-elements": { id: "question-elements", title: "Question Elements", - componentIds: ["multi", "single", "text", "dropdown"], + componentIds: ["multi", "single", "text", "dropdown", "urgent"], }, "layout-elements": { id: "layout-elements", diff --git a/frontend/src/components/FormBuilder/index.js b/frontend/src/components/FormBuilder/index.js index a9cc40dd..7ad323ef 100644 --- a/frontend/src/components/FormBuilder/index.js +++ b/frontend/src/components/FormBuilder/index.js @@ -1,5 +1,5 @@ import "./index.css"; -import { useSelector } from "react-redux"; +import { useSelector, useDispatch } from "react-redux"; import { appColor } from "../../constants"; import DropdownEditor from "../Dropdown/DropdownEditor"; import FileInputEditor from "../FileInput/FileInputEditor"; @@ -7,6 +7,7 @@ import TextAnswerEditor from "../TextAnswer/TextAnswerEditor"; import MultiSelectEditor from "../MultiSelect/MultiSelectEditor"; import ContactInfoEditor from "../ContactInfo/ContactInfoEditor"; import SingleSelectEditor from "../SingleSelect/SingleSelectEditor"; +import UrgentEditor from "../Urgent/UrgentEditor"; import FormTitle from "./FormTitle"; import HeadingEditor from "../Heading/HeadingEditor"; import FileDownloadEditor from "../FileDownload/FileDownloadEditor"; @@ -58,15 +59,20 @@ function renderQuestion(question) { return ; case "project": return ; + case "urgent": + return ; default: return null; } } function FormBuilder() { + const dispatch = useDispatch(); + const questionList = useSelector( (state) => state.questionReducer.questionList ); + const selectedQuestion = useSelector( (state) => state.logicReducer.currentLogicQuestion ); @@ -160,6 +166,7 @@ function FormBuilder() {
)} {provided.placeholder} + )} diff --git a/frontend/src/components/SingleSelect/index.js b/frontend/src/components/SingleSelect/index.js index a573e045..9e34466f 100644 --- a/frontend/src/components/SingleSelect/index.js +++ b/frontend/src/components/SingleSelect/index.js @@ -63,6 +63,7 @@ function SingleSelect({ question }) { question: option, }, }); + console.log(option) dispatch({ type: ADD_RESPONSE, payload: { diff --git a/frontend/src/components/Urgent/UrgentEditor/index.css b/frontend/src/components/Urgent/UrgentEditor/index.css new file mode 100644 index 00000000..db92e5dc --- /dev/null +++ b/frontend/src/components/Urgent/UrgentEditor/index.css @@ -0,0 +1,49 @@ +.question-cancel-button{ + font-size: 18px; + /* text-align: center; */ + background: transparent; + border: none; + align-self: right; +} + +.single-select-option{ + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; +} +.single-select-option-delete{ + height: 50%; +} + +.single-select-options-container{ + display: flex; + margin-top: 2%; + margin-bottom: 2%; + width: 100%; + padding-left: 5px; + padding-right: 5px; +} + +.new-question-input-container { + display: flex; + margin:none; + padding:none; + justify-content: left; + align-items: center; + width: 100%; +} + +.new-question-input { + display: flex; + border: none; + width: 100%; +} + +.new-question-input:focus{ + outline: none; +} + +.new-question-checkbox{ + width: 30px; +} \ No newline at end of file diff --git a/frontend/src/components/Urgent/UrgentEditor/index.js b/frontend/src/components/Urgent/UrgentEditor/index.js new file mode 100644 index 00000000..03809a7c --- /dev/null +++ b/frontend/src/components/Urgent/UrgentEditor/index.js @@ -0,0 +1,176 @@ +import "./index.css"; +import "../../index.css"; +import { useEffect, useState } from "react"; +import Radio from "@mui/material/Radio"; +import RadioGroup from "@mui/material/RadioGroup"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import FormControl from "@mui/material/FormControl"; +import X from "../../../assets/X.png"; +import DragDots from "../../../assets/DragDots.png"; +import { useDispatch, useSelector } from "react-redux"; +import uuid from "react-uuid"; +import { + DELETE_ANSWER, + DELETE_QUESTION, + LOAD_QUESTION, + SAVE_ANSWER, + SAVE_QUESTION, +} from "../../../redux/actions/questionActions"; +import { SET_LOGIC_QUESTION } from "../../../redux/actions/logicActions"; +import EditComponentFooter from "../../EditComponentFooter"; + +function UrgentEditor({ question }) { + const dispatch = useDispatch(); + const questionList = useSelector( + (state) => state.questionReducer.questionList + ); + + const [options, setOptions] = useState([]); + const answerList = useSelector((state) => state.questionReducer.answerList); + + const [questionNum, setQuestionNum] = useState(""); + const [title, setTitle] = useState(""); + + useEffect(() => { + setQuestionNum(`Q${question.position_index}`); + setTitle("Is this request urgent?"); + }, [question]); + + useEffect(() => { + var optionList = answerList[question.question_id ?? ""] ?? []; + optionList = optionList.sort((a, b) => { + let fa = a.answer; + let fb = b.answer; + + if (fa < fb) { + return -1; + } + if (fa > fb) { + return 1; + } + return 0; + }); + optionList = optionList.filter((option) => option !== ""); + optionList.push(""); + optionList.unshift({answer_id: uuid(), answer: "No"}); + optionList.unshift({answer_id: uuid(), answer: "Yes"}); + setOptions(optionList); + }, [answerList, question]); + + return ( +
+
+
{ + dispatch({ + type: SET_LOGIC_QUESTION, + payload: question, + }); + }} + > + {questionNum} +
+ setTitle(event.target.value)} + onBlur={() => { + dispatch({ + type: SAVE_QUESTION, + payload: { + ...question, + form_id: question.fk_form_id, + question_title: title, //text.target.value, + question_index: question.position_index, + }, + }); + dispatch({ type: LOAD_QUESTION, payload: question.fk_form_id }); + }} + /> + Delete { + questionList.forEach((questionObj) => { + if (questionObj.position_index >= question.position_index) { + questionObj.question_index = questionObj.position_index - 1; + questionObj.question_title = questionObj.question; + questionObj.form_id = questionObj.fk_form_id; + dispatch({ type: SAVE_QUESTION, payload: questionObj }); + } + }); + dispatch({ + type: DELETE_QUESTION, + payload: { + question_id: question.question_id, + }, + }); + dispatch({ type: LOAD_QUESTION, payload: question.fk_form_id }); + }} + /> +
+ {/* Copy Everything Except Content Below For Reusability */} +
+ DragDots + + + {options.map((option, index) => { + return ( +
+ } /> + { + const answerVal = e.target.value; + if (answerVal.trim() !== "") { + dispatch({ + type: SAVE_ANSWER, + payload: { + answer_id: option.answer_id ?? uuid(), + fk_question_id: question.question_id, + question_type: question.question_type, + answer: answerVal, + form_id: question.fk_form_id, + }, + }); + } else { + dispatch({ + type: DELETE_ANSWER, + payload: { + answer_id: option.answer_id, + form_id: question.fk_form_id, + }, + }); + } + setOptions([]); + }} + // If we want to have key down functionality as well: + onKeyDown={(e) => { + if (e.key === "Enter") { + e.target.blur(); + } + }} + /> +
+ ); + })} +
+
+
+ {/* Copy Everything Except Content Above For Reusability */} + +
+ ); +} + +export default UrgentEditor; diff --git a/frontend/src/components/Urgent/index.css b/frontend/src/components/Urgent/index.css new file mode 100644 index 00000000..db92e5dc --- /dev/null +++ b/frontend/src/components/Urgent/index.css @@ -0,0 +1,49 @@ +.question-cancel-button{ + font-size: 18px; + /* text-align: center; */ + background: transparent; + border: none; + align-self: right; +} + +.single-select-option{ + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; +} +.single-select-option-delete{ + height: 50%; +} + +.single-select-options-container{ + display: flex; + margin-top: 2%; + margin-bottom: 2%; + width: 100%; + padding-left: 5px; + padding-right: 5px; +} + +.new-question-input-container { + display: flex; + margin:none; + padding:none; + justify-content: left; + align-items: center; + width: 100%; +} + +.new-question-input { + display: flex; + border: none; + width: 100%; +} + +.new-question-input:focus{ + outline: none; +} + +.new-question-checkbox{ + width: 30px; +} \ No newline at end of file diff --git a/frontend/src/components/Urgent/index.js b/frontend/src/components/Urgent/index.js new file mode 100644 index 00000000..ea932ea5 --- /dev/null +++ b/frontend/src/components/Urgent/index.js @@ -0,0 +1,103 @@ +import "./index.css"; +import "../index.css"; +import { useEffect, useState } from "react"; +import Radio from "@mui/material/Radio"; +import RadioGroup from "@mui/material/RadioGroup"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import FormControl from "@mui/material/FormControl"; +import { useDispatch, useSelector } from "react-redux"; +import Divider from "../Divider"; +import { + ADD_RESPONSE, + REMOVE_SINGLE_RESPONSE, +} from "../../redux/actions/formActions"; +import uuid from "react-uuid"; + +function Urgent({ question }) { + const dispatch = useDispatch(); + const [options, setOptions] = useState([]); + const answerList = useSelector((state) => state.questionReducer.answerList); + + useEffect(() => { + var optionList = answerList[question.question_id ?? ""] ?? []; + optionList = optionList.sort((a, b) => { + let fa = a.answer; + let fb = b.answer; + if (fa < fb) { + return -1; + } + if (fa > fb) { + return 1; + } + return 0; + }); + optionList = optionList.filter((option) => option !== ""); + optionList.unshift({question_id: question.question_id, + fk_form_id: question.fk_form_id, + question_type: 'urgent', + question: 'Is this request urgent?', + answer_id: uuid(), + answer: "No"}); + optionList.unshift({question_id: question.question_id, + fk_form_id: question.fk_form_id, + question_type: 'urgent', + question: 'Is this request urgent?', + answer_id: uuid(), + answer: "Yes"}); + setOptions(optionList); + }, [answerList, question]); + + return ( +
+
+ {"Is this request urgent?"} +

{question.mandatory ? "*" : ""}

+
+
+ + + {options.map((option, index) => { + return ( +
+ { + if (e.target.checked) { + dispatch({ + type: REMOVE_SINGLE_RESPONSE, + payload: { + question: option, + }, + }); + console.log(option) + dispatch({ + type: ADD_RESPONSE, + payload: { + id: uuid(), + response: option.answer_id, + question: option, + }, + }); + } + }} + /> + } + /> +
{option.answer}
+
+ ); + })} +
+
+
+ +
+ ); +} + +export default Urgent; diff --git a/frontend/src/screens/request-form/complete-request-form.js b/frontend/src/screens/request-form/complete-request-form.js index 2afde2ed..32dfe2b6 100644 --- a/frontend/src/screens/request-form/complete-request-form.js +++ b/frontend/src/screens/request-form/complete-request-form.js @@ -5,6 +5,7 @@ import { appColor } from "../../constants"; import { LOAD_QUESTION } from "../../redux/actions/questionActions"; import { LOAD_COST } from "../../redux/actions/costActions"; import MultiSelect from "../../components/MultiSelect"; +import Urgent from "../../components/Urgent"; import SingleSelect from "../../components/SingleSelect"; import TextAnswer from "../../components/TextAnswer"; import Dropdown from "../../components/Dropdown"; @@ -57,6 +58,8 @@ function RequestForm() { return ; case "contact": return ; + case "urgent": + return ; default: return null; } diff --git a/frontend/src/screens/request-form/request-form.js b/frontend/src/screens/request-form/request-form.js index 6be7f648..eab18d5b 100644 --- a/frontend/src/screens/request-form/request-form.js +++ b/frontend/src/screens/request-form/request-form.js @@ -3,10 +3,11 @@ import { useDispatch, useSelector } from "react-redux"; import { Navigate } from "react-router-dom"; import "./request-form.css"; import { appColor } from "../../constants"; -import { LOAD_QUESTION } from "../../redux/actions/questionActions"; +import { LOAD_QUESTION} from "../../redux/actions/questionActions"; import { LOAD_COST } from "../../redux/actions/costActions"; import MultiSelect from "../../components/MultiSelect"; import SingleSelect from "../../components/SingleSelect"; +import Urgent from "../../components/Urgent"; import TextAnswer from "../../components/TextAnswer"; import Dropdown from "../../components/Dropdown"; import Heading from "../../components/Heading"; @@ -70,6 +71,8 @@ function RequestForm() { return ; case "project": return ; + case "urgent": + return ; default: return null; } @@ -78,6 +81,8 @@ function RequestForm() { // Basic Form Validation and Submit function submitForm() { var filled = true; + var isUrgent = false; + var urgentMultiplier = 1.5; questions.forEach((question) => { if ( question.mandatory && @@ -101,6 +106,12 @@ function RequestForm() { ? projectItem[0].response : "PROJECTID-A"; const billableList = []; + formResponses.some((response) => { + if (response.question.question_type === "urgent") { + isUrgent = response.question.answer === "Yes" ? true : false; + } + return null; + }); formResponses.map((response) => { const cost = costEstimateMap.get(response.question.answer_id); let quantity = response.quantity ?? 1; @@ -109,7 +120,7 @@ function RequestForm() { billableList.push({ service: service, quantity: quantity, - cost: cost * quantity, + cost: isUrgent? cost * quantity * urgentMultiplier : cost * quantity, }); } else { // This question's cost not in current stored cost estimate