diff --git a/apps/api/migrations/versions/d8bc71595932_add_reference_for_assignmenttasks.py b/apps/api/migrations/versions/d8bc71595932_add_reference_for_assignmenttasks.py
new file mode 100644
index 00000000..10634418
--- /dev/null
+++ b/apps/api/migrations/versions/d8bc71595932_add_reference_for_assignmenttasks.py
@@ -0,0 +1,31 @@
+"""Add reference for AssignmentTasks
+
+Revision ID: d8bc71595932
+Revises: 6295e05ff7d0
+Create Date: 2024-07-12 18:59:50.242716
+
+"""
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+import sqlmodel
+
+
+# revision identifiers, used by Alembic.
+revision: str = 'd8bc71595932'
+down_revision: Union[str, None] = '6295e05ff7d0'
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.add_column('assignmenttask', sa.Column('reference_file', sa.VARCHAR(), nullable=True))
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_column('assignmenttask', 'reference_file')
+ # ### end Alembic commands ###
diff --git a/apps/api/src/db/courses/activities.py b/apps/api/src/db/courses/activities.py
index 8e40e21a..d6d8f0d8 100644
--- a/apps/api/src/db/courses/activities.py
+++ b/apps/api/src/db/courses/activities.py
@@ -29,8 +29,8 @@ class ActivitySubTypeEnum(str, Enum):
class ActivityBase(SQLModel):
name: str
- activity_type: ActivityTypeEnum = ActivityTypeEnum.TYPE_CUSTOM
- activity_sub_type: ActivitySubTypeEnum = ActivitySubTypeEnum.SUBTYPE_CUSTOM
+ activity_type: ActivityTypeEnum
+ activity_sub_type: ActivitySubTypeEnum
content: dict = Field(default={}, sa_column=Column(JSON))
published: bool = False
@@ -51,12 +51,16 @@ class Activity(ActivityBase, table=True):
class ActivityCreate(ActivityBase):
chapter_id: int
+ activity_type: ActivityTypeEnum = ActivityTypeEnum.TYPE_CUSTOM
+ activity_sub_type: ActivitySubTypeEnum = ActivitySubTypeEnum.SUBTYPE_CUSTOM
pass
class ActivityUpdate(ActivityBase):
name: Optional[str]
content: dict = Field(default={}, sa_column=Column(JSON))
+ activity_type: Optional[ActivityTypeEnum]
+ activity_sub_type: Optional[ActivitySubTypeEnum]
published_version: Optional[int]
version: Optional[int]
diff --git a/apps/api/src/db/courses/assignments.py b/apps/api/src/db/courses/assignments.py
index 67b1f872..fc288e36 100644
--- a/apps/api/src/db/courses/assignments.py
+++ b/apps/api/src/db/courses/assignments.py
@@ -97,6 +97,7 @@ class AssignmentTaskBase(SQLModel):
title: str
description: str
hint: str
+ reference_file: Optional[str]
assignment_type: AssignmentTaskTypeEnum
contents: Dict = Field(default={}, sa_column=Column(JSON))
max_grade_value: int = 0 # Value is always between 0-100
@@ -108,7 +109,7 @@ class AssignmentTaskBase(SQLModel):
activity_id: int
-class AssignmentTaskCreate(AssignmentTaskBase ):
+class AssignmentTaskCreate(AssignmentTaskBase):
"""Model for creating a new assignment task."""
pass # Inherits all fields from AssignmentTaskBase
diff --git a/apps/api/src/routers/courses/assignments.py b/apps/api/src/routers/courses/assignments.py
index 1dba87ce..f627d099 100644
--- a/apps/api/src/routers/courses/assignments.py
+++ b/apps/api/src/routers/courses/assignments.py
@@ -22,6 +22,7 @@ from src.services.courses.activities.assignments import (
delete_assignment_task,
delete_assignment_task_submission,
read_assignment,
+ read_assignment_from_activity_uuid,
read_assignment_submissions,
read_assignment_task_submissions,
read_assignment_tasks,
@@ -62,6 +63,18 @@ async def api_read_assignment(
"""
return await read_assignment(request, assignment_uuid, current_user, db_session)
+@router.get("/activity/{activity_uuid}")
+async def api_read_assignment_from_activity(
+ request: Request,
+ activity_uuid: str,
+ current_user: PublicUser = Depends(get_current_user),
+ db_session=Depends(get_db_session),
+) -> AssignmentRead:
+ """
+ Read an assignment
+ """
+ return await read_assignment_from_activity_uuid(request, activity_uuid, current_user, db_session)
+
@router.put("/{assignment_uuid}")
async def api_update_assignment(
diff --git a/apps/api/src/services/courses/activities/assignments.py b/apps/api/src/services/courses/activities/assignments.py
index cb133190..46102cf7 100644
--- a/apps/api/src/services/courses/activities/assignments.py
+++ b/apps/api/src/services/courses/activities/assignments.py
@@ -104,6 +104,48 @@ async def read_assignment(
# return assignment read
return AssignmentRead.model_validate(assignment)
+async def read_assignment_from_activity_uuid(
+ request: Request,
+ activity_uuid: str,
+ current_user: PublicUser | AnonymousUser,
+ db_session: Session,
+):
+ # Check if activity exists
+ statement = select(Activity).where(Activity.activity_uuid == activity_uuid)
+ activity = db_session.exec(statement).first()
+
+ if not activity:
+ raise HTTPException(
+ status_code=404,
+ detail="Activity not found",
+ )
+
+ # Check if course exists
+ statement = select(Course).where(Course.id == activity.course_id)
+ course = db_session.exec(statement).first()
+
+ if not course:
+ raise HTTPException(
+ status_code=404,
+ detail="Course not found",
+ )
+
+ # Check if assignment exists
+ statement = select(Assignment).where(Assignment.activity_id == activity.id)
+ assignment = db_session.exec(statement).first()
+
+ if not assignment:
+ raise HTTPException(
+ status_code=404,
+ detail="Assignment not found",
+ )
+
+ # RBAC check
+ await rbac_check(request, course.course_uuid, current_user, "read", db_session)
+
+ # return assignment read
+ return AssignmentRead.model_validate(assignment)
+
async def update_assignment(
request: Request,
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
new file mode 100644
index 00000000..c76be5e6
--- /dev/null
+++ b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor.tsx
@@ -0,0 +1,32 @@
+'use client';
+import { Info, Link } from 'lucide-react'
+import React from 'react'
+
+function AssignmentTaskEditor({ task_uuid, page }: any) {
+ const [selectedSubPage, setSelectedSubPage] = React.useState(page)
+ return (
+
+
+
+
+ Assignment Test #1
+
+
+
+
+ )
+}
+
+export default AssignmentTaskEditor
\ No newline at end of file
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
new file mode 100644
index 00000000..f2933817
--- /dev/null
+++ b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/Tasks.tsx
@@ -0,0 +1,35 @@
+import { useAssignments } from '@components/Contexts/Assignments/AssignmentContext'
+import { Plus } from 'lucide-react';
+import React, { useEffect } from 'react'
+
+function AssignmentTasks() {
+ const assignments = useAssignments() as any;
+
+ useEffect(() => {
+ console.log(assignments)
+ }, [assignments])
+
+
+ return (
+
+
+ {assignments && assignments?.assignment_tasks?.map((task: any) => {
+ return (
+
+ )
+ })}
+
+
+
+
+ )
+}
+
+export default AssignmentTasks
\ No newline at end of file
diff --git a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/page.tsx b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/page.tsx
new file mode 100644
index 00000000..cf7f609a
--- /dev/null
+++ b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/page.tsx
@@ -0,0 +1,59 @@
+'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 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';
+
+function AssignmentEdit() {
+ const params = useParams<{ assignmentuuid: string; }>()
+ return (
+
+ )
+}
+
+export default AssignmentEdit
\ No newline at end of file
diff --git a/apps/web/app/orgs/[orgslug]/dash/assignments/page.tsx b/apps/web/app/orgs/[orgslug]/dash/assignments/page.tsx
new file mode 100644
index 00000000..c54c9cc9
--- /dev/null
+++ b/apps/web/app/orgs/[orgslug]/dash/assignments/page.tsx
@@ -0,0 +1,9 @@
+import React from 'react'
+
+function AssignmentsHome() {
+ return (
+ AssignmentsHome
+ )
+}
+
+export default AssignmentsHome
\ No newline at end of file
diff --git a/apps/web/app/orgs/[orgslug]/dash/courses/course/[courseuuid]/[subpage]/page.tsx b/apps/web/app/orgs/[orgslug]/dash/courses/course/[courseuuid]/[subpage]/page.tsx
index 9de28441..9b44a988 100644
--- a/apps/web/app/orgs/[orgslug]/dash/courses/course/[courseuuid]/[subpage]/page.tsx
+++ b/apps/web/app/orgs/[orgslug]/dash/courses/course/[courseuuid]/[subpage]/page.tsx
@@ -82,7 +82,6 @@ function CourseOverviewPage({ params }: { params: CourseOverviewParams }) {
-
diff --git a/apps/web/components/Contexts/Assignments/AssignmentContext.tsx b/apps/web/components/Contexts/Assignments/AssignmentContext.tsx
new file mode 100644
index 00000000..f54844ee
--- /dev/null
+++ b/apps/web/components/Contexts/Assignments/AssignmentContext.tsx
@@ -0,0 +1,40 @@
+'use client'
+import { getAPIUrl } from '@services/config/config'
+import { swrFetcher } from '@services/utils/ts/requests'
+import React, { createContext, useContext, useEffect } from 'react'
+import useSWR from 'swr'
+import { useLHSession } from '@components/Contexts/LHSessionContext'
+
+export const AssignmentContext = createContext({})
+
+export function AssignmentProvider({ children, assignment_uuid }: { children: React.ReactNode, assignment_uuid: string }) {
+ const session = useLHSession() as any
+ const accessToken = session?.data?.tokens?.access_token
+ const [assignmentsFull, setAssignmentsFull] = React.useState({ assignment_object: null, assignment_tasks: null })
+
+ const { data: assignment, error: assignmentError } = useSWR(
+ `${getAPIUrl()}assignments/${assignment_uuid}`,
+ (url) => swrFetcher(url, accessToken)
+ )
+
+ const { data: assignment_tasks, error: assignmentTasksError } = useSWR(
+ `${getAPIUrl()}assignments/${assignment_uuid}/tasks`,
+ (url) => swrFetcher(url, accessToken)
+ )
+
+ useEffect(() => {
+ setAssignmentsFull({ assignment_object: assignment, assignment_tasks: assignment_tasks })
+ }
+ , [assignment, assignment_tasks])
+
+ if (assignmentError || assignmentTasksError) return
+
+ if (!assignment || !assignment_tasks) return
+
+
+ return {children}
+}
+
+export function useAssignments() {
+ return useContext(AssignmentContext)
+}
diff --git a/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ActivityElement.tsx b/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ActivityElement.tsx
index a631adcc..daeea79a 100644
--- a/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ActivityElement.tsx
+++ b/apps/web/components/Dashboard/Course/EditCourseStructure/DraggableElements/ActivityElement.tsx
@@ -3,6 +3,7 @@ import { getAPIUrl, getUriWithOrg } from '@services/config/config'
import { deleteActivity, updateActivity } from '@services/courses/activities'
import { revalidateTags } from '@services/utils/ts/requests'
import {
+ Backpack,
Eye,
File,
FilePenLine,
@@ -16,10 +17,12 @@ import {
import { useLHSession } from '@components/Contexts/LHSessionContext'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
-import React from 'react'
+import React, { useEffect, useState } from 'react'
import { Draggable } from 'react-beautiful-dnd'
import { mutate } from 'swr'
-import { deleteAssignment, deleteAssignmentUsingActivityUUID } from '@services/courses/assignments'
+import { deleteAssignment, deleteAssignmentUsingActivityUUID, getAssignmentFromActivityUUID } from '@services/courses/assignments'
+import { useOrg } from '@components/Contexts/OrgContext'
+import { useCourse } from '@components/Contexts/CourseContext'
type ActivitiyElementProps = {
orgslug: string
@@ -47,7 +50,7 @@ function ActivityElement(props: ActivitiyElementProps) {
async function deleteActivityUI() {
// Assignments
- if(props.activity.activity_type === 'TYPE_ASSIGNMENT') {
+ if (props.activity.activity_type === 'TYPE_ASSIGNMENT') {
await deleteAssignmentUsingActivityUUID(props.activity.activity_uuid, access_token)
}
@@ -66,8 +69,6 @@ function ActivityElement(props: ActivitiyElementProps) {
let modifiedActivityCopy = {
name: modifiedActivity.activityName,
description: '',
- type: props.activity.type,
- content: props.activity.content,
}
await updateActivity(modifiedActivityCopy, activityUUID, access_token)
@@ -135,29 +136,7 @@ function ActivityElement(props: ActivitiyElementProps) {
{/* Edit and View Button */}
- {props.activity.activity_type === 'TYPE_DYNAMIC' && (
- <>
-
-
- Edit Page
-
-
- >
- )}
+
{
>
)}
+ {props.activityType === 'TYPE_ASSIGNMENT' && (
+ <>
+
+
+ {' '}
+
+
+ Assignment
+
{' '}
+
+ >
+ )}
{props.activityType === 'TYPE_DYNAMIC' && (
<>
@@ -240,4 +231,78 @@ const ActivityTypeIndicator = (props: { activityType: string }) => {
)
}
+
+const ActivityElementOptions = ({ activity }: any) => {
+ const [assignmentUUID, setAssignmentUUID] = useState('');
+ const org = useOrg() as any;
+ const course = useCourse() as any;
+ const session = useLHSession() as any;
+ const access_token = session?.data?.tokens?.access_token;
+
+ async function getAssignmentUUIDFromActivityUUID(activityUUID: string) {
+ const activity = await getAssignmentFromActivityUUID(activityUUID, access_token);
+ if (activity) {
+ return activity.data.assignment_uuid;
+ }
+ }
+
+ const fetchAssignmentUUID = async () => {
+ if (activity.activity_type === 'TYPE_ASSIGNMENT') {
+ const assignment_uuid = await getAssignmentUUIDFromActivityUUID(activity.activity_uuid);
+ setAssignmentUUID(assignment_uuid.replace('assignment_', ''));
+ }
+ };
+
+ useEffect(() => {
+
+ console.log(activity)
+
+ fetchAssignmentUUID();
+ }, [activity, course]);
+
+ return (
+ <>
+ {activity.activity_type === 'TYPE_DYNAMIC' && (
+ <>
+
+
+ Edit Page
+
+
+ >
+ )}
+ {activity.activity_type === 'TYPE_ASSIGNMENT' && assignmentUUID && (
+ <>
+
+
+ Edit Assignment
+
+
+ >
+ )}
+ >
+ );
+};
+
export default ActivityElement
diff --git a/apps/web/components/Dashboard/UI/BreadCrumbs.tsx b/apps/web/components/Dashboard/UI/BreadCrumbs.tsx
index 05396e2b..f242f315 100644
--- a/apps/web/components/Dashboard/UI/BreadCrumbs.tsx
+++ b/apps/web/components/Dashboard/UI/BreadCrumbs.tsx
@@ -1,15 +1,16 @@
-import { useCourse } from '@components/Contexts/CourseContext'
-import { Book, ChevronRight, School, User, Users } from 'lucide-react'
+'use client';
+import { useOrg } from '@components/Contexts/OrgContext';
+import { Backpack, Book, ChevronRight, School, User, Users } from 'lucide-react'
import Link from 'next/link'
import React from 'react'
type BreadCrumbsProps = {
- type: 'courses' | 'user' | 'users' | 'org' | 'orgusers'
+ type: 'courses' | 'user' | 'users' | 'org' | 'orgusers' | 'assignments'
last_breadcrumb?: string
}
function BreadCrumbs(props: BreadCrumbsProps) {
- const course = useCourse() as any
+ const org = useOrg() as any
return (
@@ -25,6 +26,15 @@ function BreadCrumbs(props: BreadCrumbsProps) {
) : (
''
)}
+ {props.type == 'assignments' ? (
+
+ {' '}
+
+ Assignments
+
+ ) : (
+ ''
+ )}
{props.type == 'user' ? (
{' '}
@@ -64,7 +74,6 @@ function BreadCrumbs(props: BreadCrumbsProps) {
-
)
}
diff --git a/apps/web/components/Dashboard/UI/LeftMenu.tsx b/apps/web/components/Dashboard/UI/LeftMenu.tsx
index 9f695bbc..bc2eff28 100644
--- a/apps/web/components/Dashboard/UI/LeftMenu.tsx
+++ b/apps/web/components/Dashboard/UI/LeftMenu.tsx
@@ -3,7 +3,7 @@ import { useOrg } from '@components/Contexts/OrgContext'
import { signOut } from 'next-auth/react'
import ToolTip from '@components/StyledElements/Tooltip/Tooltip'
import LearnHouseDashboardLogo from '@public/dashLogo.png'
-import { BookCopy, Home, LogOut, School, Settings, Users } from 'lucide-react'
+import { Backpack, BookCopy, Home, LogOut, School, Settings, Users } from 'lucide-react'
import Image from 'next/image'
import Link from 'next/link'
import React, { useEffect } from 'react'
@@ -96,6 +96,14 @@ function LeftMenu() {
+
+
+
+
+