Grade home works
This commit is contained in:
@@ -1,32 +1,35 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { onMount } from 'svelte';
|
||||||
import Modal from '../../../../Modal/index.svelte';
|
import Modal from '../../../../Modal/index.svelte';
|
||||||
import Select from '../../../../Form/Select.svelte';
|
import Select from '../../../../Form/Select.svelte';
|
||||||
|
import PrimaryButton from '../../../../PrimaryButton/index.svelte';
|
||||||
|
import { VARIANTS } from '../../../../PrimaryButton/constants';
|
||||||
import Preview from './Preview.svelte';
|
import Preview from './Preview.svelte';
|
||||||
import { SELECTABLE_STATUS } from './constants';
|
import { SELECTABLE_STATUS } from './constants';
|
||||||
import { snackbarStore } from '../../../../Snackbar/store';
|
import { snackbarStore } from '../../../../Snackbar/store';
|
||||||
import { onMount } from 'svelte';
|
|
||||||
|
|
||||||
export let open;
|
export let open = false;
|
||||||
export let onClose;
|
export let onClose = () => {};
|
||||||
|
export let handleSave = () => {};
|
||||||
|
|
||||||
// export let submissionId;
|
// export let submissionId;
|
||||||
export let data = {};
|
export let data = {};
|
||||||
export let status = SELECTABLE_STATUS[0];
|
export let updateStatus = () => {};
|
||||||
|
|
||||||
|
let status = SELECTABLE_STATUS[0];
|
||||||
|
|
||||||
let grades = {};
|
|
||||||
let total = 0;
|
let total = 0;
|
||||||
let gradeTotal = 0;
|
let maxPoints = 0;
|
||||||
|
|
||||||
function setGrades(data) {
|
function getMaxPoints(questions) {
|
||||||
grades = (data.questions || []).reduce(
|
return (questions || []).reduce(
|
||||||
(acc, question) => ({ ...acc, [question.name]: question.points }),
|
(acc, question) => acc + question.points,
|
||||||
{}
|
0
|
||||||
);
|
);
|
||||||
|
|
||||||
gradeTotal = calculateTotal(grades);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateTotal(grades) {
|
function calculateTotal(grades) {
|
||||||
|
if (!grades) return 0;
|
||||||
return Object.values(grades).reduce(
|
return Object.values(grades).reduce(
|
||||||
(acc, grade) => acc + parseInt(grade),
|
(acc, grade) => acc + parseInt(grade),
|
||||||
0
|
0
|
||||||
@@ -34,21 +37,48 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleStatusChange() {
|
function handleStatusChange() {
|
||||||
console.log(`status`, status);
|
updateStatus({
|
||||||
|
submissionId: data.id,
|
||||||
|
prevStatusId: data.status_id,
|
||||||
|
nextStatusId: status.value,
|
||||||
|
total,
|
||||||
|
});
|
||||||
|
|
||||||
$snackbarStore.open = true;
|
$snackbarStore.open = true;
|
||||||
$snackbarStore.message = `Submission updated to '${status.label}'`;
|
$snackbarStore.message = `Submission updated to '${status.label}'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setStatus(data) {
|
||||||
|
status = SELECTABLE_STATUS.find(
|
||||||
|
(status) => status.value === data.status_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusColor(status) {
|
||||||
|
// if (!status)
|
||||||
|
return '';
|
||||||
|
|
||||||
|
// switch (status.value) {
|
||||||
|
// case STATUS.SUBMITTED:
|
||||||
|
// return '';
|
||||||
|
// case STATUS.IN_PROGRESS:
|
||||||
|
// return 'text-white bg-blue-700';
|
||||||
|
// case STATUS.GRADED:
|
||||||
|
// return 'text-white bg-green-700';
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
console.log('mounting');
|
console.log('mounting');
|
||||||
});
|
});
|
||||||
|
|
||||||
$: setGrades(data);
|
$: total = calculateTotal(data.questionAnswerByPoint);
|
||||||
$: total = calculateTotal(grades);
|
$: maxPoints = getMaxPoints(data.questions);
|
||||||
|
$: setStatus(data);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
modalHeading="Lesson 1: How to use props in React.js"
|
modalHeading={data.title}
|
||||||
bind:open
|
bind:open
|
||||||
{onClose}
|
{onClose}
|
||||||
width="w-4/5"
|
width="w-4/5"
|
||||||
@@ -61,16 +91,27 @@
|
|||||||
answers: data.answers || {},
|
answers: data.answers || {},
|
||||||
isFinished: true,
|
isFinished: true,
|
||||||
}}
|
}}
|
||||||
bind:grades
|
bind:grades={data.questionAnswerByPoint}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-10 w-1/3">
|
<div class="ml-4 w-2/5 sticky top-0">
|
||||||
<Select
|
<div class="flex items-center justify-between">
|
||||||
bind:value={status}
|
<Select
|
||||||
options={SELECTABLE_STATUS}
|
bind:value={status}
|
||||||
color="primary"
|
options={SELECTABLE_STATUS}
|
||||||
onChange={handleStatusChange}
|
selectClassName={getStatusColor(status)}
|
||||||
/>
|
onChange={handleStatusChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PrimaryButton
|
||||||
|
onClick={() => {
|
||||||
|
handleSave(data);
|
||||||
|
}}
|
||||||
|
label="Save"
|
||||||
|
variant={VARIANTS.CONTAINED_SUCCESS}
|
||||||
|
className="py-2 px-8"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="border border-gray-300 rounded-md mt-2">
|
<div class="border border-gray-300 rounded-md mt-2">
|
||||||
<div
|
<div
|
||||||
class="hover:bg-gray-100 border-b border-t-0 border-l-0 border-r-0 border-gray-300 p-3"
|
class="hover:bg-gray-100 border-b border-t-0 border-l-0 border-r-0 border-gray-300 p-3"
|
||||||
@@ -79,24 +120,31 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center text-sm p-3">
|
<div class="flex items-center text-sm p-3">
|
||||||
<p class="font-bold w-1/2">Total grade</p>
|
<p class="font-bold w-2/5">Total grade</p>
|
||||||
<p>{total} / {gradeTotal}</p>
|
<p>{total} / {maxPoints}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center text-sm p-3">
|
<!-- <div class="flex items-center text-sm p-3">
|
||||||
<p class="font-bold w-1/2">Status</p>
|
<p class="font-bold w-1/2">Status</p>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<p class="rounded-full w-5 h-5 bg-yellow-300 mr-2" />
|
<p class="rounded-full w-5 h-5 bg-yellow-300 mr-2" />
|
||||||
<p>Grading</p>
|
<p>Grading</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
<div class="flex items-center text-sm p-3">
|
<div class="flex items-center text-sm p-3">
|
||||||
<p class="font-bold w-1/2">Student</p>
|
<p class="font-bold w-2/5">Student</p>
|
||||||
<p>Jeremy Matt</p>
|
{#if data.student}
|
||||||
|
<img
|
||||||
|
alt="Student avatar"
|
||||||
|
class="block rounded-full h-6 w-6"
|
||||||
|
src={data.student.avatar_url}
|
||||||
|
/>
|
||||||
|
<p class="ml-2 text-sm">{data.student.fullname}</p>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center text-sm p-3">
|
<!-- <div class="flex items-center text-sm p-3">
|
||||||
<p class="font-bold w-1/2">Teacher</p>
|
<p class="font-bold w-1/2">Teacher</p>
|
||||||
<p>rotimi-best</p>
|
<p>rotimi-best</p>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
export let onSubmit = () => {};
|
export let onSubmit = () => {};
|
||||||
export let onPrevious = () => {};
|
export let onPrevious = () => {};
|
||||||
export let handleGrade = () => {};
|
export let handleGrade = () => {};
|
||||||
|
export let disableGrading = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#each questions as currentQuestion, currentQuestionIndex}
|
{#each questions as currentQuestion, currentQuestionIndex}
|
||||||
@@ -25,9 +26,10 @@
|
|||||||
onPrevious,
|
onPrevious,
|
||||||
true
|
true
|
||||||
)}
|
)}
|
||||||
bind:grade={grades[currentQuestion.name]}
|
bind:grade={grades[currentQuestion.id]}
|
||||||
gradeMax={currentQuestion.points}
|
gradeMax={currentQuestion.points}
|
||||||
handleGrade={handleGrade(currentQuestion.name)}
|
handleGrade={handleGrade(currentQuestion.id)}
|
||||||
|
{disableGrading}
|
||||||
/>
|
/>
|
||||||
{:else if QUESTION_TYPE.CHECKBOX === currentQuestion.question_type.id}
|
{:else if QUESTION_TYPE.CHECKBOX === currentQuestion.question_type.id}
|
||||||
<CheckboxQuestion
|
<CheckboxQuestion
|
||||||
@@ -40,9 +42,10 @@
|
|||||||
onPrevious,
|
onPrevious,
|
||||||
true
|
true
|
||||||
)}
|
)}
|
||||||
bind:grade={grades[currentQuestion.name]}
|
bind:grade={grades[currentQuestion.id]}
|
||||||
gradeMax={currentQuestion.points}
|
gradeMax={currentQuestion.points}
|
||||||
handleGrade={handleGrade(currentQuestion.name)}
|
handleGrade={handleGrade(currentQuestion.id)}
|
||||||
|
{disableGrading}
|
||||||
/>
|
/>
|
||||||
{:else if QUESTION_TYPE.TEXTAREA === currentQuestion.question_type.id}
|
{:else if QUESTION_TYPE.TEXTAREA === currentQuestion.question_type.id}
|
||||||
<TextareaQuestion
|
<TextareaQuestion
|
||||||
@@ -55,9 +58,10 @@
|
|||||||
onPrevious,
|
onPrevious,
|
||||||
true
|
true
|
||||||
)}
|
)}
|
||||||
bind:grade={grades[currentQuestion.name]}
|
bind:grade={grades[currentQuestion.id]}
|
||||||
gradeMax={currentQuestion.points}
|
gradeMax={currentQuestion.points}
|
||||||
handleGrade={handleGrade(currentQuestion.name)}
|
handleGrade={handleGrade(currentQuestion.id)}
|
||||||
|
{disableGrading}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -13,7 +13,9 @@
|
|||||||
import { QUESTION_TYPE } from '../../../../Question/constants';
|
import { QUESTION_TYPE } from '../../../../Question/constants';
|
||||||
import { STATUS } from './constants';
|
import { STATUS } from './constants';
|
||||||
import { getPropsForQuestion, filterOutDeleted } from './functions';
|
import { getPropsForQuestion, filterOutDeleted } from './functions';
|
||||||
|
import { formatAnswers } from '../../../function';
|
||||||
import { submitExercise } from '../../../../../utils/services/courses';
|
import { submitExercise } from '../../../../../utils/services/courses';
|
||||||
|
import { fetchSubmission } from '../../../../../utils/services/submissions';
|
||||||
import { profile } from '../../../../../utils/store/user';
|
import { profile } from '../../../../../utils/store/user';
|
||||||
export let preview;
|
export let preview;
|
||||||
|
|
||||||
@@ -21,6 +23,15 @@
|
|||||||
|
|
||||||
let currentQuestion = {};
|
let currentQuestion = {};
|
||||||
let renderProps = {};
|
let renderProps = {};
|
||||||
|
let submission;
|
||||||
|
|
||||||
|
function getGroupMemberId(people, profileId) {
|
||||||
|
const groupMember = people.find(
|
||||||
|
(person) => person.profile_id === profileId
|
||||||
|
);
|
||||||
|
|
||||||
|
return groupMember ? groupMember.id : null;
|
||||||
|
}
|
||||||
|
|
||||||
function handleStart() {
|
function handleStart() {
|
||||||
$questionnaireMetaData.currentQuestionIndex += 1;
|
$questionnaireMetaData.currentQuestionIndex += 1;
|
||||||
@@ -55,13 +66,13 @@
|
|||||||
|
|
||||||
// If last question send to server
|
// If last question send to server
|
||||||
if (isFinished) {
|
if (isFinished) {
|
||||||
|
$questionnaireMetaData.status = 1;
|
||||||
submitExercise(
|
submitExercise(
|
||||||
$questionnaireMetaData.answers,
|
$questionnaireMetaData.answers,
|
||||||
questions,
|
questions,
|
||||||
exerciseId,
|
exerciseId,
|
||||||
$course.id,
|
$course.id,
|
||||||
$group,
|
getGroupMemberId($group.people, $profile.id)
|
||||||
$profile
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,15 +81,6 @@
|
|||||||
$questionnaireMetaData.currentQuestionIndex -= 1;
|
$questionnaireMetaData.currentQuestionIndex -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTotalScores() {
|
|
||||||
let total = 0;
|
|
||||||
for (const score in $questionnaireMetaData.scores) {
|
|
||||||
total += parseInt($questionnaireMetaData.scores[score]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getProgressValue(currentQuestionIndex) {
|
function getProgressValue(currentQuestionIndex) {
|
||||||
if ($questionnaireMetaData.isFinished) {
|
if ($questionnaireMetaData.isFinished) {
|
||||||
return 100;
|
return 100;
|
||||||
@@ -91,6 +93,55 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTotalPossibleGrade() {
|
||||||
|
return $questionnaire.questions.reduce((acc, question) => {
|
||||||
|
acc += question.points;
|
||||||
|
return acc;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkForSubmission(people, profileId, courseId) {
|
||||||
|
console.log('checkForSubmission');
|
||||||
|
|
||||||
|
if (!Array.isArray(people) || !profileId || !courseId || !!submission) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = {
|
||||||
|
exerciseId,
|
||||||
|
courseId,
|
||||||
|
submittedBy: getGroupMemberId(people, profileId),
|
||||||
|
};
|
||||||
|
const { data } = await fetchSubmission(args);
|
||||||
|
|
||||||
|
if (Array.isArray(data) && data.length) {
|
||||||
|
console.log(`submissions`, data);
|
||||||
|
submission = data[0];
|
||||||
|
|
||||||
|
$questionnaireMetaData.answers = formatAnswers({
|
||||||
|
questions: $questionnaire.questions,
|
||||||
|
answers: submission.answers,
|
||||||
|
});
|
||||||
|
|
||||||
|
$questionnaireMetaData.totalPossibleGrade = getTotalPossibleGrade();
|
||||||
|
|
||||||
|
$questionnaireMetaData.currentQuestionIndex =
|
||||||
|
$questionnaire.questions.length;
|
||||||
|
$questionnaireMetaData.isFinished = true;
|
||||||
|
$questionnaireMetaData.status = submission.status_id;
|
||||||
|
$questionnaireMetaData.finalTotalGrade = 0;
|
||||||
|
$questionnaireMetaData.grades = submission.answers.reduce(
|
||||||
|
(acc, answer) => {
|
||||||
|
acc[answer.question_id] = answer.point;
|
||||||
|
$questionnaireMetaData.finalTotalGrade += answer.point;
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
currentQuestion =
|
currentQuestion =
|
||||||
$questionnaire.questions[$questionnaireMetaData.currentQuestionIndex - 1];
|
$questionnaire.questions[$questionnaireMetaData.currentQuestionIndex - 1];
|
||||||
@@ -114,6 +165,8 @@
|
|||||||
$: $questionnaireMetaData.progressValue = getProgressValue(
|
$: $questionnaireMetaData.progressValue = getProgressValue(
|
||||||
$questionnaireMetaData.currentQuestionIndex
|
$questionnaireMetaData.currentQuestionIndex
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$: checkForSubmission($group.people, $profile.id, $course.id);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !preview && $questionnaire.questions.length && !$questionnaireMetaData.isFinished}
|
{#if !preview && $questionnaire.questions.length && !$questionnaireMetaData.isFinished}
|
||||||
@@ -137,7 +190,20 @@
|
|||||||
{:else if $questionnaireMetaData.currentQuestionIndex === 0}
|
{:else if $questionnaireMetaData.currentQuestionIndex === 0}
|
||||||
<div>
|
<div>
|
||||||
<h2 class="my-1">{$questionnaire.title}</h2>
|
<h2 class="my-1">{$questionnaire.title}</h2>
|
||||||
<p>{$questionnaire.description}</p>
|
|
||||||
|
<div class="flex items-center">
|
||||||
|
<p class="mx-2">
|
||||||
|
<strong>{$questionnaire.questions.length}</strong> questions
|
||||||
|
</p>
|
||||||
|
|
|
||||||
|
<p class="mx-2">
|
||||||
|
<strong>{getTotalPossibleGrade()}</strong> points.
|
||||||
|
</p>
|
||||||
|
|
|
||||||
|
<p class="mx-2">All required</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-lg mt-3">{$questionnaire.description}</p>
|
||||||
|
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
onClick={handleStart}
|
onClick={handleStart}
|
||||||
@@ -150,20 +216,20 @@
|
|||||||
<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">{$questionnaire.title}</h2>
|
<h2 class="text-xl mb-2 mt-0">{$questionnaire.title}</h2>
|
||||||
{#if STATUS.PENDING === $questionnaireMetaData.status}
|
{#if STATUS.GRADED === $questionnaireMetaData.status}
|
||||||
<span
|
|
||||||
class="bg-yellow-600 text-white rounded-full py-2 px-6 text-center"
|
|
||||||
title="Status: Pending Review"
|
|
||||||
>
|
|
||||||
Pending
|
|
||||||
</span>
|
|
||||||
{:else if STATUS.GRADED === $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"
|
||||||
>
|
>
|
||||||
Graded
|
Graded
|
||||||
</span>
|
</span>
|
||||||
|
{:else}
|
||||||
|
<span
|
||||||
|
class="bg-yellow-600 text-white rounded-full py-2 px-6 text-center"
|
||||||
|
title="Status: Pending Review"
|
||||||
|
>
|
||||||
|
Pending
|
||||||
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -171,12 +237,14 @@
|
|||||||
class="border-2 border-gray-700 rounded-full h-24 w-24 flex items-center justify-center text-2xl"
|
class="border-2 border-gray-700 rounded-full h-24 w-24 flex items-center justify-center text-2xl"
|
||||||
title="Status: Pending Review"
|
title="Status: Pending Review"
|
||||||
>
|
>
|
||||||
{getTotalScores()}/100
|
{$questionnaireMetaData.finalTotalGrade}/{$questionnaireMetaData.totalPossibleGrade}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Preview
|
<Preview
|
||||||
questions={$questionnaire.questions}
|
questions={$questionnaire.questions}
|
||||||
questionnaireMetaData={$questionnaireMetaData}
|
questionnaireMetaData={$questionnaireMetaData}
|
||||||
|
grades={$questionnaireMetaData.grades}
|
||||||
|
disableGrading={true}
|
||||||
/>
|
/>
|
||||||
{:else if currentQuestion}
|
{:else if currentQuestion}
|
||||||
{#key currentQuestion.id}
|
{#key currentQuestion.id}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
export const STATUS = {
|
export const STATUS = {
|
||||||
PENDING: 0,
|
SUBMITTED: 1,
|
||||||
IN_PROGRESS: 1,
|
IN_PROGRESS: 2,
|
||||||
GRADED: 2,
|
GRADED: 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SELECTABLE_STATUS = [
|
export const SELECTABLE_STATUS = [
|
||||||
{
|
{
|
||||||
label: 'Submitted',
|
label: 'Submitted',
|
||||||
value: STATUS.PENDING,
|
value: STATUS.SUBMITTED,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'In progress',
|
label: 'In progress',
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ import { STATUS } from '../Exercise/constants';
|
|||||||
const initAnswerState = {
|
const initAnswerState = {
|
||||||
answers: {},
|
answers: {},
|
||||||
scores: {},
|
scores: {},
|
||||||
|
grades: {},
|
||||||
|
totalPossibleGrade: 0,
|
||||||
|
finalTotalGrade: 0,
|
||||||
currentQuestionIndex: 0,
|
currentQuestionIndex: 0,
|
||||||
isFinished: false,
|
isFinished: false,
|
||||||
progressValue: 100,
|
progressValue: 100,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ROUTES } from "./constants";
|
import { ROUTES } from './constants';
|
||||||
|
|
||||||
export function getNavItemRoute(courseId, routeId) {
|
export function getNavItemRoute(courseId, routeId) {
|
||||||
const path = `/${ROUTES.COURSES}/${courseId}`;
|
const path = `/${ROUTES.COURSES}/${courseId}`;
|
||||||
@@ -27,3 +27,23 @@ export function getLectureNo(index) {
|
|||||||
|
|
||||||
return `0${index}`;
|
return `0${index}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatAnswers(data) {
|
||||||
|
const answers = {};
|
||||||
|
const questionByIdAndName = {};
|
||||||
|
|
||||||
|
for (const question of data.questions) {
|
||||||
|
questionByIdAndName[question.id] = question.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const answer of data.answers) {
|
||||||
|
const questionName = questionByIdAndName[answer.question_id];
|
||||||
|
|
||||||
|
answers[questionName] =
|
||||||
|
Array.isArray(answer.answers) && answer.answers.length
|
||||||
|
? answer.answers
|
||||||
|
: answer.open_answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return answers;
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
export let labelKey = 'label';
|
export let labelKey = 'label';
|
||||||
export let isRequired = false;
|
export let isRequired = false;
|
||||||
export let className = '';
|
export let className = '';
|
||||||
|
export let selectClassName = '';
|
||||||
export let color = 'default'; // primary
|
export let color = 'default'; // primary
|
||||||
export let onChange = () => {}; // This is to know if element is 'dirty'
|
export let onChange = () => {}; // This is to know if element is 'dirty'
|
||||||
|
|
||||||
@@ -19,10 +20,10 @@
|
|||||||
{#if label} <span class="text-gray-700 mr-2">{label}</span> {/if}
|
{#if label} <span class="text-gray-700 mr-2">{label}</span> {/if}
|
||||||
<select
|
<select
|
||||||
bind:value
|
bind:value
|
||||||
class="form-select block w-auto mt-1"
|
class="form-select block w-auto mt-1 {selectClassName}"
|
||||||
required={isRequired}
|
required={isRequired}
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
class:primary={color === 'primary'}
|
class:customColor={!!selectClassName}
|
||||||
>
|
>
|
||||||
{#each options as option}
|
{#each options as option}
|
||||||
<option class="bg-white text-black" value={option}>
|
<option class="bg-white text-black" value={option}>
|
||||||
@@ -33,18 +34,14 @@
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
select.primary {
|
select.customColor {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 0.375rem;
|
border-radius: 4px;
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgba(255, 255, 255, var(--tw-text-opacity));
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgba(29, 78, 216, var(--tw-bg-opacity));
|
|
||||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' class='text-white' viewBox='0 0 20 20'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' class='text-white' viewBox='0 0 20 20'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
||||||
}
|
}
|
||||||
|
|
||||||
select.primary option {
|
select.customColor option {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
export let className = '';
|
export let className = '';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root mt-4 mx-auto {width} {className} relative pb-5">
|
<div class="root mt-4 mx-auto {width} {className} relative pb-5 px-4">
|
||||||
{#if $$slots.header}
|
{#if $$slots.header}
|
||||||
<div
|
<div
|
||||||
class="head flex items-center justify-between sticky right-0 w-full py-3"
|
class="head flex items-center justify-between sticky right-0 w-full py-3"
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
export const VARIANTS = {
|
export const VARIANTS = {
|
||||||
CONTAINED: 'CONTAINED',
|
CONTAINED: 'CONTAINED',
|
||||||
CONTAINED_DANGER: 'CONTAINED_DANGER',
|
CONTAINED_DANGER: 'CONTAINED_DANGER',
|
||||||
|
CONTAINED_SUCCESS: 'CONTAINED_SUCCESS',
|
||||||
OUTLINED: 'OUTLINED',
|
OUTLINED: 'OUTLINED',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const VARIANTS_CLASS = {
|
export const VARIANTS_CLASS = {
|
||||||
[VARIANTS.CONTAINED]: 'bg-blue-700 hover:bg-blue-900 text-white font-bold',
|
[VARIANTS.CONTAINED]: 'bg-blue-700 hover:bg-blue-900 text-white font-bold',
|
||||||
|
[VARIANTS.CONTAINED_SUCCESS]:
|
||||||
|
'bg-green-700 hover:bg-green-900 text-white font-bold',
|
||||||
[VARIANTS.CONTAINED_DANGER]:
|
[VARIANTS.CONTAINED_DANGER]:
|
||||||
'bg-red-700 hover:bg-red-900 text-white font-bold',
|
'bg-red-700 hover:bg-red-900 text-white font-bold',
|
||||||
[VARIANTS.OUTLINED]:
|
[VARIANTS.OUTLINED]:
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
export let nextButtonProps = {};
|
export let nextButtonProps = {};
|
||||||
export let grade;
|
export let grade;
|
||||||
export let gradeMax = 0;
|
export let gradeMax = 0;
|
||||||
|
export let disableGrading = false;
|
||||||
|
|
||||||
function getVal(form, name) {
|
function getVal(form, name) {
|
||||||
let values = [];
|
let values = [];
|
||||||
@@ -67,6 +68,7 @@
|
|||||||
bind:value={grade}
|
bind:value={grade}
|
||||||
type="number"
|
type="number"
|
||||||
className="w-20"
|
className="w-20"
|
||||||
|
isDisabled={disableGrading}
|
||||||
/>
|
/>
|
||||||
<p class="ml-2 text-lg">/ {gradeMax}</p>
|
<p class="ml-2 text-lg">/ {gradeMax}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
export let nextButtonProps = {};
|
export let nextButtonProps = {};
|
||||||
export let grade;
|
export let grade;
|
||||||
export let gradeMax = 0;
|
export let gradeMax = 0;
|
||||||
|
export let disableGrading = false;
|
||||||
|
|
||||||
function getRadioVal(form, name) {
|
function getRadioVal(form, name) {
|
||||||
let val;
|
let val;
|
||||||
@@ -67,6 +68,7 @@
|
|||||||
bind:value={grade}
|
bind:value={grade}
|
||||||
type="number"
|
type="number"
|
||||||
className="w-20"
|
className="w-20"
|
||||||
|
isDisabled={disableGrading}
|
||||||
/>
|
/>
|
||||||
<p class="ml-2 text-lg">/ {gradeMax}</p>
|
<p class="ml-2 text-lg">/ {gradeMax}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
export let grade;
|
export let grade;
|
||||||
export let gradeMax = 0;
|
export let gradeMax = 0;
|
||||||
|
export let disableGrading = false;
|
||||||
|
|
||||||
function handleFormSubmit(event) {
|
function handleFormSubmit(event) {
|
||||||
if (isPreview) return;
|
if (isPreview) return;
|
||||||
@@ -42,6 +43,7 @@
|
|||||||
bind:value={grade}
|
bind:value={grade}
|
||||||
type="number"
|
type="number"
|
||||||
className="w-20"
|
className="w-20"
|
||||||
|
isDisabled={disableGrading}
|
||||||
/>
|
/>
|
||||||
<p class="ml-2 text-lg">/ {gradeMax}</p>
|
<p class="ml-2 text-lg">/ {gradeMax}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export const SNACKBAR_SEVERITY = {
|
export const SNACKBAR_SEVERITY = {
|
||||||
ERROR: 'ERROR',
|
ERROR: 'ERROR',
|
||||||
WARNING: 'WARNING',
|
WARNING: 'WARNING',
|
||||||
|
DEFAULT: 'DEFAULT',
|
||||||
INFO: 'INFO',
|
INFO: 'INFO',
|
||||||
SUCCESS: 'SUCCESS',
|
SUCCESS: 'SUCCESS',
|
||||||
};
|
};
|
||||||
@@ -9,5 +10,6 @@ export const SNACKBAR_SEVERITY_COLOR = {
|
|||||||
ERROR: 'bg-red-600',
|
ERROR: 'bg-red-600',
|
||||||
WARNING: 'bg-yellow-600',
|
WARNING: 'bg-yellow-600',
|
||||||
INFO: 'bg-blue-600',
|
INFO: 'bg-blue-600',
|
||||||
|
DEFAULT: 'bg-gray-600',
|
||||||
SUCCESS: 'bg-green-600',
|
SUCCESS: 'bg-green-600',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { SNACKBAR_SEVERITY } from './constants';
|
|||||||
export const snackbarStore = writable({
|
export const snackbarStore = writable({
|
||||||
open: false,
|
open: false,
|
||||||
message: '',
|
message: '',
|
||||||
severity: SNACKBAR_SEVERITY.INFO,
|
severity: SNACKBAR_SEVERITY.DEFAULT,
|
||||||
handleClose: () => {},
|
handleClose: () => {},
|
||||||
autoHideDuration: 6000,
|
autoHideDuration: 6000,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -26,7 +26,14 @@
|
|||||||
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';
|
||||||
import { setCourseData, course } from '../../../components/Course/store';
|
import { setCourseData, course } from '../../../components/Course/store';
|
||||||
import { fetchSubmissions } from '../../../utils/services/submissions';
|
import {
|
||||||
|
fetchSubmissions,
|
||||||
|
updateSubmission,
|
||||||
|
updateQuestionAnswer,
|
||||||
|
} from '../../../utils/services/submissions';
|
||||||
|
import { formatAnswers } from '../../../components/Course/function';
|
||||||
|
import { snackbarStore } from '../../../components/Snackbar/store';
|
||||||
|
|
||||||
const flipDurationMs = 300;
|
const flipDurationMs = 300;
|
||||||
|
|
||||||
export let courseData = {};
|
export let courseData = {};
|
||||||
@@ -59,18 +66,41 @@
|
|||||||
const { page } = stores();
|
const { page } = stores();
|
||||||
|
|
||||||
function handleItemFinalize(columnIdx, newItems) {
|
function handleItemFinalize(columnIdx, newItems) {
|
||||||
console.log('handleItemFinalize columnIdx=>', columnIdx, newItems);
|
let itemToWithNewStatus;
|
||||||
sections[columnIdx].items = newItems;
|
|
||||||
// onFinalUpdate([...sections]);
|
const { id } = sections[columnIdx];
|
||||||
|
|
||||||
|
// Set column in the UI
|
||||||
|
sections[columnIdx].items = newItems.map((item) => {
|
||||||
|
if (item.statusId !== id) {
|
||||||
|
itemToWithNewStatus = item;
|
||||||
|
item.statusId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update backend
|
||||||
|
if (itemToWithNewStatus) {
|
||||||
|
// Update key mapping for each submission also
|
||||||
|
submissionIdData[itemToWithNewStatus.id] = {
|
||||||
|
...submissionIdData[itemToWithNewStatus.id],
|
||||||
|
status_id: itemToWithNewStatus.statusId,
|
||||||
|
};
|
||||||
|
console.log(
|
||||||
|
`submissionIdData[itemToWithNewStatus.id]`,
|
||||||
|
submissionIdData[itemToWithNewStatus.id]
|
||||||
|
);
|
||||||
|
|
||||||
|
updateSubmission({
|
||||||
|
id: itemToWithNewStatus.id,
|
||||||
|
status_id: itemToWithNewStatus.statusId,
|
||||||
|
}).then((res) => console.log('Updated submission', res));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDndConsiderCards(columnIdx) {
|
function handleDndConsiderCards(columnIdx) {
|
||||||
return (e) => {
|
return function (e) {
|
||||||
console.warn(
|
|
||||||
'handleDndConsiderCards columnIdx',
|
|
||||||
columnIdx,
|
|
||||||
e.detail.items
|
|
||||||
);
|
|
||||||
sections[columnIdx].items = e.detail.items;
|
sections[columnIdx].items = e.detail.items;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -83,30 +113,66 @@
|
|||||||
goto($page.path);
|
goto($page.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatAnswers(data) {
|
// Via dialog
|
||||||
const answers = {};
|
function updateStatus({ submissionId, prevStatusId, nextStatusId, total }) {
|
||||||
const questionByIdAndName = {};
|
let itemToWithNewStatus;
|
||||||
|
|
||||||
for (const question of data.questions) {
|
// Remove from current column
|
||||||
questionByIdAndName[question.id] = question.name;
|
const { items } = sections[prevStatusId - 1];
|
||||||
|
sections[prevStatusId - 1].items = items.filter((item) => {
|
||||||
|
if (item.id === submissionId) {
|
||||||
|
itemToWithNewStatus = Object.assign(item);
|
||||||
|
itemToWithNewStatus.statusId = nextStatusId;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
console.log(`itemToWithNewStatus`, itemToWithNewStatus);
|
||||||
|
// Move to right column
|
||||||
|
sections[nextStatusId - 1].items = [
|
||||||
|
...sections[nextStatusId - 1].items,
|
||||||
|
itemToWithNewStatus,
|
||||||
|
];
|
||||||
|
|
||||||
|
// If something changed
|
||||||
|
if (itemToWithNewStatus) {
|
||||||
|
// Update key mapping for each submission also
|
||||||
|
submissionIdData[itemToWithNewStatus.id] = {
|
||||||
|
...submissionIdData[itemToWithNewStatus.id],
|
||||||
|
status_id: itemToWithNewStatus.statusId,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update backend
|
||||||
|
updateSubmission({
|
||||||
|
id: itemToWithNewStatus.id,
|
||||||
|
status_id: itemToWithNewStatus.statusId,
|
||||||
|
total,
|
||||||
|
}).then((res) => console.log('Updated submission', res));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const answer of data.answers) {
|
async function handleSave(submission) {
|
||||||
const questionName = questionByIdAndName[answer.question_id];
|
const { questionAnswerByPoint, questionAnswers } = submission;
|
||||||
|
|
||||||
answers[questionName] =
|
const updates = Object.keys(questionAnswerByPoint).map((questionId) => {
|
||||||
Array.isArray(answer.answers) && answer.answers.length
|
const questionAnswer = questionAnswers.find(
|
||||||
? answer.answers
|
(answer) => answer.question_id == questionId
|
||||||
: answer.open_answer;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return answers;
|
const point = questionAnswerByPoint[questionId];
|
||||||
|
|
||||||
|
return updateQuestionAnswer({ point }, { id: questionAnswer.id });
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(updates);
|
||||||
|
$snackbarStore.open = true;
|
||||||
|
$snackbarStore.message = `Saved successfully`;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
setCourseData(courseData);
|
setCourseData(courseData);
|
||||||
console.log(`courseData`, courseData);
|
|
||||||
console.log(`$course`, $course);
|
|
||||||
const { data: submissions } = await fetchSubmissions(
|
const { data: submissions } = await fetchSubmissions(
|
||||||
courseData.id || $course.id
|
courseData.id || $course.id
|
||||||
);
|
);
|
||||||
@@ -118,6 +184,7 @@
|
|||||||
|
|
||||||
const submissionItem = {
|
const submissionItem = {
|
||||||
id,
|
id,
|
||||||
|
statusId: status_id,
|
||||||
exercise: {
|
exercise: {
|
||||||
id: exercise.id,
|
id: exercise.id,
|
||||||
title: exercise.title,
|
title: exercise.title,
|
||||||
@@ -129,10 +196,20 @@
|
|||||||
title: exercise.lesson.title,
|
title: exercise.lesson.title,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
console.log(`answers`, answers);
|
||||||
submissionIdData[id] = {
|
submissionIdData[id] = {
|
||||||
|
id,
|
||||||
|
status_id,
|
||||||
|
title: exercise.title,
|
||||||
|
student: submissionItem.student,
|
||||||
questions: exercise.questions,
|
questions: exercise.questions,
|
||||||
answers: formatAnswers({ questions: exercise.questions, answers }),
|
answers: formatAnswers({ questions: exercise.questions, answers }),
|
||||||
|
questionAnswers: answers,
|
||||||
|
questionAnswerByPoint: answers.reduce((acc, answer) => {
|
||||||
|
acc[answer.question_id] = answer.point;
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Array.isArray(sectionById[status_id])) {
|
if (Array.isArray(sectionById[status_id])) {
|
||||||
@@ -151,13 +228,11 @@
|
|||||||
? sectionById[index + 1]
|
? sectionById[index + 1]
|
||||||
: [],
|
: [],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log(`submissionIdData`, submissionIdData);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
submissionId = $page.query.submissionId;
|
submissionId = $page.query.submissionId;
|
||||||
openExercise = !!submissionId;
|
openExercise = !!submissionId && submissionIdData[submissionId];
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -165,6 +240,8 @@
|
|||||||
bind:open={openExercise}
|
bind:open={openExercise}
|
||||||
onClose={handleModalClose}
|
onClose={handleModalClose}
|
||||||
data={submissionIdData[submissionId] || {}}
|
data={submissionIdData[submissionId] || {}}
|
||||||
|
{handleSave}
|
||||||
|
{updateStatus}
|
||||||
{submissionId}
|
{submissionId}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -172,14 +249,14 @@
|
|||||||
<PageNav title="Submitted Exercises" />
|
<PageNav title="Submitted Exercises" />
|
||||||
|
|
||||||
<PageBody width="w-11/12 overflow-x-auto overflow-y-hidden">
|
<PageBody width="w-11/12 overflow-x-auto overflow-y-hidden">
|
||||||
<div class="flex items-center justify-between w-full">
|
<div class="flex items-center 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-80 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">
|
||||||
<Chip value={items.length} />
|
<Chip value={items.length} class="bg-set" />
|
||||||
<p class="ml-2 font-bold">{title}</p>
|
<p class="ml-2 font-bold">{title}</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -197,19 +274,16 @@
|
|||||||
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
|
<div
|
||||||
class="flex items-center no-underline hover:underline text-black mb-2"
|
class="flex items-center no-underline hover:underline text-black mb-2"
|
||||||
href="{$page.path}?submissionId={item.student.id}"
|
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Placeholder"
|
alt="Student avatar"
|
||||||
class="block rounded-full"
|
class="block rounded-full h-6 w-6"
|
||||||
width="24"
|
|
||||||
height="20"
|
|
||||||
src={item.student.avatar_url}
|
src={item.student.avatar_url}
|
||||||
/>
|
/>
|
||||||
<p class="ml-2 text-sm">{item.student.username}</p>
|
<p class="ml-2 text-sm">{item.student.username}</p>
|
||||||
</a>
|
</div>
|
||||||
<a
|
<a
|
||||||
class="text-blue-700 text-md font-bold"
|
class="text-blue-700 text-md font-bold"
|
||||||
href="{$page.path}?submissionId={item.id}"
|
href="{$page.path}?submissionId={item.id}"
|
||||||
@@ -219,7 +293,7 @@
|
|||||||
<a
|
<a
|
||||||
class="flex items-center no-underline hover:underline text-black my-2"
|
class="flex items-center no-underline hover:underline text-black my-2"
|
||||||
href="{$page.path.replace('submissions', 'lessons')}/{item
|
href="{$page.path.replace('submissions', 'lessons')}/{item
|
||||||
.lesson.id}/exercises"
|
.lesson.id}/exercises/{item.exercise.id}"
|
||||||
>
|
>
|
||||||
<p class="text-grey text-sm">
|
<p class="text-grey text-sm">
|
||||||
#{item.lesson.title}
|
#{item.lesson.title}
|
||||||
|
|||||||
@@ -302,14 +302,9 @@ export async function submitExercise(
|
|||||||
questions,
|
questions,
|
||||||
exerciseId,
|
exerciseId,
|
||||||
courseId,
|
courseId,
|
||||||
group,
|
groupMemberId
|
||||||
profile
|
|
||||||
) {
|
) {
|
||||||
const groupMember = group.people.find(
|
if (!groupMemberId) {
|
||||||
(person) => person.profile_id === profile.id
|
|
||||||
);
|
|
||||||
console.log(`groupMember`, groupMember);
|
|
||||||
if (!groupMember) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,7 +315,7 @@ export async function submitExercise(
|
|||||||
const questionAnswers = [];
|
const questionAnswers = [];
|
||||||
|
|
||||||
const { data: submission } = await supabase.from('submission').insert({
|
const { data: submission } = await supabase.from('submission').insert({
|
||||||
submitted_by: groupMember.id,
|
submitted_by: groupMemberId,
|
||||||
exercise_id: exerciseId,
|
exercise_id: exerciseId,
|
||||||
course_id: courseId,
|
course_id: courseId,
|
||||||
});
|
});
|
||||||
@@ -329,7 +324,7 @@ export async function submitExercise(
|
|||||||
const value = answers[questionName];
|
const value = answers[questionName];
|
||||||
|
|
||||||
const questionAnswer = {
|
const questionAnswer = {
|
||||||
group_member_id: groupMember.id,
|
group_member_id: groupMemberId,
|
||||||
question_id: questionsByName[questionName],
|
question_id: questionsByName[questionName],
|
||||||
open_answer: '',
|
open_answer: '',
|
||||||
answers: [],
|
answers: [],
|
||||||
|
|||||||
@@ -31,3 +31,36 @@ export function fetchSubmissions(course_id) {
|
|||||||
course_id,
|
course_id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchSubmission({ courseId, exerciseId, submittedBy }) {
|
||||||
|
return supabase
|
||||||
|
.from('submission')
|
||||||
|
.select(
|
||||||
|
`
|
||||||
|
id,
|
||||||
|
answers:question_answer(*),
|
||||||
|
status_id
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.match({
|
||||||
|
submitted_by: submittedBy,
|
||||||
|
exercise_id: exerciseId,
|
||||||
|
course_id: courseId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateSubmission({ id, status_id, total }) {
|
||||||
|
const toUpdate = {
|
||||||
|
status_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof total === 'number') {
|
||||||
|
toUpdate.total = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
return supabase.from('submission').update(toUpdate).match({ id });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateQuestionAnswer(update, match) {
|
||||||
|
return supabase.from('question_answer').update(update).match(match);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user