diff --git a/apps/api/src/db/courses/assignments.py b/apps/api/src/db/courses/assignments.py
index fc288e36..a7dbc221 100644
--- a/apps/api/src/db/courses/assignments.py
+++ b/apps/api/src/db/courses/assignments.py
@@ -102,11 +102,7 @@ class AssignmentTaskBase(SQLModel):
contents: Dict = Field(default={}, sa_column=Column(JSON))
max_grade_value: int = 0 # Value is always between 0-100
- assignment_id: int
- org_id: int
- course_id: int
- chapter_id: int
- activity_id: int
+
class AssignmentTaskCreate(AssignmentTaskBase):
diff --git a/apps/api/src/routers/courses/assignments.py b/apps/api/src/routers/courses/assignments.py
index f627d099..e238449c 100644
--- a/apps/api/src/routers/courses/assignments.py
+++ b/apps/api/src/routers/courses/assignments.py
@@ -24,6 +24,7 @@ from src.services.courses.activities.assignments import (
read_assignment,
read_assignment_from_activity_uuid,
read_assignment_submissions,
+ read_assignment_task,
read_assignment_task_submissions,
read_assignment_tasks,
read_user_assignment_submissions,
@@ -151,6 +152,20 @@ async def api_read_assignment_tasks(
)
+@router.get("/task/{assignment_task_uuid}")
+async def api_read_assignment_task(
+ request: Request,
+ assignment_task_uuid: str,
+ current_user: PublicUser = Depends(get_current_user),
+ db_session=Depends(get_db_session),
+):
+ """
+ Read task for an assignment
+ """
+ return await read_assignment_task(
+ request, assignment_task_uuid, current_user, db_session
+ )
+
@router.put("/{assignment_uuid}/tasks/{task_uuid}")
async def api_update_assignment_tasks(
request: Request,
diff --git a/apps/api/src/services/courses/activities/assignments.py b/apps/api/src/services/courses/activities/assignments.py
index 46102cf7..6414a222 100644
--- a/apps/api/src/services/courses/activities/assignments.py
+++ b/apps/api/src/services/courses/activities/assignments.py
@@ -315,6 +315,10 @@ async def create_assignment_task(
assignment_task.creation_date = str(datetime.now())
assignment_task.update_date = str(datetime.now())
assignment_task.org_id = course.org_id
+ assignment_task.chapter_id = assignment.chapter_id
+ assignment_task.activity_id = assignment.activity_id
+ assignment_task.assignment_id = assignment.id # type: ignore
+ assignment_task.course_id = assignment.course_id
# Insert Assignment Task in DB
db_session.add(assignment_task)
@@ -365,6 +369,48 @@ async def read_assignment_tasks(
for assignment_task in db_session.exec(statement).all()
]
+async def read_assignment_task(
+ request: Request,
+ assignment_task_uuid: str,
+ current_user: PublicUser | AnonymousUser,
+ db_session: Session,
+):
+ # Find assignment
+ statement = select(AssignmentTask).where(AssignmentTask.assignment_task_uuid == assignment_task_uuid)
+ assignmenttask = db_session.exec(statement).first()
+
+ if not assignmenttask:
+ raise HTTPException(
+ status_code=404,
+ detail="Assignment Task not found",
+ )
+
+ # Check if assignment exists
+ statement = select(Assignment).where(Assignment.id == assignmenttask.assignment_id)
+ assignment = db_session.exec(statement).first()
+
+ if not assignment:
+ raise HTTPException(
+ status_code=404,
+ detail="Assignment not found",
+ )
+
+ # 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",
+ )
+
+ # RBAC check
+ await rbac_check(request, course.course_uuid, current_user, "read", db_session)
+
+ # return assignment task read
+ return AssignmentTaskRead.model_validate(assignmenttask)
+
async def update_assignment_task(
request: Request,
diff --git a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/Modals/NewTaskModal.tsx b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/Modals/NewTaskModal.tsx
new file mode 100644
index 00000000..84a7e66f
--- /dev/null
+++ b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/Modals/NewTaskModal.tsx
@@ -0,0 +1,77 @@
+import { useLHSession } from '@components/Contexts/LHSessionContext';
+import { getAPIUrl } from '@services/config/config';
+import { createAssignmentTask } from '@services/courses/assignments'
+import { AArrowUp, FileUp, ListTodo } from 'lucide-react'
+import React from 'react'
+import toast from 'react-hot-toast';
+import { mutate } from 'swr';
+
+function NewTaskModal({ closeModal, assignment_uuid }: any) {
+ const session = useLHSession() as any;
+ const access_token = session?.data?.tokens?.access_token;
+ const reminderShownRef = React.useRef(false);
+
+ function showReminderToast() {
+ // Check if the reminder has already been shown using sessionStorage
+ if (sessionStorage.getItem("TasksReminderShown") !== "true") {
+ setTimeout(() => {
+ toast('When editing/adding your tasks, make sure to Unpublish your Assignment to avoid any issues with students, you can Publish it again when you are ready.',
+ { icon: '✋', duration: 10000, style: { minWidth: 600 } });
+ // Mark the reminder as shown in sessionStorage
+ sessionStorage.setItem("TasksReminderShown", "true");
+ }, 3000);
+ }
+ }
+
+ async function createTask(type: string) {
+ const task_object = {
+ title: "Untitled Task",
+ description: "",
+ hint: "",
+ reference_file: "",
+ assignment_type: type,
+ contents: {},
+ max_grade_value: 100,
+ }
+ await createAssignmentTask(task_object, assignment_uuid, access_token)
+ toast.success('Task created successfully')
+ showReminderToast()
+ mutate(`${getAPIUrl()}assignments/${assignment_uuid}/tasks`)
+ closeModal(false)
+ }
+
+
+ return (
+
+
createTask('QUIZ')}
+ className='flex flex-col space-y-2 justify-center text-center pt-10'>
+
+
+
+
Quiz
+
Questions with multiple choice answers
+
+
createTask('FILE_SUBMISSION')}
+ className='flex flex-col space-y-2 justify-center text-center pt-10'>
+
+
+
+
File submissions
+
Students can submit files for this task
+
+
toast.error('Forms are not yet supported')}
+ className='flex flex-col space-y-2 justify-center text-center pt-10 opacity-25'>
+
+
Forms
+
Forms for students to fill out
+
+
+ )
+}
+
+export default NewTaskModal
\ No newline at end of file
diff --git a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor.tsx b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor.tsx
index c76be5e6..c9e917b5 100644
--- a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor.tsx
+++ b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor.tsx
@@ -1,30 +1,52 @@
'use client';
-import { Info, Link } from 'lucide-react'
-import React from 'react'
+import { useAssignmentsTask } from '@components/Contexts/Assignments/AssignmentsTaskContext';
+import { Info, TentTree } from 'lucide-react'
+import React, { useEffect } from 'react'
-function AssignmentTaskEditor({ task_uuid, page }: any) {
+function AssignmentTaskEditor({ page }: any) {
const [selectedSubPage, setSelectedSubPage] = React.useState(page)
+ const assignmentTaskState = useAssignmentsTask() as any
+
+ useEffect(() => {
+ console.log(assignmentTaskState)
+ }
+ , [assignmentTaskState])
+
return (
-
-
-
- Assignment Test #1
-
-
-
-
-
-
Overview
+ {assignmentTaskState.assignmentTask && Object.keys(assignmentTaskState.assignmentTask).length > 0 && (
+
+
+ Assignment Test #1
+
+
-
+ )}
+ {Object.keys(assignmentTaskState.assignmentTask).length == 0 && (
+
+
+
+
+
+ No Task Selected
+
+
+
+
+ )}
+
)
}
diff --git a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/Tasks.tsx b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/Tasks.tsx
index f2933817..1236cd45 100644
--- a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/Tasks.tsx
+++ b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/Tasks.tsx
@@ -1,12 +1,20 @@
import { useAssignments } from '@components/Contexts/Assignments/AssignmentContext'
-import { Plus } from 'lucide-react';
+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';
-function AssignmentTasks() {
+function AssignmentTasks({ assignment_uuid }: any) {
const assignments = useAssignments() as any;
+ const assignmentTaskHook = useAssignmentsTaskDispatch() as any;
+ const [isNewTaskModalOpen, setIsNewTaskModalOpen] = React.useState(false)
+
+ async function setSelectTask(task_uuid: string) {
+ assignmentTaskHook({ type: 'setSelectedAssignmentTaskUUID', payload: task_uuid })
+ }
useEffect(() => {
- console.log(assignments)
}, [assignments])
@@ -15,19 +23,47 @@ function AssignmentTasks() {
{assignments && assignments?.assignment_tasks?.map((task: any) => {
return (
-
-
-
{task.title}
+
setSelectTask(task.assignment_task_uuid)}
+ >
+
+
+
+ {task.assignment_type === 'QUIZ' && }
+ {task.assignment_type === 'FILE_SUBMISSION' && }
+
+
{task.title}
+
+
)
})}
-
+
+
+ }
+ dialogTitle="Add an Assignment Task"
+ dialogDescription="Create a new task for this assignment"
+ dialogTrigger={
+
+ }
+ />
+
-
+
)
}
diff --git a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/page.tsx b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/page.tsx
index cf7f609a..47f9793e 100644
--- a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/page.tsx
+++ b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/page.tsx
@@ -1,12 +1,13 @@
'use client';
import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'
-import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement'
-import { BookOpen, BookOpenCheck, BookX, Check, Ellipsis, EllipsisVertical, GalleryVerticalEnd, Info, LayoutList, UserRoundCog } from 'lucide-react'
+import { BookOpen, BookX, EllipsisVertical, LayoutList } from 'lucide-react'
import React from 'react'
import AssignmentTaskEditor from './_components/TaskEditor';
import { AssignmentProvider } from '@components/Contexts/Assignments/AssignmentContext';
import AssignmentTasks from './_components/Tasks';
import { useParams } from 'next/navigation';
+import { AssignmentsTaskProvider } from '@components/Contexts/Assignments/AssignmentsTaskContext';
+import ToolTip from '@components/StyledElements/Tooltip/Tooltip';
function AssignmentEdit() {
const params = useParams<{ assignmentuuid: string; }>()
@@ -24,33 +25,47 @@ function AssignmentEdit() {
-
-
)
diff --git a/apps/web/components/Contexts/Assignments/AssignmentsTaskContext.tsx b/apps/web/components/Contexts/Assignments/AssignmentsTaskContext.tsx
new file mode 100644
index 00000000..64910a86
--- /dev/null
+++ b/apps/web/components/Contexts/Assignments/AssignmentsTaskContext.tsx
@@ -0,0 +1,80 @@
+'use client'
+import React, { createContext, useContext, useEffect, useReducer } from 'react'
+import { useLHSession } from '@components/Contexts/LHSessionContext'
+import { getAssignmentTask } from '@services/courses/assignments'
+
+interface State {
+ selectedAssignmentTaskUUID: string | null;
+ assignmentTask: Record
;
+}
+
+interface Action {
+ type: string;
+ payload?: any;
+}
+
+const initialState: State = {
+ selectedAssignmentTaskUUID: null,
+ assignmentTask: {}
+};
+
+export const AssignmentsTaskContext = createContext(undefined);
+export const AssignmentsTaskDispatchContext = createContext | undefined>(undefined);
+
+export function AssignmentsTaskProvider({ children }: { children: React.ReactNode }) {
+ const session = useLHSession() as any;
+ const access_token = session?.data?.tokens?.access_token;
+
+ const [state, dispatch] = useReducer(assignmentstaskReducer, initialState);
+
+ async function fetchAssignmentTask(assignmentTaskUUID: string) {
+ const res = await getAssignmentTask(assignmentTaskUUID, access_token);
+ if (res.success) {
+ dispatch({ type: 'setAssignmentTask', payload: res });
+ }
+ }
+
+ useEffect(() => {
+ if (state.selectedAssignmentTaskUUID) {
+ fetchAssignmentTask(state.selectedAssignmentTaskUUID);
+ }
+ }, [state.selectedAssignmentTaskUUID]);
+
+ return (
+
+
+ {children}
+
+
+ );
+}
+
+export function useAssignmentsTask() {
+ const context = useContext(AssignmentsTaskContext);
+ if (context === undefined) {
+ throw new Error('useAssignmentsTask must be used within an AssignmentsTaskProvider');
+ }
+ return context;
+}
+
+export function useAssignmentsTaskDispatch() {
+ const context = useContext(AssignmentsTaskDispatchContext);
+ if (context === undefined) {
+ throw new Error('useAssignmentsTaskDispatch must be used within an AssignmentsTaskProvider');
+ }
+ return context;
+}
+
+function assignmentstaskReducer(state: State, action: Action): State {
+ switch (action.type) {
+ case 'setSelectedAssignmentTaskUUID':
+ return { ...state, selectedAssignmentTaskUUID: action.payload };
+ case 'setAssignmentTask':
+ return { ...state, assignmentTask: action.payload };
+ case 'reload':
+ return { ...state };
+ default:
+ return state;
+ }
+}
+
diff --git a/apps/web/services/courses/assignments.ts b/apps/web/services/courses/assignments.ts
index f73f57ab..db88b95d 100644
--- a/apps/web/services/courses/assignments.ts
+++ b/apps/web/services/courses/assignments.ts
@@ -64,3 +64,15 @@ export async function createAssignmentTask(
const res = await getResponseMetadata(result)
return res
}
+
+export async function getAssignmentTask(
+ assignmentTaskUUID: string,
+ access_token: string
+) {
+ const result: any = await fetch(
+ `${getAPIUrl()}assignments/task/${assignmentTaskUUID}`,
+ RequestBodyWithAuthHeader('GET', null, null, access_token)
+ )
+ const res = await getResponseMetadata(result)
+ return res
+}