CLASSROOMIO-T-112:Redesigned the UI of the Exercise page
CLASSROOMIO-T-112:Fixed the page reload for student and tutors Merge branch 'CLASSROOMIO-T-112' of https://gitlab.com/thenextgreat/classroomio into CLASSROOMIO-T-112 CLASSROOMIO-T-112:Fixed the Modal button Merge branch 'CLASSROOMIO-T-112' of https://gitlab.com/thenextgreat/classroomio into CLASSROOMIO-T-112 CLASSROOMIO-T-112: UX improvement CLASSROOMIO-T-112:Removed the console log CLASSROOMIO-T-112:Updated the UI/UX of the exercise page Merge branch 'main' into CLASSROOMIO-T-112 CLASSROOMIO-T-112:Redesigned the UI of the Exercise page Merge branch 'CLASSROOMIO-T-112' of https://gitlab.com/thenextgreat/classroomio into CLASSROOMIO-T-112 CLASSROOMIO-T-112:Made the empty fit the page CLASSROOMIO-T-112: Handle cases of options in a question not saving. Co-authored-by: Ifeanyi Nwosu <ifeanyi@digdippa.com> Co-authored-by: Rotimi Best <rotimiibitoyeemma@gmail.com> Merge-request: CLASSROOMIO-MR-60 Merged-by: Best Admin <best@digdippa.com>
This commit is contained in:
committed by
Space Cloud
co-authored by
Ifeanyi Nwosu
Rotimi Best
parent
ce63736b40
commit
9d0fd56f05
@@ -19,12 +19,10 @@
|
|||||||
import Modal from '$lib/components/Modal/index.svelte';
|
import Modal from '$lib/components/Modal/index.svelte';
|
||||||
import TextArea from '$lib/components/Form/TextArea.svelte';
|
import TextArea from '$lib/components/Form/TextArea.svelte';
|
||||||
import Checkbox from '$lib/components/Form/Checkbox.svelte';
|
import Checkbox from '$lib/components/Form/Checkbox.svelte';
|
||||||
import DateTime from '$lib/components/Form/DateTime.svelte';
|
|
||||||
import RadioItem from '$lib/components/Form/RadioItem.svelte';
|
import RadioItem from '$lib/components/Form/RadioItem.svelte';
|
||||||
import IconButton from '$lib/components/IconButton/index.svelte';
|
import IconButton from '$lib/components/IconButton/index.svelte';
|
||||||
import ErrorMessage from '$lib/components/ErrorMessage/index.svelte';
|
import ErrorMessage from '$lib/components/ErrorMessage/index.svelte';
|
||||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||||
import EditContent from '$lib/components/EditContent/index.svelte';
|
|
||||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||||
import Select from '$lib/components/Form/Select.svelte';
|
import Select from '$lib/components/Form/Select.svelte';
|
||||||
import QuestionContainer from '$lib/components/QuestionContainer/index.svelte';
|
import QuestionContainer from '$lib/components/QuestionContainer/index.svelte';
|
||||||
@@ -34,6 +32,7 @@
|
|||||||
import { filterOutDeleted } from './functions';
|
import { filterOutDeleted } from './functions';
|
||||||
import { deleteExercise } from '$lib/utils/services/courses';
|
import { deleteExercise } from '$lib/utils/services/courses';
|
||||||
import { lesson } from '../store/lessons';
|
import { lesson } from '../store/lessons';
|
||||||
|
|
||||||
// import EditContent from "$lib/components/EditContent/index.svelte";
|
// import EditContent from "$lib/components/EditContent/index.svelte";
|
||||||
|
|
||||||
// let value = readme;
|
// let value = readme;
|
||||||
@@ -47,13 +46,12 @@
|
|||||||
let errors = {};
|
let errors = {};
|
||||||
let questionIdToDelete = null;
|
let questionIdToDelete = null;
|
||||||
let questions = [];
|
let questions = [];
|
||||||
let shouldDelete = false;
|
|
||||||
let isDeleting = false;
|
let isDeleting = false;
|
||||||
|
|
||||||
function shouldScrollToLast(questionId, questions) {
|
function shouldScrollToLast(questionId, questions) {
|
||||||
|
const [lastQuestion] = questions.slice(-1);
|
||||||
const currentQuestionsLength = questions.length;
|
const currentQuestionsLength = questions.length;
|
||||||
const index = questions.findIndex((question) => question.id === questionId);
|
const isLast = lastQuestion.id === questionId;
|
||||||
const isLast = index + 1 === currentQuestionsLength;
|
|
||||||
|
|
||||||
return isLast && initialQuestionsLength !== currentQuestionsLength;
|
return isLast && initialQuestionsLength !== currentQuestionsLength;
|
||||||
}
|
}
|
||||||
@@ -97,8 +95,6 @@
|
|||||||
..._lesson,
|
..._lesson,
|
||||||
exercises: _lesson.exercises.filter((exercise) => exercise.id !== exerciseId)
|
exercises: _lesson.exercises.filter((exercise) => exercise.id !== exerciseId)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
shouldDelete = false;
|
|
||||||
goBack();
|
goBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,62 +115,26 @@
|
|||||||
width="w-2/4"
|
width="w-2/4"
|
||||||
modalHeading="Update description"
|
modalHeading="Update description"
|
||||||
>
|
>
|
||||||
{#if shouldDelete}
|
<form on:submit|preventDefault>
|
||||||
<form on:submit|preventDefault={() => (shouldDelete = false)}>
|
<h1 class="dark:text-white text-2xl">Are you sure?</h1>
|
||||||
<h1 class="dark:text-white text-2xl">Are you sure?</h1>
|
|
||||||
|
|
||||||
<div class="mt-5 flex items-center justify-between">
|
<div class="mt-5 flex items-center justify-between">
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
className="px-6 py-3"
|
className="px-6 py-3"
|
||||||
variant={VARIANTS.OUTLINED}
|
variant={VARIANTS.OUTLINED}
|
||||||
label="No, cancel"
|
label="No, cancel"
|
||||||
type="submit"
|
type="submit"
|
||||||
/>
|
onClick={() => (editDescription = false)}
|
||||||
<PrimaryButton
|
/>
|
||||||
className="px-6 py-3"
|
<PrimaryButton
|
||||||
variant={VARIANTS.CONTAINED}
|
className="px-6 py-3"
|
||||||
label={isDeleting ? 'Deleting...' : 'Yes, delete'}
|
variant={VARIANTS.CONTAINED}
|
||||||
isDisabled={isDeleting}
|
label={isDeleting ? 'Deleting...' : 'Yes, delete'}
|
||||||
onClick={handleDelete}
|
isDisabled={isDeleting}
|
||||||
/>
|
onClick={handleDelete}
|
||||||
</div>
|
/>
|
||||||
</form>
|
|
||||||
{:else}
|
|
||||||
<div>
|
|
||||||
<QuestionContainer isTitle={true}>
|
|
||||||
<TextField
|
|
||||||
placeholder="Title"
|
|
||||||
bind:value={$questionnaire.title}
|
|
||||||
className="mb-2"
|
|
||||||
onChange={() => ($questionnaire.is_title_dirty = true)}
|
|
||||||
/>
|
|
||||||
<DateTime
|
|
||||||
label="Due by"
|
|
||||||
className="w-50"
|
|
||||||
value={$questionnaire.due_by}
|
|
||||||
onInput={(e) => {
|
|
||||||
$questionnaire.due_by = e.target.value;
|
|
||||||
$questionnaire.is_due_by_dirty = true;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<EditContent
|
|
||||||
writeLabel="Description"
|
|
||||||
bind:value={$questionnaire.description}
|
|
||||||
placeholder="Start typing your lesson"
|
|
||||||
textAreaHeight="100px"
|
|
||||||
onInputChange={() => ($questionnaire.is_description_dirty = true)}
|
|
||||||
/>
|
|
||||||
</QuestionContainer>
|
|
||||||
<div class="float-right">
|
|
||||||
<PrimaryButton
|
|
||||||
className="px-6 py-3"
|
|
||||||
variant={VARIANTS.CONTAINED}
|
|
||||||
label="Delete this exercise"
|
|
||||||
onClick={() => (shouldDelete = true)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
</form>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
@@ -207,14 +167,6 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <Dropdown
|
|
||||||
options={extraActions}
|
|
||||||
handleOptionClick={handleOptionClick(question.id)}
|
|
||||||
>
|
|
||||||
<AddFilledIcon class="carbon-icon dark:text-white" />
|
|
||||||
<p class="dark:text-white ml-2 text-gray-600">Add</p>
|
|
||||||
</Dropdown> -->
|
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
bind:value={question.question_type}
|
bind:value={question.question_type}
|
||||||
options={QUESTION_TYPES}
|
options={QUESTION_TYPES}
|
||||||
@@ -297,7 +249,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if QUESTION_TYPE.TEXTAREA === question.question_type.id}
|
{#if QUESTION_TYPE.TEXTAREA === question.question_type.id}
|
||||||
<TextArea bind:value={question.value} />
|
<TextArea bind:value={question.value} disabled={true} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if getQuestionErrorMsg(errors, question, 'option')}
|
{#if getQuestionErrorMsg(errors, question, 'option')}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<script>
|
||||||
|
import QuestionContainer from '$lib/components/QuestionContainer/index.svelte';
|
||||||
|
import EditContent from '$lib/components/EditContent/index.svelte';
|
||||||
|
import DateTime from '$lib/components/Form/DateTime.svelte';
|
||||||
|
import TextField from '$lib/components/Form/TextField.svelte';
|
||||||
|
import { questionnaire } from '../store/exercise';
|
||||||
|
import { marked } from 'marked';
|
||||||
|
export let preview;
|
||||||
|
|
||||||
|
function getTotalPossibleGrade() {
|
||||||
|
return $questionnaire.questions.reduce((acc, question) => {
|
||||||
|
acc += parseFloat(question.points, 10);
|
||||||
|
return acc;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="mb-5 px-6">
|
||||||
|
<QuestionContainer isTitle={true}>
|
||||||
|
{#if !preview}
|
||||||
|
<TextField
|
||||||
|
placeholder="Title"
|
||||||
|
bind:value={$questionnaire.title}
|
||||||
|
className="mb-2"
|
||||||
|
onChange={() => ($questionnaire.is_title_dirty = true)}
|
||||||
|
/>
|
||||||
|
<DateTime
|
||||||
|
label="Due by"
|
||||||
|
className="w-50"
|
||||||
|
value={$questionnaire.due_by}
|
||||||
|
onInput={(e) => {
|
||||||
|
$questionnaire.due_by = e.target.value;
|
||||||
|
$questionnaire.is_due_by_dirty = true;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<EditContent
|
||||||
|
writeLabel="Description"
|
||||||
|
bind:value={$questionnaire.description}
|
||||||
|
placeholder="Start typing your lesson"
|
||||||
|
textAreaHeight="100px"
|
||||||
|
onInputChange={() => ($questionnaire.is_description_dirty = true)}
|
||||||
|
/>
|
||||||
|
{:else if preview}
|
||||||
|
<h2 class="my-1">{$questionnaire.title}</h2>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<p class="dark:text-white mx-2">
|
||||||
|
<strong>{$questionnaire.questions.length}</strong> questions
|
||||||
|
</p>
|
||||||
|
|
|
||||||
|
<p class="dark:text-white mx-2">
|
||||||
|
<strong>{getTotalPossibleGrade()}</strong> points.
|
||||||
|
</p>
|
||||||
|
|
|
||||||
|
<p class="dark:text-white mx-2">All required</p>
|
||||||
|
{#if $questionnaire.due_by}
|
||||||
|
|
|
||||||
|
<p class="dark:text-white mx-2">
|
||||||
|
<strong>Due by:</strong>
|
||||||
|
{new Date($questionnaire.due_by).toLocaleString()}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<article class="preview prose prose-sm sm:prose p-2">
|
||||||
|
{@html marked($questionnaire.description || 'No description')}
|
||||||
|
</article>
|
||||||
|
{/if}
|
||||||
|
</QuestionContainer>
|
||||||
|
</div>
|
||||||
@@ -20,6 +20,8 @@
|
|||||||
import { submitExercise } from '$lib/utils/services/courses';
|
import { submitExercise } from '$lib/utils/services/courses';
|
||||||
import { fetchSubmission } from '$lib/utils/services/submissions';
|
import { fetchSubmission } from '$lib/utils/services/submissions';
|
||||||
import { profile } from '$lib/utils/store/user';
|
import { profile } from '$lib/utils/store/user';
|
||||||
|
import MODES from '$lib/utils/constants/mode.js';
|
||||||
|
import { exerciseMode } from './store';
|
||||||
|
|
||||||
export let preview;
|
export let preview;
|
||||||
|
|
||||||
@@ -163,10 +165,12 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if preview}
|
{#if preview}
|
||||||
<Preview
|
{#if $exerciseMode.editMode}
|
||||||
questions={filterOutDeleted($questionnaire.questions)}
|
<Preview
|
||||||
questionnaireMetaData={$questionnaireMetaData}
|
questions={filterOutDeleted($questionnaire.questions)}
|
||||||
/>
|
questionnaireMetaData={$questionnaireMetaData}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
{:else if !$questionnaire.questions.length}
|
{:else if !$questionnaire.questions.length}
|
||||||
<Box>
|
<Box>
|
||||||
<img src="/images/empty-exercise-icon.svg" alt="Exercise svg" class="my-2.5" />
|
<img src="/images/empty-exercise-icon.svg" alt="Exercise svg" class="my-2.5" />
|
||||||
@@ -178,34 +182,41 @@
|
|||||||
</p>
|
</p>
|
||||||
</Box>
|
</Box>
|
||||||
{:else if $questionnaireMetaData.currentQuestionIndex === 0}
|
{:else if $questionnaireMetaData.currentQuestionIndex === 0}
|
||||||
<div>
|
<RoleBasedSecurity allowedRoles={[3]}>
|
||||||
<h2 class="my-1">{$questionnaire.title}</h2>
|
<div>
|
||||||
|
<h2 class="my-1">{$questionnaire.title}</h2>
|
||||||
|
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<p class="dark:text-white mx-2">
|
<p class="dark:text-white mx-2">
|
||||||
<strong>{$questionnaire.questions.length}</strong> questions
|
<strong>{$questionnaire.questions.length}</strong> questions
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
<p class="dark:text-white mx-2">
|
|
||||||
<strong>{getTotalPossibleGrade()}</strong> points.
|
|
||||||
</p>
|
|
||||||
|
|
|
||||||
<p class="dark:text-white mx-2">All required</p>
|
|
||||||
{#if $questionnaire.due_by}
|
|
||||||
|
|
|
|
||||||
<p class="dark:text-white mx-2">
|
<p class="dark:text-white mx-2">
|
||||||
<strong>Due by:</strong>
|
<strong>{getTotalPossibleGrade()}</strong> points.
|
||||||
{new Date($questionnaire.due_by).toLocaleString()}
|
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
|
|
||||||
|
<p class="dark:text-white mx-2">All required</p>
|
||||||
|
{#if $questionnaire.due_by}
|
||||||
|
|
|
||||||
|
<p class="dark:text-white mx-2">
|
||||||
|
<strong>Due by:</strong>
|
||||||
|
{new Date($questionnaire.due_by).toLocaleString()}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<article class="preview prose prose-sm sm:prose p-2">
|
||||||
|
{@html marked($questionnaire.description || 'No desription')}
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<PrimaryButton
|
||||||
|
onClick={handleStart}
|
||||||
|
label="Start"
|
||||||
|
className="my-5 float-right"
|
||||||
|
type="button"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</RoleBasedSecurity>
|
||||||
<article class="preview prose prose-sm sm:prose p-2">
|
|
||||||
{@html marked($questionnaire.description || 'No desription')}
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<PrimaryButton onClick={handleStart} label="Start" className="my-5 float-right" type="button" />
|
|
||||||
</div>
|
|
||||||
{:else if $questionnaireMetaData.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 w-full">
|
<div class="flex flex-col justify-between w-full">
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
import AddAltIcon from 'carbon-icons-svelte/lib/AddAlt.svelte';
|
import AddAltIcon from 'carbon-icons-svelte/lib/AddAlt.svelte';
|
||||||
import TableIcon from 'carbon-icons-svelte/lib/Table.svelte';
|
import TableIcon from 'carbon-icons-svelte/lib/Table.svelte';
|
||||||
import SettingsIcon from 'carbon-icons-svelte/lib/Settings.svelte';
|
|
||||||
import ViewIcon from 'carbon-icons-svelte/lib/View.svelte';
|
import ViewIcon from 'carbon-icons-svelte/lib/View.svelte';
|
||||||
import ViewFilledIcon from 'carbon-icons-svelte/lib/ViewFilled.svelte';
|
import ViewFilledIcon from 'carbon-icons-svelte/lib/ViewFilled.svelte';
|
||||||
import {
|
import {
|
||||||
@@ -20,121 +19,128 @@
|
|||||||
import EditMode from './EditMode.svelte';
|
import EditMode from './EditMode.svelte';
|
||||||
import MODES from '$lib/utils/constants/mode.js';
|
import MODES from '$lib/utils/constants/mode.js';
|
||||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||||
import { upsertExercise } from '$lib/utils/services/courses';
|
import { deleteExercise, upsertExercise } from '$lib/utils/services/courses';
|
||||||
|
import UpdateDescription from './UpdateDescription.svelte';
|
||||||
|
import { TrashCan } from 'carbon-icons-svelte';
|
||||||
|
import { exerciseMode } from './store';
|
||||||
|
import { snackbarStore } from '$lib/components/Snackbar/store';
|
||||||
|
import { SNACKBAR_SEVERITY } from '$lib/components/Snackbar/constants';
|
||||||
|
|
||||||
export let exerciseId;
|
export let exerciseId;
|
||||||
export let refetchExercise = () => {};
|
export let refetchExercise = () => {};
|
||||||
export let goBack = () => {};
|
export let goBack = () => {};
|
||||||
|
|
||||||
let mode = MODES.view;
|
|
||||||
let preview = false;
|
let preview = false;
|
||||||
let editDescription = false;
|
let editDescription = false;
|
||||||
|
let isSaving = false;
|
||||||
|
|
||||||
async function handleMode() {
|
async function handleSave() {
|
||||||
if (mode === MODES.edit) {
|
if ($exerciseMode.editMode) {
|
||||||
const errors = validateQuestionnaire($questionnaire.questions);
|
const errors = validateQuestionnaire($questionnaire.questions);
|
||||||
if (Object.values(errors).length > 0) {
|
if (Object.values(errors).length > 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
isSaving = true;
|
||||||
mode = MODES.view;
|
// $exerciseMode.mode = MODES.view;
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
|
try {
|
||||||
|
const updatedQuestions = await upsertExercise($questionnaire, exerciseId);
|
||||||
|
|
||||||
const updatedQuestions = await upsertExercise($questionnaire, exerciseId);
|
questionnaire.update((q) => ({
|
||||||
|
...q,
|
||||||
questionnaire.update((q) => ({
|
is_title_dirty: false,
|
||||||
...q,
|
is_description_dirty: false,
|
||||||
is_title_dirty: false,
|
questions: updatedQuestions
|
||||||
is_description_dirty: false,
|
}));
|
||||||
questions: updatedQuestions
|
$snackbarStore.open = true;
|
||||||
}));
|
$snackbarStore.message = 'Saved Successfully';
|
||||||
} else {
|
$snackbarStore.severity = SNACKBAR_SEVERITY.SUCCESS;
|
||||||
mode = MODES.edit;
|
} catch (error) {
|
||||||
preview = false;
|
$snackbarStore.open = true;
|
||||||
|
$snackbarStore.message = `An Error Occurred`;
|
||||||
|
$snackbarStore.severity = SNACKBAR_SEVERITY.ERROR;
|
||||||
|
}
|
||||||
|
isSaving = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDeleteExercise() {}
|
|
||||||
|
|
||||||
function onCancel() {
|
|
||||||
mode = MODES.view;
|
|
||||||
refetchExercise();
|
|
||||||
}
|
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
reset();
|
reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$: $questionnaire.questions.length < 1 && handleAddQuestion();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<PageBody padding="px-4">
|
<PageBody padding="px-4" headerClassName="bg-gray-100 rounded-md mb-3">
|
||||||
<svelte:fragment slot="header">
|
<svelte:fragment slot="header">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center py-2 w-">
|
||||||
<RoleBasedSecurity allowedRoles={[1, 2]}>
|
<RoleBasedSecurity allowedRoles={[1, 2]}>
|
||||||
<PrimaryButton
|
{#if $exerciseMode.editMode}
|
||||||
className="mr-2"
|
|
||||||
variant={VARIANTS.CONTAINED}
|
|
||||||
label={mode === MODES.edit ? 'Save' : 'Edit'}
|
|
||||||
onClick={handleMode}
|
|
||||||
/>
|
|
||||||
{#if mode === MODES.edit}
|
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
variant={VARIANTS.OUTLINED}
|
variant={VARIANTS.CONTAINED}
|
||||||
label={'Cancel'}
|
label={'Save'}
|
||||||
onClick={onCancel}
|
onClick={handleSave}
|
||||||
|
isLoading={isSaving}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $exerciseMode.editMode}
|
||||||
|
<IconButton
|
||||||
|
onClick={() => (preview = !preview)}
|
||||||
|
toolTipProps={{ title: 'Toggle preview', direction: 'right' }}
|
||||||
|
selected={preview}
|
||||||
|
contained={true}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{#if preview}
|
||||||
|
<ViewFilledIcon size={24} class="carbon-icon dark:text-white" />
|
||||||
|
{:else}
|
||||||
|
<ViewIcon size={24} class="carbon-icon dark:text-white" />
|
||||||
|
{/if}
|
||||||
|
</IconButton>
|
||||||
|
{/if}
|
||||||
</RoleBasedSecurity>
|
</RoleBasedSecurity>
|
||||||
|
|
||||||
{#if mode !== MODES.edit}
|
|
||||||
<IconButton
|
|
||||||
onClick={() => (preview = !preview)}
|
|
||||||
toolTipProps={{ title: 'Toggle preview', direction: 'right' }}
|
|
||||||
selected={preview}
|
|
||||||
contained={true}
|
|
||||||
>
|
|
||||||
{#if preview}
|
|
||||||
<ViewFilledIcon size={32} class="carbon-icon dark:text-white" />
|
|
||||||
{:else}
|
|
||||||
<ViewIcon size={32} class="carbon-icon dark:text-white" />
|
|
||||||
{/if}
|
|
||||||
</IconButton>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if mode === MODES.edit}
|
{#if $exerciseMode.editMode}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={handleAddQuestion}
|
onClick={() => handleAddQuestion()}
|
||||||
toolTipProps={{ title: 'Add question', direction: 'bottom' }}
|
toolTipProps={{ title: 'Add question', direction: 'bottom' }}
|
||||||
|
size="small"
|
||||||
>
|
>
|
||||||
<AddAltIcon size={32} class="carbon-icon dark:text-white" />
|
<AddAltIcon size={24} class="carbon-icon dark:text-white" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => ($questionnaireOrder.open = true)}
|
onClick={() => ($questionnaireOrder.open = true)}
|
||||||
toolTipProps={{ title: 'Order questionnaire', direction: 'bottom' }}
|
toolTipProps={{ title: 'Order questionnaire', direction: 'bottom' }}
|
||||||
|
size="small"
|
||||||
>
|
>
|
||||||
<TableIcon size={32} class="carbon-icon dark:text-white" />
|
<TableIcon size={24} class="carbon-icon dark:text-white" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => (editDescription = true)}
|
onClick={() => (editDescription = true)}
|
||||||
toolTipProps={{ title: 'Edit description', direction: 'bottom' }}
|
toolTipProps={{ title: 'Delete Exercise', direction: 'bottom' }}
|
||||||
|
size="small"
|
||||||
>
|
>
|
||||||
<SettingsIcon size={32} class="carbon-icon dark:text-white" />
|
<TrashCan size={24} class="carbon-icon dark:text-white" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<!-- <IconButton
|
<!-- <IconButton
|
||||||
onClick={handleDeleteExercise}
|
onClick={handleDeleteExercise}
|
||||||
toolTipProps={{ title: 'Delete questionnaire', direction: 'bottom' }}
|
toolTipProps={{ title: 'Delete questionnaire', direction: 'bottom' }}
|
||||||
>
|
>
|
||||||
<Delete32 class="carbon-icon dark:text-white" />
|
<Delete32 class="carbon-icon dark:text-white" />
|
||||||
</IconButton> -->
|
</IconButton> -->
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
|
||||||
{#if mode === MODES.edit && !preview}
|
{#if $exerciseMode.editMode}
|
||||||
|
<UpdateDescription {preview} {exerciseId} />
|
||||||
|
{/if}
|
||||||
|
{#if $exerciseMode.editMode && !preview}
|
||||||
<EditMode bind:editDescription {exerciseId} {goBack} />
|
<EditMode bind:editDescription {exerciseId} {goBack} />
|
||||||
{:else}
|
{:else}
|
||||||
<ViewMode {preview} {exerciseId} />
|
<ViewMode {preview} {exerciseId} />
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { writable } from 'svelte/store';
|
||||||
|
import MODES from '$lib/utils/constants/mode.js';
|
||||||
|
import { ROLE } from '$lib/utils/constants/roles';
|
||||||
|
|
||||||
|
export const exerciseMode = writable({
|
||||||
|
editMode: true
|
||||||
|
});
|
||||||
@@ -14,7 +14,10 @@
|
|||||||
import { createExercise } from '$lib/utils/services/courses';
|
import { createExercise } from '$lib/utils/services/courses';
|
||||||
import { QUESTION_TYPES } from '$lib/components/Question/constants';
|
import { QUESTION_TYPES } from '$lib/components/Question/constants';
|
||||||
import { lesson } from '../store/lessons';
|
import { lesson } from '../store/lessons';
|
||||||
import { questionnaire, isQuestionnaireFetching } from '../store/exercise';
|
import { questionnaire, isQuestionnaireFetching, handleAddQuestion } from '../store/exercise';
|
||||||
|
import MODES from '$lib/utils/constants/mode.js';
|
||||||
|
import { ROLE } from '$lib/utils/constants/roles';
|
||||||
|
import { exerciseMode } from '../Exercise/store';
|
||||||
|
|
||||||
export let path;
|
export let path;
|
||||||
export let exerciseId;
|
export let exerciseId;
|
||||||
@@ -46,7 +49,7 @@
|
|||||||
function handleCancelAddExercise() {
|
function handleCancelAddExercise() {
|
||||||
open = false;
|
open = false;
|
||||||
newExercise = {
|
newExercise = {
|
||||||
id: $lesson.exercises.length + 1,
|
id: new Date().getTime(),
|
||||||
title: '',
|
title: '',
|
||||||
description: ''
|
description: ''
|
||||||
};
|
};
|
||||||
@@ -122,6 +125,10 @@
|
|||||||
getExercises();
|
getExercises();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const gotoEdit = () => {
|
||||||
|
$exerciseMode.editMode = true;
|
||||||
|
};
|
||||||
|
|
||||||
$: getExercise(exerciseId);
|
$: getExercise(exerciseId);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -145,15 +152,16 @@
|
|||||||
<PageBody>
|
<PageBody>
|
||||||
<slot:fragment slot="header">
|
<slot:fragment slot="header">
|
||||||
<RoleBasedSecurity allowedRoles={[1, 2]}>
|
<RoleBasedSecurity allowedRoles={[1, 2]}>
|
||||||
<PrimaryButton className="mr-2" label="Add" onClick={() => (open = !open)} />
|
<PrimaryButton className="mr-2 mb-2" label="Add" onClick={() => (open = !open)} />
|
||||||
</RoleBasedSecurity>
|
</RoleBasedSecurity>
|
||||||
</slot:fragment>
|
</slot:fragment>
|
||||||
|
|
||||||
<div class="flex flex-wrap">
|
<div class="flex flex-wrap">
|
||||||
{#each $lesson.exercises as exercise}
|
{#each $lesson.exercises as exercise}
|
||||||
<a
|
<a
|
||||||
class="w-52 bg-gray-100 dark:bg-gray-700 px-4 py-7 mr-4 mb-4"
|
class="w-52 bg-gray-100 dark:bg-gray-700 px-4 py-7 mr-4 mb-4 rounded-lg"
|
||||||
href="{path}/{exercise.id}"
|
href="{path}/{exercise.id}"
|
||||||
|
on:click={gotoEdit}
|
||||||
>
|
>
|
||||||
<h3 class="dark:text-white text-xl">{exercise.title}</h3>
|
<h3 class="dark:text-white text-xl">{exercise.title}</h3>
|
||||||
<p class="dark:text-white mt-4 text-sm">Created Jul 3, 2021</p>
|
<p class="dark:text-white mt-4 text-sm">Created Jul 3, 2021</p>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<CopyCourseModal />
|
<CopyCourseModal />
|
||||||
|
|
||||||
<div class="w-full cards-container my-4 m-auto">
|
<div class={`w-full ${$courseMetaDeta.isLoading || $courses ? 'cards-container' : ''} my-4 m-auto`}>
|
||||||
{#if $courseMetaDeta.isLoading}
|
{#if $courseMetaDeta.isLoading}
|
||||||
<CardLoader />
|
<CardLoader />
|
||||||
<CardLoader />
|
<CardLoader />
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<select
|
<select
|
||||||
bind:value
|
bind:value
|
||||||
class="form-select block w-auto mt-1 {selectClassName}"
|
class="form-select block w-auto mt-1 dark:text-gray-800 {selectClassName}"
|
||||||
required={isRequired}
|
required={isRequired}
|
||||||
on:blur={() => {}}
|
on:blur={() => {}}
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
export let label = '';
|
export let label = '';
|
||||||
|
export let disabled = false;
|
||||||
export let placeholder = 'Write your answer...';
|
export let placeholder = 'Write your answer...';
|
||||||
export let value = '';
|
export let value = '';
|
||||||
export let rows = '3';
|
export let rows = '3';
|
||||||
@@ -34,11 +35,12 @@
|
|||||||
</pre> -->
|
</pre> -->
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
|
{disabled}
|
||||||
{rows}
|
{rows}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
bind:value
|
bind:value
|
||||||
required={isRequired}
|
required={isRequired}
|
||||||
class="form-textarea dark:bg-gray-500 border-l-0 border-r-0 border-t-0 border-b-2 border-gray-200 focus:border-l-0 focus:border-r-0 rounded-t-md focus:border-t-0 focus:border-b-2 focus:border-primary-600 mt-1 block w-full
|
class="form-textarea dark:bg-gray-500 dark:text-gray-800 border-l-0 border-r-0 border-t-0 border-b-2 border-gray-200 focus:border-l-0 focus:border-r-0 rounded-t-md focus:border-t-0 focus:border-b-2 focus:border-primary-600 mt-1 block w-full
|
||||||
{bgColor} {!!errorMessage ? 'border-red-500' : 'border-gray-300'}"
|
{bgColor} {!!errorMessage ? 'border-red-500' : 'border-gray-300'}"
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -4,11 +4,14 @@
|
|||||||
export let width = 'max-w-4xl w-full lg:w-11/12';
|
export let width = 'max-w-4xl w-full lg:w-11/12';
|
||||||
export let padding = 'pb-5 px-4';
|
export let padding = 'pb-5 px-4';
|
||||||
export let className = '';
|
export let className = '';
|
||||||
|
export let headerClassName = '';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root mt-4 mx-auto {width} {className} relative {padding}">
|
<div class="root mt-4 mx-auto {width} {className} relative {padding}">
|
||||||
{#if $$slots.header}
|
{#if $$slots.header}
|
||||||
<div class="head dark:bg-gray-800 flex items-center justify-between sticky right-0 w-full px-3">
|
<div
|
||||||
|
class="head dark:bg-gray-800 flex items-center justify-between sticky right-0 w-full px-3 {headerClassName}"
|
||||||
|
>
|
||||||
<slot name="header" />
|
<slot name="header" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -19,6 +22,7 @@
|
|||||||
<style>
|
<style>
|
||||||
.root {
|
.root {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
height: calc(100vh - 127px);
|
height: calc(100vh - 127px);
|
||||||
}
|
}
|
||||||
.head {
|
.head {
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
<script>
|
<script>
|
||||||
// import { onMount } from "svelte";
|
|
||||||
import TextField from '../Form/TextField.svelte';
|
import TextField from '../Form/TextField.svelte';
|
||||||
import CloseButton from '../Buttons/Close/index.svelte';
|
import TrashCanIcon from 'carbon-icons-svelte/lib/TrashCan.svelte';
|
||||||
|
import IconButton from '$lib/components/IconButton/index.svelte';
|
||||||
|
|
||||||
export let isTitle = false;
|
export let isTitle = false;
|
||||||
export let onClose = () => {};
|
export let onClose = () => {};
|
||||||
export let scrollToQuestion = false;
|
export let scrollToQuestion = false;
|
||||||
export let points = undefined;
|
export let points = undefined;
|
||||||
export let hasError = false;
|
export let hasError = false;
|
||||||
export let onPointsChange = () => {};
|
export let onPointsChange = () => {};
|
||||||
let ref;
|
|
||||||
|
|
||||||
// onMount(() => {
|
let ref;
|
||||||
// if (scrollToQuestion) {
|
|
||||||
// ref.scrollIntoView({ block: "end", behavior: "smooth" });
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (ref && scrollToQuestion) {
|
if (ref && scrollToQuestion) {
|
||||||
@@ -36,18 +32,14 @@
|
|||||||
{#if isTitle}
|
{#if isTitle}
|
||||||
<div class="title absolute bg-primary-700" />
|
<div class="title absolute bg-primary-700" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if onClose && !isTitle}
|
|
||||||
<div class="close-button hidden absolute -top-6 -right-6">
|
|
||||||
<CloseButton onClick={onClose} contained={true} />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<div class="px-4 {isTitle ? 'pt-4' : 'pt-2'} pb-3">
|
<div class="px-4 {isTitle ? 'pt-4' : 'pt-2'} pb-3">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if typeof points !== 'undefined'}
|
{#if typeof points !== 'undefined'}
|
||||||
<div class="border-gray border-t-2 border-r-0 border-b-0 border-l-0 p-2">
|
<div
|
||||||
|
class="flex justify-between items-center border-gray border-t-2 border-r-0 border-b-0 border-l-0 p-2"
|
||||||
|
>
|
||||||
<div class="flex items-center w-40">
|
<div class="flex items-center w-40">
|
||||||
<p class="dark:text-white text-sm mr-2">Points:</p>
|
<p class="dark:text-white text-sm mr-2">Points:</p>
|
||||||
<TextField
|
<TextField
|
||||||
@@ -57,14 +49,17 @@
|
|||||||
onChange={onPointsChange}
|
onChange={onPointsChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if onClose && !isTitle}
|
||||||
|
<IconButton onClick={onClose}>
|
||||||
|
<TrashCanIcon size={24} />
|
||||||
|
</IconButton>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.root:hover .close-button {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.title {
|
.title {
|
||||||
color: rgba(255, 255, 255, 1);
|
color: rgba(255, 255, 255, 1);
|
||||||
border-top-left-radius: 8px;
|
border-top-left-radius: 8px;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
export let avatar = null;
|
export let avatar = null;
|
||||||
export let src = null;
|
export let src = null;
|
||||||
export let widthHeight = null;
|
export let widthHeight = '';
|
||||||
export let shape = 'rounded-full';
|
export let shape = 'rounded-full';
|
||||||
export let errorMessage = null;
|
export let errorMessage = null;
|
||||||
|
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ export async function upsertExercise(questionnaire: any, exerciseId: Exercise['i
|
|||||||
let questionSupabaseRes;
|
let questionSupabaseRes;
|
||||||
|
|
||||||
if (is_dirty || isNew(id)) {
|
if (is_dirty || isNew(id)) {
|
||||||
const res = await supabase.from('question').upsert(newQuestion);
|
const res = await supabase.from('question').upsert(newQuestion).select();
|
||||||
|
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
console.error(`Upsert question`, res.error);
|
console.error(`Upsert question`, res.error);
|
||||||
@@ -293,7 +293,7 @@ export async function upsertExercise(questionnaire: any, exerciseId: Exercise['i
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (is_dirty || isNew(option.id)) {
|
if (is_dirty || isNew(option.id)) {
|
||||||
const { data } = await supabase.from('option').upsert(newOption);
|
const { data } = await supabase.from('option').upsert(newOption).select();
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
newQuestion.options.push(data[0]);
|
newQuestion.options.push(data[0]);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
import PageBody from '$lib/components/PageBody/index.svelte';
|
import PageBody from '$lib/components/PageBody/index.svelte';
|
||||||
import TextChip from '$lib/components/Chip/Text.svelte';
|
import TextChip from '$lib/components/Chip/Text.svelte';
|
||||||
import Avatar from '$lib/components/Apps/components/Poll/components/Avatar.svelte';
|
import Avatar from '$lib/components/Apps/components/Poll/components/Avatar.svelte';
|
||||||
|
import { exerciseMode } from '$lib/components/Course/components/Lesson/Exercise/store';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
const { courseId } = data;
|
const { courseId } = data;
|
||||||
@@ -108,6 +109,7 @@
|
|||||||
isStudent = user.role_id === 3;
|
isStudent = user.role_id === 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$: $exerciseMode.editMode = !isStudent;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CourseContainer bind:isStudent>
|
<CourseContainer bind:isStudent>
|
||||||
|
|||||||
Reference in New Issue
Block a user