feat: edit tasks and general improvements

This commit is contained in:
swve 2024-07-14 14:06:25 +02:00
parent acfcea026b
commit 3c41e0ee73
13 changed files with 570 additions and 93 deletions

View file

@ -58,7 +58,7 @@ function NewTaskModal({ closeModal, assignment_uuid }: any) {
<div className='px-5 py-5 rounded-full nice-shadow w-fit mx-auto bg-gray-100/50 text-gray-500 cursor-pointer hover:bg-gray-100 transition-all ease-linear'>
<FileUp size={30} />
</div>
<p className='text-xl text-gray-700 font-semibold'>File submissions</p>
<p className='text-xl text-gray-700 font-semibold'>File submission</p>
<p className='text-sm text-gray-500 w-40'>Students can submit files for this task</p>
</div>
<div
@ -67,7 +67,7 @@ function NewTaskModal({ closeModal, assignment_uuid }: any) {
<div className='px-5 py-5 rounded-full nice-shadow w-fit mx-auto bg-gray-100/50 text-gray-500 cursor-pointer hover:bg-gray-100 transition-all ease-linear'>
<AArrowUp size={30} />
</div>
<p className='text-xl text-gray-700 font-semibold'>Forms</p>
<p className='text-xl text-gray-700 font-semibold'>Form</p>
<p className='text-sm text-gray-500 w-40'>Forms for students to fill out</p>
</div>
</div>

View file

@ -1,7 +1,17 @@
'use client';
import { useAssignmentsTask } from '@components/Contexts/Assignments/AssignmentsTaskContext';
import { Info, TentTree } from 'lucide-react'
import React, { useEffect } from 'react'
import { useAssignments } from '@components/Contexts/Assignments/AssignmentContext';
import { useAssignmentsTask, useAssignmentsTaskDispatch } from '@components/Contexts/Assignments/AssignmentsTaskContext';
import { useLHSession } from '@components/Contexts/LHSessionContext';
import FormLayout, { FormField, FormLabelAndMessage, Input, Textarea } from '@components/StyledElements/Form/Form';
import * as Form from '@radix-ui/react-form';
import { getActivity } from '@services/courses/activities';
import { updateAssignmentTask, updateReferenceFile } from '@services/courses/assignments';
import { getTaskRefFileDir } from '@services/media/media';
import { useFormik } from 'formik';
import { ArrowBigUpDash, Cloud, File, GalleryVerticalEnd, Info, Loader, TentTree, Upload, UploadCloud } from 'lucide-react'
import Link from 'next/link';
import React, { use, useEffect } from 'react'
import toast from 'react-hot-toast';
function AssignmentTaskEditor({ page }: any) {
const [selectedSubPage, setSelectedSubPage] = React.useState(page)
@ -15,23 +25,41 @@ function AssignmentTaskEditor({ page }: any) {
return (
<div className="flex flex-col font-black text-sm w-full z-20">
{assignmentTaskState.assignmentTask && Object.keys(assignmentTaskState.assignmentTask).length > 0 && (
<div className='flex flex-col bg-white pl-10 pr-10 text-sm tracking-tight z-10 shadow-[0px_4px_16px_rgba(0,0,0,0.06)] pt-5'>
<div className='font-semibold text-lg py-1'>
Assignment Test #1
</div>
<div className='flex space-x-2 '>
<div
className={`flex space-x-4 py-2 w-fit text-center border-black transition-all ease-linear ${selectedSubPage === 'overview'
? 'border-b-4'
: 'opacity-50'
} cursor-pointer`}
>
<div className="flex items-center space-x-2.5 mx-2">
<Info size={16} />
<div>Overview</div>
<div className='flex flex-col space-y-3'>
<div className='flex flex-col bg-white pl-10 pr-10 text-sm tracking-tight z-10 shadow-[0px_4px_16px_rgba(0,0,0,0.06)] pt-5 mb-3 nice-shadow'>
<div className='font-semibold text-lg py-1'>
{assignmentTaskState?.assignmentTask.title}
</div>
<div className='flex space-x-2 '>
<div
onClick={() => setSelectedSubPage('general')}
className={`flex space-x-4 py-2 w-fit text-center border-black transition-all ease-linear ${selectedSubPage === 'general'
? 'border-b-4'
: 'opacity-50'
} cursor-pointer`}
>
<div className="flex items-center space-x-2.5 mx-2">
<Info size={16} />
<div>General</div>
</div>
</div>
<div
onClick={() => setSelectedSubPage('content')}
className={`flex space-x-4 py-2 w-fit text-center border-black transition-all ease-linear ${selectedSubPage === 'content'
? 'border-b-4'
: 'opacity-50'
} cursor-pointer`}
>
<div className="flex items-center space-x-2.5 mx-2">
<GalleryVerticalEnd size={16} />
<div>Content</div>
</div>
</div>
</div>
</div>
<div className='ml-10 mr-10 mt-10 mx-auto bg-white rounded-xl shadow-sm px-6 py-5 nice-shadow'>
{selectedSubPage === 'general' && <AssignmentTaskGeneralEdit />}
</div>
</div>
)}
{Object.keys(assignmentTaskState.assignmentTask).length == 0 && (
@ -51,4 +79,255 @@ function AssignmentTaskEditor({ page }: any) {
)
}
function AssignmentTaskGeneralEdit() {
const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
const assignmentTaskState = useAssignmentsTask() as any
const assignmentTaskStateHook = useAssignmentsTaskDispatch() as any
const assignment = useAssignments() as any
const validate = (values: any) => {
const errors: any = {};
if (values.max_grade_value < 20 || values.max_grade_value > 100) {
errors.max_grade_value = 'Value should be between 20 and 100';
}
return errors;
};
const formik = useFormik({
initialValues: {
title: assignmentTaskState.assignmentTask.title,
description: assignmentTaskState.assignmentTask.description,
hint: assignmentTaskState.assignmentTask.hint,
max_grade_value: assignmentTaskState.assignmentTask.max_grade_value,
},
validate,
onSubmit: async values => {
const res = await updateAssignmentTask(values, assignmentTaskState.assignmentTask.assignment_task_uuid, assignment.assignment_object.assignment_uuid, access_token)
if (res) {
assignmentTaskStateHook({ type: 'reload' })
}
else {
toast.error('Error updating task, please retry later.')
}
},
enableReinitialize: true,
}) as any;
return (
<FormLayout onSubmit={formik.handleSubmit}>
<FormField name="title">
<FormLabelAndMessage label="Title" message={formik.errors.title} />
<Form.Control asChild>
<Input
onChange={formik.handleChange}
value={formik.values.title}
type="text"
/>
</Form.Control>
</FormField>
<FormField name="description">
<FormLabelAndMessage label="Description" message={formik.errors.description} />
<Form.Control asChild>
<Input
onChange={formik.handleChange}
value={formik.values.description}
type="text"
/>
</Form.Control>
</FormField>
<FormField name="hint">
<FormLabelAndMessage label="Hint" message={formik.errors.hint} />
<Form.Control asChild>
<Textarea
onChange={formik.handleChange}
value={formik.values.hint}
/>
</Form.Control>
</FormField>
<FormField name="hint">
<div className='flex space-x-3 justify-between items-center'>
<FormLabelAndMessage label="Reference file" message={formik.errors.hint} />
<div className='flex 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>
</div>
<Form.Control asChild>
<UpdateTaskRef />
</Form.Control>
</FormField>
<FormField name="max_grade_value">
<FormLabelAndMessage label="Max Grade Value" message={formik.errors.max_grade_value} />
<Form.Control asChild>
<Input
onChange={formik.handleChange}
value={formik.values.max_grade_value}
type="number"
/>
</Form.Control>
</FormField>
{/* Submit button */}
<Form.Submit >
<button
type="submit"
className="flex items-center justify-center w-full px-4 py-2 mt-4 font-semibold text-white bg-green-500 rounded-md hover:bg-green-600"
>
Submit
</button>
</Form.Submit>
</FormLayout>
)
}
function UpdateTaskRef() {
const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
const assignmentTaskState = useAssignmentsTask() as any
const assignmentTaskStateHook = useAssignmentsTaskDispatch() as any
const assignment = useAssignments() as any
const [isLoading, setIsLoading] = React.useState(false)
const [error, setError] = React.useState('') as any
const [localRefFile, setLocalRefFile] = React.useState(null) as any
const [activity, setActivity] = React.useState('') as any
const handleFileChange = async (event: any) => {
const file = event.target.files[0]
setLocalRefFile(file)
setIsLoading(true)
const res = await updateReferenceFile(
file,
assignmentTaskState.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('')
}
}
const deleteReferenceFile = async () => {
setIsLoading(true)
const res = await updateReferenceFile(
'',
assignmentTaskState.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 getActivityUI() {
const res = await getActivity(assignment.assignment_object.activity_id, null, access_token)
console.log(res)
setActivity(res.data)
}
useEffect(() => {
getActivityUI()
console.log(assignment.assignment_object.assignment_uuid)
console.log(assignmentTaskState.assignmentTask.assignment_task_uuid)
}
, [assignmentTaskState])
return (
<div className="w-auto bg-gray-50 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>
{assignmentTaskState.assignmentTask.reference_file && (
<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>
<File size={20} className='' />
<div className='font-semibold text-sm uppercase'>
{assignmentTaskState.assignmentTask.reference_file.split('.').pop()}
</div>
<div className='flex space-x-2 mt-2'>
<Link
href={''}
//href={getTaskRefFileDir(assignment.assignment_object.assignment_uuid, assignmentTaskState.assignmentTask.reference_file)}
className='bg-blue-500 text-white px-3 py-1 rounded-full text-xs font-semibold'>Download</Link>
{/** <button onClick={() => deleteReferenceFile()}
className='bg-red-500 text-white px-3 py-1 rounded-full text-xs font-semibold'>Delete</button> */}
</div>
</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>Change Reference File</span>
</button>
</div>
)}
</div>
</div>
</div>
)
}
export default AssignmentTaskEditor

View file

@ -3,10 +3,11 @@ import Modal from '@components/StyledElements/Modal/Modal';
import { FileUp, ListTodo, PanelLeftOpen, Plus } from 'lucide-react';
import React, { useEffect } from 'react'
import NewTaskModal from './Modals/NewTaskModal';
import { useAssignmentsTaskDispatch } from '@components/Contexts/Assignments/AssignmentsTaskContext';
import { useAssignmentsTask, useAssignmentsTaskDispatch } from '@components/Contexts/Assignments/AssignmentsTaskContext';
function AssignmentTasks({ assignment_uuid }: any) {
const assignments = useAssignments() as any;
const assignmentTask = useAssignmentsTask() as any;
const assignmentTaskHook = useAssignmentsTaskDispatch() as any;
const [isNewTaskModalOpen, setIsNewTaskModalOpen] = React.useState(false)
@ -21,30 +22,7 @@ function AssignmentTasks({ assignment_uuid }: any) {
return (
<div className='flex w-full'>
<div className='flex flex-col space-y-3 mx-auto'>
{assignments && assignments?.assignment_tasks?.map((task: any) => {
return (
<div
key={task.id}
className='flex flex-col w-[250px] nice-shadow bg-white shadow-[0px_4px_16px_rgba(0,0,0,0.06)] p-3 rounded-md'
onClick={() => setSelectTask(task.assignment_task_uuid)}
>
<div className='flex items-center px-2 justify-between'>
<div className="flex space-x-3 items-center">
<div className='text-gray-500'>
{task.assignment_type === 'QUIZ' && <ListTodo size={15} />}
{task.assignment_type === 'FILE_SUBMISSION' && <FileUp size={15} />}
</div>
<div className='font-semibold text-sm'>{task.title}</div>
</div>
<button className="outline outline-1 outline-gray-200 hover:bg-slate-100/50 rounded-md text-gray-500 font-bold py-2 px-3 focus:bg-slate-100 ease-linear transition-all">
<PanelLeftOpen size={16} />
</button>
</div>
</div>
)
})}
<Modal
{assignments && assignments?.assignment_tasks?.length < 10 && (<Modal
isDialogOpen={isNewTaskModalOpen}
onOpenChange={setIsNewTaskModalOpen}
minHeight="sm"
@ -60,7 +38,31 @@ function AssignmentTasks({ assignment_uuid }: any) {
<p>Add Task</p>
</div>
}
/>
/>)}
{assignments && assignments?.assignment_tasks?.map((task: any) => {
return (
<div
key={task.id}
className='flex flex-col w-[250px] nice-shadow bg-white shadow-[0px_4px_16px_rgba(0,0,0,0.06)] p-3 rounded-md'
onClick={() => setSelectTask(task.assignment_task_uuid)}
>
<div className='flex items-center px-2 justify-between'>
<div className="flex space-x-3 items-center">
<div className='text-gray-500'>
{task.assignment_type === 'QUIZ' && <ListTodo size={15} />}
{task.assignment_type === 'FILE_SUBMISSION' && <FileUp size={15} />}
</div>
<div className='font-semibold text-sm'>{task.title}</div>
</div>
<button className={`outline outline-1 outline-gray-200 ${task.assignment_task_uuid == assignmentTask.selectedAssignmentTaskUUID ? 'bg-slate-100' : ''} hover:bg-slate-100/50 rounded-md text-gray-500 font-bold py-2 px-3 ease-linear transition-all`}>
<PanelLeftOpen size={16} />
</button>
</div>
</div>
)
})}
</div>

View file

@ -40,32 +40,32 @@ function AssignmentEdit() {
slateBlack
sideOffset={10}
content="Make your Assignment unavailable for students" >
<div 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>
<div 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>
</div>
</div>
</div>
</div>
<div className="flex h-full w-full">
<AssignmentsTaskProvider>
<div className='flex w-[400px] flex-col h-full custom-dots-bg'>
<div className='flex mx-auto px-3.5 py-1 bg-neutral-600/80 space-x-2 my-5 items-center text-sm font-bold text-white rounded-full'>
<LayoutList size={18} />
<p>Tasks</p>
</div>
<AssignmentProvider assignment_uuid={'assignment_' + params.assignmentuuid}>
<AssignmentProvider assignment_uuid={'assignment_' + params.assignmentuuid}>
<AssignmentsTaskProvider>
<div className='flex w-[400px] flex-col h-full custom-dots-bg'>
<div className='flex mx-auto px-3.5 py-1 bg-neutral-600/80 space-x-2 my-5 items-center text-sm font-bold text-white rounded-full'>
<LayoutList size={18} />
<p>Tasks</p>
</div>
<AssignmentTasks assignment_uuid={'assignment_' + params.assignmentuuid} />
</AssignmentProvider>
</div>
<div className='flex flex-grow bg-[#fefcfe] nice-shadow h-full w-full'>
<AssignmentProvider assignment_uuid={'assignment_' + params.assignmentuuid}>
<AssignmentTaskEditor task_uuid='UUID' page='overview' />
</AssignmentProvider>
</div>
</AssignmentsTaskProvider>
</div>
<div className='flex flex-grow bg-[#fefcfe] nice-shadow h-full w-full'>
<AssignmentProvider assignment_uuid={'assignment_' + params.assignmentuuid}>
<AssignmentTaskEditor page='general' />
</AssignmentProvider>
</div>
</AssignmentsTaskProvider>
</AssignmentProvider>
</div>
</div>
)