mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: enhance assignment page with edit functionality and description display
This commit is contained in:
parent
67ac0b9d67
commit
0229380cba
3 changed files with 285 additions and 39 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
import BreadCrumbs from '@components/Dashboard/Misc/BreadCrumbs'
|
import BreadCrumbs from '@components/Dashboard/Misc/BreadCrumbs'
|
||||||
import { BookOpen, BookX, EllipsisVertical, Eye, Layers2, Monitor, UserRoundPen } from 'lucide-react'
|
import { ArrowRight, BookOpen, BookX, EllipsisVertical, Eye, Layers2, Monitor, Pencil, UserRoundPen } 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 ToolTip from '@components/Objects/StyledElements/Tooltip/Tooltip';
|
import ToolTip from '@components/Objects/StyledElements/Tooltip/Tooltip';
|
||||||
|
|
@ -16,6 +16,7 @@ import { updateActivity } from '@services/courses/activities';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import AssignmentEditorSubPage from './subpages/AssignmentEditorSubPage';
|
import AssignmentEditorSubPage from './subpages/AssignmentEditorSubPage';
|
||||||
import { useMediaQuery } from 'usehooks-ts';
|
import { useMediaQuery } from 'usehooks-ts';
|
||||||
|
import EditAssignmentModal from '@components/Objects/Modals/Activities/Assignments/EditAssignmentModal';
|
||||||
const AssignmentSubmissionsSubPage = dynamic(() => import('./subpages/AssignmentSubmissionsSubPage'))
|
const AssignmentSubmissionsSubPage = dynamic(() => import('./subpages/AssignmentSubmissionsSubPage'))
|
||||||
|
|
||||||
function AssignmentEdit() {
|
function AssignmentEdit() {
|
||||||
|
|
@ -46,7 +47,9 @@ function AssignmentEdit() {
|
||||||
<div className="pl-10 mr-10 tracking-tighter">
|
<div className="pl-10 mr-10 tracking-tighter">
|
||||||
<BrdCmpx />
|
<BrdCmpx />
|
||||||
<div className="w-100 flex justify-between">
|
<div className="w-100 flex justify-between">
|
||||||
<div className="flex font-bold text-2xl">Assignment Tools </div>
|
<div className="flex font-bold text-2xl">
|
||||||
|
<AssignmentTitle />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col justify-center antialiased'>
|
<div className='flex flex-col justify-center antialiased'>
|
||||||
|
|
@ -106,6 +109,7 @@ function PublishingState() {
|
||||||
const assignment = useAssignments() as any;
|
const assignment = useAssignments() as any;
|
||||||
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 [isEditModalOpen, setIsEditModalOpen] = React.useState(false);
|
||||||
|
|
||||||
async function updateAssignmentPublishState(assignmentUUID: string) {
|
async function updateAssignmentPublishState(assignmentUUID: string) {
|
||||||
const res = await updateAssignment({ published: !assignment?.assignment_object?.published }, assignmentUUID, access_token)
|
const res = await updateAssignment({ published: !assignment?.assignment_object?.published }, assignmentUUID, access_token)
|
||||||
|
|
@ -125,50 +129,83 @@ function PublishingState() {
|
||||||
}, [assignment])
|
}, [assignment])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex mx-auto mt-5 items-center space-x-4'>
|
<>
|
||||||
<div className={`flex text-xs rounded-full px-3.5 py-2 mx-auto font-bold outline outline-1 ${!assignment?.assignment_object?.published ? 'outline-gray-300 bg-gray-200/60' : 'outline-green-300 bg-green-200/60'}`}>
|
<div className='flex mx-auto mt-5 items-center space-x-4'>
|
||||||
{assignment?.assignment_object?.published ? 'Published' : 'Unpublished'}
|
<div className={`flex text-xs rounded-full px-3.5 py-2 mx-auto font-bold outline outline-1 ${!assignment?.assignment_object?.published ? 'outline-gray-300 bg-gray-200/60' : 'outline-green-300 bg-green-200/60'}`}>
|
||||||
</div>
|
{assignment?.assignment_object?.published ? 'Published' : 'Unpublished'}
|
||||||
<div><EllipsisVertical className='text-gray-500' size={13} /></div>
|
|
||||||
|
|
||||||
<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-linear-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 && <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-linear-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>
|
||||||
</ToolTip>}
|
<div><EllipsisVertical className='text-gray-500' size={13} /></div>
|
||||||
{!assignment?.assignment_object?.published &&
|
|
||||||
<ToolTip
|
<ToolTip
|
||||||
side='left'
|
side='left'
|
||||||
slateBlack
|
slateBlack
|
||||||
sideOffset={10}
|
sideOffset={10}
|
||||||
content="Make your Assignment public and available for students" >
|
content="Edit Assignment Details">
|
||||||
|
<div
|
||||||
|
onClick={() => setIsEditModalOpen(true)}
|
||||||
|
className='flex px-3 py-2 cursor-pointer rounded-md space-x-2 items-center bg-linear-to-bl text-blue-800 font-medium from-blue-400/50 to-blue-200/80 border border-blue-600/10 shadow-blue-900/10 shadow-lg'>
|
||||||
|
<Pencil size={18} />
|
||||||
|
<p className='text-sm font-bold'>Edit</p>
|
||||||
|
</div>
|
||||||
|
</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-linear-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 && <ToolTip
|
||||||
|
side='left'
|
||||||
|
slateBlack
|
||||||
|
sideOffset={10}
|
||||||
|
content="Make your Assignment unavailable for students" >
|
||||||
<div
|
<div
|
||||||
onClick={() => updateAssignmentPublishState(assignment?.assignment_object?.assignment_uuid)}
|
onClick={() => updateAssignmentPublishState(assignment?.assignment_object?.assignment_uuid)}
|
||||||
className='flex px-3 py-2 cursor-pointer rounded-md space-x-2 items-center bg-linear-to-bl text-green-800 font-medium from-green-400/50 to-lime-200/80 border border-green-600/10 shadow-green-900/10 shadow-lg'>
|
className='flex px-3 py-2 cursor-pointer rounded-md space-x-2 items-center bg-linear-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'>
|
||||||
<BookOpen size={18} />
|
<BookX size={18} />
|
||||||
<p className=' text-sm font-bold'>Publish</p>
|
<p className='text-sm font-bold'>Unpublish</p>
|
||||||
</div>
|
</div>
|
||||||
</ToolTip>}
|
</ToolTip>}
|
||||||
</div>
|
{!assignment?.assignment_object?.published &&
|
||||||
|
<ToolTip
|
||||||
|
side='left'
|
||||||
|
slateBlack
|
||||||
|
sideOffset={10}
|
||||||
|
content="Make your Assignment public and available 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-linear-to-bl text-green-800 font-medium from-green-400/50 to-lime-200/80 border border-green-600/10 shadow-green-900/10 shadow-lg'>
|
||||||
|
<BookOpen size={18} />
|
||||||
|
<p className=' text-sm font-bold'>Publish</p>
|
||||||
|
</div>
|
||||||
|
</ToolTip>}
|
||||||
|
</div>
|
||||||
|
{isEditModalOpen && (
|
||||||
|
<EditAssignmentModal
|
||||||
|
isOpen={isEditModalOpen}
|
||||||
|
onClose={() => setIsEditModalOpen(false)}
|
||||||
|
assignment={assignment?.assignment_object}
|
||||||
|
accessToken={access_token}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function AssignmentTitle() {
|
||||||
|
const assignment = useAssignments() as any;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
Assignment Tools
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,22 @@ function AssignmentStudentActivity() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='w-full rounded-full bg-slate-500/5 nice-shadow h-[2px]'></div>
|
|
||||||
|
|
||||||
|
{assignments?.assignment_object?.description && (
|
||||||
|
<div className='flex flex-col space-y-2 p-4 md:p-6 bg-slate-100/30 rounded-md nice-shadow'>
|
||||||
|
<div className='flex flex-col space-y-3'>
|
||||||
|
<div className='flex items-center gap-2 text-slate-700'>
|
||||||
|
<Info size={16} className="text-slate-500" />
|
||||||
|
<h3 className='text-sm font-semibold'>Assignment Description</h3>
|
||||||
|
</div>
|
||||||
|
<div className='pl-6'>
|
||||||
|
<p className='text-sm leading-relaxed text-slate-600'>{assignments.assignment_object.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
{assignments && assignments?.assignment_tasks?.sort((a: any, b: any) => a.id - b.id).map((task: any, index: number) => {
|
{assignments && assignments?.assignment_tasks?.sort((a: any, b: any) => a.id - b.id).map((task: any, index: number) => {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,194 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { updateAssignment } from '@services/courses/assignments';
|
||||||
|
import { mutate } from 'swr';
|
||||||
|
import { getAPIUrl } from '@services/config/config';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import FormLayout, {
|
||||||
|
FormField,
|
||||||
|
FormLabelAndMessage,
|
||||||
|
Input,
|
||||||
|
Textarea,
|
||||||
|
Flex,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage
|
||||||
|
} from '@components/Objects/StyledElements/Form/Form';
|
||||||
|
import * as Form from '@radix-ui/react-form';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import Modal from '@components/Objects/StyledElements/Modal/Modal';
|
||||||
|
|
||||||
|
interface Assignment {
|
||||||
|
assignment_uuid: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
due_date?: string;
|
||||||
|
grading_type?: 'ALPHABET' | 'NUMERIC' | 'PERCENTAGE';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EditAssignmentFormProps {
|
||||||
|
onClose: () => void;
|
||||||
|
assignment: Assignment;
|
||||||
|
accessToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EditAssignmentModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
assignment: Assignment;
|
||||||
|
accessToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditAssignmentForm: React.FC<EditAssignmentFormProps> = ({
|
||||||
|
onClose,
|
||||||
|
assignment,
|
||||||
|
accessToken
|
||||||
|
}) => {
|
||||||
|
const formik = useFormik({
|
||||||
|
initialValues: {
|
||||||
|
title: assignment.title || '',
|
||||||
|
description: assignment.description || '',
|
||||||
|
due_date: assignment.due_date || '',
|
||||||
|
grading_type: assignment.grading_type || 'ALPHABET'
|
||||||
|
},
|
||||||
|
enableReinitialize: true,
|
||||||
|
onSubmit: async (values, { setSubmitting }) => {
|
||||||
|
const toast_loading = toast.loading('Updating assignment...');
|
||||||
|
try {
|
||||||
|
const res = await updateAssignment(values, assignment.assignment_uuid, accessToken);
|
||||||
|
if (res.success) {
|
||||||
|
mutate(`${getAPIUrl()}assignments/${assignment.assignment_uuid}`);
|
||||||
|
toast.success('Assignment updated successfully');
|
||||||
|
onClose();
|
||||||
|
} else {
|
||||||
|
toast.error('Failed to update assignment');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error('An error occurred while updating the assignment');
|
||||||
|
} finally {
|
||||||
|
toast.dismiss(toast_loading);
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormLayout onSubmit={formik.handleSubmit}>
|
||||||
|
<FormField name="title">
|
||||||
|
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
|
||||||
|
<FormLabel>Assignment Title</FormLabel>
|
||||||
|
<FormMessage match="valueMissing">
|
||||||
|
Please provide a name for your assignment
|
||||||
|
</FormMessage>
|
||||||
|
</Flex>
|
||||||
|
<Form.Control asChild>
|
||||||
|
<Input
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.title}
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField name="description">
|
||||||
|
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
|
||||||
|
<FormLabel>Assignment Description</FormLabel>
|
||||||
|
<FormMessage match="valueMissing">
|
||||||
|
Please provide a description for your assignment
|
||||||
|
</FormMessage>
|
||||||
|
</Flex>
|
||||||
|
<Form.Control asChild>
|
||||||
|
<Textarea
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.description}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField name="due_date">
|
||||||
|
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
|
||||||
|
<FormLabel>Due Date</FormLabel>
|
||||||
|
<FormMessage match="valueMissing">
|
||||||
|
Please provide a due date for your assignment
|
||||||
|
</FormMessage>
|
||||||
|
</Flex>
|
||||||
|
<Form.Control asChild>
|
||||||
|
<Input
|
||||||
|
type="date"
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.due_date}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField name="grading_type">
|
||||||
|
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
|
||||||
|
<FormLabel>Grading Type</FormLabel>
|
||||||
|
<FormMessage match="valueMissing">
|
||||||
|
Please provide a grading type for your assignment
|
||||||
|
</FormMessage>
|
||||||
|
</Flex>
|
||||||
|
<select
|
||||||
|
id="grading_type"
|
||||||
|
name="grading_type"
|
||||||
|
className='w-full bg-gray-100/40 rounded-lg px-3 py-2 outline outline-1 outline-gray-100'
|
||||||
|
onChange={(e) => formik.setFieldValue('grading_type', e.target.value, true)}
|
||||||
|
value={formik.values.grading_type}
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="ALPHABET">Alphabet</option>
|
||||||
|
<option value="NUMERIC">Numeric</option>
|
||||||
|
<option value="PERCENTAGE">Percentage</option>
|
||||||
|
</select>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<div className="flex justify-end space-x-3 mt-6">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClose}
|
||||||
|
className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-md"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<Form.Submit asChild>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={formik.isSubmitting}
|
||||||
|
className="px-4 py-2 bg-black text-white font-bold rounded-md hover:bg-black/90"
|
||||||
|
>
|
||||||
|
{formik.isSubmitting ? 'Saving...' : 'Save Changes'}
|
||||||
|
</button>
|
||||||
|
</Form.Submit>
|
||||||
|
</div>
|
||||||
|
</FormLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const EditAssignmentModal: React.FC<EditAssignmentModalProps> = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
assignment,
|
||||||
|
accessToken
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isDialogOpen={isOpen}
|
||||||
|
onOpenChange={onClose}
|
||||||
|
minHeight="md"
|
||||||
|
minWidth="lg"
|
||||||
|
dialogContent={
|
||||||
|
<EditAssignmentForm
|
||||||
|
onClose={onClose}
|
||||||
|
assignment={assignment}
|
||||||
|
accessToken={accessToken}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
dialogTitle="Edit Assignment"
|
||||||
|
dialogDescription="Update assignment details"
|
||||||
|
dialogTrigger={null}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditAssignmentModal;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue