fix(cbt): comprehensive overhaul of CBT & DPP test pipelines, JSON schemas, active states, and exam pattern filters
This commit is contained in:
180
backend/main.py
180
backend/main.py
@@ -443,21 +443,28 @@ def login(payload: LoginPayload, session: Session = Depends(get_session)):
|
|||||||
def list_active_tests(exam_pattern: Optional[str] = None, type: Optional[str] = None, session: Session = Depends(get_session), current_user: models.User = Depends(get_current_user)):
|
def list_active_tests(exam_pattern: Optional[str] = None, type: Optional[str] = None, session: Session = Depends(get_session), current_user: models.User = Depends(get_current_user)):
|
||||||
query = select(models.Test).where(models.Test.is_active == True)
|
query = select(models.Test).where(models.Test.is_active == True)
|
||||||
if exam_pattern:
|
if exam_pattern:
|
||||||
if exam_pattern == 'JEE_NEET':
|
pattern_str = str(exam_pattern).strip()
|
||||||
|
if pattern_str in ['JEE_NEET', 'ALL', 'All']:
|
||||||
pass
|
pass
|
||||||
elif exam_pattern == 'JEE':
|
elif pattern_str in ['JEE', 'JEE_Main', 'JEE_MAIN', 'JEE Main', 'JEE_Advanced', 'JEE_ADVANCED', 'JEE Advanced']:
|
||||||
query = query.where(models.Test.exam_pattern.in_([models.ExamEnum.JEE_MAIN, models.ExamEnum.JEE_ADVANCED]))
|
query = query.where(models.Test.exam_pattern.in_([models.ExamEnum.JEE_MAIN, models.ExamEnum.JEE_ADVANCED, models.ExamEnum.JEE_NEET]))
|
||||||
|
elif pattern_str in ['NEET']:
|
||||||
|
query = query.where(models.Test.exam_pattern.in_([models.ExamEnum.NEET, models.ExamEnum.JEE_NEET]))
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
# Find matching enum by value string (e.g. "JEE_Main")
|
enum_val = models.ExamEnum(pattern_str)
|
||||||
enum_val = models.ExamEnum(exam_pattern)
|
query = query.where(models.Test.exam_pattern.in_([enum_val, models.ExamEnum.JEE_NEET]))
|
||||||
query = query.where(models.Test.exam_pattern == enum_val)
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
if type:
|
if type:
|
||||||
query = query.where(models.Test.type == type)
|
if type in ["Mock", "CBT"]:
|
||||||
|
query = query.where(models.Test.type.in_(["Mock", "CBT"]))
|
||||||
|
elif type in ["DPS", "DPP"]:
|
||||||
|
query = query.where(models.Test.type.in_(["DPS", "DPP"]))
|
||||||
|
else:
|
||||||
|
query = query.where(models.Test.type == type)
|
||||||
else:
|
else:
|
||||||
query = query.where(models.Test.type != "DPS")
|
query = query.where(models.Test.type.not_in(["DPS", "DPP"]))
|
||||||
return session.exec(query).all()
|
return session.exec(query).all()
|
||||||
|
|
||||||
@app.get("/api/tests/{test_id}", response_model=models.Test, tags=["CBT Engine"])
|
@app.get("/api/tests/{test_id}", response_model=models.Test, tags=["CBT Engine"])
|
||||||
@@ -1303,26 +1310,30 @@ def get_admin_tests(session: Session = Depends(get_session), current_user: model
|
|||||||
return tests
|
return tests
|
||||||
|
|
||||||
class ManualQuestionInput(BaseModel):
|
class ManualQuestionInput(BaseModel):
|
||||||
section: str = "General"
|
section: Optional[str] = "General"
|
||||||
subject: str
|
subject: Optional[str] = "General"
|
||||||
topic: str
|
topic: Optional[str] = "General"
|
||||||
subtopic: str
|
subtopic: Optional[str] = "General"
|
||||||
difficulty: str
|
difficulty: Optional[str] = "Medium"
|
||||||
type: str
|
type: Optional[str] = "MCQ_Single"
|
||||||
question_text: str
|
question_text: Optional[str] = None
|
||||||
options: Optional[Dict[str, Any]] = None
|
question: Optional[str] = None
|
||||||
correct_answer: Dict[str, Any]
|
options: Optional[Any] = None
|
||||||
solution_text: str
|
options_hi: Optional[Any] = None
|
||||||
language: str
|
correct_answer: Optional[Any] = None
|
||||||
|
solution_text: Optional[str] = None
|
||||||
|
solution: Optional[str] = None
|
||||||
|
explanation: Optional[str] = None
|
||||||
|
language: Optional[str] = "English"
|
||||||
|
|
||||||
class ManualTestCreate(BaseModel):
|
class ManualTestCreate(BaseModel):
|
||||||
title: str
|
title: str
|
||||||
type: str = "CBT"
|
type: str = "Mock"
|
||||||
exam_pattern: str
|
exam_pattern: str
|
||||||
time_limit: int
|
time_limit: int
|
||||||
scheduled_date: Optional[datetime] = None
|
scheduled_date: Optional[datetime] = None
|
||||||
end_date: Optional[datetime] = None
|
end_date: Optional[datetime] = None
|
||||||
admin_id: UUID
|
admin_id: Optional[UUID] = None
|
||||||
questions_json: List[ManualQuestionInput]
|
questions_json: List[ManualQuestionInput]
|
||||||
|
|
||||||
@app.post("/api/admin/tests/upload", tags=["Admin"])
|
@app.post("/api/admin/tests/upload", tags=["Admin"])
|
||||||
@@ -1332,7 +1343,7 @@ async def upload_admin_test(
|
|||||||
exam_pattern: str = Form(...),
|
exam_pattern: str = Form(...),
|
||||||
time_limit: int = Form(...),
|
time_limit: int = Form(...),
|
||||||
scheduled_date: Optional[datetime] = Form(None),
|
scheduled_date: Optional[datetime] = Form(None),
|
||||||
admin_id: UUID = Form(...),
|
admin_id: Optional[UUID] = Form(None),
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
current_user: models.User = Depends(get_current_user),
|
current_user: models.User = Depends(get_current_user),
|
||||||
):
|
):
|
||||||
@@ -1344,6 +1355,24 @@ async def upload_admin_test(
|
|||||||
import storage
|
import storage
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
# Safe pattern normalization
|
||||||
|
pat_str = str(exam_pattern).strip()
|
||||||
|
if pat_str in ["JEE", "JEE_MAIN", "JEE Main", "JEE_Main"]:
|
||||||
|
pat_enum = models.ExamEnum.JEE_MAIN
|
||||||
|
elif pat_str in ["JEE_ADVANCED", "JEE Advanced", "JEE_Advanced"]:
|
||||||
|
pat_enum = models.ExamEnum.JEE_ADVANCED
|
||||||
|
elif pat_str in ["NEET"]:
|
||||||
|
pat_enum = models.ExamEnum.NEET
|
||||||
|
elif pat_str in ["JEE_NEET", "JEE & NEET"]:
|
||||||
|
pat_enum = models.ExamEnum.JEE_NEET
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
pat_enum = models.ExamEnum(pat_str)
|
||||||
|
except ValueError:
|
||||||
|
pat_enum = models.ExamEnum.JEE_MAIN
|
||||||
|
|
||||||
|
effective_admin_id = admin_id or uuid.UUID("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
|
||||||
|
|
||||||
pdf_bytes = await file.read()
|
pdf_bytes = await file.read()
|
||||||
current_year = datetime.utcnow().year
|
current_year = datetime.utcnow().year
|
||||||
object_name = f"tests/{current_year}/{uuid.uuid4()}_{file.filename}"
|
object_name = f"tests/{current_year}/{uuid.uuid4()}_{file.filename}"
|
||||||
@@ -1354,28 +1383,19 @@ async def upload_admin_test(
|
|||||||
test_obj = models.Test(
|
test_obj = models.Test(
|
||||||
title=title,
|
title=title,
|
||||||
type="DPS",
|
type="DPS",
|
||||||
exam_pattern=models.ExamEnum(exam_pattern),
|
exam_pattern=pat_enum,
|
||||||
sections={},
|
sections={},
|
||||||
total_time=time_limit,
|
total_time=time_limit,
|
||||||
difficulty="Medium",
|
difficulty="Medium",
|
||||||
scheduled_date=scheduled_date,
|
scheduled_date=scheduled_date,
|
||||||
is_active=False,
|
is_active=True,
|
||||||
created_by=admin_id,
|
created_by=effective_admin_id,
|
||||||
pdf_url=url
|
pdf_url=url
|
||||||
)
|
)
|
||||||
session.add(test_obj)
|
session.add(test_obj)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(test_obj)
|
session.refresh(test_obj)
|
||||||
|
|
||||||
# Phase 2: Uncomment below to trigger AI question extraction from PDF
|
|
||||||
# from worker import process_pdf_test
|
|
||||||
# process_pdf_test.delay(
|
|
||||||
# test_id_str=str(test_obj.id),
|
|
||||||
# object_name=object_name,
|
|
||||||
# admin_id_str=str(admin_id),
|
|
||||||
# exam_pattern=exam_pattern
|
|
||||||
# )
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": "uploaded",
|
"status": "uploaded",
|
||||||
"test_id": test_obj.id,
|
"test_id": test_obj.id,
|
||||||
@@ -1388,18 +1408,37 @@ def create_manual_test(
|
|||||||
session: Session = Depends(get_session), current_user: models.User = Depends(get_current_user)
|
session: Session = Depends(get_session), current_user: models.User = Depends(get_current_user)
|
||||||
):
|
):
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
# Safe pattern normalization
|
||||||
|
pat_str = str(data.exam_pattern).strip()
|
||||||
|
if pat_str in ["JEE", "JEE_MAIN", "JEE Main", "JEE_Main"]:
|
||||||
|
pat_enum = models.ExamEnum.JEE_MAIN
|
||||||
|
elif pat_str in ["JEE_ADVANCED", "JEE Advanced", "JEE_Advanced"]:
|
||||||
|
pat_enum = models.ExamEnum.JEE_ADVANCED
|
||||||
|
elif pat_str in ["NEET"]:
|
||||||
|
pat_enum = models.ExamEnum.NEET
|
||||||
|
elif pat_str in ["JEE_NEET", "JEE & NEET"]:
|
||||||
|
pat_enum = models.ExamEnum.JEE_NEET
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
pat_enum = models.ExamEnum(pat_str)
|
||||||
|
except ValueError:
|
||||||
|
pat_enum = models.ExamEnum.JEE_MAIN
|
||||||
|
|
||||||
|
effective_admin_id = data.admin_id or uuid.UUID("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
|
||||||
|
|
||||||
# 1. Create the test object
|
# 1. Create the test object
|
||||||
test_obj = models.Test(
|
test_obj = models.Test(
|
||||||
title=data.title,
|
title=data.title,
|
||||||
type=data.type,
|
type=data.type,
|
||||||
exam_pattern=models.ExamEnum(data.exam_pattern),
|
exam_pattern=pat_enum,
|
||||||
sections={},
|
sections={},
|
||||||
total_time=data.time_limit,
|
total_time=data.time_limit,
|
||||||
difficulty="Medium",
|
difficulty="Medium",
|
||||||
scheduled_date=data.scheduled_date,
|
scheduled_date=data.scheduled_date,
|
||||||
end_date=data.end_date,
|
end_date=data.end_date,
|
||||||
is_active=True,
|
is_active=True,
|
||||||
created_by=data.admin_id
|
created_by=effective_admin_id
|
||||||
)
|
)
|
||||||
session.add(test_obj)
|
session.add(test_obj)
|
||||||
|
|
||||||
@@ -1407,22 +1446,67 @@ def create_manual_test(
|
|||||||
sections_dict = {}
|
sections_dict = {}
|
||||||
|
|
||||||
for q_data in data.questions_json:
|
for q_data in data.questions_json:
|
||||||
|
# Extract text with fallbacks
|
||||||
|
q_text = q_data.question_text or q_data.question or "Question"
|
||||||
|
sol_text = q_data.solution_text or q_data.solution or q_data.explanation or "Detailed solution will be available after review."
|
||||||
|
subj = q_data.subject or "General"
|
||||||
|
top = q_data.topic or subj
|
||||||
|
subtop = q_data.subtopic or top
|
||||||
|
diff = q_data.difficulty or "Medium"
|
||||||
|
|
||||||
|
# Normalize question type safely
|
||||||
|
t_raw = str(q_data.type or "MCQ_Single").strip()
|
||||||
|
if t_raw in ["Single_Choice", "MCQ", "Single", "MCQ_Single"]:
|
||||||
|
q_type = models.QuestionTypeEnum.MCQ_SINGLE
|
||||||
|
elif t_raw in ["Multiple_Choice", "Multiple", "MCQ_Multiple"]:
|
||||||
|
q_type = models.QuestionTypeEnum.MCQ_MULTIPLE
|
||||||
|
elif t_raw in ["Numerical", "Subjective", "Integer"]:
|
||||||
|
q_type = models.QuestionTypeEnum.NUMERICAL
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
q_type = models.QuestionTypeEnum(t_raw)
|
||||||
|
except ValueError:
|
||||||
|
q_type = models.QuestionTypeEnum.MCQ_SINGLE
|
||||||
|
|
||||||
|
# Normalize language
|
||||||
|
l_raw = str(q_data.language or "English").strip().capitalize()
|
||||||
|
if l_raw not in ["English", "Hindi", "Bilingual"]:
|
||||||
|
l_raw = "English"
|
||||||
|
lang_enum = models.MediumEnum(l_raw)
|
||||||
|
|
||||||
|
# Normalize options
|
||||||
|
opts = q_data.options
|
||||||
|
if isinstance(opts, list):
|
||||||
|
opts = {chr(65 + i): str(v) for i, v in enumerate(opts)}
|
||||||
|
|
||||||
|
opts_hi = q_data.options_hi
|
||||||
|
if isinstance(opts_hi, list):
|
||||||
|
opts_hi = {chr(65 + i): str(v) for i, v in enumerate(opts_hi)}
|
||||||
|
|
||||||
|
# Normalize correct_answer
|
||||||
|
ca = q_data.correct_answer
|
||||||
|
if not isinstance(ca, dict):
|
||||||
|
ca = {"value": ca if ca is not None else "A"}
|
||||||
|
elif "value" not in ca:
|
||||||
|
ca = {"value": ca.get("correct") or ca.get("answer") or "A"}
|
||||||
|
|
||||||
q_obj = models.Question(
|
q_obj = models.Question(
|
||||||
subject=q_data.subject,
|
subject=subj,
|
||||||
topic=q_data.topic,
|
topic=top,
|
||||||
subtopic=q_data.subtopic,
|
subtopic=subtop,
|
||||||
difficulty=q_data.difficulty,
|
difficulty=diff,
|
||||||
type=models.QuestionTypeEnum(q_data.type),
|
type=q_type,
|
||||||
question_text=q_data.question_text,
|
question_text=q_text,
|
||||||
options=q_data.options,
|
options=opts,
|
||||||
correct_answer=q_data.correct_answer,
|
options_hi=opts_hi,
|
||||||
solution_text=q_data.solution_text,
|
correct_answer=ca,
|
||||||
language=models.MediumEnum(q_data.language),
|
solution_text=sol_text,
|
||||||
created_by=data.admin_id
|
language=lang_enum,
|
||||||
|
created_by=effective_admin_id
|
||||||
)
|
)
|
||||||
session.add(q_obj)
|
session.add(q_obj)
|
||||||
|
|
||||||
sec_name = q_data.section
|
sec_name = q_data.section or subj or "General"
|
||||||
if sec_name not in sections_dict:
|
if sec_name not in sections_dict:
|
||||||
sections_dict[sec_name] = {
|
sections_dict[sec_name] = {
|
||||||
"name": sec_name,
|
"name": sec_name,
|
||||||
|
|||||||
@@ -14,23 +14,88 @@ import { z } from "zod"
|
|||||||
|
|
||||||
import { useActiveUser } from "@/hooks/useActiveUser"
|
import { useActiveUser } from "@/hooks/useActiveUser"
|
||||||
|
|
||||||
// Strict Zod schema for client-side validation
|
// Permissive, auto-normalizing Zod schema for client-side question parsing
|
||||||
const QuestionSchema = z.object({
|
const QuestionSchema = z.preprocess((val: any) => {
|
||||||
|
if (typeof val !== "object" || val === null) return val;
|
||||||
|
const q = { ...val };
|
||||||
|
|
||||||
|
// Normalize question text
|
||||||
|
if (!q.question_text && q.question) {
|
||||||
|
q.question_text = q.question;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize solution text
|
||||||
|
if (!q.solution_text && (q.solution || q.explanation)) {
|
||||||
|
q.solution_text = q.solution || q.explanation;
|
||||||
|
}
|
||||||
|
if (!q.solution_text) {
|
||||||
|
q.solution_text = "Detailed solution will be available after review.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize topic / subtopic / subject / difficulty
|
||||||
|
if (!q.subject) q.subject = "General";
|
||||||
|
if (!q.topic) q.topic = q.subject || "General";
|
||||||
|
if (!q.subtopic) q.subtopic = q.topic || "General";
|
||||||
|
if (!q.difficulty) q.difficulty = "Medium";
|
||||||
|
if (!q.section) q.section = q.subject || "General";
|
||||||
|
|
||||||
|
// Normalize question type
|
||||||
|
const typeStr = String(q.type || "").trim();
|
||||||
|
if (["Single_Choice", "MCQ", "Single", "MCQ_Single"].includes(typeStr)) {
|
||||||
|
q.type = "MCQ_Single";
|
||||||
|
} else if (["Multiple_Choice", "Multiple", "MCQ_Multiple"].includes(typeStr)) {
|
||||||
|
q.type = "MCQ_Multiple";
|
||||||
|
} else if (["Numerical", "Subjective", "Integer"].includes(typeStr)) {
|
||||||
|
q.type = "Numerical";
|
||||||
|
} else {
|
||||||
|
q.type = "MCQ_Single";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize language
|
||||||
|
const langStr = String(q.language || "").trim().toLowerCase();
|
||||||
|
if (langStr === "hindi") q.language = "Hindi";
|
||||||
|
else if (langStr === "bilingual") q.language = "Bilingual";
|
||||||
|
else q.language = "English";
|
||||||
|
|
||||||
|
// Normalize options if passed as an array
|
||||||
|
if (Array.isArray(q.options)) {
|
||||||
|
const optsObj: Record<string, string> = {};
|
||||||
|
q.options.forEach((opt: any, idx: number) => {
|
||||||
|
const letter = String.fromCharCode(65 + idx);
|
||||||
|
optsObj[letter] = String(opt);
|
||||||
|
});
|
||||||
|
q.options = optsObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize correct_answer format
|
||||||
|
if (typeof q.correct_answer === "string" || typeof q.correct_answer === "number") {
|
||||||
|
q.correct_answer = { value: String(q.correct_answer) };
|
||||||
|
} else if (Array.isArray(q.correct_answer)) {
|
||||||
|
q.correct_answer = { value: q.correct_answer.map(String) };
|
||||||
|
} else if (typeof q.correct_answer === "object" && q.correct_answer !== null) {
|
||||||
|
const val = q.correct_answer.value ?? q.correct_answer.correct ?? q.correct_answer.answer ?? "A";
|
||||||
|
q.correct_answer = { value: val };
|
||||||
|
} else {
|
||||||
|
q.correct_answer = { value: "A" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return q;
|
||||||
|
}, z.object({
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
section: z.string().default("General"),
|
section: z.string().default("General"),
|
||||||
subject: z.string(),
|
subject: z.string().default("General"),
|
||||||
topic: z.string(),
|
topic: z.string().default("General"),
|
||||||
subtopic: z.string(),
|
subtopic: z.string().default("General"),
|
||||||
difficulty: z.string(),
|
difficulty: z.string().default("Medium"),
|
||||||
type: z.enum(["MCQ_Single", "MCQ_Multiple", "Numerical"]),
|
type: z.enum(["MCQ_Single", "MCQ_Multiple", "Numerical"]),
|
||||||
question_text: z.string(),
|
question_text: z.string().min(1, "Question text is required"),
|
||||||
options: z.record(z.string(), z.any()).nullable().optional(),
|
options: z.record(z.string(), z.any()).nullable().optional(),
|
||||||
correct_answer: z.object({
|
correct_answer: z.object({
|
||||||
value: z.any()
|
value: z.any()
|
||||||
}),
|
}),
|
||||||
solution_text: z.string(),
|
solution_text: z.string(),
|
||||||
language: z.enum(["English", "Hindi", "Bilingual"])
|
language: z.enum(["English", "Hindi", "Bilingual"])
|
||||||
}).passthrough();
|
}).passthrough());
|
||||||
|
|
||||||
const TestSchema = z.array(QuestionSchema);
|
const TestSchema = z.array(QuestionSchema);
|
||||||
|
|
||||||
@@ -173,13 +238,14 @@ export default function AdminTestsPage() {
|
|||||||
const handleUpload = async (e: React.FormEvent) => {
|
const handleUpload = async (e: React.FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
|
const effectiveAdminId = userId && userId !== "admin" ? userId : "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
|
||||||
|
|
||||||
if (testType === 'DPP_PDF') {
|
if (testType === 'DPP_PDF') {
|
||||||
const file = fileInputRef.current?.files?.[0]
|
const file = fileInputRef.current?.files?.[0]
|
||||||
if (!file) {
|
if (!file) {
|
||||||
alert("Please select a PDF file to upload to Minio.")
|
alert("Please select a PDF file to upload to Minio.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!userId) return
|
|
||||||
setUploading(true)
|
setUploading(true)
|
||||||
try {
|
try {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
@@ -187,7 +253,7 @@ export default function AdminTestsPage() {
|
|||||||
formData.append("title", title)
|
formData.append("title", title)
|
||||||
formData.append("exam_pattern", examPattern)
|
formData.append("exam_pattern", examPattern)
|
||||||
formData.append("time_limit", timeLimit)
|
formData.append("time_limit", timeLimit)
|
||||||
formData.append("admin_id", userId === "admin" ? "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" : (userId as string))
|
formData.append("admin_id", effectiveAdminId)
|
||||||
if (scheduledDate) formData.append("scheduled_date", scheduledDate.toISOString())
|
if (scheduledDate) formData.append("scheduled_date", scheduledDate.toISOString())
|
||||||
|
|
||||||
const res = await fetchWithAuth("/api/admin/tests/upload", {
|
const res = await fetchWithAuth("/api/admin/tests/upload", {
|
||||||
@@ -215,7 +281,6 @@ export default function AdminTestsPage() {
|
|||||||
alert("Please upload a valid JSON file with questions first.")
|
alert("Please upload a valid JSON file with questions first.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!userId) return
|
|
||||||
|
|
||||||
setUploading(true)
|
setUploading(true)
|
||||||
|
|
||||||
@@ -224,7 +289,7 @@ export default function AdminTestsPage() {
|
|||||||
type: testType,
|
type: testType,
|
||||||
exam_pattern: examPattern,
|
exam_pattern: examPattern,
|
||||||
time_limit: parseInt(timeLimit),
|
time_limit: parseInt(timeLimit),
|
||||||
admin_id: userId === "admin" ? "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" : userId,
|
admin_id: effectiveAdminId,
|
||||||
questions_json: validQuestions,
|
questions_json: validQuestions,
|
||||||
scheduled_date: scheduledDate ? scheduledDate.toISOString() : null,
|
scheduled_date: scheduledDate ? scheduledDate.toISOString() : null,
|
||||||
end_date: endDate ? endDate.toISOString() : null,
|
end_date: endDate ? endDate.toISOString() : null,
|
||||||
@@ -449,7 +514,7 @@ export default function AdminTestsPage() {
|
|||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h3 className="font-semibold">{test.title}</h3>
|
<h3 className="font-semibold">{test.title}</h3>
|
||||||
<Badge variant="outline">{test.exam_pattern.replace("_", " ")}</Badge>
|
<Badge variant="outline">{test.exam_pattern?.replace("_", " ") || "All"}</Badge>
|
||||||
<Badge variant={test.is_active ? "default" : "secondary"}>
|
<Badge variant={test.is_active ? "default" : "secondary"}>
|
||||||
{test.is_active ? "Active" : "Inactive / Processing"}
|
{test.is_active ? "Active" : "Inactive / Processing"}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|||||||
@@ -119,11 +119,18 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
|||||||
setExamType(testData.type);
|
setExamType(testData.type);
|
||||||
if (testData.title) setTestName(testData.title);
|
if (testData.title) setTestName(testData.title);
|
||||||
|
|
||||||
// Extract all question IDs from sections
|
// Extract all question IDs from sections safely
|
||||||
const questionIds: string[] = [];
|
const questionIds: string[] = [];
|
||||||
Object.values(testData.sections).forEach((section: any) => {
|
if (testData.sections) {
|
||||||
questionIds.push(...section.questions);
|
const sectionsList = Array.isArray(testData.sections)
|
||||||
});
|
? testData.sections
|
||||||
|
: Object.values(testData.sections);
|
||||||
|
sectionsList.forEach((section: any) => {
|
||||||
|
if (section && Array.isArray(section.questions)) {
|
||||||
|
questionIds.push(...section.questions);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const qRes = await fetchWithAuth(`/api/tests/${examId}/questions`);
|
const qRes = await fetchWithAuth(`/api/tests/${examId}/questions`);
|
||||||
if (!qRes.ok) throw new Error("Failed to fetch questions in bulk");
|
if (!qRes.ok) throw new Error("Failed to fetch questions in bulk");
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export default function CBTPage() {
|
|||||||
if (selectedPattern === "JEE") {
|
if (selectedPattern === "JEE") {
|
||||||
testsData = testsData.filter((t: any) => t.exam_pattern?.includes("JEE"));
|
testsData = testsData.filter((t: any) => t.exam_pattern?.includes("JEE"));
|
||||||
} else if (selectedPattern === "NEET") {
|
} else if (selectedPattern === "NEET") {
|
||||||
testsData = testsData.filter((t: any) => t.exam_pattern === "NEET");
|
testsData = testsData.filter((t: any) => t.exam_pattern === "NEET" || t.exam_pattern === "JEE_NEET");
|
||||||
}
|
}
|
||||||
setExams(testsData);
|
setExams(testsData);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user