mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-18 20:09:25 +00:00
feat: add publish status change from the course edition page
This commit is contained in:
parent
73e81830d3
commit
d6aa071425
12 changed files with 929 additions and 581 deletions
|
|
@ -249,7 +249,7 @@ class AssignmentUserSubmissionBase(SQLModel):
|
|||
"""Represents the submission status of an assignment for a user."""
|
||||
|
||||
submission_status: AssignmentUserSubmissionStatus = (
|
||||
AssignmentUserSubmissionStatus.PENDING
|
||||
AssignmentUserSubmissionStatus.SUBMITTED
|
||||
)
|
||||
grade: int
|
||||
user_id: int = Field(
|
||||
|
|
@ -295,7 +295,7 @@ class AssignmentUserSubmission(AssignmentUserSubmissionBase, table=True):
|
|||
assignmentusersubmission_uuid: str
|
||||
|
||||
submission_status: AssignmentUserSubmissionStatus = (
|
||||
AssignmentUserSubmissionStatus.PENDING
|
||||
AssignmentUserSubmissionStatus.SUBMITTED
|
||||
)
|
||||
grade: int
|
||||
user_id: int = Field(
|
||||
|
|
|
|||
|
|
@ -263,7 +263,7 @@ async def api_handle_assignment_task_submissions(
|
|||
)
|
||||
|
||||
|
||||
@router.get("/{assignment_uuid}/tasks/{assignment_task_uuid}/submissions/{user_id}")
|
||||
@router.get("/{assignment_uuid}/tasks/{assignment_task_uuid}/submissions/user/{user_id}")
|
||||
async def api_read_user_assignment_task_submissions(
|
||||
request: Request,
|
||||
assignment_task_uuid: str,
|
||||
|
|
@ -279,7 +279,7 @@ async def api_read_user_assignment_task_submissions(
|
|||
)
|
||||
|
||||
|
||||
@router.get("/{assignment_uuid}/tasks/{assignment_task_uuid}/submissions/user/me")
|
||||
@router.get("/{assignment_uuid}/tasks/{assignment_task_uuid}/submissions/me")
|
||||
async def api_read_user_assignment_task_submissions_me(
|
||||
request: Request,
|
||||
assignment_task_uuid: str,
|
||||
|
|
|
|||
|
|
@ -1088,7 +1088,7 @@ async def create_assignment_submission(
|
|||
assignment_id=assignment.id, # type: ignore
|
||||
grade=0,
|
||||
assignmentusersubmission_uuid=str(f"assignmentusersubmission_{uuid4()}"),
|
||||
submission_status=AssignmentUserSubmissionStatus.PENDING,
|
||||
submission_status=AssignmentUserSubmissionStatus.SUBMITTED,
|
||||
creation_date=str(datetime.now()),
|
||||
update_date=str(datetime.now()),
|
||||
)
|
||||
|
|
@ -1129,7 +1129,7 @@ async def read_assignment_submissions(
|
|||
|
||||
# Find assignments tasks for an assignment
|
||||
statement = select(AssignmentUserSubmission).where(
|
||||
assignment.assignment_uuid == assignment_uuid
|
||||
AssignmentUserSubmission.assignment_id == assignment.id
|
||||
)
|
||||
|
||||
# RBAC check
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import { useAssignments } from '@components/Contexts/Assignments/AssignmentConte
|
|||
import { useAssignmentsTask, useAssignmentsTaskDispatch } from '@components/Contexts/Assignments/AssignmentsTaskContext';
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext';
|
||||
import AssignmentBoxUI from '@components/Objects/Activities/Assignment/AssignmentBoxUI';
|
||||
import { getAssignmentTask, getAssignmentTaskSubmissionsMe, handleAssignmentTaskSubmission, updateAssignmentTask } from '@services/courses/assignments';
|
||||
import { Check, Minus, Plus, PlusCircle, X } from 'lucide-react';
|
||||
import { getAssignmentTask, getAssignmentTaskSubmissionsMe, getAssignmentTaskSubmissionsUser, handleAssignmentTaskSubmission, updateAssignmentTask } from '@services/courses/assignments';
|
||||
import { Check, Info, Minus, Plus, PlusCircle, X } from 'lucide-react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
|
@ -29,11 +29,12 @@ type QuizSubmitSchema = {
|
|||
};
|
||||
|
||||
type TaskQuizObjectProps = {
|
||||
view: 'teacher' | 'student';
|
||||
view: 'teacher' | 'student' | 'grading';
|
||||
user_id?: string; // Only for read-only view
|
||||
assignmentTaskUUID?: string;
|
||||
};
|
||||
|
||||
function TaskQuizObject({ view, assignmentTaskUUID }: TaskQuizObjectProps) {
|
||||
function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectProps) {
|
||||
const session = useLHSession() as any;
|
||||
const access_token = session?.data?.tokens?.access_token;
|
||||
const assignmentTaskState = useAssignmentsTask() as any;
|
||||
|
|
@ -118,6 +119,7 @@ function TaskQuizObject({ view, assignmentTaskUUID }: TaskQuizObjectProps) {
|
|||
submissions: [],
|
||||
});
|
||||
const [showSavingDisclaimer, setShowSavingDisclaimer] = useState<boolean>(false);
|
||||
const [assignmentTaskOutsideProvider, setAssignmentTaskOutsideProvider] = useState<any>(null);
|
||||
|
||||
async function chooseOption(qIndex: number, oIndex: number) {
|
||||
const updatedSubmissions = [...userSubmissions.submissions];
|
||||
|
|
@ -147,6 +149,7 @@ function TaskQuizObject({ view, assignmentTaskUUID }: TaskQuizObjectProps) {
|
|||
if (assignmentTaskUUID) {
|
||||
const res = await getAssignmentTask(assignmentTaskUUID, access_token);
|
||||
if (res.success) {
|
||||
setAssignmentTaskOutsideProvider(res.data);
|
||||
setQuestions(res.data.contents.questions);
|
||||
}
|
||||
|
||||
|
|
@ -164,11 +167,11 @@ function TaskQuizObject({ view, assignmentTaskUUID }: TaskQuizObjectProps) {
|
|||
}
|
||||
}
|
||||
|
||||
// Detect changes between initial and current submissions
|
||||
useEffect(() => {
|
||||
const hasChanges = JSON.stringify(initialUserSubmissions.submissions) !== JSON.stringify(userSubmissions.submissions);
|
||||
setShowSavingDisclaimer(hasChanges);
|
||||
}, [userSubmissions, initialUserSubmissions.submissions]);
|
||||
// Detect changes between initial and current submissions
|
||||
useEffect(() => {
|
||||
const hasChanges = JSON.stringify(initialUserSubmissions.submissions) !== JSON.stringify(userSubmissions.submissions);
|
||||
setShowSavingDisclaimer(hasChanges);
|
||||
}, [userSubmissions, initialUserSubmissions.submissions]);
|
||||
|
||||
|
||||
|
||||
|
|
@ -193,10 +196,57 @@ function TaskQuizObject({ view, assignmentTaskUUID }: TaskQuizObjectProps) {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* STUDENT VIEW CODE */
|
||||
|
||||
/* GRADING VIEW CODE */
|
||||
const [userSubmissionObject, setUserSubmissionObject] = useState<any>(null);
|
||||
async function getAssignmentTaskSubmissionFromIdentifiedUserUI() {
|
||||
if (assignmentTaskUUID && user_id) {
|
||||
const res = await getAssignmentTaskSubmissionsUser(assignmentTaskUUID, user_id, assignment.assignment_object.assignment_uuid, access_token);
|
||||
if (res.success) {
|
||||
setUserSubmissions(res.data.task_submission);
|
||||
setUserSubmissionObject(res.data);
|
||||
setInitialUserSubmissions(res.data.task_submission);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
async function gradeFC() {
|
||||
if (assignmentTaskUUID) {
|
||||
// Ensure maxPoints is defined
|
||||
const maxPoints = assignmentTaskOutsideProvider?.max_grade_value || 100; // Default to 100 if not defined
|
||||
|
||||
// Ensure userSubmissions.questions are set
|
||||
const totalQuestions = questions.length;
|
||||
const correctQuestions = userSubmissions.submissions.filter((submission) => {
|
||||
const question = questions.find((q) => q.questionUUID === submission.questionUUID);
|
||||
const option = question?.options.find((o) => o.optionUUID === submission.optionUUID);
|
||||
return option?.correct;
|
||||
}).length;
|
||||
|
||||
// Calculate grade based on correct questions
|
||||
const grade = Math.floor((correctQuestions / totalQuestions) * maxPoints);
|
||||
|
||||
// Save the grade to the server
|
||||
const values = {
|
||||
task_submission: userSubmissions,
|
||||
grade,
|
||||
task_submission_grade_feedback: 'Auto graded by system',
|
||||
};
|
||||
|
||||
const res = await handleAssignmentTaskSubmission(values, assignmentTaskUUID, assignment.assignment_object.assignment_uuid, access_token);
|
||||
if (res) {
|
||||
getAssignmentTaskSubmissionFromIdentifiedUserUI();
|
||||
toast.success(`Task graded successfully with ${grade} points`);
|
||||
} else {
|
||||
toast.error('Error grading task, please retry later.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* GRADING VIEW CODE */
|
||||
|
||||
useEffect(() => {
|
||||
assignmentTaskStateHook({
|
||||
setSelectedAssignmentTaskUUID: assignmentTaskUUID,
|
||||
|
|
@ -210,131 +260,180 @@ function TaskQuizObject({ view, assignmentTaskUUID }: TaskQuizObjectProps) {
|
|||
getAssignmentTaskUI();
|
||||
getAssignmentTaskSubmissionFromUserUI();
|
||||
}
|
||||
|
||||
// Grading area
|
||||
else if (view == 'grading') {
|
||||
getAssignmentTaskUI();
|
||||
//setQuestions(assignmentTaskState.assignmentTask.contents.questions);
|
||||
getAssignmentTaskSubmissionFromIdentifiedUserUI();
|
||||
|
||||
}
|
||||
}, [assignmentTaskState, assignment, assignmentTaskStateHook, access_token]);
|
||||
|
||||
|
||||
return (
|
||||
<AssignmentBoxUI submitFC={submitFC} saveFC={saveFC} view={view} showSavingDisclaimer={showSavingDisclaimer} type="quiz">
|
||||
<div className="flex flex-col space-y-6">
|
||||
{questions && questions.map((question, qIndex) => (
|
||||
<div key={qIndex} className="flex flex-col space-y-1.5">
|
||||
<div className="flex space-x-2 items-center">
|
||||
{view === 'teacher' ? (
|
||||
<input
|
||||
value={question.questionText}
|
||||
onChange={(e) => handleQuestionChange(qIndex, e.target.value)}
|
||||
placeholder="Question"
|
||||
className="w-full px-3 text-neutral-600 bg-[#00008b00] border-2 border-gray-200 rounded-md border-dotted text-sm font-bold"
|
||||
/>
|
||||
) : (
|
||||
<p className="w-full px-3 text-neutral-600 bg-[#00008b00] border-2 border-gray-200 rounded-md border-dotted text-sm font-bold">
|
||||
{question.questionText}
|
||||
</p>
|
||||
)}
|
||||
{view === 'teacher' && (
|
||||
<div
|
||||
className="w-[20px] flex-none flex items-center h-[20px] rounded-lg bg-slate-200/60 text-slate-500 hover:bg-slate-300 text-sm transition-all ease-linear cursor-pointer"
|
||||
onClick={() => removeQuestion(qIndex)}
|
||||
>
|
||||
<Minus size={12} className="mx-auto" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2">
|
||||
{question.options.map((option, oIndex) => (
|
||||
<div className="flex" key={oIndex}>
|
||||
if (questions && questions.length >= 0) {
|
||||
return (
|
||||
<AssignmentBoxUI submitFC={submitFC} saveFC={saveFC} gradeFC={gradeFC} view={view} currentPoints={userSubmissionObject?.grade} maxPoints={assignmentTaskOutsideProvider?.max_grade_value} showSavingDisclaimer={showSavingDisclaimer} type="quiz">
|
||||
<div className="flex flex-col space-y-6">
|
||||
{questions && questions.map((question, qIndex) => (
|
||||
<div key={qIndex} className="flex flex-col space-y-1.5">
|
||||
<div className="flex space-x-2 items-center">
|
||||
{view === 'teacher' ? (
|
||||
<input
|
||||
value={question.questionText}
|
||||
onChange={(e) => handleQuestionChange(qIndex, e.target.value)}
|
||||
placeholder="Question"
|
||||
className="w-full px-3 text-neutral-600 bg-[#00008b00] border-2 border-gray-200 rounded-md border-dotted text-sm font-bold"
|
||||
/>
|
||||
) : (
|
||||
<p className="w-full px-3 text-neutral-600 bg-[#00008b00] border-2 border-gray-200 rounded-md border-dotted text-sm font-bold">
|
||||
{question.questionText}
|
||||
</p>
|
||||
)}
|
||||
{view === 'teacher' && (
|
||||
<div
|
||||
onClick={() => view === 'student' && chooseOption(qIndex, oIndex)}
|
||||
className={"answer outline outline-3 outline-white pr-2 shadow w-full flex items-center space-x-2 h-[30px] hover:bg-opacity-100 hover:shadow-md rounded-lg bg-white text-sm duration-150 cursor-pointer ease-linear nice-shadow " + (view == 'student' ? 'active:scale-110' : '')}
|
||||
className="w-[20px] flex-none flex items-center h-[20px] rounded-lg bg-slate-200/60 text-slate-500 hover:bg-slate-300 text-sm transition-all ease-linear cursor-pointer"
|
||||
onClick={() => removeQuestion(qIndex)}
|
||||
>
|
||||
<div className="font-bold text-base flex items-center h-full w-[40px] rounded-l-md text-slate-800 bg-slate-100/80">
|
||||
<p className="mx-auto font-bold text-sm">{String.fromCharCode(65 + oIndex)}</p>
|
||||
</div>
|
||||
{view === 'teacher' ? (
|
||||
<input
|
||||
type="text"
|
||||
value={option.text}
|
||||
onChange={(e) => handleOptionChange(qIndex, oIndex, e.target.value)}
|
||||
placeholder="Option"
|
||||
className="w-full mx-2 px-3 pr-6 text-neutral-600 bg-[#00008b00] border-2 border-gray-200 rounded-md border-dotted text-sm font-bold"
|
||||
/>
|
||||
) : (
|
||||
<p className="w-full mx-2 px-3 pr-6 text-neutral-600 bg-[#00008b00] text-sm font-bold">
|
||||
{option.text}
|
||||
</p>
|
||||
)}
|
||||
{view === 'teacher' && (
|
||||
<>
|
||||
<div
|
||||
className={`w-fit flex-none flex text-xs px-2 py-0.5 space-x-1 items-center h-fit rounded-lg ${option.correct ? 'bg-lime-200 text-lime-600' : 'bg-rose-200/60 text-rose-500'
|
||||
} hover:bg-lime-300 text-sm transition-all ease-linear cursor-pointer`}
|
||||
onClick={() => toggleCorrectOption(qIndex, oIndex)}
|
||||
>
|
||||
{option.correct ? <Check size={12} className="mx-auto" /> : <X size={12} className="mx-auto" />}
|
||||
{option.correct ? (
|
||||
<p className="mx-auto font-bold text-xs">Correct</p>
|
||||
) : (
|
||||
<p className="mx-auto font-bold text-xs">Incorrect</p>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="w-[20px] flex-none flex items-center h-[20px] rounded-lg bg-slate-200/60 text-slate-500 hover:bg-slate-300 text-sm transition-all ease-linear cursor-pointer"
|
||||
onClick={() => removeOption(qIndex, oIndex)}
|
||||
>
|
||||
<Minus size={12} className="mx-auto" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{view === 'student' && (
|
||||
<div className={`w-[20px] flex-none flex items-center h-[20px] rounded-lg ${userSubmissions.submissions.find(
|
||||
(submission) =>
|
||||
submission.questionUUID === question.questionUUID && submission.optionUUID === option.optionUUID
|
||||
)
|
||||
? "bg-green-200/60 text-green-500 hover:bg-green-300" // Selected state colors
|
||||
: "bg-slate-200/60 text-slate-500 hover:bg-slate-300" // Default state colors
|
||||
} text-sm transition-all ease-linear cursor-pointer`}>
|
||||
{userSubmissions.submissions.find(
|
||||
<Minus size={12} className="mx-auto" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2">
|
||||
{question.options.map((option, oIndex) => (
|
||||
<div className="flex" key={oIndex}>
|
||||
<div
|
||||
onClick={() => view === 'student' && chooseOption(qIndex, oIndex)}
|
||||
className={"answer outline outline-3 outline-white pr-2 shadow w-full flex items-center space-x-2 h-[30px] hover:bg-opacity-100 hover:shadow-md rounded-lg bg-white text-sm duration-150 cursor-pointer ease-linear nice-shadow " + (view == 'student' ? 'active:scale-110' : '')}
|
||||
>
|
||||
<div className="font-bold text-base flex items-center h-full w-[40px] rounded-l-md text-slate-800 bg-slate-100/80">
|
||||
<p className="mx-auto font-bold text-sm">{String.fromCharCode(65 + oIndex)}</p>
|
||||
</div>
|
||||
{view === 'teacher' ? (
|
||||
<input
|
||||
type="text"
|
||||
value={option.text}
|
||||
onChange={(e) => handleOptionChange(qIndex, oIndex, e.target.value)}
|
||||
placeholder="Option"
|
||||
className="w-full mx-2 px-3 pr-6 text-neutral-600 bg-[#00008b00] border-2 border-gray-200 rounded-md border-dotted text-sm font-bold"
|
||||
/>
|
||||
) : (
|
||||
<p className="w-full mx-2 px-3 pr-6 text-neutral-600 bg-[#00008b00] text-sm font-bold">
|
||||
{option.text}
|
||||
</p>
|
||||
)}
|
||||
{view === 'teacher' && (
|
||||
<>
|
||||
<div
|
||||
className={`w-fit flex-none flex text-xs px-2 py-0.5 space-x-1 items-center h-fit rounded-lg ${option.correct ? 'bg-lime-200 text-lime-600' : 'bg-rose-200/60 text-rose-500'
|
||||
} hover:bg-lime-300 text-sm transition-all ease-linear cursor-pointer`}
|
||||
onClick={() => toggleCorrectOption(qIndex, oIndex)}
|
||||
>
|
||||
{option.correct ? <Check size={12} className="mx-auto" /> : <X size={12} className="mx-auto" />}
|
||||
{option.correct ? (
|
||||
<p className="mx-auto font-bold text-xs">Correct</p>
|
||||
) : (
|
||||
<p className="mx-auto font-bold text-xs">Incorrect</p>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="w-[20px] flex-none flex items-center h-[20px] rounded-lg bg-slate-200/60 text-slate-500 hover:bg-slate-300 text-sm transition-all ease-linear cursor-pointer"
|
||||
onClick={() => removeOption(qIndex, oIndex)}
|
||||
>
|
||||
<Minus size={12} className="mx-auto" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{view === 'grading' && (
|
||||
<>
|
||||
<div
|
||||
className={`w-fit flex-none flex text-xs px-2 py-0.5 space-x-1 items-center h-fit rounded-lg ${option.correct ? 'bg-lime-200 text-lime-600' : 'bg-rose-200/60 text-rose-500'
|
||||
} hover:bg-lime-300 text-sm transition-all ease-linear cursor-pointer`}
|
||||
>
|
||||
{option.correct ? <Check size={12} className="mx-auto" /> : <X size={12} className="mx-auto" />}
|
||||
{option.correct ? (
|
||||
<p className="mx-auto font-bold text-xs">Marked as Correct</p>
|
||||
) : (
|
||||
<p className="mx-auto font-bold text-xs">Marked as Incorrect</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</>
|
||||
)}
|
||||
{view === 'student' && (
|
||||
<div className={`w-[20px] flex-none flex items-center h-[20px] rounded-lg ${userSubmissions.submissions.find(
|
||||
(submission) =>
|
||||
submission.questionUUID === question.questionUUID && submission.optionUUID === option.optionUUID
|
||||
) ? (
|
||||
<Check size={12} className="mx-auto" />
|
||||
) : (
|
||||
<X size={12} className="mx-auto" />
|
||||
)}
|
||||
)
|
||||
? "bg-green-200/60 text-green-500 hover:bg-green-300" // Selected state colors
|
||||
: "bg-slate-200/60 text-slate-500 hover:bg-slate-300" // Default state colors
|
||||
} text-sm transition-all ease-linear cursor-pointer`}>
|
||||
{userSubmissions.submissions.find(
|
||||
(submission) =>
|
||||
submission.questionUUID === question.questionUUID && submission.optionUUID === option.optionUUID
|
||||
) ? (
|
||||
<Check size={12} className="mx-auto" />
|
||||
) : (
|
||||
<X size={12} className="mx-auto" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{view === 'grading' && (
|
||||
<div className={`w-[20px] flex-none flex items-center h-[20px] rounded-lg ${userSubmissions.submissions.find(
|
||||
(submission) =>
|
||||
submission.questionUUID === question.questionUUID && submission.optionUUID === option.optionUUID
|
||||
)
|
||||
? "bg-green-200/60 text-green-500 hover:bg-green-300" // Selected state colors
|
||||
: "bg-slate-200/60 text-slate-500 hover:bg-slate-300" // Default state colors
|
||||
} text-sm transition-all ease-linear cursor-pointer`}>
|
||||
{userSubmissions.submissions.find(
|
||||
(submission) =>
|
||||
submission.questionUUID === question.questionUUID && submission.optionUUID === option.optionUUID
|
||||
) ? (
|
||||
<Check size={12} className="mx-auto" />
|
||||
) : (
|
||||
<X size={12} className="mx-auto" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
{view === 'teacher' && oIndex === question.options.length - 1 && questions[qIndex].options.length <= 4 && (
|
||||
<div className="flex justify-center mx-auto px-2">
|
||||
<div
|
||||
className="outline text-xs outline-3 outline-white px-2 shadow w-full flex items-center h-[30px] hover:bg-opacity-100 hover:shadow-md rounded-lg bg-white duration-150 cursor-pointer ease-linear nice-shadow"
|
||||
onClick={() => addOption(qIndex)}
|
||||
>
|
||||
<Plus size={14} className="inline-block" />
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
{view === 'teacher' && oIndex === question.options.length - 1 && questions[qIndex].options.length <= 4 && (
|
||||
<div className="flex justify-center mx-auto px-2">
|
||||
<div
|
||||
className="outline text-xs outline-3 outline-white px-2 shadow w-full flex items-center h-[30px] hover:bg-opacity-100 hover:shadow-md rounded-lg bg-white duration-150 cursor-pointer ease-linear nice-shadow"
|
||||
onClick={() => addOption(qIndex)}
|
||||
>
|
||||
<Plus size={14} className="inline-block" />
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{view === 'teacher' && questions.length <= 5 && (
|
||||
<div className="flex justify-center mx-auto px-2">
|
||||
<div
|
||||
className="flex w-full my-2 py-2 px-4 bg-white text-slate text-xs rounded-md nice-shadow hover:shadow-sm cursor-pointer space-x-3 items-center transition duration-150 ease-linear"
|
||||
onClick={addQuestion}
|
||||
>
|
||||
<PlusCircle size={14} className="inline-block" />
|
||||
<span>Add Question</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{view === 'teacher' &&questions.length <= 5 && (
|
||||
<div className="flex justify-center mx-auto px-2">
|
||||
<div
|
||||
className="flex w-full my-2 py-2 px-4 bg-white text-slate text-xs rounded-md nice-shadow hover:shadow-sm cursor-pointer space-x-3 items-center transition duration-150 ease-linear"
|
||||
onClick={addQuestion}
|
||||
>
|
||||
<PlusCircle size={14} className="inline-block" />
|
||||
<span>Add Question</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</AssignmentBoxUI>
|
||||
);
|
||||
)}
|
||||
</AssignmentBoxUI>
|
||||
);
|
||||
}
|
||||
else {
|
||||
return <div className='flex flex-row space-x-2 text-sm items-center'>
|
||||
<Info size={12} />
|
||||
<p>No questions found</p>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default TaskQuizObject;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,16 @@
|
|||
import { useLHSession } from '@components/Contexts/LHSessionContext';
|
||||
import { getAPIUrl } from '@services/config/config'
|
||||
import UserAvatar from '@components/Objects/UserAvatar';
|
||||
import Modal from '@components/StyledElements/Modal/Modal';
|
||||
import { getAPIUrl } from '@services/config/config';
|
||||
import { getUserAvatarMediaDirectory } from '@services/media/media';
|
||||
import { swrFetcher } from '@services/utils/ts/requests';
|
||||
import React from 'react'
|
||||
import { Loader, SendHorizonal, UserCheck, X } from 'lucide-react';
|
||||
import React, { useEffect } from 'react';
|
||||
import useSWR from 'swr';
|
||||
import EvaluateAssignment from './Modals/EvaluateAssignment';
|
||||
import { AssignmentProvider } from '@components/Contexts/Assignments/AssignmentContext';
|
||||
import { AssignmentsTaskProvider } from '@components/Contexts/Assignments/AssignmentsTaskContext';
|
||||
import AssignmentSubmissionProvider from '@components/Contexts/Assignments/AssignmentSubmissionContext';
|
||||
|
||||
function AssignmentSubmissionsSubPage({ assignment_uuid }: { assignment_uuid: string }) {
|
||||
const session = useLHSession() as any;
|
||||
|
|
@ -11,15 +19,123 @@ function AssignmentSubmissionsSubPage({ assignment_uuid }: { assignment_uuid: st
|
|||
const { data: assignmentSubmission, error: assignmentError } = useSWR(
|
||||
`${getAPIUrl()}assignments/assignment_${assignment_uuid}/submissions`,
|
||||
(url) => swrFetcher(url, access_token)
|
||||
)
|
||||
return (
|
||||
<div className='pl-10 mr-10 flex'>
|
||||
{assignmentSubmission && assignmentSubmission.length > 0 && (
|
||||
<div>s</div>
|
||||
)}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(assignmentSubmission);
|
||||
}, [session, assignmentSubmission]);
|
||||
|
||||
const renderSubmissions = (status: string) => {
|
||||
return assignmentSubmission
|
||||
?.filter((submission: any) => submission.submission_status === status)
|
||||
.map((submission: any) => (
|
||||
<SubmissionBox key={submission.submission_uuid} submission={submission} assignment_uuid={assignment_uuid} user_id={submission.user_id} />
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='pl-10 mr-10 flex flex-col pt-3 w-full'>
|
||||
<div className='flex flex-row w-full'>
|
||||
<div className='flex-1'>
|
||||
<div className='flex w-fit mx-auto px-3.5 py-1 bg-rose-600/80 space-x-2 my-5 items-center text-sm font-bold text-white rounded-full'>
|
||||
<X size={18} />
|
||||
<h3>Late</h3>
|
||||
</div>
|
||||
{renderSubmissions('LATE')}
|
||||
</div>
|
||||
<div className='flex-1'>
|
||||
<div className='flex w-fit mx-auto px-3.5 py-1 bg-amber-600/80 space-x-2 my-5 items-center text-sm font-bold text-white rounded-full'>
|
||||
<SendHorizonal size={18} />
|
||||
<h3>Submitted</h3>
|
||||
</div>
|
||||
{renderSubmissions('SUBMITTED')}
|
||||
</div>
|
||||
<div className='flex-1'>
|
||||
<div className='flex w-fit mx-auto px-3.5 py-1 bg-emerald-600/80 space-x-2 my-5 items-center text-sm font-bold text-white rounded-full'>
|
||||
<UserCheck size={18} />
|
||||
<h3>Graded</h3>
|
||||
</div>
|
||||
{renderSubmissions('GRADED')}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default AssignmentSubmissionsSubPage
|
||||
function SubmissionBox({ assignment_uuid, user_id, submission }: any) {
|
||||
const session = useLHSession() as any;
|
||||
const access_token = session?.data?.tokens?.access_token;
|
||||
const [gradeSudmissionModal, setGradeSubmissionModal] = React.useState({
|
||||
open: false,
|
||||
submission_id: '',
|
||||
});
|
||||
|
||||
const { data: user, error: userError } = useSWR(
|
||||
`${getAPIUrl()}users/id/${user_id}`,
|
||||
(url) => swrFetcher(url, access_token)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(user);
|
||||
}
|
||||
, [session, user]);
|
||||
|
||||
return (
|
||||
<div className='flex flex-row bg-white shadow-[0px_4px_16px_rgba(0,0,0,0.06)] nice-shadow rounded-lg p-4 w-[350px] mx-auto'>
|
||||
<div className='flex flex-col space-y-2 w-full'>
|
||||
<div className='flex justify-between w-full'>
|
||||
<h2 className='uppercase text-slate-400 text-xs tracking-tight font-semibold'>Submission</h2>
|
||||
<p className='uppercase text-xs tracking-tight font-semibold'>
|
||||
{new Date(submission.creation_date).toLocaleDateString('en-UK', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div className='flex justify-between space-x-2'>
|
||||
<div className='flex space-x-2'>
|
||||
<UserAvatar
|
||||
border="border-4"
|
||||
avatar_url={getUserAvatarMediaDirectory(user?.user_uuid, user?.avatar_image)}
|
||||
predefined_avatar={user?.avatar_image ? undefined : 'empty'}
|
||||
width={40}
|
||||
/>
|
||||
<div className='flex flex-col'>
|
||||
{user?.first_name && user?.last_name ? (<p className='text-sm font-semibold'>{user?.first_name} {user?.last_name}</p>) : (<p className='text-sm font-semibold'>@{user?.username}</p>)}
|
||||
<p className='text-xs text-slate-400'>{user?.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col'>
|
||||
|
||||
<Modal
|
||||
isDialogOpen={gradeSudmissionModal.open && gradeSudmissionModal.submission_id === submission.submission_uuid}
|
||||
onOpenChange={(open: boolean) => setGradeSubmissionModal({ open, submission_id: submission.submission_uuid })}
|
||||
minHeight="lg"
|
||||
minWidth="lg"
|
||||
dialogContent={
|
||||
<AssignmentProvider assignment_uuid={"assignment_" + assignment_uuid}>
|
||||
<AssignmentsTaskProvider>
|
||||
<AssignmentSubmissionProvider assignment_uuid={"assignment_" + assignment_uuid}>
|
||||
<EvaluateAssignment user_id={user_id} />
|
||||
</AssignmentSubmissionProvider>
|
||||
</AssignmentsTaskProvider>
|
||||
</AssignmentProvider>
|
||||
}
|
||||
dialogTitle={`Evaluate @${user?.username}`}
|
||||
dialogDescription="Evaluate the submission"
|
||||
dialogTrigger={
|
||||
<div className='bg-slate-800 hover:bg-slate-700 text-white font-bold py-2 px-4 rounded text-xs cursor-pointer'>
|
||||
Evaluate
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AssignmentSubmissionsSubPage;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
import { useAssignments } from '@components/Contexts/Assignments/AssignmentContext';
|
||||
import { Download, Info, Medal } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import React from 'react'
|
||||
import TaskQuizObject from '../../_components/TaskEditor/Subs/TaskTypes/TaskQuizObject';
|
||||
import TaskFileObject from '../../_components/TaskEditor/Subs/TaskTypes/TaskFileObject';
|
||||
import { useOrg } from '@components/Contexts/OrgContext';
|
||||
import { getTaskRefFileDir } from '@services/media/media';
|
||||
|
||||
function EvaluateAssignment({ user_id }: any) {
|
||||
const assignments = useAssignments() as any;
|
||||
const org = useOrg() as any;
|
||||
|
||||
console.log(assignments);
|
||||
return (
|
||||
<div className='flex-col space-y-4 px-3 py-3 overflow-y-auto min-h-fit'>
|
||||
{assignments && assignments?.assignment_tasks?.sort((a: any, b: any) => a.id - b.id).map((task: any, index: number) => {
|
||||
return (
|
||||
<div className='flex flex-col space-y-2' key={task.assignment_task_uuid}>
|
||||
<div className='flex justify-between py-2'>
|
||||
<div className='flex space-x-2 font-semibold text-slate-800'>
|
||||
<p>Task {index + 1} : </p>
|
||||
<p className='text-slate-500'>{task.description}</p>
|
||||
</div>
|
||||
<div className='flex space-x-2'>
|
||||
<div
|
||||
onClick={() => alert(task.hint)}
|
||||
className='px-3 py-1 flex items-center nice-shadow bg-amber-50/40 text-amber-900 rounded-full space-x-2 cursor-pointer'>
|
||||
<Info size={13} />
|
||||
<p className='text-xs font-semibold'>View Hint</p>
|
||||
</div>
|
||||
<Link
|
||||
href={getTaskRefFileDir(
|
||||
org?.org_uuid,
|
||||
assignments?.course_object.course_uuid,
|
||||
assignments?.activity_object.activity_uuid,
|
||||
assignments?.assignment_object.assignment_uuid,
|
||||
task.assignment_task_uuid,
|
||||
task.reference_file
|
||||
)}
|
||||
target='_blank'
|
||||
download={true}
|
||||
className='px-3 py-1 flex items-center nice-shadow bg-cyan-50/40 text-cyan-900 rounded-full space-x-2 cursor-pointer'>
|
||||
<Download size={13} />
|
||||
<div className='flex items-center space-x-2'>
|
||||
{task.reference_file && (
|
||||
<span className='relative'>
|
||||
<span className='absolute right-0 top-0 block h-2 w-2 rounded-full ring-2 ring-white bg-green-400'></span>
|
||||
</span>
|
||||
)}
|
||||
<p className='text-xs font-semibold'>Reference Document</p>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{task.assignment_type === 'QUIZ' && <TaskQuizObject key={task.assignment_task_uuid} view='grading' user_id={user_id} assignmentTaskUUID={task.assignment_task_uuid} />}
|
||||
{task.assignment_type === 'FILE_SUBMISSION' && <TaskFileObject key={task.assignment_task_uuid} view='student' assignmentTaskUUID={task.assignment_task_uuid} />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<div className='flex flex-row-reverse font-semibold items-center'>
|
||||
<button className='flex space-x-2 px-4 py-2 bg-teal-600/80 text-white rounded-lg nice-shadow items-center'>
|
||||
<Medal size={18} />
|
||||
<span>Set final grade</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EvaluateAssignment
|
||||
|
|
@ -7,6 +7,8 @@ import {
|
|||
Eye,
|
||||
File,
|
||||
FilePenLine,
|
||||
Globe,
|
||||
Lock,
|
||||
MoreVertical,
|
||||
Pencil,
|
||||
Save,
|
||||
|
|
@ -60,6 +62,19 @@ function ActivityElement(props: ActivitiyElementProps) {
|
|||
router.refresh()
|
||||
}
|
||||
|
||||
async function changePublicStatus() {
|
||||
await updateActivity(
|
||||
{
|
||||
published: !props.activity.published,
|
||||
},
|
||||
props.activity.activity_uuid,
|
||||
access_token
|
||||
)
|
||||
mutate(`${getAPIUrl()}courses/${props.course_uuid}/meta`)
|
||||
await revalidateTags(['courses'], props.orgslug)
|
||||
router.refresh()
|
||||
}
|
||||
|
||||
async function updateActivityName(activityId: string) {
|
||||
if (
|
||||
modifiedActivity?.activityId === activityId &&
|
||||
|
|
@ -134,9 +149,27 @@ function ActivityElement(props: ActivitiyElementProps) {
|
|||
className="text-neutral-400 hover:cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Edit and View Button */}
|
||||
<div className="flex flex-row space-x-2">
|
||||
<ActivityElementOptions activity={props.activity} />
|
||||
{/* Publishing */}
|
||||
<div
|
||||
className={`hover:cursor-pointer p-1 px-3 border shadow-lg rounded-md font-bold text-xs flex items-center space-x-1 ${!props.activity.published
|
||||
? 'bg-gradient-to-bl text-green-800 from-green-400/50 to-lime-200/80 border-green-600/10 shadow-green-900/10'
|
||||
: 'bg-gradient-to-bl text-gray-800 from-gray-400/50 to-gray-200/80 border-gray-600/10 shadow-gray-900/10'
|
||||
}`}
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => changePublicStatus()}
|
||||
>
|
||||
{!props.activity.published ? (
|
||||
<Globe strokeWidth={2} size={12} className="text-green-600" />
|
||||
) : (
|
||||
<Lock strokeWidth={2} size={12} className="text-gray-600" />
|
||||
)}
|
||||
<span>{!props.activity.published ? 'Publish' : 'UnPublish'}</span>
|
||||
</div>
|
||||
<Link
|
||||
href={
|
||||
getUriWithOrg(props.orgslug, '') +
|
||||
|
|
@ -149,10 +182,10 @@ function ActivityElement(props: ActivitiyElementProps) {
|
|||
)}`
|
||||
}
|
||||
prefetch
|
||||
className=" hover:cursor-pointer p-1 px-3 bg-gray-200 rounded-md font-bold text-xs flex items-center space-x-1"
|
||||
className=" hover:cursor-pointer p-1 px-3 bg-gradient-to-bl text-cyan-800 from-sky-400/50 to-cyan-200/80 border border-cyan-600/10 shadow-cyan-900/10 shadow-lg rounded-md font-bold text-xs flex items-center space-x-1"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Eye strokeWidth={2} size={12} className="text-gray-600" />
|
||||
<Eye strokeWidth={2} size={12} className="text-sky-600" />
|
||||
<span>Preview</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,22 +1,25 @@
|
|||
import { useAssignmentSubmission } from '@components/Contexts/Assignments/AssignmentSubmissionContext'
|
||||
import { BookUser, EllipsisVertical, FileUp, Forward, Info, InfoIcon, ListTodo, Save } from 'lucide-react'
|
||||
import React, { use, useEffect } from 'react'
|
||||
import { BookPlus, BookUser, EllipsisVertical, FileUp, Forward, InfoIcon, ListTodo, Save } from 'lucide-react'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
type AssignmentBoxProps = {
|
||||
type: 'quiz' | 'file'
|
||||
view?: 'teacher' | 'student'
|
||||
view?: 'teacher' | 'student' | 'grading'
|
||||
maxPoints?: number
|
||||
currentPoints?: number
|
||||
saveFC?: () => void
|
||||
submitFC?: () => void
|
||||
gradeFC?: () => void
|
||||
showSavingDisclaimer?: boolean
|
||||
children: React.ReactNode
|
||||
|
||||
}
|
||||
|
||||
function AssignmentBoxUI({ type, view, saveFC, submitFC, showSavingDisclaimer, children }: AssignmentBoxProps) {
|
||||
function AssignmentBoxUI({ type, view, currentPoints, maxPoints, saveFC, submitFC, gradeFC, showSavingDisclaimer, children }: AssignmentBoxProps) {
|
||||
const submission = useAssignmentSubmission() as any
|
||||
useEffect(() => {
|
||||
}
|
||||
, [submission])
|
||||
, [submission])
|
||||
return (
|
||||
<div className='flex flex-col px-6 py-4 nice-shadow rounded-md bg-slate-100/30'>
|
||||
<div className='flex justify-between space-x-2 pb-2 text-slate-400 items-center'>
|
||||
|
|
@ -44,16 +47,22 @@ function AssignmentBoxUI({ type, view, saveFC, submitFC, showSavingDisclaimer, c
|
|||
<p>Teacher view</p>
|
||||
</div>
|
||||
}
|
||||
{maxPoints &&
|
||||
<div className='flex bg-emerald-200/20 text-xs rounded-full space-x-1 px-2 py-0.5 mx-auto font-bold outline items-center text-emerald-600 outline-1 outline-emerald-300/40'>
|
||||
<BookPlus size={12} />
|
||||
<p>{maxPoints} points</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className='flex px-1 py-1 rounded-md items-center'>
|
||||
{showSavingDisclaimer &&
|
||||
<div className='flex space-x-2 items-center font-semibold px-3 py-1 outline-dashed outline-red-200 text-red-400 mr-5 rounded-full'>
|
||||
<InfoIcon size={14} />
|
||||
<p className='text-xs'>Don't forget to save your progress</p>
|
||||
</div>
|
||||
{showSavingDisclaimer &&
|
||||
<div className='flex space-x-2 items-center font-semibold px-3 py-1 outline-dashed outline-red-200 text-red-400 mr-5 rounded-full'>
|
||||
<InfoIcon size={14} />
|
||||
<p className='text-xs'>Don't forget to save your progress</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
{/* Save button */}
|
||||
{/* Teacher button */}
|
||||
{view === 'teacher' &&
|
||||
<div
|
||||
onClick={() => saveFC && saveFC()}
|
||||
|
|
@ -62,7 +71,9 @@ function AssignmentBoxUI({ type, view, saveFC, submitFC, showSavingDisclaimer, c
|
|||
<p className='text-xs font-semibold'>Save</p>
|
||||
</div>
|
||||
}
|
||||
{view === 'student' && submission.length <= 0 &&
|
||||
|
||||
{/* Student button */}
|
||||
{view === 'student' && submission && submission.length <= 0 &&
|
||||
<div
|
||||
onClick={() => submitFC && submitFC()}
|
||||
className='flex px-2 py-1 cursor-pointer rounded-md space-x-2 items-center bg-gradient-to-bl text-emerald-700 bg-emerald-300/20 hover:bg-emerald-300/10 hover:outline-offset-4 active:outline-offset-1 linear transition-all outline-offset-2 outline-dashed outline-emerald-500/60'>
|
||||
|
|
@ -71,6 +82,18 @@ function AssignmentBoxUI({ type, view, saveFC, submitFC, showSavingDisclaimer, c
|
|||
</div>
|
||||
}
|
||||
|
||||
{/* Grading button */}
|
||||
{view === 'grading' &&
|
||||
<div
|
||||
onClick={() => gradeFC && gradeFC()}
|
||||
className='flex px-0.5 py-0.5 cursor-pointer rounded-md space-x-2 items-center bg-gradient-to-bl hover:outline-offset-4 active:outline-offset-1 linear transition-all outline-offset-2 outline-dashed outline-orange-500/60'>
|
||||
<p className='font-semibold px-2 text-xs text-orange-700'>Current points : {currentPoints}</p>
|
||||
<div className='bg-gradient-to-bl text-orange-700 bg-orange-300/20 hover:bg-orange-300/10 items-center flex rounded-md px-2 py-1 space-x-2'>
|
||||
<BookPlus size={14} />
|
||||
<p className='text-xs font-semibold'>Grade</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ const contentClose = keyframes({
|
|||
|
||||
const DialogOverlay = styled(Dialog.Overlay, {
|
||||
backgroundColor: blackA.blackA9,
|
||||
backdropFilter: 'blur(0.6px)',
|
||||
position: 'fixed',
|
||||
zIndex: 500,
|
||||
inset: 0,
|
||||
|
|
|
|||
|
|
@ -20,25 +20,25 @@
|
|||
"@radix-ui/react-switch": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"@stitches/react": "^1.2.8",
|
||||
"@tiptap/core": "^2.5.4",
|
||||
"@tiptap/extension-code-block-lowlight": "^2.5.4",
|
||||
"@tiptap/extension-collaboration": "^2.5.4",
|
||||
"@tiptap/extension-collaboration-cursor": "^2.5.4",
|
||||
"@tiptap/extension-youtube": "^2.5.4",
|
||||
"@tiptap/html": "^2.5.4",
|
||||
"@tiptap/pm": "^2.5.4",
|
||||
"@tiptap/react": "^2.5.4",
|
||||
"@tiptap/starter-kit": "^2.5.4",
|
||||
"@tiptap/core": "^2.5.8",
|
||||
"@tiptap/extension-code-block-lowlight": "^2.5.8",
|
||||
"@tiptap/extension-collaboration": "^2.5.8",
|
||||
"@tiptap/extension-collaboration-cursor": "^2.5.8",
|
||||
"@tiptap/extension-youtube": "^2.5.8",
|
||||
"@tiptap/html": "^2.5.8",
|
||||
"@tiptap/pm": "^2.5.8",
|
||||
"@tiptap/react": "^2.5.8",
|
||||
"@tiptap/starter-kit": "^2.5.8",
|
||||
"@types/randomcolor": "^0.5.9",
|
||||
"avvvatars-react": "^0.4.2",
|
||||
"dayjs": "^1.11.11",
|
||||
"dayjs": "^1.11.12",
|
||||
"formik": "^2.4.6",
|
||||
"framer-motion": "^10.18.0",
|
||||
"get-youtube-id": "^1.0.1",
|
||||
"highlight.js": "^11.10.0",
|
||||
"katex": "^0.16.11",
|
||||
"lowlight": "^3.1.0",
|
||||
"lucide-react": "^0.408.0",
|
||||
"lucide-react": "^0.424.0",
|
||||
"next": "14.2.5",
|
||||
"next-auth": "^4.24.7",
|
||||
"nextjs-toploader": "^1.6.12",
|
||||
|
|
@ -60,7 +60,7 @@
|
|||
"tailwind-scrollbar": "^3.1.0",
|
||||
"uuid": "^9.0.1",
|
||||
"y-indexeddb": "^9.0.12",
|
||||
"y-prosemirror": "^1.2.9",
|
||||
"y-prosemirror": "^1.2.11",
|
||||
"y-webrtc": "^10.3.0",
|
||||
"yjs": "^13.6.18"
|
||||
},
|
||||
|
|
@ -73,12 +73,12 @@
|
|||
"@types/react-transition-group": "^4.4.10",
|
||||
"@types/styled-components": "^5.1.34",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-next": "^14.2.5",
|
||||
"eslint-plugin-unused-imports": "^3.2.0",
|
||||
"postcss": "^8.4.39",
|
||||
"tailwindcss": "^3.4.6",
|
||||
"postcss": "^8.4.40",
|
||||
"tailwindcss": "^3.4.7",
|
||||
"typescript": "5.4.4"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
809
apps/web/pnpm-lock.yaml
generated
809
apps/web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -97,7 +97,21 @@ export async function getAssignmentTaskSubmissionsMe(
|
|||
access_token: string
|
||||
) {
|
||||
const result: any = await fetch(
|
||||
`${getAPIUrl()}assignments/${assignmentUUID}/tasks/${assignmentTaskUUID}/submissions/user/me`,
|
||||
`${getAPIUrl()}assignments/${assignmentUUID}/tasks/${assignmentTaskUUID}/submissions/me`,
|
||||
RequestBodyWithAuthHeader('GET', null, null, access_token)
|
||||
)
|
||||
const res = await getResponseMetadata(result)
|
||||
return res
|
||||
}
|
||||
|
||||
export async function getAssignmentTaskSubmissionsUser(
|
||||
assignmentTaskUUID: string,
|
||||
user_id: string,
|
||||
assignmentUUID: string,
|
||||
access_token: string
|
||||
) {
|
||||
const result: any = await fetch(
|
||||
`${getAPIUrl()}assignments/${assignmentUUID}/tasks/${assignmentTaskUUID}/submissions/user/${user_id}`,
|
||||
RequestBodyWithAuthHeader('GET', null, null, access_token)
|
||||
)
|
||||
const res = await getResponseMetadata(result)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue