Skip to content
Open
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
f265c16
setup generic
Jun 11, 2025
48dc4be
add generic component graphql
Jun 11, 2025
ddad0df
render questions
Jun 13, 2025
07d1ad5
render questions and collect answers
Jun 15, 2025
d065fc4
refactor
Jun 15, 2025
662aecb
handle multi questions set
Jun 16, 2025
b0cf037
add conclusions view
Jun 17, 2025
aeeb84b
add show instructions
Jun 17, 2025
612d3fe
update Modal common
Jun 17, 2025
0ca367b
submit answers
Jun 20, 2025
26c932f
Merge branch 'master' into TD-2435-generic-assessment-ui
Jun 20, 2025
aa365bb
use assessmentID
Jun 20, 2025
2874102
bump version
Jun 20, 2025
8a997c1
refactor
Jun 20, 2025
a17c8c4
update graphql responses
Jun 24, 2025
8e04610
refactor code
Jun 27, 2025
0664436
add retry attempts
Jun 27, 2025
4295879
render score
Jun 28, 2025
317f1d0
remove secret key
Jul 17, 2025
57db093
Merge branch 'master' into TD-2435-generic-assessment-ui
Jul 17, 2025
869c028
render breakdown
Jul 21, 2025
398f21f
apply space-between
Jul 23, 2025
b262188
show hide all
Jul 23, 2025
7ce3439
format
Jul 23, 2025
cfb3a3c
handle question with set image
Jul 24, 2025
33408ce
Merge branch 'TD-2435-generic-assessment-ui' into TD-2500-render-results
Jul 24, 2025
02fde09
handle locale
Jul 25, 2025
a2537ab
update heading and hr tag
Jul 30, 2025
cd2a674
helper
Jul 30, 2025
91a5ed8
rearrange css class
Jul 30, 2025
c4bd1f8
use translate
Aug 1, 2025
a50b734
parse completedAt by millisecond
Aug 4, 2025
4152072
Merge branch 'master' into TD-2500-render-results
Aug 12, 2025
43315d5
Added loading state
tomprats Aug 12, 2025
80c930f
remove header and actions
Aug 13, 2025
e416ea3
Merge branch 'master' into TD-2500-render-results
Aug 16, 2025
3241452
set loaded active assessment + remove redundant generic result css
Aug 18, 2025
bfafcb5
bump version to 3.9.0
Aug 18, 2025
1770629
extend box and container
Aug 18, 2025
1d57f34
extend container
Aug 18, 2025
2825d0f
refactor btn border
Aug 18, 2025
4529d36
return after setActive
Aug 20, 2025
71e7faf
name format
Aug 21, 2025
a51a651
get result if completed
Aug 21, 2025
2c2def5
not return cached survey if completed
Aug 29, 2025
1ba75f1
not call to result api
Sep 5, 2025
5d42077
render totalCorrectResponses
Sep 8, 2025
4a48daf
let question set in column for mobile
Sep 18, 2025
5ac1e92
survey mobile
Sep 19, 2025
9df964e
refactor direction class
Sep 19, 2025
e17e6a3
update btn mobile
Sep 19, 2025
173ecda
update reserved words
Sep 19, 2025
e8f085c
refactor with early return
Sep 19, 2025
d52a623
refactor progress bar
Sep 19, 2025
034eb57
add Generic components
Sep 19, 2025
9b84208
render score from api
Sep 20, 2025
23896f8
Merge branch 'master' into TD-2500-render-results
Sep 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "traitify-widgets",
"version": "3.8.1",
"version": "3.9.0",
"description": "Traitiy Widgets",
"repository": {
"type": "git",
Expand Down
49 changes: 48 additions & 1 deletion public/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,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"),
Expand Down Expand Up @@ -240,6 +241,24 @@ function createElement(options = {}) {
return element;
}

function createGenericAssessment() {
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;
cache.set("assessmentID", id);
} catch(error) {
console.log(error);
}
setTimeout(createWidget, 500);
});
}

function destroyWidget() { Traitify.destroy(); }

function setupTargets() {
Expand Down Expand Up @@ -369,6 +388,7 @@ function setupDom() {
options: [
{text: "Benchmark", value: "benchmark"},
{text: "Cognitive", value: "cognitive"},
{text: "Generic", value: "generic"},
{text: "Order", value: "order"},
{text: "Personality", value: "personality"}
],
Expand Down Expand Up @@ -404,6 +424,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);
Expand All @@ -427,6 +450,29 @@ function setupCognitive() {
});
}

