CLASSROOMIO-T-154: Notify tutors when a student submits an exercise
CLASSROOMIO-T-154: Fix textarea grade issue CLASSROOMIO-T-154: Get it to work properly Merge branch 'CLASSROOMIO-T-154' of https://gitlab.com/thenextgreat/classroomio into CLASSROOMIO-T-154 CLASSROOMIO-T-154: PR fixes CLASSROOMIO-T-154:Updated the code Merge branch 'main' into CLASSROOMIO-T-154 CLASSROOMIO-T-154:Notify tutors when a student submits an exercise Co-authored-by: Ifeanyi Nwosu <ifeanyi@digdippa.com> Co-authored-by: Rotimi Best <rotimiibitoyeemma@gmail.com> Merge-request: CLASSROOMIO-MR-109 Merged-by: Best Admin <best@digdippa.com>
This commit is contained in:
committed by
Space Cloud
co-authored by
Rotimi Best
parent
91f28a345f
commit
0f2d60534d
@@ -88,23 +88,6 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4 w-2/5 sticky top-0">
|
<div class="ml-4 w-2/5 sticky top-0">
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<Select
|
|
||||||
bind:value={status}
|
|
||||||
options={SELECTABLE_STATUS}
|
|
||||||
selectClassName={getStatusColor(status)}
|
|
||||||
onChange={handleStatusChange}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PrimaryButton
|
|
||||||
onClick={() => {
|
|
||||||
handleSave(data);
|
|
||||||
}}
|
|
||||||
label="Save"
|
|
||||||
variant={VARIANTS.CONTAINED_SUCCESS}
|
|
||||||
className="py-3 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 dark:bg-neutral-800 border-b border-t-0 border-l-0 border-r-0 border-gray-300 p-3"
|
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"
|
||||||
@@ -141,6 +124,26 @@
|
|||||||
<p class="dark:text-white ml-2 text-sm">{data.student.fullname}</p>
|
<p class="dark:text-white ml-2 text-sm">{data.student.fullname}</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center text-sm p-3">
|
||||||
|
<p class="dark:text-white font-bold w-2/5">Status</p>
|
||||||
|
<Select
|
||||||
|
bind:value={status}
|
||||||
|
options={SELECTABLE_STATUS}
|
||||||
|
selectClassName={getStatusColor(status)}
|
||||||
|
onChange={handleStatusChange}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-center w-full p-3">
|
||||||
|
<PrimaryButton
|
||||||
|
onClick={() => {
|
||||||
|
handleSave(data);
|
||||||
|
}}
|
||||||
|
label="Submit Grades"
|
||||||
|
variant={VARIANTS.CONTAINED_SUCCESS}
|
||||||
|
className="py-3 px-8 w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<!-- <div class="flex items-center text-sm p-3">
|
<!-- <div class="flex items-center text-sm p-3">
|
||||||
<p class="dark:text-white font-bold w-1/2">Teacher</p>
|
<p class="dark:text-white font-bold w-1/2">Teacher</p>
|
||||||
<p class="dark:text-white">rotimi-best</p>
|
<p class="dark:text-white">rotimi-best</p>
|
||||||
|
|||||||
@@ -19,9 +19,14 @@
|
|||||||
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 { currentOrg } from '$lib/utils/store/org';
|
||||||
|
import {
|
||||||
|
NOTIFICATION_NAME,
|
||||||
|
triggerSendEmail
|
||||||
|
} from '$lib/utils/services/notification/notification';
|
||||||
|
import { lesson } from '../store/lessons';
|
||||||
|
|
||||||
export let preview = false;
|
export let preview;
|
||||||
|
|
||||||
export let exerciseId = '';
|
export let exerciseId = '';
|
||||||
|
|
||||||
let currentQuestion = {};
|
let currentQuestion = {};
|
||||||
@@ -33,10 +38,40 @@
|
|||||||
$questionnaireMetaData.currentQuestionIndex += 1;
|
$questionnaireMetaData.currentQuestionIndex += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getStudent = (people, profileId) => {
|
||||||
|
return people.find((person) => person.profile_id === profileId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendEmail = () => {
|
||||||
|
const student = getStudent($group.students, $profile.id);
|
||||||
|
const teacherFullname = $group.tutors[0]?.fullname;
|
||||||
|
const teacherEmail = $group.tutors[0]?.email;
|
||||||
|
|
||||||
|
if (!student || !teacherFullname || !teacherEmail) return;
|
||||||
|
|
||||||
|
const baseUrl = `https://app.classroomio.com/courses/${$course.id}`;
|
||||||
|
const exerciseLink = `${baseUrl}/lessons/${$lesson.id}/exercises/${exerciseId}`;
|
||||||
|
const submissionLink = `${baseUrl}/submissions`;
|
||||||
|
const content = `
|
||||||
|
<p>Hello ${teacherFullname},</p>
|
||||||
|
<p>A student ${student.profile.fullname} just submitted an exercise <a href=${exerciseLink}>${$questionnaire.title}</a>
|
||||||
|
<p>You can get started grading by clicking "Open Submissions"</p>
|
||||||
|
<div>
|
||||||
|
<a class="button" href=${submissionLink}>Open Submissions</a>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
triggerSendEmail(NOTIFICATION_NAME.EXERCISE_SUBMISSION_UPDATE, {
|
||||||
|
to: teacherEmail,
|
||||||
|
content,
|
||||||
|
orgName: $currentOrg?.name,
|
||||||
|
exerciseTitle: $questionnaire.title
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
function onSubmit(id, value, moveToNextQuestion = false) {
|
function onSubmit(id, value, moveToNextQuestion = false) {
|
||||||
const { answers } = $questionnaireMetaData;
|
const { answers } = $questionnaireMetaData;
|
||||||
const { questions } = $questionnaire;
|
const { questions } = $questionnaire;
|
||||||
|
|
||||||
const prevAnswer = answers[id] || [];
|
const prevAnswer = answers[id] || [];
|
||||||
const formattedAnswer =
|
const formattedAnswer =
|
||||||
typeof value === 'string' ? value : removeDuplicate([...prevAnswer, ...(value || [])]);
|
typeof value === 'string' ? value : removeDuplicate([...prevAnswer, ...(value || [])]);
|
||||||
@@ -69,6 +104,7 @@
|
|||||||
$course.id,
|
$course.id,
|
||||||
getGroupMemberId($group.people, $profile.id)
|
getGroupMemberId($group.people, $profile.id)
|
||||||
);
|
);
|
||||||
|
sendEmail();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +189,6 @@
|
|||||||
$: $questionnaireMetaData.progressValue = getProgressValue(
|
$: $questionnaireMetaData.progressValue = getProgressValue(
|
||||||
$questionnaireMetaData.currentQuestionIndex
|
$questionnaireMetaData.currentQuestionIndex
|
||||||
);
|
);
|
||||||
|
|
||||||
$: checkForSubmission($group.people, $profile.id, $course.id);
|
$: checkForSubmission($group.people, $profile.id, $course.id);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -69,10 +69,12 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<PageBody bind:isPageNavHidden={isStudent} padding="px-4 overflow-x-hidden">
|
<PageBody bind:isPageNavHidden={isStudent} padding="px-4 overflow-x-hidden">
|
||||||
<ContentSwitcher bind:selectedIndex class="mb-2">
|
<RoleBasedSecurity allowedRoles={[1, 2]}>
|
||||||
<Switch text={`Questions ${0}`} />
|
<ContentSwitcher bind:selectedIndex class="mb-2">
|
||||||
<Switch text="Analytics" />
|
<Switch text="Questions" />
|
||||||
</ContentSwitcher>
|
<Switch text="Analytics" />
|
||||||
|
</ContentSwitcher>
|
||||||
|
</RoleBasedSecurity>
|
||||||
|
|
||||||
{#if selectedIndex === 0}
|
{#if selectedIndex === 0}
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleCursor(event) {
|
function handleCursor(event) {
|
||||||
if (!resize) {
|
if (!resize && sidebarRef) {
|
||||||
const isNearLeftBorder = event.clientX - sidebarRef.getBoundingClientRect().left < 8;
|
const isNearLeftBorder = event.clientX - sidebarRef.getBoundingClientRect().left < 8;
|
||||||
const isNearRightBorder = sidebarRef.getBoundingClientRect().right - event.clientX < 8;
|
const isNearRightBorder = sidebarRef.getBoundingClientRect().right - event.clientX < 8;
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function startDragging(event) {
|
function startDragging(event) {
|
||||||
if (event.button === 0) {
|
if (event.button === 0 && sidebarRef) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const isNearRightBorder = sidebarRef.getBoundingClientRect().right - event.clientX < 8;
|
const isNearRightBorder = sidebarRef.getBoundingClientRect().right - event.clientX < 8;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import CodeSnippet from '$lib/components/CodeSnippet/index.svelte';
|
import CodeSnippet from '$lib/components/CodeSnippet/index.svelte';
|
||||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||||
import Checkbox from '$lib/components/Form/Checkbox.svelte';
|
import Checkbox from '$lib/components/Form/Checkbox.svelte';
|
||||||
import TextField from '$lib/components/Form/TextField.svelte';
|
|
||||||
import HtmlRender from '$lib/components/HTMLRender/HTMLRender.svelte';
|
import HtmlRender from '$lib/components/HTMLRender/HTMLRender.svelte';
|
||||||
|
import Grade from '$lib/components/Question/Grade.svelte';
|
||||||
|
|
||||||
export let title = '';
|
export let title = '';
|
||||||
export let code;
|
export let code;
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
export let isPreview = false;
|
export let isPreview = false;
|
||||||
export let nextButtonProps = {};
|
export let nextButtonProps = {};
|
||||||
export let grade;
|
export let grade: number | undefined;
|
||||||
export let gradeMax = 0;
|
export let gradeMax = 0;
|
||||||
export let disableGrading = false;
|
export let disableGrading = false;
|
||||||
|
|
||||||
@@ -63,21 +63,10 @@
|
|||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<HtmlRender className="mt-4">
|
<HtmlRender className="mt-4">
|
||||||
<svelte:fragment slot="content">
|
<svelte:fragment slot="content">
|
||||||
<h3 class={!isNaN(grade) ? 'w-3/4' : ''}>{title}</h3>
|
<h3 class={typeof grade === 'number' ? 'w-3/4' : ''}>{title}</h3>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</HtmlRender>
|
</HtmlRender>
|
||||||
{#if !isNaN(grade)}
|
<Grade {gradeMax} {grade} {disableGrading} />
|
||||||
<div class="flex items-center">
|
|
||||||
<TextField
|
|
||||||
placeholder="Points"
|
|
||||||
bind:value={grade}
|
|
||||||
type="number"
|
|
||||||
className="w-20"
|
|
||||||
isDisabled={disableGrading}
|
|
||||||
/>
|
|
||||||
<p class="dark:text-white ml-2 text-lg">/ {gradeMax}</p>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if code}
|
{#if code}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import TextField from '$lib/components/Form/TextField.svelte';
|
||||||
|
export let gradeMax = 0;
|
||||||
|
export let disableGrading = false;
|
||||||
|
export let grade: number | undefined = 0;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if typeof grade === 'number'}
|
||||||
|
<div class="flex items-center">
|
||||||
|
<TextField
|
||||||
|
placeholder="Points"
|
||||||
|
bind:value={grade}
|
||||||
|
type="number"
|
||||||
|
className="w-12"
|
||||||
|
isDisabled={disableGrading}
|
||||||
|
/>
|
||||||
|
<p class="dark:text-white ml-2 text-lg flex items-center">
|
||||||
|
<span class="mr-1">/</span> <span>{gradeMax}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import CodeSnippet from '$lib/components/CodeSnippet/index.svelte';
|
import CodeSnippet from '$lib/components/CodeSnippet/index.svelte';
|
||||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||||
import RadioItem from '$lib/components/Form/RadioItem.svelte';
|
import RadioItem from '$lib/components/Form/RadioItem.svelte';
|
||||||
import TextField from '$lib/components/Form/TextField.svelte';
|
|
||||||
import HtmlRender from '$lib/components/HTMLRender/HTMLRender.svelte';
|
import HtmlRender from '$lib/components/HTMLRender/HTMLRender.svelte';
|
||||||
|
import Grade from '$lib/components/Question/Grade.svelte';
|
||||||
|
|
||||||
export let title = '';
|
export let title = '';
|
||||||
export let code = '';
|
export let code = '';
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
export let isPreview = false;
|
export let isPreview = false;
|
||||||
export let nextButtonProps = {};
|
export let nextButtonProps = {};
|
||||||
export let grade = undefined;
|
export let grade: number;
|
||||||
export let gradeMax = 0;
|
export let gradeMax = 0;
|
||||||
export let disableGrading = false;
|
export let disableGrading = false;
|
||||||
export let labelClassName = '';
|
export let labelClassName = '';
|
||||||
@@ -65,23 +65,12 @@
|
|||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<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} {typeof grade === 'number' && 'w-3/4'}">
|
||||||
{@html title}
|
{title}
|
||||||
</h3>
|
</h3>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</HtmlRender>
|
</HtmlRender>
|
||||||
{#if !isNaN(grade)}
|
<Grade {gradeMax} {grade} {disableGrading} />
|
||||||
<div class="flex items-center">
|
|
||||||
<TextField
|
|
||||||
placeholder="Points"
|
|
||||||
bind:value={grade}
|
|
||||||
type="number"
|
|
||||||
className="w-20"
|
|
||||||
isDisabled={disableGrading}
|
|
||||||
/>
|
|
||||||
<p class="dark:text-white ml-2 text-lg">/ {gradeMax}</p>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if code}
|
{#if code}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import CodeSnippet from '$lib/components/CodeSnippet/index.svelte';
|
import CodeSnippet from '$lib/components/CodeSnippet/index.svelte';
|
||||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||||
import TextArea from '$lib/components/Form/TextArea.svelte';
|
import TextArea from '$lib/components/Form/TextArea.svelte';
|
||||||
import TextField from '$lib/components/Form/TextField.svelte';
|
|
||||||
import HtmlRender from '$lib/components/HTMLRender/HTMLRender.svelte';
|
import HtmlRender from '$lib/components/HTMLRender/HTMLRender.svelte';
|
||||||
|
import Grade from '$lib/components/Question/Grade.svelte';
|
||||||
|
|
||||||
export let title = '';
|
export let title = '';
|
||||||
export let code;
|
export let code = '';
|
||||||
export let name = '';
|
export let name = '';
|
||||||
export let onSubmit = () => {};
|
export let onSubmit = () => {};
|
||||||
export let onPrevious = () => {};
|
export let onPrevious = () => {};
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
export let isLast = false;
|
export let isLast = false;
|
||||||
export let isPreview = false;
|
export let isPreview = false;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
export let grade;
|
export let grade: number | undefined;
|
||||||
export let gradeMax = 0;
|
export let gradeMax = 0;
|
||||||
export let disableGrading = false;
|
export let disableGrading = false;
|
||||||
|
|
||||||
@@ -34,24 +34,13 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form on:submit|preventDefault={handleFormSubmit}>
|
<form on:submit|preventDefault={handleFormSubmit}>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between mb-2">
|
||||||
<HtmlRender className="mt-4">
|
<HtmlRender className="mt-4">
|
||||||
<svelte:fragment slot="content">
|
<svelte:fragment slot="content">
|
||||||
<h3 class={!isNaN(grade) ? 'w-3/4' : ''}>{title}</h3>
|
<h3 class={typeof grade === 'number' ? 'w-3/4' : ''}>{title}</h3>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</HtmlRender>
|
</HtmlRender>
|
||||||
{#if !isNaN(grade)}
|
<Grade {gradeMax} {grade} {disableGrading} />
|
||||||
<div class="flex items-center">
|
|
||||||
<TextField
|
|
||||||
placeholder="Points"
|
|
||||||
bind:value={grade}
|
|
||||||
type="number"
|
|
||||||
className="w-20"
|
|
||||||
isDisabled={disableGrading}
|
|
||||||
/>
|
|
||||||
<p class="dark:text-white ml-2 text-lg">/ {gradeMax}</p>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if code}
|
{#if code}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { browser } from '$app/environment';
|
||||||
import type { Course } from '../types';
|
import type { Course } from '../types';
|
||||||
|
|
||||||
export const isCourseFree = (cost: number) => !(Number(cost) > 0);
|
export const isCourseFree = (cost: number) => !(Number(cost) > 0);
|
||||||
@@ -18,6 +19,8 @@ export const getStudentInviteLink = (_course: Course, orgSiteName: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getTextFromHTML = (html: string): string => {
|
export const getTextFromHTML = (html: string): string => {
|
||||||
|
if (!browser) return html;
|
||||||
|
|
||||||
const dummyDiv = document.createElement('div');
|
const dummyDiv = document.createElement('div');
|
||||||
dummyDiv.innerHTML = html;
|
dummyDiv.innerHTML = html;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ export const NOTIFICATION_NAME = {
|
|||||||
STUDENT_PROOVE_COURSE_PAYMENT: 'STUDENT PROOVE COURSE PAYMENT',
|
STUDENT_PROOVE_COURSE_PAYMENT: 'STUDENT PROOVE COURSE PAYMENT',
|
||||||
STUDENT_COURSE_WELCOME: 'STUDENT COURSE WELCOME',
|
STUDENT_COURSE_WELCOME: 'STUDENT COURSE WELCOME',
|
||||||
TEACHER_STUDENT_JOINED: 'TEACHER STUDENT JOINED',
|
TEACHER_STUDENT_JOINED: 'TEACHER STUDENT JOINED',
|
||||||
SUBMISSION_UPDATE: 'SUBMISSION UPDATE'
|
SUBMISSION_UPDATE: 'SUBMISSION UPDATE',
|
||||||
|
EXERCISE_SUBMISSION_UPDATE: 'EXERCISE SUBMISSION UPDATE'
|
||||||
};
|
};
|
||||||
|
|
||||||
const NAME_TO_PATH = {
|
const NAME_TO_PATH = {
|
||||||
@@ -20,7 +21,8 @@ const NAME_TO_PATH = {
|
|||||||
[NOTIFICATION_NAME.STUDENT_PROOVE_COURSE_PAYMENT]: '/api/email/course/student_prove_payment',
|
[NOTIFICATION_NAME.STUDENT_PROOVE_COURSE_PAYMENT]: '/api/email/course/student_prove_payment',
|
||||||
[NOTIFICATION_NAME.STUDENT_COURSE_WELCOME]: '/api/email/course/student_welcome',
|
[NOTIFICATION_NAME.STUDENT_COURSE_WELCOME]: '/api/email/course/student_welcome',
|
||||||
[NOTIFICATION_NAME.TEACHER_STUDENT_JOINED]: '/api/email/course/teacher_student_joined',
|
[NOTIFICATION_NAME.TEACHER_STUDENT_JOINED]: '/api/email/course/teacher_student_joined',
|
||||||
[NOTIFICATION_NAME.SUBMISSION_UPDATE]: '/api/email/course/submission_update'
|
[NOTIFICATION_NAME.SUBMISSION_UPDATE]: '/api/email/course/submission_update',
|
||||||
|
[NOTIFICATION_NAME.EXERCISE_SUBMISSION_UPDATE]: '/api/email/course/exercise_submission_update'
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAccessToken = async () => {
|
const getAccessToken = async () => {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export interface GroupPerson {
|
|||||||
profile: Profile;
|
profile: Profile;
|
||||||
profile_id: string;
|
profile_id: string;
|
||||||
role_id: Role['id'];
|
role_id: Role['id'];
|
||||||
|
fullname?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomQuestionType {
|
export interface CustomQuestionType {
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { json } from '@sveltejs/kit';
|
||||||
|
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||||
|
import { sendEmail } from '$lib/utils/services/notification/send';
|
||||||
|
|
||||||
|
const supabase = getSupabase();
|
||||||
|
|
||||||
|
export async function POST({ request }) {
|
||||||
|
const { to, content, orgName, exerciseTitle } = await request.json();
|
||||||
|
const accessToken = request.headers.get('Authorization') || '';
|
||||||
|
|
||||||
|
if (!to || !content || !orgName) {
|
||||||
|
return json({ success: false, message: 'Missing required fields' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
let user;
|
||||||
|
try {
|
||||||
|
const { data } = await supabase.auth.getUser(accessToken);
|
||||||
|
user = data.user;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
await sendEmail({
|
||||||
|
from: `"${orgName} (via ClassroomIO.com)" <help@classroomio.com>`,
|
||||||
|
to,
|
||||||
|
subject: `[Submitted]: ${exerciseTitle}`,
|
||||||
|
content
|
||||||
|
});
|
||||||
|
|
||||||
|
return json({
|
||||||
|
success: true,
|
||||||
|
message: 'Email sent'
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ export async function POST({ request }) {
|
|||||||
await sendEmail({
|
await sendEmail({
|
||||||
from: `"${orgName} (via ClassroomIO.com)" <help@classroomio.com>`,
|
from: `"${orgName} (via ClassroomIO.com)" <help@classroomio.com>`,
|
||||||
to,
|
to,
|
||||||
subject: `Submission Update`,
|
subject: 'Submission Update',
|
||||||
content
|
content
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
import { profile } from '$lib/utils/store/user';
|
import { profile } from '$lib/utils/store/user';
|
||||||
import { snackbar } from '$lib/components/Snackbar/store';
|
import { snackbar } from '$lib/components/Snackbar/store';
|
||||||
import Avatar from '$lib/components/Avatar/index.svelte';
|
import Avatar from '$lib/components/Avatar/index.svelte';
|
||||||
|
import type { GroupPerson } from '$lib/utils/types';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
const { courseId } = data;
|
const { courseId } = data;
|
||||||
@@ -62,9 +63,7 @@
|
|||||||
|
|
||||||
async function deletePerson() {
|
async function deletePerson() {
|
||||||
$group.people = $group.people.filter((person: { id: string }) => person.id !== member.id);
|
$group.people = $group.people.filter((person: { id: string }) => person.id !== member.id);
|
||||||
$group.tutors = $group.tutors.filter(
|
$group.tutors = $group.tutors.filter((person: GroupPerson) => person.memberId !== member.id);
|
||||||
(person: { memberId: string }) => person.memberId !== member.id
|
|
||||||
);
|
|
||||||
|
|
||||||
await deleteGroupMember(member.id);
|
await deleteGroupMember(member.id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,12 +114,9 @@
|
|||||||
${
|
${
|
||||||
status_id == 3
|
status_id == 3
|
||||||
? `<p>Your score was ${totalMark}/${maxMark}</p>
|
? `<p>Your score was ${totalMark}/${maxMark}</p>
|
||||||
<div>
|
<a class="button" href="${exerciseLink}">View your Result</a>
|
||||||
<a class="button" href="${exerciseLink}">View your Result</a>
|
`
|
||||||
<div>`
|
: `<a class="button" href="${exerciseLink}">Open Exercise</a>`
|
||||||
: `<div>
|
|
||||||
<a class="button" href="${exerciseLink}">Open Exercise</a>
|
|
||||||
<div>`
|
|
||||||
}
|
}
|
||||||
<p>This exercise is for <strong>${
|
<p>This exercise is for <strong>${
|
||||||
lessonDetails.title
|
lessonDetails.title
|
||||||
@@ -254,7 +251,7 @@
|
|||||||
total: totalPoints
|
total: totalPoints
|
||||||
}).then((res) => console.log('Updated submission', res));
|
}).then((res) => console.log('Updated submission', res));
|
||||||
|
|
||||||
snackbar.success();
|
snackbar.success('Grading saved and student notified');
|
||||||
|
|
||||||
await Promise.all(updates);
|
await Promise.all(updates);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user