feat: delete submission

This commit is contained in:
Rotimi Best
2024-08-03 20:58:07 +01:00
parent e8de6b9457
commit 30a6193e3c
20 changed files with 305 additions and 161 deletions
@@ -18,24 +18,24 @@
onClose={() => (openDeleteModal = false)} onClose={() => (openDeleteModal = false)}
bind:open={openDeleteModal} bind:open={openDeleteModal}
width="w-96" width="w-96"
modalHeading={$t('course.navItem.lessons.delete_lesson.modal_heading')} modalHeading={$t('delete_modal.label')}
> >
<div> <div>
<h1 class="dark:text-white text-lg"> <h1 class="dark:text-white text-lg">
{$t('course.navItem.lessons.delete_lesson.content')} <strong>{lessonTitle}</strong>? {$t('delete_modal.content')} <strong>{lessonTitle}</strong>?
</h1> </h1>
<div class="mt-5 flex items-center justify-between"> <div class="mt-5 flex items-center justify-between">
<PrimaryButton <PrimaryButton
className="px-6 py-3" className="px-6 py-3"
variant={VARIANTS.OUTLINED} variant={VARIANTS.OUTLINED}
label={$t('course.navItem.lessons.delete_lesson.no')} label={$t('delete_modal.no')}
onClick={() => (openDeleteModal = false)} onClick={() => (openDeleteModal = false)}
/> />
<PrimaryButton <PrimaryButton
className="px-6 py-3" className="px-6 py-3"
variant={VARIANTS.OUTLINED} variant={VARIANTS.OUTLINED}
label={$t('course.navItem.lessons.delete_lesson.yes')} label={$t('delete_modal.yes')}
onClick={handleDelete} onClick={handleDelete}
/> />
</div> </div>
@@ -10,14 +10,21 @@
import { useCompletion } from 'ai/svelte'; import { useCompletion } from 'ai/svelte';
import { QUESTION_TYPE } from '$lib/components/Question/constants'; import { QUESTION_TYPE } from '$lib/components/Question/constants';
import { t } from '$lib/utils/functions/translations'; import { t } from '$lib/utils/functions/translations';
import { OverflowMenu, OverflowMenuItem } from 'carbon-components-svelte';
import type { SubmissionIdData } from '$lib/utils/types/submission';
export let open = false; export let open = false;
export let onClose = () => {}; export let onClose = () => {};
export let handleSave = () => {}; export let handleSave = (d: SubmissionIdData) => {};
export let isGradeWithAI = false; export let isGradeWithAI = false;
export let data: SubmissionIdData;
export let data = {}; export let deleteSubmission = async (id: string, statusId: number) => {};
export let updateStatus = () => {}; export let updateStatus = (arg: {
submissionId: string;
prevStatusId: number;
nextStatusId: number;
total: number;
}) => {};
const SELECTABLE_STATUS = [ const SELECTABLE_STATUS = [
{ {
@@ -40,6 +47,9 @@
let isLoading = false; let isLoading = false;
let total = 0; let total = 0;
let maxPoints = 0; let maxPoints = 0;
let openMenu = false;
let openDeletePrompt = false;
let isDeleting = false;
function getMaxPoints(questions) { function getMaxPoints(questions) {
return (questions || []).reduce((acc, question) => acc + question.points, 0); return (questions || []).reduce((acc, question) => acc + question.points, 0);
@@ -54,12 +64,13 @@
const newSelectedId = event.detail.selectedId; const newSelectedId = event.detail.selectedId;
setStatus({ setStatus({
status_id: newSelectedId ...data,
statusId: newSelectedId
}); });
updateStatus({ updateStatus({
submissionId: data.id, submissionId: data.id,
prevStatusId: data.status_id, prevStatusId: data.statusId,
nextStatusId: status.id, nextStatusId: status.id,
total total
}); });
@@ -67,8 +78,8 @@
snackbar.success(`${$t('snackbar.exercise.submission_updated')} '${status.text}'`); snackbar.success(`${$t('snackbar.exercise.submission_updated')} '${status.text}'`);
} }
function setStatus(data) { function setStatus(data: SubmissionIdData) {
const statusBySelectedId = SELECTABLE_STATUS.find((status) => status.id === data.status_id); const statusBySelectedId = SELECTABLE_STATUS.find((status) => status.id === data.statusId);
if (!statusBySelectedId) { if (!statusBySelectedId) {
return; return;
@@ -76,18 +87,28 @@
status = statusBySelectedId; status = statusBySelectedId;
if (data.status_id !== selectedId) { if (data.statusId !== selectedId) {
selectedId = data.status_id; selectedId = data.statusId;
} }
} }
async function handleDeleteSubmission() {
isDeleting = true;
await deleteSubmission(data.id, status.id);
isDeleting = false;
}
const { input, handleSubmit, completion } = useCompletion({ const { input, handleSubmit, completion } = useCompletion({
api: '/api/completion/gradingprompt', api: '/api/completion/gradingprompt',
onFinish: async () => { onFinish: async () => {
try { try {
const responseData = $completion.replace('```json', '').replace('```', ''); const responseData = $completion.replace('```json', '').replace('```', '');
let aiResponses = []; let aiResponses: {
id: number;
score: number;
explanation: string;
}[] = [];
try { try {
// Parse the modified response data as JSON // Parse the modified response data as JSON
aiResponses = JSON.parse(responseData); aiResponses = JSON.parse(responseData);
@@ -100,14 +121,14 @@
if (question_type_id !== QUESTION_TYPE.TEXTAREA) { if (question_type_id !== QUESTION_TYPE.TEXTAREA) {
const answer = data.questionAnswers.find((q) => q.question_id === id); const answer = data.questionAnswers.find((q) => q.question_id === id);
const answerPoints = answer?.answers?.length ?? 0;
reasons = { reasons = {
...reasons, ...reasons,
[id]: `${$t('course.navItem.submissions.grading_modal.questions_tried')} ${ [id]: `${$t(
answer.answers.length 'course.navItem.submissions.grading_modal.questions_tried'
} ` )} ${answerPoints} `
}; };
data.questionAnswerByPoint[id] = points / answer.answers.length; data.questionAnswerByPoint[id] = `${points / answerPoints}`;
} else if (aiResponses.length) { } else if (aiResponses.length) {
const graded = aiResponses.find((res) => res.id === id); const graded = aiResponses.find((res) => res.id === id);
@@ -116,7 +137,7 @@
[id]: `${graded?.explanation}` [id]: `${graded?.explanation}`
}; };
data.questionAnswerByPoint[id] = graded.score; data.questionAnswerByPoint[id] = `${graded?.score}`;
} }
}); });
} catch (error) { } catch (error) {
@@ -160,26 +181,51 @@
headerClass="py-2" headerClass="py-2"
labelClass="text-base font-semibold" labelClass="text-base font-semibold"
> >
<div class="w-full h-full"> <div class="w-full h-full mt-2">
<Preview {#if openDeletePrompt}
questions={Array.isArray(data.questions) <div class="w-96 mx-auto p-3 border border-gray-300 rounded-md">
? data.questions.sort((a, b) => a.order - b.order) <h1 class="dark:text-white text-lg">
: []} {$t('delete_modal.content')}
questionnaireMetaData={{ </h1>
answers: data.answers || {},
isFinished: true <div class="mt-5 flex items-center justify-between">
}} <PrimaryButton
bind:grades={data.questionAnswerByPoint} className="px-6 py-3"
bind:reasons variant={VARIANTS.OUTLINED}
bind:isGradeWithAI label={$t('delete_modal.no')}
bind:isLoading onClick={() => (openDeletePrompt = false)}
disableGrading={false} isDisabled={isDeleting}
/> />
<PrimaryButton
className="px-6 py-3"
variant={VARIANTS.OUTLINED}
label={$t('delete_modal.yes')}
onClick={handleDeleteSubmission}
isLoading={isDeleting}
/>
</div>
</div>
{:else}
<Preview
questions={Array.isArray(data.questions)
? data.questions.sort((a, b) => a.order - b.order)
: []}
questionnaireMetaData={{
answers: data.answers || {},
isFinished: true
}}
bind:grades={data.questionAnswerByPoint}
bind:reasons
bind:isGradeWithAI
bind:isLoading
disableGrading={false}
/>
{/if}
</div> </div>
<div class="ml-4 w-2/5 sticky top-0"> <div class="ml-4 mt-2 w-2/5 sticky top-0">
<div class="border border-gray-300 rounded-md"> <div class="border border-gray-300 rounded-md">
<div <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" class="flex gap-1 justify-between items-center border-b border-t-0 border-l-0 border-r-0 border-gray-300 p-3"
> >
<p class="dark:text-white font-bold text-base"> <p class="dark:text-white font-bold text-base">
{$t('course.navItem.submissions.grading_modal.details')} {$t('course.navItem.submissions.grading_modal.details')}
@@ -193,6 +239,17 @@
</span> </span>
{/if} {/if}
</p> </p>
<OverflowMenu open={openMenu} flipped>
<OverflowMenuItem
text={$t('delete_modal.label')}
on:click={() => {
openDeletePrompt = true;
openMenu = false;
}}
danger
/>
</OverflowMenu>
</div> </div>
<div class="flex items-center space-x-4 text-sm px-3 py-2"> <div class="flex items-center space-x-4 text-sm px-3 py-2">
@@ -35,7 +35,7 @@ export function getLectureNo(index, initNo = '0') {
} }
export function formatAnswers(data) { export function formatAnswers(data) {
const answers = {}; const answers: Record<string, string> = {};
const questionByIdAndName = {}; const questionByIdAndName = {};
for (const question of data.questions) { for (const question of data.questions) {
@@ -1,9 +1,9 @@
<script> <script lang="ts">
export let label = ''; export let label = '';
export let placeholder = ''; export let placeholder = '';
export let value = ''; export let value = '';
export let className = ''; export let className = '';
export let onChange = () => {}; // This is to know if element is 'dirty' export let onChange = (e?: Event) => {}; // This is to know if element is 'dirty'
</script> </script>
<label class="block {className}"> <label class="block {className}">
@@ -1,4 +1,3 @@
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'; import type { ExerciseSubmissions } from '$lib/utils/types';
@@ -87,8 +86,8 @@ export async function updateSubmission(
status_id, status_id,
total, total,
feedback feedback
}: { id?: number; status_id?: number; total?: number; feedback?: string }, }: { id: string; status_id?: number; total?: number; feedback?: string },
otherArgs?: any otherArgs?: Record<string, string>
) { ) {
const toUpdate: { const toUpdate: {
status_id?: number; status_id?: number;
@@ -106,6 +105,13 @@ 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: any, match: any) { export async function deleteSubmission(id: string) {
return supabase.from('submission').delete().match({ id });
}
export async function updateQuestionAnswer(
update: Record<string, string>,
match: Record<string, string>
) {
return supabase.from('question_answer').update(update).match(match); return supabase.from('question_answer').update(update).match(match);
} }
@@ -497,7 +497,10 @@
"accept_grade": "Akzeptieren", "accept_grade": "Akzeptieren",
"reject_grade": "Ablehnen", "reject_grade": "Ablehnen",
"add_comment_placeholder": "Hinterlasse einen Kommentar", "add_comment_placeholder": "Hinterlasse einen Kommentar",
"questions_tried": "Diese Punktzahl wurde aufgrund der Gesamtzahl der Versuche vergeben:" "questions_tried": "Diese Punktzahl wurde aufgrund der Gesamtzahl der Versuche vergeben:",
"delete_prompt": "Löschen",
"delete_error": "Die Übermittlung konnte nicht gelöscht werden",
"delete_success": "Einreichung erfolgreich gelöscht"
} }
}, },
"attendance": { "attendance": {
@@ -735,12 +738,6 @@
"materials": "Materialien", "materials": "Materialien",
"exercises": "Übungen" "exercises": "Übungen"
}, },
"delete_lesson": {
"modal_heading": "Löschen",
"content": "Sind Sie sicher, dass Sie löschen möchten:",
"no": "NEIN",
"yes": "Ja"
},
"add_lesson": { "add_lesson": {
"button_title": "Hinzufügen", "button_title": "Hinzufügen",
"modal_heading": "Neue Lektion hinzufügen", "modal_heading": "Neue Lektion hinzufügen",
@@ -1390,5 +1387,11 @@
"empty_heading": "Kein Kurs verfügbar", "empty_heading": "Kein Kurs verfügbar",
"empty_description": "Derzeit sind keine veröffentlichten Kurse verfügbar", "empty_description": "Derzeit sind keine veröffentlichten Kurse verfügbar",
"heading": "Entdecken Sie unsere Kurse" "heading": "Entdecken Sie unsere Kurse"
},
"delete_modal": {
"label": "Löschen",
"content": "Bist du sicher?",
"no": "NEIN",
"yes": "Ja"
} }
} }
@@ -10,6 +10,13 @@
"snackbar_error": "error while sending code" "snackbar_error": "error while sending code"
}, },
"delete_modal": {
"label": "Delete",
"content": "Are you sure?",
"no": "No",
"yes": "Yes"
},
"onboarding": { "onboarding": {
"sell": "Sell courses online", "sell": "Sell courses online",
"teach": "Teach my current students online", "teach": "Teach my current students online",
@@ -514,12 +521,6 @@
"edit": "Edit", "edit": "Edit",
"delete": "Delete" "delete": "Delete"
}, },
"delete_lesson": {
"modal_heading": "Delete",
"content": "Are you sure you want to delete:",
"no": "No",
"yes": "Yes"
},
"join_lesson": "Join lesson", "join_lesson": "Join lesson",
"no_link": "No link", "no_link": "No link",
"no_tutor": "No tutor added", "no_tutor": "No tutor added",
@@ -789,7 +790,10 @@
"submit_grades": "Submit Grades", "submit_grades": "Submit Grades",
"accept_grade": "Accept", "accept_grade": "Accept",
"reject_grade": "Reject", "reject_grade": "Reject",
"questions_tried": "Allocated this score because of total tries:" "questions_tried": "Allocated this score because of total tries:",
"delete_prompt": "Delete",
"delete_error": "Failed to delete submission",
"delete_success": "Successfully deleted submission"
} }
}, },
@@ -497,7 +497,10 @@
"accept_grade": "Aceptar", "accept_grade": "Aceptar",
"reject_grade": "Rechazar", "reject_grade": "Rechazar",
"add_comment_placeholder": "Deja un comentario", "add_comment_placeholder": "Deja un comentario",
"questions_tried": "Se asignó esta puntuación debido al total de intentos:" "questions_tried": "Se asignó esta puntuación debido al total de intentos:",
"delete_prompt": "Borrar",
"delete_error": "No se pudo eliminar el envío",
"delete_success": "Envío eliminado correctamente"
} }
}, },
"attendance": { "attendance": {
@@ -735,12 +738,6 @@
"materials": "Materiales", "materials": "Materiales",
"exercises": "Ejercicios" "exercises": "Ejercicios"
}, },
"delete_lesson": {
"modal_heading": "Borrar",
"content": "Estas seguro que quieres borrarlo:",
"no": "No",
"yes": "Sí"
},
"add_lesson": { "add_lesson": {
"button_title": "Agregar", "button_title": "Agregar",
"modal_heading": "Agregar nueva lección", "modal_heading": "Agregar nueva lección",
@@ -1390,5 +1387,11 @@
"empty_heading": "No hay curso disponible", "empty_heading": "No hay curso disponible",
"empty_description": "Actualmente no hay ningún curso publicado disponible", "empty_description": "Actualmente no hay ningún curso publicado disponible",
"heading": "Explora nuestros cursos" "heading": "Explora nuestros cursos"
},
"delete_modal": {
"label": "Borrar",
"content": "¿Está seguro?",
"no": "No",
"yes": "Sí"
} }
} }
@@ -497,7 +497,10 @@
"accept_grade": "Accepter", "accept_grade": "Accepter",
"reject_grade": "Rejeter", "reject_grade": "Rejeter",
"add_comment_placeholder": "laissez un commentaire", "add_comment_placeholder": "laissez un commentaire",
"questions_tried": "Attribué ce score en raison du nombre total d'essais :" "questions_tried": "Attribué ce score en raison du nombre total d'essais :",
"delete_prompt": "Supprimer",
"delete_error": "Échec de la suppression de la soumission",
"delete_success": "Soumission supprimée avec succès"
} }
}, },
"attendance": { "attendance": {
@@ -735,12 +738,6 @@
"materials": "Matériaux", "materials": "Matériaux",
"exercises": "Des exercices" "exercises": "Des exercices"
}, },
"delete_lesson": {
"modal_heading": "Supprimer",
"content": "Etes-vous sûr que vous voulez supprimer:",
"no": "Non",
"yes": "Oui"
},
"add_lesson": { "add_lesson": {
"button_title": "Ajouter", "button_title": "Ajouter",
"modal_heading": "Ajouter une nouvelle leçon", "modal_heading": "Ajouter une nouvelle leçon",
@@ -1390,5 +1387,11 @@
"empty_heading": "Aucun cours disponible", "empty_heading": "Aucun cours disponible",
"empty_description": "Il n'y a actuellement aucun cours publié disponible", "empty_description": "Il n'y a actuellement aucun cours publié disponible",
"heading": "Découvrez nos cours" "heading": "Découvrez nos cours"
},
"delete_modal": {
"label": "Supprimer",
"content": "Es-tu sûr?",
"no": "Non",
"yes": "Oui"
} }
} }
@@ -497,7 +497,10 @@
"accept_grade": "स्वीकार करना", "accept_grade": "स्वीकार करना",
"reject_grade": "अस्वीकार करना", "reject_grade": "अस्वीकार करना",
"add_comment_placeholder": "एक टिप्पणी छोड़ें", "add_comment_placeholder": "एक टिप्पणी छोड़ें",
"questions_tried": "कुल प्रयासों के कारण यह स्कोर आवंटित किया गया:" "questions_tried": "कुल प्रयासों के कारण यह स्कोर आवंटित किया गया:",
"delete_prompt": "मिटाना",
"delete_error": "सबमिशन हटाने में विफल",
"delete_success": "सबमिशन सफलतापूर्वक हटा दिया गया"
} }
}, },
"attendance": { "attendance": {
@@ -735,12 +738,6 @@
"materials": "सामग्री", "materials": "सामग्री",
"exercises": "अभ्यास" "exercises": "अभ्यास"
}, },
"delete_lesson": {
"modal_heading": "मिटाना",
"content": "क्या आप आश्वस्त है कि आपको डिलीट करना है:",
"no": "नहीं",
"yes": "हाँ"
},
"add_lesson": { "add_lesson": {
"button_title": "जोड़ना", "button_title": "जोड़ना",
"modal_heading": "नया पाठ जोड़ें", "modal_heading": "नया पाठ जोड़ें",
@@ -1390,5 +1387,11 @@
"empty_heading": "कोई पाठ्यक्रम उपलब्ध नहीं है", "empty_heading": "कोई पाठ्यक्रम उपलब्ध नहीं है",
"empty_description": "वर्तमान में कोई प्रकाशित पाठ्यक्रम उपलब्ध नहीं है", "empty_description": "वर्तमान में कोई प्रकाशित पाठ्यक्रम उपलब्ध नहीं है",
"heading": "हमारे पाठ्यक्रमों का अन्वेषण करें" "heading": "हमारे पाठ्यक्रमों का अन्वेषण करें"
},
"delete_modal": {
"label": "मिटाना",
"content": "क्या आपको यकीन है?",
"no": "नहीं",
"yes": "हाँ"
} }
} }
@@ -497,7 +497,10 @@
"accept_grade": "Aceitar", "accept_grade": "Aceitar",
"reject_grade": "Rejeitar", "reject_grade": "Rejeitar",
"add_comment_placeholder": "Deixe um comentário", "add_comment_placeholder": "Deixe um comentário",
"questions_tried": "Alocada esta pontuação por causa do total de tentativas:" "questions_tried": "Alocada esta pontuação por causa do total de tentativas:",
"delete_prompt": "Excluir",
"delete_error": "Falha ao excluir o envio",
"delete_success": "Envio excluído com sucesso"
} }
}, },
"attendance": { "attendance": {
@@ -735,12 +738,6 @@
"materials": "Materiais", "materials": "Materiais",
"exercises": "Exercícios" "exercises": "Exercícios"
}, },
"delete_lesson": {
"modal_heading": "Excluir",
"content": "Tem certeza de que deseja excluir:",
"no": "Não",
"yes": "Sim"
},
"add_lesson": { "add_lesson": {
"button_title": "Adicionar", "button_title": "Adicionar",
"modal_heading": "Adicionar nova lição", "modal_heading": "Adicionar nova lição",
@@ -1390,5 +1387,11 @@
"empty_heading": "Nenhum curso disponível", "empty_heading": "Nenhum curso disponível",
"empty_description": "Atualmente não há nenhum curso publicado disponível", "empty_description": "Atualmente não há nenhum curso publicado disponível",
"heading": "Explore nossos cursos" "heading": "Explore nossos cursos"
},
"delete_modal": {
"label": "Excluir",
"content": "Tem certeza?",
"no": "Não",
"yes": "Sim"
} }
} }
@@ -497,7 +497,10 @@
"accept_grade": "Принимать", "accept_grade": "Принимать",
"reject_grade": "Отклонять", "reject_grade": "Отклонять",
"add_comment_placeholder": "Оставить комментарий", "add_comment_placeholder": "Оставить комментарий",
"questions_tried": "Присвоен этот балл из-за общего количества попыток:" "questions_tried": "Присвоен этот балл из-за общего количества попыток:",
"delete_prompt": "Удалить",
"delete_error": "Не удалось удалить отправку",
"delete_success": "Отправка успешно удалена"
} }
}, },
"attendance": { "attendance": {
@@ -735,12 +738,6 @@
"materials": "Материалы", "materials": "Материалы",
"exercises": "Упражнения" "exercises": "Упражнения"
}, },
"delete_lesson": {
"modal_heading": "Удалить",
"content": "Вы уверены, что хотите удалить:",
"no": "Нет",
"yes": "Да"
},
"add_lesson": { "add_lesson": {
"button_title": "Добавлять", "button_title": "Добавлять",
"modal_heading": "Добавить новый урок", "modal_heading": "Добавить новый урок",
@@ -1390,5 +1387,11 @@
"empty_heading": "Нет доступных курсов", "empty_heading": "Нет доступных курсов",
"empty_description": "На данный момент нет доступных опубликованных курсов", "empty_description": "На данный момент нет доступных опубликованных курсов",
"heading": "Ознакомьтесь с нашими курсами" "heading": "Ознакомьтесь с нашими курсами"
},
"delete_modal": {
"label": "Удалить",
"content": "Вы уверены?",
"no": "Нет",
"yes": "Да"
} }
} }
@@ -497,7 +497,10 @@
"accept_grade": "Chấp nhận", "accept_grade": "Chấp nhận",
"reject_grade": "Từ chối", "reject_grade": "Từ chối",
"add_comment_placeholder": "Để lại một bình luận", "add_comment_placeholder": "Để lại một bình luận",
"questions_tried": "Phân bổ số điểm này dựa trên tổng số lần thử:" "questions_tried": "Phân bổ số điểm này dựa trên tổng số lần thử:",
"delete_prompt": "Xóa bỏ",
"delete_error": "Không thể xóa bài nộp",
"delete_success": "Đã xóa bài nộp thành công"
} }
}, },
"attendance": { "attendance": {
@@ -735,12 +738,6 @@
"materials": "Nguyên vật liệu", "materials": "Nguyên vật liệu",
"exercises": "Bài tập" "exercises": "Bài tập"
}, },
"delete_lesson": {
"modal_heading": "Xóa bỏ",
"content": "Bạn có chắc chắn muốn xóa:",
"no": "KHÔNG",
"yes": "Đúng"
},
"add_lesson": { "add_lesson": {
"button_title": "Thêm vào", "button_title": "Thêm vào",
"modal_heading": "Thêm bài học mới", "modal_heading": "Thêm bài học mới",
@@ -1390,5 +1387,11 @@
"empty_heading": "Không có khóa học nào", "empty_heading": "Không có khóa học nào",
"empty_description": "Hiện tại chưa có khóa học nào được xuất bản", "empty_description": "Hiện tại chưa có khóa học nào được xuất bản",
"heading": "Khám phá các khóa học của chúng tôi" "heading": "Khám phá các khóa học của chúng tôi"
},
"delete_modal": {
"label": "Xóa bỏ",
"content": "Bạn có chắc không?",
"no": "KHÔNG",
"yes": "Đúng"
} }
} }
@@ -0,0 +1,48 @@
import { Profile } from '.';
export interface SubmissionItem {
id: string;
statusId: number;
isEarly: boolean;
submittedAt: string;
exercise: {
id: string;
title: string;
};
answers: string;
student: Partial<Profile>;
lesson: {
id: string;
title: string;
};
}
export interface SubmissionSection {
id: number;
title: string;
value: number;
items: SubmissionItem[] | [];
}
export interface SubmissionIdData {
id: string;
statusId: number;
title: string;
isEarly: boolean;
questionAnswerByPoint: Record<string, string>;
answers: Record<string, string>;
questions: {
id: number;
title: string;
order: number;
points: number;
question_type_id: number;
}[];
questionAnswers: {
open_answer: string;
question_id: number;
answers: [];
}[];
student?: Partial<Profile>;
feedback: string;
}
@@ -15,7 +15,7 @@
import PageBody from '$lib/components/PageBody/index.svelte'; import PageBody from '$lib/components/PageBody/index.svelte';
import Box from '$lib/components/Box/index.svelte'; import Box from '$lib/components/Box/index.svelte';
import { course, group } from '$lib/components/Course/store'; import { course, group } from '$lib/components/Course/store';
import { getLectureNo } from '$lib/components/Course/function'; import { getLectureNo } from '$lib/components/Course/function.js';
import { lessons } from '$lib/components/Course/components/Lesson/store/lessons'; import { lessons } from '$lib/components/Course/components/Lesson/store/lessons';
import { ROLE } from '$lib/utils/constants/roles'; import { ROLE } from '$lib/utils/constants/roles';
import { takeAttendance } from '$lib/utils/services/attendance'; import { takeAttendance } from '$lib/utils/services/attendance';
@@ -117,7 +117,7 @@
function searchStudents(query: string, _students: GroupPerson[]) { function searchStudents(query: string, _students: GroupPerson[]) {
const lowercaseQuery = query.toLowerCase(); const lowercaseQuery = query.toLowerCase();
return _students.filter((student) => return _students.filter((student) =>
student.profile.fullname.toLowerCase().includes(lowercaseQuery) student.profile?.fullname?.toLowerCase()?.includes(lowercaseQuery)
); );
} }
@@ -272,7 +272,9 @@
<DateField <DateField
value={formatDate(lesson.lesson_at)} value={formatDate(lesson.lesson_at)}
className="p-2 my-2 rounded-md sm:w-[179px] dark:bg-neutral-800 dark:text-white" className="p-2 my-2 rounded-md sm:w-[179px] dark:bg-neutral-800 dark:text-white"
onChange={(e) => (lesson.lesson_at = e.target.value)} onChange={(e) => {
lesson.lesson_at = e?.target?.value;
}}
/> />
{:else} {:else}
<div class="mb-2 flex"> <div class="mb-2 flex">
@@ -19,7 +19,7 @@
import PageBody from '$lib/components/PageBody/index.svelte'; import PageBody from '$lib/components/PageBody/index.svelte';
import Materials from '$lib/components/Course/components/Lesson/Materials/index.svelte'; import Materials from '$lib/components/Course/components/Lesson/Materials/index.svelte';
import Exercises from '$lib/components/Course/components/Lesson/Exercises/index.svelte'; import Exercises from '$lib/components/Course/components/Lesson/Exercises/index.svelte';
import LanguageLessonVersionHistory from '$lib/components/Course/components/Lesson/LanguageLessonVersionHistory.svelte'; import LessonVersionHistory from '$lib/components/Course/components/Lesson/LessonVersionHistory.svelte';
import MODES from '$lib/utils/constants/mode'; import MODES from '$lib/utils/constants/mode';
import { course } from '$lib/components/Course/store'; import { course } from '$lib/components/Course/store';
import Download from 'carbon-icons-svelte/lib/Download.svelte'; import Download from 'carbon-icons-svelte/lib/Download.svelte';
@@ -458,7 +458,7 @@
<!-- Version Control Preview --> <!-- Version Control Preview -->
{#if isVersionDrawerOpen && window.innerWidth >= 1024} {#if isVersionDrawerOpen && window.innerWidth >= 1024}
<LanguageLessonVersionHistory <LessonVersionHistory
open={isVersionDrawerOpen} open={isVersionDrawerOpen}
on:close={() => (isVersionDrawerOpen = false)} on:close={() => (isVersionDrawerOpen = false)}
on:restore={refetchDataAfterVersionRestore} on:restore={refetchDataAfterVersionRestore}
@@ -10,7 +10,7 @@
import { group } from '$lib/components/Course/store'; import { group } from '$lib/components/Course/store';
import { lessons } from '$lib/components/Course/components/Lesson/store/lessons'; import { lessons } from '$lib/components/Course/components/Lesson/store/lessons';
import { fetchExercisesByMarks } from '$lib/utils/services/courses'; import { fetchExercisesByMarks } from '$lib/utils/services/courses';
import { getLectureNo } from '$lib/components/Course/function'; import { getLectureNo } from '$lib/components/Course/function.js';
import { fetchMarks } from '$lib/utils/services/marks'; import { fetchMarks } from '$lib/utils/services/marks';
import { profile } from '$lib/utils/store/user'; import { profile } from '$lib/utils/store/user';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
@@ -13,9 +13,10 @@
import { import {
fetchSubmissions, fetchSubmissions,
updateSubmission, updateSubmission,
updateQuestionAnswer updateQuestionAnswer,
} from '$lib/utils/services/submissions/index'; deleteSubmission
import { formatAnswers } from '$lib/components/Course/function'; } from '$lib/utils/services/submissions';
import { formatAnswers } from '$lib/components/Course/function.js';
import { snackbar } from '$lib/components/Snackbar/store'; import { snackbar } from '$lib/components/Snackbar/store';
import isSubmissionEarly from '$lib/utils/functions/isSubmissionEarly'; import isSubmissionEarly from '$lib/utils/functions/isSubmissionEarly';
import formatDate from '$lib/utils/functions/formatDate'; import formatDate from '$lib/utils/functions/formatDate';
@@ -26,29 +27,11 @@
import { currentOrg, currentOrgDomain } from '$lib/utils/store/org'; import { currentOrg, currentOrgDomain } from '$lib/utils/store/org';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import { t } from '$lib/utils/functions/translations'; import { t } from '$lib/utils/functions/translations';
import type {
type items = { SubmissionIdData,
id: number; SubmissionItem,
statusId: number; SubmissionSection
isEarly: boolean; } from '$lib/utils/types/submission';
submittedAt: string;
exercise: {
id: string;
title: string;
};
answers: string;
student: any;
lesson: {
id: string | any;
title: string | any;
};
};
type sectionType = {
id: number;
title: string;
value: number;
items: items[] | [];
};
export let data; export let data;
const { courseId } = data; const { courseId } = data;
@@ -58,8 +41,8 @@
let lessonDetails: { id: string; title: string }; let lessonDetails: { id: string; title: string };
let totalMark = 0; let totalMark = 0;
let maxMark = 0; let maxMark = 0;
let submissionIdData: { [key: number]: any } = {}; let submissionIdData: { [key: number]: SubmissionIdData } = {};
let submissionId: string | number | null; let submissionId: string;
let openExercise = false; let openExercise = false;
let isGradeWithAI = false; let isGradeWithAI = false;
@@ -69,7 +52,7 @@
3: $t('course.navItem.submissions.submission_status.graded') 3: $t('course.navItem.submissions.submission_status.graded')
}; };
let sections: sectionType[] = [ let sections: SubmissionSection[] = [
{ {
id: 1, id: 1,
title: $t('course.navItem.submissions.submission_status.submitted'), title: $t('course.navItem.submissions.submission_status.submitted'),
@@ -104,16 +87,16 @@
totalMark = calculateTotal(submissionData?.questionAnswerByPoint); totalMark = calculateTotal(submissionData?.questionAnswerByPoint);
const { fullname, email } = submissionData?.student; const { fullname, email } = submissionData?.student;
const { title, status_id }: { title: string; status_id: number } = submissionData; const { title, statusId }: { title: string; statusId: number } = submissionData;
const exerciseLink = `${$currentOrgDomain}/courses/${courseId}/lessons/${lessonDetails.id}/exercises/${exerciseDetails.id}`; const exerciseLink = `${$currentOrgDomain}/courses/${courseId}/lessons/${lessonDetails.id}/exercises/${exerciseDetails.id}`;
const content = ` const content = `
<p>Hello ${fullname},</p> <p>Hello ${fullname},</p>
<p>The status of your submitted exercise on <strong>${title}</strong> has been updated to ${ <p>The status of your submitted exercise on <strong>${title}</strong> has been updated to ${
submissionStatus[status_id] submissionStatus[statusId]
}</p> }</p>
${ ${
status_id == 3 statusId == 3
? `<p>Your score was ${totalMark}/${maxMark}</p> ? `<p>Your score was ${totalMark}/${maxMark}</p>
<a class="button" href="${exerciseLink}">View your Result</a> <a class="button" href="${exerciseLink}">View your Result</a>
` `
@@ -133,9 +116,9 @@
function handleItemFinalize( function handleItemFinalize(
columnIdx: number, columnIdx: number,
newItems: { map: (arg0: (item: items) => items) => items[] } newItems: { map: (arg0: (item: SubmissionItem) => SubmissionItem) => SubmissionItem[] }
) { ) {
let itemToWithNewStatus: items | undefined; let itemToWithNewStatus: SubmissionItem | undefined;
const { id } = sections[columnIdx]; const { id } = sections[columnIdx];
@@ -154,7 +137,7 @@
// Update key mapping for each submission also // Update key mapping for each submission also
submissionIdData[itemToWithNewStatus.id] = { submissionIdData[itemToWithNewStatus.id] = {
...submissionIdData[itemToWithNewStatus.id], ...submissionIdData[itemToWithNewStatus.id],
status_id: itemToWithNewStatus.statusId statusId: itemToWithNewStatus.statusId
}; };
notifyStudent(submissionIdData[itemToWithNewStatus.id]); notifyStudent(submissionIdData[itemToWithNewStatus.id]);
@@ -188,12 +171,12 @@
nextStatusId, nextStatusId,
total total
}: { }: {
submissionId: number; submissionId: string;
prevStatusId: number; prevStatusId: number;
nextStatusId: number; nextStatusId: number;
total: number; total: number;
}) { }) {
let itemToWithNewStatus: items | undefined; let itemToWithNewStatus: SubmissionItem | undefined;
// Remove from current column // Remove from current column
const { items } = sections[prevStatusId - 1]; const { items } = sections[prevStatusId - 1];
@@ -218,7 +201,7 @@
// Update key mapping for each submission also // Update key mapping for each submission also
submissionIdData[itemToWithNewStatus.id] = { submissionIdData[itemToWithNewStatus.id] = {
...submissionIdData[itemToWithNewStatus.id], ...submissionIdData[itemToWithNewStatus.id],
status_id: itemToWithNewStatus.statusId statusId: itemToWithNewStatus.statusId
}; };
notifyStudent(submissionIdData[itemToWithNewStatus.id]); notifyStudent(submissionIdData[itemToWithNewStatus.id]);
@@ -231,6 +214,28 @@
} }
} }
async function handleDeleteSubmission(id: string, statusId: number) {
const { items } = sections[statusId - 1];
sections[statusId - 1].items = items?.filter((item) => {
return item.id === id ? false : true;
});
submissionIdData[id] = undefined;
const { error } = await deleteSubmission(id);
if (error) {
console.error('Error deleting submission', error);
snackbar.error('course.navItem.submissions.grading_modal.delete_error');
return;
}
snackbar.success('course.navItem.submissions.grading_modal.delete_success');
handleModalClose();
}
async function handleSave(submission: { async function handleSave(submission: {
questionAnswerByPoint: any; questionAnswerByPoint: any;
questionAnswers: any; questionAnswers: any;
@@ -262,25 +267,19 @@
await Promise.all(updates); await Promise.all(updates);
} }
$: {
const query = new URLSearchParams($page.url.search);
submissionId = query.get('submissionId');
openExercise = !!submissionId && submissionIdData[submissionId];
}
async function firstRender(courseId: string) { async function firstRender(courseId: string) {
const { data: submissions } = await fetchSubmissions(courseId); const { data: submissions } = await fetchSubmissions(courseId);
const sectionById: { [key: number]: sectionType[] } = {}; const sectionById: { [key: number]: SubmissionSection[] } = {};
if (submissions) { if (submissions) {
for (const submission of submissions) { for (const submission of submissions) {
const { id, created_at, exercise, course, answers, groupmember, status_id, feedback } = const { id, created_at, answers, groupmember, feedback, exercise } = submission;
submission;
const isEarly = isSubmissionEarly(created_at, exercise.due_by); const isEarly = isSubmissionEarly(created_at, exercise.due_by);
const submissionItem = { const submissionItem = {
id, id,
statusId: status_id, statusId: submission.status_id,
isEarly, isEarly,
feedback, feedback,
submittedAt: formatDate(created_at), submittedAt: formatDate(created_at),
@@ -295,14 +294,17 @@
title: exercise.lesson.title title: exercise.lesson.title
} }
}; };
exerciseDetails = { id: submissionItem.exercise.id, title: submissionItem.exercise.title }; exerciseDetails = { id: submissionItem.exercise.id, title: submissionItem.exercise.title };
lessonDetails = { lessonDetails = {
id: submissionItem.lesson.id, id: submissionItem.lesson.id,
title: submissionItem.lesson.title title: submissionItem.lesson.title
}; };
submissionIdData[id] = { submissionIdData[id] = {
id, id,
status_id, statusId: submissionItem.statusId,
feedback, feedback,
isEarly, isEarly,
title: exercise.title, title: exercise.title,
@@ -317,10 +319,10 @@
}, {}) }, {})
}; };
if (Array.isArray(sectionById[status_id])) { if (Array.isArray(sectionById[submission.status_id])) {
sectionById[status_id].push(submissionItem); sectionById[submission.status_id].push(submissionItem);
} else { } else {
sectionById[status_id] = [submissionItem]; sectionById[submission.status_id] = [submissionItem];
} }
} }
} }
@@ -333,6 +335,11 @@
} }
$: browser && $course.id && firstRender($course.id); $: browser && $course.id && firstRender($course.id);
$: {
const query = new URLSearchParams($page.url.search);
submissionId = query.get('submissionId') ?? '';
openExercise = !!submissionId && submissionIdData[submissionId];
}
</script> </script>
<MarkExerciseModal <MarkExerciseModal
@@ -341,7 +348,7 @@
data={submissionIdData[submissionId] || {}} data={submissionIdData[submissionId] || {}}
{handleSave} {handleSave}
{updateStatus} {updateStatus}
{submissionId} deleteSubmission={handleDeleteSubmission}
bind:isGradeWithAI bind:isGradeWithAI
/> />
@@ -403,7 +410,6 @@
> >
<p class="dark:text-white text-grey text-sm"> <p class="dark:text-white text-grey text-sm">
#{item.lesson.title} #{item.lesson.title}
{`${item.tutor ? 'created by' + item.tutor.name : ''}`}
</p> </p>
</a> </a>
<p class="dark:text-white text-gray-500 text-xs"> <p class="dark:text-white text-gray-500 text-xs">