mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: enable Tasks submissions
This commit is contained in:
parent
6d7e521bba
commit
bfb977ac5d
9 changed files with 330 additions and 110 deletions
|
|
@ -1,5 +1,5 @@
|
|||
from typing import Optional, Dict
|
||||
from sqlalchemy import JSON, Column, ForeignKey
|
||||
from sqlalchemy import JSON, Column, ForeignKey, null
|
||||
from sqlmodel import Field, SQLModel
|
||||
from enum import Enum
|
||||
|
||||
|
|
@ -125,7 +125,7 @@ class AssignmentTaskUpdate(SQLModel):
|
|||
description: Optional[str]
|
||||
hint: Optional[str]
|
||||
assignment_type: Optional[AssignmentTaskTypeEnum]
|
||||
contents: Optional[Dict] = Field(default={}, sa_column=Column(JSON))
|
||||
contents: Optional[Dict] = Field(default=None, sa_column=Column(JSON))
|
||||
max_grade_value: Optional[int]
|
||||
|
||||
|
||||
|
|
@ -194,17 +194,12 @@ class AssignmentTaskSubmissionRead(AssignmentTaskSubmissionBase):
|
|||
|
||||
class AssignmentTaskSubmissionUpdate(SQLModel):
|
||||
"""Model for updating an assignment task submission."""
|
||||
|
||||
assignment_task_id: Optional[int]
|
||||
assignment_task_submission_uuid: Optional[str]
|
||||
task_submission: Optional[Dict] = Field(default={}, sa_column=Column(JSON))
|
||||
task_submission: Optional[Dict] = Field(default=None, sa_column=Column(JSON))
|
||||
grade: Optional[int]
|
||||
task_submission_grade_feedback: Optional[str]
|
||||
assignment_type: Optional[AssignmentTaskTypeEnum]
|
||||
user_id: Optional[int]
|
||||
activity_id: Optional[int]
|
||||
course_id: Optional[int]
|
||||
chapter_id: Optional[int]
|
||||
assignment_task_id: Optional[int]
|
||||
|
||||
|
||||
class AssignmentTaskSubmission(AssignmentTaskSubmissionBase, table=True):
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from src.db.courses.assignments import (
|
|||
AssignmentRead,
|
||||
AssignmentTaskCreate,
|
||||
AssignmentTaskSubmissionCreate,
|
||||
AssignmentTaskSubmissionUpdate,
|
||||
AssignmentTaskUpdate,
|
||||
AssignmentUpdate,
|
||||
AssignmentUserSubmissionCreate,
|
||||
|
|
@ -15,12 +16,12 @@ from src.services.courses.activities.assignments import (
|
|||
create_assignment,
|
||||
create_assignment_submission,
|
||||
create_assignment_task,
|
||||
create_assignment_task_submission,
|
||||
delete_assignment,
|
||||
delete_assignment_from_activity_uuid,
|
||||
delete_assignment_submission,
|
||||
delete_assignment_task,
|
||||
delete_assignment_task_submission,
|
||||
handle_assignment_task_submission,
|
||||
put_assignment_task_reference_file,
|
||||
put_assignment_task_submission_file,
|
||||
read_assignment,
|
||||
|
|
@ -31,6 +32,7 @@ from src.services.courses.activities.assignments import (
|
|||
read_assignment_tasks,
|
||||
read_user_assignment_submissions,
|
||||
read_user_assignment_task_submissions,
|
||||
read_user_assignment_task_submissions_me,
|
||||
update_assignment,
|
||||
update_assignment_submission,
|
||||
update_assignment_task,
|
||||
|
|
@ -240,10 +242,10 @@ async def api_delete_assignment_tasks(
|
|||
## ASSIGNMENTS Tasks Submissions ##
|
||||
|
||||
|
||||
@router.post("/{assignment_uuid}/tasks/{assignment_task_uuid}/submissions")
|
||||
async def api_create_assignment_task_submissions(
|
||||
@router.put("/{assignment_uuid}/tasks/{assignment_task_uuid}/submissions")
|
||||
async def api_handle_assignment_task_submissions(
|
||||
request: Request,
|
||||
assignment_task_submission_object: AssignmentTaskSubmissionCreate,
|
||||
assignment_task_submission_object: AssignmentTaskSubmissionUpdate,
|
||||
assignment_task_uuid: str,
|
||||
current_user: PublicUser = Depends(get_current_user),
|
||||
db_session=Depends(get_db_session),
|
||||
|
|
@ -251,7 +253,7 @@ async def api_create_assignment_task_submissions(
|
|||
"""
|
||||
Create new task submissions for an assignment
|
||||
"""
|
||||
return await create_assignment_task_submission(
|
||||
return await handle_assignment_task_submission(
|
||||
request,
|
||||
assignment_task_uuid,
|
||||
assignment_task_submission_object,
|
||||
|
|
@ -275,6 +277,20 @@ async def api_read_user_assignment_task_submissions(
|
|||
request, assignment_task_uuid, user_id, current_user, db_session
|
||||
)
|
||||
|
||||
@router.get("/{assignment_uuid}/tasks/{assignment_task_uuid}/submissions/user/me")
|
||||
async def api_read_user_assignment_task_submissions_me(
|
||||
request: Request,
|
||||
assignment_task_uuid: str,
|
||||
current_user: PublicUser = Depends(get_current_user),
|
||||
db_session=Depends(get_db_session),
|
||||
):
|
||||
"""
|
||||
Read task submissions for an assignment from a user
|
||||
"""
|
||||
return await read_user_assignment_task_submissions_me(
|
||||
request, assignment_task_uuid, current_user, db_session
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{assignment_uuid}/tasks/{assignment_task_uuid}/submissions")
|
||||
async def api_read_assignment_task_submissions(
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ from src.db.courses.assignments import (
|
|||
AssignmentTaskSubmission,
|
||||
AssignmentTaskSubmissionCreate,
|
||||
AssignmentTaskSubmissionRead,
|
||||
AssignmentTaskSubmissionUpdate,
|
||||
AssignmentTaskUpdate,
|
||||
AssignmentUpdate,
|
||||
AssignmentUserSubmission,
|
||||
|
|
@ -478,7 +479,13 @@ async def put_assignment_task_reference_file(
|
|||
f"{assignment_task_uuid}{uuid4()}.{reference_file.filename.split('.')[-1]}"
|
||||
)
|
||||
await upload_reference_file(
|
||||
reference_file, name_in_disk, activity.activity_uuid, org.org_uuid, course.course_uuid, assignment.assignment_uuid, assignment_task_uuid
|
||||
reference_file,
|
||||
name_in_disk,
|
||||
activity.activity_uuid,
|
||||
org.org_uuid,
|
||||
course.course_uuid,
|
||||
assignment.assignment_uuid,
|
||||
assignment_task_uuid,
|
||||
)
|
||||
course.thumbnail_image = name_in_disk
|
||||
# Update reference file
|
||||
|
|
@ -494,6 +501,7 @@ async def put_assignment_task_reference_file(
|
|||
# return assignment task read
|
||||
return AssignmentTaskRead.model_validate(assignment_task)
|
||||
|
||||
|
||||
async def put_assignment_task_submission_file(
|
||||
request: Request,
|
||||
db_session: Session,
|
||||
|
|
@ -546,15 +554,18 @@ async def put_assignment_task_submission_file(
|
|||
|
||||
# Upload reference file
|
||||
if sub_file and sub_file.filename and activity and org:
|
||||
name_in_disk = (
|
||||
f"{assignment_task_uuid}_sub_{current_user.email}_{uuid4()}.{sub_file.filename.split('.')[-1]}"
|
||||
)
|
||||
name_in_disk = f"{assignment_task_uuid}_sub_{current_user.email}_{uuid4()}.{sub_file.filename.split('.')[-1]}"
|
||||
await upload_submission_file(
|
||||
sub_file, name_in_disk, activity.activity_uuid, org.org_uuid, course.course_uuid, assignment.assignment_uuid, assignment_task_uuid
|
||||
sub_file,
|
||||
name_in_disk,
|
||||
activity.activity_uuid,
|
||||
org.org_uuid,
|
||||
course.course_uuid,
|
||||
assignment.assignment_uuid,
|
||||
assignment_task_uuid,
|
||||
)
|
||||
|
||||
return {"message": "Assignment Task Submission File uploaded"}
|
||||
|
||||
return {"file_uuid": name_in_disk}
|
||||
|
||||
|
||||
async def update_assignment_task(
|
||||
|
|
@ -665,13 +676,14 @@ async def delete_assignment_task(
|
|||
## > Assignments Tasks Submissions CRUD
|
||||
|
||||
|
||||
async def create_assignment_task_submission(
|
||||
async def handle_assignment_task_submission(
|
||||
request: Request,
|
||||
assignment_task_uuid: str,
|
||||
assignment_task_submission_object: AssignmentTaskSubmissionCreate,
|
||||
assignment_task_submission_object: AssignmentTaskSubmissionUpdate,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
):
|
||||
# TODO: Improve terrible implementation of this function
|
||||
# Check if assignment task exists
|
||||
statement = select(AssignmentTask).where(
|
||||
AssignmentTask.assignment_task_uuid == assignment_task_uuid
|
||||
|
|
@ -694,51 +706,82 @@ async def create_assignment_task_submission(
|
|||
detail="Assignment not found",
|
||||
)
|
||||
|
||||
# Check if course exists
|
||||
statement = select(Course).where(Course.id == assignment.course_id)
|
||||
course = db_session.exec(statement).first()
|
||||
# Check if user already submitted the assignment
|
||||
statement = select(AssignmentTaskSubmission).where(
|
||||
AssignmentTaskSubmission.assignment_task_id == assignment_task.id,
|
||||
AssignmentTaskSubmission.user_id == current_user.id,
|
||||
)
|
||||
assignment_task_submission = db_session.exec(statement).first()
|
||||
|
||||
if not course:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Course not found",
|
||||
# Update Task submission if it exists
|
||||
if assignment_task_submission:
|
||||
# Update only the fields that were passed in
|
||||
for var, value in vars(assignment_task_submission_object).items():
|
||||
if value is not None:
|
||||
setattr(assignment_task_submission, var, value)
|
||||
assignment_task_submission.update_date = str(datetime.now())
|
||||
|
||||
# Insert Assignment Task Submission in DB
|
||||
db_session.add(assignment_task_submission)
|
||||
db_session.commit()
|
||||
db_session.refresh(assignment_task_submission)
|
||||
|
||||
# return assignment task submission read
|
||||
return AssignmentTaskSubmissionRead.model_validate(assignment_task_submission)
|
||||
|
||||
else:
|
||||
# Create new Task submission
|
||||
current_time = str(datetime.now())
|
||||
|
||||
# Assuming model_dump() returns a dictionary
|
||||
model_data = assignment_task_submission_object.model_dump()
|
||||
|
||||
assignment_task_submission = AssignmentTaskSubmission(
|
||||
assignment_task_submission_uuid=f"assignmenttasksubmission_{uuid4()}",
|
||||
task_submission=model_data["task_submission"],
|
||||
grade=model_data["grade"],
|
||||
task_submission_grade_feedback=model_data["task_submission_grade_feedback"],
|
||||
assignment_task_id=int(assignment_task.id), # type: ignore
|
||||
assignment_type=assignment_task.assignment_type,
|
||||
activity_id=assignment.activity_id,
|
||||
course_id=assignment.course_id,
|
||||
chapter_id=assignment.chapter_id,
|
||||
user_id=current_user.id,
|
||||
creation_date=current_time,
|
||||
update_date=current_time,
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, course.course_uuid, current_user, "create", db_session)
|
||||
# Insert Assignment Task Submission in DB
|
||||
db_session.add(assignment_task_submission)
|
||||
db_session.commit()
|
||||
|
||||
# Create Assignment Task Submission
|
||||
assignment_task_submission = AssignmentTaskSubmission(
|
||||
**assignment_task_submission_object.model_dump()
|
||||
)
|
||||
|
||||
assignment_task_submission.assignment_task_submission_uuid = str(
|
||||
f"assignmenttasksubmission_{uuid4()}"
|
||||
)
|
||||
assignment_task_submission.creation_date = str(datetime.now())
|
||||
assignment_task_submission.update_date = str(datetime.now())
|
||||
assignment_task_submission.org_id = course.org_id
|
||||
|
||||
# Insert Assignment Task Submission in DB
|
||||
db_session.add(assignment_task_submission)
|
||||
db_session.commit()
|
||||
db_session.refresh(assignment_task_submission)
|
||||
|
||||
# return assignment task submission read
|
||||
return AssignmentTaskSubmissionRead.model_validate(assignment_task_submission)
|
||||
# return assignment task submission read
|
||||
return AssignmentTaskSubmissionRead.model_validate(assignment_task_submission)
|
||||
|
||||
|
||||
async def read_user_assignment_task_submissions(
|
||||
request: Request,
|
||||
assignment_task_submission_uuid: str,
|
||||
assignment_task_uuid: str,
|
||||
user_id: int,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
):
|
||||
|
||||
# Check if assignment task exists
|
||||
statement = select(AssignmentTask).where(
|
||||
AssignmentTask.assignment_task_uuid == assignment_task_uuid
|
||||
)
|
||||
assignment_task = db_session.exec(statement).first()
|
||||
|
||||
if not assignment_task:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Assignment Task not found",
|
||||
)
|
||||
|
||||
# Check if assignment task submission exists
|
||||
statement = select(AssignmentTaskSubmission).where(
|
||||
AssignmentTaskSubmission.assignment_task_submission_uuid
|
||||
== assignment_task_submission_uuid,
|
||||
AssignmentTaskSubmission.assignment_task_id == assignment_task.id,
|
||||
AssignmentTaskSubmission.user_id == user_id,
|
||||
)
|
||||
assignment_task_submission = db_session.exec(statement).first()
|
||||
|
|
@ -749,18 +792,6 @@ async def read_user_assignment_task_submissions(
|
|||
detail="Assignment Task Submission not found",
|
||||
)
|
||||
|
||||
# Check if assignment task exists
|
||||
statement = select(AssignmentTask).where(
|
||||
AssignmentTask.id == assignment_task_submission.assignment_task_id
|
||||
)
|
||||
assignment_task = db_session.exec(statement).first()
|
||||
|
||||
if not assignment_task:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Assignment Task not found",
|
||||
)
|
||||
|
||||
# Check if assignment exists
|
||||
statement = select(Assignment).where(Assignment.id == assignment_task.assignment_id)
|
||||
assignment = db_session.exec(statement).first()
|
||||
|
|
@ -788,6 +819,21 @@ async def read_user_assignment_task_submissions(
|
|||
return AssignmentTaskSubmissionRead.model_validate(assignment_task_submission)
|
||||
|
||||
|
||||
async def read_user_assignment_task_submissions_me(
|
||||
request: Request,
|
||||
assignment_task_uuid: str,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
):
|
||||
return await read_user_assignment_task_submissions(
|
||||
request,
|
||||
assignment_task_uuid,
|
||||
current_user.id,
|
||||
current_user,
|
||||
db_session,
|
||||
)
|
||||
|
||||
|
||||
async def read_assignment_task_submissions(
|
||||
request: Request,
|
||||
assignment_task_submission_uuid: str,
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ import { useAssignments } from '@components/Contexts/Assignments/AssignmentConte
|
|||
import { useAssignmentsTask, useAssignmentsTaskDispatch } from '@components/Contexts/Assignments/AssignmentsTaskContext';
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext';
|
||||
import AssignmentBoxUI from '@components/Objects/Activities/Assignment/AssignmentBoxUI'
|
||||
import { getAssignmentTask, updateSubFile } from '@services/courses/assignments';
|
||||
import { getAssignmentTask, getAssignmentTaskSubmissionsMe, handleAssignmentTaskSubmission, updateSubFile } from '@services/courses/assignments';
|
||||
import { Cloud, File, Info, Loader, UploadCloud } from 'lucide-react'
|
||||
import React, { useEffect } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
type FileSchema = {
|
||||
fileID: string;
|
||||
fileUUID: string;
|
||||
};
|
||||
|
||||
type TaskFileObjectProps = {
|
||||
|
|
@ -26,6 +26,18 @@ export default function TaskFileObject({ view, assignmentTaskUUID }: TaskFileObj
|
|||
const assignmentTaskStateHook = useAssignmentsTaskDispatch() as any;
|
||||
const assignment = useAssignments() as any;
|
||||
|
||||
/* TEACHER VIEW CODE */
|
||||
/* TEACHER VIEW CODE */
|
||||
|
||||
/* STUDENT VIEW CODE */
|
||||
const [showSavingDisclaimer, setShowSavingDisclaimer] = useState<boolean>(false);
|
||||
const [userSubmissions, setUserSubmissions] = useState<FileSchema>({
|
||||
fileUUID: '',
|
||||
});
|
||||
const [initialUserSubmissions, setInitialUserSubmissions] = useState<FileSchema>({
|
||||
fileUUID: '',
|
||||
});
|
||||
|
||||
const handleFileChange = async (event: any) => {
|
||||
const file = event.target.files[0]
|
||||
|
||||
|
|
@ -37,19 +49,56 @@ export default function TaskFileObject({ view, assignmentTaskUUID }: TaskFileObj
|
|||
assignment.assignment_object.assignment_uuid,
|
||||
access_token
|
||||
)
|
||||
assignmentTaskStateHook({ type: 'reload' })
|
||||
|
||||
// wait for 1 second to show loading animation
|
||||
await new Promise((r) => setTimeout(r, 1500))
|
||||
if (res.success === false) {
|
||||
|
||||
setError(res.data.detail)
|
||||
setIsLoading(false)
|
||||
} else {
|
||||
assignmentTaskStateHook({ type: 'reload' })
|
||||
setUserSubmissions({
|
||||
fileUUID: res.data.file_uuid,
|
||||
})
|
||||
setIsLoading(false)
|
||||
setError('')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function getAssignmentTaskSubmissionFromUserUI() {
|
||||
if (assignmentTaskUUID) {
|
||||
const res = await getAssignmentTaskSubmissionsMe(assignmentTaskUUID, assignment.assignment_object.assignment_uuid, access_token);
|
||||
if (res.success) {
|
||||
setUserSubmissions(res.data.task_submission);
|
||||
setInitialUserSubmissions(res.data.task_submission);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const submitFC = async () => {
|
||||
// Save the quiz to the server
|
||||
const values = {
|
||||
task_submission: userSubmissions,
|
||||
grade: 0,
|
||||
task_submission_grade_feedback: '',
|
||||
};
|
||||
if (assignmentTaskUUID) {
|
||||
const res = await handleAssignmentTaskSubmission(values, assignmentTaskUUID, assignment.assignment_object.assignment_uuid, access_token);
|
||||
if (res) {
|
||||
assignmentTaskStateHook({
|
||||
type: 'reload',
|
||||
});
|
||||
toast.success('Task saved successfully');
|
||||
setShowSavingDisclaimer(false);
|
||||
} else {
|
||||
toast.error('Error saving task, please retry later.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async function getAssignmentTaskUI() {
|
||||
if (assignmentTaskUUID) {
|
||||
const res = await getAssignmentTask(assignmentTaskUUID, access_token);
|
||||
|
|
@ -60,13 +109,27 @@ export default function TaskFileObject({ view, assignmentTaskUUID }: TaskFileObj
|
|||
}
|
||||
}
|
||||
|
||||
// Detect changes between initial and current submissions
|
||||
useEffect(() => {
|
||||
getAssignmentTaskUI()
|
||||
if (userSubmissions.fileUUID !== initialUserSubmissions.fileUUID) {
|
||||
setShowSavingDisclaimer(true);
|
||||
} else {
|
||||
setShowSavingDisclaimer(false);
|
||||
}
|
||||
}, [userSubmissions]);
|
||||
|
||||
/* STUDENT VIEW CODE */
|
||||
|
||||
useEffect(() => {
|
||||
if (view === 'student') {
|
||||
getAssignmentTaskUI()
|
||||
getAssignmentTaskSubmissionFromUserUI()
|
||||
}
|
||||
}
|
||||
, [assignmentTaskUUID])
|
||||
|
||||
return (
|
||||
<AssignmentBoxUI view={view} type="file">
|
||||
<AssignmentBoxUI submitFC={submitFC} showSavingDisclaimer={showSavingDisclaimer} view={view} type="file">
|
||||
{view === 'teacher' && (
|
||||
<div className='flex py-5 text-sm justify-center mx-auto space-x-2 text-slate-500'>
|
||||
<Info size={20} />
|
||||
|
|
@ -94,17 +157,32 @@ export default function TaskFileObject({ view, assignmentTaskUUID }: TaskFileObj
|
|||
|
||||
<div className='flex space-x-2 mt-2'>
|
||||
|
||||
<File size={20} className='' />
|
||||
<div className='font-semibold text-sm uppercase'>
|
||||
{localUploadFile.name}
|
||||
<File size={20} className='' />
|
||||
<div className='font-semibold text-sm uppercase'>
|
||||
{localUploadFile.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{userSubmissions.fileUUID && !isLoading && !localUploadFile && (
|
||||
<div className='flex flex-col rounded-lg bg-white text-gray-400 shadow-lg nice-shadow px-5 py-3 space-y-1 items-center relative'>
|
||||
<div className='absolute top-0 right-0 transform translate-x-1/2 -translate-y-1/2 bg-green-500 rounded-full px-1.5 py-1.5 text-white flex justify-center items-center'>
|
||||
<Cloud size={15} />
|
||||
</div>
|
||||
|
||||
<div className='flex space-x-2 mt-2'>
|
||||
|
||||
<File size={20} className='' />
|
||||
<div className='font-semibold text-sm uppercase'>
|
||||
{`${userSubmissions.fileUUID.slice(0, 8)}...${userSubmissions.fileUUID.slice(-4)}`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className='flex pt-4 font-semibold space-x-1.5 text-xs items-center text-gray-500 '>
|
||||
<Info size={16} />
|
||||
<p>Allowed formats : pdf, docx, mp4, jpg, jpeg, pptx</p>
|
||||
</div>
|
||||
<Info size={16} />
|
||||
<p>Allowed formats : pdf, docx, mp4, jpg, jpeg, pptx</p>
|
||||
</div>
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center items-center">
|
||||
<input
|
||||
|
|
@ -122,13 +200,13 @@ export default function TaskFileObject({ view, assignmentTaskUUID }: TaskFileObj
|
|||
<div className="flex justify-center items-center">
|
||||
<input
|
||||
type="file"
|
||||
id="fileInput"
|
||||
id={"fileInput_" + assignmentTaskUUID}
|
||||
style={{ display: 'none' }}
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
<button
|
||||
className="font-bold antialiased items-center text-gray text-sm rounded-md px-4 mt-6 flex"
|
||||
onClick={() => document.getElementById('fileInput')?.click()}
|
||||
onClick={() => document.getElementById("fileInput_" + assignmentTaskUUID)?.click()}
|
||||
>
|
||||
<UploadCloud size={16} className="mr-2" />
|
||||
<span>Submit File</span>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useAssignments } from '@components/Contexts/Assignments/AssignmentConte
|
|||
import { useAssignmentsTask, useAssignmentsTaskDispatch } from '@components/Contexts/Assignments/AssignmentsTaskContext';
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext';
|
||||
import AssignmentBoxUI from '@components/Objects/Activities/Assignment/AssignmentBoxUI';
|
||||
import { getAssignmentTask, updateAssignmentTask } from '@services/courses/assignments';
|
||||
import { getAssignmentTask, getAssignmentTaskSubmissionsMe, handleAssignmentTaskSubmission, updateAssignmentTask } from '@services/courses/assignments';
|
||||
import { Check, Minus, Plus, PlusCircle, X } from 'lucide-react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
|
|
@ -113,6 +113,11 @@ function TaskQuizObject({ view, assignmentTaskUUID }: TaskQuizObjectProps) {
|
|||
questions: [],
|
||||
submissions: [],
|
||||
});
|
||||
const [initialUserSubmissions, setInitialUserSubmissions] = useState<QuizSubmitSchema>({
|
||||
questions: [],
|
||||
submissions: [],
|
||||
});
|
||||
const [showSavingDisclaimer, setShowSavingDisclaimer] = useState<boolean>(false);
|
||||
|
||||
async function chooseOption(qIndex: number, oIndex: number) {
|
||||
const updatedSubmissions = [...userSubmissions.submissions];
|
||||
|
|
@ -136,11 +141,8 @@ function TaskQuizObject({ view, assignmentTaskUUID }: TaskQuizObjectProps) {
|
|||
...userSubmissions,
|
||||
submissions: updatedSubmissions,
|
||||
});
|
||||
console.log(userSubmissions);
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function getAssignmentTaskUI() {
|
||||
if (assignmentTaskUUID) {
|
||||
const res = await getAssignmentTask(assignmentTaskUUID, access_token);
|
||||
|
|
@ -151,6 +153,48 @@ function TaskQuizObject({ view, assignmentTaskUUID }: TaskQuizObjectProps) {
|
|||
}
|
||||
}
|
||||
|
||||
async function getAssignmentTaskSubmissionFromUserUI() {
|
||||
if (assignmentTaskUUID) {
|
||||
const res = await getAssignmentTaskSubmissionsMe(assignmentTaskUUID, assignment.assignment_object.assignment_uuid, access_token);
|
||||
if (res.success) {
|
||||
setUserSubmissions(res.data.task_submission);
|
||||
setInitialUserSubmissions(res.data.task_submission);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Detect changes between initial and current submissions
|
||||
useEffect(() => {
|
||||
const hasChanges = JSON.stringify(initialUserSubmissions.submissions) !== JSON.stringify(userSubmissions.submissions);
|
||||
setShowSavingDisclaimer(hasChanges);
|
||||
}, [userSubmissions, initialUserSubmissions.submissions]);
|
||||
|
||||
|
||||
|
||||
const submitFC = async () => {
|
||||
// Save the quiz to the server
|
||||
const values = {
|
||||
task_submission: userSubmissions,
|
||||
grade: 0,
|
||||
task_submission_grade_feedback: '',
|
||||
};
|
||||
if (assignmentTaskUUID) {
|
||||
const res = await handleAssignmentTaskSubmission(values, assignmentTaskUUID, assignment.assignment_object.assignment_uuid, access_token);
|
||||
if (res) {
|
||||
assignmentTaskStateHook({
|
||||
type: 'reload',
|
||||
});
|
||||
toast.success('Task saved successfully');
|
||||
setShowSavingDisclaimer(false);
|
||||
} else {
|
||||
toast.error('Error saving task, please retry later.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* STUDENT VIEW CODE */
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -164,12 +208,13 @@ function TaskQuizObject({ view, assignmentTaskUUID }: TaskQuizObjectProps) {
|
|||
// Student area
|
||||
else if (view == 'student') {
|
||||
getAssignmentTaskUI();
|
||||
getAssignmentTaskSubmissionFromUserUI();
|
||||
}
|
||||
}, [assignmentTaskState, assignment, assignmentTaskStateHook, access_token]);
|
||||
|
||||
|
||||
return (
|
||||
<AssignmentBoxUI saveFC={saveFC} view={view} type="quiz">
|
||||
<AssignmentBoxUI submitFC={submitFC} saveFC={saveFC} view={view} showSavingDisclaimer={showSavingDisclaimer} type="quiz">
|
||||
<div className="flex flex-col space-y-6">
|
||||
{questions && questions.map((question, qIndex) => (
|
||||
<div key={qIndex} className="flex flex-col space-y-1.5">
|
||||
|
|
@ -245,8 +290,8 @@ function TaskQuizObject({ view, assignmentTaskUUID }: TaskQuizObjectProps) {
|
|||
(submission) =>
|
||||
submission.questionUUID === question.questionUUID && submission.optionUUID === option.optionUUID
|
||||
)
|
||||
? "bg-green-200/60 text-green-500 hover:bg-green-300" // Selected state colors
|
||||
: "bg-slate-200/60 text-slate-500 hover:bg-slate-300" // Default state colors
|
||||
? "bg-green-200/60 text-green-500 hover:bg-green-300" // Selected state colors
|
||||
: "bg-slate-200/60 text-slate-500 hover:bg-slate-300" // Default state colors
|
||||
} text-sm transition-all ease-linear cursor-pointer`}>
|
||||
{userSubmissions.submissions.find(
|
||||
(submission) =>
|
||||
|
|
|
|||
|
|
@ -93,18 +93,7 @@ function PublishingState() {
|
|||
{assignment?.assignment_object?.published ? 'Published' : 'Unpublished'}
|
||||
</div>
|
||||
<div><EllipsisVertical className='text-gray-500' size={13} /></div>
|
||||
{assignment?.assignment_object?.published && <ToolTip
|
||||
side='left'
|
||||
slateBlack
|
||||
sideOffset={10}
|
||||
content="Make your Assignment unavailable for students" >
|
||||
<div
|
||||
onClick={() => updateAssignmentPublishState(assignment?.assignment_object?.assignment_uuid)}
|
||||
className='flex px-3 py-2 cursor-pointer rounded-md space-x-2 items-center bg-gradient-to-bl text-gray-800 font-medium from-gray-400/50 to-gray-200/80 border border-gray-600/10 shadow-gray-900/10 shadow-lg'>
|
||||
<BookX size={18} />
|
||||
<p className='text-sm font-bold'>Unpublish</p>
|
||||
</div>
|
||||
</ToolTip>}
|
||||
|
||||
<ToolTip
|
||||
side='left'
|
||||
slateBlack
|
||||
|
|
@ -118,6 +107,18 @@ function PublishingState() {
|
|||
<p className=' text-sm font-bold'>Preview</p>
|
||||
</Link>
|
||||
</ToolTip>
|
||||
{assignment?.assignment_object?.published && <ToolTip
|
||||
side='left'
|
||||
slateBlack
|
||||
sideOffset={10}
|
||||
content="Make your Assignment unavailable for students" >
|
||||
<div
|
||||
onClick={() => updateAssignmentPublishState(assignment?.assignment_object?.assignment_uuid)}
|
||||
className='flex px-3 py-2 cursor-pointer rounded-md space-x-2 items-center bg-gradient-to-bl text-gray-800 font-medium from-gray-400/50 to-gray-200/80 border border-gray-600/10 shadow-gray-900/10 shadow-lg'>
|
||||
<BookX size={18} />
|
||||
<p className='text-sm font-bold'>Unpublish</p>
|
||||
</div>
|
||||
</ToolTip>}
|
||||
{!assignment?.assignment_object?.published &&
|
||||
<ToolTip
|
||||
side='left'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { BookUser, EllipsisVertical, FileUp, Forward, ListTodo, Save } from 'lucide-react'
|
||||
import { BookUser, EllipsisVertical, FileUp, Forward, Info, InfoIcon, ListTodo, Save } from 'lucide-react'
|
||||
import React from 'react'
|
||||
|
||||
type AssignmentBoxProps = {
|
||||
|
|
@ -6,11 +6,12 @@ type AssignmentBoxProps = {
|
|||
view?: 'teacher' | 'student'
|
||||
saveFC?: () => void
|
||||
submitFC?: () => void
|
||||
showSavingDisclaimer?: boolean
|
||||
children: React.ReactNode
|
||||
|
||||
}
|
||||
|
||||
function AssignmentBoxUI({ type, view, saveFC, submitFC, children }: AssignmentBoxProps) {
|
||||
function AssignmentBoxUI({ type, view, saveFC, submitFC, showSavingDisclaimer, children }: AssignmentBoxProps) {
|
||||
return (
|
||||
<div className='flex flex-col px-6 py-4 nice-shadow rounded-md bg-slate-100/30'>
|
||||
<div className='flex justify-between space-x-2 pb-2 text-slate-400 items-center'>
|
||||
|
|
@ -40,12 +41,17 @@ function AssignmentBoxUI({ type, view, saveFC, submitFC, children }: AssignmentB
|
|||
}
|
||||
</div>
|
||||
<div className='flex px-1 py-1 rounded-md items-center'>
|
||||
|
||||
{showSavingDisclaimer &&
|
||||
<div className='flex space-x-2 items-center font-semibold px-3 py-1 outline-dashed outline-red-200 text-red-400 mr-5 rounded-full'>
|
||||
<InfoIcon size={14} />
|
||||
<p className='text-xs'>Don't forget to save your progress</p>
|
||||
</div>
|
||||
}
|
||||
{/* Save button */}
|
||||
{view === 'teacher' &&
|
||||
<div
|
||||
onClick={() => saveFC && saveFC()}
|
||||
className='flex px-2 py-1 cursor-pointer rounded-md space-x-2 items-center bg-gradient-to-bl text-slate-500 bg-white/60 hover:bg-white/80 linear transition-all nice-shadow '>
|
||||
className='flex px-2 py-1 cursor-pointer rounded-md space-x-2 items-center bg-gradient-to-bl text-emerald-700 bg-emerald-300/20 hover:bg-emerald-300/10 hover:outline-offset-4 active:outline-offset-1 linear transition-all outline-offset-2 outline-dashed outline-emerald-500/60'>
|
||||
<Save size={14} />
|
||||
<p className='text-xs font-semibold'>Save</p>
|
||||
</div>
|
||||
|
|
@ -53,9 +59,9 @@ function AssignmentBoxUI({ type, view, saveFC, submitFC, children }: AssignmentB
|
|||
{view === 'student' &&
|
||||
<div
|
||||
onClick={() => submitFC && submitFC()}
|
||||
className='flex px-2 py-1 cursor-pointer rounded-md space-x-2 items-center bg-gradient-to-bl text-slate-500 bg-white/60 hover:bg-white/80 linear transition-all nice-shadow '>
|
||||
className='flex px-2 py-1 cursor-pointer rounded-md space-x-2 items-center bg-gradient-to-bl text-emerald-700 bg-emerald-300/20 hover:bg-emerald-300/10 hover:outline-offset-4 active:outline-offset-1 linear transition-all outline-offset-2 outline-dashed outline-emerald-500/60'>
|
||||
<Forward size={14} />
|
||||
<p className='text-xs font-semibold'>Save</p>
|
||||
<p className='text-xs font-semibold'>Save your progress</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ function AssignmentStudentActivity() {
|
|||
const org = useOrg() as any;
|
||||
|
||||
useEffect(() => {
|
||||
console.log(assignments)
|
||||
}, [assignments, org])
|
||||
|
||||
|
||||
|
|
@ -71,7 +70,14 @@ function AssignmentStudentActivity() {
|
|||
download={true}
|
||||
className='px-3 py-1 flex items-center nice-shadow bg-cyan-50/40 text-cyan-900 rounded-full space-x-2 cursor-pointer'>
|
||||
<Download size={13} />
|
||||
<p className='text-xs font-semibold'>Reference file</p>
|
||||
<div className='flex items-center space-x-2'>
|
||||
{task.reference_file && (
|
||||
<span className='relative'>
|
||||
<span className='absolute right-0 top-0 block h-2 w-2 rounded-full ring-2 ring-white bg-green-400'></span>
|
||||
</span>
|
||||
)}
|
||||
<p className='text-xs font-semibold'>Reference Document</p>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -91,6 +91,33 @@ export async function getAssignmentTask(
|
|||
return res
|
||||
}
|
||||
|
||||
export async function getAssignmentTaskSubmissionsMe(
|
||||
assignmentTaskUUID: string,
|
||||
assignmentUUID: string,
|
||||
access_token: string
|
||||
) {
|
||||
const result: any = await fetch(
|
||||
`${getAPIUrl()}assignments/${assignmentUUID}/tasks/${assignmentTaskUUID}/submissions/user/me`,
|
||||
RequestBodyWithAuthHeader('GET', null, null, access_token)
|
||||
)
|
||||
const res = await getResponseMetadata(result)
|
||||
return res
|
||||
}
|
||||
|
||||
export async function handleAssignmentTaskSubmission(
|
||||
body: any,
|
||||
assignmentTaskUUID: string,
|
||||
assignmentUUID: string,
|
||||
access_token: string
|
||||
) {
|
||||
const result: any = await fetch(
|
||||
`${getAPIUrl()}assignments/${assignmentUUID}/tasks/${assignmentTaskUUID}/submissions`,
|
||||
RequestBodyWithAuthHeader('PUT', body, null, access_token)
|
||||
)
|
||||
const res = await getResponseMetadata(result)
|
||||
return res
|
||||
}
|
||||
|
||||
export async function updateAssignmentTask(
|
||||
body: any,
|
||||
assignmentTaskUUID: string,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue