From b215885dc668f26f7bbc97c3e5009927ffa39248 Mon Sep 17 00:00:00 2001 From: Joao Correia Date: Tue, 4 Feb 2025 23:22:56 +0000 Subject: [PATCH] - Make isDiagnostic false when all steps of the exam workflow have been approved. - Implement Load Exam and Edit Exam buttons --- .../ExamEditor/SettingsEditor/level.tsx | 2 +- .../SettingsEditor/listening/index.tsx | 2 +- .../SettingsEditor/reading/index.tsx | 2 +- .../SettingsEditor/speaking/index.tsx | 2 +- .../SettingsEditor/writing/index.tsx | 2 +- src/pages/approval-workflows/[id]/index.tsx | 106 +++++++++++++++--- 6 files changed, 96 insertions(+), 20 deletions(-) diff --git a/src/components/ExamEditor/SettingsEditor/level.tsx b/src/components/ExamEditor/SettingsEditor/level.tsx index 913abb4b..e55e6c15 100644 --- a/src/components/ExamEditor/SettingsEditor/level.tsx +++ b/src/components/ExamEditor/SettingsEditor/level.tsx @@ -195,7 +195,7 @@ const LevelSettings: React.FC = () => { category: s.settings.category }; }).filter(part => part.exercises.length > 0), - isDiagnostic: false, + isDiagnostic: true, // using isDiagnostic to keep exam hidden until the respective approval workflow is completed. minTimer, module: "level", id: title, diff --git a/src/components/ExamEditor/SettingsEditor/listening/index.tsx b/src/components/ExamEditor/SettingsEditor/listening/index.tsx index 0c9ae1f6..489d9c3d 100644 --- a/src/components/ExamEditor/SettingsEditor/listening/index.tsx +++ b/src/components/ExamEditor/SettingsEditor/listening/index.tsx @@ -138,7 +138,7 @@ const ListeningSettings: React.FC = () => { category: s.settings.category }; }), - isDiagnostic: false, + isDiagnostic: true, // using isDiagnostic to keep exam hidden until the respective approval workflow is completed. minTimer, module: "listening", id: title, diff --git a/src/components/ExamEditor/SettingsEditor/reading/index.tsx b/src/components/ExamEditor/SettingsEditor/reading/index.tsx index 2d4abe67..0597dea3 100644 --- a/src/components/ExamEditor/SettingsEditor/reading/index.tsx +++ b/src/components/ExamEditor/SettingsEditor/reading/index.tsx @@ -76,7 +76,7 @@ const ReadingSettings: React.FC = () => { category: localSettings.category }; }), - isDiagnostic: false, + isDiagnostic: true, // using isDiagnostic to keep exam hidden until the respective approval workflow is completed. minTimer, module: "reading", id: title, diff --git a/src/components/ExamEditor/SettingsEditor/speaking/index.tsx b/src/components/ExamEditor/SettingsEditor/speaking/index.tsx index ba86d02d..d187e2e4 100644 --- a/src/components/ExamEditor/SettingsEditor/speaking/index.tsx +++ b/src/components/ExamEditor/SettingsEditor/speaking/index.tsx @@ -181,7 +181,7 @@ const SpeakingSettings: React.FC = () => { minTimer, module: "speaking", id: title, - isDiagnostic: false, + isDiagnostic: true, // using isDiagnostic to keep exam hidden until the respective approval workflow is completed. variant: undefined, difficulty, instructorGender: "varied", diff --git a/src/components/ExamEditor/SettingsEditor/writing/index.tsx b/src/components/ExamEditor/SettingsEditor/writing/index.tsx index 7799ca07..2147f8ec 100644 --- a/src/components/ExamEditor/SettingsEditor/writing/index.tsx +++ b/src/components/ExamEditor/SettingsEditor/writing/index.tsx @@ -131,7 +131,7 @@ const WritingSettings: React.FC = () => { minTimer, module: "writing", id: title, - isDiagnostic: false, + isDiagnostic: true, // using isDiagnostic to keep exam hidden until the respective approval workflow is completed. variant: undefined, difficulty, private: isPrivate, diff --git a/src/pages/approval-workflows/[id]/index.tsx b/src/pages/approval-workflows/[id]/index.tsx index 64d16bd5..c4a6bb3e 100644 --- a/src/pages/approval-workflows/[id]/index.tsx +++ b/src/pages/approval-workflows/[id]/index.tsx @@ -10,9 +10,11 @@ import useApprovalWorkflow from "@/hooks/useApprovalWorkflow"; import { ApprovalWorkflow, getUserTypeLabelShort, WorkflowStep } from "@/interfaces/approval.workflow"; import { User } from "@/interfaces/user"; import { sessionOptions } from "@/lib/session"; +import useExamStore from "@/stores/exam"; import { redirect, serialize } from "@/utils"; import { requestUser } from "@/utils/api"; import { getApprovalWorkflow } from "@/utils/approval.workflows.be"; +import { getExamById } from "@/utils/exams"; import { shouldRedirectHome } from "@/utils/navigation.disabled"; import { getSpecificUsers, getUser } from "@/utils/users.be"; import axios from "axios"; @@ -20,6 +22,7 @@ import { AnimatePresence, LayoutGroup, motion } from "framer-motion"; import { withIronSessionSsr } from "iron-session/next"; import Head from "next/head"; import Link from "next/link"; +import { useRouter } from "next/router"; import { useState } from "react"; import { BsChevronLeft } from "react-icons/bs"; import { FaSpinner, FaWpforms } from "react-icons/fa6"; @@ -79,14 +82,18 @@ export default function Home({ user, initialWorkflow, id, workflowAssignees, wor const currentWorkflow = workflow || initialWorkflow; - let currentStep = currentWorkflow.steps.findIndex(step => !step.completed || step.rejected); - if (currentStep === -1) - currentStep = currentWorkflow.steps.length - 1; + let currentStepIndex = currentWorkflow.steps.findIndex(step => !step.completed || step.rejected); + if (currentStepIndex === -1) + currentStepIndex = currentWorkflow.steps.length - 1; - const [selectedStepIndex, setSelectedStepIndex] = useState(currentStep); + const [selectedStepIndex, setSelectedStepIndex] = useState(currentStepIndex); const [selectedStep, setSelectedStep] = useState(currentWorkflow.steps[selectedStepIndex]); const [isPanelOpen, setIsPanelOpen] = useState(true); const [comments, setComments] = useState(selectedStep.comments || ""); + const [viewExamIsLoading, setViewExamIsLoading] = useState(false); + const [editExamIsLoading, setEditExamIsLoading] = useState(false); + + const router = useRouter(); const handleStepClick = (index: number, stepInfo: WorkflowStep) => { setSelectedStep(stepInfo); @@ -128,6 +135,11 @@ export default function Home({ user, initialWorkflow, id, workflowAssignees, wor }; const handleApproveStep = () => { + const isLastStep = (selectedStepIndex + 1 === currentWorkflow.steps.length); + if (isLastStep) { + if (!confirm(`Are you sure you want to approve the last step? Doing so will approve the exam.`)) return; + } + const updatedWorkflow: ApprovalWorkflow = { ...currentWorkflow, status: selectedStepIndex === currentWorkflow.steps.length - 1 ? "approved" : "pending", @@ -161,15 +173,35 @@ export default function Home({ user, initialWorkflow, id, workflowAssignees, wor return; }) - if (selectedStepIndex + 1 < currentWorkflow.steps.length){ - handleStepClick(selectedStepIndex + 1, currentWorkflow.steps[selectedStepIndex + 1]); - } else { + if (isLastStep) { setIsPanelOpen(false); + const examModule = currentWorkflow.modules[0]; + const examId = currentWorkflow.examId; + + axios + .patch(`/api/exam/${examModule}/${examId}`, { isDiagnostic: false }) + .then(() => toast.success(`The exam was successfuly approved and this workflow has been completed.`)) + .catch((reason) => { + if (reason.response.status === 404) { + toast.error("Exam not found!"); + return; + } + + if (reason.response.status === 403) { + toast.error("You do not have permission to update this exam!"); + return; + } + + toast.error("Something went wrong, please try again later."); + }) + .finally(reload); + } else { + handleStepClick(selectedStepIndex + 1, currentWorkflow.steps[selectedStepIndex + 1]); } }; const handleRejectStep = () => { - if (!confirm(`Are you sure you want to reject this step?`)) return; + if (!confirm(`Are you sure you want to reject this step? Doing so will terminate this approval workflow.`)) return; const updatedWorkflow: ApprovalWorkflow = { ...currentWorkflow, @@ -206,12 +238,36 @@ export default function Home({ user, initialWorkflow, id, workflowAssignees, wor }) }; - const handleViewExam = () => { + const dispatch = useExamStore((store) => store.dispatch); + const handleViewExam = async () => { + setViewExamIsLoading(true); + const examModule = currentWorkflow.modules[0]; + const examId = currentWorkflow.examId; + if (examModule && examId) { + const exam = await getExamById(examModule, examId.trim()); + if (!exam) { + toast.error( + "Unknown Exam ID! Please make sure you selected the right module and entered the right exam ID", + { toastId: "invalid-exam-id" } + ); + setViewExamIsLoading(false); + return; + } + dispatch({ + type: "INIT_EXAM", + payload: { exams: [exam], modules: [examModule] }, + }); + router.push("/exam"); + } } const handleEditExam = () => { + setEditExamIsLoading(true); + const examModule = currentWorkflow.modules[0]; + const examId = currentWorkflow.examId; + router.push(`/generation?id=${examId}&module=${examModule}`); } return ( @@ -257,21 +313,41 @@ export default function Home({ user, initialWorkflow, id, workflowAssignees, wor color="purple" variant="solid" onClick={handleViewExam} + disabled={viewExamIsLoading} padding="px-6 py-2" className="w-[240px] text-lg flex items-center justify-center gap-2 text-left" > - - View Exam + {viewExamIsLoading ? ( + <> + + Loading... + + ) : ( + <> + + Load Exam + + )} @@ -292,7 +368,7 @@ export default function Home({ user, initialWorkflow, id, workflowAssignees, wor stepType={step.stepType} assignees={step.assignees} finalStep={index === currentWorkflow.steps.length - 1} - currentStep={index === currentStep} + currentStep={index === currentStepIndex} selected={index === selectedStepIndex} onClick={() => handleStepClick(index, step)} /> @@ -393,7 +469,7 @@ export default function Home({ user, initialWorkflow, id, workflowAssignees, wor )} - {selectedStepIndex === currentStep && !selectedStep.completed && !selectedStep.rejected && + {selectedStepIndex === currentStepIndex && !selectedStep.completed && !selectedStep.rejected &&