diff --git a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskFileObject.tsx b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskFileObject.tsx index ff338183..5a5c8d6d 100644 --- a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskFileObject.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskFileObject.tsx @@ -1,9 +1,12 @@ import { useAssignments } from '@components/Contexts/Assignments/AssignmentContext'; import { useAssignmentsTask, useAssignmentsTaskDispatch } from '@components/Contexts/Assignments/AssignmentsTaskContext'; import { useLHSession } from '@components/Contexts/LHSessionContext'; +import { useOrg } from '@components/Contexts/OrgContext'; import AssignmentBoxUI from '@components/Objects/Activities/Assignment/AssignmentBoxUI' -import { getAssignmentTask, getAssignmentTaskSubmissionsMe, handleAssignmentTaskSubmission, updateSubFile } from '@services/courses/assignments'; -import { Cloud, File, Info, Loader, UploadCloud } from 'lucide-react' +import { getAssignmentTask, getAssignmentTaskSubmissionsMe, getAssignmentTaskSubmissionsUser, handleAssignmentTaskSubmission, updateSubFile } from '@services/courses/assignments'; +import { getTaskFileSubmissionDir } from '@services/media/media'; +import { Cloud, Download, File, Info, Loader, UploadCloud } from 'lucide-react' +import Link from 'next/link'; import React, { useEffect, useState } from 'react' import toast from 'react-hot-toast'; @@ -12,12 +15,14 @@ type FileSchema = { }; type TaskFileObjectProps = { - view: 'teacher' | 'student'; + view: 'teacher' | 'student' | 'grading' | 'custom-grading'; assignmentTaskUUID?: string; + user_id?: string; }; -export default function TaskFileObject({ view, assignmentTaskUUID }: TaskFileObjectProps) { +export default function TaskFileObject({ view, user_id, assignmentTaskUUID }: TaskFileObjectProps) { const session = useLHSession() as any; + const org = useOrg() as any; const access_token = session?.data?.tokens?.access_token; const [isLoading, setIsLoading] = React.useState(false); const [localUploadFile, setLocalUploadFile] = React.useState(null); @@ -104,6 +109,7 @@ export default function TaskFileObject({ view, assignmentTaskUUID }: TaskFileObj const res = await getAssignmentTask(assignmentTaskUUID, access_token); if (res.success) { setAssignmentTask(res.data); + setAssignmentTaskOutsideProvider(res.data); } } @@ -120,22 +126,98 @@ export default function TaskFileObject({ view, assignmentTaskUUID }: TaskFileObj /* STUDENT VIEW CODE */ + /* GRADING VIEW CODE */ + const [userSubmissionObject, setUserSubmissionObject] = useState(null); + async function getAssignmentTaskSubmissionFromIdentifiedUserUI() { + if (assignmentTaskUUID && user_id) { + const res = await getAssignmentTaskSubmissionsUser(assignmentTaskUUID, user_id, assignment.assignment_object.assignment_uuid, access_token); + if (res.success) { + setUserSubmissions(res.data.task_submission); + setUserSubmissionObject(res.data); + setInitialUserSubmissions(res.data.task_submission); + } + + } + } + + async function gradeCustomFC(grade: number) { + if (assignmentTaskUUID) { + if (grade > assignmentTaskOutsideProvider.max_grade_value) { + toast.error(`Grade cannot be more than ${assignmentTaskOutsideProvider.max_grade_value} points`); + return; + } + + + // Save the grade to the server + const values = { + task_submission: userSubmissions, + grade: grade, + task_submission_grade_feedback: 'Graded by teacher : @' + session.data.user.username, + }; + + const res = await handleAssignmentTaskSubmission(values, assignmentTaskUUID, assignment.assignment_object.assignment_uuid, access_token); + if (res) { + getAssignmentTaskSubmissionFromIdentifiedUserUI(); + toast.success(`Task graded successfully with ${grade} points`); + } else { + toast.error('Error grading task, please retry later.'); + } + } + } + + /* GRADING VIEW CODE */ + const [assignmentTaskOutsideProvider, setAssignmentTaskOutsideProvider] = useState(null); useEffect(() => { + // Student area if (view === 'student') { getAssignmentTaskUI() getAssignmentTaskSubmissionFromUserUI() } + + // Grading area + else if (view == 'custom-grading') { + getAssignmentTaskUI(); + //setQuestions(assignmentTaskState.assignmentTask.contents.questions); + getAssignmentTaskSubmissionFromIdentifiedUserUI(); + } } , [assignmentTaskUUID]) return ( - + {view === 'teacher' && (

User will be able to submit a file for this task, you'll be able to review it in the Submissions Tab

)} + {view === 'custom-grading' && ( +
+
+ +

Please download the file and grade it manually, then input the grade below

+
+ {userSubmissions.fileUUID && !isLoading && assignmentTaskUUID && ( + +
+ +
+ +
+ +
+ {`${userSubmissions.fileUUID.slice(0, 8)}...${userSubmissions.fileUUID.slice(-4)}`} +
+
+ + )} +
+ )} {view === 'student' && ( <>
@@ -213,7 +295,6 @@ export default function TaskFileObject({ view, assignmentTaskUUID }: TaskFileObj
)} - diff --git a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskQuizObject.tsx b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskQuizObject.tsx index 80460f35..a514c67c 100644 --- a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskQuizObject.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskQuizObject.tsx @@ -216,34 +216,47 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro if (assignmentTaskUUID) { // Ensure maxPoints is defined const maxPoints = assignmentTaskOutsideProvider?.max_grade_value || 100; // Default to 100 if not defined - + // Ensure userSubmissions.questions are set const totalQuestions = questions.length; - const correctQuestions = userSubmissions.submissions.filter((submission) => { + let correctQuestions = 0; + let incorrectQuestions = 0; + + userSubmissions.submissions.forEach((submission) => { const question = questions.find((q) => q.questionUUID === submission.questionUUID); const option = question?.options.find((o) => o.optionUUID === submission.optionUUID); - return option?.correct; - }).length; - - // Calculate grade based on correct questions - const grade = Math.floor((correctQuestions / totalQuestions) * maxPoints); - + if (option?.correct) { + correctQuestions++; + } else { + incorrectQuestions++; + } + }); + + // Calculate grade with penalties for incorrect answers + const pointsPerQuestion = maxPoints / totalQuestions; + const rawGrade = (correctQuestions - incorrectQuestions) * pointsPerQuestion; + + // Ensure the grade is within the valid range + const finalGrade = Math.max(0, Math.min(rawGrade, maxPoints)); + // Save the grade to the server const values = { task_submission: userSubmissions, - grade, + grade: finalGrade, task_submission_grade_feedback: 'Auto graded by system', }; - + const res = await handleAssignmentTaskSubmission(values, assignmentTaskUUID, assignment.assignment_object.assignment_uuid, access_token); if (res) { getAssignmentTaskSubmissionFromIdentifiedUserUI(); - toast.success(`Task graded successfully with ${grade} points`); + toast.success(`Task graded successfully with ${finalGrade} points`); } else { toast.error('Error grading task, please retry later.'); } } } + + /* GRADING VIEW CODE */ diff --git a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/subpages/Modals/EvaluateAssignment.tsx b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/subpages/Modals/EvaluateAssignment.tsx index 0ef0ac9a..e4c5784a 100644 --- a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/subpages/Modals/EvaluateAssignment.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/subpages/Modals/EvaluateAssignment.tsx @@ -27,7 +27,7 @@ function EvaluateAssignment({ user_id }: any) { onClick={() => alert(task.hint)} className='px-3 py-1 flex items-center nice-shadow bg-amber-50/40 text-amber-900 rounded-full space-x-2 cursor-pointer'> -

View Hint

+

Hint

{task.assignment_type === 'QUIZ' && } - {task.assignment_type === 'FILE_SUBMISSION' && } + {task.assignment_type === 'FILE_SUBMISSION' && }
) diff --git a/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ActivityElement.tsx b/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ActivityElement.tsx index bfb43372..e693ab67 100644 --- a/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ActivityElement.tsx +++ b/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ActivityElement.tsx @@ -168,7 +168,7 @@ function ActivityElement(props: ActivitiyElementProps) { ) : ( )} - {!props.activity.published ? 'Publish' : 'UnPublish'} + {!props.activity.published ? 'Publish' : 'Unpublish'} void submitFC?: () => void gradeFC?: () => void + gradeCustomFC?: (grade: number) => void showSavingDisclaimer?: boolean children: React.ReactNode } -function AssignmentBoxUI({ type, view, currentPoints, maxPoints, saveFC, submitFC, gradeFC, showSavingDisclaimer, children }: AssignmentBoxProps) { +function AssignmentBoxUI({ type, view, currentPoints, maxPoints, saveFC, submitFC, gradeFC, gradeCustomFC, showSavingDisclaimer, children }: AssignmentBoxProps) { + const [customGrade, setCustomGrade] = React.useState(0) const submission = useAssignmentSubmission() as any useEffect(() => { } , [submission]) + return (
@@ -89,7 +92,23 @@ function AssignmentBoxUI({ type, view, currentPoints, maxPoints, saveFC, submitF className='flex px-0.5 py-0.5 cursor-pointer rounded-md space-x-2 items-center bg-gradient-to-bl hover:outline-offset-4 active:outline-offset-1 linear transition-all outline-offset-2 outline-dashed outline-orange-500/60'>

Current points : {currentPoints}

- + +

Grade

+
+
+ } + + {/* CustomGrading button */} + {view === 'custom-grading' && maxPoints && +
gradeCustomFC && gradeCustomFC(customGrade)} + className='flex px-0.5 py-0.5 cursor-pointer rounded-md space-x-2 items-center bg-gradient-to-bl hover:outline-offset-4 active:outline-offset-1 linear transition-all outline-offset-2 outline-dashed outline-orange-500/60'> +

Current points : {currentPoints}

+ setCustomGrade(parseInt(e.target.value))} + placeholder={maxPoints.toString()} className='w-[100px] light-shadow text-sm py-0.5 outline outline-gray-200 rounded-lg px-2' type="number" /> +
+

Grade

diff --git a/apps/web/components/Objects/Activities/Assignment/AssignmentStudentActivity.tsx b/apps/web/components/Objects/Activities/Assignment/AssignmentStudentActivity.tsx index c7d3dfd0..da4fe81f 100644 --- a/apps/web/components/Objects/Activities/Assignment/AssignmentStudentActivity.tsx +++ b/apps/web/components/Objects/Activities/Assignment/AssignmentStudentActivity.tsx @@ -55,7 +55,7 @@ function AssignmentStudentActivity() { onClick={() => alert(task.hint)} className='px-3 py-1 flex items-center nice-shadow bg-amber-50/40 text-amber-900 rounded-full space-x-2 cursor-pointer'> -

View Hint

+

Hint