function setupGeneric() {
const query = Traitify.GraphQL.generic.surveys;
const variables = {localeKey: cache.get("locale")};

Traitify.http.post(Traitify.GraphQL.generic.path, {query, variables}).then((response) => {
try {
const options = response.data.genericSurveys
.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);
}
});

}

function setupTraitify() {
const environment = cache.get("environment");

Expand Down Expand Up @@ -455,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"].filter((type) => type !== value);
const otherValues = ["benchmark", "cognitive", "generic", "order", "personality"].filter((type) => type !== value);

cache.set("assessmentID", assessmentID);

Expand All @@ -468,4 +514,5 @@ function onSurveyTypeChange(e) {
setupTraitify();
setupDom();
setupCognitive();
setupGeneric();
createWidget();
6 changes: 4 additions & 2 deletions src/components/common/modal/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, containerClass = null, onClose, title}) {
const translate = useTranslate();
const sectionClass = [style.modalContainer, containerClass].filter(Boolean).join(" ");
return (
<div className={`${style.modal} ${style.container}`}>
<section className={style.modalContainer}>
<section className={sectionClass}>
<div className={style.modalContent}>
<div className={style.header}>
<div>{title}</div>
Expand All @@ -34,6 +35,7 @@ export default function Modal({children, onClose, title}) {

Modal.propTypes = {
children: PropTypes.node.isRequired,
containerClass: PropTypes.string,
onClose: PropTypes.func.isRequired,
title: PropTypes.string.isRequired
};
6 changes: 6 additions & 0 deletions src/components/container/hooks/use-order-effect.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +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 PersonalitySurvey from "./survey/personality";

export default {
Expand Down Expand Up @@ -90,6 +91,7 @@ export default {
Survey: {
Cognitive: CognitiveSurvey,
Container: Survey,
Generic,
Personality: PersonalitySurvey
}
};
59 changes: 59 additions & 0 deletions src/components/results/generic/breakdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
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);
};

return (
<div className={style.breakdown}>
<div className={style.description}>
<div>
<div className={style.title}>{translate("results.generic.breakdown")}</div>
<span>{translate("results.generic.breakdown_description")}</span>
</div>
<div>
<button className={style.toggleButton} type="button" onClick={showHideAll}>
{translate("show_hide_all")}
</button>
</div>
</div>
<div className={style.questions}>
{assessmentResult.responses.map((question, index) => (
<Question
key={question.questionId}
question={question}
index={index}
showState={showAll}
/>
))}
</div>
</div>
);
}

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
};
43 changes: 43 additions & 0 deletions src/components/results/generic/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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 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]);

return (
<div>
{result && (
<div className={style.container}>
<div className={style.contentBody}>
<Score assessmentResult={assessmentResult} />
<Breakdown assessmentResult={assessmentResult} />
</div>
</div>
)}
</div>
);
}
98 changes: 98 additions & 0 deletions src/components/results/generic/question.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import {faCheck, faXmark, faChevronDown, faChevronUp} from "@fortawesome/free-solid-svg-icons";
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
: 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);
}, [showState]);

const toggleContent = () => {
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 (
<div key={question.questionId} className={style.question}>
<div className={style.questionTitle}>
<div>
{question.isCorrect
? <Icon className={style.iconCorrect} alt="Checked" icon={faCheck} />
: <Icon className={style.iconIncorrect} alt="X Mark" icon={faXmark} />}
</div>
<div> {translate("cognitive_question_alt_text")} {index + 1}</div>
<div>
<button type="button" onClick={toggleContent} className={style.toggleButton}>
<Icon className={style.icon} alt="Expand" icon={showContent ? faChevronUp : faChevronDown} />
</button>
</div>
</div>
{showContent && (
<div className={style.questionContent}>
<div className={style.responseOptions}>
<div className={style.questionText}>{question.questionText}</div>
<div className={responsesClassName} style={{flexDirection: optionsDirection}}>
{question.responseOptions.map((option) => (
<div
key={option.responseOptionId}
className={`${optionClassName(option)} ${style.responseOption}`}
>
{option.responseOptionText}
</div>
))}
</div>
</div>
{question.setImage && (
<div className={style.questionImage}>
<img src={question.setImage} alt={question.questionText} />
</div>
)}
</div>
)}
</div>
);
}

Question.propTypes = {
question: PropTypes.shape({
questionText: PropTypes.string,
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,
isCorrect: PropTypes.bool.isRequired
})).isRequired
}).isRequired,
index: PropTypes.number.isRequired,
showState: PropTypes.bool.isRequired
};
Loading