CLASSROOMIO-T-226: Reorganise exercise page + fetch exercise submission
Co-authored-by: Rotimi Best <[email protected]> Merge-request: CLASSROOMIO-MR-108 Merged-by: Best Admin <[email protected]>
This commit is contained in:
committed by
Space Cloud
co-authored by
Rotimi Best
parent
bfd42a11e9
commit
b31fd5bcf2
@@ -0,0 +1,31 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { fetchSubmission } from '$lib/utils/services/submissions';
|
||||||
|
import type { ExerciseSubmissions } from '$lib/utils/types';
|
||||||
|
|
||||||
|
export let exerciseId: string;
|
||||||
|
|
||||||
|
let isLoading = false;
|
||||||
|
let analyticsData: ExerciseSubmissions[] = [];
|
||||||
|
|
||||||
|
async function fetchAnalyticsData(id: string | undefined) {
|
||||||
|
console.log('id', id);
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
const { data } = await fetchSubmission({
|
||||||
|
exerciseId: id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
// This is the data you need, look at the typescript interface
|
||||||
|
analyticsData = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$: fetchAnalyticsData(exerciseId);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if isLoading}
|
||||||
|
Loading data
|
||||||
|
{:else}
|
||||||
|
Analytics data
|
||||||
|
{/if}
|
||||||
@@ -17,11 +17,11 @@
|
|||||||
import CheckmarkFilledIcon from 'carbon-icons-svelte/lib/CheckmarkFilled.svelte';
|
import CheckmarkFilledIcon from 'carbon-icons-svelte/lib/CheckmarkFilled.svelte';
|
||||||
import CheckmarkOutlineIcon from 'carbon-icons-svelte/lib/CheckmarkOutline.svelte';
|
import CheckmarkOutlineIcon from 'carbon-icons-svelte/lib/CheckmarkOutline.svelte';
|
||||||
|
|
||||||
|
import TextField from '$lib/components/Form/TextField.svelte';
|
||||||
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 RadioItem from '$lib/components/Form/RadioItem.svelte';
|
import RadioItem from '$lib/components/Form/RadioItem.svelte';
|
||||||
import AutoGrowTextField from '$lib/components/Form/AutoGrowTextField.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';
|
||||||
@@ -34,10 +34,9 @@
|
|||||||
import { deleteExercise } from '$lib/utils/services/courses';
|
import { deleteExercise } from '$lib/utils/services/courses';
|
||||||
import { lesson } from '../store/lessons';
|
import { lesson } from '../store/lessons';
|
||||||
|
|
||||||
const extraActions = ['Code Snippets', 'Upload image'];
|
|
||||||
const initialQuestionsLength = $questionnaire.questions.length;
|
const initialQuestionsLength = $questionnaire.questions.length;
|
||||||
|
|
||||||
export let editDescription = false;
|
export let shouldDeleteExercise = false;
|
||||||
export let exerciseId;
|
export let exerciseId;
|
||||||
export let goBack = () => {};
|
export let goBack = () => {};
|
||||||
|
|
||||||
@@ -65,21 +64,6 @@
|
|||||||
handleRemoveQuestion(questionIdToDelete)();
|
handleRemoveQuestion(questionIdToDelete)();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleOptionClick(questionIndex) {
|
|
||||||
return (actionIndex) => {
|
|
||||||
switch (actionIndex) {
|
|
||||||
case 0:
|
|
||||||
handleCode(questionIndex, true);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
// uploadImage(questionIndex);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log('No option');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getQuestionErrorMsg(errors, question, errorKey) {
|
function getQuestionErrorMsg(errors, question, errorKey) {
|
||||||
return errors[question.id] ? errors[question.id][errorKey] : null;
|
return errors[question.id] ? errors[question.id][errorKey] : null;
|
||||||
}
|
}
|
||||||
@@ -108,13 +92,13 @@
|
|||||||
<OrderModal />
|
<OrderModal />
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
onClose={() => (editDescription = false)}
|
onClose={() => (shouldDeleteExercise = false)}
|
||||||
bind:open={editDescription}
|
bind:open={shouldDeleteExercise}
|
||||||
width="w-2/4"
|
width="w-2/4"
|
||||||
modalHeading="Update description"
|
modalHeading="Delete Modal"
|
||||||
>
|
>
|
||||||
<form on:submit|preventDefault>
|
<form on:submit|preventDefault>
|
||||||
<h1 class="dark:text-white text-2xl">Are you sure?</h1>
|
<h1 class="dark:text-white text-xl">Are you sure you want to delete this exercise?</h1>
|
||||||
|
|
||||||
<div class="mt-5 flex items-center justify-between">
|
<div class="mt-5 flex items-center justify-between">
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
@@ -122,7 +106,7 @@
|
|||||||
variant={VARIANTS.OUTLINED}
|
variant={VARIANTS.OUTLINED}
|
||||||
label="No, cancel"
|
label="No, cancel"
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={() => (editDescription = false)}
|
onClick={() => (shouldDeleteExercise = false)}
|
||||||
/>
|
/>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
className="px-6 py-3"
|
className="px-6 py-3"
|
||||||
@@ -153,15 +137,15 @@
|
|||||||
question.is_dirty = true;
|
question.is_dirty = true;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between items-center">
|
||||||
<div class="mr-5 w-3/5">
|
<div class="mr-5 w-3/5">
|
||||||
<AutoGrowTextField
|
<TextField
|
||||||
|
placeholder="Question"
|
||||||
bind:value={question.title}
|
bind:value={question.title}
|
||||||
onInput={(e) => {
|
isRequired={true}
|
||||||
question.title = e.target?.textContent;
|
onChange={() => {
|
||||||
question.is_dirty = true;
|
question.is_dirty = true;
|
||||||
}}
|
}}
|
||||||
placeholder="Question"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
export let preview: boolean;
|
export let preview: boolean;
|
||||||
|
|
||||||
function getTotalPossibleGrade(questions) {
|
function getTotalPossibleGrade(questions: { points: string }[]) {
|
||||||
return questions.reduce((acc, question) => {
|
return questions.reduce((acc, question) => {
|
||||||
acc += parseFloat(question.points, 10);
|
acc += parseFloat(question.points, 10);
|
||||||
return acc;
|
return acc;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import { group, course } from '$lib/components/Course/store';
|
import { group, course } from '$lib/components/Course/store';
|
||||||
import { questionnaire } from '../store/exercise';
|
import { questionnaire } from '../store/exercise';
|
||||||
@@ -19,16 +19,15 @@
|
|||||||
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 { exerciseMode } from './store';
|
|
||||||
|
|
||||||
export let preview;
|
export let preview = false;
|
||||||
|
|
||||||
export let exerciseId;
|
export let exerciseId = '';
|
||||||
|
|
||||||
let currentQuestion = {};
|
let currentQuestion = {};
|
||||||
let renderProps = {};
|
let renderProps = {};
|
||||||
let submission;
|
let submission;
|
||||||
let hasSubmission = null;
|
let hasSubmission = false;
|
||||||
|
|
||||||
function handleStart() {
|
function handleStart() {
|
||||||
$questionnaireMetaData.currentQuestionIndex += 1;
|
$questionnaireMetaData.currentQuestionIndex += 1;
|
||||||
@@ -92,7 +91,7 @@
|
|||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkForSubmission(people, profileId, courseId) {
|
async function checkForSubmission(people, profileId: string, courseId: string) {
|
||||||
if (!Array.isArray(people) || !profileId || !courseId || !!submission) {
|
if (!Array.isArray(people) || !profileId || !courseId || !!submission) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -163,12 +162,12 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if preview}
|
{#if preview}
|
||||||
{#if $exerciseMode.editMode}
|
<RoleBasedSecurity allowedRoles={[1, 2]}>
|
||||||
<Preview
|
<Preview
|
||||||
questions={filterOutDeleted($questionnaire.questions)}
|
questions={filterOutDeleted($questionnaire.questions)}
|
||||||
questionnaireMetaData={$questionnaireMetaData}
|
questionnaireMetaData={$questionnaireMetaData}
|
||||||
/>
|
/>
|
||||||
{/if}
|
</RoleBasedSecurity>
|
||||||
{: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" />
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
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 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,44 +19,46 @@
|
|||||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||||
import { upsertExercise } from '$lib/utils/services/courses';
|
import { upsertExercise } from '$lib/utils/services/courses';
|
||||||
import UpdateDescription from './UpdateDescription.svelte';
|
import UpdateDescription from './UpdateDescription.svelte';
|
||||||
import { TrashCan } from 'carbon-icons-svelte';
|
|
||||||
import { exerciseMode } from './store';
|
|
||||||
import { snackbar } from '$lib/components/Snackbar/store';
|
import { snackbar } from '$lib/components/Snackbar/store';
|
||||||
|
import { OverflowMenu, OverflowMenuItem } from 'carbon-components-svelte';
|
||||||
|
import Analytics from './Analytics/index.svelte';
|
||||||
|
import { ContentSwitcher, Switch } from 'carbon-components-svelte';
|
||||||
|
|
||||||
export let exerciseId;
|
export let exerciseId = '';
|
||||||
export let goBack = () => {};
|
export let goBack = () => {};
|
||||||
export let isStudent = true;
|
export let isStudent = true;
|
||||||
|
|
||||||
let preview = false;
|
let preview = false;
|
||||||
let editDescription = false;
|
let shouldDeleteExercise = false;
|
||||||
let isSaving = false;
|
let isSaving = false;
|
||||||
|
let selectedIndex = 0;
|
||||||
|
|
||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
if ($exerciseMode.editMode) {
|
if (isStudent) return;
|
||||||
const errors = validateQuestionnaire($questionnaire.questions);
|
|
||||||
if (Object.values(errors).length > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
isSaving = true;
|
|
||||||
// $exerciseMode.mode = MODES.view;
|
|
||||||
|
|
||||||
reset();
|
const errors = validateQuestionnaire($questionnaire.questions);
|
||||||
try {
|
if (Object.values(errors).length > 0) {
|
||||||
const updatedQuestions = await upsertExercise($questionnaire, exerciseId);
|
return;
|
||||||
|
|
||||||
questionnaire.update((q) => ({
|
|
||||||
...q,
|
|
||||||
is_title_dirty: false,
|
|
||||||
is_description_dirty: false,
|
|
||||||
questions: updatedQuestions
|
|
||||||
}));
|
|
||||||
snackbar.success('Saved Successfully');
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
snackbar.error();
|
|
||||||
}
|
|
||||||
isSaving = false;
|
|
||||||
}
|
}
|
||||||
|
isSaving = true;
|
||||||
|
|
||||||
|
reset();
|
||||||
|
try {
|
||||||
|
const updatedQuestions = await upsertExercise($questionnaire, exerciseId);
|
||||||
|
|
||||||
|
questionnaire.update((q) => ({
|
||||||
|
...q,
|
||||||
|
is_title_dirty: false,
|
||||||
|
is_description_dirty: false,
|
||||||
|
// @ts-ignore
|
||||||
|
questions: updatedQuestions
|
||||||
|
}));
|
||||||
|
snackbar.success('Saved Successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
snackbar.error();
|
||||||
|
}
|
||||||
|
isSaving = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
@@ -67,30 +68,33 @@
|
|||||||
$: $questionnaire.questions.length < 1 && handleAddQuestion();
|
$: $questionnaire.questions.length < 1 && handleAddQuestion();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<PageBody
|
<PageBody bind:isPageNavHidden={isStudent} padding="px-4 overflow-x-hidden">
|
||||||
bind:isPageNavHidden={isStudent}
|
<ContentSwitcher bind:selectedIndex class="mb-2">
|
||||||
padding="px-4 overflow-x-hidden"
|
<Switch text={`Questions ${0}`} />
|
||||||
headerClassName="bg-gray-100 rounded-md mb-3"
|
<Switch text="Analytics" />
|
||||||
>
|
</ContentSwitcher>
|
||||||
<svelte:fragment slot="header">
|
|
||||||
<RoleBasedSecurity allowedRoles={[1, 2]}>
|
{#if selectedIndex === 0}
|
||||||
<div class="flex items-center py-2 w-">
|
<div
|
||||||
{#if $exerciseMode.editMode}
|
class="top-0 z-10 bg-gray-100 rounded-md mb-3 dark:bg-neutral-800 flex items-center justify-end sticky right-0 w-full px-3"
|
||||||
|
>
|
||||||
|
<RoleBasedSecurity allowedRoles={[1, 2]}>
|
||||||
|
<div class="flex items-center">
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
variant={VARIANTS.CONTAINED}
|
variant={VARIANTS.CONTAINED}
|
||||||
label={'Save'}
|
label="Save"
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
isLoading={isSaving}
|
isLoading={isSaving}
|
||||||
/>
|
/>
|
||||||
{/if}
|
|
||||||
{#if $exerciseMode.editMode}
|
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => (preview = !preview)}
|
onClick={() => (preview = !preview)}
|
||||||
toolTipProps={{ title: 'Toggle preview', direction: 'right' }}
|
contained={preview}
|
||||||
selected={preview}
|
toolTipProps={{
|
||||||
contained={true}
|
title: 'Preview',
|
||||||
size="small"
|
direction: 'bottom',
|
||||||
|
hotkeys: []
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{#if preview}
|
{#if preview}
|
||||||
<ViewFilledIcon size={24} class="carbon-icon dark:text-white" />
|
<ViewFilledIcon size={24} class="carbon-icon dark:text-white" />
|
||||||
@@ -98,50 +102,32 @@
|
|||||||
<ViewIcon size={24} class="carbon-icon dark:text-white" />
|
<ViewIcon size={24} class="carbon-icon dark:text-white" />
|
||||||
{/if}
|
{/if}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
{/if}
|
<IconButton onClick={() => handleAddQuestion()} size="small">
|
||||||
</div>
|
<AddAltIcon size={24} class="carbon-icon dark:text-white" />
|
||||||
|
</IconButton>
|
||||||
|
<OverflowMenu flipped>
|
||||||
|
<OverflowMenuItem
|
||||||
|
text="Reorder Questions"
|
||||||
|
on:click={() => ($questionnaireOrder.open = true)}
|
||||||
|
/>
|
||||||
|
<OverflowMenuItem
|
||||||
|
danger
|
||||||
|
text="Delete Exercise"
|
||||||
|
on:click={() => (shouldDeleteExercise = true)}
|
||||||
|
/>
|
||||||
|
</OverflowMenu>
|
||||||
|
</div>
|
||||||
|
</RoleBasedSecurity>
|
||||||
|
</div>
|
||||||
|
<RoleBasedSecurity allowedRoles={[1, 2]}>
|
||||||
|
<UpdateDescription {preview} />
|
||||||
</RoleBasedSecurity>
|
</RoleBasedSecurity>
|
||||||
|
{#if !isStudent && !preview}
|
||||||
{#if $exerciseMode.editMode}
|
<EditMode bind:shouldDeleteExercise {exerciseId} {goBack} />
|
||||||
<div class="flex items-center">
|
{:else}
|
||||||
<IconButton
|
<ViewMode {preview} {exerciseId} />
|
||||||
onClick={() => handleAddQuestion()}
|
|
||||||
toolTipProps={{ title: 'Add question', direction: 'bottom' }}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<AddAltIcon size={24} class="carbon-icon dark:text-white" />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
onClick={() => ($questionnaireOrder.open = true)}
|
|
||||||
toolTipProps={{ title: 'Order questionnaire', direction: 'bottom' }}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<TableIcon size={24} class="carbon-icon dark:text-white" />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
onClick={() => (editDescription = true)}
|
|
||||||
toolTipProps={{ title: 'Delete Exercise', direction: 'bottom' }}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<TrashCan size={24} class="carbon-icon dark:text-white" />
|
|
||||||
</IconButton>
|
|
||||||
|
|
||||||
<!-- <IconButton
|
|
||||||
onClick={handleDeleteExercise}
|
|
||||||
toolTipProps={{ title: 'Delete questionnaire', direction: 'bottom' }}
|
|
||||||
>
|
|
||||||
<Delete32 class="carbon-icon dark:text-white" />
|
|
||||||
</IconButton> -->
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
</svelte:fragment>
|
{:else if selectedIndex === 1}
|
||||||
|
<Analytics bind:exerciseId />
|
||||||
{#if $exerciseMode.editMode}
|
|
||||||
<UpdateDescription {preview} {exerciseId} />
|
|
||||||
{/if}
|
|
||||||
{#if $exerciseMode.editMode && !preview}
|
|
||||||
<EditMode bind:editDescription {exerciseId} {goBack} />
|
|
||||||
{:else}
|
|
||||||
<ViewMode {preview} {exerciseId} />
|
|
||||||
{/if}
|
{/if}
|
||||||
</PageBody>
|
</PageBody>
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import { writable } from 'svelte/store';
|
|
||||||
|
|
||||||
export const exerciseMode = writable({
|
|
||||||
editMode: true
|
|
||||||
});
|
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
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 } from '../store/exercise';
|
||||||
import { exerciseMode } from '../Exercise/store';
|
|
||||||
import NewExerciseModal from '$lib/components/Course/components/Lesson/Exercises/NewExerciseModal.svelte';
|
import NewExerciseModal from '$lib/components/Course/components/Lesson/Exercises/NewExerciseModal.svelte';
|
||||||
import type { ExerciseTemplate } from '$lib/utils/types';
|
import type { ExerciseTemplate } from '$lib/utils/types';
|
||||||
|
|
||||||
@@ -124,7 +123,6 @@
|
|||||||
data.is_due_by_dirty = false;
|
data.is_due_by_dirty = false;
|
||||||
|
|
||||||
questionnaire.set(data);
|
questionnaire.set(data);
|
||||||
console.log('getExercise', exerciseId, $questionnaire);
|
|
||||||
|
|
||||||
isQuestionnaireFetching.update(() => false);
|
isQuestionnaireFetching.update(() => false);
|
||||||
isFetching = false;
|
isFetching = false;
|
||||||
@@ -139,8 +137,6 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
$: getExercise(exerciseId);
|
$: getExercise(exerciseId);
|
||||||
|
|
||||||
$: $exerciseMode.editMode = !isStudent;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if isFetching}
|
{#if isFetching}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ToolTip from '../ToolTip/index.svelte';
|
import ToolTip from '../ToolTip/index.svelte';
|
||||||
|
|
||||||
export let onClick = (v: string) => {};
|
export let onClick = (v: string) => {};
|
||||||
export let stopPropagation = false;
|
export let stopPropagation = false;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
>
|
>
|
||||||
{#if $$slots.header}
|
{#if $$slots.header}
|
||||||
<div
|
<div
|
||||||
class="head dark:bg-black flex items-center justify-between sticky right-0 w-full px-3 {headerClassName}"
|
class="head dark:bg-neutral-800 flex items-center justify-between sticky right-0 w-full px-3 {headerClassName}"
|
||||||
>
|
>
|
||||||
<slot name="header" />
|
<slot name="header" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
<HtmlRender className="mt-4">
|
<HtmlRender className="mt-4">
|
||||||
<svelte:fragment slot="content">
|
<svelte:fragment slot="content">
|
||||||
<h3 class="dark:text-white {labelClassName} {!isNaN(grade) && 'w-3/4'}">
|
<h3 class="dark:text-white {labelClassName} {!isNaN(grade) && 'w-3/4'}">
|
||||||
{title}
|
{@html title}
|
||||||
</h3>
|
</h3>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</HtmlRender>
|
</HtmlRender>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
export let title;
|
export let title = '';
|
||||||
export let hotkeys = [];
|
export let hotkeys: string[] = [];
|
||||||
export let direction;
|
export let direction = '';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !!title}
|
{#if !!title}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
import type { PostgrestSingleResponse } from '@supabase/supabase-js';
|
||||||
import { supabase } from '$lib/utils/functions/supabase';
|
import { supabase } from '$lib/utils/functions/supabase';
|
||||||
|
import type { ExerciseSubmissions } from '$lib/utils/types';
|
||||||
|
|
||||||
export function fetchSubmissionStatus() {
|
export function fetchSubmissionStatus() {
|
||||||
return supabase.from('submissionstatus').select(`*`);
|
return supabase.from('submissionstatus').select(`*`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchSubmissions(course_id) {
|
export function fetchSubmissions(course_id: string) {
|
||||||
return supabase
|
return supabase
|
||||||
.from('submission')
|
.from('submission')
|
||||||
.select(
|
.select(
|
||||||
@@ -33,28 +35,58 @@ export function fetchSubmissions(course_id) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchSubmission({ courseId, exerciseId, submittedBy }) {
|
export async function fetchSubmission({
|
||||||
|
courseId,
|
||||||
|
exerciseId,
|
||||||
|
submittedBy
|
||||||
|
}: {
|
||||||
|
exerciseId: string;
|
||||||
|
courseId?: string;
|
||||||
|
submittedBy?: string;
|
||||||
|
}) {
|
||||||
|
const query: {
|
||||||
|
exercise_id: string;
|
||||||
|
course_id?: string;
|
||||||
|
submitted_by?: string;
|
||||||
|
} = {
|
||||||
|
exercise_id: exerciseId
|
||||||
|
};
|
||||||
|
|
||||||
|
if (courseId) {
|
||||||
|
query.course_id = courseId;
|
||||||
|
}
|
||||||
|
if (submittedBy) {
|
||||||
|
query.submitted_by = submittedBy;
|
||||||
|
}
|
||||||
|
|
||||||
return supabase
|
return supabase
|
||||||
.from('submission')
|
.from('submission')
|
||||||
.select(
|
.select(
|
||||||
`
|
`
|
||||||
id,
|
id,
|
||||||
answers:question_answer(*),
|
answers:question_answer(*),
|
||||||
status_id
|
status_id,
|
||||||
|
submitted_by:groupmember!inner(
|
||||||
|
profile!inner(
|
||||||
|
id,
|
||||||
|
fullname,
|
||||||
|
avatar_url
|
||||||
|
)
|
||||||
|
)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
.match({
|
.match(query)
|
||||||
submitted_by: submittedBy,
|
.returns<ExerciseSubmissions[]>();
|
||||||
exercise_id: exerciseId,
|
|
||||||
course_id: courseId
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateSubmission(
|
export async function updateSubmission(
|
||||||
{ id, status_id, total }: { id?: number; status_id?: number; total?: number },
|
{ id, status_id, total }: { id?: number; status_id?: number; total?: number },
|
||||||
otherArgs?: any
|
otherArgs?: any
|
||||||
) {
|
) {
|
||||||
const toUpdate = {
|
const toUpdate: {
|
||||||
|
status_id?: number;
|
||||||
|
total?: number;
|
||||||
|
} = {
|
||||||
status_id
|
status_id
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -65,6 +97,6 @@ export async function updateSubmission(
|
|||||||
return supabase.from('submission').update(toUpdate, otherArgs).match({ id });
|
return supabase.from('submission').update(toUpdate, otherArgs).match({ id });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateQuestionAnswer(update, match) {
|
export async function updateQuestionAnswer(update: any, match: any) {
|
||||||
return supabase.from('question_answer').update(update).match(match);
|
return supabase.from('question_answer').update(update).match(match);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,26 @@ export interface ExerciseTemplate {
|
|||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
export interface ExerciseSubmissions {
|
||||||
|
id: string;
|
||||||
|
status_id: number;
|
||||||
|
submitted_by: {
|
||||||
|
profile: {
|
||||||
|
id: string;
|
||||||
|
fullname: string;
|
||||||
|
avatar_url: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
answers: {
|
||||||
|
answers: string[];
|
||||||
|
group_member_id: string;
|
||||||
|
id: number;
|
||||||
|
open_answer: string;
|
||||||
|
point: number;
|
||||||
|
question_id: number;
|
||||||
|
submission_id: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
//===========================================
|
//===========================================
|
||||||
|
|
||||||
interface CourseMetadata {
|
interface CourseMetadata {
|
||||||
|
|||||||
Reference in New Issue
Block a user