feat: grade exercise with AI (#296)
* working on the UI of the grading * working on the grading function of the radio and checkboxes * ai grading fully implemented with dummy functions for generating scores * working on the openai response * working on the openai response * finished with the ai_grading * finished with the ai_grading * creating new migration file * working on reviews * updated the translations * improvements --------- Co-authored-by: Rotimi Best <best@classroomio.com>
This commit is contained in:
@@ -161,7 +161,7 @@
|
||||
class="w-[50px]"
|
||||
bind:selected={question.question_type.id}
|
||||
on:change={(e) => {
|
||||
const id = parseInt(e.target.value);
|
||||
const id = parseInt(e.target?.value);
|
||||
question.question_type = QUESTION_TYPES.find((q) => q.id === id);
|
||||
question.is_dirty = true;
|
||||
}}
|
||||
@@ -176,7 +176,7 @@
|
||||
<div class="flex justify-between items-center my-3 w-3/5">
|
||||
<TextArea
|
||||
bind:value={question.code}
|
||||
rows="2"
|
||||
rows={2}
|
||||
placeholder={$t('course.navItem.lessons.exercises.all_exercises.edit_mode.write')}
|
||||
/>
|
||||
<IconButton value="write-code" onClick={() => handleCode(question.id, false)}>
|
||||
|
||||
+195
-56
@@ -1,24 +1,43 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
<script lang="ts">
|
||||
import Modal from '$lib/components/Modal/index.svelte';
|
||||
import Select from '$lib/components/Form/Select.svelte';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
import Preview from './Preview.svelte';
|
||||
import { SELECTABLE_STATUS } from './constants';
|
||||
import { STATUS } from './constants';
|
||||
import { snackbar } from '$lib/components/Snackbar/store';
|
||||
import TextArea from '$lib/components/Form/TextArea.svelte';
|
||||
import { Dropdown, Tag } from 'carbon-components-svelte';
|
||||
import { useCompletion } from 'ai/svelte';
|
||||
import { QUESTION_TYPE } from '$lib/components/Question/constants';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
|
||||
export let open = false;
|
||||
export let onClose = () => {};
|
||||
export let handleSave = () => {};
|
||||
export let isGradeWithAI = false;
|
||||
|
||||
// export let submissionId;
|
||||
export let data = {};
|
||||
export let updateStatus = () => {};
|
||||
|
||||
let status = SELECTABLE_STATUS[0];
|
||||
const SELECTABLE_STATUS = [
|
||||
{
|
||||
id: STATUS.SUBMITTED,
|
||||
text: $t('course.navItem.submissions.submission_status.submitted')
|
||||
},
|
||||
{
|
||||
id: STATUS.IN_PROGRESS,
|
||||
text: $t('course.navItem.submissions.submission_status.in_progress')
|
||||
},
|
||||
{
|
||||
id: STATUS.GRADED,
|
||||
text: $t('course.navItem.submissions.submission_status.graded')
|
||||
}
|
||||
];
|
||||
|
||||
let status = SELECTABLE_STATUS[0];
|
||||
let selectedId = status.id;
|
||||
let reasons = {};
|
||||
let isLoading = false;
|
||||
let total = 0;
|
||||
let maxPoints = 0;
|
||||
|
||||
@@ -26,44 +45,107 @@
|
||||
return (questions || []).reduce((acc, question) => acc + question.points, 0);
|
||||
}
|
||||
|
||||
function calculateTotal(grades) {
|
||||
function calculateTotal(grades: Record<string, string>): number {
|
||||
if (!grades) return 0;
|
||||
return Object.values(grades).reduce((acc, grade) => acc + parseInt(grade), 0);
|
||||
return Object.values(grades).reduce((acc, grade) => acc + parseInt(grade || '0'), 0);
|
||||
}
|
||||
|
||||
function handleStatusChange() {
|
||||
function handleStatusChange(event) {
|
||||
const newSelectedId = event.detail.selectedId;
|
||||
|
||||
setStatus({
|
||||
status_id: newSelectedId
|
||||
});
|
||||
|
||||
updateStatus({
|
||||
submissionId: data.id,
|
||||
prevStatusId: data.status_id,
|
||||
nextStatusId: status.value,
|
||||
nextStatusId: status.id,
|
||||
total
|
||||
});
|
||||
|
||||
snackbar.success(`${$t('snackbar.exercise.submission_updated')} '${status.label}'`);
|
||||
snackbar.success(`${$t('snackbar.exercise.submission_updated')} '${status.text}'`);
|
||||
}
|
||||
|
||||
function setStatus(data) {
|
||||
status = SELECTABLE_STATUS.find((status) => status.value === data.status_id);
|
||||
const statusBySelectedId = SELECTABLE_STATUS.find((status) => status.id === data.status_id);
|
||||
|
||||
if (!statusBySelectedId) {
|
||||
return;
|
||||
}
|
||||
|
||||
status = statusBySelectedId;
|
||||
|
||||
if (data.status_id !== selectedId) {
|
||||
selectedId = data.status_id;
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusColor(status) {
|
||||
// if (!status)
|
||||
return '';
|
||||
const { input, handleSubmit, completion } = useCompletion({
|
||||
api: '/api/completion/gradingprompt',
|
||||
onFinish: async () => {
|
||||
try {
|
||||
const responseData = $completion.replace('```json', '').replace('```', '');
|
||||
|
||||
// switch (status.value) {
|
||||
// case STATUS.SUBMITTED:
|
||||
// return '';
|
||||
// case STATUS.IN_PROGRESS:
|
||||
// return 'text-white bg-primary-700';
|
||||
// case STATUS.GRADED:
|
||||
// return 'text-white bg-green-700';
|
||||
// }
|
||||
}
|
||||
let aiResponses = [];
|
||||
try {
|
||||
// Parse the modified response data as JSON
|
||||
aiResponses = JSON.parse(responseData);
|
||||
} catch (error) {
|
||||
console.error('Error parsing AI response', error);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
console.log('mounting');
|
||||
data?.questions.forEach((question) => {
|
||||
const { id, points, question_type_id } = question;
|
||||
|
||||
if (question_type_id !== QUESTION_TYPE.TEXTAREA) {
|
||||
const answer = data.questionAnswers.find((q) => q.question_id === id);
|
||||
|
||||
reasons = {
|
||||
...reasons,
|
||||
[id]: `${$t('course.navItem.submissions.grading_modal.questions_tried')} ${
|
||||
answer.answers.length
|
||||
} `
|
||||
};
|
||||
data.questionAnswerByPoint[id] = points / answer.answers.length;
|
||||
} else if (aiResponses.length) {
|
||||
const graded = aiResponses.find((res) => res.id === id);
|
||||
|
||||
reasons = {
|
||||
...reasons,
|
||||
[id]: `${graded?.explanation}`
|
||||
};
|
||||
|
||||
data.questionAnswerByPoint[id] = graded.score;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error', error);
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function gradeWithAI() {
|
||||
isGradeWithAI = true;
|
||||
isLoading = true;
|
||||
const paragraphAiInput = data.questions
|
||||
.filter((q) => q.question_type_id === QUESTION_TYPE.TEXTAREA)
|
||||
.map((q) => {
|
||||
const answer = data.questionAnswers.find((qA) => qA.question_id === q.id); // { open_answer: '' }
|
||||
return {
|
||||
id: q.id,
|
||||
question: q.title,
|
||||
answer: answer?.open_answer,
|
||||
point: q.points
|
||||
};
|
||||
});
|
||||
|
||||
$input = JSON.stringify(paragraphAiInput);
|
||||
handleSubmit({ preventDefault: () => {} });
|
||||
}
|
||||
|
||||
$: total = calculateTotal(data.questionAnswerByPoint);
|
||||
$: maxPoints = getMaxPoints(data.questions);
|
||||
$: setStatus(data);
|
||||
@@ -73,10 +155,12 @@
|
||||
modalHeading={data.title}
|
||||
bind:open
|
||||
{onClose}
|
||||
width="w-4/5"
|
||||
containerClass="flex items-start"
|
||||
width="w-4/5 h-[90%]"
|
||||
containerClass="flex items-start !max-h-full h-[85%] py-0 px-4"
|
||||
headerClass="py-2"
|
||||
labelClass="text-base font-semibold"
|
||||
>
|
||||
<div class="w-full">
|
||||
<div class="w-full h-full">
|
||||
<Preview
|
||||
questions={Array.isArray(data.questions)
|
||||
? data.questions.sort((a, b) => a.order - b.order)
|
||||
@@ -86,26 +170,41 @@
|
||||
isFinished: true
|
||||
}}
|
||||
bind:grades={data.questionAnswerByPoint}
|
||||
bind:reasons
|
||||
bind:isGradeWithAI
|
||||
bind:isLoading
|
||||
disableGrading={false}
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-4 w-2/5 sticky top-0">
|
||||
<div class="border border-gray-300 rounded-md mt-2">
|
||||
<div class="border border-gray-300 rounded-md">
|
||||
<div
|
||||
class="hover:bg-gray-100 dark:bg-neutral-800 border-b border-t-0 border-l-0 border-r-0 border-gray-300 p-3"
|
||||
>
|
||||
<p class="dark:text-white font-bold text-lg">
|
||||
Details
|
||||
<p class="dark:text-white font-bold text-base">
|
||||
{$t('course.navItem.submissions.grading_modal.details')}
|
||||
{#if data.isEarly}
|
||||
<span class="ml-2 text-sm badge rounded-sm px-2 bg-green-500 text-white"> early </span>
|
||||
<span class="ml-2 text-sm badge rounded-sm px-2 bg-green-500 text-white">
|
||||
{$t('course.navItem.submissions.grading_modal.early')}</span
|
||||
>
|
||||
{:else}
|
||||
<span class="ml-2 badge text-sm rounded-sm px-2 bg-red-500 text-white"> late </span>
|
||||
<span class="ml-2 badge text-sm rounded-sm px-2 bg-red-500 text-white">
|
||||
{$t('course.navItem.submissions.grading_modal.late')}
|
||||
</span>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center text-sm p-3">
|
||||
<p class="dark:text-white font-bold w-2/5">Total grade</p>
|
||||
<p class="dark:text-white">{total} / {maxPoints}</p>
|
||||
<div class="flex items-center space-x-4 text-sm px-3 py-2">
|
||||
<p class="dark:text-white text-sm text-gray-500 font-semibold">
|
||||
{$t('course.navItem.submissions.grading_modal.total_grade')}:
|
||||
</p>
|
||||
|
||||
<Tag
|
||||
class="dark:text-white font-semibold text-black bg-gray-100 dark:bg-neutral-700 rounded-md w-fit"
|
||||
>
|
||||
{total}/{maxPoints}
|
||||
</Tag>
|
||||
</div>
|
||||
<!-- <div class="flex items-center text-sm p-3">
|
||||
<p class="dark:text-white font-bold w-1/2">Status</p>
|
||||
@@ -114,35 +213,75 @@
|
||||
<p class="dark:text-white">Grading</p>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="flex items-center text-sm p-3">
|
||||
<p class="dark:text-white font-bold w-2/5">Student</p>
|
||||
<div class="flex items-center space-x-4 text-sm px-3 py-2">
|
||||
<p class="dark:text-white text-sm text-gray-500 font-semibold">
|
||||
{$t('course.navItem.submissions.grading_modal.student')}:
|
||||
</p>
|
||||
{#if data.student}
|
||||
<img
|
||||
alt="Student avatar"
|
||||
class="block rounded-full h-6 w-6"
|
||||
src={data.student.avatar_url}
|
||||
/>
|
||||
<p class="dark:text-white ml-2 text-sm">{data.student.fullname}</p>
|
||||
<div
|
||||
class="flex flex-row justify-center items-center bg-gray-100 dark:bg-neutral-700 rounded-md p-[6px]"
|
||||
>
|
||||
<img
|
||||
alt="Student avatar"
|
||||
class="flex rounded-full h-5 w-5"
|
||||
src={data.student.avatar_url}
|
||||
/>
|
||||
<p class="dark:text-white font-semibold ml-2 text-sm line-clamp-1">
|
||||
{data.student.fullname}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex items-center text-sm p-3">
|
||||
<p class="dark:text-white font-bold w-2/5">Status</p>
|
||||
<Select
|
||||
bind:value={status}
|
||||
options={SELECTABLE_STATUS}
|
||||
selectClassName={getStatusColor(status)}
|
||||
onChange={handleStatusChange}
|
||||
className="w-full"
|
||||
<!-- <div class="flex items-center space-x-4 text-sm px-3 py-2">
|
||||
<p class="dark:text-white text-sm text-gray-500 font-semibold">Assesment Type:</p>
|
||||
<Tag
|
||||
class="dark:text-white font-semibold bg-gray-100 dark:bg-neutral-700 rounded-md text-black w-fit"
|
||||
>Paragraph</Tag
|
||||
>
|
||||
</div> -->
|
||||
|
||||
<div class="flex flex-col items-start text-sm px-3 py-2">
|
||||
<p class="dark:text-white text-gray-500 font-semibold">
|
||||
{$t('course.navItem.submissions.grading_modal.status')}:
|
||||
</p>
|
||||
<Dropdown
|
||||
bind:selectedId
|
||||
items={SELECTABLE_STATUS}
|
||||
class="w-full"
|
||||
on:select={handleStatusChange}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center justify-center w-full p-3">
|
||||
|
||||
<div class="flex flex-col items-start text-sm px-3 py-2">
|
||||
<p class="dark:text-white text-gray-500 font-semibold">
|
||||
{$t('course.navItem.submissions.grading_modal.add_comment')}:
|
||||
</p>
|
||||
<TextArea
|
||||
bgColor="bg-gray-100 dark:bg-neutral-700"
|
||||
className="font-semibold"
|
||||
placeholder={$t('course.navItem.submissions.grading_modal.add_comment_placeholder')}
|
||||
bind:value={data.feedback}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full space-y-3 px-3 py-2">
|
||||
<PrimaryButton
|
||||
onClick={gradeWithAI}
|
||||
variant={VARIANTS.OUTLINED}
|
||||
className="space-x-3 py-3 px-8 w-full "
|
||||
>
|
||||
<img src="/ai.svg" alt="ai" />
|
||||
<p class="font-semibold text-sm">
|
||||
{$t('course.navItem.submissions.grading_modal.grade_with_ai')}
|
||||
</p>
|
||||
</PrimaryButton>
|
||||
<PrimaryButton
|
||||
onClick={() => {
|
||||
handleSave(data);
|
||||
onClose();
|
||||
}}
|
||||
label="Submit Grades"
|
||||
variant={VARIANTS.CONTAINED_SUCCESS}
|
||||
label={$t('course.navItem.submissions.grading_modal.submit_grades')}
|
||||
variant={VARIANTS.CONTAINED}
|
||||
className="py-3 px-8 w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,10 @@
|
||||
export let onSubmit = () => {};
|
||||
export let onPrevious = () => {};
|
||||
export let handleGrade = () => {};
|
||||
export let disableGrading = false;
|
||||
export let disableGrading = true;
|
||||
export let isGradeWithAI = false;
|
||||
export let isLoading = false;
|
||||
export let reasons = {};
|
||||
</script>
|
||||
|
||||
{#each questions as currentQuestion, currentQuestionIndex}
|
||||
@@ -27,6 +30,9 @@
|
||||
true
|
||||
)}
|
||||
bind:grade={grades[currentQuestion.id]}
|
||||
bind:isGradeWithAI
|
||||
bind:reason={reasons[currentQuestion.id]}
|
||||
bind:isLoading
|
||||
gradeMax={currentQuestion.points}
|
||||
handleGrade={handleGrade(currentQuestion.id)}
|
||||
{disableGrading}
|
||||
@@ -44,6 +50,9 @@
|
||||
true
|
||||
)}
|
||||
bind:grade={grades[currentQuestion.id]}
|
||||
bind:isGradeWithAI
|
||||
bind:reason={reasons[currentQuestion.id]}
|
||||
bind:isLoading
|
||||
gradeMax={currentQuestion.points}
|
||||
handleGrade={handleGrade(currentQuestion.id)}
|
||||
{disableGrading}
|
||||
@@ -61,6 +70,9 @@
|
||||
true
|
||||
)}
|
||||
bind:grade={grades[currentQuestion.id]}
|
||||
bind:isGradeWithAI
|
||||
bind:reason={reasons[currentQuestion.id]}
|
||||
bind:isLoading
|
||||
gradeMax={currentQuestion.points}
|
||||
handleGrade={handleGrade(currentQuestion.id)}
|
||||
{disableGrading}
|
||||
|
||||
+38
-18
@@ -26,9 +26,12 @@
|
||||
} from '$lib/utils/services/notification/notification';
|
||||
import { lesson } from '../store/lessons';
|
||||
import { browser } from '$app/environment';
|
||||
import { CloseFilled } from 'carbon-icons-svelte';
|
||||
import IconButton from '$lib/components/IconButton/index.svelte';
|
||||
|
||||
export let preview: false;
|
||||
export let preview: boolean = false;
|
||||
export let exerciseId = '';
|
||||
export let isFetchingExercise = false;
|
||||
|
||||
let currentQuestion = {};
|
||||
let renderProps = {};
|
||||
@@ -36,6 +39,7 @@
|
||||
let hasSubmission = false;
|
||||
let isLoadingAutoSavedData = false;
|
||||
let alreadyCheckedAutoSavedData = false;
|
||||
let gradedComment = true;
|
||||
|
||||
function handleStart() {
|
||||
$questionnaireMetaData.currentQuestionIndex += 1;
|
||||
@@ -110,13 +114,14 @@
|
||||
$questionnaireMetaData.status = 1;
|
||||
$questionnaireMetaData.totalPossibleGrade = getTotalPossibleGrade($questionnaire.questions);
|
||||
$questionnaireMetaData.grades = {};
|
||||
submitExercise(
|
||||
$questionnaireMetaData.answers,
|
||||
questions,
|
||||
exerciseId,
|
||||
$course.id,
|
||||
getGroupMemberId($group.people, $profile.id)
|
||||
);
|
||||
($questionnaireMetaData.comment = ''),
|
||||
submitExercise(
|
||||
$questionnaireMetaData.answers,
|
||||
questions,
|
||||
exerciseId,
|
||||
$course.id,
|
||||
getGroupMemberId($group.people, $profile.id)
|
||||
);
|
||||
notifyEducator();
|
||||
}
|
||||
}
|
||||
@@ -140,7 +145,7 @@
|
||||
}, 0);
|
||||
}
|
||||
|
||||
async function checkForSubmission(people, profileId: string, courseId: string) {
|
||||
async function checkForSubmission(people, profileId?: string, courseId?: string) {
|
||||
if (!Array.isArray(people) || !profileId || !courseId || !!submission) {
|
||||
return;
|
||||
}
|
||||
@@ -157,6 +162,7 @@
|
||||
|
||||
if (Array.isArray(data) && data.length) {
|
||||
submission = data[0];
|
||||
|
||||
$questionnaireMetaData.answers = formatAnswers({
|
||||
questions: $questionnaire.questions,
|
||||
answers: submission.answers
|
||||
@@ -168,6 +174,7 @@
|
||||
$questionnaireMetaData.isFinished = true;
|
||||
$questionnaireMetaData.status = submission.status_id;
|
||||
$questionnaireMetaData.finalTotalGrade = 0;
|
||||
$questionnaireMetaData.comment = submission.feedback;
|
||||
$questionnaireMetaData.grades = submission.answers.reduce((acc, answer) => {
|
||||
acc[answer.question_id] = answer.point;
|
||||
$questionnaireMetaData.finalTotalGrade += answer.point;
|
||||
@@ -194,6 +201,10 @@
|
||||
alreadyCheckedAutoSavedData = true;
|
||||
}
|
||||
|
||||
const toggleComments = () => {
|
||||
gradedComment = !gradedComment;
|
||||
};
|
||||
|
||||
$: browser && !alreadyCheckedAutoSavedData && getAutoSavedData();
|
||||
|
||||
// Reactive code
|
||||
@@ -218,7 +229,8 @@
|
||||
$questionnaireMetaData.currentQuestionIndex
|
||||
);
|
||||
}
|
||||
$: checkForSubmission($group.people, $profile.id, $course.id);
|
||||
|
||||
$: !isFetchingExercise && checkForSubmission($group.people, $profile.id, $course.id);
|
||||
</script>
|
||||
|
||||
{#if !preview && $questionnaire.questions.length && !$questionnaireMetaData.isFinished}
|
||||
@@ -281,18 +293,18 @@
|
||||
{:else if $questionnaireMetaData.isFinished}
|
||||
{#if !isLoadingAutoSavedData}
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-col justify-between w-full">
|
||||
<h2 class="text-xl mb-2 mt-0">{$questionnaire.title}</h2>
|
||||
<div class="flex flex-col lg:flex-row items-start lg:items-center lg:space-x-4 w-full">
|
||||
<h2 class="text-xl font-normal">{$questionnaire.title}</h2>
|
||||
{#if STATUS.GRADED === $questionnaireMetaData.status}
|
||||
<span
|
||||
class="status-text bg-green-700 text-white rounded-full py-3 px-6 text-center"
|
||||
class="status-text bg-green-700 text-white py-1 px-2 text-center"
|
||||
title="Status: Pending Review"
|
||||
>
|
||||
Graded
|
||||
</span>
|
||||
{:else}
|
||||
<span
|
||||
class="status-text bg-yellow-600 text-white rounded-full py-3 px-6 text-center"
|
||||
class="status-text bg-yellow-600 text-white py-1 px-2 text-center"
|
||||
title="Status: Pending Review"
|
||||
>
|
||||
Pending
|
||||
@@ -301,13 +313,21 @@
|
||||
</div>
|
||||
{#if STATUS.GRADED === $questionnaireMetaData.status}
|
||||
<span
|
||||
class="p-5 border-2 border-gray-700 rounded-full h-24 w-24 flex items-center justify-center text-2xl"
|
||||
class="p-6 border-2 border-gray-300 bg-[#F5F8FE] rounded-full h-10 w-10 flex items-center justify-center text-[#2751DA] text-sm font-semibold"
|
||||
title="Status: Pending Review"
|
||||
>
|
||||
{$questionnaireMetaData.finalTotalGrade}/{$questionnaireMetaData.totalPossibleGrade}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if $questionnaireMetaData.status === STATUS.GRADED && $questionnaireMetaData.comment}
|
||||
<div
|
||||
class="flex items-center justify-between bg-primary-700 p-4 text-white font-semibold rounded-sm mt-3"
|
||||
>
|
||||
<span> {$questionnaireMetaData.comment}</span>
|
||||
</div>
|
||||
{/if}
|
||||
<Preview
|
||||
questions={$questionnaire.questions.sort((a, b) => a.order - b.order)}
|
||||
questionnaireMetaData={$questionnaireMetaData}
|
||||
@@ -320,11 +340,11 @@
|
||||
<!-- <div transition:fade id="question"> -->
|
||||
<div in:fly={{ x: 500, duration: 1000 }} id="question">
|
||||
{#if QUESTION_TYPE.RADIO === currentQuestion.question_type.id}
|
||||
<RadioQuestion {...renderProps} key={currentQuestion.id} />
|
||||
<RadioQuestion {...renderProps} key={currentQuestion.id} hideGrading={true} />
|
||||
{:else if QUESTION_TYPE.CHECKBOX === currentQuestion.question_type.id}
|
||||
<CheckboxQuestion {...renderProps} key={currentQuestion.id} />
|
||||
<CheckboxQuestion {...renderProps} key={currentQuestion.id} hideGrading={true} />
|
||||
{:else if QUESTION_TYPE.TEXTAREA === currentQuestion.question_type.id}
|
||||
<TextareaQuestion {...renderProps} key={currentQuestion.id} />
|
||||
<TextareaQuestion {...renderProps} key={currentQuestion.id} hideGrading={true} />
|
||||
{/if}
|
||||
</div>
|
||||
{/key}
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
|
||||
export const STATUS = {
|
||||
SUBMITTED: 1,
|
||||
IN_PROGRESS: 2,
|
||||
GRADED: 3
|
||||
};
|
||||
|
||||
export const SELECTABLE_STATUS = [
|
||||
{
|
||||
label: 'Submitted',
|
||||
value: STATUS.SUBMITTED
|
||||
},
|
||||
{
|
||||
label: 'In progress',
|
||||
value: STATUS.IN_PROGRESS
|
||||
},
|
||||
{
|
||||
label: 'Graded',
|
||||
value: STATUS.GRADED
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { onDestroy } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import AddAltIcon from 'carbon-icons-svelte/lib/AddAlt.svelte';
|
||||
@@ -36,8 +36,9 @@
|
||||
export let path = '';
|
||||
export let goBack = () => {};
|
||||
export let isStudent = true;
|
||||
export let isFetching = false;
|
||||
|
||||
let preview = false;
|
||||
let preview: boolean = false;
|
||||
let shouldDeleteExercise = false;
|
||||
let isSaving = false;
|
||||
let selectedIndex = 0;
|
||||
@@ -78,12 +79,11 @@
|
||||
});
|
||||
|
||||
$: $questionnaire?.questions?.length < 1 && handleAddQuestion();
|
||||
console.log('question', $questionnaire.questions);
|
||||
</script>
|
||||
|
||||
<PageBody bind:isPageNavHidden={isStudent} padding="px-4 overflow-x-hidden">
|
||||
<div class="bg-gray-100 dark:bg-neutral-800 top-0 z-10 sticky p-2 mb-3">
|
||||
<Breadcrumb noTrailingSlash class="mb-5">
|
||||
<Breadcrumb noTrailingSlash>
|
||||
<BreadcrumbItem href={path}
|
||||
>{$t('course.navItem.lessons.exercises.all_exercises.heading')}</BreadcrumbItem
|
||||
>
|
||||
@@ -157,7 +157,7 @@
|
||||
{#if !isStudent && !preview}
|
||||
<EditMode bind:shouldDeleteExercise {exerciseId} {goBack} />
|
||||
{:else}
|
||||
<ViewMode {preview} {exerciseId} />
|
||||
<ViewMode {preview} {exerciseId} isFetchingExercise={isFetching} />
|
||||
{/if}
|
||||
{:else if selectedIndex === 1}
|
||||
<Analytics bind:exerciseId />
|
||||
|
||||
@@ -151,7 +151,7 @@
|
||||
{/if}
|
||||
|
||||
{#if exerciseId}
|
||||
<Exercise {exerciseId} {goBack} bind:path bind:isStudent />
|
||||
<Exercise {exerciseId} {goBack} bind:path bind:isStudent {isFetching} />
|
||||
{:else}
|
||||
<NewExerciseModal
|
||||
bind:open
|
||||
|
||||
@@ -19,7 +19,8 @@ const initAnswerState = {
|
||||
currentQuestionIndex: 0,
|
||||
isFinished: false,
|
||||
progressValue: 100,
|
||||
status: STATUS.PENDING
|
||||
status: STATUS.PENDING,
|
||||
comment: ''
|
||||
};
|
||||
|
||||
export const questionnaireMetaData = writable(initAnswerState);
|
||||
|
||||
Reference in New Issue
Block a user