diff --git a/apps/api/src/routers/courses/assignments.py b/apps/api/src/routers/courses/assignments.py index 808234ff..277ee134 100644 --- a/apps/api/src/routers/courses/assignments.py +++ b/apps/api/src/routers/courses/assignments.py @@ -20,6 +20,7 @@ from src.services.courses.activities.assignments import ( delete_assignment_submission, delete_assignment_task, delete_assignment_task_submission, + get_assignments_from_course, get_grade_assignment_submission, grade_assignment_submission, handle_assignment_task_submission, @@ -426,6 +427,8 @@ async def api_delete_user_assignment_submissions( return await delete_assignment_submission( request, user_id, assignment_uuid, current_user, db_session ) + + @router.get("/{assignment_uuid}/submissions/{user_id}/grade") async def api_get_submission_grade( request: Request, @@ -442,6 +445,7 @@ async def api_get_submission_grade( request, user_id, assignment_uuid, current_user, db_session ) + @router.post("/{assignment_uuid}/submissions/{user_id}/grade") async def api_final_grade_submission( request: Request, @@ -474,3 +478,18 @@ async def api_submission_mark_as_done( return await mark_activity_as_done_for_user( request, user_id, assignment_uuid, current_user, db_session ) + + +@router.get("/course/{course_uuid}") +async def api_get_assignments( + request: Request, + course_uuid: str, + current_user: PublicUser = Depends(get_current_user), + db_session=Depends(get_db_session), +): + """ + Get assignments for a course + """ + return await get_assignments_from_course( + request, course_uuid, current_user, db_session + ) diff --git a/apps/api/src/services/courses/activities/assignments.py b/apps/api/src/services/courses/activities/assignments.py index 7b6153e2..bf790ed4 100644 --- a/apps/api/src/services/courses/activities/assignments.py +++ b/apps/api/src/services/courses/activities/assignments.py @@ -1576,6 +1576,40 @@ async def mark_activity_as_done_for_user( # return OK return {"message": "Activity marked as done for user"} +async def get_assignments_from_course( + request: Request, + course_uuid: str, + current_user: PublicUser | AnonymousUser, + db_session: Session, +): + # Find course + statement = select(Course).where(Course.course_uuid == course_uuid) + course = db_session.exec(statement).first() + + if not course: + raise HTTPException( + status_code=404, + detail="Course not found", + ) + + # Get Activities + statement = select(Activity).where(Activity.course_id == course.id) + activities = db_session.exec(statement).all() + + # Get Assignments + assignments = [] + for activity in activities: + statement = select(Assignment).where(Assignment.activity_id == activity.id) + assignment = db_session.exec(statement).first() + if assignment: + assignments.append(assignment) + + # RBAC check + await rbac_check(request, course.course_uuid, current_user, "read", db_session) + + # return assignments read + return [AssignmentRead.model_validate(assignment) for assignment in assignments] + ## 🔒 RBAC Utils ## 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 b4056e04..f4c0fdf0 100644 --- a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/page.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/page.tsx @@ -10,7 +10,7 @@ import { mutate } from 'swr'; import { getAPIUrl } from '@services/config/config'; import toast from 'react-hot-toast'; import Link from 'next/link'; -import { useParams } from 'next/navigation'; +import { useParams, useSearchParams } from 'next/navigation'; import { updateActivity } from '@services/courses/activities'; // Lazy Loading import dynamic from 'next/dynamic'; @@ -19,7 +19,8 @@ const AssignmentSubmissionsSubPage = dynamic(() => import('./subpages/Assignment function AssignmentEdit() { const params = useParams<{ assignmentuuid: string; }>() - const [selectedSubPage, setSelectedSubPage] = React.useState('editor') + const searchParams = useSearchParams() + const [selectedSubPage, setSelectedSubPage] = React.useState( searchParams.get('subpage') || 'editor') return (
diff --git a/apps/web/app/orgs/[orgslug]/dash/assignments/page.tsx b/apps/web/app/orgs/[orgslug]/dash/assignments/page.tsx index c54c9cc9..5a92a45c 100644 --- a/apps/web/app/orgs/[orgslug]/dash/assignments/page.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/assignments/page.tsx @@ -1,9 +1,167 @@ +'use client'; +import { useLHSession } from '@components/Contexts/LHSessionContext'; +import { useOrg } from '@components/Contexts/OrgContext'; +import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs' +import { getAPIUrl, getUriWithOrg } from '@services/config/config'; +import { getAssignmentsFromACourse } from '@services/courses/assignments'; +import { getCourseThumbnailMediaDirectory } from '@services/media/media'; +import { swrFetcher } from '@services/utils/ts/requests'; +import { Book, EllipsisVertical, GalleryVertical, GalleryVerticalEnd, Layers2, PenBox, UserRoundPen } from 'lucide-react'; +import Link from 'next/link'; import React from 'react' +import useSWR from 'swr'; function AssignmentsHome() { + const session = useLHSession() as any; + const access_token = session?.data?.tokens?.access_token; + const org = useOrg() as any; + const [courseAssignments, setCourseAssignments] = React.useState([]) + + const { data: courses } = useSWR(`${getAPIUrl()}courses/org_slug/${org?.slug}/page/1/limit/50`, (url) => swrFetcher(url, access_token)) + + async function getAvailableAssignmentsForCourse(course_uuid: string) { + const res = await getAssignmentsFromACourse(course_uuid, access_token) + return res.data + } + + function removeAssignmentPrefix(assignment_uuid: string) { + return assignment_uuid.replace('assignment_', '') + } + + function removeCoursePrefix(course_uuid: string) { + return course_uuid.replace('course_', '') + } + + + React.useEffect(() => { + if (courses) { + const course_uuids = courses.map((course: any) => course.course_uuid) + const courseAssignmentsPromises = course_uuids.map((course_uuid: string) => getAvailableAssignmentsForCourse(course_uuid)) + Promise.all(courseAssignmentsPromises).then((results) => { + setCourseAssignments(results) + }) + } + }, [courses]) + + return ( -
AssignmentsHome
+
+
+
+ +

Assignments

+
+
+ {courseAssignments.map((assignments: any, index: number) => ( +
+
+
+
+ +
+

Course

+

{courses[index].name}

+
+ +
+ + +

Course Editor

+ +
+ + + {assignments && assignments.map((assignment: any) => ( +
+
+
+

Assignment

+
+
{assignment.title}
+
{assignment.description}
+
+
+ + + + +

Editor

+ + + +

Submissions

+ +
+
+ ))} +
+
+ ))} + +
+
+ +
) } +const MiniThumbnail = (props: { course: any }) => { + const org = useOrg() as any + + // function to remove "course_" from the course_uuid + function removeCoursePrefix(course_uuid: string) { + return course_uuid.replace('course_', '') + } + + return ( + + {props.course.thumbnail_image ? ( +
+ ) : ( +
+ )} + + ) +} + + export default AssignmentsHome \ No newline at end of file diff --git a/apps/web/services/courses/assignments.ts b/apps/web/services/courses/assignments.ts index e7ea35ce..0d301036 100644 --- a/apps/web/services/courses/assignments.ts +++ b/apps/web/services/courses/assignments.ts @@ -278,3 +278,15 @@ export async function markActivityAsDoneForUser( const res = await getResponseMetadata(result) return res } + +export async function getAssignmentsFromACourse( + courseUUID: string, + access_token: string +) { + const result: any = await fetch( + `${getAPIUrl()}assignments/course/${courseUUID}`, + RequestBodyWithAuthHeader('GET', null, null, access_token) + ) + const res = await getResponseMetadata(result) + return res +} diff --git a/apps/web/styles/globals.css b/apps/web/styles/globals.css index 393a85b8..6b8d69b3 100644 --- a/apps/web/styles/globals.css +++ b/apps/web/styles/globals.css @@ -9,6 +9,11 @@ @apply shadow-md shadow-gray-300/25 outline outline-1 outline-neutral-200/40 } +.light-shadow { + @apply shadow-lg shadow-gray-300/15 outline outline-1 outline-neutral-200/30 +} + + .custom-dots-bg { @apply bg-fixed; background-image: radial-gradient(#4744446b 1px, transparent 1px),