mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: enable assignments page
This commit is contained in:
parent
40ef2d0cec
commit
364c24e15d
6 changed files with 232 additions and 3 deletions
|
|
@ -20,6 +20,7 @@ from src.services.courses.activities.assignments import (
|
||||||
delete_assignment_submission,
|
delete_assignment_submission,
|
||||||
delete_assignment_task,
|
delete_assignment_task,
|
||||||
delete_assignment_task_submission,
|
delete_assignment_task_submission,
|
||||||
|
get_assignments_from_course,
|
||||||
get_grade_assignment_submission,
|
get_grade_assignment_submission,
|
||||||
grade_assignment_submission,
|
grade_assignment_submission,
|
||||||
handle_assignment_task_submission,
|
handle_assignment_task_submission,
|
||||||
|
|
@ -426,6 +427,8 @@ async def api_delete_user_assignment_submissions(
|
||||||
return await delete_assignment_submission(
|
return await delete_assignment_submission(
|
||||||
request, user_id, assignment_uuid, current_user, db_session
|
request, user_id, assignment_uuid, current_user, db_session
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{assignment_uuid}/submissions/{user_id}/grade")
|
@router.get("/{assignment_uuid}/submissions/{user_id}/grade")
|
||||||
async def api_get_submission_grade(
|
async def api_get_submission_grade(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
|
@ -442,6 +445,7 @@ async def api_get_submission_grade(
|
||||||
request, user_id, assignment_uuid, current_user, db_session
|
request, user_id, assignment_uuid, current_user, db_session
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{assignment_uuid}/submissions/{user_id}/grade")
|
@router.post("/{assignment_uuid}/submissions/{user_id}/grade")
|
||||||
async def api_final_grade_submission(
|
async def api_final_grade_submission(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
|
@ -474,3 +478,18 @@ async def api_submission_mark_as_done(
|
||||||
return await mark_activity_as_done_for_user(
|
return await mark_activity_as_done_for_user(
|
||||||
request, user_id, assignment_uuid, current_user, db_session
|
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
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1576,6 +1576,40 @@ async def mark_activity_as_done_for_user(
|
||||||
# return OK
|
# return OK
|
||||||
return {"message": "Activity marked as done for user"}
|
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 ##
|
## 🔒 RBAC Utils ##
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { mutate } from 'swr';
|
||||||
import { getAPIUrl } from '@services/config/config';
|
import { getAPIUrl } from '@services/config/config';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useParams } from 'next/navigation';
|
import { useParams, useSearchParams } from 'next/navigation';
|
||||||
import { updateActivity } from '@services/courses/activities';
|
import { updateActivity } from '@services/courses/activities';
|
||||||
// Lazy Loading
|
// Lazy Loading
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
|
|
@ -19,7 +19,8 @@ const AssignmentSubmissionsSubPage = dynamic(() => import('./subpages/Assignment
|
||||||
|
|
||||||
function AssignmentEdit() {
|
function AssignmentEdit() {
|
||||||
const params = useParams<{ assignmentuuid: string; }>()
|
const params = useParams<{ assignmentuuid: string; }>()
|
||||||
const [selectedSubPage, setSelectedSubPage] = React.useState('editor')
|
const searchParams = useSearchParams()
|
||||||
|
const [selectedSubPage, setSelectedSubPage] = React.useState( searchParams.get('subpage') || 'editor')
|
||||||
return (
|
return (
|
||||||
<div className='flex w-full flex-col'>
|
<div className='flex w-full flex-col'>
|
||||||
<AssignmentProvider assignment_uuid={'assignment_' + params.assignmentuuid}>
|
<AssignmentProvider assignment_uuid={'assignment_' + params.assignmentuuid}>
|
||||||
|
|
|
||||||
|
|
@ -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 React from 'react'
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
function AssignmentsHome() {
|
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<any[]>([])
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<div>AssignmentsHome</div>
|
<div className='flex w-full'>
|
||||||
|
<div className='pl-10 mr-10 tracking-tighter flex flex-col space-y-5 w-full'>
|
||||||
|
<div className='flex flex-col space-y-2'>
|
||||||
|
<BreadCrumbs type="assignments" />
|
||||||
|
<h1 className="pt-3 flex font-bold text-4xl">Assignments</h1>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col space-y-3 w-full'>
|
||||||
|
{courseAssignments.map((assignments: any, index: number) => (
|
||||||
|
<div key={index} className='flex flex-col space-y-2 bg-white nice-shadow p-4 rounded-xl w-full'>
|
||||||
|
<div>
|
||||||
|
<div className='flex space-x-2 items-center justify-between w-full'>
|
||||||
|
<div className='flex space-x-2 items-center'>
|
||||||
|
<MiniThumbnail course={courses[index]} />
|
||||||
|
<div className='flex flex-col font-bold text-lg '>
|
||||||
|
<p className='bg-gray-200 text-gray-700 px-2 text-xs py-0.5 rounded-full'>Course</p>
|
||||||
|
<p>{courses[index].name}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
href={{
|
||||||
|
pathname: getUriWithOrg(org.slug, `/dash/courses/course/${removeCoursePrefix(courses[index].course_uuid)}/content`),
|
||||||
|
query: { subpage: 'editor' }
|
||||||
|
}}
|
||||||
|
prefetch
|
||||||
|
className='bg-black font-semibold text-sm text-zinc-100 rounded-md flex space-x-1.5 nice-shadow items-center px-3 py-1'>
|
||||||
|
<GalleryVerticalEnd size={15} />
|
||||||
|
<p>Course Editor</p>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{assignments && assignments.map((assignment: any) => (
|
||||||
|
<div key={assignment.assignment_uuid} className='flex mt-3 p-3 rounded flex-row space-x-2 w-full light-shadow justify-between bg-gray-50 items-center'>
|
||||||
|
<div className='flex flex-row items-center space-x-2 '>
|
||||||
|
<div className='flex text-xs font-bold bg-gray-200 text-gray-700 px-2 py-0.5 rounded-full h-fit'>
|
||||||
|
<p>Assignment</p>
|
||||||
|
</div>
|
||||||
|
<div className='flex font-semibold text-lg'>{assignment.title}</div>
|
||||||
|
<div className='flex font-semibold text'>{assignment.description}</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex space-x-2 font-bold text-sm items-center'>
|
||||||
|
|
||||||
|
<EllipsisVertical className='text-gray-500' size={17} />
|
||||||
|
<Link
|
||||||
|
href={{
|
||||||
|
pathname: getUriWithOrg(org.slug, `/dash/assignments/${removeAssignmentPrefix(assignment.assignment_uuid)}`),
|
||||||
|
query: { subpage: 'editor' }
|
||||||
|
}}
|
||||||
|
prefetch
|
||||||
|
className='bg-white rounded-full flex space-x-2 nice-shadow items-center px-3 py-0.5'>
|
||||||
|
<Layers2 size={15} />
|
||||||
|
<p>Editor</p>
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href={{
|
||||||
|
pathname: getUriWithOrg(org.slug, `/dash/assignments/${removeAssignmentPrefix(assignment.assignment_uuid)}`),
|
||||||
|
query: { subpage: 'submissions' }
|
||||||
|
}}
|
||||||
|
|
||||||
|
prefetch
|
||||||
|
className='bg-white rounded-full flex space-x-2 nice-shadow items-center px-3 py-0.5'>
|
||||||
|
<UserRoundPen size={15} />
|
||||||
|
<p>Submissions</p>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Link
|
||||||
|
href={getUriWithOrg(
|
||||||
|
org.orgslug,
|
||||||
|
'/course/' + removeCoursePrefix(props.course.course_uuid)
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{props.course.thumbnail_image ? (
|
||||||
|
<div
|
||||||
|
className="inset-0 ring-1 ring-inset ring-black/10 rounded-lg shadow-xl w-[70px] h-[40px] bg-cover"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(${getCourseThumbnailMediaDirectory(
|
||||||
|
org?.org_uuid,
|
||||||
|
props.course.course_uuid,
|
||||||
|
props.course.thumbnail_image
|
||||||
|
)})`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="inset-0 ring-1 ring-inset ring-black/10 rounded-lg shadow-xl w-[70px] h-[40px] bg-cover"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url('../empty_thumbnail.png')`,
|
||||||
|
backgroundSize: 'contain',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export default AssignmentsHome
|
export default AssignmentsHome
|
||||||
|
|
@ -278,3 +278,15 @@ export async function markActivityAsDoneForUser(
|
||||||
const res = await getResponseMetadata(result)
|
const res = await getResponseMetadata(result)
|
||||||
return res
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,11 @@
|
||||||
@apply shadow-md shadow-gray-300/25 outline outline-1 outline-neutral-200/40
|
@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 {
|
.custom-dots-bg {
|
||||||
@apply bg-fixed;
|
@apply bg-fixed;
|
||||||
background-image: radial-gradient(#4744446b 1px, transparent 1px),
|
background-image: radial-gradient(#4744446b 1px, transparent 1px),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue