From f265c1642db03a025f9ba9f4d86c1ef37ba2c7a8 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Wed, 11 Jun 2025 15:29:11 +0700 Subject: [PATCH 01/51] setup generic --- public/index.js | 58 +++++++++++++++++-- .../container/hooks/use-base-effect.js | 5 +- src/components/container/hooks/use-props.js | 6 +- src/components/index.js | 2 + src/components/status/assessment.js | 1 + src/components/survey/index.js | 2 + src/lib/recoil/assessment.js | 37 +++++++++++- src/lib/recoil/base.js | 1 + src/lib/traitify.js | 3 +- 9 files changed, 106 insertions(+), 9 deletions(-) diff --git a/public/index.js b/public/index.js index 65b00a56..153c4c39 100644 --- a/public/index.js +++ b/public/index.js @@ -63,6 +63,11 @@ function createWidget() { if(!orderID) { return; } Traitify.listener.on("Survey.start", (x) => console.log(x)); Traitify.options.orderID = orderID; + } else if(surveyType === "generic") { + Traitify.options.surveyID = cache.get("surveyID"); + Traitify.options.profileID = cache.get("profileID"); + Traitify.options.assessmentID = cache.get("assessmentID"); + Traitify.options.surveyType = surveyType; } else { const assessmentID = cache.get("assessmentID"); console.log("createWidget", {assessmentID}); @@ -144,6 +149,7 @@ function createAssessment() { if(cache.get("surveyType") === "benchmark") { return createWidget(); } if(cache.get("surveyType") === "cognitive") { return createCognitiveAssessment(); } if(cache.get("surveyType") === "order") { return createWidget(); } + if(cache.get("surveyType") === "generic") { return createGenericAssessment(); } const params = { deck_id: cache.get("deckID"), @@ -240,6 +246,24 @@ function createElement(options = {}) { return element; } +function createGenericAssessment() { + Traitify.http.authKey = "admin-secret"; + const query = Traitify.GraphQL.generic.create; + const variables = { + surveyID: cache.get("surveyID"), + profileID: cache.get("profileID") + }; + + Traitify.http.post(Traitify.GraphQL.generic.path, {query, variables}).then((response) => { + try { + const id = response.data.getOrCreateGenericAssessment.id; + } catch(error) { + console.log(error); + } + setTimeout(createWidget, 500); + }); +} + function destroyWidget() { Traitify.destroy(); } function setupTargets() { @@ -370,7 +394,8 @@ function setupDom() { {text: "Benchmark", value: "benchmark"}, {text: "Cognitive", value: "cognitive"}, {text: "Order", value: "order"}, - {text: "Personality", value: "personality"} + {text: "Personality", value: "personality"}, + {text: "Generic", value: "generic"} ], text: "Survey Type:" })); @@ -404,6 +429,9 @@ function setupDom() { row.appendChild(createOption({name: "orderID", text: "Order ID:"})); group.appendChild(row) + row = createElement({className: surveyType !== "generic" ? "hide" : "", id: "generic-options"}); + row.appendChild(createOption({name: "profileID", text: "Profile ID:"})); + group.appendChild(row); row = createElement({className: "row"}); row.appendChild(createElement({onClick: createAssessment, tag: "button", text: "Create / Load"})); group.appendChild(row); @@ -427,11 +455,32 @@ function setupCognitive() { }); } +function setupGeneric() { + Traitify.http.authKey = "admin-secret"; + const query = Traitify.GraphQL.generic.surveys; + const variables = {localeKey: cache.get("locale")}; + + Traitify.http.post(Traitify.GraphQL.generic.path, {query, variables}).then((response) => { + const options = response.data.genericAssessments + .map(({id, name}) => ({text: name, value: id})) + .sort((a, b) => a.text.localeCompare(b.text)); + + document.querySelector("#generic-options").appendChild(createOption({ + name: "surveyID", + onChange: onInputChange, + options, + text: "Survey:" + })); + }); + +} + function setupTraitify() { const environment = cache.get("environment"); if(environment === "staging") { - Traitify.http.host = "https://api.stag.awse.traitify.com"; + // Traitify.http.host = "https://api.stag.awse.traitify.com"; + Traitify.http.host = "http://localhost:4000/"; } else { Traitify.http.host = "https://api.traitify.com"; } @@ -455,7 +504,7 @@ function onSurveyTypeChange(e) { const name = e.target.name; const value = e.target.value; const assessmentID = cache.get(`${value}AssessmentID`); - const otherValues = ["benchmark", "cognitive", "order", "personality"].filter((type) => type !== value); + const otherValues = ["benchmark", "cognitive", "order", "personality", "generic"].filter((type) => type !== value); cache.set("assessmentID", assessmentID); @@ -467,5 +516,6 @@ function onSurveyTypeChange(e) { setupTraitify(); setupDom(); -setupCognitive(); +// setupCognitive(); +setupGeneric(); createWidget(); diff --git a/src/components/container/hooks/use-base-effect.js b/src/components/container/hooks/use-base-effect.js index 8c0b69f5..364cf648 100644 --- a/src/components/container/hooks/use-base-effect.js +++ b/src/components/container/hooks/use-base-effect.js @@ -7,7 +7,8 @@ import { orderIDState, orderState, packageIDState, - profileIDState + profileIDState, + surveyIDState } from "lib/recoil"; export default function useBaseEffect() { @@ -17,10 +18,12 @@ export default function useBaseEffect() { const setOrderID = useSetRecoilState(orderIDState); const setPackageID = useSetRecoilState(packageIDState); const setProfileID = useSetRecoilState(profileIDState); + const setSurveyID = useSetRecoilState(surveyIDState); useEffect(() => { setBenchmarkID(base.benchmarkID); }, [base.benchmarkID]); useEffect(() => { setOrderID(base.orderID); }, [base.orderID]); useEffect(() => { setPackageID(base.packageID); }, [base.packageID]); useEffect(() => { setProfileID(base.profileID); }, [base.profileID]); + useEffect(() => { setSurveyID(base.surveyID); }, [base.surveyID]); useDidUpdate(() => { resetOrder(); }, [base]); } diff --git a/src/components/container/hooks/use-props.js b/src/components/container/hooks/use-props.js index 74914c34..2a33e911 100644 --- a/src/components/container/hooks/use-props.js +++ b/src/components/container/hooks/use-props.js @@ -33,7 +33,8 @@ export default function useProps(props) { options = {}, orderID, packageID, - profileID + profileID, + surveyID } = props; useEffect(() => { @@ -74,7 +75,8 @@ export default function useProps(props) { if(orderID) { base.orderID = orderID; } if(packageID) { base.packageID = packageID; } if(profileID) { base.profileID = profileID; } + if(surveyID) { base.surveyID = surveyID; } setBase(base); - }, [assessmentID, benchmarkID, orderID, packageID, profileID]); + }, [assessmentID, benchmarkID, orderID, packageID, profileID, surveyID]); } diff --git a/src/components/index.js b/src/components/index.js index ec0d2e6b..3de77d8d 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -29,6 +29,7 @@ import RecommendationChart from "./results/recommendation/chart"; import Status from "./status"; import Survey from "./survey"; import CognitiveSurvey from "./survey/cognitive"; +import Generic from "./survey/generic"; import PersonalitySurvey from "./survey/personality"; export default { @@ -86,6 +87,7 @@ export default { Survey: { Cognitive: CognitiveSurvey, Container: Survey, + Generic, Personality: PersonalitySurvey } }; diff --git a/src/components/status/assessment.js b/src/components/status/assessment.js index a9cc3224..0ce1a321 100644 --- a/src/components/status/assessment.js +++ b/src/components/status/assessment.js @@ -18,6 +18,7 @@ const translations = { survey: { cognitive_assessment: "Cognitive Assessment", external_assessment: "External Assessment", + generic_assessment: "Generic Assessment", personality_assessment: "Personality Assessment" } }; diff --git a/src/components/survey/index.js b/src/components/survey/index.js index 16d3361a..a84f7f65 100644 --- a/src/components/survey/index.js +++ b/src/components/survey/index.js @@ -1,6 +1,7 @@ import Status from "components/status"; import useActive from "lib/hooks/use-active"; import Cognitive from "./cognitive"; +import Generic from "./generic"; import Personality from "./personality"; export default function Survey() { @@ -10,6 +11,7 @@ export default function Survey() { if(active.surveyType === "cognitive") { return ; } if(active.surveyType === "external") { return ; } if(active.surveyType === "personality") { return ; } + if(active.surveyType === "generic") { return ; } return null; } diff --git a/src/lib/recoil/assessment.js b/src/lib/recoil/assessment.js index 17dec745..60bea727 100644 --- a/src/lib/recoil/assessment.js +++ b/src/lib/recoil/assessment.js @@ -6,7 +6,9 @@ import { graphqlState, httpState, localeState, - safeCacheKeyState + profileIDState, + safeCacheKeyState, + surveyIDState } from "./base"; // TODO: Return error instead of null for cognitive and external @@ -102,11 +104,44 @@ export const personalityAssessmentQuery = selectorFamily({ key: "assessment/personality" }); +export const genericAssessmentQuery = selectorFamily({ + get: (id) => async({get}) => { + if(!id) { return null; } + + const cache = get(cacheState); + const cacheKey = get(safeCacheKeyState({id, type: "assessment"})); + const cached = cache.get(cacheKey); + if(cached) { return cached; } + + const GraphQL = get(graphqlState); + const http = get(httpState); + const params = { + query: GraphQL.generic.questions, + variables: {profileID: get(profileIDState), surveyID: get(surveyIDState)} + }; + + const response = await http.post({path: GraphQL.generic.path, params}); + if(response.errors) { + console.warn("generic-assessment", response.errors); /* eslint-disable-line no-console */ + return null; + } + + const questions = response.data.genericAssessmentQuestions; + if(!questions?.length) { return questions; } + + cache.set(cacheKey, questions); + + return questions; + }, + key: "assessment/generic" +}); + export const assessmentQuery = selectorFamily({ get: ({id, surveyType}) => async({get}) => { if(surveyType === "cognitive") { return get(cognitiveAssessmentQuery(id)); } if(surveyType === "external") { return get(externalAssessmentQuery(id)); } if(surveyType === "personality") { return get(personalityAssessmentQuery(id)); } + if(surveyType === "generic") { return get(genericAssessmentQuery(id)); } return null; }, diff --git a/src/lib/recoil/base.js b/src/lib/recoil/base.js index 108a4b89..e86075ba 100644 --- a/src/lib/recoil/base.js +++ b/src/lib/recoil/base.js @@ -24,6 +24,7 @@ export const optionsState = atom({default: null, key: "options"}); export const orderIDState = atom({default: null, key: "order-id"}); export const packageIDState = atom({default: null, key: "package-id"}); export const profileIDState = atom({default: null, key: "profile-id"}); +export const surveyIDState = atom({default: null, key: "survey-id"}); // NOTE: Breaking up state prevents over-triggering selectors export const activeIDState = selector({ diff --git a/src/lib/traitify.js b/src/lib/traitify.js index 2c5db73e..59004a19 100644 --- a/src/lib/traitify.js +++ b/src/lib/traitify.js @@ -29,7 +29,8 @@ export default class Traitify { "locale", "orderID", "packageID", - "profileID" + "profileID", + "surveyID" ]); return {...objects, ...props, options}; From 48dc4be560bc63efad4daadee2370de4e9589e6d Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Wed, 11 Jun 2025 15:29:43 +0700 Subject: [PATCH 02/51] add generic component graphql --- src/components/survey/generic/index.js | 9 +++++ src/lib/graphql/generic.js | 46 ++++++++++++++++++++++++++ src/lib/graphql/index.js | 2 ++ 3 files changed, 57 insertions(+) create mode 100644 src/components/survey/generic/index.js create mode 100644 src/lib/graphql/generic.js diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js new file mode 100644 index 00000000..ab8c2078 --- /dev/null +++ b/src/components/survey/generic/index.js @@ -0,0 +1,9 @@ +import {useEffect, useState} from "react"; +import useAssessment from "lib/hooks/use-assessment"; + +export default function Generic() { + const assessment = useAssessment({surveyType: "generic"}); + return ( +

Generic

+ ); +} diff --git a/src/lib/graphql/generic.js b/src/lib/graphql/generic.js new file mode 100644 index 00000000..f60bc483 --- /dev/null +++ b/src/lib/graphql/generic.js @@ -0,0 +1,46 @@ +export const surveys = ` + query($localeKey: String!) { + genericAssessments(localeKey: $localeKey) { + id + name + } + } +`; + +export const create = ` + mutation($profileID: ID!, $surveyID: ID!) { + getOrCreateGenericAssessment(profileId: $profileID, surveyId: $surveyID) { + id + surveyId + profileId + startedAt + completedAt + } + } +`; + +export const questions = ` + query($profileID: ID!, $surveyID: ID!) { + genericAssessmentQuestions(profileId: $profileID, surveyId: $surveyID) { + id + name + conclusions + instructions + instructionButton + questionSets { + text + setImage + questions { + id + text + responseOptions { + id + text + } + } + } + } + } +`; + +export const path = "/generic-assessments/graphql"; diff --git a/src/lib/graphql/index.js b/src/lib/graphql/index.js index 57df33d2..357217c4 100644 --- a/src/lib/graphql/index.js +++ b/src/lib/graphql/index.js @@ -1,6 +1,7 @@ import * as benchmark from "./benchmark"; import * as cognitive from "./cognitive"; import * as external from "./external"; +import * as generic from "./generic"; import * as guide from "./guide"; import * as order from "./order"; import * as xavier from "./xavier"; @@ -9,6 +10,7 @@ export default { benchmark, cognitive, external, + generic, guide, order, xavier From ddad0dfde5d087dc9176bd2a49aa9d6d4a7e82c8 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Fri, 13 Jun 2025 17:50:10 +0700 Subject: [PATCH 03/51] render questions --- public/index.js | 25 +++-- src/components/survey/generic/container.js | 18 ++++ src/components/survey/generic/index.js | 21 +++- src/components/survey/generic/question-set.js | 29 ++++++ src/components/survey/generic/responses.js | 29 ++++++ src/components/survey/generic/style.scss | 98 +++++++++++++++++++ 6 files changed, 208 insertions(+), 12 deletions(-) create mode 100644 src/components/survey/generic/container.js create mode 100644 src/components/survey/generic/question-set.js create mode 100644 src/components/survey/generic/responses.js create mode 100644 src/components/survey/generic/style.scss diff --git a/public/index.js b/public/index.js index 153c4c39..93975959 100644 --- a/public/index.js +++ b/public/index.js @@ -257,6 +257,7 @@ function createGenericAssessment() { Traitify.http.post(Traitify.GraphQL.generic.path, {query, variables}).then((response) => { try { const id = response.data.getOrCreateGenericAssessment.id; + cache.set("assessmentID", id); } catch(error) { console.log(error); } @@ -461,16 +462,20 @@ function setupGeneric() { const variables = {localeKey: cache.get("locale")}; Traitify.http.post(Traitify.GraphQL.generic.path, {query, variables}).then((response) => { - const options = response.data.genericAssessments - .map(({id, name}) => ({text: name, value: id})) - .sort((a, b) => a.text.localeCompare(b.text)); - - document.querySelector("#generic-options").appendChild(createOption({ - name: "surveyID", - onChange: onInputChange, - options, - text: "Survey:" - })); + try { + const options = response.data.genericAssessments + .map(({id, name}) => ({text: name, value: id})) + .sort((a, b) => a.text.localeCompare(b.text)); + + document.querySelector("#generic-options").appendChild(createOption({ + name: "surveyID", + onChange: onInputChange, + options, + text: "Survey:" + })); + } catch(error) { + console.log(error); + } }); } diff --git a/src/components/survey/generic/container.js b/src/components/survey/generic/container.js new file mode 100644 index 00000000..670c360b --- /dev/null +++ b/src/components/survey/generic/container.js @@ -0,0 +1,18 @@ +import PropTypes from "prop-types"; +import style from "./style.scss"; + +export default function Container({children, progress}) { + return ( +
+
+
+
+ {children} +
+ ); +} + +Container.propTypes = { + children: PropTypes.node.isRequired, + progress: PropTypes.number.isRequired +}; diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index ab8c2078..1c714f90 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -1,9 +1,26 @@ -import {useEffect, useState} from "react"; +import {useState} from "react"; import useAssessment from "lib/hooks/use-assessment"; +import Container from "./container"; +import QuestionSet from "./question-set"; export default function Generic() { + const [questionSetIndex, setQuestionSetIndex] = useState(0); const assessment = useAssessment({surveyType: "generic"}); + const questionSets = assessment ? assessment.questionSets : []; + const currentQuestionSet = questionSets ? questionSets[questionSetIndex] : {}; + const progress = questionSetIndex >= 0 ? (questionSetIndex / questionSets.length) * 100 : 0; + const lastSlide = questionSets[questionSetIndex - 1]; + const nextSlide = questionSets[questionSetIndex + 1]; + + const props = {progress}; + + const updateSlide = (response) => { + console.log("Updating slide with response:", response); + }; + return ( -

Generic

+ + {currentQuestionSet && } + ); } diff --git a/src/components/survey/generic/question-set.js b/src/components/survey/generic/question-set.js new file mode 100644 index 00000000..a197ced2 --- /dev/null +++ b/src/components/survey/generic/question-set.js @@ -0,0 +1,29 @@ +import PropTypes from "prop-types"; +import Responses from "./responses"; +import style from "./style.scss"; + +export default function QuestionSet({text, questions = [], setImage}) { + const questionSetClass = [style.questionSet].join(" "); + + return ( +
+ {text} +
+ {questions.map((question) => ( +
+

{question.text}

+ +
+ ))} +
+ ); +} + +QuestionSet.propTypes = { + text: PropTypes.string.isRequired, + questions: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string.isRequired, + text: PropTypes.string.isRequired + })).isRequired, + setImage: PropTypes.string.isRequired +}; diff --git a/src/components/survey/generic/responses.js b/src/components/survey/generic/responses.js new file mode 100644 index 00000000..ee16e5d2 --- /dev/null +++ b/src/components/survey/generic/responses.js @@ -0,0 +1,29 @@ +import PropTypes from "prop-types"; +import style from "./style.scss"; + +export default function Responses({responseOptions = []}) { + const buttonClass = ["traitify--response-button", style.response].join(" "); + const buttonWidth = (text) => { + console.log("Calculating button width for text:", text.length); + return text.length > 20 ? "100%" : "auto"; + }; + + return ( +
+ {responseOptions.map((option) => ( + + ))} +
+ ); +} + +Responses.propTypes = { + responseOptions: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + text: PropTypes.string.isRequired + }) + ).isRequired +}; diff --git a/src/components/survey/generic/style.scss b/src/components/survey/generic/style.scss new file mode 100644 index 00000000..4b15fe58 --- /dev/null +++ b/src/components/survey/generic/style.scss @@ -0,0 +1,98 @@ +@import "style/helpers"; + +.container { + @extend %container; + display: flex; + flex-direction: column; + margin: 0 auto; + max-height: 100vh; + max-height: 100svh; + max-width: 100%; + position: relative; + z-index: 1; + padding: $buffer-lg; + + @include min-width("xs") { + aspect-ratio: 2/1; + max-width: $breakpoint-md * 0.85; + } + + button { + border: 1px solid #DADCE0; + border-radius: $border-radius-sm * 0.5; + margin: $buffer $buffer * 0.5; + height: 48px; + } + + .progress { + border-radius: $border-radius-sm; + height: 100%; + transition: width 0.3s; + width: 0%; + + @include theme("background-color", "progress-bar"); + } + .progressBar { + border-radius: $border-radius-sm; + height: $border-width-xl; + margin-bottom: $buffer * 2; + + @include theme("background-color", "border"); + } + &:after { + top: 0; + bottom: 0; + border: $border-width solid; + border-radius: $border-radius; + content: ""; + left: 0; + position: absolute; + right: 0; + z-index: -1; + + @include theme("border-color", "border"); + } +} + +.questionSet { + width: 100%; + align-items: center; + text-align: center; + padding: $buffer; + + img { + margin-bottom: $buffer * 2; + } + + .question { + text-align: left; + } +} + +.response { + flex: 1 1 0; + letter-spacing: 2px; + line-height: 2; + padding: $buffer * 0.25 $buffer * 0.75; + text-align: center; + + @include min-width("xs") { padding: $buffer * 0.25 $buffer * 0.75; } + @include min-width("sm") { + font-size: $font-size * 1.25; + line-height: $line-height; + padding: $buffer * 0.5 $buffer * 0.75; + } + @include theme("color", "text-light"); + &:focus { + outline-offset: -1px; + outline-style: solid; + outline-width: 1px; + } + button.btnActive { + background-color: #25C9D0; + color: #FFFFFF; + } + button.btnDisabled { + &:active, &:hover { @include theme("background-color", "inactive"); } + } +} \ No newline at end of file From 07d1ad524497c508e7570e09f4cb9353c997c9f3 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Sun, 15 Jun 2025 22:14:14 +0700 Subject: [PATCH 04/51] render questions and collect answers --- src/components/survey/generic/index.js | 47 ++++++++++++++++--- src/components/survey/generic/question-set.js | 26 ++++++---- src/components/survey/generic/responses.js | 14 ++++-- src/components/survey/generic/style.scss | 27 +++++++++-- 4 files changed, 91 insertions(+), 23 deletions(-) diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index 1c714f90..7d1b8e2a 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -1,26 +1,61 @@ -import {useState} from "react"; +import {faChevronLeft} from "@fortawesome/free-solid-svg-icons"; +import {useEffect, useState} from "react"; +import Icon from "components/common/icon"; import useAssessment from "lib/hooks/use-assessment"; +import useTranslate from "lib/hooks/use-translate"; import Container from "./container"; import QuestionSet from "./question-set"; +import style from "./style.scss"; export default function Generic() { const [questionSetIndex, setQuestionSetIndex] = useState(0); + const [answers, setAnswers] = useState([]); const assessment = useAssessment({surveyType: "generic"}); const questionSets = assessment ? assessment.questionSets : []; const currentQuestionSet = questionSets ? questionSets[questionSetIndex] : {}; const progress = questionSetIndex >= 0 ? (questionSetIndex / questionSets.length) * 100 : 0; - const lastSlide = questionSets[questionSetIndex - 1]; - const nextSlide = questionSets[questionSetIndex + 1]; + const finished = questionSets.length > 0 && questionSets.length === answers.length; const props = {progress}; + const translate = useTranslate(); - const updateSlide = (response) => { - console.log("Updating slide with response:", response); + const updateSlide = (questionId, selectedOptionId) => { + const currentAnswers = answers.filter((answer) => answer.questionId !== questionId); + setAnswers([...currentAnswers, + {questionId, selectedResponseOptionId: selectedOptionId}]); + if(questionSetIndex + 1 < questionSets.length) { + setQuestionSetIndex(questionSetIndex + 1); + } }; + const back = () => { setQuestionSetIndex(questionSetIndex - 1); }; + + const onSubmit = () => { + console.log("Submitting answers:", answers); + }; + + useEffect(() => { + if(!finished) { return; } + onSubmit(); + }, [finished]); + return ( - {currentQuestionSet && } + {currentQuestionSet + && ( + + )} + + {questionSetIndex > 0 && ( + + )} ); } diff --git a/src/components/survey/generic/question-set.js b/src/components/survey/generic/question-set.js index a197ced2..c50e4415 100644 --- a/src/components/survey/generic/question-set.js +++ b/src/components/survey/generic/question-set.js @@ -2,17 +2,20 @@ import PropTypes from "prop-types"; import Responses from "./responses"; import style from "./style.scss"; -export default function QuestionSet({text, questions = [], setImage}) { +export default function QuestionSet({questionSet, updateSlide = null}) { const questionSetClass = [style.questionSet].join(" "); return (
- {text} + {questionSet.text}
- {questions.map((question) => ( + {questionSet.questions.map((question) => (

{question.text}

- + updateSlide(question.id, optionId)} + />
))}
@@ -20,10 +23,13 @@ export default function QuestionSet({text, questions = [], setImage}) { } QuestionSet.propTypes = { - text: PropTypes.string.isRequired, - questions: PropTypes.arrayOf(PropTypes.shape({ - id: PropTypes.string.isRequired, - text: PropTypes.string.isRequired - })).isRequired, - setImage: PropTypes.string.isRequired + questionSet: PropTypes.shape({ + text: PropTypes.string.isRequired, + questions: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string.isRequired, + text: PropTypes.string.isRequired + })).isRequired, + setImage: PropTypes.string.isRequired + }).isRequired, + updateSlide: PropTypes.func }; diff --git a/src/components/survey/generic/responses.js b/src/components/survey/generic/responses.js index ee16e5d2..4aa470b0 100644 --- a/src/components/survey/generic/responses.js +++ b/src/components/survey/generic/responses.js @@ -1,17 +1,22 @@ import PropTypes from "prop-types"; import style from "./style.scss"; -export default function Responses({responseOptions = []}) { +export default function Responses({responseOptions = [], updateSlide = null}) { const buttonClass = ["traitify--response-button", style.response].join(" "); const buttonWidth = (text) => { - console.log("Calculating button width for text:", text.length); return text.length > 20 ? "100%" : "auto"; }; return (
{responseOptions.map((option) => ( - ))} @@ -25,5 +30,6 @@ Responses.propTypes = { id: PropTypes.string.isRequired, text: PropTypes.string.isRequired }) - ).isRequired + ).isRequired, + updateSlide: PropTypes.func }; diff --git a/src/components/survey/generic/style.scss b/src/components/survey/generic/style.scss index 4b15fe58..2011ea4f 100644 --- a/src/components/survey/generic/style.scss +++ b/src/components/survey/generic/style.scss @@ -5,8 +5,6 @@ display: flex; flex-direction: column; margin: 0 auto; - max-height: 100vh; - max-height: 100svh; max-width: 100%; position: relative; z-index: 1; @@ -22,6 +20,27 @@ border-radius: $border-radius-sm * 0.5; margin: $buffer $buffer * 0.5; height: 48px; + + .back { + border-radius: $border-radius-sm; + bottom: 30px; + opacity: 0.7; + padding: $buffer * 0.5; + position: absolute; + width: 40px; + z-index: 1; + + @include theme("color", "text-light"); + @include theme("background-color", "fullscreen"); + &:hover{ opacity: 1; } + svg { + display: block; + height: 100%; + margin: auto; + width: auto; + } + } + .back { left: 30px; } } .progress { @@ -95,4 +114,6 @@ button.btnDisabled { &:active, &:hover { @include theme("background-color", "inactive"); } } -} \ No newline at end of file +} + +.hide { display: none; } \ No newline at end of file From d065fc44a0f0bb892ce61c8c21db64434e49f5cc Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Sun, 15 Jun 2025 22:18:06 +0700 Subject: [PATCH 05/51] refactor --- src/components/survey/generic/index.js | 1 - src/components/survey/generic/responses.js | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index 7d1b8e2a..bf4fcef0 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -5,7 +5,6 @@ import useAssessment from "lib/hooks/use-assessment"; import useTranslate from "lib/hooks/use-translate"; import Container from "./container"; import QuestionSet from "./question-set"; -import style from "./style.scss"; export default function Generic() { const [questionSetIndex, setQuestionSetIndex] = useState(0); diff --git a/src/components/survey/generic/responses.js b/src/components/survey/generic/responses.js index 4aa470b0..2807914d 100644 --- a/src/components/survey/generic/responses.js +++ b/src/components/survey/generic/responses.js @@ -3,9 +3,7 @@ import style from "./style.scss"; export default function Responses({responseOptions = [], updateSlide = null}) { const buttonClass = ["traitify--response-button", style.response].join(" "); - const buttonWidth = (text) => { - return text.length > 20 ? "100%" : "auto"; - }; + const buttonWidth = (text) => (text.length > 20 ? "100%" : "auto"); return (
From 662aecb61a20b929888cca386bbdb9a34988036d Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Mon, 16 Jun 2025 23:42:50 +0700 Subject: [PATCH 06/51] handle multi questions set --- src/components/survey/generic/index.js | 12 ++++++------ src/components/survey/generic/question-set.js | 19 ++++++++++++++++--- src/components/survey/generic/responses.js | 14 ++++++++++---- src/components/survey/generic/style.scss | 5 +++-- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index bf4fcef0..c7351836 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -11,22 +11,21 @@ export default function Generic() { const [answers, setAnswers] = useState([]); const assessment = useAssessment({surveyType: "generic"}); const questionSets = assessment ? assessment.questionSets : []; + const questionCount = questionSets.reduce((count, questionSet) => count + questionSet.questions.length, 0); const currentQuestionSet = questionSets ? questionSets[questionSetIndex] : {}; const progress = questionSetIndex >= 0 ? (questionSetIndex / questionSets.length) * 100 : 0; - const finished = questionSets.length > 0 && questionSets.length === answers.length; + const finished = questionSets.length > 0 && questionCount === answers.length; const props = {progress}; const translate = useTranslate(); - const updateSlide = (questionId, selectedOptionId) => { + const updateAnswer = (questionId, selectedOptionId) => { const currentAnswers = answers.filter((answer) => answer.questionId !== questionId); setAnswers([...currentAnswers, {questionId, selectedResponseOptionId: selectedOptionId}]); - if(questionSetIndex + 1 < questionSets.length) { - setQuestionSetIndex(questionSetIndex + 1); - } }; + const next = () => { setQuestionSetIndex(questionSetIndex + 1); }; const back = () => { setQuestionSetIndex(questionSetIndex - 1); }; const onSubmit = () => { @@ -45,7 +44,8 @@ export default function Generic() { )} diff --git a/src/components/survey/generic/question-set.js b/src/components/survey/generic/question-set.js index c50e4415..5ed9060c 100644 --- a/src/components/survey/generic/question-set.js +++ b/src/components/survey/generic/question-set.js @@ -1,9 +1,21 @@ import PropTypes from "prop-types"; +import {useEffect, useState} from "react"; import Responses from "./responses"; import style from "./style.scss"; -export default function QuestionSet({questionSet, updateSlide = null}) { +export default function QuestionSet({questionSet, updateAnswer = null, next = null}) { const questionSetClass = [style.questionSet].join(" "); + const [selectedOptions, setSelectedOptions] = useState([]); + const setFinished = questionSet.questions.length === selectedOptions.length; + const selectOption = (questionId, optionId) => { + if(!selectedOptions.includes(questionId)) setSelectedOptions([...selectedOptions, questionId]); + updateAnswer(questionId, optionId); + }; + + useEffect(() => { + if(!setFinished) return; + next(); + }, [setFinished]); return (
@@ -14,7 +26,7 @@ export default function QuestionSet({questionSet, updateSlide = null}) {

{question.text}

updateSlide(question.id, optionId)} + updateAnswer={(optionId) => selectOption(question.id, optionId)} />
))} @@ -31,5 +43,6 @@ QuestionSet.propTypes = { })).isRequired, setImage: PropTypes.string.isRequired }).isRequired, - updateSlide: PropTypes.func + updateAnswer: PropTypes.func, + next: PropTypes.func }; diff --git a/src/components/survey/generic/responses.js b/src/components/survey/generic/responses.js index 2807914d..20ddf469 100644 --- a/src/components/survey/generic/responses.js +++ b/src/components/survey/generic/responses.js @@ -1,9 +1,15 @@ import PropTypes from "prop-types"; +import {useState} from "react"; import style from "./style.scss"; -export default function Responses({responseOptions = [], updateSlide = null}) { +export default function Responses({responseOptions = [], updateAnswer = null}) { const buttonClass = ["traitify--response-button", style.response].join(" "); const buttonWidth = (text) => (text.length > 20 ? "100%" : "auto"); + const [activeButton, setActiveButton] = useState(null); + const selectOption = (optionId) => { + setActiveButton(optionId); + updateAnswer(optionId); + }; return (
@@ -11,8 +17,8 @@ export default function Responses({responseOptions = [], updateSlide = null}) { + + ); + } + return ( {currentQuestionSet @@ -50,9 +65,9 @@ export default function Generic() { )} {questionSetIndex > 0 && ( - )} diff --git a/src/components/survey/generic/style.scss b/src/components/survey/generic/style.scss index 9bb43680..98a185c7 100644 --- a/src/components/survey/generic/style.scss +++ b/src/components/survey/generic/style.scss @@ -9,6 +9,7 @@ position: relative; z-index: 1; padding: $buffer-lg; + text-align: center; @include min-width("xs") { aspect-ratio: 2/1; @@ -20,27 +21,21 @@ border-radius: $border-radius-sm * 0.5; margin: $buffer $buffer * 0.5; height: 48px; + vertical-align: middle; - .back { - border-radius: $border-radius-sm; - bottom: 30px; - opacity: 0.7; - padding: $buffer * 0.5; - position: absolute; - width: 40px; - z-index: 1; - - @include theme("color", "text-light"); - @include theme("background-color", "fullscreen"); - &:hover{ opacity: 1; } - svg { - display: block; - height: 100%; - margin: auto; - width: auto; + &.back { + padding: $buffer * 0.5 $buffer-lg * 0.5; + width: 150px; + + .icon { + margin-right: $buffer-sm; + font-size: $font-size-lg; + } } } - .back { left: 30px; } + + .markdown { + text-align: center; } .progress { @@ -108,8 +103,8 @@ outline-width: 1px; } &.btnActive { - background-color: #25C9D0; - color: #FFFFFF; + background-color: $-blue; + color: $-white; border-color: transparent; } &.btnDisabled { @@ -117,4 +112,15 @@ } } +button.btnPrimary { + background-color: $-blue; + color: $-white; + display: inline-block; + padding: $buffer-lg * .5 $buffer; + text-align: center; + border: transparent; + max-width: 110px; + margin: $buffer-lg auto; +} + .hide { display: none; } \ No newline at end of file From aeeb84b5f7e43c4950bad42913ea37198c149709 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Tue, 17 Jun 2025 19:17:08 +0700 Subject: [PATCH 08/51] add show instructions --- src/components/survey/generic/index.js | 23 +++++++++++++++ src/components/survey/generic/style.scss | 36 +++++++++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index 306a5493..51ed4212 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -2,6 +2,7 @@ import {faArrowLeft} from "@fortawesome/free-solid-svg-icons"; import {useEffect, useState} from "react"; import Icon from "components/common/icon"; import Markdown from "components/common/markdown"; +import Modal from "components/common/modal"; import useAssessment from "lib/hooks/use-assessment"; import useTranslate from "lib/hooks/use-translate"; import Container from "./container"; @@ -11,6 +12,7 @@ import style from "./style.scss"; export default function Generic() { const [questionSetIndex, setQuestionSetIndex] = useState(0); const [answers, setAnswers] = useState([]); + const [showInstructions, setShowInstructions] = useState(false); const [showConclusions, setShowConclusions] = useState(false); const assessment = useAssessment({surveyType: "generic"}); const questionSets = assessment ? assessment.questionSets : []; @@ -36,6 +38,10 @@ export default function Generic() { setShowConclusions(true); }; + useEffect(() => { + setShowInstructions(true); + }, [assessment]); + useEffect(() => { if(!finished) { return; } onSubmit(); @@ -70,6 +76,23 @@ export default function Generic() { Go Back )} + {showInstructions + && ( + setShowInstructions(false)} + className={style.instructions} + > + + {assessment.instructions} + +
+
+ + +
+
+ )} ); } diff --git a/src/components/survey/generic/style.scss b/src/components/survey/generic/style.scss index 98a185c7..4b202bdb 100644 --- a/src/components/survey/generic/style.scss +++ b/src/components/survey/generic/style.scss @@ -16,6 +16,11 @@ max-width: $breakpoint-md * 0.85; } + .modalContainer { + overflow: auto; + max-width: 600px; + } + button { border: 1px solid #DADCE0; border-radius: $border-radius-sm * 0.5; @@ -123,4 +128,33 @@ button.btnPrimary { margin: $buffer-lg auto; } -.hide { display: none; } \ No newline at end of file +.hide { display: none; } + +.grayDivider { + color: #555555; + width: 100%; + margin: $buffer * 3 auto $buffer * .5; + line-height: inherit; + clear: both; + user-select: none; + break-after: page; + border: $border-width solid #e8e8ec; + border-radius: $border-radius-sm; +} + +.footer { + display: flex; + justify-content: flex-end; + gap: $buffer * 0.5; + + .cancelBtn { + @include buttonLight("text-dark"); + @include theme("border-color", "border-light"); + border: $border-width solid; + border-radius: $border-radius-sm * 0.5; + } + + .btnPrimary { + margin: $buffer; + } +} \ No newline at end of file From 612d3fe92db130c3fb4088f2be5077531c2906c9 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Tue, 17 Jun 2025 23:13:01 +0700 Subject: [PATCH 09/51] update Modal common --- src/components/common/modal/index.js | 8 +++++--- src/components/survey/generic/index.js | 18 +++++++++++++++--- src/components/survey/generic/style.scss | 8 +++----- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/components/common/modal/index.js b/src/components/common/modal/index.js index b976a043..0ad8fbcf 100644 --- a/src/components/common/modal/index.js +++ b/src/components/common/modal/index.js @@ -4,11 +4,12 @@ import Icon from "components/common/icon"; import useTranslate from "lib/hooks/use-translate"; import style from "./style.scss"; -export default function Modal({children, onClose, title}) { +export default function Modal({children, onClose, title, containerClass = ""}) { const translate = useTranslate(); + const sectionClass = [style.modalContainer, containerClass].join(" "); return (
-
+
{title}
@@ -35,5 +36,6 @@ export default function Modal({children, onClose, title}) { Modal.propTypes = { children: PropTypes.node.isRequired, onClose: PropTypes.func.isRequired, - title: PropTypes.string.isRequired + title: PropTypes.string.isRequired, + containerClass: PropTypes.string }; diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index 51ed4212..bd0c3aeb 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -81,15 +81,27 @@ export default function Generic() { setShowInstructions(false)} - className={style.instructions} + containerClass={style.modalContainer} > {assessment.instructions}
- - + +
)} diff --git a/src/components/survey/generic/style.scss b/src/components/survey/generic/style.scss index 4b202bdb..c7c6b4a3 100644 --- a/src/components/survey/generic/style.scss +++ b/src/components/survey/generic/style.scss @@ -112,9 +112,6 @@ color: $-white; border-color: transparent; } - &.btnDisabled { - &:active, &:hover { @include theme("background-color", "inactive"); } - } } button.btnPrimary { @@ -145,16 +142,17 @@ button.btnPrimary { .footer { display: flex; justify-content: flex-end; - gap: $buffer * 0.5; + gap: $buffer * 0.25; .cancelBtn { @include buttonLight("text-dark"); @include theme("border-color", "border-light"); border: $border-width solid; border-radius: $border-radius-sm * 0.5; + margin: $buffer * 0.5; } .btnPrimary { - margin: $buffer; + margin: $buffer * 0.5; } } \ No newline at end of file From 0ca367b19b17de8ffc025783855588bf7453736b Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Fri, 20 Jun 2025 11:19:23 +0700 Subject: [PATCH 10/51] submit answers --- src/components/survey/generic/index.js | 25 ++++++++++++++++++++++--- src/lib/graphql/generic.js | 12 ++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index bd0c3aeb..4fbf11be 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -4,6 +4,8 @@ import Icon from "components/common/icon"; import Markdown from "components/common/markdown"; import Modal from "components/common/modal"; import useAssessment from "lib/hooks/use-assessment"; +import useGraphql from "lib/hooks/use-graphql"; +import useHttp from "lib/hooks/use-http"; import useTranslate from "lib/hooks/use-translate"; import Container from "./container"; import QuestionSet from "./question-set"; @@ -14,6 +16,7 @@ export default function Generic() { const [answers, setAnswers] = useState([]); const [showInstructions, setShowInstructions] = useState(false); const [showConclusions, setShowConclusions] = useState(false); + const assessment = useAssessment({surveyType: "generic"}); const questionSets = assessment ? assessment.questionSets : []; const questionCount = questionSets.reduce((count, set) => count + set.questions.length, 0); @@ -21,8 +24,10 @@ export default function Generic() { const progress = questionSetIndex >= 0 ? (questionSetIndex / questionSets.length) * 100 : 0; const finished = questionSets.length > 0 && questionCount === answers.length; - const props = {progress}; + const graphQL = useGraphql(); + const http = useHttp(); const translate = useTranslate(); + const props = {progress}; const updateAnswer = (questionId, selectedOptionId) => { const currentAnswers = answers.filter((answer) => answer.questionId !== questionId); @@ -34,8 +39,22 @@ export default function Generic() { const back = () => { setQuestionSetIndex(questionSetIndex - 1); }; const onSubmit = () => { - console.log("Submitting answers:", answers); - setShowConclusions(true); + const query = graphQL.generic.update; + const variables = { + surveyID: localStorage.getItem("surveyID"), + profileID: localStorage.getItem("profileID"), + answers + }; + + http.post(graphQL.generic.path, {query, variables}).then(({data, errors}) => { + if(!errors && data.submitGenericAssessmentAnswers.success) { + setShowConclusions(true); + } else { + console.warn(errors || data); // eslint-disable-line no-console + + // setTimeout(() => setSubmitAttempts((x) => x + 1), 2000); + } + }); }; useEffect(() => { diff --git a/src/lib/graphql/generic.js b/src/lib/graphql/generic.js index f60bc483..fd9c2ba3 100644 --- a/src/lib/graphql/generic.js +++ b/src/lib/graphql/generic.js @@ -43,4 +43,16 @@ export const questions = ` } `; +export const update = ` + mutation($profileID: ID!, $surveyID: ID!, $answers: [Answers]!) { + submitGenericAssessmentAnswers(profileId: $profileID, surveyId: $surveyID, answers: $answers) { + id + surveyId + profileId + startedAt + completedAt + } + } +`; + export const path = "/generic-assessments/graphql"; From aa365bb19772d77a3cb084ef7e6b273f8c469d72 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Fri, 20 Jun 2025 17:20:41 +0700 Subject: [PATCH 11/51] use assessmentID --- src/components/container/hooks/use-base-effect.js | 5 +---- src/components/container/hooks/use-props.js | 6 ++---- src/components/status/assessment.js | 15 --------------- src/components/survey/generic/index.js | 5 ++--- src/lib/graphql/generic.js | 15 +++++++++++---- src/lib/recoil/assessment.js | 14 ++++++-------- src/lib/recoil/base.js | 1 - 7 files changed, 22 insertions(+), 39 deletions(-) diff --git a/src/components/container/hooks/use-base-effect.js b/src/components/container/hooks/use-base-effect.js index 364cf648..8c0b69f5 100644 --- a/src/components/container/hooks/use-base-effect.js +++ b/src/components/container/hooks/use-base-effect.js @@ -7,8 +7,7 @@ import { orderIDState, orderState, packageIDState, - profileIDState, - surveyIDState + profileIDState } from "lib/recoil"; export default function useBaseEffect() { @@ -18,12 +17,10 @@ export default function useBaseEffect() { const setOrderID = useSetRecoilState(orderIDState); const setPackageID = useSetRecoilState(packageIDState); const setProfileID = useSetRecoilState(profileIDState); - const setSurveyID = useSetRecoilState(surveyIDState); useEffect(() => { setBenchmarkID(base.benchmarkID); }, [base.benchmarkID]); useEffect(() => { setOrderID(base.orderID); }, [base.orderID]); useEffect(() => { setPackageID(base.packageID); }, [base.packageID]); useEffect(() => { setProfileID(base.profileID); }, [base.profileID]); - useEffect(() => { setSurveyID(base.surveyID); }, [base.surveyID]); useDidUpdate(() => { resetOrder(); }, [base]); } diff --git a/src/components/container/hooks/use-props.js b/src/components/container/hooks/use-props.js index 59b18580..c375fb37 100644 --- a/src/components/container/hooks/use-props.js +++ b/src/components/container/hooks/use-props.js @@ -33,8 +33,7 @@ export default function useProps(props) { options = {}, orderID, packageID, - profileID, - surveyID + profileID } = props; useEffect(() => { @@ -78,8 +77,7 @@ export default function useProps(props) { if(orderID) { base.orderID = orderID; } if(packageID) { base.packageID = packageID; } if(profileID) { base.profileID = profileID; } - if(surveyID) { base.surveyID = surveyID; } setBase(base); - }, [assessmentID, benchmarkID, orderID, packageID, profileID, surveyID]); + }, [assessmentID, benchmarkID, orderID, packageID, profileID]); } diff --git a/src/components/status/assessment.js b/src/components/status/assessment.js index 42c5ee58..a6a9d366 100644 --- a/src/components/status/assessment.js +++ b/src/components/status/assessment.js @@ -9,21 +9,6 @@ import useTranslate from "lib/hooks/use-translate"; import {activeState} from "lib/recoil"; import style from "./style.scss"; -// TODO: Extract text to translate -const translations = { - complete: "Complete", - loading: "Loading", // Already translated - status: { - start: "Start Assessment" - }, - survey: { - cognitive_assessment: "Cognitive Assessment", - external_assessment: "External Assessment", - generic_assessment: "Generic Assessment", - personality_assessment: "Personality Assessment" - } -}; - function Button({assessment}) { const listener = useListener(); const options = useOption("status") || {}; diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index 4fbf11be..8e59d909 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -41,13 +41,12 @@ export default function Generic() { const onSubmit = () => { const query = graphQL.generic.update; const variables = { - surveyID: localStorage.getItem("surveyID"), - profileID: localStorage.getItem("profileID"), + assessmentID: assessment.assessment.id, answers }; http.post(graphQL.generic.path, {query, variables}).then(({data, errors}) => { - if(!errors && data.submitGenericAssessmentAnswers.success) { + if(!errors && data.submitGenericAssessmentAnswers) { setShowConclusions(true); } else { console.warn(errors || data); // eslint-disable-line no-console diff --git a/src/lib/graphql/generic.js b/src/lib/graphql/generic.js index fd9c2ba3..deee00c9 100644 --- a/src/lib/graphql/generic.js +++ b/src/lib/graphql/generic.js @@ -20,8 +20,8 @@ export const create = ` `; export const questions = ` - query($profileID: ID!, $surveyID: ID!) { - genericAssessmentQuestions(profileId: $profileID, surveyId: $surveyID) { + query($assessmentID: ID!) { + genericAssessmentQuestions(assessmentId: $assessmentID) { id name conclusions @@ -39,13 +39,20 @@ export const questions = ` } } } + assessment { + id + surveyId + profileId + startedAt + completedAt + } } } `; export const update = ` - mutation($profileID: ID!, $surveyID: ID!, $answers: [Answers]!) { - submitGenericAssessmentAnswers(profileId: $profileID, surveyId: $surveyID, answers: $answers) { + mutation($assessmentID: ID!, $answers: [Answers]!) { + submitGenericAssessmentAnswers(assessmentId: $assessmentID, answers: $answers) { id surveyId profileId diff --git a/src/lib/recoil/assessment.js b/src/lib/recoil/assessment.js index 60bea727..8d6aa67c 100644 --- a/src/lib/recoil/assessment.js +++ b/src/lib/recoil/assessment.js @@ -6,9 +6,7 @@ import { graphqlState, httpState, localeState, - profileIDState, - safeCacheKeyState, - surveyIDState + safeCacheKeyState } from "./base"; // TODO: Return error instead of null for cognitive and external @@ -117,7 +115,7 @@ export const genericAssessmentQuery = selectorFamily({ const http = get(httpState); const params = { query: GraphQL.generic.questions, - variables: {profileID: get(profileIDState), surveyID: get(surveyIDState)} + variables: {assessmentID: id} }; const response = await http.post({path: GraphQL.generic.path, params}); @@ -126,12 +124,12 @@ export const genericAssessmentQuery = selectorFamily({ return null; } - const questions = response.data.genericAssessmentQuestions; - if(!questions?.length) { return questions; } + const survey = response.data.genericAssessmentQuestions; + if(!survey?.length || !survey?.assessment.completedAt) { return survey; } - cache.set(cacheKey, questions); + cache.set(cacheKey, survey); - return questions; + return survey; }, key: "assessment/generic" }); diff --git a/src/lib/recoil/base.js b/src/lib/recoil/base.js index e86075ba..108a4b89 100644 --- a/src/lib/recoil/base.js +++ b/src/lib/recoil/base.js @@ -24,7 +24,6 @@ export const optionsState = atom({default: null, key: "options"}); export const orderIDState = atom({default: null, key: "order-id"}); export const packageIDState = atom({default: null, key: "package-id"}); export const profileIDState = atom({default: null, key: "profile-id"}); -export const surveyIDState = atom({default: null, key: "survey-id"}); // NOTE: Breaking up state prevents over-triggering selectors export const activeIDState = selector({ From 2874102b1581d39803bbbf4b2461161214a16e94 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Fri, 20 Jun 2025 17:21:07 +0700 Subject: [PATCH 12/51] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 89997123..446aa2de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "traitify-widgets", - "version": "3.7.1", + "version": "3.8.0", "description": "Traitiy Widgets", "repository": { "type": "git", From 8a997c1ded9c4ea6953ec2460e795c93dea487eb Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Fri, 20 Jun 2025 17:38:44 +0700 Subject: [PATCH 13/51] refactor --- public/index.js | 5 ----- src/lib/traitify.js | 3 +-- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/public/index.js b/public/index.js index 11688202..e2907913 100644 --- a/public/index.js +++ b/public/index.js @@ -63,11 +63,6 @@ function createWidget() { if(!orderID) { return; } Traitify.listener.on("Survey.start", (x) => console.log(x)); Traitify.options.orderID = orderID; - } else if(surveyType === "generic") { - Traitify.options.surveyID = cache.get("surveyID"); - Traitify.options.profileID = cache.get("profileID"); - Traitify.options.assessmentID = cache.get("assessmentID"); - Traitify.options.surveyType = surveyType; } else { const assessmentID = cache.get("assessmentID"); console.log("createWidget", {assessmentID}); diff --git a/src/lib/traitify.js b/src/lib/traitify.js index 59004a19..2c5db73e 100644 --- a/src/lib/traitify.js +++ b/src/lib/traitify.js @@ -29,8 +29,7 @@ export default class Traitify { "locale", "orderID", "packageID", - "profileID", - "surveyID" + "profileID" ]); return {...objects, ...props, options}; From a17c8c47cc016cc4efdc9445f7cc55cb3d43c884 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Tue, 24 Jun 2025 17:48:06 +0700 Subject: [PATCH 14/51] update graphql responses --- public/index.js | 2 +- src/components/survey/generic/index.js | 10 +++---- src/lib/graphql/generic.js | 38 +++++++++++++------------- src/lib/recoil/assessment.js | 8 +++--- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/public/index.js b/public/index.js index e2907913..09ad1d74 100644 --- a/public/index.js +++ b/public/index.js @@ -458,7 +458,7 @@ function setupGeneric() { Traitify.http.post(Traitify.GraphQL.generic.path, {query, variables}).then((response) => { try { - const options = response.data.genericAssessments + const options = response.data.genericSurveys .map(({id, name}) => ({text: name, value: id})) .sort((a, b) => a.text.localeCompare(b.text)); diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index 8e59d909..2af44b0a 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -18,7 +18,7 @@ export default function Generic() { const [showConclusions, setShowConclusions] = useState(false); const assessment = useAssessment({surveyType: "generic"}); - const questionSets = assessment ? assessment.questionSets : []; + const questionSets = assessment ? assessment.survey.questionSets : []; const questionCount = questionSets.reduce((count, set) => count + set.questions.length, 0); const currentQuestionSet = questionSets ? questionSets[questionSetIndex] : {}; const progress = questionSetIndex >= 0 ? (questionSetIndex / questionSets.length) * 100 : 0; @@ -41,7 +41,7 @@ export default function Generic() { const onSubmit = () => { const query = graphQL.generic.update; const variables = { - assessmentID: assessment.assessment.id, + assessmentID: assessment.id, answers }; @@ -69,7 +69,7 @@ export default function Generic() { return ( - {assessment.conclusions} + {assessment.survey.conclusions} @@ -102,7 +102,7 @@ export default function Generic() { containerClass={style.modalContainer} > - {assessment.instructions} + {assessment.survey.instructions}
@@ -118,7 +118,7 @@ export default function Generic() { className={style.btnPrimary} onClick={() => setShowInstructions(false)} > - {assessment.instructionButton} + {assessment.survey.instructionButton}
diff --git a/src/lib/graphql/generic.js b/src/lib/graphql/generic.js index deee00c9..672325ff 100644 --- a/src/lib/graphql/generic.js +++ b/src/lib/graphql/generic.js @@ -1,6 +1,6 @@ export const surveys = ` query($localeKey: String!) { - genericAssessments(localeKey: $localeKey) { + genericSurveys(localeKey: $localeKey) { id name } @@ -21,31 +21,31 @@ export const create = ` export const questions = ` query($assessmentID: ID!) { - genericAssessmentQuestions(assessmentId: $assessmentID) { + genericSurveyQuestions(assessmentId: $assessmentID) { id - name - conclusions - instructions - instructionButton - questionSets { - text - setImage - questions { - id + surveyId + profileId + startedAt + completedAt + survey { + id + name + conclusions + instructions + instructionButton + questionSets { text - responseOptions { + setImage + questions { id text + responseOptions { + id + text + } } } } - assessment { - id - surveyId - profileId - startedAt - completedAt - } } } `; diff --git a/src/lib/recoil/assessment.js b/src/lib/recoil/assessment.js index 8d6aa67c..0373e1b5 100644 --- a/src/lib/recoil/assessment.js +++ b/src/lib/recoil/assessment.js @@ -124,12 +124,12 @@ export const genericAssessmentQuery = selectorFamily({ return null; } - const survey = response.data.genericAssessmentQuestions; - if(!survey?.length || !survey?.assessment.completedAt) { return survey; } + const assessment = response.data.genericSurveyQuestions; + if(!assessment?.length || !assessment?.completedAt) { return assessment; } - cache.set(cacheKey, survey); + cache.set(cacheKey, assessment); - return survey; + return assessment; }, key: "assessment/generic" }); From 8e04610261036c836fbd6688e2818cb221ef10b6 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Fri, 27 Jun 2025 15:30:28 +0700 Subject: [PATCH 15/51] refactor code --- public/index.js | 9 ++++----- src/components/common/modal/index.js | 8 ++++---- src/components/results/generic/index.js | 12 ++++++++++++ src/components/survey/generic/container.js | 5 ++--- src/components/survey/generic/index.js | 5 ++--- src/components/survey/generic/question-set.js | 6 +++--- src/components/survey/generic/responses.js | 4 ++-- src/components/survey/index.js | 2 +- src/lib/recoil/assessment.js | 2 +- 9 files changed, 31 insertions(+), 22 deletions(-) create mode 100644 src/components/results/generic/index.js diff --git a/public/index.js b/public/index.js index 09ad1d74..4a9486c1 100644 --- a/public/index.js +++ b/public/index.js @@ -389,9 +389,9 @@ function setupDom() { options: [ {text: "Benchmark", value: "benchmark"}, {text: "Cognitive", value: "cognitive"}, + {text: "Generic", value: "generic"}, {text: "Order", value: "order"}, - {text: "Personality", value: "personality"}, - {text: "Generic", value: "generic"} + {text: "Personality", value: "personality"} ], text: "Survey Type:" })); @@ -479,8 +479,7 @@ function setupTraitify() { const environment = cache.get("environment"); if(environment === "staging") { - // Traitify.http.host = "https://api.stag.awse.traitify.com"; - Traitify.http.host = "http://localhost:4000/"; + Traitify.http.host = "https://api.stag.awse.traitify.com"; } else { Traitify.http.host = "https://api.traitify.com"; } @@ -516,6 +515,6 @@ function onSurveyTypeChange(e) { setupTraitify(); setupDom(); -// setupCognitive(); +setupCognitive(); setupGeneric(); createWidget(); diff --git a/src/components/common/modal/index.js b/src/components/common/modal/index.js index 0ad8fbcf..c6a6c652 100644 --- a/src/components/common/modal/index.js +++ b/src/components/common/modal/index.js @@ -4,9 +4,9 @@ import Icon from "components/common/icon"; import useTranslate from "lib/hooks/use-translate"; import style from "./style.scss"; -export default function Modal({children, onClose, title, containerClass = ""}) { +export default function Modal({children, containerClass = null, onClose, title}) { const translate = useTranslate(); - const sectionClass = [style.modalContainer, containerClass].join(" "); + const sectionClass = [style.modalContainer, containerClass].filter(Boolean).join(" "); return (
@@ -35,7 +35,7 @@ export default function Modal({children, onClose, title, containerClass = ""}) { Modal.propTypes = { children: PropTypes.node.isRequired, + containerClass: PropTypes.string, onClose: PropTypes.func.isRequired, - title: PropTypes.string.isRequired, - containerClass: PropTypes.string + title: PropTypes.string.isRequired }; diff --git a/src/components/results/generic/index.js b/src/components/results/generic/index.js new file mode 100644 index 00000000..4bd8f120 --- /dev/null +++ b/src/components/results/generic/index.js @@ -0,0 +1,12 @@ +import useResults from "lib/hooks/use-results"; + +export default function Generic() { + const results = useResults({surveyType: "generic"}); + console.log("Generic assessment result:", results); + return ( +
+

Generic Report

+

This is a placeholder for a generic report. Please customize it as needed.

+
+ ); +} diff --git a/src/components/survey/generic/container.js b/src/components/survey/generic/container.js index 7642ccf6..491ad244 100644 --- a/src/components/survey/generic/container.js +++ b/src/components/survey/generic/container.js @@ -4,12 +4,11 @@ import style from "./style.scss"; export default function Container({children, progress}) { return (
- {progress < 100 - && ( + {progress < 100 && (
- )} + )} {children}
); diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index 2af44b0a..e4453c5c 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -78,15 +78,14 @@ export default function Generic() { return ( - {currentQuestionSet - && ( + {currentQuestionSet && ( - )} + )} {questionSetIndex > 0 && ( +
+
+
+ {assessmentResult.responses.map((question, index) => ( + + ))} +
+
+ ); +} + +Breakdown.propTypes = { + assessmentResult: PropTypes.shape({ + responses: PropTypes.arrayOf( + PropTypes.shape({ + questionId: PropTypes.string.isRequired, + questionText: PropTypes.string, + isCorrect: PropTypes.bool.isRequired, + selectedResponseOptionId: PropTypes.string, + responseOptions: PropTypes.arrayOf( + PropTypes.shape({ + responseOptionId: PropTypes.string.isRequired, + responseOptionText: PropTypes.string.isRequired, + isCorrect: PropTypes.bool + }) + ).isRequired + }) + ).isRequired + }).isRequired +}; diff --git a/src/components/results/generic/index.js b/src/components/results/generic/index.js index 59a79bab..2f0d99ed 100644 --- a/src/components/results/generic/index.js +++ b/src/components/results/generic/index.js @@ -2,12 +2,15 @@ import {useState, useEffect} from "react"; import useGraphql from "lib/hooks/use-graphql"; import useHttp from "lib/hooks/use-http"; import useResults from "lib/hooks/use-results"; +import Breakdown from "./breakdown"; +import Score from "./score"; import style from "./style.scss"; export default function Generic() { const [result, setResult] = useState(null); const assessment = useResults({surveyType: "generic"}); const profile = result ? result.profile : {}; + const assessmentResult = result ? result.assessment : {}; const graphQL = useGraphql(); const http = useHttp(); @@ -31,28 +34,16 @@ export default function Generic() {
{result && (
- {profile.firstName} {profile.lastName} -

Completed on: {assessment ? assessment.completedAt : ""}

-
-
-

Assessment Results

+
+ {profile.firstName} {profile.lastName} +

Completed on: {assessment ? assessment.completedAt : ""}

-
-

Score

-
-
-
Correct:
-
16 / 20
-
-
-
Incorrect:
-
4 / 20
-
-
-
Overall Score:
-
80%
-
+
+
+
Assessment Results
+ +
)} diff --git a/src/components/results/generic/question.js b/src/components/results/generic/question.js new file mode 100644 index 00000000..fa03589e --- /dev/null +++ b/src/components/results/generic/question.js @@ -0,0 +1,69 @@ +import {faCheck, faXmark, faChevronDown, faChevronUp} from "@fortawesome/free-solid-svg-icons"; +import PropTypes from "prop-types"; +import {useState} from "react"; +import Icon from "components/common/icon"; +import style from "./style.scss"; + +export default function Question({question, index}) { + const [showContent, setShowContent] = useState(false); + + return ( +
+
+
+ {question.isCorrect + ? + : } +
+
Question {index + 1}
+
+ +
+
+ {showContent && ( +
+
+
{question.questionText}
+ {question.responseOptions.map((option) => { + let optionClassName = ""; + if(question.isCorrect) { + optionClassName = option.isCorrect ? style.correctResponse : ""; + } else { + if(option.isCorrect) { + optionClassName = style.correctOption; + } else if(option.responseOptionId === question.selectedResponseOptionId) { + optionClassName = style.incorrectResponse; + } + } + return ( +
+ {option.responseOptionText} +
+ ); + })} +
+
+ Question illustration +
+
+ )} +
+ ); +} + +Question.propTypes = { + question: PropTypes.shape({ + questionText: PropTypes.string, + questionId: PropTypes.string.isRequired, + isCorrect: PropTypes.bool.isRequired, + selectedResponseOptionId: PropTypes.string, + responseOptions: PropTypes.arrayOf(PropTypes.shape({ + responseOptionId: PropTypes.string.isRequired, + responseOptionText: PropTypes.string.isRequired, + isCorrect: PropTypes.bool.isRequired + })).isRequired + }).isRequired, + index: PropTypes.number.isRequired +}; diff --git a/src/components/results/generic/score.js b/src/components/results/generic/score.js new file mode 100644 index 00000000..7fcb665d --- /dev/null +++ b/src/components/results/generic/score.js @@ -0,0 +1,50 @@ +import PropTypes from "prop-types"; +import style from "./style.scss"; + +export default function Score({assessmentResult}) { + const totalQuestions = assessmentResult ? assessmentResult.responses.length : 0; + const totalCorrectResponses = assessmentResult ? assessmentResult.totalCorrectResponses : 0; + const totalIncorrectResponses = totalQuestions - totalCorrectResponses; + const overallScore = totalQuestions > 0 ? (totalCorrectResponses / totalQuestions) * 100 : 0; + + return ( +
+
Score
+
+
+
Correct:
+
{totalCorrectResponses} / {totalQuestions}
+
+
+
Incorrect:
+
{totalIncorrectResponses} / {totalQuestions}
+
+
+
Overall Score:
+
{overallScore}%
+
+
+
+ ); +} + +Score.propTypes = { + assessmentResult: PropTypes.shape({ + responses: PropTypes.arrayOf( + PropTypes.shape({ + questionId: PropTypes.string.isRequired, + questionText: PropTypes.string, + isCorrect: PropTypes.bool.isRequired, + selectedResponseOptionId: PropTypes.string, + responseOptions: PropTypes.arrayOf( + PropTypes.shape({ + responseOptionId: PropTypes.string.isRequired, + responseOptionText: PropTypes.string.isRequired, + isCorrect: PropTypes.bool + }) + ).isRequired + }) + ).isRequired, + totalCorrectResponses: PropTypes.number.isRequired + }).isRequired +}; diff --git a/src/components/results/generic/style.scss b/src/components/results/generic/style.scss index 4a54c85b..34b23117 100644 --- a/src/components/results/generic/style.scss +++ b/src/components/results/generic/style.scss @@ -1,39 +1,132 @@ @import "style/helpers"; .container { - .score { - .scoreRow { - display: flex; - gap: $buffer * 0.5; - font-size: $font-size-lg; - .correct { - background-color: $-aquamarine; - } - .incorrect { - background-color: $-red; - } - .overall { - background-color: $-blue; - } - .correct, .incorrect, .overall { + .contentBody { + display: flex; + flex-direction: column; + gap: $buffer-lg; + width: 100%; + & > div { + border: 1px solid #DADCE0; + border-radius: $border-radius-sm; + padding: $buffer; + } + .score { + .scoreRow { + margin-top: $buffer; + display: flex; + gap: $buffer * 0.5; + font-size: $font-size-lg; + .correct { + background-color: $-aquamarine; + } + .incorrect { + background-color: $-red; + } + .overall { + background-color: $-blue; + } + .correct, .incorrect, .overall { + div { + display: flex; + width: 50%; + } + } div { display: flex; - width: 50%; + width: 30%; + height: $buffer * 2.75; + border-radius: $border-radius-sm; + padding: $buffer * 0.5 $buffer; + color: $-white; + align-items: center; + .count { + justify-content: end; + } } } - div { + } + .breakdown { + display: flex; + gap: $buffer-lg * 0.5; + flex-direction: column; + .header { display: flex; - width: 30%; - height: $buffer * 2.75; - border-radius: $border-radius-sm; - padding: $buffer * 0.5 $buffer; - color: $-white; align-items: center; - .count { - justify-content: end; + gap: $buffer * 0.5; + .toggleButtonWrapper { + margin-left: auto; + } + } + .questions { + display: flex; + flex-direction: column; + gap: $buffer * 0.5; + .question { + border: 1px solid #DADCE0; + border-radius: $border-radius-sm; + padding: $buffer * 0.75 $buffer; + .questionTitle { + display: flex; + align-items: center; + gap: $buffer * 0.75; + font-weight: 700; + .iconCorrect { + color: $-aquamarine; + font-size: $font-size-heading * 1.25; + } + .iconIncorrect { + color: $-red; + font-size: $font-size-heading * 1.25; + } + .toggleButtonWrapper { + display: flex; + align-items: center; + margin-left: auto; + } + } + .questionContent { + display: flex; + gap: $buffer * 0.5; + align-items: center; + .questionText { + font-weight: 600; + padding: $buffer 0; + } + .responseOptions { + display: flex; + flex-direction: column; + flex: 1; + gap: $buffer; + .responseOption { + border: 1px solid #DADCE0; + padding: $buffer-lg * 0.25 $buffer-lg * 0.5; + border-radius: $border-radius-sm; + text-align: center; + &.correctResponse { + background-color: $-aquamarine; + color: $-white; + } + &.incorrectResponse { + background-color: $-red; + color: $-white; + } + &.correctOption { + border: 2px solid $-aquamarine; + } + } + } + .questionImage { + flex: 1; + } + } } } } + .title { + @extend %heading; + font-size: $font-size; + } } button { border: 1px solid #DADCE0; @@ -51,6 +144,15 @@ font-size: $font-size-lg; } } + &.toggleButton { + @include buttonLight("text-dark"); + border-radius: $border-radius-sm * 0.5; + padding: $buffer-sm $buffer-sm * 1.25; + margin: $buffer-sm * 0.5; + cursor: pointer; + height: $buffer * 2; + justify-content: end; + } } .markdown { diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index b537fefb..2a13e6a0 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -4,9 +4,9 @@ import Icon from "components/common/icon"; import Markdown from "components/common/markdown"; import Modal from "components/common/modal"; import useAssessment from "lib/hooks/use-assessment"; +import useDidUpdate from "lib/hooks/use-did-update"; import useGraphql from "lib/hooks/use-graphql"; import useHttp from "lib/hooks/use-http"; -import useDidUpdate from "lib/hooks/use-did-update"; import useTranslate from "lib/hooks/use-translate"; import Container from "./container"; import QuestionSet from "./question-set"; From 398f21f620572002446508202699b96c029b570c Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Wed, 23 Jul 2025 18:21:36 +0700 Subject: [PATCH 20/51] apply space-between --- src/components/results/generic/breakdown.js | 16 +++- src/components/results/generic/header.js | 26 +++++ src/components/results/generic/index.js | 11 +-- src/components/results/generic/question.js | 2 +- .../results/generic/result_actions.js | 18 ++++ src/components/results/generic/style.scss | 94 ++++++++++--------- 6 files changed, 113 insertions(+), 54 deletions(-) create mode 100644 src/components/results/generic/header.js create mode 100644 src/components/results/generic/result_actions.js diff --git a/src/components/results/generic/breakdown.js b/src/components/results/generic/breakdown.js index d56f4d6f..d5e7483a 100644 --- a/src/components/results/generic/breakdown.js +++ b/src/components/results/generic/breakdown.js @@ -3,15 +3,25 @@ import Question from "./question"; import style from "./style.scss"; export default function Breakdown({assessmentResult}) { + const showHideAll = () => { + + }; + + const showHideQuestion = (questionId) => { + + }; + return (
-
+
Breakdown
Here is the breakdown of the questions you answered in the assessment
-
- +
+
diff --git a/src/components/results/generic/header.js b/src/components/results/generic/header.js new file mode 100644 index 00000000..620f0716 --- /dev/null +++ b/src/components/results/generic/header.js @@ -0,0 +1,26 @@ +import propTypes from "prop-types"; +import style from "./style.scss"; + +export default function Header({profile, assessment}) { + const initials = `${profile.firstName.charAt(0)}${profile.lastName.charAt(0)}`.toUpperCase(); + return ( +
+
{initials}
+
+
{profile.firstName} {profile.lastName}
+
Generic Assessment
+
Completed on: {assessment ? assessment.completedAt : ""}
+
+
+ ); +} + +Header.propTypes = { + profile: propTypes.shape({ + firstName: propTypes.string.isRequired, + lastName: propTypes.string.isRequired + }).isRequired, + assessment: propTypes.shape({ + completedAt: propTypes.string + }).isRequired +}; diff --git a/src/components/results/generic/index.js b/src/components/results/generic/index.js index 2f0d99ed..c6c0a0e7 100644 --- a/src/components/results/generic/index.js +++ b/src/components/results/generic/index.js @@ -3,6 +3,8 @@ import useGraphql from "lib/hooks/use-graphql"; import useHttp from "lib/hooks/use-http"; import useResults from "lib/hooks/use-results"; import Breakdown from "./breakdown"; +import Header from "./header"; +import ResultActions from "./result_actions"; import Score from "./score"; import style from "./style.scss"; @@ -34,14 +36,9 @@ export default function Generic() {
{result && (
-
- {profile.firstName} {profile.lastName} -

Completed on: {assessment ? assessment.completedAt : ""}

-
+
-
-
Assessment Results
-
+
diff --git a/src/components/results/generic/question.js b/src/components/results/generic/question.js index fa03589e..24d8f8d8 100644 --- a/src/components/results/generic/question.js +++ b/src/components/results/generic/question.js @@ -16,7 +16,7 @@ export default function Question({question, index}) { : }
Question {index + 1}
-
+
diff --git a/src/components/results/generic/result_actions.js b/src/components/results/generic/result_actions.js new file mode 100644 index 00000000..d1b4d655 --- /dev/null +++ b/src/components/results/generic/result_actions.js @@ -0,0 +1,18 @@ +import {faPrint, faQuestionCircle, faEnvelopeOpen, faArrowUpFromBracket, faArrowRotateLeft} from "@fortawesome/free-solid-svg-icons"; +import Icon from "components/common/icon"; +import style from "./style.scss"; + +export default function ResultActions() { + return ( +
+
Assessment Results
+
+ + + + + +
+
+ ); +} diff --git a/src/components/results/generic/style.scss b/src/components/results/generic/style.scss index 34b23117..38ea8862 100644 --- a/src/components/results/generic/style.scss +++ b/src/components/results/generic/style.scss @@ -1,22 +1,64 @@ @import "style/helpers"; .container { + @extend %container; + color: $-black; + display: flex; + flex-direction: column; + width: 100%; + .header { + display: flex; + padding: $buffer $buffer-lg; + background-color: $-white; + border-bottom: 1px solid #DADCE0; + gap: $buffer; + .profileCircle { + display: flex; + align-items: center; + border-radius: 50%; + height: $buffer-lg * 2.5; + justify-content: center; + width: $buffer-lg * 2.5; + background-color: $-traitify-blue; + color: $-white; + font-size: $font-size-lg; + } + .profileDetails { + color: #A9A9A9; + .profileName { + font-size: $font-size-lg; + font-weight: 700; + color: $-black; + } + } + } .contentBody { display: flex; flex-direction: column; gap: $buffer-lg; - width: 100%; + width: $breakpoint-lg; + margin: $buffer-lg auto; & > div { border: 1px solid #DADCE0; border-radius: $border-radius-sm; padding: $buffer; } + .assessmentResult { + display: flex; + justify-content: space-between; + .actions { + color: #A9A9A9; + display: flex; + gap: $buffer; + } + } .score { .scoreRow { margin-top: $buffer; display: flex; gap: $buffer * 0.5; font-size: $font-size-lg; + justify-content: space-between; .correct { background-color: $-aquamarine; } @@ -27,22 +69,13 @@ background-color: $-blue; } .correct, .incorrect, .overall { - div { - display: flex; - width: 50%; - } - } - div { display: flex; - width: 30%; + justify-content: space-between; + width: 100%; height: $buffer * 2.75; border-radius: $border-radius-sm; padding: $buffer * 0.5 $buffer; color: $-white; - align-items: center; - .count { - justify-content: end; - } } } } @@ -50,13 +83,11 @@ display: flex; gap: $buffer-lg * 0.5; flex-direction: column; - .header { + .description { display: flex; align-items: center; + justify-content: space-between; gap: $buffer * 0.5; - .toggleButtonWrapper { - margin-left: auto; - } } .questions { display: flex; @@ -71,6 +102,7 @@ align-items: center; gap: $buffer * 0.75; font-weight: 700; + justify-content: space-between; .iconCorrect { color: $-aquamarine; font-size: $font-size-heading * 1.25; @@ -79,7 +111,7 @@ color: $-red; font-size: $font-size-heading * 1.25; } - .toggleButtonWrapper { + div:last-of-type { display: flex; align-items: center; margin-left: auto; @@ -118,6 +150,7 @@ } .questionImage { flex: 1; + text-align: center } } } @@ -135,38 +168,13 @@ height: 48px; vertical-align: middle; - &.back { - padding: $buffer * 0.5 $buffer-lg * 0.5; - width: 150px; - - .icon { - margin-right: $buffer-sm; - font-size: $font-size-lg; - } - } &.toggleButton { @include buttonLight("text-dark"); border-radius: $border-radius-sm * 0.5; - padding: $buffer-sm $buffer-sm * 1.25; + padding: $buffer-sm * 0.5 $buffer-sm * 1.25; margin: $buffer-sm * 0.5; cursor: pointer; height: $buffer * 2; - justify-content: end; } } - - .markdown { - text-align: center; - } } - -button.btnPrimary { - background-color: $-blue; - color: $-white; - display: inline-block; - padding: $buffer-lg * .5 $buffer; - text-align: center; - border: transparent; - max-width: 110px; - margin: $buffer-lg auto; -} \ No newline at end of file From b26218817d750f48576de0dd43b32a534a0a5a4e Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Wed, 23 Jul 2025 18:37:56 +0700 Subject: [PATCH 21/51] show hide all --- src/components/results/generic/breakdown.js | 10 ++++------ src/components/results/generic/question.js | 13 ++++++++++--- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/components/results/generic/breakdown.js b/src/components/results/generic/breakdown.js index d5e7483a..1322efa6 100644 --- a/src/components/results/generic/breakdown.js +++ b/src/components/results/generic/breakdown.js @@ -1,14 +1,12 @@ import PropTypes from "prop-types"; +import {useState} from "react"; import Question from "./question"; import style from "./style.scss"; export default function Breakdown({assessmentResult}) { + const [showAll, setShowAll] = useState(false); const showHideAll = () => { - - }; - - const showHideQuestion = (questionId) => { - + setShowAll(!showAll); }; return ( @@ -26,7 +24,7 @@ export default function Breakdown({assessmentResult}) {
{assessmentResult.responses.map((question, index) => ( - + ))}
diff --git a/src/components/results/generic/question.js b/src/components/results/generic/question.js index 24d8f8d8..65621a23 100644 --- a/src/components/results/generic/question.js +++ b/src/components/results/generic/question.js @@ -1,12 +1,19 @@ import {faCheck, faXmark, faChevronDown, faChevronUp} from "@fortawesome/free-solid-svg-icons"; import PropTypes from "prop-types"; -import {useState} from "react"; +import {useState, useEffect} from "react"; import Icon from "components/common/icon"; import style from "./style.scss"; -export default function Question({question, index}) { +export default function Question({question, index, showState}) { const [showContent, setShowContent] = useState(false); + useEffect(() => { + setShowContent(showState); + }, [showState]); + const toggleContent = () => { + setShowContent(!showContent); + }; + return (
@@ -17,7 +24,7 @@ export default function Question({question, index}) {
Question {index + 1}
-
From 7ce343920bcce53f4fd6150b51a95a35fcb7fd9a Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Wed, 23 Jul 2025 18:39:05 +0700 Subject: [PATCH 22/51] format --- src/components/results/generic/breakdown.js | 7 ++++++- src/components/results/generic/question.js | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/results/generic/breakdown.js b/src/components/results/generic/breakdown.js index 1322efa6..2d1643fc 100644 --- a/src/components/results/generic/breakdown.js +++ b/src/components/results/generic/breakdown.js @@ -24,7 +24,12 @@ export default function Breakdown({assessmentResult}) {
{assessmentResult.responses.map((question, index) => ( - + ))}
diff --git a/src/components/results/generic/question.js b/src/components/results/generic/question.js index 65621a23..6c24f43a 100644 --- a/src/components/results/generic/question.js +++ b/src/components/results/generic/question.js @@ -10,6 +10,7 @@ export default function Question({question, index, showState}) { useEffect(() => { setShowContent(showState); }, [showState]); + const toggleContent = () => { setShowContent(!showContent); }; @@ -72,5 +73,6 @@ Question.propTypes = { isCorrect: PropTypes.bool.isRequired })).isRequired }).isRequired, - index: PropTypes.number.isRequired + index: PropTypes.number.isRequired, + showState: PropTypes.bool.isRequired }; From cfb3a3cb1dd4d5be314e836c398e892ea871f2ed Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Thu, 24 Jul 2025 17:01:11 +0700 Subject: [PATCH 23/51] handle question with set image --- src/components/results/generic/header.js | 10 +++-- src/components/results/generic/index.js | 2 +- src/components/results/generic/question.js | 49 +++++++++++++--------- src/components/results/generic/style.scss | 14 +++++++ src/lib/graphql/generic.js | 2 + 5 files changed, 54 insertions(+), 23 deletions(-) diff --git a/src/components/results/generic/header.js b/src/components/results/generic/header.js index 620f0716..425810c2 100644 --- a/src/components/results/generic/header.js +++ b/src/components/results/generic/header.js @@ -3,13 +3,16 @@ import style from "./style.scss"; export default function Header({profile, assessment}) { const initials = `${profile.firstName.charAt(0)}${profile.lastName.charAt(0)}`.toUpperCase(); + const surveyName = assessment ? assessment.surveyName : ""; + const completedAt = assessment ? assessment.completedAt : ""; + return (
{initials}
{profile.firstName} {profile.lastName}
-
Generic Assessment
-
Completed on: {assessment ? assessment.completedAt : ""}
+
{surveyName}
+
Completed on: {completedAt}
); @@ -21,6 +24,7 @@ Header.propTypes = { lastName: propTypes.string.isRequired }).isRequired, assessment: propTypes.shape({ - completedAt: propTypes.string + completedAt: propTypes.string, + surveyName: propTypes.string.isRequired }).isRequired }; diff --git a/src/components/results/generic/index.js b/src/components/results/generic/index.js index c6c0a0e7..5d5f8252 100644 --- a/src/components/results/generic/index.js +++ b/src/components/results/generic/index.js @@ -36,7 +36,7 @@ export default function Generic() {
{result && (
-
+
diff --git a/src/components/results/generic/question.js b/src/components/results/generic/question.js index 6c24f43a..3afac646 100644 --- a/src/components/results/generic/question.js +++ b/src/components/results/generic/question.js @@ -6,6 +6,9 @@ import style from "./style.scss"; export default function Question({question, index, showState}) { const [showContent, setShowContent] = useState(false); + const responsesClassName = question.setImage + ? style.responsesWithImage + : style.responsesWithoutImage; useEffect(() => { setShowContent(showState); @@ -34,27 +37,34 @@ export default function Question({question, index, showState}) {
{question.questionText}
- {question.responseOptions.map((option) => { - let optionClassName = ""; - if(question.isCorrect) { - optionClassName = option.isCorrect ? style.correctResponse : ""; - } else { - if(option.isCorrect) { - optionClassName = style.correctOption; - } else if(option.responseOptionId === question.selectedResponseOptionId) { - optionClassName = style.incorrectResponse; +
+ {question.responseOptions.map((option) => { + let optionClassName = ""; + if(question.isCorrect) { + optionClassName = option.isCorrect ? style.correctResponse : ""; + } else { + if(option.isCorrect) { + optionClassName = style.correctOption; + } else if(option.responseOptionId === question.selectedResponseOptionId) { + optionClassName = style.incorrectResponse; + } } - } - return ( -
- {option.responseOptionText} -
- ); - })} -
-
- Question illustration + return ( +
+ {option.responseOptionText} +
+ ); + })} +
+ {question.setImage && ( +
+ {question.questionText} +
+ )}
)}
@@ -67,6 +77,7 @@ Question.propTypes = { questionId: PropTypes.string.isRequired, isCorrect: PropTypes.bool.isRequired, selectedResponseOptionId: PropTypes.string, + setImage: PropTypes.string, responseOptions: PropTypes.arrayOf(PropTypes.shape({ responseOptionId: PropTypes.string.isRequired, responseOptionText: PropTypes.string.isRequired, diff --git a/src/components/results/generic/style.scss b/src/components/results/generic/style.scss index 38ea8862..08d41d94 100644 --- a/src/components/results/generic/style.scss +++ b/src/components/results/generic/style.scss @@ -130,6 +130,20 @@ flex-direction: column; flex: 1; gap: $buffer; + .responsesWithoutImage { + display: flex; + justify-content: space-around; + flex-direction: row; + gap: $buffer; + .responseOption { + width: 100%; + } + } + .responsesWithImage { + display: flex; + flex-direction: column; + gap: $buffer; + } .responseOption { border: 1px solid #DADCE0; padding: $buffer-lg * 0.25 $buffer-lg * 0.5; diff --git a/src/lib/graphql/generic.js b/src/lib/graphql/generic.js index 4e7bc096..f9df71a3 100644 --- a/src/lib/graphql/generic.js +++ b/src/lib/graphql/generic.js @@ -61,6 +61,7 @@ export const result = ` assessment { profileId surveyId + surveyName startedAt totalCorrectResponses completedAt @@ -68,6 +69,7 @@ export const result = ` questionId questionText selectedResponseOptionId + setImage isCorrect responseOptions { responseOptionId From 02fde096f373dd8dc01c735f983f3c8f1babd4c0 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Fri, 25 Jul 2025 18:44:50 +0700 Subject: [PATCH 24/51] handle locale --- src/components/results/generic/header.js | 28 +++++++++++++++++++---- src/components/results/generic/index.js | 1 - src/components/results/generic/style.scss | 28 +++++++++++++---------- src/lib/graphql/generic.js | 1 + 4 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/components/results/generic/header.js b/src/components/results/generic/header.js index 425810c2..989a5736 100644 --- a/src/components/results/generic/header.js +++ b/src/components/results/generic/header.js @@ -1,18 +1,35 @@ import propTypes from "prop-types"; +import SimpleDropdown from "components/common/dropdown/simple"; +import i18nData from "lib/i18n-data"; import style from "./style.scss"; export default function Header({profile, assessment}) { const initials = `${profile.firstName.charAt(0)}${profile.lastName.charAt(0)}`.toUpperCase(); const surveyName = assessment ? assessment.surveyName : ""; const completedAt = assessment ? assessment.completedAt : ""; + const assessmentLocale = assessment ? assessment.localeKey : "en-US"; + const localeOptions = Object.keys(i18nData).map((key) => ({ + value: key, + name: i18nData[key].name + })); return (
-
{initials}
-
{profile.firstName} {profile.lastName}
-
{surveyName}
-
Completed on: {completedAt}
+
{initials}
+
+
{profile.firstName} {profile.lastName}
+
{surveyName}
+
Completed on: {completedAt}
+
+
+
+
); @@ -25,6 +42,7 @@ Header.propTypes = { }).isRequired, assessment: propTypes.shape({ completedAt: propTypes.string, - surveyName: propTypes.string.isRequired + surveyName: propTypes.string.isRequired, + localeKey: propTypes.string.isRequired }).isRequired }; diff --git a/src/components/results/generic/index.js b/src/components/results/generic/index.js index 5d5f8252..419bdb35 100644 --- a/src/components/results/generic/index.js +++ b/src/components/results/generic/index.js @@ -24,7 +24,6 @@ export default function Generic() { http.post(graphQL.generic.path, {query, variables}).then(({data, errors}) => { if(!errors && data.genericAssessmentResult) { - console.log("Generic assessment result data:", data.genericAssessmentResult); setResult(data.genericAssessmentResult); } else { console.warn(errors || data); // eslint-disable-line no-console diff --git a/src/components/results/generic/style.scss b/src/components/results/generic/style.scss index 08d41d94..e4b907ca 100644 --- a/src/components/results/generic/style.scss +++ b/src/components/results/generic/style.scss @@ -11,20 +11,24 @@ padding: $buffer $buffer-lg; background-color: $-white; border-bottom: 1px solid #DADCE0; - gap: $buffer; - .profileCircle { - display: flex; - align-items: center; - border-radius: 50%; - height: $buffer-lg * 2.5; - justify-content: center; - width: $buffer-lg * 2.5; - background-color: $-traitify-blue; - color: $-white; - font-size: $font-size-lg; - } + justify-content: space-between; .profileDetails { + display: flex; + flex-direction: row; + gap: $buffer; color: #A9A9A9; + .profileCircle { + display: flex; + align-items: center; + border-radius: 50%; + height: $buffer-lg * 2.5; + justify-content: center; + width: $buffer-lg * 2.5; + background-color: #DD7373; + color: $-white; + font-size: $font-size-lg; + border: 2px solid #FAC4C4; + } .profileName { font-size: $font-size-lg; font-weight: 700; diff --git a/src/lib/graphql/generic.js b/src/lib/graphql/generic.js index f9df71a3..2ce05a9d 100644 --- a/src/lib/graphql/generic.js +++ b/src/lib/graphql/generic.js @@ -59,6 +59,7 @@ export const result = ` lastName } assessment { + localeKey profileId surveyId surveyName From a2537ab37bde2efa601ba99f1ff066c4a95fcae4 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Wed, 30 Jul 2025 17:31:17 +0700 Subject: [PATCH 25/51] update heading and hr tag --- package-lock.json | 4 ++-- src/components/survey/generic/index.js | 4 ++-- src/components/survey/generic/question-set.js | 6 +++--- src/components/survey/generic/responses.js | 11 ++++++++--- src/components/survey/generic/style.scss | 18 +++++++++++------- 5 files changed, 26 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index d55b33a0..96e14900 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "traitify-widgets", - "version": "3.7.2", + "version": "3.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "traitify-widgets", - "version": "3.7.2", + "version": "3.8.0", "license": "MIT", "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.5.2", diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index c11c6a72..3129679f 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -106,7 +106,7 @@ export default function Generic() { {questionSetIndex > 0 && ( )} {showInstructions @@ -119,7 +119,7 @@ export default function Generic() { {assessment.survey.instructions} -
+
diff --git a/src/components/survey/generic/style.scss b/src/components/survey/generic/style.scss index c7c6b4a3..249cebb3 100644 --- a/src/components/survey/generic/style.scss +++ b/src/components/survey/generic/style.scss @@ -24,9 +24,10 @@ button { border: 1px solid #DADCE0; border-radius: $border-radius-sm * 0.5; - margin: $buffer $buffer * 0.5; + margin: $buffer * 1.25 0 0; height: 48px; vertical-align: middle; + font-weight: 500; &.back { padding: $buffer * 0.5 $buffer-lg * 0.5; @@ -80,18 +81,21 @@ padding: $buffer; img { - margin-bottom: $buffer * 2; + margin-bottom: $buffer; } .question { text-align: left; + font-weight: 600; } } +.responseOptions{ + display: flex; + justify-content: space-between; +} + .response { - flex: 1 1 0; - letter-spacing: 2px; - line-height: 2; padding: $buffer * 0.25 $buffer * 0.75; text-align: center; @@ -99,7 +103,7 @@ @include min-width("sm") { font-size: $font-size * 1.25; line-height: $line-height; - padding: $buffer * 0.5 $buffer * 0.75; + padding: $buffer * 0.5 $buffer * 1.5; } @include theme("color", "text-light"); &:focus { @@ -130,7 +134,7 @@ button.btnPrimary { .grayDivider { color: #555555; width: 100%; - margin: $buffer * 3 auto $buffer * .5; + margin: $buffer-lg auto $buffer-lg; line-height: inherit; clear: both; user-select: none; From cd2a6744e5ad87d696cf20cbb23175dc4174a0df Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Wed, 30 Jul 2025 17:33:19 +0700 Subject: [PATCH 26/51] helper --- public/index.js | 2 +- src/components/results/generic/question.js | 49 +-- src/components/results/generic/style.scss | 384 +++++++++++---------- 3 files changed, 237 insertions(+), 198 deletions(-) diff --git a/public/index.js b/public/index.js index b51f2835..a995ed9e 100644 --- a/public/index.js +++ b/public/index.js @@ -501,7 +501,7 @@ function onSurveyTypeChange(e) { const name = e.target.name; const value = e.target.value; const assessmentID = cache.get(`${value}AssessmentID`); - const otherValues = ["benchmark", "cognitive", "order", "personality", "generic"].filter((type) => type !== value); + const otherValues = ["benchmark", "cognitive", "generic", "order", "personality"].filter((type) => type !== value); cache.set("assessmentID", assessmentID); diff --git a/src/components/results/generic/question.js b/src/components/results/generic/question.js index 3afac646..ebf42a6d 100644 --- a/src/components/results/generic/question.js +++ b/src/components/results/generic/question.js @@ -9,6 +9,11 @@ export default function Question({question, index, showState}) { const responsesClassName = question.setImage ? style.responsesWithImage : style.responsesWithoutImage; + const longTextCondition = (option) => option.responseOptionText.length > 20; + const longTextResponses = question.responseOptions.some(longTextCondition); + const optionsDirection = (longTextResponses || responsesClassName === style.responsesWithImage) + ? "column" + : "row"; useEffect(() => { setShowContent(showState); @@ -18,6 +23,20 @@ export default function Question({question, index, showState}) { setShowContent(!showContent); }; + const optionClassName = (option) => { + let className = ""; + if(question.isCorrect) { + className = option.isCorrect ? style.correctResponse : ""; + } else { + if(option.isCorrect) { + className = style.correctOption; + } else if(option.responseOptionId === question.selectedResponseOptionId) { + className = style.incorrectResponse; + } + } + return className; + }; + return (
@@ -37,27 +56,15 @@ export default function Question({question, index, showState}) {
{question.questionText}
-
- {question.responseOptions.map((option) => { - let optionClassName = ""; - if(question.isCorrect) { - optionClassName = option.isCorrect ? style.correctResponse : ""; - } else { - if(option.isCorrect) { - optionClassName = style.correctOption; - } else if(option.responseOptionId === question.selectedResponseOptionId) { - optionClassName = style.incorrectResponse; - } - } - return ( -
- {option.responseOptionText} -
- ); - })} +
+ {question.responseOptions.map((option) => ( +
+ {option.responseOptionText} +
+ ))}
{question.setImage && ( diff --git a/src/components/results/generic/style.scss b/src/components/results/generic/style.scss index e4b907ca..a661f694 100644 --- a/src/components/results/generic/style.scss +++ b/src/components/results/generic/style.scss @@ -1,198 +1,230 @@ @import "style/helpers"; +.actions { + color: #A9A9A9; + display: flex; + gap: $buffer; +} + +.assessmentResult { + display: flex; + justify-content: space-between; +} + +.breakdown { + display: flex; + flex-direction: column; + gap: $buffer-lg * 0.5; +} + .container { @extend %container; color: $-black; display: flex; flex-direction: column; width: 100%; - .header { - display: flex; - padding: $buffer $buffer-lg; - background-color: $-white; - border-bottom: 1px solid #DADCE0; - justify-content: space-between; - .profileDetails { - display: flex; - flex-direction: row; - gap: $buffer; - color: #A9A9A9; - .profileCircle { - display: flex; - align-items: center; - border-radius: 50%; - height: $buffer-lg * 2.5; - justify-content: center; - width: $buffer-lg * 2.5; - background-color: #DD7373; - color: $-white; - font-size: $font-size-lg; - border: 2px solid #FAC4C4; - } - .profileName { - font-size: $font-size-lg; - font-weight: 700; - color: $-black; - } - } - } - .contentBody { - display: flex; - flex-direction: column; - gap: $buffer-lg; - width: $breakpoint-lg; - margin: $buffer-lg auto; - & > div { - border: 1px solid #DADCE0; - border-radius: $border-radius-sm; - padding: $buffer; - } - .assessmentResult { - display: flex; - justify-content: space-between; - .actions { - color: #A9A9A9; - display: flex; - gap: $buffer; - } - } - .score { - .scoreRow { - margin-top: $buffer; - display: flex; - gap: $buffer * 0.5; - font-size: $font-size-lg; - justify-content: space-between; - .correct { - background-color: $-aquamarine; - } - .incorrect { - background-color: $-red; - } - .overall { - background-color: $-blue; - } - .correct, .incorrect, .overall { - display: flex; - justify-content: space-between; - width: 100%; - height: $buffer * 2.75; - border-radius: $border-radius-sm; - padding: $buffer * 0.5 $buffer; - color: $-white; - } - } - } - .breakdown { - display: flex; - gap: $buffer-lg * 0.5; - flex-direction: column; - .description { - display: flex; - align-items: center; - justify-content: space-between; - gap: $buffer * 0.5; - } - .questions { - display: flex; - flex-direction: column; - gap: $buffer * 0.5; - .question { - border: 1px solid #DADCE0; - border-radius: $border-radius-sm; - padding: $buffer * 0.75 $buffer; - .questionTitle { - display: flex; - align-items: center; - gap: $buffer * 0.75; - font-weight: 700; - justify-content: space-between; - .iconCorrect { - color: $-aquamarine; - font-size: $font-size-heading * 1.25; - } - .iconIncorrect { - color: $-red; - font-size: $font-size-heading * 1.25; - } - div:last-of-type { - display: flex; - align-items: center; - margin-left: auto; - } - } - .questionContent { - display: flex; - gap: $buffer * 0.5; - align-items: center; - .questionText { - font-weight: 600; - padding: $buffer 0; - } - .responseOptions { - display: flex; - flex-direction: column; - flex: 1; - gap: $buffer; - .responsesWithoutImage { - display: flex; - justify-content: space-around; - flex-direction: row; - gap: $buffer; - .responseOption { - width: 100%; - } - } - .responsesWithImage { - display: flex; - flex-direction: column; - gap: $buffer; - } - .responseOption { - border: 1px solid #DADCE0; - padding: $buffer-lg * 0.25 $buffer-lg * 0.5; - border-radius: $border-radius-sm; - text-align: center; - &.correctResponse { - background-color: $-aquamarine; - color: $-white; - } - &.incorrectResponse { - background-color: $-red; - color: $-white; - } - &.correctOption { - border: 2px solid $-aquamarine; - } - } - } - .questionImage { - flex: 1; - text-align: center - } - } - } - } - } - .title { - @extend %heading; - font-size: $font-size; - } - } + button { border: 1px solid #DADCE0; border-radius: $border-radius-sm * 0.5; - margin: $buffer $buffer * 0.5; height: 48px; + margin: $buffer $buffer * 0.5; vertical-align: middle; &.toggleButton { @include buttonLight("text-dark"); border-radius: $border-radius-sm * 0.5; - padding: $buffer-sm * 0.5 $buffer-sm * 1.25; - margin: $buffer-sm * 0.5; cursor: pointer; height: $buffer * 2; + margin: $buffer-sm * 0.5; + padding: $buffer-sm * 0.5 $buffer-sm * 1.25; } } } + +.contentBody { + display: flex; + flex-direction: column; + gap: $buffer-lg; + margin: $buffer-lg auto; + width: $breakpoint-lg; + + & > div { + border: 1px solid #DADCE0; + border-radius: $border-radius-sm; + padding: $buffer; + } +} + +.correct { + background-color: $-aquamarine; +} + +.correct, .incorrect, .overall { + border-radius: $border-radius-sm; + color: $-white; + display: flex; + height: $buffer * 2.75; + justify-content: space-between; + padding: $buffer * 0.5 $buffer; + width: 100%; +} + +.description { + align-items: center; + display: flex; + gap: $buffer * 0.5; + justify-content: space-between; +} + +.header { + background-color: $-white; + border-bottom: 1px solid #DADCE0; + display: flex; + justify-content: space-between; + padding: $buffer $buffer-lg; +} + +.iconCorrect { + color: $-aquamarine; + font-size: $font-size-heading * 1.25; +} + +.iconIncorrect { + color: $-red; + font-size: $font-size-heading * 1.25; +} + +.incorrect { + background-color: $-red; +} + +.overall { + background-color: $-blue; +} + +.profileCircle { + align-items: center; + background-color: #DD7373; + border: 2px solid #FAC4C4; + border-radius: 50%; + color: $-white; + display: flex; + font-size: $font-size-lg; + height: $buffer-lg * 2.5; + justify-content: center; + width: $buffer-lg * 2.5; +} + +.profileDetails { + color: #A9A9A9; + display: flex; + flex-direction: row; + gap: $buffer; +} + +.profileName { + color: $-black; + font-size: $font-size-lg; + font-weight: 700; +} + +.question { + border: 1px solid #DADCE0; + border-radius: $border-radius-sm; + padding: $buffer * 0.75 $buffer; +} + +.questionContent { + align-items: center; + display: flex; + gap: $buffer * 0.5; +} + +.questionImage { + flex: 1; + text-align: center; +} + +.questionText { + font-weight: 600; + padding: $buffer 0; +} + +.questionTitle { + align-items: center; + display: flex; + font-weight: 700; + gap: $buffer * 0.75; + justify-content: space-between; + + div:last-of-type { + align-items: center; + display: flex; + margin-left: auto; + } +} + +.questions { + display: flex; + flex-direction: column; + gap: $buffer * 0.5; +} + +.responseOption { + border: 1px solid #DADCE0; + border-radius: $border-radius-sm; + padding: $buffer-lg * 0.25 $buffer-lg * 0.5; + text-align: center; + font-weight: 500; + &.correctOption { + border: 2px solid $-aquamarine; + } + &.incorrectResponse { + background-color: $-red; + color: $-white; + } + &.correctResponse { + background-color: $-aquamarine; + color: $-white; + } +} + +.responseOptions { + display: flex; + flex: 1; + flex-direction: column; + gap: $buffer; +} + +.responsesWithImage { + display: flex; + flex-direction: column; + gap: $buffer; +} + +.responsesWithoutImage { + display: flex; + flex-direction: row; + gap: $buffer; + justify-content: space-around; + + .responseOption { + width: 100%; + } +} + +.score { + .scoreRow { + display: flex; + font-size: $font-size-lg; + gap: $buffer * 0.5; + justify-content: space-between; + margin-top: $buffer; + } +} + +.title { + @extend %heading; + font-size: $font-size; +} From 91a5ed894f32b951cf0fa6379dd0d3ba8f093ea9 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Wed, 30 Jul 2025 17:50:54 +0700 Subject: [PATCH 27/51] rearrange css class --- src/components/results/generic/style.scss | 3 +- src/components/survey/generic/style.scss | 88 +++++++++++------------ 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/src/components/results/generic/style.scss b/src/components/results/generic/style.scss index a661f694..c85744b2 100644 --- a/src/components/results/generic/style.scss +++ b/src/components/results/generic/style.scss @@ -174,9 +174,10 @@ .responseOption { border: 1px solid #DADCE0; border-radius: $border-radius-sm; + font-weight: 500; padding: $buffer-lg * 0.25 $buffer-lg * 0.5; text-align: center; - font-weight: 500; + &.correctOption { border: 2px solid $-aquamarine; } diff --git a/src/components/survey/generic/style.scss b/src/components/survey/generic/style.scss index 249cebb3..364bafbf 100644 --- a/src/components/survey/generic/style.scss +++ b/src/components/survey/generic/style.scss @@ -1,5 +1,16 @@ @import "style/helpers"; +button.btnPrimary { + background-color: $-blue; + color: $-white; + display: inline-block; + padding: $buffer-lg * .5 $buffer; + text-align: center; + border: transparent; + max-width: 110px; + margin: $buffer-lg auto; +} + .container { @extend %container; display: flex; @@ -74,6 +85,36 @@ } } +.footer { + display: flex; + justify-content: flex-end; + gap: $buffer * 0.25; + + .cancelBtn { + @include buttonLight("text-dark"); + @include theme("border-color", "border-light"); + border: $border-width solid; + border-radius: $border-radius-sm * 0.5; + margin: $buffer * 0.5; + } + + .btnPrimary { + margin: $buffer * 0.5; + } +} + +.grayDivider { + color: #555555; + width: 100%; + margin: $buffer-lg auto $buffer-lg; + line-height: inherit; + clear: both; + user-select: none; + break-after: page; + border: $border-width solid #e8e8ec; + border-radius: $border-radius-sm; +} + .questionSet { width: 100%; align-items: center; @@ -90,11 +131,6 @@ } } -.responseOptions{ - display: flex; - justify-content: space-between; -} - .response { padding: $buffer * 0.25 $buffer * 0.75; text-align: center; @@ -118,45 +154,7 @@ } } -button.btnPrimary { - background-color: $-blue; - color: $-white; - display: inline-block; - padding: $buffer-lg * .5 $buffer; - text-align: center; - border: transparent; - max-width: 110px; - margin: $buffer-lg auto; -} - -.hide { display: none; } - -.grayDivider { - color: #555555; - width: 100%; - margin: $buffer-lg auto $buffer-lg; - line-height: inherit; - clear: both; - user-select: none; - break-after: page; - border: $border-width solid #e8e8ec; - border-radius: $border-radius-sm; -} - -.footer { +.responseOptions{ display: flex; - justify-content: flex-end; - gap: $buffer * 0.25; - - .cancelBtn { - @include buttonLight("text-dark"); - @include theme("border-color", "border-light"); - border: $border-width solid; - border-radius: $border-radius-sm * 0.5; - margin: $buffer * 0.5; - } - - .btnPrimary { - margin: $buffer * 0.5; - } + justify-content: space-between; } \ No newline at end of file From c4bd1f8d2bdfcb0b64f98880e4a19c087dda1347 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Fri, 1 Aug 2025 17:56:56 +0700 Subject: [PATCH 28/51] use translate --- src/components/results/generic/breakdown.js | 8 +++++--- src/components/results/generic/header.js | 8 +++++--- src/components/results/generic/question.js | 4 +++- src/components/results/generic/result_actions.js | 4 +++- src/components/results/generic/score.js | 10 ++++++---- src/components/survey/generic/index.js | 2 +- src/lib/i18n-data/en-us.json | 11 +++++++++++ 7 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/components/results/generic/breakdown.js b/src/components/results/generic/breakdown.js index 2d1643fc..3c2e9321 100644 --- a/src/components/results/generic/breakdown.js +++ b/src/components/results/generic/breakdown.js @@ -1,10 +1,12 @@ import PropTypes from "prop-types"; import {useState} from "react"; +import useTranslate from "lib/hooks/use-translate"; import Question from "./question"; import style from "./style.scss"; export default function Breakdown({assessmentResult}) { const [showAll, setShowAll] = useState(false); + const translate = useTranslate(); const showHideAll = () => { setShowAll(!showAll); }; @@ -13,12 +15,12 @@ export default function Breakdown({assessmentResult}) {
-
Breakdown
- Here is the breakdown of the questions you answered in the assessment +
{translate("results.generic.breakdown")}
+ {translate("results.generic.breakdown_description")}
diff --git a/src/components/results/generic/header.js b/src/components/results/generic/header.js index 989a5736..8f5eb039 100644 --- a/src/components/results/generic/header.js +++ b/src/components/results/generic/header.js @@ -1,12 +1,14 @@ import propTypes from "prop-types"; import SimpleDropdown from "components/common/dropdown/simple"; +import useTranslate from "lib/hooks/use-translate"; import i18nData from "lib/i18n-data"; import style from "./style.scss"; export default function Header({profile, assessment}) { + const translate = useTranslate(); const initials = `${profile.firstName.charAt(0)}${profile.lastName.charAt(0)}`.toUpperCase(); const surveyName = assessment ? assessment.surveyName : ""; - const completedAt = assessment ? assessment.completedAt : ""; + const completedAt = assessment ? assessment.completedAt : []; const assessmentLocale = assessment ? assessment.localeKey : "en-US"; const localeOptions = Object.keys(i18nData).map((key) => ({ value: key, @@ -20,7 +22,7 @@ export default function Header({profile, assessment}) {
{profile.firstName} {profile.lastName}
{surveyName}
-
Completed on: {completedAt}
+
{translate("results.generic.completed_on", {date: completedAt[0], time: completedAt[1]})}
@@ -41,7 +43,7 @@ Header.propTypes = { lastName: propTypes.string.isRequired }).isRequired, assessment: propTypes.shape({ - completedAt: propTypes.string, + completedAt: propTypes.arrayOf(propTypes.string).isRequired, surveyName: propTypes.string.isRequired, localeKey: propTypes.string.isRequired }).isRequired diff --git a/src/components/results/generic/question.js b/src/components/results/generic/question.js index ebf42a6d..76f7374d 100644 --- a/src/components/results/generic/question.js +++ b/src/components/results/generic/question.js @@ -2,9 +2,11 @@ import {faCheck, faXmark, faChevronDown, faChevronUp} from "@fortawesome/free-so import PropTypes from "prop-types"; import {useState, useEffect} from "react"; import Icon from "components/common/icon"; +import useTranslate from "lib/hooks/use-translate"; import style from "./style.scss"; export default function Question({question, index, showState}) { + const translate = useTranslate(); const [showContent, setShowContent] = useState(false); const responsesClassName = question.setImage ? style.responsesWithImage @@ -45,7 +47,7 @@ export default function Question({question, index, showState}) { ? : }
-
Question {index + 1}
+
{translate("cognitive_question_alt_text")} {index + 1}
)} - {showInstructions - && ( - setShowInstructions(false)} - containerClass={style.modalContainer} - > - - {assessment.survey.instructions} - -
-
- - -
- - )} + {showInstructions && ( + setShowInstructions(false)} + containerClass={style.modalContainer} + > + + {assessment.survey.instructions} + +
+
+ + +
+ + )} ); } From 80c930fa1077364aee482450c9493ab59f29f1e4 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Wed, 13 Aug 2025 14:31:22 +0700 Subject: [PATCH 31/51] remove header and actions --- src/components/results/generic/header.js | 57 ------------------- src/components/results/generic/index.js | 5 -- .../results/generic/result_actions.js | 20 ------- 3 files changed, 82 deletions(-) delete mode 100644 src/components/results/generic/header.js delete mode 100644 src/components/results/generic/result_actions.js diff --git a/src/components/results/generic/header.js b/src/components/results/generic/header.js deleted file mode 100644 index c22bec97..00000000 --- a/src/components/results/generic/header.js +++ /dev/null @@ -1,57 +0,0 @@ -import propTypes from "prop-types"; -import {useState} from "react"; -import SimpleDropdown from "components/common/dropdown/simple"; -import useTranslate from "lib/hooks/use-translate"; -import i18nData from "lib/i18n-data"; -import style from "./style.scss"; - -export default function Header({profile, assessment}) { - const translate = useTranslate(); - const initials = `${profile.firstName.charAt(0)}${profile.lastName.charAt(0)}`.toUpperCase(); - const surveyName = assessment ? assessment.surveyName : ""; - const assessmentLocale = assessment ? assessment.localeKey : "en-US"; - - const [locale, setLocale] = useState(assessmentLocale); - const completedAt = assessment ? new Date(Number(assessment.completedAt)) : ""; - const formattedCompletedAt = assessment - ? completedAt.toLocaleString(locale, {year: "numeric", month: "long", day: "2-digit", hour: "2-digit", minute: "2-digit"}) - : ""; - const localeOptions = Object.keys(i18nData).map((key) => ({ - value: key, - name: i18nData[key].name - })); - - return ( -
-
-
{initials}
-
-
{profile.firstName} {profile.lastName}
-
{surveyName}
-
{translate("results.generic.completed_on")} {formattedCompletedAt}
-
-
-
- setLocale(value)} - /> -
-
- ); -} - -Header.propTypes = { - profile: propTypes.shape({ - firstName: propTypes.string.isRequired, - lastName: propTypes.string.isRequired - }).isRequired, - assessment: propTypes.shape({ - completedAt: propTypes.string.isRequired, - surveyName: propTypes.string.isRequired, - localeKey: propTypes.string.isRequired - }).isRequired -}; diff --git a/src/components/results/generic/index.js b/src/components/results/generic/index.js index 419bdb35..a3655d8b 100644 --- a/src/components/results/generic/index.js +++ b/src/components/results/generic/index.js @@ -3,15 +3,12 @@ import useGraphql from "lib/hooks/use-graphql"; import useHttp from "lib/hooks/use-http"; import useResults from "lib/hooks/use-results"; import Breakdown from "./breakdown"; -import Header from "./header"; -import ResultActions from "./result_actions"; import Score from "./score"; import style from "./style.scss"; export default function Generic() { const [result, setResult] = useState(null); const assessment = useResults({surveyType: "generic"}); - const profile = result ? result.profile : {}; const assessmentResult = result ? result.assessment : {}; const graphQL = useGraphql(); @@ -35,9 +32,7 @@ export default function Generic() {
{result && (
-
-
diff --git a/src/components/results/generic/result_actions.js b/src/components/results/generic/result_actions.js deleted file mode 100644 index 14012dcb..00000000 --- a/src/components/results/generic/result_actions.js +++ /dev/null @@ -1,20 +0,0 @@ -import {faPrint, faQuestionCircle, faEnvelopeOpen, faArrowUpFromBracket, faArrowRotateLeft} from "@fortawesome/free-solid-svg-icons"; -import Icon from "components/common/icon"; -import useTranslate from "lib/hooks/use-translate"; -import style from "./style.scss"; - -export default function ResultActions() { - const translate = useTranslate(); - return ( -
-
{translate("results.generic.assessment_results")}
-
- - - - - -
-
- ); -} From 3241452f18c872841f7872039790444604a1606e Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Mon, 18 Aug 2025 16:52:30 +0700 Subject: [PATCH 32/51] set loaded active assessment + remove redundant generic result css --- .../container/hooks/use-order-effect.js | 6 +++ src/components/results/generic/style.scss | 45 +------------------ src/components/survey/generic/index.js | 1 + src/lib/recoil/assessment.js | 2 +- 4 files changed, 9 insertions(+), 45 deletions(-) diff --git a/src/components/container/hooks/use-order-effect.js b/src/components/container/hooks/use-order-effect.js index 42a11c33..4b51406c 100644 --- a/src/components/container/hooks/use-order-effect.js +++ b/src/components/container/hooks/use-order-effect.js @@ -35,6 +35,12 @@ export default function useOrderEffect() { if(nextAssessment) { setActive({...nextAssessment}); } return; + } else { + // Update base active assessment from baseAssessmentState with loaded assessment + if(active.completed === undefined) { + const currentAssessment = order.assessments.find(({id}) => id === active.id); + setActive({...currentAssessment}); + } } // NOTE: Show personality results diff --git a/src/components/results/generic/style.scss b/src/components/results/generic/style.scss index c85744b2..eeeb8d90 100644 --- a/src/components/results/generic/style.scss +++ b/src/components/results/generic/style.scss @@ -1,16 +1,5 @@ @import "style/helpers"; -.actions { - color: #A9A9A9; - display: flex; - gap: $buffer; -} - -.assessmentResult { - display: flex; - justify-content: space-between; -} - .breakdown { display: flex; flex-direction: column; @@ -77,13 +66,7 @@ justify-content: space-between; } -.header { - background-color: $-white; - border-bottom: 1px solid #DADCE0; - display: flex; - justify-content: space-between; - padding: $buffer $buffer-lg; -} + .iconCorrect { color: $-aquamarine; @@ -103,32 +86,6 @@ background-color: $-blue; } -.profileCircle { - align-items: center; - background-color: #DD7373; - border: 2px solid #FAC4C4; - border-radius: 50%; - color: $-white; - display: flex; - font-size: $font-size-lg; - height: $buffer-lg * 2.5; - justify-content: center; - width: $buffer-lg * 2.5; -} - -.profileDetails { - color: #A9A9A9; - display: flex; - flex-direction: row; - gap: $buffer; -} - -.profileName { - color: $-black; - font-size: $font-size-lg; - font-weight: 700; -} - .question { border: 1px solid #DADCE0; border-radius: $border-radius-sm; diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index 8b913ead..c9317b53 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -25,6 +25,7 @@ export default function Generic() { const [submitAttempts, setSubmitAttempts] = useState(0); const assessment = useAssessment({surveyType: "generic"}); + if(assessment?.completedAt) { return; } const assessmentCacheKey = useCacheKey("assessment"); const cache = useCache(); const questionSets = assessment ? assessment.survey.questionSets : []; diff --git a/src/lib/recoil/assessment.js b/src/lib/recoil/assessment.js index 8c8f4dfd..84becbea 100644 --- a/src/lib/recoil/assessment.js +++ b/src/lib/recoil/assessment.js @@ -127,7 +127,7 @@ export const genericAssessmentQuery = selectorFamily({ } const assessment = response.data.genericSurveyQuestions; - if(!assessment?.length || !assessment?.completedAt) { return assessment; } + if(!assessment?.completedAt) { return assessment; } cache.set(cacheKey, assessment); From bfafcb5ede2f1cdb315b2b86b3cfb697de98cc15 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Mon, 18 Aug 2025 17:54:04 +0700 Subject: [PATCH 33/51] bump version to 3.9.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7e06b500..dc47934a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "traitify-widgets", - "version": "3.8.1", + "version": "3.9.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "traitify-widgets", - "version": "3.8.1", + "version": "3.9.0", "license": "MIT", "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.5.2", diff --git a/package.json b/package.json index 692a21aa..3cb5e322 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "traitify-widgets", - "version": "3.8.1", + "version": "3.9.0", "description": "Traitiy Widgets", "repository": { "type": "git", From 1770629fba6a0e57ea4dd438bbc1b6e373a79ed1 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Mon, 18 Aug 2025 18:23:02 +0700 Subject: [PATCH 34/51] extend box and container --- src/components/results/generic/style.scss | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/components/results/generic/style.scss b/src/components/results/generic/style.scss index eeeb8d90..9eed07be 100644 --- a/src/components/results/generic/style.scss +++ b/src/components/results/generic/style.scss @@ -34,14 +34,11 @@ .contentBody { display: flex; flex-direction: column; - gap: $buffer-lg; - margin: $buffer-lg auto; - width: $breakpoint-lg; + @extend %container; & > div { - border: 1px solid #DADCE0; - border-radius: $border-radius-sm; - padding: $buffer; + @extend %box; + margin-bottom: $buffer-section; } } From 1d57f34783aa7d23540d7323fbcbf5a1f2409f99 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Mon, 18 Aug 2025 18:23:51 +0700 Subject: [PATCH 35/51] extend container --- src/components/results/generic/style.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/results/generic/style.scss b/src/components/results/generic/style.scss index 9eed07be..63477abf 100644 --- a/src/components/results/generic/style.scss +++ b/src/components/results/generic/style.scss @@ -38,6 +38,7 @@ & > div { @extend %box; + @extend %container; margin-bottom: $buffer-section; } } From 2825d0fdff6f9f49309a95d88fdb2f5a44a474eb Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Mon, 18 Aug 2025 18:53:51 +0700 Subject: [PATCH 36/51] refactor btn border --- src/components/results/generic/style.scss | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/components/results/generic/style.scss b/src/components/results/generic/style.scss index 63477abf..407709ef 100644 --- a/src/components/results/generic/style.scss +++ b/src/components/results/generic/style.scss @@ -14,7 +14,6 @@ width: 100%; button { - border: 1px solid #DADCE0; border-radius: $border-radius-sm * 0.5; height: 48px; margin: $buffer $buffer * 0.5; @@ -27,6 +26,7 @@ height: $buffer * 2; margin: $buffer-sm * 0.5; padding: $buffer-sm * 0.5 $buffer-sm * 1.25; + border: 1px solid #DADCE0; } } } @@ -36,7 +36,7 @@ flex-direction: column; @extend %container; - & > div { + .score, .breakdown { @extend %box; @extend %container; margin-bottom: $buffer-section; @@ -85,9 +85,7 @@ } .question { - border: 1px solid #DADCE0; - border-radius: $border-radius-sm; - padding: $buffer * 0.75 $buffer; + @extend %box; } .questionContent { @@ -123,12 +121,11 @@ .questions { display: flex; flex-direction: column; - gap: $buffer * 0.5; + gap: $buffer; } .responseOption { - border: 1px solid #DADCE0; - border-radius: $border-radius-sm; + @extend %box; font-weight: 500; padding: $buffer-lg * 0.25 $buffer-lg * 0.5; text-align: center; From 4529d366946db13d1a5b27718fdb6643415fc3df Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Wed, 20 Aug 2025 12:01:39 +0700 Subject: [PATCH 37/51] return after setActive --- src/components/container/hooks/use-order-effect.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/components/container/hooks/use-order-effect.js b/src/components/container/hooks/use-order-effect.js index 4b51406c..b5ba03a3 100644 --- a/src/components/container/hooks/use-order-effect.js +++ b/src/components/container/hooks/use-order-effect.js @@ -35,12 +35,6 @@ export default function useOrderEffect() { if(nextAssessment) { setActive({...nextAssessment}); } return; - } else { - // Update base active assessment from baseAssessmentState with loaded assessment - if(active.completed === undefined) { - const currentAssessment = order.assessments.find(({id}) => id === active.id); - setActive({...currentAssessment}); - } } // NOTE: Show personality results @@ -58,9 +52,10 @@ export default function useOrderEffect() { // NOTE: Start next assessment if(currentAssessment.completed) { const nextAssessment = order.assessments.find(({completed}) => !completed); - if(nextAssessment) { setActive({...nextAssessment}); } - - return; + if(nextAssessment) { + setActive({...nextAssessment}); + return; + } } // NOTE: Load updates for active assessment From 71e7faf355fca63b53e437353145fb8d382c29bb Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Thu, 21 Aug 2025 16:14:25 +0700 Subject: [PATCH 38/51] name format --- src/components/container/hooks/use-order-effect.js | 1 + src/components/index.js | 4 ++-- src/components/results/generic/index.js | 2 +- src/components/results/index.js | 4 ++-- src/components/survey/generic/index.js | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/container/hooks/use-order-effect.js b/src/components/container/hooks/use-order-effect.js index b5ba03a3..b154f9c3 100644 --- a/src/components/container/hooks/use-order-effect.js +++ b/src/components/container/hooks/use-order-effect.js @@ -54,6 +54,7 @@ export default function useOrderEffect() { const nextAssessment = order.assessments.find(({completed}) => !completed); if(nextAssessment) { setActive({...nextAssessment}); + return; } } diff --git a/src/components/index.js b/src/components/index.js index d1dcac71..be85e838 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -30,7 +30,7 @@ import RecommendationChart from "./results/recommendation/chart"; import Status from "./status"; import Survey from "./survey"; import CognitiveSurvey from "./survey/cognitive"; -import Generic from "./survey/generic"; +import GenericSurvey from "./survey/generic"; import PersonalitySurvey from "./survey/personality"; export default { @@ -91,7 +91,7 @@ export default { Survey: { Cognitive: CognitiveSurvey, Container: Survey, - Generic, + GenericSurvey, Personality: PersonalitySurvey } }; diff --git a/src/components/results/generic/index.js b/src/components/results/generic/index.js index a3655d8b..cab5febf 100644 --- a/src/components/results/generic/index.js +++ b/src/components/results/generic/index.js @@ -6,7 +6,7 @@ import Breakdown from "./breakdown"; import Score from "./score"; import style from "./style.scss"; -export default function Generic() { +export default function GenericResults() { const [result, setResult] = useState(null); const assessment = useResults({surveyType: "generic"}); const assessmentResult = result ? result.assessment : {}; diff --git a/src/components/results/index.js b/src/components/results/index.js index de35ced2..40e17c12 100644 --- a/src/components/results/index.js +++ b/src/components/results/index.js @@ -4,7 +4,7 @@ import EmployeeReport from "components/report/employee"; import ManagerReport from "components/report/manager"; import Cognitive from "components/results/cognitive"; import FinancialRiskResults from "components/results/financial-risk"; -import Generic from "components/results/generic"; +import GenericResults from "components/results/generic"; import Skipped from "components/status/skipped"; import useActive from "lib/hooks/use-active"; import useComponentEvents from "lib/hooks/use-component-events"; @@ -22,7 +22,7 @@ export default function Results() { if(active.skipped) { return ; } if(!active.completed) { return null; } if(active.surveyType === "cognitive") { return ; } - if(active.surveyType === "generic") { return ; } + if(active.surveyType === "generic") { return ; } if(active.surveyType !== "personality") { return null; } if(!results) { return null; } if(results.scoring_scale === "LIKERT_CUMULATIVE_POMP") { diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index c9317b53..731c8a79 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -17,7 +17,7 @@ import Container from "./container"; import QuestionSet from "./question-set"; import style from "./style.scss"; -export default function Generic() { +export default function GenericSurvey() { const [questionSetIndex, setQuestionSetIndex] = useState(0); const [answers, setAnswers] = useState([]); const [showInstructions, setShowInstructions] = useState(false); From a51a651bfc8f6c1b707aad546e4c09de27808d7a Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Thu, 21 Aug 2025 16:59:39 +0700 Subject: [PATCH 39/51] get result if completed --- src/components/results/generic/index.js | 28 +++---------------------- src/lib/recoil/assessment.js | 11 ++++++++-- 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/src/components/results/generic/index.js b/src/components/results/generic/index.js index cab5febf..8d6ad7bd 100644 --- a/src/components/results/generic/index.js +++ b/src/components/results/generic/index.js @@ -1,40 +1,18 @@ -import {useState, useEffect} from "react"; -import useGraphql from "lib/hooks/use-graphql"; -import useHttp from "lib/hooks/use-http"; import useResults from "lib/hooks/use-results"; import Breakdown from "./breakdown"; import Score from "./score"; import style from "./style.scss"; export default function GenericResults() { - const [result, setResult] = useState(null); - const assessment = useResults({surveyType: "generic"}); - const assessmentResult = result ? result.assessment : {}; - - const graphQL = useGraphql(); - const http = useHttp(); - - useEffect(() => { - if(assessment === null) return; - const query = graphQL.generic.result; - const variables = {assessmentID: assessment.id}; - - http.post(graphQL.generic.path, {query, variables}).then(({data, errors}) => { - if(!errors && data.genericAssessmentResult) { - setResult(data.genericAssessmentResult); - } else { - console.warn(errors || data); // eslint-disable-line no-console - } - }); - }, [assessment]); + const result = useResults({surveyType: "generic"}); return (
{result && (
- - + +
)} diff --git a/src/lib/recoil/assessment.js b/src/lib/recoil/assessment.js index 84becbea..1477f7bd 100644 --- a/src/lib/recoil/assessment.js +++ b/src/lib/recoil/assessment.js @@ -129,9 +129,16 @@ export const genericAssessmentQuery = selectorFamily({ const assessment = response.data.genericSurveyQuestions; if(!assessment?.completedAt) { return assessment; } - cache.set(cacheKey, assessment); + const query = GraphQL.generic.result; + const variables = {assessmentID: id}; - return assessment; + const resultResponse = await http.post(GraphQL.generic.path, {query, variables}); + if(resultResponse.errors) { return null; } + const result = resultResponse.data.genericAssessmentResult.assessment; + + cache.set(cacheKey, result); + + return result; }, key: "assessment/generic" }); From 2c2def5f1fc9e46395bba0518047833d4ac2d5bb Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Fri, 29 Aug 2025 18:00:45 +0700 Subject: [PATCH 40/51] not return cached survey if completed --- src/lib/recoil/assessment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/recoil/assessment.js b/src/lib/recoil/assessment.js index 1477f7bd..10771888 100644 --- a/src/lib/recoil/assessment.js +++ b/src/lib/recoil/assessment.js @@ -111,7 +111,7 @@ export const genericAssessmentQuery = selectorFamily({ const cache = get(cacheState); const cacheKey = get(safeCacheKeyState({id, type: "assessment"})); const cached = cache.get(cacheKey); - if(cached) { return cached; } + if(cached && !cached.completed) { return cached; } const GraphQL = get(graphqlState); const http = get(httpState); From 1ba75f106566c987a0531c1457d3b10b62530e0d Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Fri, 5 Sep 2025 18:49:05 +0700 Subject: [PATCH 41/51] not call to result api --- src/components/survey/generic/index.js | 3 +- src/lib/graphql/generic.js | 52 +++++++++++--------------- src/lib/recoil/assessment.js | 13 ++----- 3 files changed, 27 insertions(+), 41 deletions(-) diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index 731c8a79..46ef1172 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -61,7 +61,8 @@ export default function GenericSurvey() { if(!errors && data.submitGenericAssessmentAnswers) { setShowConclusions(true); setTimeout(() => { - cache.set(assessmentCacheKey, {...assessment, completed: true}); + const response = data.submitGenericAssessmentAnswers; + cache.set(assessmentCacheKey, {...response, completed: true}); refreshAssessment(); }, 5000); } else { diff --git a/src/lib/graphql/generic.js b/src/lib/graphql/generic.js index 2ce05a9d..dbdc02f8 100644 --- a/src/lib/graphql/generic.js +++ b/src/lib/graphql/generic.js @@ -46,37 +46,16 @@ export const questions = ` } } } - } - } -`; - -export const result = ` - query($assessmentID: ID!) { - genericAssessmentResult(assessmentId: $assessmentID) { - profile { - profileId - firstName - lastName - } - assessment { - localeKey - profileId - surveyId - surveyName - startedAt - totalCorrectResponses - completedAt - responses { - questionId - questionText - selectedResponseOptionId - setImage + responses { + questionId + questionText + selectedResponseOptionId + setImage + isCorrect + responseOptions { + responseOptionId + responseOptionText isCorrect - responseOptions { - responseOptionId - responseOptionText - isCorrect - } } } } @@ -91,6 +70,19 @@ export const update = ` profileId startedAt completedAt + totalCorrectResponses + responses { + questionId + questionText + selectedResponseOptionId + setImage + isCorrect + responseOptions { + responseOptionId + responseOptionText + isCorrect + } + } } } `; diff --git a/src/lib/recoil/assessment.js b/src/lib/recoil/assessment.js index 10771888..84becbea 100644 --- a/src/lib/recoil/assessment.js +++ b/src/lib/recoil/assessment.js @@ -111,7 +111,7 @@ export const genericAssessmentQuery = selectorFamily({ const cache = get(cacheState); const cacheKey = get(safeCacheKeyState({id, type: "assessment"})); const cached = cache.get(cacheKey); - if(cached && !cached.completed) { return cached; } + if(cached) { return cached; } const GraphQL = get(graphqlState); const http = get(httpState); @@ -129,16 +129,9 @@ export const genericAssessmentQuery = selectorFamily({ const assessment = response.data.genericSurveyQuestions; if(!assessment?.completedAt) { return assessment; } - const query = GraphQL.generic.result; - const variables = {assessmentID: id}; - - const resultResponse = await http.post(GraphQL.generic.path, {query, variables}); - if(resultResponse.errors) { return null; } - const result = resultResponse.data.genericAssessmentResult.assessment; - - cache.set(cacheKey, result); + cache.set(cacheKey, assessment); - return result; + return assessment; }, key: "assessment/generic" }); From 5d420773138629dc0e2d025a9555838989571388 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Mon, 8 Sep 2025 16:39:22 +0700 Subject: [PATCH 42/51] render totalCorrectResponses --- src/lib/graphql/generic.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/graphql/generic.js b/src/lib/graphql/generic.js index dbdc02f8..06ac68e8 100644 --- a/src/lib/graphql/generic.js +++ b/src/lib/graphql/generic.js @@ -15,6 +15,7 @@ export const create = ` profileId startedAt completedAt + localeKey } } `; @@ -27,6 +28,8 @@ export const questions = ` profileId startedAt completedAt + totalCorrectResponses + localeKey survey { id name From 4a48daff81115fb644c73cf21888f67055ba1ad6 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Thu, 18 Sep 2025 18:13:31 +0700 Subject: [PATCH 43/51] let question set in column for mobile --- src/components/results/generic/style.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/results/generic/style.scss b/src/components/results/generic/style.scss index 407709ef..c4809331 100644 --- a/src/components/results/generic/style.scss +++ b/src/components/results/generic/style.scss @@ -92,11 +92,15 @@ align-items: center; display: flex; gap: $buffer * 0.5; + @include max-width("xs") { flex-direction: column; } } .questionImage { flex: 1; text-align: center; + img { + max-width: 100%; + } } .questionText { @@ -174,6 +178,7 @@ gap: $buffer * 0.5; justify-content: space-between; margin-top: $buffer; + @include max-width("xs") { flex-direction: column; } } } From 5ac1e92a0e4119b0eb6da9a2a5666d41d19c77bb Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Fri, 19 Sep 2025 14:35:58 +0700 Subject: [PATCH 44/51] survey mobile --- src/components/survey/generic/responses.js | 8 +++----- src/components/survey/generic/style.scss | 10 ++++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/components/survey/generic/responses.js b/src/components/survey/generic/responses.js index 1d37d208..8cc1e961 100644 --- a/src/components/survey/generic/responses.js +++ b/src/components/survey/generic/responses.js @@ -6,7 +6,8 @@ export default function Responses({responseOptions = [], updateAnswer}) { const buttonClass = ["traitify--response-button", style.response].join(" "); const longTextResponses = responseOptions.some((option) => option.text.length > 20); const buttonWidth = longTextResponses ? "100%" : "auto"; - const optionsDirection = longTextResponses ? "column" : "row"; + const directionClass = longTextResponses ? style.flexDirectionColumn : ""; + const responseOptionsClass = [style.responseOptions, directionClass].join(" "); const [activeButton, setActiveButton] = useState(null); const selectOption = (optionId) => { setActiveButton(optionId); @@ -14,10 +15,7 @@ export default function Responses({responseOptions = [], updateAnswer}) { }; return ( -
+
{responseOptions.map((option) => ( diff --git a/src/components/survey/generic/question-set.js b/src/components/survey/generic/question-set.js index d69258b4..68b21025 100644 --- a/src/components/survey/generic/question-set.js +++ b/src/components/survey/generic/question-set.js @@ -3,7 +3,7 @@ import {useEffect, useState} from "react"; import Responses from "./responses"; import style from "./style.scss"; -export default function QuestionSet({next, questionSet, updateAnswer}) { +export default function QuestionSet({onNext, questionSet, updateAnswer}) { const questionSetClass = [style.questionSet].join(" "); const [selectedOptions, setSelectedOptions] = useState([]); const setFinished = questionSet.questions.length === selectedOptions.length; @@ -14,7 +14,7 @@ export default function QuestionSet({next, questionSet, updateAnswer}) { useEffect(() => { if(!setFinished) return; - next(); + onNext(); }, [setFinished]); return ( @@ -35,7 +35,7 @@ export default function QuestionSet({next, questionSet, updateAnswer}) { } QuestionSet.propTypes = { - next: PropTypes.func.isRequired, + onNext: PropTypes.func.isRequired, questionSet: PropTypes.shape({ text: PropTypes.string.isRequired, questions: PropTypes.arrayOf(PropTypes.shape({ From e8f085cf4d1aa41df192bf2fac8034c029e0ecf1 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Fri, 19 Sep 2025 15:39:44 +0700 Subject: [PATCH 48/51] refactor with early return --- src/components/results/generic/index.js | 13 ++++++------- src/components/results/generic/question.js | 17 ++++++++--------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/components/results/generic/index.js b/src/components/results/generic/index.js index 8d6ad7bd..262de5f9 100644 --- a/src/components/results/generic/index.js +++ b/src/components/results/generic/index.js @@ -5,17 +5,16 @@ import style from "./style.scss"; export default function GenericResults() { const result = useResults({surveyType: "generic"}); + if(!result) { return null; } return (
- {result && ( -
-
- - -
+
+
+ +
- )} +
); } diff --git a/src/components/results/generic/question.js b/src/components/results/generic/question.js index 8d1a2e91..c75faff6 100644 --- a/src/components/results/generic/question.js +++ b/src/components/results/generic/question.js @@ -24,17 +24,16 @@ export default function Question({question, index, showState}) { }; const optionClassName = (option) => { - let className = ""; if(question.isCorrect) { - className = option.isCorrect ? style.correctResponse : ""; - } else { - if(option.isCorrect) { - className = style.correctOption; - } else if(option.responseOptionId === question.selectedResponseOptionId) { - className = style.incorrectResponse; - } + return option.isCorrect ? style.correctResponse : ""; } - return className; + if(option.isCorrect) { + return style.correctOption; + } + if(option.responseOptionId === question.selectedResponseOptionId) { + return style.incorrectResponse; + } + return ""; }; return ( From d52a623933bd64407622711d34ff1f4bb8237690 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Fri, 19 Sep 2025 16:25:55 +0700 Subject: [PATCH 49/51] refactor progress bar --- src/components/survey/generic/container.js | 20 ------------------- src/components/survey/generic/index.js | 12 +++++------ src/components/survey/generic/progress-bar.js | 14 +++++++++++++ src/components/survey/generic/style.scss | 2 ++ 4 files changed, 22 insertions(+), 26 deletions(-) delete mode 100644 src/components/survey/generic/container.js create mode 100644 src/components/survey/generic/progress-bar.js diff --git a/src/components/survey/generic/container.js b/src/components/survey/generic/container.js deleted file mode 100644 index 491ad244..00000000 --- a/src/components/survey/generic/container.js +++ /dev/null @@ -1,20 +0,0 @@ -import PropTypes from "prop-types"; -import style from "./style.scss"; - -export default function Container({children, progress}) { - return ( -
- {progress < 100 && ( -
-
-
- )} - {children} -
- ); -} - -Container.propTypes = { - children: PropTypes.node.isRequired, - progress: PropTypes.number.isRequired -}; diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index 1c5255e9..2014e614 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -13,7 +13,7 @@ import useGraphql from "lib/hooks/use-graphql"; import useHttp from "lib/hooks/use-http"; import useTranslate from "lib/hooks/use-translate"; import {activeAssessmentQuery} from "lib/recoil"; -import Container from "./container"; +import ProgressBar from "./progress-bar"; import QuestionSet from "./question-set"; import style from "./style.scss"; @@ -37,7 +37,6 @@ export default function GenericSurvey() { const graphQL = useGraphql(); const http = useHttp(); const translate = useTranslate(); - const props = {progress}; const refreshAssessment = useRecoilRefresher(activeAssessmentQuery); const updateAnswer = (questionId, selectedOptionId) => { @@ -88,17 +87,18 @@ export default function GenericSurvey() { if(showConclusions) { return ( - +
{assessment.survey.conclusions} - +
); } return ( - +
+ {currentQuestionSet && ( )} - +
); } diff --git a/src/components/survey/generic/progress-bar.js b/src/components/survey/generic/progress-bar.js new file mode 100644 index 00000000..e94afeea --- /dev/null +++ b/src/components/survey/generic/progress-bar.js @@ -0,0 +1,14 @@ +import PropTypes from "prop-types"; +import style from "./style.scss"; + +export default function ProgressBar({progress}) { + return ( +
+
+
+ ); +} + +ProgressBar.propTypes = { + progress: PropTypes.number.isRequired +}; diff --git a/src/components/survey/generic/style.scss b/src/components/survey/generic/style.scss index 06b7b02b..c1f65346 100644 --- a/src/components/survey/generic/style.scss +++ b/src/components/survey/generic/style.scss @@ -52,6 +52,7 @@ button.btnPrimary { &.back { padding: $buffer * 0.5 $buffer-lg * 0.5; width: 150px; + @include max-width("xs") { width: 100px; } .icon { margin-right: $buffer-sm; @@ -111,6 +112,7 @@ button.btnPrimary { .btnPrimary { margin: $buffer * 0.5; + @include max-width("xs") { padding: $buffer * 0.25 $buffer; } } } From 034eb570de57d9b3890a51b0c6ff2af5da6f4b3f Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Fri, 19 Sep 2025 16:26:25 +0700 Subject: [PATCH 50/51] add Generic components --- src/components/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/index.js b/src/components/index.js index be85e838..71eee796 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -12,6 +12,7 @@ import CareerList from "./results/career/list"; import CareerModal from "./results/career/modal"; import CognitiveResults from "./results/cognitive"; import CognitiveChart from "./results/cognitive/chart"; +import GenericResults from "./results/generic"; import ClientGuide from "./results/guide/client"; import PersonalityGuide from "./results/guide/personality"; import ArchetypeHeading from "./results/personality/archetype/heading"; @@ -55,6 +56,7 @@ export default { Container: CognitiveResults }, Container: Results, + Generic: GenericResults, Guide: { Client: ClientGuide, Personality: PersonalityGuide @@ -91,7 +93,7 @@ export default { Survey: { Cognitive: CognitiveSurvey, Container: Survey, - GenericSurvey, + Generic: GenericSurvey, Personality: PersonalitySurvey } }; From 9b842087efd2f52205105c4433f761b67aa8331e Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Sat, 20 Sep 2025 11:33:05 +0700 Subject: [PATCH 51/51] render score from api --- src/components/results/generic/score.js | 8 +++++--- src/lib/graphql/generic.js | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/results/generic/score.js b/src/components/results/generic/score.js index f452d765..dbd0e0ec 100644 --- a/src/components/results/generic/score.js +++ b/src/components/results/generic/score.js @@ -6,8 +6,8 @@ export default function Score({assessmentResult}) { const translate = useTranslate(); const totalQuestions = assessmentResult ? assessmentResult.responses.length : 0; const totalCorrectResponses = assessmentResult ? assessmentResult.totalCorrectResponses : 0; - const totalIncorrectResponses = totalQuestions - totalCorrectResponses; - const overallScore = totalQuestions > 0 ? (totalCorrectResponses / totalQuestions) * 100 : 0; + const totalIncorrectResponses = assessmentResult ? assessmentResult.totalIncorrectResponses : 0; + const overallScore = assessmentResult ? assessmentResult.overallScore : 0; return (
@@ -47,6 +47,8 @@ Score.propTypes = { ).isRequired }) ).isRequired, - totalCorrectResponses: PropTypes.number.isRequired + totalCorrectResponses: PropTypes.number.isRequired, + totalIncorrectResponses: PropTypes.number.isRequired, + overallScore: PropTypes.number.isRequired }).isRequired }; diff --git a/src/lib/graphql/generic.js b/src/lib/graphql/generic.js index 06ac68e8..723ad207 100644 --- a/src/lib/graphql/generic.js +++ b/src/lib/graphql/generic.js @@ -29,6 +29,8 @@ export const questions = ` startedAt completedAt totalCorrectResponses + totalIncorrectResponses + overallScore localeKey survey { id @@ -74,6 +76,8 @@ export const update = ` startedAt completedAt totalCorrectResponses + totalIncorrectResponses + overallScore responses { questionId questionText