-
Notifications
You must be signed in to change notification settings - Fork 2
Td 2435 generic assessment UI #357
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
f265c16
48dc4be
ddad0df
07d1ad5
d065fc4
662aecb
b0cf037
aeeb84b
612d3fe
0ca367b
26c932f
aa365bb
2874102
8a997c1
a17c8c4
8e04610
0664436
317f1d0
57db093
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<div> | ||
<h1>Generic Report</h1> | ||
<p>This is a placeholder for a generic report. Please customize it as needed.</p> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import PropTypes from "prop-types"; | ||
import style from "./style.scss"; | ||
|
||
export default function Container({children, progress}) { | ||
return ( | ||
<div className={`${style.container}`}> | ||
{progress < 100 && ( | ||
<div className={style.progressBar}> | ||
<div className={style.progress} style={{width: `${progress}%`}} /> | ||
</div> | ||
)} | ||
{children} | ||
</div> | ||
); | ||
} | ||
|
||
Container.propTypes = { | ||
children: PropTypes.node.isRequired, | ||
progress: PropTypes.number.isRequired | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import {faArrowLeft} from "@fortawesome/free-solid-svg-icons"; | ||
import {useEffect, useState} from "react"; | ||
import {useRecoilRefresher_UNSTABLE as useRecoilRefresher} from "recoil"; | ||
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 useCache from "lib/hooks/use-cache"; | ||
import useCacheKey from "lib/hooks/use-cache-key"; | ||
import useDidUpdate from "lib/hooks/use-did-update"; | ||
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 QuestionSet from "./question-set"; | ||
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 [submitAttempts, setSubmitAttempts] = useState(0); | ||
|
||
const assessment = useAssessment({surveyType: "generic"}); | ||
const assessmentCacheKey = useCacheKey("assessment"); | ||
const cache = useCache(); | ||
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; | ||
const finished = questionSets.length > 0 && questionCount === answers.length; | ||
|
||
const graphQL = useGraphql(); | ||
const http = useHttp(); | ||
const translate = useTranslate(); | ||
const props = {progress}; | ||
const refreshAssessment = useRecoilRefresher(activeAssessmentQuery); | ||
|
||
const updateAnswer = (questionId, selectedOptionId) => { | ||
const currentAnswers = answers.filter((answer) => answer.questionId !== questionId); | ||
setAnswers([...currentAnswers, | ||
{questionId, selectedResponseOptionId: selectedOptionId}]); | ||
}; | ||
|
||
const next = () => { setQuestionSetIndex(questionSetIndex + 1); }; | ||
const back = () => { setQuestionSetIndex(questionSetIndex - 1); }; | ||
|
||
const onSubmit = () => { | ||
if(submitAttempts > 3) { return; } | ||
const query = graphQL.generic.update; | ||
const variables = { | ||
assessmentID: assessment.id, | ||
answers | ||
}; | ||
|
||
http.post(graphQL.generic.path, {query, variables}).then(({data, errors}) => { | ||
if(!errors && data.submitGenericAssessmentAnswers) { | ||
setShowConclusions(true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the conclusions might need to be in the results component from a candidate's perspective, so here we'd want either cache the results if they come back from this request, or clear the cache (like in the personality survey) so it automatically requests it again and the results component is rendered. This part could go fine for now if you want to focus on results in the next PR |
||
setTimeout(() => { | ||
cache.set(assessmentCacheKey, {...assessment, completed: true}); | ||
refreshAssessment(); | ||
}, 5000); | ||
} else { | ||
console.warn(errors || data); // eslint-disable-line no-console | ||
|
||
setTimeout(() => setSubmitAttempts((x) => x + 1), 2000); | ||
} | ||
}); | ||
}; | ||
|
||
useDidUpdate(() => { onSubmit(); }, [submitAttempts]); | ||
|
||
useEffect(() => { | ||
setShowInstructions(true); | ||
}, [assessment]); | ||
|
||
useEffect(() => { | ||
if(!finished) { return; } | ||
onSubmit(); | ||
}, [finished]); | ||
|
||
if(showConclusions) { | ||
return ( | ||
<Container {...props}> | ||
<Markdown className={style.markdown}> | ||
{assessment.survey.conclusions} | ||
</Markdown> | ||
<button type="button" className={style.btnPrimary}>Finished!</button> | ||
</Container> | ||
); | ||
} | ||
|
||
return ( | ||
<Container {...props}> | ||
{currentQuestionSet && ( | ||
<QuestionSet | ||
key={questionSetIndex} | ||
questionSet={currentQuestionSet} | ||
updateAnswer={updateAnswer} | ||
next={next} | ||
/> | ||
)} | ||
|
||
{questionSetIndex > 0 && ( | ||
<button onClick={back} type="button" className={style.back}> | ||
<Icon className={style.icon} alt={translate("back")} icon={faArrowLeft} /> | ||
Go Back | ||
</button> | ||
)} | ||
{showInstructions | ||
&& ( | ||
<Modal | ||
title="Instructions" | ||
onClose={() => setShowInstructions(false)} | ||
containerClass={style.modalContainer} | ||
> | ||
<Markdown> | ||
{assessment.survey.instructions} | ||
</Markdown> | ||
<hr className={style.grayDivider} /> | ||
<div className={style.footer}> | ||
<button | ||
type="button" | ||
className={style.cancelBtn} | ||
onClick={() => setShowInstructions(false)} | ||
> | ||
Cancel | ||
</button> | ||
<button | ||
type="button" | ||
className={style.btnPrimary} | ||
onClick={() => setShowInstructions(false)} | ||
> | ||
{assessment.survey.instructionButton} | ||
</button> | ||
</div> | ||
</Modal> | ||
)} | ||
</Container> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import PropTypes from "prop-types"; | ||
import {useEffect, useState} from "react"; | ||
import Responses from "./responses"; | ||
import style from "./style.scss"; | ||
|
||
export default function QuestionSet({next, questionSet, updateAnswer}) { | ||
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 ( | ||
<div className={questionSetClass}> | ||
<img src={questionSet.setImage} alt={questionSet.text} /> | ||
<hr /> | ||
{questionSet.questions.map((question) => ( | ||
<div key={question.id}> | ||
<h3 className={style.question}>{question.text}</h3> | ||
<Responses | ||
responseOptions={question.responseOptions} | ||
updateAnswer={(optionId) => selectOption(question.id, optionId)} | ||
/> | ||
</div> | ||
))} | ||
</div> | ||
); | ||
} | ||
|
||
QuestionSet.propTypes = { | ||
next: PropTypes.func.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, | ||
updateAnswer: PropTypes.func.isRequired | ||
}; |
Uh oh!
There was an error while loading. Please reload this page.