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> <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'}">
+2 -1
View File
@@ -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'}"
> >
+2 -1
View File
@@ -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'}"
> >
+17 -2
View File
@@ -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) &&
+1 -1
View File
@@ -14,7 +14,7 @@
} }
*::-webkit-scrollbar { *::-webkit-scrollbar {
width: 5px; width: 10px;
} }
*::-webkit-scrollbar-track { *::-webkit-scrollbar-track {
+2 -2
View File
@@ -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;
+137 -22
View File
@@ -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>