Make questionnaire Ui reusable

This commit is contained in:
Ibitoye Rotimi Best
2021-07-22 08:04:48 +03:00
parent d92d0c8a30
commit 29d3e6b7e0
16 changed files with 401 additions and 194 deletions
@@ -0,0 +1,53 @@
<script>
import Modal from '../../../../Modal/index.svelte';
export let open;
export let onClose;
</script>
<Modal
modalHeading="Lesson 1: How to use props in React.js"
bind:open
{onClose}
width="w-3/5"
>
<p class="p-2 mb-2">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Repellendus debitis
quos porro doloremque amet alias modi ducimus delectus cum dolor sunt
laborum provident, deserunt magni facilis non nobis illum ipsum. Lorem ipsum
dolor sit amet, consectetur adipisicing elit. Deleniti rem aliquid, saepe
fuga reiciendis ullam officiis soluta cum, vitae aut eum expedita cumque
perspiciatis pariatur necessitatibus, praesentium ea laudantium ut?
</p>
<p class="p-2 mb-2">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Repellendus debitis
quos porro doloremque amet alias modi ducimus delectus cum dolor sunt
laborum provident, deserunt magni facilis non nobis illum ipsum. Lorem ipsum
dolor sit amet, consectetur adipisicing elit. Deleniti rem aliquid, saepe
fuga reiciendis ullam officiis soluta cum, vitae aut eum expedita cumque
perspiciatis pariatur necessitatibus, praesentium ea laudantium ut?
</p>
<p class="p-2 mb-2">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Repellendus debitis
quos porro doloremque amet alias modi ducimus delectus cum dolor sunt
laborum provident, deserunt magni facilis non nobis illum ipsum. Lorem ipsum
dolor sit amet, consectetur adipisicing elit. Deleniti rem aliquid, saepe
fuga reiciendis ullam officiis soluta cum, vitae aut eum expedita cumque
perspiciatis pariatur necessitatibus, praesentium ea laudantium ut?
</p>
<p class="p-2 mb-2">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Repellendus debitis
quos porro doloremque amet alias modi ducimus delectus cum dolor sunt
laborum provident, deserunt magni facilis non nobis illum ipsum. Lorem ipsum
dolor sit amet, consectetur adipisicing elit. Deleniti rem aliquid, saepe
fuga reiciendis ullam officiis soluta cum, vitae aut eum expedita cumque
perspiciatis pariatur necessitatibus, praesentium ea laudantium ut?
</p>
<p class="p-2 mb-2">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Repellendus debitis
quos porro doloremque amet alias modi ducimus delectus cum dolor sunt
laborum provident, deserunt magni facilis non nobis illum ipsum. Lorem ipsum
dolor sit amet, consectetur adipisicing elit. Deleniti rem aliquid, saepe
fuga reiciendis ullam officiis soluta cum, vitae aut eum expedita cumque
perspiciatis pariatur necessitatibus, praesentium ea laudantium ut?
</p>
</Modal>
@@ -0,0 +1,52 @@
<script>
import RadioQuestion from '../../../../Question/RadioQuestion/index.svelte';
import CheckboxQuestion from '../../../../Question/CheckboxQuestion/index.svelte';
import TextareaQuestion from '../../../../Question/TextareaQuestion/index.svelte';
import { QUESTION_TYPE } from '../../../../Question/constants';
import { getPropsForQuestion } from './functions';
export let questions;
export let questionnaireMetaData;
export let onSubmit = () => {};
export let onPrevious = () => {};
</script>
{#each questions as currentQuestion, currentQuestionIndex}
{#if QUESTION_TYPE.RADIO === currentQuestion.type}
<RadioQuestion
{...getPropsForQuestion(
questions,
currentQuestion,
questionnaireMetaData,
currentQuestionIndex + 1,
onSubmit,
onPrevious,
true
)}
/>
{:else if QUESTION_TYPE.CHECKBOX === currentQuestion.type}
<CheckboxQuestion
{...getPropsForQuestion(
questions,
currentQuestion,
questionnaireMetaData,
currentQuestionIndex + 1,
onSubmit,
onPrevious,
true
)}
/>
{:else if QUESTION_TYPE.TEXTAREA === currentQuestion.type}
<TextareaQuestion
{...getPropsForQuestion(
questions,
currentQuestion,
questionnaireMetaData,
currentQuestionIndex + 1,
onSubmit,
onPrevious,
true
)}
/>
{/if}
{/each}
@@ -1,6 +1,7 @@
<script>
import { questionnaire } from './store/index';
import { userQuestionniareAnswers } from './store/answers';
import { questionnaireMetaData } from './store/answers';
import Preview from './Preview.svelte';
import RadioQuestion from '../../../../Question/RadioQuestion/index.svelte';
import CheckboxQuestion from '../../../../Question/CheckboxQuestion/index.svelte';
import TextareaQuestion from '../../../../Question/TextareaQuestion/index.svelte';
@@ -9,122 +10,52 @@
import { removeDuplicate } from '../../../../../utils/functions/removeDuplicate';
import { QUESTION_TYPE } from '../../../../Question/constants';
import { STATUS } from './constants';
import { wasCorrectAnswerSelected, getPropsForQuestion } from './functions';
export let preview;
let currentQuestion = {};
let renderProps = {};
let isAnswerCorrect;
const { questions, title, description } = $questionnaire;
function handleStart() {
$userQuestionniareAnswers.currentQuestionIndex += 1;
$questionnaireMetaData.currentQuestionIndex += 1;
}
function onSubmit(id, value) {
const prevAnswer = $userQuestionniareAnswers.answers[id] || [];
const prevAnswer = $questionnaireMetaData.answers[id] || [];
const formattedAnswer =
typeof value === 'string'
? value
: removeDuplicate([...prevAnswer, ...(value || [])]);
$userQuestionniareAnswers.answers = {
...$userQuestionniareAnswers.answers,
$questionnaireMetaData.answers = {
...$questionnaireMetaData.answers,
[id]: formattedAnswer,
};
if (isAnswerCorrect) {
$userQuestionniareAnswers.currentQuestionIndex += 1;
if (
wasCorrectAnswerSelected(currentQuestion, $questionnaireMetaData.answers)
) {
$questionnaireMetaData.currentQuestionIndex += 1;
}
}
function onPrevious() {
$userQuestionniareAnswers.currentQuestionIndex -= 1;
}
function wasCorrectAnswerSelected(currentQuestion) {
const isOpenQuesiton = currentQuestion.type === QUESTION_TYPE.TEXTAREA;
if (isOpenQuesiton) {
return true;
}
const answer = $userQuestionniareAnswers.answers[currentQuestion.id];
const formattedAnswers = typeof answer === 'string' ? [answer] : answer;
isAnswerCorrect =
formattedAnswers &&
formattedAnswers.some((answer) => {
if (
!currentQuestion.answers ||
!currentQuestion.answers.length ||
currentQuestion.answers.includes(answer)
) {
return true;
}
return false;
});
return isAnswerCorrect;
}
function getRenderProps(question, questionIndex, answers) {
const isLast = questionIndex === questions.length;
const isOpenQuesiton = question.type === QUESTION_TYPE.TEXTAREA;
return {
title: questionIndex + '. ' + question.title,
name: `${question.id}`,
options: question.options,
answers: question.answers,
code: question.code,
defaultValue: isOpenQuesiton
? answers[question.id] || ''
: answers[question.id] || [],
onSubmit,
onPrevious,
disablePreviousButton: questionIndex === 1,
isLast,
isPreview: preview || $userQuestionniareAnswers.isFinished,
disabled:
($userQuestionniareAnswers.isFinished &&
wasCorrectAnswerSelected(question)) ||
$userQuestionniareAnswers.isFinished,
nextButtonProps: isLast
? {
label: 'Finish',
isActive: true,
}
: isOpenQuesiton
? {
label: 'Next',
isActive: true,
}
: wasCorrectAnswerSelected(question)
? {
label: 'Next',
isActive: true,
disableOptionSelect: true,
}
: {
label: 'Check',
isActive: false,
},
};
$questionnaireMetaData.currentQuestionIndex -= 1;
}
function getTotalScores() {
let total = 0;
for (const score in $userQuestionniareAnswers.scores) {
total += parseInt($userQuestionniareAnswers.scores[score]);
for (const score in $questionnaireMetaData.scores) {
total += parseInt($questionnaireMetaData.scores[score]);
}
return total;
}
function getProgressValue(currentQuestionIndex) {
if ($userQuestionniareAnswers.isFinished) {
if ($questionnaireMetaData.isFinished) {
return 100;
}
@@ -135,62 +66,35 @@
$: {
currentQuestion =
questions[$userQuestionniareAnswers.currentQuestionIndex - 1];
if (
$userQuestionniareAnswers.currentQuestionIndex > 0 &&
!currentQuestion
) {
$userQuestionniareAnswers.isFinished = true;
questions[$questionnaireMetaData.currentQuestionIndex - 1];
if ($questionnaireMetaData.currentQuestionIndex > 0 && !currentQuestion) {
$questionnaireMetaData.isFinished = true;
}
if (currentQuestion) {
renderProps = getRenderProps(
renderProps = getPropsForQuestion(
questions,
currentQuestion,
$userQuestionniareAnswers.currentQuestionIndex,
$userQuestionniareAnswers.answers
$questionnaireMetaData,
$questionnaireMetaData.currentQuestionIndex,
onSubmit,
onPrevious,
preview
);
}
}
$: $userQuestionniareAnswers.progressValue = getProgressValue(
$userQuestionniareAnswers.currentQuestionIndex
$: $questionnaireMetaData.progressValue = getProgressValue(
$questionnaireMetaData.currentQuestionIndex
);
</script>
<!-- <svelte:window on:keydown={handleKeydown} /> -->
{#if !preview && questions.length && !$userQuestionniareAnswers.isFinished}
<Progress value={$userQuestionniareAnswers.progressValue} />
{#if !preview && questions.length && !$questionnaireMetaData.isFinished}
<Progress value={$questionnaireMetaData.progressValue} />
{/if}
{#if preview}
{#each questions as currentQuestion, currentQuestionIndex}
{#if QUESTION_TYPE.RADIO === currentQuestion.type}
<RadioQuestion
{...getRenderProps(
currentQuestion,
currentQuestionIndex + 1,
$userQuestionniareAnswers.answers
)}
/>
{:else if QUESTION_TYPE.CHECKBOX === currentQuestion.type}
<CheckboxQuestion
{...getRenderProps(
currentQuestion,
currentQuestionIndex + 1,
$userQuestionniareAnswers.answers
)}
/>
{:else if QUESTION_TYPE.TEXTAREA === currentQuestion.type}
<TextareaQuestion
{...getRenderProps(
currentQuestion,
currentQuestionIndex + 1,
$userQuestionniareAnswers.answers
)}
/>
{/if}
{/each}
<Preview {questions} questionnaireMetaData={$questionnaireMetaData} />
{:else if !questions.length}
<div
class="w-4/5 h-40 m-auto flex items-center justify-center text-center border-2 border-gray-200 rounded-md"
@@ -200,7 +104,7 @@
<span class="text-blue-700">Edit</span> button to add.
</h3>
</div>
{:else if $userQuestionniareAnswers.currentQuestionIndex === 0}
{:else if $questionnaireMetaData.currentQuestionIndex === 0}
<div>
<h2 class="my-1">{title}</h2>
<p>{description}</p>
@@ -212,18 +116,18 @@
type="button"
/>
</div>
{:else if $userQuestionniareAnswers.isFinished}
{:else if $questionnaireMetaData.isFinished}
<div class="flex items-center justify-between">
<div class="flex flex-col justify-between">
<h2 class="text-xl mb-2 mt-0">{title}</h2>
{#if STATUS.IN_PROGRESS === $userQuestionniareAnswers.status}
{#if STATUS.IN_PROGRESS === $questionnaireMetaData.status}
<span
class="bg-yellow-600 text-white rounded-full py-2 px-6 text-center"
title="Status: Pending Review"
>
Pending
</span>
{:else if STATUS.REVIEWED === $userQuestionniareAnswers.status && getTotalScores() > 0}
{:else if STATUS.REVIEWED === $questionnaireMetaData.status && getTotalScores() > 0}
<span
class="bg-green-700 text-white rounded-full py-2 px-6 text-center"
title="Status: Pending Review"
@@ -240,33 +144,7 @@
{getTotalScores()}/100
</span>
</div>
{#each questions as currentQuestion, currentQuestionIndex}
{#if QUESTION_TYPE.RADIO === currentQuestion.type}
<RadioQuestion
{...getRenderProps(
currentQuestion,
currentQuestionIndex + 1,
$userQuestionniareAnswers.answers
)}
/>
{:else if QUESTION_TYPE.CHECKBOX === currentQuestion.type}
<CheckboxQuestion
{...getRenderProps(
currentQuestion,
currentQuestionIndex + 1,
$userQuestionniareAnswers.answers
)}
/>
{:else if QUESTION_TYPE.TEXTAREA === currentQuestion.type}
<TextareaQuestion
{...getRenderProps(
currentQuestion,
currentQuestionIndex + 1,
$userQuestionniareAnswers.answers
)}
/>
{/if}
{/each}
<Preview {questions} questionnaireMetaData={$questionnaireMetaData} />
{:else if QUESTION_TYPE.RADIO === currentQuestion.type}
<RadioQuestion {...renderProps} />
{:else if QUESTION_TYPE.CHECKBOX === currentQuestion.type}
@@ -0,0 +1,72 @@
import { QUESTION_TYPE } from '../../../../Question/constants';
export function wasCorrectAnswerSelected(currentQuestion, answers) {
if (currentQuestion.type === QUESTION_TYPE.TEXTAREA) {
return true;
}
const answer = answers[currentQuestion.id];
const formattedAnswers = typeof answer === 'string' ? [answer] : answer;
return (
formattedAnswers &&
formattedAnswers.some((answer) => {
if (
!currentQuestion.answers ||
!currentQuestion.answers.length ||
currentQuestion.answers.includes(answer)
) {
return true;
}
return false;
})
);
}
export function getPropsForQuestion(
questions,
question,
questionnaireMetaData,
questionIndex,
onSubmit,
onPrevious,
preview
) {
const { answers, isFinished } = questionnaireMetaData;
const isLast = questionIndex === questions.length;
const isOpenQuesiton = question.type === QUESTION_TYPE.TEXTAREA;
return {
title: questionIndex + '. ' + question.title,
name: `${question.id}`,
options: question.options,
answers: question.answers,
code: question.code,
defaultValue: isOpenQuesiton
? answers[question.id] || ''
: answers[question.id] || [],
onSubmit,
onPrevious,
disablePreviousButton: questionIndex === 1,
isLast,
isPreview: preview || isFinished,
disabled:
(isFinished && wasCorrectAnswerSelected(question, answers)) || isFinished,
nextButtonProps: isOpenQuesiton
? {
label: 'Next',
isActive: true,
}
: wasCorrectAnswerSelected(question, answers)
? {
label: isLast ? 'Finish' : 'Next',
isActive: true,
disableOptionSelect: true,
}
: {
label: 'Check',
isActive: false,
},
};
}
@@ -1,5 +1,6 @@
<script>
import { handleAddQuestion } from './store/index';
import { questionnaireMetaData } from './store/answers';
import PrimaryButton from '../../../../PrimaryButton/index.svelte';
import PageBody from '../../../../PageBody/index.svelte';
import ViewMode from './ViewMode.svelte';
@@ -15,6 +16,15 @@
function handleMode() {
mode = mode === MODES.edit ? MODES.view : MODES.edit;
preview = false;
if (mode === MODES.view) {
questionnaireMetaData.update((metaData) => {
metaData.answers = {};
metaData.currentQuestionIndex = 0;
metaData.isFinished = false;
return metaData;
});
}
}
</script>
@@ -2,7 +2,7 @@ import { writable } from 'svelte/store';
import { STATUS } from '../constants';
// export const userQuestionniareAnswers = writable({
// export const questionnaireMetaData = writable({
// answers: {},
// scores: { gates: 60 },
// currentQuestionIndex: 5,
@@ -19,4 +19,4 @@ const initAnswerState = {
status: STATUS.IN_PROGRESS,
};
export const userQuestionniareAnswers = writable(initAnswerState);
export const questionnaireMetaData = writable(initAnswerState);
@@ -19,7 +19,7 @@ export function handleAddQuestion() {
...questions,
{
...QUESTION_TEMPLATE,
id: questions.length + 1,
id: `${questions.length + 1}-form`,
value: '',
answers: [],
options: [
@@ -20,7 +20,7 @@
export let courseId;
export let path;
let show = true;
let show = null;
const { page } = stores();
@@ -70,6 +70,8 @@
}
onMount(() => {
show = localStorage.getItem('hideCourseNav') === 'false';
hotkeys('b', function (event, handler) {
event.preventDefault();
switch (handler.key) {
@@ -79,6 +81,12 @@
}
});
});
$: {
if (process.browser && show !== null) {
localStorage.setItem('hideCourseNav', `${!show}`);
}
}
</script>
<div class="root z-10 {!show && 'hide'}">