Complete questionnaire builder

This commit is contained in:
Ibitoye Rotimi Best
2021-07-06 08:39:17 +03:00
parent c4ed327d36
commit 946d99d273
17 changed files with 227 additions and 108 deletions
@@ -45,7 +45,6 @@
hasUserVoted = poll.options.some((option) =>
option.selectedBy.find((u) => u.value === currentUserId)
);
console.log("hasUserVoted", hasUserVoted, currentUserId);
}
</script>
@@ -69,7 +69,6 @@
function handleMarkAnswered(id) {
return () => {
console.log(`id`, id);
questions = questions
.map((question) => {
if (question.id === id) {
+9 -9
View File
@@ -28,19 +28,19 @@
}
onMount(() => {
hotkeys("ctrl+1,ctrl+2,ctrl+3,ctrl+4", function (event, handler) {
hotkeys("A+1,A+2,A+3,A+4", function (event, handler) {
event.preventDefault();
switch (handler.key) {
case "ctrl+1":
case "A+1":
handleAppClick(APPS_CONSTANTS.APPS.LIVE_CHAT);
break;
case "ctrl+2":
case "A+2":
handleAppClick(APPS_CONSTANTS.APPS.QANDA);
break;
case "ctrl+3":
case "A+3":
handleAppClick(APPS_CONSTANTS.APPS.NOTES);
break;
case "ctrl+4":
case "A+4":
handleAppClick(APPS_CONSTANTS.APPS.POLL);
break;
}
@@ -51,7 +51,7 @@
<div class="root">
<div class="apps">
<IconButton
toolTipProps={{ title: "Live Chat", hotkeys: ["Ctrl", "1"] }}
toolTipProps={{ title: "Live Chat", hotkeys: ["A", "1"] }}
value={APPS_CONSTANTS.APPS.LIVE_CHAT}
onClick={handleAppClick}
selected={APPS_CONSTANTS.APPS.LIVE_CHAT === selectedApp}
@@ -60,7 +60,7 @@
</IconButton>
<IconButton
toolTipProps={{ title: "QandA", hotkeys: ["Ctrl", "2"] }}
toolTipProps={{ title: "QandA", hotkeys: ["A", "2"] }}
value={APPS_CONSTANTS.APPS.QANDA}
onClick={handleAppClick}
selected={APPS_CONSTANTS.APPS.QANDA === selectedApp}
@@ -69,7 +69,7 @@
</IconButton>
<IconButton
toolTipProps={{ title: "Notes", hotkeys: ["Ctrl", "3"] }}
toolTipProps={{ title: "Notes", hotkeys: ["A", "3"] }}
value={APPS_CONSTANTS.APPS.NOTES}
onClick={handleAppClick}
selected={APPS_CONSTANTS.APPS.NOTES === selectedApp}
@@ -78,7 +78,7 @@
</IconButton>
<IconButton
toolTipProps={{ title: "Poll", hotkeys: ["Ctrl", "4"] }}
toolTipProps={{ title: "Poll", hotkeys: ["A", "4"] }}
value={APPS_CONSTANTS.APPS.POLL}
onClick={handleAppClick}
selected={APPS_CONSTANTS.APPS.POLL === selectedApp}
-2
View File
@@ -21,7 +21,6 @@
];
function handleFacultyFilters() {
console.log("clicking");
facultyFilterOpened = facultyFilterOpened ? false : true;
}
let things = [
@@ -34,7 +33,6 @@
function handleClick() {
things = things.slice(1);
console.log("things", things);
}
</script>
@@ -83,10 +83,10 @@
];
onMount(() => {
hotkeys("ctrl+b", function (event, handler) {
hotkeys("b", function (event, handler) {
event.preventDefault();
switch (handler.key) {
case "ctrl+b":
case "b":
show = !show;
break;
}
@@ -128,7 +128,7 @@
onClick={() => (show = !show)}
toolTipProps={{
title: "Toggle sidebar",
hotkeys: ["Ctrl", "B"],
hotkeys: ["B"],
direction: "right",
}}
>
@@ -5,7 +5,7 @@
import TextareaQuestion from "../../../../Question/TextareaQuestion/index.svelte";
import PrimaryButton from "../../../../PrimaryButton/index.svelte";
import Progress from "../../../../Progress/index.svelte";
import { removeDuplicate } from "../../../../../utils/functions/removeDuplicate";
import { QUESTION_TYPE } from "../../../../Question/constants";
export let preview;
@@ -15,6 +15,8 @@
let currentQuestion = {};
let renderProps = {};
let progressValue = 0;
let isAnswerCorrect;
let isFinished;
const { questions, title, description } = $questionnaire;
@@ -22,39 +24,98 @@
currentQuestionIndex += 1;
}
function onSubmit(name, value) {
// if (!value) return;
function onSubmit(id, value) {
const prevAnswer = answers[id] || [];
const formattedAnswer =
typeof value === "string"
? value
: removeDuplicate([...prevAnswer, ...(value || [])]);
answers = {
...answers,
[name]: value,
[id]: formattedAnswer,
};
currentQuestionIndex += 1;
if (isAnswerCorrect) {
currentQuestionIndex += 1;
}
}
function onPrevious() {
currentQuestionIndex -= 1;
}
function getRenderProps(question, questionIndex) {
function wasCorrectAnswerSelected(currentQuestion) {
const isOpenQuesiton = currentQuestion.type === QUESTION_TYPE.TEXTAREA;
if (isOpenQuesiton) {
isAnswerCorrect = true;
return true;
}
const answer = answers[currentQuestion.id];
const formattedAnswers = typeof answer === "string" ? [answer] : answer;
const options = currentQuestion.options;
isAnswerCorrect =
formattedAnswers &&
formattedAnswers.some((answer) => {
if (
!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:
question.type === QUESTION_TYPE.TEXTAREA
? question.value || ""
: answers[question.id] || [],
defaultValue: isOpenQuesiton
? answers[question.id] || ""
: answers[question.id] || [],
onSubmit,
onPrevious,
disablePreviousButton: questionIndex === 1,
isLast: questionIndex === questions.length,
isPreview: preview,
isLast,
isPreview: preview || isFinished,
disabled: wasCorrectAnswerSelected(question) && 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 getProgressValue(currentQuestionIndex) {
if (!currentQuestion) {
if (isFinished) {
return 100;
}
@@ -65,9 +126,16 @@
$: {
currentQuestion = questions[currentQuestionIndex - 1];
if (currentQuestionIndex > 0 && !currentQuestion) {
isFinished = true;
}
if (currentQuestion) {
renderProps = getRenderProps(currentQuestion, currentQuestionIndex);
renderProps = getRenderProps(
currentQuestion,
currentQuestionIndex,
answers
);
}
}
@@ -82,26 +150,17 @@
{#if preview}
{#each questions as currentQuestion, currentQuestionIndex}
{#if currentQuestionIndex === 0}
<div class="mb-2">
<h2 class="my-1">{title}</h2>
<p>{description}</p>
</div>
{:else if !currentQuestion}
<pre>
<code>{JSON.stringify(answers)}</code>
</pre>
{:else if QUESTION_TYPE.RADIO === currentQuestion.type}
{#if QUESTION_TYPE.RADIO === currentQuestion.type}
<RadioQuestion
{...getRenderProps(currentQuestion, currentQuestionIndex)}
{...getRenderProps(currentQuestion, currentQuestionIndex + 1, answers)}
/>
{:else if QUESTION_TYPE.CHECKBOX === currentQuestion.type}
<CheckboxQuestion
{...getRenderProps(currentQuestion, currentQuestionIndex)}
{...getRenderProps(currentQuestion, currentQuestionIndex + 1, answers)}
/>
{:else if QUESTION_TYPE.TEXTAREA === currentQuestion.type}
<TextareaQuestion
{...getRenderProps(currentQuestion, currentQuestionIndex)}
{...getRenderProps(currentQuestion, currentQuestionIndex + 1, answers)}
/>
{/if}
{/each}
@@ -117,10 +176,23 @@
type="button"
/>
</div>
{:else if !currentQuestion}
<pre>
<code>{JSON.stringify(answers)}</code>
</pre>
{:else if isFinished}
<h2 class="text-lg">Congratulations you have completed your task</h2>
{#each questions as currentQuestion, currentQuestionIndex}
{#if QUESTION_TYPE.RADIO === currentQuestion.type}
<RadioQuestion
{...getRenderProps(currentQuestion, currentQuestionIndex + 1, answers)}
/>
{:else if QUESTION_TYPE.CHECKBOX === currentQuestion.type}
<CheckboxQuestion
{...getRenderProps(currentQuestion, currentQuestionIndex + 1, answers)}
/>
{:else if QUESTION_TYPE.TEXTAREA === currentQuestion.type}
<TextareaQuestion
{...getRenderProps(currentQuestion, currentQuestionIndex + 1, answers)}
/>
{/if}
{/each}
{:else if QUESTION_TYPE.RADIO === currentQuestion.type}
<RadioQuestion {...renderProps} />
{:else if QUESTION_TYPE.CHECKBOX === currentQuestion.type}
@@ -11,27 +11,27 @@ export const questionnaire = writable({
questions: [
{
id: "react-founder",
type: QUESTION_TYPE.RADIO,
type: QUESTION_TYPE.CHECKBOX,
title: "Who is the creator of React.js",
options: [
{
id: 1,
id: "1",
value: "Dan Abrahmov",
},
{
id: 2,
id: "2",
value: "Google",
},
{
id: 3,
id: "3",
value: "Facebook",
},
{
id: 4,
id: "4",
value: "Traversy Media",
},
],
answers: [],
answers: ['3'],
},
{
id: "vue-founder",
@@ -44,47 +44,47 @@ export const questionnaire = writable({
`,
options: [
{
id: 1,
id: "1",
value: "Evan Vue",
},
{
id: 2,
id: "2",
value: "Mark Zukerberg",
},
{
id: 3,
id: "3",
value: "Prince Charles",
},
{
id: 4,
id: "4",
value: "Bill Gates",
},
],
answers: [],
answers: ['3'],
},
{
id: "svelte-founder",
type: QUESTION_TYPE.CHECKBOX,
type: QUESTION_TYPE.RADIO,
title: "Who is the creator of Svelte.js",
options: [
{
id: 1,
id: "1",
value: "Hillary Svelte",
},
{
id: 2,
id: "2",
value: "Mircosoft",
},
{
id: 3,
id: "3",
value: "Elevate",
},
{
id: 4,
id: "4",
value: "Coding Train",
},
],
answers: [2],
answers: ["2"],
},
{
id: "angular-founder",
@@ -109,7 +109,7 @@ export function handleAddQuestion() {
answers: [],
options: [
{
id: 1,
id: "1",
value: null,
},
],
@@ -215,7 +215,9 @@ export function handleAnswerSelect(questionId, optionId) {
const selectOption = question.options.find(option => option.id === optionId);
const newAnswers = [];
if (question.answers.includes(selectOption.id)) {
if (question.type === QUESTION_TYPE.RADIO) {
newAnswers.push(optionId)
} else if (question.answers.includes(selectOption.id)) {
newAnswers.push(
...question.answers.filter(answer => answer !== selectOption.id)
)
@@ -1,5 +1,4 @@
<script>
import { onMount } from "svelte";
import PageNav from "../../../PageNav/index.svelte";
import Classwork from "./Classwork.svelte";
import Homework from "./Homework/index.svelte";
@@ -9,10 +8,6 @@
export let path;
export let tab;
export let showLectureHome;
onMount(() => {
console.log("mounted");
});
</script>
{#if showLectureHome}
@@ -1,2 +0,0 @@
import { writable } from "svelte/store";
-1
View File
@@ -9,7 +9,6 @@
function onClick(optionIndex) {
return () => {
open = !open;
console.log("onClick", optionIndex);
handleOptionClick(optionIndex);
};
}
+7 -2
View File
@@ -4,15 +4,20 @@
export let checked;
export let name;
export let isEditable = false;
export let disabled = false;
</script>
<label class="inline-flex items-center w-full">
<label
class="inline-flex items-center w-full {disabled
? 'cursor-not-allowed'
: 'cursor-pointer'}"
>
<input
type="checkbox"
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-offset-0 focus:ring-indigo-200 focus:ring-opacity-50"
{name}
{value}
disabled={isEditable}
disabled={disabled || isEditable}
bind:checked
/>
{#if isEditable}
+7 -2
View File
@@ -4,16 +4,21 @@
export let checked;
export let name;
export let isEditable = false;
export let disabled = false;
</script>
<label class="inline-flex items-center w-full">
<label
class="inline-flex items-center w-full {disabled
? 'cursor-not-allowed'
: 'cursor-pointer'}"
>
<input
class="form-radio"
type="radio"
{checked}
{name}
{value}
disabled={isEditable}
disabled={disabled || isEditable}
/>
{#if isEditable}
<input
+3 -3
View File
@@ -8,13 +8,13 @@
export let variant = VARIANTS.CONTAINED;
export let disablePadding = false;
export let isDisabled = false;
const variantClass = VARIANTS_CLASS[variant];
</script>
<button
class="{isDisabled &&
'bg-opacity-25 hover:bg-opacity-25 cursor-not-allowed'} flex items-center h-auto {variantClass} {!disablePadding &&
'bg-opacity-25 hover:bg-opacity-25 cursor-not-allowed'} flex items-center h-auto {VARIANTS_CLASS[
variant
]} {!disablePadding &&
'py-1 px-5'} rounded {className} transition delay-150 duration-300 ease-in-out "
on:click={onClick}
{name}
@@ -8,20 +8,21 @@
export let code;
export let name = "";
export let options = [];
export let answers = [];
export let onSubmit = () => {};
export let onPrevious = () => {};
export let defaultValue = [];
export let disablePreviousButton = false;
export let isLast = false;
export let isPreview = false;
export let nextButtonProps = {};
function getRadioVal(form, name) {
function getVal(form, name) {
let values = [];
const radios = form.elements[name];
const checkboxEl = form.elements[name];
for (let i = 0, len = radios.length; i < len; i++) {
if (radios[i].checked) {
values.push(radios[i].value);
for (let i = 0, len = checkboxEl.length; i < len; i++) {
if (checkboxEl[i].checked) {
values.push(checkboxEl[i].value);
}
}
@@ -30,15 +31,27 @@
function handleFormSubmit(event) {
if (isPreview) return;
const values = getRadioVal(event.target, name);
const values = getVal(event.target, name);
onSubmit(name, values);
event.target.reset();
// event.target.reset();
}
function handlePrevious(event) {
event.preventDefault();
onPrevious();
}
function getValidationClassName(option) {
if (defaultValue.includes(option.id)) {
if (answers.includes(option.id)) {
return "border-green-700";
} else {
return "border-red-700";
}
}
return "";
}
</script>
<form on:submit|preventDefault={handleFormSubmit}>
@@ -51,14 +64,17 @@
<div class="ml-4">
{#each options as option}
<button
class="cursor-pointer text-left my-2 border border-gray-300 p-2 rounded-md cursor-pointer hover:bg-gray-200 w-full"
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(
option
)}"
type="button"
>
<Checkbox
{name}
value={option.value}
checked={defaultValue.includes(option.value)}
value={option.id}
checked={defaultValue.includes(option.id)}
label={option.label || option.value}
disabled={nextButtonProps.disableOptionSelect}
/>
</button>
{/each}
@@ -72,9 +88,11 @@
variant={VARIANTS.OUTLINED}
/>
<PrimaryButton
variant={VARIANTS.OUTLINED}
variant={nextButtonProps.isActive
? VARIANTS.CONTAINED
: VARIANTS.OUTLINED}
type="submit"
label={isLast ? "Finish" : "Next"}
label={nextButtonProps.label}
{name}
/>
</div>
@@ -8,12 +8,13 @@
export let code;
export let name = "";
export let options = [];
export let answers = [];
export let onSubmit = () => {};
export let onPrevious = () => {};
export let defaultValue = "";
export let disablePreviousButton = false;
export let isLast = false;
export let isPreview = false;
export let nextButtonProps = {};
function getRadioVal(form, name) {
let val;
@@ -31,14 +32,26 @@
function handleFormSubmit(event) {
if (isPreview) return;
const value = getRadioVal(event.target, name);
onSubmit(name, value);
event.target.reset();
onSubmit(name, [value]);
// event.target.reset();
}
function handlePrevious(event) {
event.preventDefault();
onPrevious();
}
function getValidationClassName(option) {
if (defaultValue.includes(option.id)) {
if (answers.includes(option.id)) {
return "border-green-700";
} else {
return "border-red-700";
}
}
return "";
}
</script>
<form on:submit|preventDefault={handleFormSubmit}>
@@ -51,14 +64,18 @@
<div class="ml-4">
{#each options as option}
<button
class="cursor-pointer text-left my-2 border border-gray-300 p-2 rounded-md cursor-pointer hover:bg-gray-200 w-full"
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(
option
)}"
type="button"
>
<RadioItem
{name}
value={option.value}
checked={option.value === defaultValue}
value={option.id}
checked={defaultValue.includes(option.id) &&
answers.includes(option.id)}
label={option.label || option.value}
disabled={nextButtonProps.disableOptionSelect}
/>
</button>
{/each}
@@ -73,9 +90,11 @@
variant={VARIANTS.OUTLINED}
/>
<PrimaryButton
variant={VARIANTS.OUTLINED}
variant={nextButtonProps.isActive
? VARIANTS.CONTAINED
: VARIANTS.OUTLINED}
type="submit"
label={isLast ? "Finish" : "Next"}
label={nextButtonProps.label}
{name}
/>
</div>
@@ -13,12 +13,13 @@
export let disablePreviousButton = false;
export let isLast = false;
export let isPreview = false;
export let disabled = false;
function handleFormSubmit(event) {
if (isPreview) return;
onSubmit(name, defaultValue);
event.target.reset();
// event.target.reset();
}
function handlePrevious(event) {
@@ -35,11 +36,15 @@
{/if}
<div class="ml-4">
<TextArea
bind:value={defaultValue}
rows="5"
placeholder="Write your answer here"
/>
{#if disabled}
<CodeSnippet code={defaultValue} />
{:else}
<TextArea
bind:value={defaultValue}
rows="5"
placeholder="Write your answer here"
/>
{/if}
</div>
{#if !isPreview}
+5
View File
@@ -0,0 +1,5 @@
export function removeDuplicate(arr) {
let s = new Set(arr);
let it = s.values();
return Array.from(it);
}