react-questionaire
Version:
Welcome to the React Questionaire Package! 🚀
386 lines (378 loc) • 19.2 kB
JavaScript
import * as React from 'react';
import { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
import { Container, Grid, Button, Text, Box, Input, Textarea, useMediaQuery, Flex } from '@chakra-ui/react';
import { Select } from 'chakra-react-select';
import { Controller, useForm } from 'react-hook-form';
const SelectController = ({ form, questionObject, style, }) => {
const { control } = form;
return (React.createElement(Container, { sx: { ...(style ? style : { padding: 0 }) } },
React.createElement(Controller, { control: control, name: questionObject?.code, rules: {
required: "Please select at least one",
}, render: ({ field: { onChange, onBlur, value, ref } }) => (React.createElement(Select, { ...(questionObject?.multi_select ? { isMulti: true } : {}), name: questionObject.main_question, isClearable: true, ref: ref, onChange: onChange, onBlur: onBlur, value: value, options: questionObject?.options, placeholder: "Select Option", closeMenuOnSelect: questionObject?.multi_select ? false : true })) })));
};
const reactHookFormErrMsg = {
required: "This field is required.",
min: "Value below minimum allowed.",
max: "Value exceeds maximum allowed.",
pattern: "Invalid input format.",
};
function ResponseField({ qutestionObject, form, setIsCollapsed, inputSelectStyle, toggleButtonContainer, toggleButton, toggleBtnTheme, }) {
const { code, response_type, is_mandatory, regex, min, max } = qutestionObject;
const errroType = form.formState.errors[code]?.type;
const [toggle, setToggle] = useState(false);
const handleButtonClick = (value) => {
form.setValue(code, value);
setIsCollapsed(value);
setToggle(value);
if (form.formState.errors[code]) {
form.clearErrors(code);
}
if (!value) {
let keyToUnregisterAgain = Object.keys(form.getValues()).filter((quesCode) => quesCode.startsWith(code) ||
quesCode === code ||
(quesCode.includes("-") &&
quesCode.split("-")[1].startsWith(code)));
keyToUnregisterAgain.forEach((key) => {
form.unregister(key);
});
}
};
if (!["bool", "text", "number", "date", "dropdown", "textArea"].includes(response_type))
throw new TypeError("Invalid response type");
if (response_type === "bool") {
return (React.createElement(Grid, null,
React.createElement(Grid, { sx: toggleButtonContainer
? toggleButtonContainer
: {
width: "200px",
gridTemplateColumns: "1fr 1fr",
border: `1px solid #EDF2F7`,
borderRadius: "8px",
overflow: "hidden",
}, ...form.register(code, {
required: !!is_mandatory,
}) },
React.createElement(Button, { variant: "solid", onClick: () => handleButtonClick(true), sx: toggleButton
? {
...toggleButton,
color: toggle
? toggleBtnTheme?.secondary || "white"
: toggleBtnTheme?.primary || "black",
background: toggle
? toggleBtnTheme?.primary || "#319795"
: toggleBtnTheme?.secondary || "White",
}
: {
borderRadius: "8px 0 0 8px",
padding: "0 24px",
color: toggle
? toggleBtnTheme?.secondary || "white"
: toggleBtnTheme?.primary || "black",
background: toggle
? toggleBtnTheme?.primary || "#319795"
: toggleBtnTheme?.secondary || "White",
} }, "Yes"),
React.createElement(Button, { onClick: () => handleButtonClick(false), sx: toggleButton
? {
...toggleButton,
color: toggle
? toggleBtnTheme?.secondary || "white"
: toggleBtnTheme?.primary || "black",
background: toggle
? toggleBtnTheme?.primary || "#319795"
: toggleBtnTheme?.secondary || "White",
}
: {
borderRadius: "0 8px 8px 0",
padding: "0 24px",
color: toggle
? toggleBtnTheme?.primary || "black"
: toggleBtnTheme?.secondary || "white",
background: toggle
? toggleBtnTheme?.secondary || "White"
: toggleBtnTheme?.primary || "#319795",
} }, "No")),
form.formState.errors[code] && (React.createElement(Text, { fontSize: "small", color: "red" }, reactHookFormErrMsg[errroType]))));
}
if (response_type === "text") {
return (React.createElement(Box, null,
React.createElement(Input, { type: "text", placeholder: "Your answer here...", sx: {
...(inputSelectStyle
? inputSelectStyle
: {
width: "200px",
border: `1px solid #EDF2F7`,
}),
}, ...form.register(code, {
required: !!is_mandatory,
...(regex ? { pattern: new RegExp(regex) } : {}),
}) }),
form.formState.errors[code] && (React.createElement(Text, { fontSize: "small", color: "red" }, reactHookFormErrMsg[errroType]))));
}
if (response_type === "textArea") {
return (React.createElement(Box, null,
React.createElement(Textarea, { ...form.register(code, {
required: !!is_mandatory,
...(regex ? { pattern: new RegExp(regex) } : {}),
}), sx: {
...(inputSelectStyle
? inputSelectStyle
: {
width: "200px",
border: `1px solid #EDF2F7`,
}),
}, placeholder: "Your answer here..." }),
form.formState.errors[code] && (React.createElement(Text, { fontSize: "small", color: "red" }, reactHookFormErrMsg[errroType]))));
}
if (response_type === "number") {
return (React.createElement(Box, null,
React.createElement(Input, { type: "number", placeholder: "Your answer here...", sx: {
...(inputSelectStyle
? inputSelectStyle
: {
width: "200px",
border: `1px solid #EDF2F7`,
}),
}, ...form.register(code, {
required: !!is_mandatory,
min: min ? min : 0,
...(max ? { max } : {}),
}) }),
form.formState.errors[code] && (React.createElement(Text, { fontSize: "small", color: "red" }, reactHookFormErrMsg[errroType]))));
}
if (response_type === "date") {
return (React.createElement(Box, null,
React.createElement(Input, { type: "date", sx: {
...(inputSelectStyle
? inputSelectStyle
: {
width: "200px",
border: `1px solid #EDF2F7`,
}),
}, ...form.register(code, {
required: !!is_mandatory,
...(min ? { min } : {}),
...(max ? { max } : {}),
}) }),
form.formState.errors[code] && (React.createElement(Text, { fontSize: "small", color: "red", paddingLeft: "4px" }, reactHookFormErrMsg[errroType]))));
}
if (response_type === "dropdown" &&
Number(qutestionObject?.options?.length) > 0) {
return (React.createElement(Box, null,
React.createElement(SelectController, { form: form, questionObject: qutestionObject, key: qutestionObject.code, style: inputSelectStyle }),
form.formState.errors[code] && (React.createElement(Text, { fontSize: "small", color: "red" }, reactHookFormErrMsg[errroType]))));
}
else
return React.createElement(React.Fragment, null);
}
function Questions({ qutestionObject, form, isSingle, memberArray, globalStyle, currMember, questionId, }) {
const [isCollapsed, setIsCollapsed] = useState(false);
const [selectedMember, setSelectedMember] = useState([]);
const [isMobile] = useMediaQuery("(max-width: 400px)");
const handleMemberSelection = (member) => {
if (selectedMember.includes(member)) {
setSelectedMember(selectedMember.filter((m) => m !== member));
const keyToUnregisterAgain = Object.keys(form.getValues()).filter((quesCode) => quesCode.startsWith(member) || quesCode === member);
keyToUnregisterAgain.forEach((key) => {
form.unregister(key);
});
}
else {
setSelectedMember([...selectedMember, member]);
}
};
useEffect(() => {
if (!isCollapsed) {
setSelectedMember([]);
}
}, [isCollapsed]);
if (!isSingle && memberArray?.length === 0) {
throw new Error("Member array is empty");
}
return (React.createElement(Grid, { sx: globalStyle?.questionContainer
? globalStyle?.questionContainer
: {
margin: isMobile ? "8px 0" : "12px 0",
padding: "12px",
background: "white",
borderRadius: "8px",
boxShadow: "inset 0px 0px 6px rgba(0, 0, 0, 0.1)",
} },
React.createElement(Flex, { sx: {
justifyContent: "space-between",
alignItems: isMobile ? "" : "flex-start",
flexDirection: isMobile ? "column" : "row",
} },
React.createElement(Box, { sx: { width: isMobile ? "100%" : "60%" } },
React.createElement(Text, { sx: globalStyle?.question
? globalStyle?.question
: { fontSize: "medium", fontWeight: "bold" } },
questionId ? `${questionId}. ` : "",
qutestionObject.main_question),
React.createElement(Text, { sx: globalStyle?.description
? globalStyle?.description
: { fontSize: "sm", color: "gray.600" } }, qutestionObject.question_description
? qutestionObject.question_description
: ``)),
React.createElement(Flex, { sx: {
justifyContent: "end",
alignItems: "center",
margin: isMobile ? "8px" : 0,
width: isMobile ? "100%" : "40%",
} },
React.createElement(ResponseField, { qutestionObject: qutestionObject, form: form, setIsCollapsed: setIsCollapsed, inputSelectStyle: globalStyle?.inputSelectStyle, toggleButtonContainer: globalStyle?.toggleButtonContainer, toggleButton: globalStyle?.toggleButton, toggleBtnTheme: globalStyle?.toggleBtnTheme }))),
isSingle ? (React.createElement(React.Fragment, null, isCollapsed &&
qutestionObject?.sub_ques?.length &&
Number(qutestionObject?.sub_ques?.length) > 0
? qutestionObject?.sub_ques?.map((subQuestion, i) => {
return (React.createElement(Questions, { form: form, qutestionObject: {
...subQuestion,
code: currMember
? `${currMember}-${subQuestion.code}`
: subQuestion.code,
}, key: subQuestion.code, isSingle: isSingle, memberArray: memberArray, globalStyle: globalStyle, currMember: currMember ? currMember : "", questionId: questionId ? `${questionId}.${i + 1}` : "1" }));
})
: null)) : null,
!isSingle && isCollapsed ? (React.createElement(Grid, { sx: { margin: "12px 0", padding: isMobile ? 0 : "12px" } },
React.createElement(Text, { sx: { fontSize: "16px", fontWeight: "bold", color: "#333" } }, "Select Member"),
React.createElement(Flex, { sx: { gap: "8px" } }, qutestionObject?.sub_ques?.length
? memberArray?.map((mem) => {
return (React.createElement(Button, { key: mem, onClick: () => handleMemberSelection(mem), width: "fit-content", sx: {
margin: isMobile ? "8px 0" : "12px 0",
background: selectedMember.includes(mem)
? globalStyle?.toggleBtnTheme?.primary || "#319795"
: globalStyle?.toggleBtnTheme?.secondary || "white",
border: `1px solid #edf2f7`,
color: selectedMember.includes(mem)
? globalStyle?.toggleBtnTheme?.secondary || "white"
: globalStyle?.toggleBtnTheme?.primary || "black",
"&:hover": {
background: globalStyle?.toggleBtnTheme?.primary || "#319795",
color: globalStyle?.toggleBtnTheme?.secondary || "white",
},
} }, mem));
})
: null),
selectedMember.length
? selectedMember.map((mem) => {
return (React.createElement(React.Fragment, null,
React.createElement(Text, { fontSize: "large", fontWeight: "bold", paddingLeft: "12px" }, mem),
qutestionObject?.sub_ques?.length &&
Number(qutestionObject?.sub_ques?.length) > 0
? qutestionObject?.sub_ques?.map((subQuestion, i) => {
return (React.createElement(Questions, { form: form, qutestionObject: {
...subQuestion,
code: `${mem}-${subQuestion.code}`,
}, key: subQuestion.code, isSingle: true, memberArray: memberArray, globalStyle: globalStyle, currMember: mem, questionId: questionId ? `${questionId}.${i + 1}` : "1" }));
})
: null));
})
: null)) : null));
}
const useGenerateResponseHook = () => {
const generateResponse = (response, questionState) => {
const updateValues = (obj, res) => {
if (res[obj?.code]) {
obj.value = res[obj?.code];
}
if (obj.sub_ques && obj.sub_ques.length > 0) {
obj.sub_ques.forEach((sub) => updateValues(sub, res));
}
return obj;
};
const res = questionState?.map((obj) => updateValues({ ...obj }, response));
return res;
};
const restructureObject = (res) => {
const resKeys = Object.keys(res);
const uniqueMemberHash = {};
const resWithoutKeyName = [];
resKeys.forEach((key) => {
const keySplit = key.split("-");
if (keySplit.length > 1) {
uniqueMemberHash[keySplit[0]] = "";
}
else {
resWithoutKeyName.push(key);
}
});
Object.keys(uniqueMemberHash).forEach((member) => {
resWithoutKeyName.forEach((key) => {
const temp = `${member}-${key}`;
if (resKeys.some((key) => key.startsWith(temp))) {
res[temp] = res[key];
}
});
});
return { res, uniqueMemberHash };
};
const generateResponseForMember = (response, data) => {
const { res, uniqueMemberHash } = restructureObject(response);
const temp = {};
const updateValues = (obj, res, member) => {
const tempCode = `${member}-${obj?.code}`;
if (res[tempCode]) {
obj.value = res[tempCode];
}
if (obj.sub_ques && obj.sub_ques.length > 0) {
obj.sub_ques.forEach((sub) => updateValues(sub, res, member));
}
return obj;
};
Object.keys(uniqueMemberHash).forEach((member) => {
const tempData = structuredClone(data);
temp[member] = tempData?.map((obj) => updateValues({ ...obj }, res, member));
});
return temp;
};
const handleFormSubmit = (ref) => {
if (ref.current) {
ref.current.formSubmit();
}
};
return {
generateResponse,
generateResponseForMember,
handleFormSubmit,
};
};
const Questionnaire = forwardRef(({ questions, config }, ref) => {
const form = useForm({ mode: "onChange" });
const submitref = React.useRef(null);
const [questionState, setQuestionState] = React.useState();
const { generateResponse, generateResponseForMember } = useGenerateResponseHook();
const updateDataWithCode = (data, parentCode = "") => {
return data.map((obj, index) => {
const code = parentCode ? `${parentCode}_${index}` : `${index + 1}`;
const updatedObj = { ...obj, code };
if (Number(obj?.sub_ques?.length) > 0) {
updatedObj.sub_ques = updateDataWithCode(obj?.sub_ques, code);
}
return updatedObj;
});
};
useEffect(() => {
setQuestionState(updateDataWithCode(questions));
}, [questions]);
const onFormSubmit = (event) => {
if (config.isSingle) {
config.setResponse(generateResponse(event, questionState));
}
else {
config.setResponse(generateResponseForMember(event, questionState));
}
};
useImperativeHandle(ref, () => ({
SubmitForm: (e) => {
form.handleSubmit((event) => {
onFormSubmit(event);
})(e);
},
}));
return (React.createElement("form", { ref: submitref, onSubmit: onFormSubmit }, questionState?.length
? questionState?.map((questionObject, i) => {
return (React.createElement(Questions, { key: questionObject.code, qutestionObject: questionObject, form: form, isSingle: config.isSingle, memberArray: config?.memberArray, questionId: (i + 1).toString(), globalStyle: config?.globalStyle }));
})
: null));
});
export { Questionnaire };