diff --git a/apps/frontend/src/components/common/ExamPaperPDFViewer.tsx b/apps/frontend/src/components/common/ExamPaperPDFViewer.tsx new file mode 100644 index 0000000..9c09412 --- /dev/null +++ b/apps/frontend/src/components/common/ExamPaperPDFViewer.tsx @@ -0,0 +1,901 @@ +import { useState, useEffect, useRef } from "react"; +import { + Download, + ChevronLeft, + ChevronRight, + ZoomIn, + ZoomOut, + Maximize2, + Minimize2, + Loader2, + Home, + BookOpen, + FileText, +} from "lucide-react"; +import { Button } from "../ui/button"; +type ExamPaperPDFViewerProps = { + examData: any; + sections: any; +}; +const ExamPaperPDFViewer = ({ + examData, + sections, +}: ExamPaperPDFViewerProps) => { + const [currentPage, setCurrentPage] = useState(0); + const [totalPages, setTotalPages] = useState(0); + const [zoom, setZoom] = useState(1); + const [isFlipping, setIsFlipping] = useState(false); + const [flipDirection, setFlipDirection] = useState(null); + const [pdfUrl, setPdfUrl] = useState(null); + const [isGenerating, setIsGenerating] = useState(true); + const [error, setError] = useState(null); + const [isFullscreen, setIsFullscreen] = useState(false); + const [viewMode, setViewMode] = useState("double"); + const leftCanvasRef = useRef(null); + const rightCanvasRef = useRef(null); + const pdfDocRef = useRef(null); + const containerRef = useRef(null); + + useEffect(() => { + if (!examData || !sections || sections.length === 0) { + setIsGenerating(false); + return; + } + + const generatePDF = async () => { + try { + const script = document.createElement("script"); + script.src = + "https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"; + script.async = true; + + await new Promise((resolve, reject) => { + script.onload = resolve; + script.onerror = reject; + document.head.appendChild(script); + }); + + // @ts-ignore + const { jsPDF } = window.jspdf; + + const doc = new jsPDF({ + orientation: "portrait", + unit: "mm", + format: "a4", + }); + + let yPos = 20; + const pageHeight = 297; + const margin = 20; + const maxWidth = 170; + + const checkPageBreak = (needed: number) => { + if (yPos + needed > pageHeight - margin) { + doc.addPage(); + yPos = margin; + return true; + } + return false; + }; + + // ============ FIRST PAGE - ICSE Style Header ============ + doc.setFontSize(9); + doc.setFont("times", "normal"); + doc.text( + "This Paper consists of 11 printed pages and 1 blank page.", + margin, + yPos + ); + yPos += 10; + + doc.text(`T23 ${examData.paper_code}`, margin, yPos); + doc.text("Turn Over", 190, yPos, { align: "right" }); + yPos += 10; + + doc.text("© Copyright reserved.", 105, yPos, { align: "center" }); + yPos += 15; + + doc.setFontSize(16); + doc.setFont("times", "bold"); + doc.text(examData.subject?.toUpperCase() || "PHYSICS", 105, yPos, { + align: "center", + }); + yPos += 10; + + doc.setFontSize(12); + doc.setFont("times", "italic"); + doc.text(`(${examData.paper_name || "SCIENCE PAPER 1"})`, 105, yPos, { + align: "center", + }); + yPos += 8; + + // Horizontal line + doc.setLineWidth(0.5); + doc.line(margin, yPos, 190, yPos); + yPos += 8; + + doc.setFontSize(10); + doc.setFont("times", "italic"); + doc.text(`Maximum Marks: ${examData.maximum_marks}`, 105, yPos, { + align: "center", + }); + yPos += 6; + doc.setFont("times", "bold"); + doc.text(`Time allowed: ${examData.time_allowed}`, 105, yPos, { + align: "center", + }); + yPos += 8; + + doc.setFont("times", "italic"); + doc.setFontSize(9); + const headerInstructions = [ + "Answers to this Paper must be written on the paper provided separately.", + "", + "You will not be allowed to write during first 15 minutes.", + "This time is to be spent in reading the question paper.", + "", + "The time given at the head of this Paper is the time allowed for writing the answers.", + ]; + + headerInstructions.forEach((instruction) => { + if (instruction === "") { + yPos += 3; + } else { + checkPageBreak(6); + const lines = doc.splitTextToSize(instruction, maxWidth); + doc.text(lines, 105, yPos, { align: "center" }); + yPos += lines.length * 5; + } + }); + + yPos += 5; + doc.setLineWidth(0.5); + doc.line(margin, yPos, 190, yPos); + yPos += 8; + + doc.setFont("times", "bold"); + doc.setFontSize(10); + const sectionInstruction = + "Section A is compulsory. Attempt any four questions from Section B."; + const sectionLines = doc.splitTextToSize(sectionInstruction, maxWidth); + doc.text(sectionLines, 105, yPos, { align: "center" }); + yPos += sectionLines.length * 5 + 5; + + doc.setFont("times", "italic"); + doc.setFontSize(9); + const marksInstruction = + "The intended marks for questions or parts of questions are given in brackets [ ]."; + const marksLines = doc.splitTextToSize(marksInstruction, maxWidth); + doc.text(marksLines, 105, yPos, { align: "center" }); + yPos += marksLines.length * 5 + 5; + + doc.setLineWidth(0.5); + doc.line(margin, yPos, 190, yPos); + yPos += 15; + + // ============ SECTIONS (Continue on same page if space available) ============ + sections.forEach((section: any, sectionIndex: any) => { + // Only add new page if not the first section or if no space + if (sectionIndex > 0 || yPos > pageHeight - 80) { + doc.addPage(); + yPos = 15; + + // Add page header + doc.setFontSize(9); + doc.setFont("times", "normal"); + doc.text(`T23 ${examData.paper_code}`, margin, yPos); + const pageNum = doc.internal.pages.length - 1; + doc.text(`${pageNum}`, 105, yPos, { align: "center" }); + doc.text("Turn Over", 190, yPos, { align: "right" }); + yPos += 10; + } + + doc.setFontSize(11); + doc.setFont("times", "bold"); + doc.text( + `${section.name.toUpperCase()} (${section.marks} Marks)`, + 105, + yPos, + { align: "center" } + ); + yPos += 6; + + doc.setFontSize(10); + doc.setFont("times", "italic"); + if (section.instruction) { + const instrLines = doc.splitTextToSize( + `(${section.instruction})`, + maxWidth + ); + doc.text(instrLines, 105, yPos, { align: "center" }); + yPos += instrLines.length * 5; + } + yPos += 8; + + (section.questions || []).forEach((question: any) => { + checkPageBreak(20); + + if (yPos === margin) { + doc.setFontSize(9); + doc.setFont("times", "normal"); + doc.text(`T23 ${examData.paper_code}`, margin, 15); + const currentPageNum = doc.internal.pages.length - 1; + doc.text(`${currentPageNum}`, 105, 15, { align: "center" }); + doc.text("Turn Over", 190, 15, { align: "right" }); + yPos = 25; + } + + doc.setFont("times", "bold"); + doc.setFontSize(11); + doc.text(`Question ${question.number}`, margin, yPos); + doc.setFont("times", "normal"); + doc.setFontSize(10); + doc.text(`[${question.total_marks}]`, 190, yPos, { + align: "right", + }); + yPos += 7; + + if (question.instruction) { + doc.setFont("times", "normal"); + doc.setFontSize(10); + const instrLines = doc.splitTextToSize( + question.instruction, + maxWidth + ); + doc.text(instrLines, margin, yPos); + yPos += instrLines.length * 5 + 3; + } + + (question.parts || []).forEach((part: any) => { + checkPageBreak(15); + + if (yPos === margin) { + doc.setFontSize(9); + doc.setFont("times", "normal"); + doc.text(`T23 ${examData.paper_code}`, margin, 15); + const currentPageNum = doc.internal.pages.length - 1; + doc.text(`${currentPageNum}`, 105, 15, { align: "center" }); + doc.text("Turn Over", 190, 15, { align: "right" }); + yPos = 25; + } + + doc.setFont("times", "normal"); + doc.setFontSize(10); + + if (part.number) { + const partLabel = `(${part.number})`; + if (part.marks) { + doc.text(`[${part.marks}]`, 190, yPos, { align: "right" }); + } + + const contentIndent = margin + 12; + + if (part.description) { + doc.text(partLabel, margin, yPos); + const descLines = doc.splitTextToSize( + part.description, + maxWidth - 12 + ); + doc.text(descLines[0], contentIndent, yPos); + yPos += 5; + + for (let i = 1; i < descLines.length; i++) { + doc.text(descLines[i], contentIndent, yPos); + yPos += 5; + } + } else if (part.question && !part.sub_parts?.length) { + doc.text(partLabel, margin, yPos); + const qLines = doc.splitTextToSize( + part.question, + maxWidth - 12 + ); + doc.text(qLines[0], contentIndent, yPos); + yPos += 5; + + for (let i = 1; i < qLines.length; i++) { + doc.text(qLines[i], contentIndent, yPos); + yPos += 5; + } + } else { + doc.text(partLabel, margin, yPos); + } + yPos += 3; + } + + if (part.sub_parts && part.sub_parts.length > 0) { + part.sub_parts.forEach((subPart: any, idx: number) => { + checkPageBreak(10); + + if (yPos === margin) { + doc.setFontSize(9); + doc.setFont("times", "normal"); + doc.text(`T23 ${examData.paper_code}`, margin, 15); + const currentPageNum = doc.internal.pages.length - 1; + doc.text(`${currentPageNum}`, 105, 15, { align: "center" }); + doc.text("Turn Over", 190, 15, { align: "right" }); + yPos = 25; + } + + if (subPart.question) { + const subLabel = `${subPart.letter}`; + const subIndent = part.description + ? margin + 8 + : margin + 12; + const textIndent = part.description + ? margin + 18 + : margin + 22; + + doc.text(subLabel, subIndent, yPos); + const subLines = doc.splitTextToSize( + subPart.question, + maxWidth - (textIndent - margin) + ); + doc.text(subLines, textIndent, yPos); + yPos += + subLines.length * 5 + + (idx < part.sub_parts.length - 1 ? 1 : 2); + } + }); + } + + if (part.options && part.options.length > 0) { + part.options.forEach((option: any) => { + checkPageBreak(6); + const optText = `${option.option_letter} ${option.text}`; + const optLines = doc.splitTextToSize(optText, maxWidth - 12); + doc.text(optLines, margin + 8, yPos); + yPos += optLines.length * 5; + }); + yPos += 3; + } + + if (part.diagram) { + checkPageBreak(30); + yPos += 2; + doc.setLineWidth(0.3); + doc.rect(margin + 8, yPos, 80, 25); + doc.setFont("times", "italic"); + doc.setFontSize(9); + doc.text("[Diagram]", margin + 48, yPos + 13, { + align: "center", + }); + yPos += 28; + doc.setFont("times", "normal"); + doc.setFontSize(10); + } + + yPos += 3; + }); + + yPos += 5; + }); + }); + + const pdfBlob = doc.output("blob"); + const url = URL.createObjectURL(pdfBlob); + // @ts-ignore + setPdfUrl(url); + setTotalPages(doc.internal.pages.length - 1); + setIsGenerating(false); + } catch (error) { + console.error("Error generating PDF:", error); + // @ts-ignore + setError(error.message); + setIsGenerating(false); + } + }; + + generatePDF(); + + return () => { + if (pdfUrl) URL.revokeObjectURL(pdfUrl); + }; + }, [examData, sections]); + + // Render PDF pages + useEffect(() => { + if (!pdfUrl || currentPage === 0) return; + + const loadPDF = async () => { + try { + // @ts-ignore + if (!window["pdfjs-dist/build/pdf"]) { + const script = document.createElement("script"); + script.src = + "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"; + await new Promise((resolve, reject) => { + script.onload = resolve; + script.onerror = reject; + document.head.appendChild(script); + }); + } + // @ts-ignore + const pdfjsLib = window["pdfjs-dist/build/pdf"]; + pdfjsLib.GlobalWorkerOptions.workerSrc = + "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js"; + + const pdf = await pdfjsLib.getDocument(pdfUrl).promise; + pdfDocRef.current = pdf; + + // Render left page + if (currentPage > 0 && leftCanvasRef.current) { + const page = await pdf.getPage(currentPage); + const viewport = page.getViewport({ scale: zoom * 1.5 }); + // @ts-ignore + const context = leftCanvasRef.current.getContext("2d"); + // @ts-ignore + leftCanvasRef.current.height = viewport.height; + // @ts-ignore + leftCanvasRef.current.width = viewport.width; + + await page.render({ canvasContext: context, viewport }).promise; + } + + // Render right page (if in double mode and page exists) + if ( + viewMode === "double" && + currentPage + 1 <= totalPages && + rightCanvasRef.current + ) { + const page = await pdf.getPage(currentPage + 1); + const viewport = page.getViewport({ scale: zoom * 1.5 }); + // @ts-ignore + const context = rightCanvasRef.current.getContext("2d"); + // @ts-ignore + rightCanvasRef.current.height = viewport.height; + // @ts-ignore + rightCanvasRef.current.width = viewport.width; + + await page.render({ canvasContext: context, viewport }).promise; + } + } catch (error) { + console.error("Error loading PDF:", error); + } + }; + + loadPDF(); + }, [pdfUrl, currentPage, zoom, viewMode, totalPages]); + + const nextPage = () => { + const increment = viewMode === "double" ? 2 : 1; + if (currentPage + increment <= totalPages && !isFlipping) { + // @ts-ignore + setFlipDirection("next"); + setIsFlipping(true); + setTimeout(() => { + setCurrentPage((prev) => Math.min(prev + increment, totalPages)); + setIsFlipping(false); + setFlipDirection(null); + }, 800); + } + }; + + const prevPage = () => { + const decrement = viewMode === "double" ? 2 : 1; + if (currentPage - decrement >= 1 && !isFlipping) { + // @ts-ignore + setFlipDirection("prev"); + setIsFlipping(true); + setTimeout(() => { + setCurrentPage((prev) => Math.max(prev - decrement, 1)); + setIsFlipping(false); + setFlipDirection(null); + }, 800); + } + }; + + const downloadPDF = () => { + if (pdfUrl) { + const a = document.createElement("a"); + a.href = pdfUrl; + a.download = `${examData?.subject}_${examData?.year}_Paper.pdf`; + a.click(); + } + }; + + const toggleFullscreen = () => { + if (!isFullscreen) { + // @ts-ignore + containerRef.current?.requestFullscreen?.() || + // @ts-ignore + containerRef.current?.webkitRequestFullscreen?.(); + setIsFullscreen(true); + } else { + // @ts-ignore + document.exitFullscreen?.() || document.webkitExitFullscreen?.(); + setIsFullscreen(false); + } + }; + + useEffect(() => { + const handleFullscreenChange = () => { + setIsFullscreen(!!document.fullscreenElement); + }; + document.addEventListener("fullscreenchange", handleFullscreenChange); + document.addEventListener("webkitfullscreenchange", handleFullscreenChange); + return () => { + document.removeEventListener("fullscreenchange", handleFullscreenChange); + document.removeEventListener( + "webkitfullscreenchange", + handleFullscreenChange + ); + }; + }, []); + + useEffect(() => { + const handleKeyPress = (e: any) => { + if (e.key === "ArrowRight" || e.key === "ArrowDown" || e.key === " ") { + e.preventDefault(); + nextPage(); + } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") { + e.preventDefault(); + prevPage(); + } else if (e.key === "Home") { + e.preventDefault(); + setCurrentPage(0); + } else if (e.key === "End") { + e.preventDefault(); + if (!isFlipping) { + setCurrentPage(totalPages); + } + } + }; + + window.addEventListener("keydown", handleKeyPress); + return () => window.removeEventListener("keydown", handleKeyPress); + }, [currentPage, totalPages, isFlipping, viewMode]); + + const handleZoomIn = () => { + setZoom((prev) => Math.min(2, prev + 0.15)); + }; + + const handleZoomOut = () => { + setZoom((prev) => Math.max(0.6, prev - 0.15)); + }; + + if (isGenerating) { + return ( +
+
+ +

Generating PDF Exam Paper...

+

This may take a moment

+
+
+ ); + } + + if (error) { + return ( +
+
+

Error generating PDF

+

{error}

+
+
+ ); + } + + return ( +
+ {/* Controls Bar */} +
+
+
+ {/* Left controls */} +
+ + +
+ + + +
+ + {currentPage === 0 + ? "Cover" + : viewMode === "double" && currentPage + 1 <= totalPages + ? `${currentPage}-${currentPage + 1} / ${totalPages}` + : `${currentPage} / ${totalPages}`} + +
+ + +
+ + {/* Right controls */} +
+ + +
+ + + +
+ + {Math.round(zoom * 100)}% + +
+ + + +
+ + + + +
+
+
+
+ +
+

+ {currentPage === 0 + ? "Click 'Open Exam Paper' to begin • Use navigation controls above" + : "Use arrow keys (← →) or Space to navigate • Press ESC to exit fullscreen"} +

+
+ + {/* PDF Viewer */} +
+ {currentPage === 0 ? ( + /* Cover Page */ +
+
+
+
+

+ {examData?.subject?.toUpperCase()} +

+

{examData?.paper_name}

+
+ +
+

+ Maximum Marks:{" "} + {examData?.maximum_marks} +

+

+ Time allowed:{" "} + {examData?.time_allowed} +

+
+

+ Paper Code: {examData?.paper_code} • Year {examData?.year} +

+ + +
+
+
+ ) : ( + /* PDF Pages with Realistic Book Flip Animation */ +
+ {/* Left Page (Static) */} +
+
+ +
+
+ + {/* Right Page (Flipping) - Only in double mode */} + {viewMode === "double" && currentPage + 1 <= totalPages && ( +
+
+ +
+
+ )} + + {/* Center Spine Shadow */} +
+
+ )} +
+ + +
+ ); +}; + +export default ExamPaperPDFViewer; diff --git a/apps/frontend/src/components/pages/ExamPaperPage.tsx b/apps/frontend/src/components/pages/ExamPaperPage.tsx index dab9dd5..09e3989 100644 --- a/apps/frontend/src/components/pages/ExamPaperPage.tsx +++ b/apps/frontend/src/components/pages/ExamPaperPage.tsx @@ -1,14 +1,17 @@ -import PageHeader from "@/components/common/PageHeader"; import { useApi } from "@/hook/useApi"; import { useLocation } from "@tanstack/react-router"; -import { useMemo, useRef } from "react"; -import { Button } from "../ui/button"; -import GlassDropdown from "../common/GlassDropdown"; +import { useMemo, useRef, useState, useCallback } from "react"; import GlassmorphicLoader from "../common/GlassLoader"; +import ExamPaperPDFViewer from "../common/ExamPaperPDFViewer"; +import PageHeader from "../common/PageHeader"; +import GlassLayout from "@/layouts/GlassLayout"; +import { motion, AnimatePresence } from "framer-motion"; const ExamPaperPage = () => { const location = useLocation(); + const [retryKey, setRetryKey] = useState(0); + // --- Parse search params --- const searchString = typeof location.search === "string" ? location.search @@ -30,6 +33,7 @@ const ExamPaperPage = () => { return { subject: searchParams.get("subject") ?? "", year: Number(searchParams.get("year")) || new Date().getFullYear(), + retryKey, // include retry key to re-trigger API }; } else { return { @@ -38,69 +42,46 @@ const ExamPaperPage = () => { paper: searchParams.get("paper") ?? "", code: searchParams.get("code") ?? "", year: Number(searchParams.get("year")) || new Date().getFullYear(), + retryKey, }; } - }, [location.search, prevShow]); + }, [location.search, prevShow, retryKey]); const endpoint = prevShow ? "/exam-paper/get/prev-exam-paper" : "/llm/gen-question-paper"; - // Create stable enabled condition based on payload values const isEnabled = useMemo(() => { - if (prevShow) { - // For prev=true, only need subject - return !!apiPayload.subject; - } else { - // For new generation, need more fields - return !!( - apiPayload.subject || - apiPayload.board || - apiPayload.paper || - apiPayload.code - ); - } - }, [ - prevShow, - apiPayload.subject, - apiPayload.board, - apiPayload.paper, - apiPayload.code, - ]); + if (prevShow) return !!apiPayload.subject; + return !!( + apiPayload.subject || + apiPayload.board || + apiPayload.paper || + apiPayload.code + ); + }, [prevShow, apiPayload]); - // Track successful API calls const hasSuccessfullyLoaded = useRef(false); - // Single API call with conditional endpoint - DISABLE SCHEMA VALIDATION TEMPORARILY const { data: examPaperData, - isLoading: isLoadingExamPaper, - isError: isExamPaperError, - error: examPaperError, + isLoading, + isError, + error, isSuccess, + refetch, } = useApi< { data?: { - exam_paper?: { - exam?: any; - sections?: any[]; - }; + exam_paper?: { exam?: any; sections?: any[] }; }; }, - | { subject: string; year: number } - | { - subject: string; - board: string; - paper: string; - code: string; - year: number; - } + any >( { endpoint, method: "POST", payload: apiPayload, - // responseSchema: examPaperResponseSchema, // COMMENTED OUT TO BYPASS VALIDATION }, { enabled: isEnabled, @@ -110,201 +91,62 @@ const ExamPaperPage = () => { } ); - // Mark as successfully loaded when we get data if (isSuccess && examPaperData && !hasSuccessfullyLoaded.current) { hasSuccessfullyLoaded.current = true; } - // Extract data from response - fix the data path const examPaperResponse = examPaperData?.data?.exam_paper; const examData = examPaperResponse?.exam; const sections = examPaperResponse?.sections; - /* ---------------------------------- */ - /* Helper Functions */ - /* ---------------------------------- */ - - // Helper function to extract question text from any nested structure - const getQuestionText = (questionPart: any): string => { - if (!questionPart) return ""; - - // Check main question field first - if (questionPart.question) return questionPart.question; - - // Check description field - if (questionPart.description) return questionPart.description; - - // Check if there are sub_parts with questions - if (questionPart.sub_parts && questionPart.sub_parts.length > 0) { - const firstSubPart = questionPart.sub_parts[0]; - if (firstSubPart.question) return firstSubPart.question; - } - return ""; - }; - - // Helper function to render all parts of a question - const renderQuestionParts = (parts: any[], level = 0) => { - return parts.map((part, index) => { - const questionText = getQuestionText(part); - const hasSubParts = part.sub_parts && part.sub_parts.length > 0; - - return ( -
0 ? "ml-6" : ""}`} - > - {/* Question header with number/letter */} - {(part.number || part.letter) && questionText && ( -
- - {level === 0 ? `(${part.number})` : part.letter}{" "} - - {questionText} - {part.marks && ( - [{part.marks}] - )} -
- )} - - {/* Description only (when no direct question but has description) */} - {part.description && !questionText && ( -
{part.description}
- )} - - {/* Options for multiple choice */} - {part.options && part.options.length > 0 && ( -
- {part.options.map((option: any, optIndex: number) => ( -
- - {option.option_letter} - - {option.text} -
- ))} -
- )} - - {/* Constants given */} - {part.constants_given && - Object.keys(part.constants_given).length > 0 && ( -
-
Given:
- {Object.entries(part.constants_given).map(([key, value]) => ( -
- {key} = {String(value)} -
- ))} -
- )} - - {/* Equation template */} - {part.equation_template && ( -
-
Complete the equation:
-
- {part.equation_template} -
-
- )} - - {/* Diagram */} - {part.diagram && ( -
-
- Diagram: {part.diagram.description} -
- {part.diagram.labels && part.diagram.labels.length > 0 && ( -
- Labels: {part.diagram.labels.join(", ")} -
- )} -
- )} + const handleRetry = useCallback(() => { + hasSuccessfullyLoaded.current = false; + setRetryKey((prev) => prev + 1); + refetch?.(); + }, [refetch]); - {/* Recursively render sub-parts */} - {hasSubParts && renderQuestionParts(part.sub_parts, level + 1)} -
- ); - }); - }; + // --- Reusable UI blocks --- + const LoaderOverlay = ({ message }: { message: string }) => ( + + ); - /* ---------------------------------- */ - /* UI Components */ - /* ---------------------------------- */ + const RetryButton = ({ label }: { label: string }) => ( + + {label} + + ); - const SimpleLoader = () => ( -
-
- + const ErrorState = () => ( +
+
+

+ {prevShow ? "Error Loading Exam Paper" : "Error Generating Paper"} +

+

+ {error?.message || "An unexpected error occurred. Please try again."} +

+
); - const ErrorState = () => { - hasSuccessfullyLoaded.current = false; - return ( -
-
-
-
- - - -
-

- {prevShow - ? "Error Loading Exam Paper" - : "Error Generating Exam Paper"} -

-

- {examPaperError?.message || - "An unexpected error occurred. Please try again."} -

-
- -
-
- ); - }; - const NoPayloadState = () => ( -
-
-
- - - -
-

+
+
+

No Configuration Found

-

+

Please go back and configure your exam parameters.

@@ -312,352 +154,47 @@ const ExamPaperPage = () => {
); - /* ---------------------------------- */ - /* Question Renderers */ - /* ---------------------------------- */ - - const renderMultipleChoiceQuestion = (question: any) => ( -
-
- Question {question.number}. - {question.instruction && ( - - {question.instruction} - - )} - [{question.total_marks} marks] -
- - {renderQuestionParts(question.parts || [])} -
- ); - - const renderShortAnswerQuestion = (question: any) => ( -
-
- Question {question.number}. - {question.instruction && ( - - {question.instruction} - - )} - [{question.total_marks} marks] -
- - {/* Main question text if exists */} - {question.question_text && ( -
- {question.question_text} -
- )} - - {renderQuestionParts(question.parts || [])} -
- ); - - const renderLongAnswerQuestion = (question: any) => ( -
-
- Question {question.number}. - {question.instruction && ( - - {question.instruction} - - )} - [{question.total_marks} marks] + const NoDataState = () => ( +
+
+

+ No Exam Paper Data +

+

+ No valid exam paper data was found. Please try again. +

+
- - {/* Main question text if exists */} - {question.question_text && ( -
- {question.question_text} -
- )} - - {renderQuestionParts(question.parts || [])}
); - const renderQuestion = (question: any) => { - switch (question.type) { - case "multiple_choice": - return renderMultipleChoiceQuestion(question); - case "short_answer": - case "calculation": - case "diagram_based": - case "complete_equation": - return renderShortAnswerQuestion(question); - case "long_answer": - return renderLongAnswerQuestion(question); - default: - return renderShortAnswerQuestion(question); - } - }; - - /* ---------------------------------- */ - /* Main Render */ - /* ---------------------------------- */ - - // Loading state - if (isLoadingExamPaper) { - return ; - } - - // Error state - if (isExamPaperError) { - return ; - } - - // No payload state - if (!isEnabled) { - return ; - } - - // No data state - if (!examData || !sections || sections.length === 0) { - return ( -
-
-
- - - -
-

- No Exam Paper Data -

-

- No valid exam paper data was found. Please try again. -

- -
-
- ); - } - - // Success state - render ICSE style exam paper return ( -
-
- - - {/* Download buttons */} -
-
- - - -
-
- - {/* ICSE Style Exam Paper */} -
- {/* Header Page - ICSE Style */} -
- {/* Top Border */} -
- {/* ICSE Header */} -
-
- {examData.board || - "COUNCIL FOR THE INDIAN SCHOOL CERTIFICATE EXAMINATIONS"} -
-
-
- {examData.subject || "EXAMINATION PAPER"} -
-
-
- Paper {examData.paper_code || "1"} -
-
- ({examData.paper_name || examData.subject}) -
-
- - {/* Exam Details Box */} -
- - - - - - - - - {examData.reading_time && ( - - - - - )} - -
Maximum Marks: - {examData.maximum_marks} - Time allowed: - {examData.time_allowed} -
Reading Time: - {examData.reading_time} -
-
- - {/* Instructions */} -
-
INSTRUCTIONS
-
- • This paper consists of {sections.length} section - {sections.length > 1 ? "s" : ""}. -
- {sections.map((section: any) => ( -
- • {section.name}{" "} - {section.is_compulsory - ? "is compulsory" - : section.instruction}{" "} - [{section.marks} marks] -
- ))} -
- • The intended marks for questions or parts of questions are - given in brackets [ ]. -
- {examData.additional_instructions?.map( - (instruction: string, idx: number) => ( -
• {instruction}
- ) - )} -
- - {/* Year */} -
- {examData.year} -
-
-
- - {/* Question Sections - ICSE Style */} - {sections.map((section: any) => ( -
-
- {/* Section Header */} -
-
- {section.name} -
- {section.instruction && - section.instruction !== "Follow the instructions" && ( -
- ({section.instruction}) -
- )} -
- [{section.marks} marks] -
-
- - {/* Questions */} -
- {section.questions?.map((question: any) => - renderQuestion(question) - )} -
-
-
- ))} -
-
- - {/* ICSE Print Styles */} - -
+ + {isLoading && } + + {!isLoading && isError && } + {!isLoading && !isEnabled && } + {!isLoading && + !isError && + isEnabled && + (!examData || (sections?.length ?? 0) === 0) && } + + {!isLoading && isSuccess && examData && (sections?.length ?? 0) > 0 && ( + + + + + + + )} + ); };