CLASSROOMIO-T-23: Fix reodering of questions
This commit is contained in:
@@ -1,6 +1,4 @@
|
|||||||
<script>
|
<script>
|
||||||
import { dndzone } from 'svelte-dnd-action';
|
|
||||||
import { fly } from 'svelte/transition';
|
|
||||||
import {
|
import {
|
||||||
questionnaire,
|
questionnaire,
|
||||||
deleteConfirmation,
|
deleteConfirmation,
|
||||||
@@ -32,6 +30,7 @@
|
|||||||
import Select from '../../../../Form/Select.svelte';
|
import Select from '../../../../Form/Select.svelte';
|
||||||
import QuestionContainer from '../../../../QuestionContainer/index.svelte';
|
import QuestionContainer from '../../../../QuestionContainer/index.svelte';
|
||||||
import DeleteConfirmationModal from './DeleteConfirmation.svelte';
|
import DeleteConfirmationModal from './DeleteConfirmation.svelte';
|
||||||
|
import OrderModal from './OrderModal.svelte';
|
||||||
import {
|
import {
|
||||||
QUESTION_TYPE,
|
QUESTION_TYPE,
|
||||||
QUESTION_TYPES,
|
QUESTION_TYPES,
|
||||||
@@ -50,33 +49,6 @@
|
|||||||
let errors = {};
|
let errors = {};
|
||||||
let questionIdToDelete = null;
|
let questionIdToDelete = null;
|
||||||
let questions = [];
|
let questions = [];
|
||||||
let isDragging = false;
|
|
||||||
|
|
||||||
function handleDndConsider(e) {
|
|
||||||
questions = e.detail.items;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDndFinalize(e) {
|
|
||||||
const prevQuestionsByOrder = questions.reduce(
|
|
||||||
(acc, l, index) => ({ ...acc, [l.id]: l.order }),
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
questions = e.detail.items;
|
|
||||||
|
|
||||||
// // Only update the lesson order that changed
|
|
||||||
e.detail.items.forEach((item, index) => {
|
|
||||||
const order = index + 1;
|
|
||||||
|
|
||||||
if (order !== prevQuestionsByOrder[item.id]) {
|
|
||||||
$questionnaire.questions.some((q) => {
|
|
||||||
if (q.id === item.id) {
|
|
||||||
q.is_dirty = true;
|
|
||||||
q.order = order;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldScrollToLast(questionId, questions) {
|
function shouldScrollToLast(questionId, questions) {
|
||||||
const currentQuestionsLength = questions.length;
|
const currentQuestionsLength = questions.length;
|
||||||
@@ -125,6 +97,8 @@
|
|||||||
onDelete={onFinalDeleteClicked}
|
onDelete={onFinalDeleteClicked}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<OrderModal />
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
onClose={() => (editDescription = false)}
|
onClose={() => (editDescription = false)}
|
||||||
bind:open={editDescription}
|
bind:open={editDescription}
|
||||||
@@ -165,19 +139,7 @@
|
|||||||
<ErrorMessage message="You have some errors" />
|
<ErrorMessage message="You have some errors" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div
|
<div class="questions pt-6 px-6">
|
||||||
use:dndzone={{
|
|
||||||
items: questions,
|
|
||||||
flipDurationMs: 300,
|
|
||||||
dropTargetStyle: {
|
|
||||||
border: '2px #1d4ed8 solid',
|
|
||||||
'border-style': 'dashed',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
on:consider={handleDndConsider}
|
|
||||||
on:finalize={handleDndFinalize}
|
|
||||||
class="questions pt-6 px-6"
|
|
||||||
>
|
|
||||||
{#each questions as question (question.id)}
|
{#each questions as question (question.id)}
|
||||||
<!-- {#key question.id} -->
|
<!-- {#key question.id} -->
|
||||||
<QuestionContainer
|
<QuestionContainer
|
||||||
@@ -332,11 +294,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.questions {
|
|
||||||
height: 80vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(#dnd-action-dragged-el div) {
|
:global(#dnd-action-dragged-el div) {
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,9 @@
|
|||||||
>
|
>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<Preview
|
<Preview
|
||||||
questions={data.questions || []}
|
questions={Array.isArray(data.questions)
|
||||||
|
? data.questions.sort((a, b) => a.order - b.order)
|
||||||
|
: []}
|
||||||
questionnaireMetaData={{
|
questionnaireMetaData={{
|
||||||
answers: data.answers || {},
|
answers: data.answers || {},
|
||||||
isFinished: true,
|
isFinished: true,
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { dndzone } from 'svelte-dnd-action';
|
||||||
|
import CheckboxCheckedFilled16 from 'carbon-icons-svelte/lib/CheckboxCheckedFilled16';
|
||||||
|
import RadioButtonChecked16 from 'carbon-icons-svelte/lib/RadioButtonChecked16';
|
||||||
|
import TextAlignJustify16 from 'carbon-icons-svelte/lib/TextAlignJustify16';
|
||||||
|
|
||||||
|
import { flip } from 'svelte/animate';
|
||||||
|
import Modal from '../../../../Modal/index.svelte';
|
||||||
|
|
||||||
|
import { questionnaireOrder, questionnaire } from '../store/exercise';
|
||||||
|
import { filterOutDeleted } from './functions';
|
||||||
|
|
||||||
|
const flipDurationMs = 300;
|
||||||
|
|
||||||
|
interface Question {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
type: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
let items: Array<Question> = [];
|
||||||
|
|
||||||
|
function handleDndConsider(e) {
|
||||||
|
items = e.detail.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDndFinalize(e) {
|
||||||
|
items = e.detail.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
items.forEach((item, index) => {
|
||||||
|
const order = index + 1;
|
||||||
|
$questionnaire.questions.some((q) => {
|
||||||
|
if (q.id === item.id) {
|
||||||
|
q.is_dirty = true;
|
||||||
|
q.order = order;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$questionnaire.questions = $questionnaire.questions.sort(
|
||||||
|
(a, b) => a.order - b.order
|
||||||
|
);
|
||||||
|
$questionnaireOrder.open = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$: items = filterOutDeleted($questionnaire.questions).map((q) => ({
|
||||||
|
id: q.id,
|
||||||
|
name: q.title,
|
||||||
|
type: q.question_type.id,
|
||||||
|
}));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
onClose={handleClose}
|
||||||
|
bind:open={$questionnaireOrder.open}
|
||||||
|
width="w-96"
|
||||||
|
modalHeading="Order questions"
|
||||||
|
>
|
||||||
|
<section
|
||||||
|
use:dndzone={{
|
||||||
|
items,
|
||||||
|
flipDurationMs,
|
||||||
|
dropTargetStyle: {
|
||||||
|
border: '2px #1d4ed8 solid',
|
||||||
|
'border-style': 'dashed',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
on:consider={handleDndConsider}
|
||||||
|
on:finalize={handleDndFinalize}
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
|
{#each items as item (item.id)}
|
||||||
|
<div
|
||||||
|
animate:flip={{ duration: flipDurationMs }}
|
||||||
|
class="flex items-center rounded-md p-4 border border-blue-600"
|
||||||
|
>
|
||||||
|
{#if item.type === 1}
|
||||||
|
<RadioButtonChecked16 class="carbon-icon active" />
|
||||||
|
{:else if item.type === 2}
|
||||||
|
<CheckboxCheckedFilled16 class="carbon-icon active" />
|
||||||
|
{:else}
|
||||||
|
<TextAlignJustify16 class="carbon-icon active" />
|
||||||
|
{/if}
|
||||||
|
{` ${item.name}`}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</section>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
section {
|
||||||
|
padding: 0.3em;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
let currentQuestion = {};
|
let currentQuestion = {};
|
||||||
let renderProps = {};
|
let renderProps = {};
|
||||||
let submission;
|
let submission;
|
||||||
|
let hasSubmission = null;
|
||||||
|
|
||||||
function handleStart() {
|
function handleStart() {
|
||||||
$questionnaireMetaData.currentQuestionIndex += 1;
|
$questionnaireMetaData.currentQuestionIndex += 1;
|
||||||
@@ -97,21 +98,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function checkForSubmission(people, profileId, courseId) {
|
async function checkForSubmission(people, profileId, courseId) {
|
||||||
console.log('checkForSubmission');
|
|
||||||
|
|
||||||
if (!Array.isArray(people) || !profileId || !courseId || !!submission) {
|
if (!Array.isArray(people) || !profileId || !courseId || !!submission) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasSubmission) return;
|
||||||
|
|
||||||
|
console.log('checkForSubmission');
|
||||||
|
|
||||||
const args = {
|
const args = {
|
||||||
exerciseId,
|
exerciseId,
|
||||||
courseId,
|
courseId,
|
||||||
submittedBy: getGroupMemberId(people, profileId),
|
submittedBy: getGroupMemberId(people, profileId),
|
||||||
};
|
};
|
||||||
const { data } = await fetchSubmission(args);
|
const { data } = await fetchSubmission(args);
|
||||||
|
hasSubmission = true;
|
||||||
|
|
||||||
if (Array.isArray(data) && data.length) {
|
if (Array.isArray(data) && data.length) {
|
||||||
console.log(`submissions`, data);
|
|
||||||
submission = data[0];
|
submission = data[0];
|
||||||
|
|
||||||
$questionnaireMetaData.answers = formatAnswers({
|
$questionnaireMetaData.answers = formatAnswers({
|
||||||
@@ -248,7 +251,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Preview
|
<Preview
|
||||||
questions={$questionnaire.questions}
|
questions={$questionnaire.questions.sort((a, b) => a.order - b.order)}
|
||||||
questionnaireMetaData={$questionnaireMetaData}
|
questionnaireMetaData={$questionnaireMetaData}
|
||||||
grades={$questionnaireMetaData.grades}
|
grades={$questionnaireMetaData.grades}
|
||||||
disableGrading={true}
|
disableGrading={true}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
import AddAlt24 from 'carbon-icons-svelte/lib/AddAlt24';
|
import AddAlt32 from 'carbon-icons-svelte/lib/AddAlt32';
|
||||||
import Delete24 from 'carbon-icons-svelte/lib/Delete24';
|
// import Delete32 from 'carbon-icons-svelte/lib/Delete32';
|
||||||
import Settings24 from 'carbon-icons-svelte/lib/Settings24';
|
import Table32 from 'carbon-icons-svelte/lib/Table32';
|
||||||
|
import Settings32 from 'carbon-icons-svelte/lib/Settings32';
|
||||||
import View32 from 'carbon-icons-svelte/lib/View32';
|
import View32 from 'carbon-icons-svelte/lib/View32';
|
||||||
import ViewFilled32 from 'carbon-icons-svelte/lib/ViewFilled32';
|
import ViewFilled32 from 'carbon-icons-svelte/lib/ViewFilled32';
|
||||||
import {
|
import {
|
||||||
@@ -10,6 +11,7 @@
|
|||||||
questionnaire,
|
questionnaire,
|
||||||
validateQuestionnaire,
|
validateQuestionnaire,
|
||||||
reset,
|
reset,
|
||||||
|
questionnaireOrder,
|
||||||
} from '../store/exercise';
|
} from '../store/exercise';
|
||||||
import PrimaryButton from '../../../../PrimaryButton/index.svelte';
|
import PrimaryButton from '../../../../PrimaryButton/index.svelte';
|
||||||
import PageBody from '../../../../PageBody/index.svelte';
|
import PageBody from '../../../../PageBody/index.svelte';
|
||||||
@@ -103,25 +105,31 @@
|
|||||||
|
|
||||||
{#if mode === MODES.edit}
|
{#if mode === MODES.edit}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
|
<IconButton
|
||||||
|
onClick={handleAddQuestion}
|
||||||
|
toolTipProps={{ title: 'Add question', direction: 'bottom' }}
|
||||||
|
>
|
||||||
|
<AddAlt32 class="carbon-icon" />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => ($questionnaireOrder.open = true)}
|
||||||
|
toolTipProps={{ title: 'Order questionnaire', direction: 'bottom' }}
|
||||||
|
>
|
||||||
|
<Table32 class="carbon-icon" />
|
||||||
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => (editDescription = true)}
|
onClick={() => (editDescription = true)}
|
||||||
toolTipProps={{ title: 'Edit description', direction: 'bottom' }}
|
toolTipProps={{ title: 'Edit description', direction: 'bottom' }}
|
||||||
>
|
>
|
||||||
<Settings24 class="carbon-icon" />
|
<Settings32 class="carbon-icon" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<!-- <IconButton
|
<!-- <IconButton
|
||||||
onClick={handleDeleteExercise}
|
onClick={handleDeleteExercise}
|
||||||
toolTipProps={{ title: 'Delete questionnaire', direction: 'bottom' }}
|
toolTipProps={{ title: 'Delete questionnaire', direction: 'bottom' }}
|
||||||
>
|
>
|
||||||
<Delete24 class="carbon-icon" />
|
<Delete32 class="carbon-icon" />
|
||||||
</IconButton> -->
|
</IconButton> -->
|
||||||
|
|
||||||
<IconButton
|
|
||||||
onClick={handleAddQuestion}
|
|
||||||
toolTipProps={{ title: 'Add question', direction: 'bottom' }}
|
|
||||||
>
|
|
||||||
<AddAlt24 class="carbon-icon" />
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { isUUID } from '../../../../../utils/functions/isUUID';
|
|||||||
|
|
||||||
export const isQuestionnaireFetching = writable(false);
|
export const isQuestionnaireFetching = writable(false);
|
||||||
export const deleteConfirmation = writable({ open: false });
|
export const deleteConfirmation = writable({ open: false });
|
||||||
|
export const questionnaireOrder = writable({ open: false });
|
||||||
|
|
||||||
// {'question-id': { option: '', title: '', ... }}
|
// {'question-id': { option: '', title: '', ... }}
|
||||||
export const questionnaireValidation = writable({});
|
export const questionnaireValidation = writable({});
|
||||||
@@ -71,6 +72,7 @@ export function handleAddQuestion() {
|
|||||||
name: `${questions.length + 1}-form`,
|
name: `${questions.length + 1}-form`,
|
||||||
value: '',
|
value: '',
|
||||||
points: 0,
|
points: 0,
|
||||||
|
order: questions.length,
|
||||||
question_type: {
|
question_type: {
|
||||||
id: QUESTION_TYPE.RADIO,
|
id: QUESTION_TYPE.RADIO,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<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
|
<div
|
||||||
class="head flex items-center justify-between sticky right-0 w-full p-3"
|
class="head flex items-center justify-between sticky right-0 w-full px-3"
|
||||||
>
|
>
|
||||||
<slot name="header" />
|
<slot name="header" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user