mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: assignments activity page init
This commit is contained in:
parent
182cd73001
commit
29600d9b6c
12 changed files with 564 additions and 105 deletions
|
|
@ -22,6 +22,7 @@ from src.services.courses.activities.assignments import (
|
||||||
delete_assignment_task,
|
delete_assignment_task,
|
||||||
delete_assignment_task_submission,
|
delete_assignment_task_submission,
|
||||||
put_assignment_task_reference_file,
|
put_assignment_task_reference_file,
|
||||||
|
put_assignment_task_submission_file,
|
||||||
read_assignment,
|
read_assignment,
|
||||||
read_assignment_from_activity_uuid,
|
read_assignment_from_activity_uuid,
|
||||||
read_assignment_submissions,
|
read_assignment_submissions,
|
||||||
|
|
@ -205,6 +206,21 @@ async def api_put_assignment_task_ref_file(
|
||||||
request, db_session, assignment_task_uuid, current_user, reference_file
|
request, db_session, assignment_task_uuid, current_user, reference_file
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@router.post("/{assignment_uuid}/tasks/{assignment_task_uuid}/sub_file")
|
||||||
|
async def api_put_assignment_task_sub_file(
|
||||||
|
request: Request,
|
||||||
|
assignment_task_uuid: str,
|
||||||
|
sub_file: UploadFile | None = None,
|
||||||
|
current_user: PublicUser = Depends(get_current_user),
|
||||||
|
db_session=Depends(get_db_session),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Update tasks for an assignment
|
||||||
|
"""
|
||||||
|
return await put_assignment_task_submission_file(
|
||||||
|
request, db_session, assignment_task_uuid, current_user, sub_file
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{assignment_uuid}/tasks/{assignment_task_uuid}")
|
@router.delete("/{assignment_uuid}/tasks/{assignment_task_uuid}")
|
||||||
async def api_delete_assignment_tasks(
|
async def api_delete_assignment_tasks(
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ from src.security.rbac.rbac import (
|
||||||
authorization_verify_if_element_is_public,
|
authorization_verify_if_element_is_public,
|
||||||
authorization_verify_if_user_is_anon,
|
authorization_verify_if_user_is_anon,
|
||||||
)
|
)
|
||||||
|
from src.services.courses.activities.uploads.sub_file import upload_submission_file
|
||||||
from src.services.courses.activities.uploads.tasks_ref_files import (
|
from src.services.courses.activities.uploads.tasks_ref_files import (
|
||||||
upload_reference_file,
|
upload_reference_file,
|
||||||
)
|
)
|
||||||
|
|
@ -493,6 +494,68 @@ async def put_assignment_task_reference_file(
|
||||||
# return assignment task read
|
# return assignment task read
|
||||||
return AssignmentTaskRead.model_validate(assignment_task)
|
return AssignmentTaskRead.model_validate(assignment_task)
|
||||||
|
|
||||||
|
async def put_assignment_task_submission_file(
|
||||||
|
request: Request,
|
||||||
|
db_session: Session,
|
||||||
|
assignment_task_uuid: str,
|
||||||
|
current_user: PublicUser | AnonymousUser,
|
||||||
|
sub_file: UploadFile | None = None,
|
||||||
|
):
|
||||||
|
# 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 exists
|
||||||
|
statement = select(Assignment).where(Assignment.id == assignment_task.assignment_id)
|
||||||
|
assignment = db_session.exec(statement).first()
|
||||||
|
|
||||||
|
if not assignment:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail="Assignment not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check for activity
|
||||||
|
statement = select(Activity).where(Activity.id == assignment.activity_id)
|
||||||
|
activity = db_session.exec(statement).first()
|
||||||
|
|
||||||
|
# Check if course exists
|
||||||
|
statement = select(Course).where(Course.id == assignment.course_id)
|
||||||
|
course = db_session.exec(statement).first()
|
||||||
|
|
||||||
|
if not course:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail="Course not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get org uuid
|
||||||
|
org_statement = select(Organization).where(Organization.id == course.org_id)
|
||||||
|
org = db_session.exec(org_statement).first()
|
||||||
|
|
||||||
|
# RBAC check
|
||||||
|
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
|
||||||
|
|
||||||
|
# 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]}"
|
||||||
|
)
|
||||||
|
await upload_submission_file(
|
||||||
|
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"}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def update_assignment_task(
|
async def update_assignment_task(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
|
|
||||||
24
apps/api/src/services/courses/activities/uploads/sub_file.py
Normal file
24
apps/api/src/services/courses/activities/uploads/sub_file.py
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
from uuid import uuid4
|
||||||
|
from src.services.utils.upload_content import upload_content
|
||||||
|
|
||||||
|
|
||||||
|
async def upload_submission_file(
|
||||||
|
file,
|
||||||
|
name_in_disk,
|
||||||
|
activity_uuid,
|
||||||
|
org_uuid,
|
||||||
|
course_uuid,
|
||||||
|
assignment_uuid,
|
||||||
|
assignment_task_uuid,
|
||||||
|
):
|
||||||
|
contents = file.file.read()
|
||||||
|
file_format = file.filename.split(".")[-1]
|
||||||
|
|
||||||
|
await upload_content(
|
||||||
|
f"courses/{course_uuid}/activities/{activity_uuid}/assignments/{assignment_uuid}/tasks/{assignment_task_uuid}/subs",
|
||||||
|
"orgs",
|
||||||
|
org_uuid,
|
||||||
|
contents,
|
||||||
|
f"{name_in_disk}",
|
||||||
|
["pdf", "docx", "mp4", "jpg", "jpeg", "png", "pptx"],
|
||||||
|
)
|
||||||
|
|
@ -16,6 +16,11 @@ import { CourseProvider } from '@components/Contexts/CourseContext'
|
||||||
import AIActivityAsk from '@components/Objects/Activities/AI/AIActivityAsk'
|
import AIActivityAsk from '@components/Objects/Activities/AI/AIActivityAsk'
|
||||||
import AIChatBotProvider from '@components/Contexts/AI/AIChatBotContext'
|
import AIChatBotProvider from '@components/Contexts/AI/AIChatBotContext'
|
||||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||||
|
import React, { useEffect } from 'react'
|
||||||
|
import { getAssignmentFromActivityUUID } from '@services/courses/assignments'
|
||||||
|
import AssignmentStudentActivity from '@components/Objects/Activities/Assignment/AssignmentStudentActivity'
|
||||||
|
import { AssignmentProvider } from '@components/Contexts/Assignments/AssignmentContext'
|
||||||
|
import { AssignmentsTaskProvider } from '@components/Contexts/Assignments/AssignmentsTaskContext'
|
||||||
|
|
||||||
interface ActivityClientProps {
|
interface ActivityClientProps {
|
||||||
activityid: string
|
activityid: string
|
||||||
|
|
@ -32,6 +37,11 @@ function ActivityClient(props: ActivityClientProps) {
|
||||||
const activity = props.activity
|
const activity = props.activity
|
||||||
const course = props.course
|
const course = props.course
|
||||||
const org = useOrg() as any
|
const org = useOrg() as any
|
||||||
|
const session = useLHSession() as any;
|
||||||
|
const access_token = session?.data?.tokens?.access_token;
|
||||||
|
const [bgColor, setBgColor] = React.useState('bg-white')
|
||||||
|
const [assignment, setAssignment] = React.useState(null) as any;
|
||||||
|
const [markStatusButtonActive, setMarkStatusButtonActive] = React.useState(false);
|
||||||
|
|
||||||
function getChapterNameByActivityId(course: any, activity_id: any) {
|
function getChapterNameByActivityId(course: any, activity_id: any) {
|
||||||
for (let i = 0; i < course.chapters.length; i++) {
|
for (let i = 0; i < course.chapters.length; i++) {
|
||||||
|
|
@ -46,6 +56,26 @@ function ActivityClient(props: ActivityClientProps) {
|
||||||
return null // return null if no matching activity is found
|
return null // return null if no matching activity is found
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getAssignmentUI() {
|
||||||
|
const assignment = await getAssignmentFromActivityUUID(activity.activity_uuid, access_token)
|
||||||
|
setAssignment(assignment.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (activity.activity_type == 'TYPE_DYNAMIC') {
|
||||||
|
setBgColor('bg-white nice-shadow');
|
||||||
|
}
|
||||||
|
else if (activity.activity_type == 'TYPE_ASSIGNMENT') {
|
||||||
|
setMarkStatusButtonActive(false);
|
||||||
|
setBgColor('bg-white nice-shadow');
|
||||||
|
getAssignmentUI();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setBgColor('bg-zinc-950');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, [activity])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CourseProvider courseuuid={course?.course_uuid}>
|
<CourseProvider courseuuid={course?.course_uuid}>
|
||||||
|
|
@ -93,6 +123,8 @@ function ActivityClient(props: ActivityClientProps) {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-1 items-center">
|
<div className="flex space-x-1 items-center">
|
||||||
<AuthenticatedClientElement checkMethod="authentication">
|
<AuthenticatedClientElement checkMethod="authentication">
|
||||||
|
{activity.activity_type != 'TYPE_ASSIGNMENT' &&
|
||||||
|
<>
|
||||||
<AIActivityAsk activity={activity} />
|
<AIActivityAsk activity={activity} />
|
||||||
<MoreVertical size={17} className="text-gray-300 " />
|
<MoreVertical size={17} className="text-gray-300 " />
|
||||||
<MarkStatus
|
<MarkStatus
|
||||||
|
|
@ -101,16 +133,16 @@ function ActivityClient(props: ActivityClientProps) {
|
||||||
course={course}
|
course={course}
|
||||||
orgslug={orgslug}
|
orgslug={orgslug}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
</AuthenticatedClientElement>
|
</AuthenticatedClientElement>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{activity ? (
|
{activity ? (
|
||||||
<div
|
<div
|
||||||
className={`p-7 pt-4 drop-shadow-sm rounded-lg ${activity.activity_type == 'TYPE_DYNAMIC'
|
className={`p-7 drop-shadow-sm rounded-lg ${bgColor}`}
|
||||||
? 'bg-white'
|
|
||||||
: 'bg-zinc-950'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
{activity.activity_type == 'TYPE_DYNAMIC' && (
|
{activity.activity_type == 'TYPE_DYNAMIC' && (
|
||||||
|
|
@ -126,6 +158,19 @@ function ActivityClient(props: ActivityClientProps) {
|
||||||
activity={activity}
|
activity={activity}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{activity.activity_type == 'TYPE_ASSIGNMENT' && (
|
||||||
|
<div>
|
||||||
|
{assignment ? (
|
||||||
|
<AssignmentProvider assignment_uuid={assignment?.assignment_uuid}>
|
||||||
|
<AssignmentsTaskProvider>
|
||||||
|
<AssignmentStudentActivity />
|
||||||
|
</AssignmentsTaskProvider>
|
||||||
|
</AssignmentProvider>
|
||||||
|
) : (
|
||||||
|
<div></div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ function AssignmentTaskContentEdit() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{assignment_task?.assignmentTask.assignment_type === 'QUIZ' && <TaskQuizObject />}
|
{assignment_task?.assignmentTask.assignment_type === 'QUIZ' && <TaskQuizObject view='teacher' />}
|
||||||
{assignment_task?.assignmentTask.assignment_type === 'FILE_SUBMISSION' && <TaskFileObject view='teacher' />}
|
{assignment_task?.assignmentTask.assignment_type === 'FILE_SUBMISSION' && <TaskFileObject view='teacher' />}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,146 @@
|
||||||
import AssignmentBoxUI from '@components/Objects/Assignments/AssignmentBoxUI'
|
import { useAssignments } from '@components/Contexts/Assignments/AssignmentContext';
|
||||||
import { Info } from 'lucide-react'
|
import { useAssignmentsTask, useAssignmentsTaskDispatch } from '@components/Contexts/Assignments/AssignmentsTaskContext';
|
||||||
import React from 'react'
|
import { useLHSession } from '@components/Contexts/LHSessionContext';
|
||||||
|
import AssignmentBoxUI from '@components/Objects/Activities/Assignment/AssignmentBoxUI'
|
||||||
|
import { getAssignmentTask, updateSubFile } from '@services/courses/assignments';
|
||||||
|
import { Cloud, File, Info, Loader, UploadCloud } from 'lucide-react'
|
||||||
|
import React, { useEffect } from 'react'
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
type FileSchema = {
|
||||||
|
fileID: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TaskFileObjectProps = {
|
||||||
|
view: 'teacher' | 'student';
|
||||||
|
assignmentTaskUUID?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function TaskFileObject({ view, assignmentTaskUUID }: TaskFileObjectProps) {
|
||||||
|
const session = useLHSession() as any;
|
||||||
|
const access_token = session?.data?.tokens?.access_token;
|
||||||
|
const [isLoading, setIsLoading] = React.useState(false);
|
||||||
|
const [localUploadFile, setLocalUploadFile] = React.useState<File | null>(null);
|
||||||
|
const [error, setError] = React.useState<string | null>(null);
|
||||||
|
const [assignmentTask, setAssignmentTask] = React.useState<any>(null);
|
||||||
|
const assignmentTaskStateHook = useAssignmentsTaskDispatch() as any;
|
||||||
|
const assignment = useAssignments() as any;
|
||||||
|
|
||||||
|
const handleFileChange = async (event: any) => {
|
||||||
|
const file = event.target.files[0]
|
||||||
|
|
||||||
|
setLocalUploadFile(file)
|
||||||
|
setIsLoading(true)
|
||||||
|
const res = await updateSubFile(
|
||||||
|
file,
|
||||||
|
assignmentTask.assignment_task_uuid,
|
||||||
|
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 {
|
||||||
|
setIsLoading(false)
|
||||||
|
setError('')
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAssignmentTaskUI() {
|
||||||
|
if (assignmentTaskUUID) {
|
||||||
|
const res = await getAssignmentTask(assignmentTaskUUID, access_token);
|
||||||
|
if (res.success) {
|
||||||
|
setAssignmentTask(res.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getAssignmentTaskUI()
|
||||||
|
}
|
||||||
|
, [assignmentTaskUUID])
|
||||||
|
|
||||||
export default function TaskFileObject({ view }: any) {
|
|
||||||
return (
|
return (
|
||||||
<AssignmentBoxUI view={view} type="file">
|
<AssignmentBoxUI view={view} type="file">
|
||||||
|
{view === 'teacher' && (
|
||||||
<div className='flex py-5 text-sm justify-center mx-auto space-x-2 text-slate-500'>
|
<div className='flex py-5 text-sm justify-center mx-auto space-x-2 text-slate-500'>
|
||||||
<Info size={20} />
|
<Info size={20} />
|
||||||
<p>User will be able to submit a file for this task, you'll be able to review it in the Submissions Tab</p>
|
<p>User will be able to submit a file for this task, you'll be able to review it in the Submissions Tab</p>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
{view === 'student' && (
|
||||||
|
<>
|
||||||
|
<div className="w-auto bg-white rounded-xl outline outline-1 outline-gray-200 h-[200px] shadow">
|
||||||
|
<div className="flex flex-col justify-center items-center h-full">
|
||||||
|
<div className="flex flex-col justify-center items-center">
|
||||||
|
<div className="flex flex-col justify-center items-center">
|
||||||
|
{error && (
|
||||||
|
<div className="flex justify-center bg-red-200 rounded-md text-red-950 space-x-2 items-center p-2 transition-all shadow-sm">
|
||||||
|
<div className="text-sm font-semibold">{error}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{localUploadFile && !isLoading && (
|
||||||
|
<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'>
|
||||||
|
{localUploadFile.name}
|
||||||
|
</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>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="flex justify-center items-center">
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
id="fileInput"
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
onChange={handleFileChange}
|
||||||
|
/>
|
||||||
|
<div className="font-bold animate-pulse antialiased items-center bg-slate-200 text-gray text-sm rounded-md px-4 py-2 mt-4 flex">
|
||||||
|
<Loader size={16} className="mr-2" />
|
||||||
|
<span>Loading</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex justify-center items-center">
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
id="fileInput"
|
||||||
|
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()}
|
||||||
|
>
|
||||||
|
<UploadCloud size={16} className="mr-2" />
|
||||||
|
<span>Submit File</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</AssignmentBoxUI>
|
</AssignmentBoxUI>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { useAssignments } from '@components/Contexts/Assignments/AssignmentContext';
|
import { useAssignments } from '@components/Contexts/Assignments/AssignmentContext';
|
||||||
import { useAssignmentsTask, useAssignmentsTaskDispatch } from '@components/Contexts/Assignments/AssignmentsTaskContext';
|
import { useAssignmentsTask, useAssignmentsTaskDispatch } from '@components/Contexts/Assignments/AssignmentsTaskContext';
|
||||||
import { useLHSession } from '@components/Contexts/LHSessionContext';
|
import { useLHSession } from '@components/Contexts/LHSessionContext';
|
||||||
import AssignmentBoxUI from '@components/Objects/Assignments/AssignmentBoxUI';
|
import AssignmentBoxUI from '@components/Objects/Activities/Assignment/AssignmentBoxUI';
|
||||||
import { updateAssignmentTask } from '@services/courses/assignments';
|
import { getAssignmentTask, updateAssignmentTask } from '@services/courses/assignments';
|
||||||
import { Check, Minus, Plus, PlusCircle, X } from 'lucide-react';
|
import { Check, Minus, Plus, PlusCircle, X } from 'lucide-react';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
@ -20,14 +20,19 @@ type QuizSchema = {
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
function TaskQuizObject() {
|
type TaskQuizObjectProps = {
|
||||||
|
view: 'teacher' | 'student';
|
||||||
|
assignmentTaskUUID?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function TaskQuizObject({ view, assignmentTaskUUID }: TaskQuizObjectProps) {
|
||||||
const session = useLHSession() as any;
|
const session = useLHSession() as any;
|
||||||
const access_token = session?.data?.tokens?.access_token;
|
const access_token = session?.data?.tokens?.access_token;
|
||||||
const assignmentTaskState = useAssignmentsTask() as any;
|
const assignmentTaskState = useAssignmentsTask() as any;
|
||||||
const assignmentTaskStateHook = useAssignmentsTaskDispatch() as any;
|
const assignmentTaskStateHook = useAssignmentsTaskDispatch() as any;
|
||||||
const assignment = useAssignments() as any;
|
const assignment = useAssignments() as any;
|
||||||
|
|
||||||
// Teacher area
|
/* TEACHER VIEW CODE */
|
||||||
const [questions, setQuestions] = useState<QuizSchema[]>([
|
const [questions, setQuestions] = useState<QuizSchema[]>([
|
||||||
{ questionText: '', questionUUID: 'question_' + uuidv4(), options: [{ text: '', fileID: '', type: 'text', correct: false, optionUUID: 'option_' + uuidv4() }] },
|
{ questionText: '', questionUUID: 'question_' + uuidv4(), options: [{ text: '', fileID: '', type: 'text', correct: false, optionUUID: 'option_' + uuidv4() }] },
|
||||||
]);
|
]);
|
||||||
|
|
@ -92,44 +97,73 @@ function TaskQuizObject() {
|
||||||
toast.error('Error saving task, please retry later.');
|
toast.error('Error saving task, please retry later.');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
/* TEACHER VIEW CODE */
|
||||||
|
|
||||||
|
/* STUDENT VIEW CODE */
|
||||||
|
async function getAssignmentTaskUI() {
|
||||||
|
if (assignmentTaskUUID) {
|
||||||
|
const res = await getAssignmentTask(assignmentTaskUUID, access_token);
|
||||||
|
if (res.success) {
|
||||||
|
setQuestions(res.data.contents.questions);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* STUDENT VIEW CODE */
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (assignmentTaskState.assignmentTask.contents?.questions) {
|
assignmentTaskStateHook({
|
||||||
|
setSelectedAssignmentTaskUUID: assignmentTaskUUID,
|
||||||
|
});
|
||||||
|
// Teacher area
|
||||||
|
if (view == 'teacher' && assignmentTaskState.assignmentTask.contents?.questions) {
|
||||||
setQuestions(assignmentTaskState.assignmentTask.contents.questions);
|
setQuestions(assignmentTaskState.assignmentTask.contents.questions);
|
||||||
}
|
}
|
||||||
|
// Student area
|
||||||
|
else if (view == 'student') {
|
||||||
|
getAssignmentTaskUI();
|
||||||
|
}
|
||||||
}, [assignmentTaskState, assignment, assignmentTaskStateHook, access_token]);
|
}, [assignmentTaskState, assignment, assignmentTaskStateHook, access_token]);
|
||||||
|
|
||||||
// Teacher area end
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AssignmentBoxUI saveFC={saveFC} view='teacher' type="quiz">
|
<AssignmentBoxUI saveFC={saveFC} view={view} type="quiz">
|
||||||
<div className="flex flex-col space-y-6">
|
<div className="flex flex-col space-y-6">
|
||||||
{questions.map((question, qIndex) => (
|
{questions && questions.map((question, qIndex) => (
|
||||||
<div key={qIndex} className="flex flex-col space-y-1.5">
|
<div key={qIndex} className="flex flex-col space-y-1.5">
|
||||||
<div className="flex space-x-2 items-center">
|
<div className="flex space-x-2 items-center">
|
||||||
|
{view === 'teacher' ? (
|
||||||
<input
|
<input
|
||||||
value={question.questionText}
|
value={question.questionText}
|
||||||
onChange={(e) => handleQuestionChange(qIndex, e.target.value)}
|
onChange={(e) => handleQuestionChange(qIndex, e.target.value)}
|
||||||
placeholder="Question"
|
placeholder="Question"
|
||||||
className="w-full px-3 text-neutral-600 bg-[#00008b00] border-2 border-gray-200 rounded-md border-dotted text-sm font-bold"
|
className="w-full px-3 text-neutral-600 bg-[#00008b00] border-2 border-gray-200 rounded-md border-dotted text-sm font-bold"
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<p className="w-full px-3 text-neutral-600 bg-[#00008b00] border-2 border-gray-200 rounded-md border-dotted text-sm font-bold">
|
||||||
|
{question.questionText}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{view === 'teacher' && (
|
||||||
<div
|
<div
|
||||||
className="w-[20px] flex-none flex items-center h-[20px] rounded-lg bg-slate-200/60 text-slate-500 hover:bg-slate-300 text-sm transition-all ease-linear cursor-pointer"
|
className="w-[20px] flex-none flex items-center h-[20px] rounded-lg bg-slate-200/60 text-slate-500 hover:bg-slate-300 text-sm transition-all ease-linear cursor-pointer"
|
||||||
onClick={() => removeQuestion(qIndex)}
|
onClick={() => removeQuestion(qIndex)}
|
||||||
>
|
>
|
||||||
<Minus size={12} className="mx-auto" />
|
<Minus size={12} className="mx-auto" />
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col space-y-2">
|
<div className="flex flex-col space-y-2">
|
||||||
{question.options.map((option, oIndex) => (
|
{question.options.map((option, oIndex) => (
|
||||||
<div className="flex" key={oIndex}>
|
<div className="flex" key={oIndex}>
|
||||||
<div
|
<div
|
||||||
key={oIndex}
|
|
||||||
className="answer outline outline-3 outline-white pr-2 shadow w-full flex items-center space-x-2 h-[30px] hover:bg-opacity-100 hover:shadow-md rounded-lg bg-white text-sm duration-150 cursor-pointer ease-linear nice-shadow"
|
className="answer outline outline-3 outline-white pr-2 shadow w-full flex items-center space-x-2 h-[30px] hover:bg-opacity-100 hover:shadow-md rounded-lg bg-white text-sm duration-150 cursor-pointer ease-linear nice-shadow"
|
||||||
>
|
>
|
||||||
<div className="font-bold text-base flex items-center h-full w-[40px] rounded-l-md text-slate-800 bg-slate-100/80">
|
<div className="font-bold text-base flex items-center h-full w-[40px] rounded-l-md text-slate-800 bg-slate-100/80">
|
||||||
<p className="mx-auto font-bold text-sm">{String.fromCharCode(65 + oIndex)}</p>
|
<p className="mx-auto font-bold text-sm">{String.fromCharCode(65 + oIndex)}</p>
|
||||||
</div>
|
</div>
|
||||||
|
{view === 'teacher' ? (
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={option.text}
|
value={option.text}
|
||||||
|
|
@ -137,6 +171,13 @@ function TaskQuizObject() {
|
||||||
placeholder="Option"
|
placeholder="Option"
|
||||||
className="w-full mx-2 px-3 pr-6 text-neutral-600 bg-[#00008b00] border-2 border-gray-200 rounded-md border-dotted text-sm font-bold"
|
className="w-full mx-2 px-3 pr-6 text-neutral-600 bg-[#00008b00] border-2 border-gray-200 rounded-md border-dotted text-sm font-bold"
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<p className="w-full mx-2 px-3 pr-6 text-neutral-600 bg-[#00008b00] text-sm font-bold">
|
||||||
|
{option.text}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{view === 'teacher' && (
|
||||||
|
<>
|
||||||
<div
|
<div
|
||||||
className={`w-fit flex-none flex text-xs px-2 py-0.5 space-x-1 items-center h-fit rounded-lg ${option.correct ? 'bg-lime-200 text-lime-600' : 'bg-rose-200/60 text-rose-500'
|
className={`w-fit flex-none flex text-xs px-2 py-0.5 space-x-1 items-center h-fit rounded-lg ${option.correct ? 'bg-lime-200 text-lime-600' : 'bg-rose-200/60 text-rose-500'
|
||||||
} hover:bg-lime-300 text-sm transition-all ease-linear cursor-pointer`}
|
} hover:bg-lime-300 text-sm transition-all ease-linear cursor-pointer`}
|
||||||
|
|
@ -155,10 +196,10 @@ function TaskQuizObject() {
|
||||||
>
|
>
|
||||||
<Minus size={12} className="mx-auto" />
|
<Minus size={12} className="mx-auto" />
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
{view === 'teacher' && oIndex === question.options.length - 1 && (
|
||||||
{/* Show this at the last option */}
|
|
||||||
{oIndex === question.options.length - 1 && (
|
|
||||||
<div className="flex justify-center mx-auto px-2">
|
<div className="flex justify-center mx-auto px-2">
|
||||||
<div
|
<div
|
||||||
className="outline text-xs outline-3 outline-white px-2 shadow w-full flex items-center h-[30px] hover:bg-opacity-100 hover:shadow-md rounded-lg bg-white duration-150 cursor-pointer ease-linear nice-shadow"
|
className="outline text-xs outline-3 outline-white px-2 shadow w-full flex items-center h-[30px] hover:bg-opacity-100 hover:shadow-md rounded-lg bg-white duration-150 cursor-pointer ease-linear nice-shadow"
|
||||||
|
|
@ -170,12 +211,12 @@ function TaskQuizObject() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
{view === 'teacher' && (
|
||||||
<div className="flex justify-center mx-auto px-2">
|
<div className="flex justify-center mx-auto px-2">
|
||||||
<div
|
<div
|
||||||
className="flex w-full my-2 py-2 px-4 bg-white text-slate text-xs rounded-md nice-shadow hover:shadow-sm cursor-pointer space-x-3 items-center transition duration-150 ease-linear"
|
className="flex w-full my-2 py-2 px-4 bg-white text-slate text-xs rounded-md nice-shadow hover:shadow-sm cursor-pointer space-x-3 items-center transition duration-150 ease-linear"
|
||||||
|
|
@ -185,6 +226,7 @@ function TaskQuizObject() {
|
||||||
<span>Add Question</span>
|
<span>Add Question</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</AssignmentBoxUI>
|
</AssignmentBoxUI>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
'use client';
|
'use client';
|
||||||
import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'
|
import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'
|
||||||
import { BookOpen, BookX, EllipsisVertical, LayoutList } from 'lucide-react'
|
import { BookOpen, BookX, EllipsisVertical, Eye, LayoutList } from 'lucide-react'
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import { AssignmentProvider, useAssignments } from '@components/Contexts/Assignments/AssignmentContext';
|
import { AssignmentProvider, useAssignments } from '@components/Contexts/Assignments/AssignmentContext';
|
||||||
import AssignmentTasks from './_components/Tasks';
|
import AssignmentTasks from './_components/Tasks';
|
||||||
import { useParams } from 'next/navigation';
|
|
||||||
import { AssignmentsTaskProvider } from '@components/Contexts/Assignments/AssignmentsTaskContext';
|
import { AssignmentsTaskProvider } from '@components/Contexts/Assignments/AssignmentsTaskContext';
|
||||||
import ToolTip from '@components/StyledElements/Tooltip/Tooltip';
|
import ToolTip from '@components/StyledElements/Tooltip/Tooltip';
|
||||||
import AssignmentTaskEditor from './_components/TaskEditor/TaskEditor';
|
import AssignmentTaskEditor from './_components/TaskEditor/TaskEditor';
|
||||||
|
|
@ -13,6 +12,8 @@ import { useLHSession } from '@components/Contexts/LHSessionContext';
|
||||||
import { mutate } from 'swr';
|
import { mutate } from 'swr';
|
||||||
import { getAPIUrl } from '@services/config/config';
|
import { getAPIUrl } from '@services/config/config';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { useParams } from 'next/navigation';
|
||||||
|
|
||||||
function AssignmentEdit() {
|
function AssignmentEdit() {
|
||||||
const params = useParams<{ assignmentuuid: string; }>()
|
const params = useParams<{ assignmentuuid: string; }>()
|
||||||
|
|
@ -83,7 +84,7 @@ function PublishingState() {
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('assignment', assignment?.assignment_object?.assignment_uuid)
|
console.log('assignment', assignment)
|
||||||
}, [assignment])
|
}, [assignment])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -104,6 +105,19 @@ function PublishingState() {
|
||||||
<p className='text-sm font-bold'>Unpublish</p>
|
<p className='text-sm font-bold'>Unpublish</p>
|
||||||
</div>
|
</div>
|
||||||
</ToolTip>}
|
</ToolTip>}
|
||||||
|
<ToolTip
|
||||||
|
side='left'
|
||||||
|
slateBlack
|
||||||
|
sideOffset={10}
|
||||||
|
content="Preview the Assignment as a student" >
|
||||||
|
<Link
|
||||||
|
target='_blank'
|
||||||
|
href={`/course/${assignment?.course_object?.course_uuid.replace('course_', '')}/activity/${assignment?.activity_object?.activity_uuid.replace('activity_', '')}`}
|
||||||
|
className='flex px-3 py-2 cursor-pointer rounded-md space-x-2 items-center bg-gradient-to-bl text-cyan-800 font-medium from-sky-400/50 to-cyan-200/80 border border-cyan-600/10 shadow-cyan-900/10 shadow-lg'>
|
||||||
|
<Eye size={18} />
|
||||||
|
<p className=' text-sm font-bold'>Preview</p>
|
||||||
|
</Link>
|
||||||
|
</ToolTip>
|
||||||
{!assignment?.assignment_object?.published &&
|
{!assignment?.assignment_object?.published &&
|
||||||
<ToolTip
|
<ToolTip
|
||||||
side='left'
|
side='left'
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ export function AssignmentProvider({ children, assignment_uuid }: { children: Re
|
||||||
(url) => swrFetcher(url, accessToken)
|
(url) => swrFetcher(url, accessToken)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Define a key for the course object based on assignment data
|
|
||||||
const course_id = assignment?.course_id
|
const course_id = assignment?.course_id
|
||||||
|
|
||||||
const { data: course_object, error: courseObjectError } = useSWR(
|
const { data: course_object, error: courseObjectError } = useSWR(
|
||||||
|
|
@ -38,12 +37,14 @@ export function AssignmentProvider({ children, assignment_uuid }: { children: Re
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (assignment && assignment_tasks && (!course_id || course_object) && (!activity_id || activity_object)) {
|
||||||
setAssignmentsFull({ assignment_object: assignment, assignment_tasks: assignment_tasks, course_object: course_object, activity_object: activity_object })
|
setAssignmentsFull({ assignment_object: assignment, assignment_tasks: assignment_tasks, course_object: course_object, activity_object: activity_object })
|
||||||
}, [assignment, assignment_tasks, course_object, activity_object])
|
}
|
||||||
|
}, [assignment, assignment_tasks, course_object, activity_object, course_id, activity_id])
|
||||||
|
|
||||||
if (assignmentError || assignmentTasksError || courseObjectError) return <div></div>
|
if (assignmentError || assignmentTasksError || courseObjectError || activityObjectError) return <div></div>
|
||||||
|
|
||||||
if (!assignment || !assignment_tasks || (course_id && !course_object)) return <div></div>
|
if (!assignment || !assignment_tasks || (course_id && !course_object) || (activity_id && !activity_object)) return <div></div>
|
||||||
|
|
||||||
return <AssignmentContext.Provider value={assignmentsFull}>{children}</AssignmentContext.Provider>
|
return <AssignmentContext.Provider value={assignmentsFull}>{children}</AssignmentContext.Provider>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
import { BookUser, EllipsisVertical, FileUp, ListTodo, Save } from 'lucide-react'
|
import { BookUser, EllipsisVertical, FileUp, Forward, ListTodo, Save } from 'lucide-react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
type AssignmentBoxProps = {
|
type AssignmentBoxProps = {
|
||||||
type: 'quiz' | 'file'
|
type: 'quiz' | 'file'
|
||||||
view?: 'teacher' | 'student'
|
view?: 'teacher' | 'student'
|
||||||
saveFC?: () => void
|
saveFC?: () => void
|
||||||
|
submitFC?: () => void
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function AssignmentBoxUI({ type, view, saveFC, children }: AssignmentBoxProps) {
|
function AssignmentBoxUI({ type, view, saveFC, submitFC, children }: AssignmentBoxProps) {
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col px-4 py-2 nice-shadow rounded-md bg-slate-100/30'>
|
<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'>
|
<div className='flex justify-between space-x-2 pb-2 text-slate-400 items-center'>
|
||||||
<div className='flex space-x-1 items-center'>
|
<div className='flex space-x-1 items-center'>
|
||||||
<div className='text-lg font-semibold'>
|
<div className='text-lg font-semibold'>
|
||||||
|
|
@ -41,12 +42,22 @@ function AssignmentBoxUI({ type, view, saveFC, children }: AssignmentBoxProps) {
|
||||||
<div className='flex px-1 py-1 rounded-md items-center'>
|
<div className='flex px-1 py-1 rounded-md items-center'>
|
||||||
|
|
||||||
{/* Save button */}
|
{/* Save button */}
|
||||||
|
{view === 'teacher' &&
|
||||||
<div
|
<div
|
||||||
onClick={() => saveFC && saveFC()}
|
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-slate-500 bg-white/60 hover:bg-white/80 linear transition-all nice-shadow '>
|
||||||
<Save size={14} />
|
<Save size={14} />
|
||||||
<p className='text-xs font-semibold'>Save</p>
|
<p className='text-xs font-semibold'>Save</p>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
{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 '>
|
||||||
|
<Forward size={14} />
|
||||||
|
<p className='text-xs font-semibold'>Save</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
import { useAssignments } from '@components/Contexts/Assignments/AssignmentContext';
|
||||||
|
import { useAssignmentsTask } from '@components/Contexts/Assignments/AssignmentsTaskContext';
|
||||||
|
import { useCourse } from '@components/Contexts/CourseContext';
|
||||||
|
import { useOrg } from '@components/Contexts/OrgContext';
|
||||||
|
import { getTaskRefFileDir } from '@services/media/media';
|
||||||
|
import TaskFileObject from 'app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskFileObject';
|
||||||
|
import TaskQuizObject from 'app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskQuizObject'
|
||||||
|
import { Backpack, Calendar, Download, EllipsisVertical, Info } from 'lucide-react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import React, { useEffect } from 'react'
|
||||||
|
|
||||||
|
function AssignmentStudentActivity() {
|
||||||
|
const assignments = useAssignments() as any;
|
||||||
|
const course = useCourse() as any;
|
||||||
|
const org = useOrg() as any;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(assignments)
|
||||||
|
}, [assignments, org])
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col space-y-6'>
|
||||||
|
<div className='flex flex-row justify-center space-x-3 items-center '>
|
||||||
|
<div className='text-xs h-fit flex space-x-3 items-center '>
|
||||||
|
<div className='flex space-x-2 py-2 px-5 h-fit text-sm text-slate-700 bg-slate-100/5 rounded-full nice-shadow'>
|
||||||
|
<Backpack size={18} />
|
||||||
|
<p className='font-semibold'>Assignment</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className='flex space-x-2 items-center'>
|
||||||
|
<EllipsisVertical className='text-slate-400' size={18} />
|
||||||
|
<div className='flex space-x-2 items-center'>
|
||||||
|
<div className='flex space-x-2 text-xs items-center text-slate-400'>
|
||||||
|
<Calendar size={14} />
|
||||||
|
<p className=' font-semibold'>Due Date</p>
|
||||||
|
<p className=' font-semibold'>{assignments?.assignment_object?.due_date}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className='w-full rounded-full bg-slate-500/5 nice-shadow h-[2px]'></div>
|
||||||
|
{assignments && assignments?.assignment_tasks?.sort((a: any, b: any) => a.id - b.id).map((task: any, index: number) => {
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col space-y-2' key={task.assignment_task_uuid}>
|
||||||
|
<div className='flex justify-between py-2'>
|
||||||
|
<div className='flex space-x-2 font-semibold text-slate-800'>
|
||||||
|
<p>Task {index + 1} : </p>
|
||||||
|
<p className='text-slate-500'>{task.description}</p>
|
||||||
|
</div>
|
||||||
|
<div className='flex space-x-2'>
|
||||||
|
<div
|
||||||
|
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'>
|
||||||
|
<Info size={13} />
|
||||||
|
<p className='text-xs font-semibold'>View Hint</p>
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
href={getTaskRefFileDir(
|
||||||
|
org?.org_uuid,
|
||||||
|
assignments?.course_object.course_uuid,
|
||||||
|
assignments?.activity_object.activity_uuid,
|
||||||
|
assignments?.assignment_object.assignment_uuid,
|
||||||
|
task.assignment_task_uuid,
|
||||||
|
task.reference_file
|
||||||
|
)}
|
||||||
|
target='_blank'
|
||||||
|
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>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{task.assignment_type === 'QUIZ' && <TaskQuizObject key={task.assignment_task_uuid} view='student' assignmentTaskUUID={task.assignment_task_uuid} />}
|
||||||
|
{task.assignment_type === 'FILE_SUBMISSION' && <TaskFileObject key={task.assignment_task_uuid} view='student' assignmentTaskUUID={task.assignment_task_uuid} />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AssignmentStudentActivity
|
||||||
|
|
@ -138,3 +138,25 @@ export async function updateReferenceFile(
|
||||||
const res = await getResponseMetadata(result)
|
const res = await getResponseMetadata(result)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function updateSubFile(
|
||||||
|
file: any,
|
||||||
|
assignmentTaskUUID: string,
|
||||||
|
assignmentUUID: string,
|
||||||
|
access_token: string
|
||||||
|
) {
|
||||||
|
|
||||||
|
// Send file thumbnail as form data
|
||||||
|
const formData = new FormData()
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
formData.append('sub_file', file)
|
||||||
|
}
|
||||||
|
const result: any = await fetch(
|
||||||
|
`${getAPIUrl()}assignments/${assignmentUUID}/tasks/${assignmentTaskUUID}/sub_file`,
|
||||||
|
RequestBodyFormWithAuthHeader('POST', formData, null, access_token)
|
||||||
|
)
|
||||||
|
const res = await getResponseMetadata(result)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue