lms/bootstrap/ssr/assets/attempt-B1heak54.js
2025-12-15 12:26:23 +01:00

343 lines
12 KiB
JavaScript

import { jsx, jsxs } from "react/jsx-runtime";
import { c as cn } from "./utils-DLCPGU0v.js";
import { B as Button } from "./button-CdJZJLGw.js";
import { I as Index } from "./index-CzkkaexN.js";
import { M as Main } from "./main-BKBelQb-.js";
import { Head, router } from "@inertiajs/react";
import { ChevronLeft, ChevronRight, AlertTriangle } from "lucide-react";
import { useState, useEffect } from "react";
import AttemptNavbar from "./attempt-navbar-2WlfrPQ4.js";
import QuestionNavigator from "./question-navigator-BNETVdM2.js";
import QuestionRenderer from "./question-renderer-CEDeFJdm.js";
import TimerComponent from "./timer-component-BZ0pE4jM.js";
import "clsx";
import "tailwind-merge";
import "@radix-ui/react-slot";
import "class-variance-authority";
import "./app-logo-DWyi5bLn.js";
import "lucide-react/dynamic";
import "next-themes";
import "sonner";
import "./use-screen-B7SDA5zE.js";
import "./badge-J-zeQvMg.js";
import "./card-B-gBwpxd.js";
import "./question-type-badge-CdL_99ID.js";
import "./fill-blank-question-DHOFVwov.js";
import "./input-BsvJqbcd.js";
import "./label-0rIIfpX0.js";
import "@radix-ui/react-label";
import "./listening-question-XvCsGH9c.js";
import "./radio-group-Wf8uu9ZY.js";
import "@radix-ui/react-radio-group";
import "./slider-6gv2Y3fS.js";
import "@radix-ui/react-slider";
import "./matching-question-BNHgZgyB.js";
import "./select-BYx0MCUK.js";
import "@radix-ui/react-select";
import "./mcq-question-C6ifvSYZ.js";
import "./multiple-select-question-Cokv890S.js";
import "./checkbox--3Zj5G-w.js";
import "@radix-ui/react-checkbox";
import "./ordering-question-DQb6NScg.js";
import "./short-answer-question-aygLWm6w.js";
import "./textarea-Z0d4V-ti.js";
const AlertDialog = ({ open, onOpenChange, children }) => {
if (!open) return null;
return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
/* @__PURE__ */ jsx(
"div",
{
className: "fixed inset-0 bg-black/80",
onClick: () => onOpenChange(false)
}
),
/* @__PURE__ */ jsx("div", { className: "relative z-50", children })
] });
};
const AlertDialogContent = ({ className, children, ...props }) => /* @__PURE__ */ jsx(
"div",
{
className: cn(
"w-full max-w-lg bg-white border rounded-lg shadow-lg p-6 space-y-4",
className
),
...props,
children
}
);
const AlertDialogHeader = ({
className,
children,
...props
}) => /* @__PURE__ */ jsx(
"div",
{
className: cn("space-y-2", className),
...props,
children
}
);
const AlertDialogFooter = ({
className,
children,
...props
}) => /* @__PURE__ */ jsx(
"div",
{
className: cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className),
...props,
children
}
);
const AlertDialogTitle = ({
className,
children,
...props
}) => /* @__PURE__ */ jsx(
"h2",
{
className: cn("text-lg font-semibold", className),
...props,
children
}
);
const AlertDialogDescription = ({
className,
children,
...props
}) => /* @__PURE__ */ jsx(
"p",
{
className: cn("text-sm text-gray-600", className),
...props,
children
}
);
const AlertDialogAction = ({
className,
children,
...props
}) => /* @__PURE__ */ jsx(
Button,
{
className: cn("", className),
...props,
children
}
);
const AlertDialogCancel = ({
className,
children,
...props
}) => /* @__PURE__ */ jsx(
Button,
{
variant: "outline",
className: cn("mt-2 sm:mt-0", className),
...props,
children
}
);
const TakeExam = ({ attempt }) => {
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
const [answers, setAnswers] = useState({});
const [markedQuestions, setMarkedQuestions] = useState(/* @__PURE__ */ new Set());
const [showSubmitDialog, setShowSubmitDialog] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const durationSeconds = ((attempt.exam.duration_hours || 0) * 60 + (attempt.exam.duration_minutes || 0)) * 60;
const attemptStart = attempt.start_time ? new Date(attempt.start_time).getTime() : Date.now();
const effectiveDuration = durationSeconds > 0 ? durationSeconds : 60 * 60;
const computedDeadline = attempt.end_time ? attempt.end_time : new Date(attemptStart + effectiveDuration * 1e3).toISOString();
const questions = attempt.exam.questions || [];
const currentQuestion = questions[currentQuestionIndex];
const answeredQuestions = new Set(Object.keys(answers).map(Number));
useEffect(() => {
const savedAnswers = localStorage.getItem(`exam-attempt-${attempt.id}`);
if (savedAnswers) {
try {
const parsed = JSON.parse(savedAnswers);
setAnswers(parsed.answers || {});
setMarkedQuestions(new Set(parsed.marked || []));
} catch (error) {
console.error("Failed to load saved answers:", error);
}
}
}, [attempt.id]);
useEffect(() => {
const interval = setInterval(() => {
saveToLocalStorage();
}, 3e4);
return () => clearInterval(interval);
}, [answers, markedQuestions]);
useEffect(() => {
const handleBeforeUnload = (e) => {
e.preventDefault();
e.returnValue = "";
};
window.addEventListener("beforeunload", handleBeforeUnload);
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
}, []);
const saveToLocalStorage = () => {
localStorage.setItem(
`exam-attempt-${attempt.id}`,
JSON.stringify({
answers,
marked: Array.from(markedQuestions),
lastSaved: (/* @__PURE__ */ new Date()).toISOString()
})
);
};
const saveAnswerToBackend = async (questionId, answer) => {
await router.post(
route("exam-attempts.answer", attempt.id),
{
question_id: questionId,
answer_data: answer
},
{
preserveScroll: true,
preserveState: true
}
);
};
const handleAnswerChange = (answer) => {
if (!currentQuestion) return;
setAnswers((prev) => ({
...prev,
[currentQuestion.id]: answer
}));
saveAnswerToBackend(currentQuestion.id, answer);
saveToLocalStorage();
};
const handlePrevious = () => {
if (currentQuestionIndex > 0) {
setCurrentQuestionIndex(currentQuestionIndex - 1);
}
};
const handleNext = () => {
if (currentQuestionIndex < questions.length - 1) {
setCurrentQuestionIndex(currentQuestionIndex + 1);
}
};
const handleSubmit = async () => {
setIsSubmitting(true);
saveToLocalStorage();
const formattedAnswers = Object.entries(answers).map(([questionId, value]) => ({
exam_question_id: Number(questionId),
answer_data: value
}));
router.post(
route("exam-attempts.submit", attempt.id),
{
exam_attempt_id: attempt.id,
answers: formattedAnswers
},
{
onError: (errors) => {
console.log(errors);
},
onSuccess: () => {
localStorage.removeItem(`exam-attempt-${attempt.id}`);
},
onFinish: () => {
setIsSubmitting(false);
}
}
);
};
useEffect(() => {
const handleKeyPress = (e) => {
if (e.key === "ArrowRight" && currentQuestionIndex < questions.length - 1) {
handleNext();
} else if (e.key === "ArrowLeft" && currentQuestionIndex > 0) {
handlePrevious();
}
};
window.addEventListener("keydown", handleKeyPress);
return () => window.removeEventListener("keydown", handleKeyPress);
}, [currentQuestionIndex, questions.length]);
const unansweredCount = questions.length - answeredQuestions.size;
return /* @__PURE__ */ jsxs(Main, { children: [
/* @__PURE__ */ jsx(Head, { title: `Taking: ${attempt.exam.title}` }),
/* @__PURE__ */ jsxs("main", { className: "flex min-h-screen flex-col justify-between overflow-x-hidden", children: [
/* @__PURE__ */ jsx(AttemptNavbar, { attempt, questionIndex: currentQuestionIndex }),
/* @__PURE__ */ jsxs("div", { className: "container py-12", children: [
/* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4 lg:grid-cols-4", children: [
/* @__PURE__ */ jsxs("div", { className: "space-y-4 lg:col-span-3", children: [
/* @__PURE__ */ jsx(TimerComponent, { attempt, endTime: computedDeadline, questionIndex: currentQuestionIndex }),
currentQuestion && /* @__PURE__ */ jsx(
QuestionRenderer,
{
question: currentQuestion,
questionNumber: currentQuestionIndex + 1,
answer: answers[currentQuestion.id],
onAnswerChange: handleAnswerChange
}
),
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded-lg bg-white p-4 shadow", children: [
/* @__PURE__ */ jsxs(Button, { onClick: handlePrevious, disabled: currentQuestionIndex === 0, variant: "outline", children: [
/* @__PURE__ */ jsx(ChevronLeft, { className: "mr-2 h-4 w-4" }),
"Previous"
] }),
currentQuestionIndex < questions.length - 1 ? /* @__PURE__ */ jsxs(Button, { onClick: handleNext, children: [
"Next",
/* @__PURE__ */ jsx(ChevronRight, { className: "ml-2 h-4 w-4" })
] }) : /* @__PURE__ */ jsx(Button, { onClick: () => setShowSubmitDialog(true), variant: "default", children: "Submit Exam" })
] })
] }),
/* @__PURE__ */ jsx("div", { className: "lg:col-span-1", children: /* @__PURE__ */ jsx(
QuestionNavigator,
{
questions,
currentQuestionIndex,
answeredQuestions,
markedQuestions,
onNavigate: setCurrentQuestionIndex
}
) })
] }) }),
/* @__PURE__ */ jsx(AlertDialog, { open: showSubmitDialog, onOpenChange: setShowSubmitDialog, children: /* @__PURE__ */ jsxs(AlertDialogContent, { children: [
/* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
/* @__PURE__ */ jsxs(AlertDialogTitle, { className: "flex items-center gap-2", children: [
/* @__PURE__ */ jsx(AlertTriangle, { className: "h-5 w-5 text-yellow-600" }),
"Submit Exam?"
] }),
/* @__PURE__ */ jsxs(AlertDialogDescription, { className: "space-y-3", children: [
/* @__PURE__ */ jsx("p", { children: "Are you sure you want to submit your exam? This action cannot be undone." }),
unansweredCount > 0 && /* @__PURE__ */ jsx("div", { className: "rounded-lg bg-yellow-50 p-3", children: /* @__PURE__ */ jsxs("p", { className: "text-sm font-semibold text-yellow-800", children: [
"Warning: You have ",
unansweredCount,
" unanswered question",
unansweredCount > 1 ? "s" : "",
"!"
] }) }),
/* @__PURE__ */ jsxs("div", { className: "text-sm", children: [
/* @__PURE__ */ jsxs("p", { children: [
/* @__PURE__ */ jsx("strong", { children: "Answered:" }),
" ",
answeredQuestions.size,
" / ",
questions.length
] }),
/* @__PURE__ */ jsxs("p", { children: [
/* @__PURE__ */ jsx("strong", { children: "Marked for review:" }),
" ",
markedQuestions.size
] })
] })
] })
] }),
/* @__PURE__ */ jsxs(AlertDialogFooter, { children: [
/* @__PURE__ */ jsx(AlertDialogCancel, { onClick: () => setShowSubmitDialog(false), children: "Cancel" }),
/* @__PURE__ */ jsx(AlertDialogAction, { onClick: handleSubmit, disabled: isSubmitting, children: isSubmitting ? "Submitting..." : "Yes, Submit Exam" })
] })
] }) })
] }),
/* @__PURE__ */ jsx(Index, {})
] })
] });
};
export {
TakeExam as default
};