Make questionnaire Ui reusable
This commit is contained in:
@@ -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>
|
<script>
|
||||||
import { questionnaire } from './store/index';
|
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 RadioQuestion from '../../../../Question/RadioQuestion/index.svelte';
|
||||||
import CheckboxQuestion from '../../../../Question/CheckboxQuestion/index.svelte';
|
import CheckboxQuestion from '../../../../Question/CheckboxQuestion/index.svelte';
|
||||||
import TextareaQuestion from '../../../../Question/TextareaQuestion/index.svelte';
|
import TextareaQuestion from '../../../../Question/TextareaQuestion/index.svelte';
|
||||||
@@ -9,122 +10,52 @@
|
|||||||
import { removeDuplicate } from '../../../../../utils/functions/removeDuplicate';
|
import { removeDuplicate } from '../../../../../utils/functions/removeDuplicate';
|
||||||
import { QUESTION_TYPE } from '../../../../Question/constants';
|
import { QUESTION_TYPE } from '../../../../Question/constants';
|
||||||
import { STATUS } from './constants';
|
import { STATUS } from './constants';
|
||||||
|
import { wasCorrectAnswerSelected, getPropsForQuestion } from './functions';
|
||||||
|
|
||||||
export let preview;
|
export let preview;
|
||||||
|
|
||||||
let currentQuestion = {};
|
let currentQuestion = {};
|
||||||
let renderProps = {};
|
let renderProps = {};
|
||||||
let isAnswerCorrect;
|
|
||||||
|
|
||||||
const { questions, title, description } = $questionnaire;
|
const { questions, title, description } = $questionnaire;
|
||||||
|
|
||||||
function handleStart() {
|
function handleStart() {
|
||||||
$userQuestionniareAnswers.currentQuestionIndex += 1;
|
$questionnaireMetaData.currentQuestionIndex += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSubmit(id, value) {
|
function onSubmit(id, value) {
|
||||||
const prevAnswer = $userQuestionniareAnswers.answers[id] || [];
|
const prevAnswer = $questionnaireMetaData.answers[id] || [];
|
||||||
const formattedAnswer =
|
const formattedAnswer =
|
||||||
typeof value === 'string'
|
typeof value === 'string'
|
||||||
? value
|
? value
|
||||||
: removeDuplicate([...prevAnswer, ...(value || [])]);
|
: removeDuplicate([...prevAnswer, ...(value || [])]);
|
||||||
|
|
||||||
$userQuestionniareAnswers.answers = {
|
$questionnaireMetaData.answers = {
|
||||||
...$userQuestionniareAnswers.answers,
|
...$questionnaireMetaData.answers,
|
||||||
[id]: formattedAnswer,
|
[id]: formattedAnswer,
|
||||||
};
|
};
|
||||||
|
if (
|
||||||
if (isAnswerCorrect) {
|
wasCorrectAnswerSelected(currentQuestion, $questionnaireMetaData.answers)
|
||||||
$userQuestionniareAnswers.currentQuestionIndex += 1;
|
) {
|
||||||
|
$questionnaireMetaData.currentQuestionIndex += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPrevious() {
|
function onPrevious() {
|
||||||
$userQuestionniareAnswers.currentQuestionIndex -= 1;
|
$questionnaireMetaData.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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTotalScores() {
|
function getTotalScores() {
|
||||||
let total = 0;
|
let total = 0;
|
||||||
for (const score in $userQuestionniareAnswers.scores) {
|
for (const score in $questionnaireMetaData.scores) {
|
||||||
total += parseInt($userQuestionniareAnswers.scores[score]);
|
total += parseInt($questionnaireMetaData.scores[score]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getProgressValue(currentQuestionIndex) {
|
function getProgressValue(currentQuestionIndex) {
|
||||||
if ($userQuestionniareAnswers.isFinished) {
|
if ($questionnaireMetaData.isFinished) {
|
||||||
return 100;
|
return 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,62 +66,35 @@
|
|||||||
|
|
||||||
$: {
|
$: {
|
||||||
currentQuestion =
|
currentQuestion =
|
||||||
questions[$userQuestionniareAnswers.currentQuestionIndex - 1];
|
questions[$questionnaireMetaData.currentQuestionIndex - 1];
|
||||||
if (
|
if ($questionnaireMetaData.currentQuestionIndex > 0 && !currentQuestion) {
|
||||||
$userQuestionniareAnswers.currentQuestionIndex > 0 &&
|
$questionnaireMetaData.isFinished = true;
|
||||||
!currentQuestion
|
|
||||||
) {
|
|
||||||
$userQuestionniareAnswers.isFinished = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentQuestion) {
|
if (currentQuestion) {
|
||||||
renderProps = getRenderProps(
|
renderProps = getPropsForQuestion(
|
||||||
|
questions,
|
||||||
currentQuestion,
|
currentQuestion,
|
||||||
$userQuestionniareAnswers.currentQuestionIndex,
|
$questionnaireMetaData,
|
||||||
$userQuestionniareAnswers.answers
|
$questionnaireMetaData.currentQuestionIndex,
|
||||||
|
onSubmit,
|
||||||
|
onPrevious,
|
||||||
|
preview
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: $userQuestionniareAnswers.progressValue = getProgressValue(
|
$: $questionnaireMetaData.progressValue = getProgressValue(
|
||||||
$userQuestionniareAnswers.currentQuestionIndex
|
$questionnaireMetaData.currentQuestionIndex
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- <svelte:window on:keydown={handleKeydown} /> -->
|
{#if !preview && questions.length && !$questionnaireMetaData.isFinished}
|
||||||
|
<Progress value={$questionnaireMetaData.progressValue} />
|
||||||
{#if !preview && questions.length && !$userQuestionniareAnswers.isFinished}
|
|
||||||
<Progress value={$userQuestionniareAnswers.progressValue} />
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if preview}
|
{#if preview}
|
||||||
{#each questions as currentQuestion, currentQuestionIndex}
|
<Preview {questions} questionnaireMetaData={$questionnaireMetaData} />
|
||||||
{#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}
|
|
||||||
{:else if !questions.length}
|
{:else if !questions.length}
|
||||||
<div
|
<div
|
||||||
class="w-4/5 h-40 m-auto flex items-center justify-center text-center border-2 border-gray-200 rounded-md"
|
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.
|
<span class="text-blue-700">Edit</span> button to add.
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
{:else if $userQuestionniareAnswers.currentQuestionIndex === 0}
|
{:else if $questionnaireMetaData.currentQuestionIndex === 0}
|
||||||
<div>
|
<div>
|
||||||
<h2 class="my-1">{title}</h2>
|
<h2 class="my-1">{title}</h2>
|
||||||
<p>{description}</p>
|
<p>{description}</p>
|
||||||
@@ -212,18 +116,18 @@
|
|||||||
type="button"
|
type="button"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{:else if $userQuestionniareAnswers.isFinished}
|
{:else if $questionnaireMetaData.isFinished}
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex flex-col justify-between">
|
<div class="flex flex-col justify-between">
|
||||||
<h2 class="text-xl mb-2 mt-0">{title}</h2>
|
<h2 class="text-xl mb-2 mt-0">{title}</h2>
|
||||||
{#if STATUS.IN_PROGRESS === $userQuestionniareAnswers.status}
|
{#if STATUS.IN_PROGRESS === $questionnaireMetaData.status}
|
||||||
<span
|
<span
|
||||||
class="bg-yellow-600 text-white rounded-full py-2 px-6 text-center"
|
class="bg-yellow-600 text-white rounded-full py-2 px-6 text-center"
|
||||||
title="Status: Pending Review"
|
title="Status: Pending Review"
|
||||||
>
|
>
|
||||||
Pending
|
Pending
|
||||||
</span>
|
</span>
|
||||||
{:else if STATUS.REVIEWED === $userQuestionniareAnswers.status && getTotalScores() > 0}
|
{:else if STATUS.REVIEWED === $questionnaireMetaData.status && getTotalScores() > 0}
|
||||||
<span
|
<span
|
||||||
class="bg-green-700 text-white rounded-full py-2 px-6 text-center"
|
class="bg-green-700 text-white rounded-full py-2 px-6 text-center"
|
||||||
title="Status: Pending Review"
|
title="Status: Pending Review"
|
||||||
@@ -240,33 +144,7 @@
|
|||||||
{getTotalScores()}/100
|
{getTotalScores()}/100
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{#each questions as currentQuestion, currentQuestionIndex}
|
<Preview {questions} questionnaireMetaData={$questionnaireMetaData} />
|
||||||
{#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}
|
|
||||||
{:else if QUESTION_TYPE.RADIO === currentQuestion.type}
|
{:else if QUESTION_TYPE.RADIO === currentQuestion.type}
|
||||||
<RadioQuestion {...renderProps} />
|
<RadioQuestion {...renderProps} />
|
||||||
{:else if QUESTION_TYPE.CHECKBOX === currentQuestion.type}
|
{: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>
|
<script>
|
||||||
import { handleAddQuestion } from './store/index';
|
import { handleAddQuestion } from './store/index';
|
||||||
|
import { questionnaireMetaData } from './store/answers';
|
||||||
import PrimaryButton from '../../../../PrimaryButton/index.svelte';
|
import PrimaryButton from '../../../../PrimaryButton/index.svelte';
|
||||||
import PageBody from '../../../../PageBody/index.svelte';
|
import PageBody from '../../../../PageBody/index.svelte';
|
||||||
import ViewMode from './ViewMode.svelte';
|
import ViewMode from './ViewMode.svelte';
|
||||||
@@ -15,6 +16,15 @@
|
|||||||
function handleMode() {
|
function handleMode() {
|
||||||
mode = mode === MODES.edit ? MODES.view : MODES.edit;
|
mode = mode === MODES.edit ? MODES.view : MODES.edit;
|
||||||
preview = false;
|
preview = false;
|
||||||
|
|
||||||
|
if (mode === MODES.view) {
|
||||||
|
questionnaireMetaData.update((metaData) => {
|
||||||
|
metaData.answers = {};
|
||||||
|
metaData.currentQuestionIndex = 0;
|
||||||
|
metaData.isFinished = false;
|
||||||
|
return metaData;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { writable } from 'svelte/store';
|
|||||||
|
|
||||||
import { STATUS } from '../constants';
|
import { STATUS } from '../constants';
|
||||||
|
|
||||||
// export const userQuestionniareAnswers = writable({
|
// export const questionnaireMetaData = writable({
|
||||||
// answers: {},
|
// answers: {},
|
||||||
// scores: { gates: 60 },
|
// scores: { gates: 60 },
|
||||||
// currentQuestionIndex: 5,
|
// currentQuestionIndex: 5,
|
||||||
@@ -19,4 +19,4 @@ const initAnswerState = {
|
|||||||
status: STATUS.IN_PROGRESS,
|
status: STATUS.IN_PROGRESS,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const userQuestionniareAnswers = writable(initAnswerState);
|
export const questionnaireMetaData = writable(initAnswerState);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function handleAddQuestion() {
|
|||||||
...questions,
|
...questions,
|
||||||
{
|
{
|
||||||
...QUESTION_TEMPLATE,
|
...QUESTION_TEMPLATE,
|
||||||
id: questions.length + 1,
|
id: `${questions.length + 1}-form`,
|
||||||
value: '',
|
value: '',
|
||||||
answers: [],
|
answers: [],
|
||||||
options: [
|
options: [
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
export let courseId;
|
export let courseId;
|
||||||
export let path;
|
export let path;
|
||||||
|
|
||||||
let show = true;
|
let show = null;
|
||||||
|
|
||||||
const { page } = stores();
|
const { page } = stores();
|
||||||
|
|
||||||
@@ -70,6 +70,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
show = localStorage.getItem('hideCourseNav') === 'false';
|
||||||
|
|
||||||
hotkeys('b', function (event, handler) {
|
hotkeys('b', function (event, handler) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
switch (handler.key) {
|
switch (handler.key) {
|
||||||
@@ -79,6 +81,12 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (process.browser && show !== null) {
|
||||||
|
localStorage.setItem('hideCourseNav', `${!show}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root z-10 {!show && 'hide'}">
|
<div class="root z-10 {!show && 'hide'}">
|
||||||
|
|||||||
@@ -5,10 +5,11 @@
|
|||||||
export let name;
|
export let name;
|
||||||
export let isEditable = false;
|
export let isEditable = false;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
|
export let className = '';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<label
|
<label
|
||||||
class="inline-flex items-center w-full {disabled
|
class="{className} inline-flex items-center w-full {disabled
|
||||||
? 'cursor-not-allowed'
|
? 'cursor-not-allowed'
|
||||||
: 'cursor-pointer'}"
|
: 'cursor-pointer'}"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -5,10 +5,11 @@
|
|||||||
export let name;
|
export let name;
|
||||||
export let isEditable = false;
|
export let isEditable = false;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
|
export let className = '';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<label
|
<label
|
||||||
class="inline-flex items-center w-full {disabled
|
class="{className} inline-flex items-center w-full {disabled
|
||||||
? 'cursor-not-allowed'
|
? 'cursor-not-allowed'
|
||||||
: 'cursor-pointer'}"
|
: 'cursor-pointer'}"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { afterUpdate } from 'svelte';
|
||||||
import CloseButton from '../Buttons/Close/index.svelte';
|
import CloseButton from '../Buttons/Close/index.svelte';
|
||||||
|
|
||||||
export let open;
|
export let open;
|
||||||
export let onClose;
|
export let onClose;
|
||||||
export let modalHeading;
|
export let modalHeading;
|
||||||
|
export let width;
|
||||||
|
|
||||||
|
afterUpdate(() => {
|
||||||
|
if (open) {
|
||||||
|
document.getElementsByTagName('body')[0].style.overflow = 'hidden';
|
||||||
|
} else {
|
||||||
|
document.getElementsByTagName('body')[0].style.overflow = 'auto';
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if open}
|
{#if open}
|
||||||
@@ -11,7 +21,7 @@
|
|||||||
class="dialog fixed overflow-x-hidden overflow-y-auto inset-0 flex justify-center items-center z-50 bg-gray-100 bg-opacity-50"
|
class="dialog fixed overflow-x-hidden overflow-y-auto inset-0 flex justify-center items-center z-50 bg-gray-100 bg-opacity-50"
|
||||||
>
|
>
|
||||||
<div aria-hidden="true" class="backdrop" />
|
<div aria-hidden="true" class="backdrop" />
|
||||||
<div class="bg-white mx-auto w-3xl shadow-lg p-5 pt-2 rounded-md">
|
<div class="bg-white mx-auto {width} shadow-lg p-5 pt-2 rounded-md">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h3 class="text-xl m-0">
|
<h3 class="text-xl m-0">
|
||||||
{modalHeading}
|
{modalHeading}
|
||||||
@@ -19,7 +29,9 @@
|
|||||||
<CloseButton onClick={onClose} contained={true} />
|
<CloseButton onClick={onClose} contained={true} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<slot />
|
<div class="body h-4/5 overflow-y-auto">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -28,4 +40,7 @@
|
|||||||
.dialog {
|
.dialog {
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
.body {
|
||||||
|
max-height: 60vh;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
if (isPreview) return;
|
if (isPreview) return;
|
||||||
const values = getVal(event.target, name);
|
const values = getVal(event.target, name);
|
||||||
onSubmit(name, values);
|
onSubmit(name, values);
|
||||||
// event.target.reset();
|
event.target.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePrevious(event) {
|
function handlePrevious(event) {
|
||||||
@@ -65,13 +65,14 @@
|
|||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
{#each options as option}
|
{#each options as option}
|
||||||
<button
|
<button
|
||||||
class="cursor-pointer text-left my-2 border-2 border-gray-300 p-2 rounded-md cursor-pointer hover:bg-gray-200 w-full {getValidationClassName(
|
class="cursor-pointer text-left my-2 border-2 border-gray-300 rounded-md cursor-pointer hover:bg-gray-200 w-full {getValidationClassName(
|
||||||
option
|
option
|
||||||
)}"
|
)}"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
{name}
|
{name}
|
||||||
|
className="p-2"
|
||||||
value={option.id}
|
value={option.id}
|
||||||
checked={defaultValue.includes(option.id)}
|
checked={defaultValue.includes(option.id)}
|
||||||
label={option.label || option.value}
|
label={option.label || option.value}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
if (isPreview) return;
|
if (isPreview) return;
|
||||||
const value = getRadioVal(event.target, name);
|
const value = getRadioVal(event.target, name);
|
||||||
onSubmit(name, [value]);
|
onSubmit(name, [value]);
|
||||||
// event.target.reset();
|
event.target.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePrevious(event) {
|
function handlePrevious(event) {
|
||||||
@@ -65,12 +65,13 @@
|
|||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
{#each options as option}
|
{#each options as option}
|
||||||
<button
|
<button
|
||||||
class="cursor-pointer text-left my-2 border-2 border-gray-300 p-2 rounded-md cursor-pointer hover:bg-gray-200 w-full {getValidationClassName(
|
class="cursor-pointer text-left my-2 border-2 border-gray-300 rounded-md cursor-pointer hover:bg-gray-200 w-full {getValidationClassName(
|
||||||
option
|
option
|
||||||
)}"
|
)}"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<RadioItem
|
<RadioItem
|
||||||
|
className="p-2"
|
||||||
{name}
|
{name}
|
||||||
value={option.id}
|
value={option.id}
|
||||||
checked={defaultValue.includes(option.id) &&
|
checked={defaultValue.includes(option.id) &&
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
*::-webkit-scrollbar {
|
*::-webkit-scrollbar {
|
||||||
width: 5px;
|
width: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
*::-webkit-scrollbar-track {
|
*::-webkit-scrollbar-track {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import ArrowUp16 from "carbon-icons-svelte/lib/ArrowUp16";
|
import ArrowUp16 from 'carbon-icons-svelte/lib/ArrowUp16';
|
||||||
import Chip from "../Chip/index.svelte";
|
import Chip from '../Chip/index.svelte';
|
||||||
|
|
||||||
export let value = 0;
|
export let value = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import { flip } from 'svelte/animate';
|
import { flip } from 'svelte/animate';
|
||||||
import { dndzone } from 'svelte-dnd-action';
|
import { dndzone } from 'svelte-dnd-action';
|
||||||
|
import { stores, goto } from '@sapper/app';
|
||||||
import PageNav from '../../../components/PageNav/index.svelte';
|
import PageNav from '../../../components/PageNav/index.svelte';
|
||||||
|
import MarkExerciseModal from '../../../components/Course/components/Lesson/Exercise/MarkExerciseModal.svelte';
|
||||||
import Chip from '../../../components/Chip/index.svelte';
|
import Chip from '../../../components/Chip/index.svelte';
|
||||||
import PageBody from '../../../components/PageBody/index.svelte';
|
import PageBody from '../../../components/PageBody/index.svelte';
|
||||||
import CourseContainer from '../../../components/CourseContainer/index.svelte';
|
import CourseContainer from '../../../components/CourseContainer/index.svelte';
|
||||||
@@ -29,15 +31,111 @@
|
|||||||
title: 'Submitted',
|
title: 'Submitted',
|
||||||
value: 5,
|
value: 5,
|
||||||
items: [
|
items: [
|
||||||
{ id: 41, name: 'item41' },
|
{
|
||||||
{ id: 42, name: 'item42' },
|
id: 41,
|
||||||
{ id: 43, name: 'item43' },
|
exerciseTitle: 'Practice props in React.js',
|
||||||
{ id: 44, name: 'item44' },
|
student: {
|
||||||
{ id: 45, name: 'item45' },
|
avatar: 'https://picsum.photos/32/32/?random',
|
||||||
{ id: 46, name: 'item46' },
|
name: 'Brad Traversy',
|
||||||
{ id: 47, name: 'item47' },
|
},
|
||||||
{ id: 48, name: 'item48' },
|
tutor: {
|
||||||
{ id: 49, name: 'item49' },
|
name: 'sergey-semko',
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
lesson: {
|
||||||
|
number: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 42,
|
||||||
|
exerciseTitle: 'Practice props in React.js',
|
||||||
|
student: {
|
||||||
|
avatar: 'https://picsum.photos/32/32/?random',
|
||||||
|
name: 'James Kola',
|
||||||
|
},
|
||||||
|
tutor: {
|
||||||
|
name: 'sergey-semko',
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
lesson: {
|
||||||
|
number: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 43,
|
||||||
|
exerciseTitle: 'Practice props in React.js',
|
||||||
|
student: {
|
||||||
|
avatar: 'https://picsum.photos/32/32/?random',
|
||||||
|
name: 'linus-torvalds',
|
||||||
|
},
|
||||||
|
tutor: {
|
||||||
|
name: 'sergey-semko',
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
lesson: {
|
||||||
|
number: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 44,
|
||||||
|
exerciseTitle: 'Practice props in React.js',
|
||||||
|
student: {
|
||||||
|
avatar: 'https://picsum.photos/32/32/?random',
|
||||||
|
name: 'rotimi-best',
|
||||||
|
},
|
||||||
|
tutor: {
|
||||||
|
name: 'sergey-semko',
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
lesson: {
|
||||||
|
number: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 45,
|
||||||
|
exerciseTitle: 'Home task: Class and Functional component',
|
||||||
|
student: {
|
||||||
|
avatar: 'https://picsum.photos/32/32/?random',
|
||||||
|
name: 'james-two',
|
||||||
|
},
|
||||||
|
tutor: {
|
||||||
|
name: 'sergey-semko',
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
lesson: {
|
||||||
|
number: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 46,
|
||||||
|
exerciseTitle: 'Home task: Class and Functional component',
|
||||||
|
student: {
|
||||||
|
avatar: 'https://picsum.photos/32/32/?random',
|
||||||
|
name: 'brarack-obama',
|
||||||
|
},
|
||||||
|
tutor: {
|
||||||
|
name: 'sergey-semko',
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
lesson: {
|
||||||
|
number: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 47,
|
||||||
|
exerciseTitle: 'Lesson task: Class and Functional component',
|
||||||
|
student: {
|
||||||
|
avatar: 'https://picsum.photos/32/32/?random',
|
||||||
|
name: 'bill-gates',
|
||||||
|
},
|
||||||
|
tutor: {
|
||||||
|
name: 'sergey-semko',
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
lesson: {
|
||||||
|
number: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -53,6 +151,9 @@
|
|||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
let openExercise = false;
|
||||||
|
|
||||||
|
const { page } = stores();
|
||||||
|
|
||||||
function handleItemFinalize(columnIdx, newItems) {
|
function handleItemFinalize(columnIdx, newItems) {
|
||||||
sections[columnIdx].items = newItems;
|
sections[columnIdx].items = newItems;
|
||||||
@@ -69,16 +170,26 @@
|
|||||||
function handleDndFinalizeCards(columnIdx) {
|
function handleDndFinalizeCards(columnIdx) {
|
||||||
return (e) => handleItemFinalize(columnIdx, e.detail.items);
|
return (e) => handleItemFinalize(columnIdx, e.detail.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleModalClose() {
|
||||||
|
console.log('closing', page.path);
|
||||||
|
|
||||||
|
goto($page.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$: openExercise = !!$page.query.submissionId;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<MarkExerciseModal bind:open={openExercise} onClose={handleModalClose} />
|
||||||
|
|
||||||
<CourseContainer {courseData}>
|
<CourseContainer {courseData}>
|
||||||
<PageNav title="All Exercises" />
|
<PageNav title="All Exercises" />
|
||||||
|
|
||||||
<PageBody width="w-11/12 overflow-x-auto overflow-y-hidden h-3/4">
|
<PageBody width="w-11/12 overflow-x-auto overflow-y-hidden">
|
||||||
<div class="flex items-center justify-between h-full w-full">
|
<div class="flex items-center justify-between w-full">
|
||||||
{#each sections as { id, title, items }, idx (id)}
|
{#each sections as { id, title, items }, idx (id)}
|
||||||
<div
|
<div
|
||||||
class="section rounded-md bg-gray-100 border border-gray-50 p-3 h-full mr-3 overflow-hidden"
|
class="section rounded-md bg-gray-100 border border-gray-50 p-3 h-80 mr-3 overflow-hidden"
|
||||||
animate:flip={{ duration: flipDurationMs }}
|
animate:flip={{ duration: flipDurationMs }}
|
||||||
>
|
>
|
||||||
<div class="flex items-center mb-2">
|
<div class="flex items-center mb-2">
|
||||||
@@ -100,24 +211,27 @@
|
|||||||
class="border border-grey-700 w-full my-2 mx-0 rounded-md bg-white py-2 px-3"
|
class="border border-grey-700 w-full my-2 mx-0 rounded-md bg-white py-2 px-3"
|
||||||
animate:flip={{ duration: flipDurationMs }}
|
animate:flip={{ duration: flipDurationMs }}
|
||||||
>
|
>
|
||||||
<a class="text-blue-700 text-md font-bold" href="courses/1"
|
|
||||||
>{item.name}</a
|
|
||||||
>
|
|
||||||
<p class="text-grey text-sm mb-2">#2 created by sergey-semko</p>
|
|
||||||
|
|
||||||
<a
|
<a
|
||||||
class="flex items-center no-underline hover:underline text-black"
|
class="flex items-center no-underline hover:underline text-black mb-2"
|
||||||
href="/"
|
href="{$page.path}?submissionId={item.id}"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Placeholder"
|
alt="Placeholder"
|
||||||
class="block rounded-full"
|
class="block rounded-full"
|
||||||
width="24"
|
width="24"
|
||||||
height="20"
|
height="20"
|
||||||
src="https://picsum.photos/32/32/?random"
|
src={item.student.avatar}
|
||||||
/>
|
/>
|
||||||
<p class="ml-2 text-sm">rotimi-best</p>
|
<p class="ml-2 text-sm">{item.student.name}</p>
|
||||||
</a>
|
</a>
|
||||||
|
<a
|
||||||
|
class="text-blue-700 text-md font-bold"
|
||||||
|
href="{$page.path}?submissionId={item.id}"
|
||||||
|
>{item.exerciseTitle}</a
|
||||||
|
>
|
||||||
|
<p class="text-grey text-sm">
|
||||||
|
#{item.lesson.number} created by {item.tutor.name}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -131,9 +245,10 @@
|
|||||||
.section {
|
.section {
|
||||||
max-width: 355px;
|
max-width: 355px;
|
||||||
min-width: 355px;
|
min-width: 355px;
|
||||||
|
height: 80vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
height: 90%;
|
height: 95%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user