aki-008 commited on
Commit
a62d2e4
·
1 Parent(s): 1444e6f

chore: fixed generated mcq rendering

Browse files
Files changed (1) hide show
  1. Frontend/src/components/quize/mcq.tsx +124 -220
Frontend/src/components/quize/mcq.tsx CHANGED
@@ -1,15 +1,22 @@
1
  import React, { useCallback, useEffect, useMemo, useState } from "react";
2
- import {
3
- ChevronRight,
4
- CheckCircle,
5
- XCircle,
6
- ArrowLeft,
7
- } from "lucide-react";
 
 
 
 
8
 
9
  interface MCQQuizPageProps {
10
  onBack: () => void;
11
- // optional: total time in seconds (defaults to 15 minutes)
12
  totalTimeSeconds?: number;
 
 
 
 
13
  }
14
 
15
  type QStatus = "notVisited" | "visited" | "answered" | "markedForReview";
@@ -18,43 +25,19 @@ interface QuestionItem {
18
  id: number;
19
  question: string;
20
  options: string[];
21
- answer: string;
 
22
  // runtime fields
23
  selected?: string | null;
24
  status?: QStatus;
25
  }
26
 
27
- const initialMockData: QuestionItem[] = [
28
- {
29
- id: 1,
30
- question: "Which hook is used to perform side effects in React?",
31
- options: ["useState", "useContext", "useEffect", "useReducer"],
32
- answer: "useEffect",
33
- },
34
- {
35
- id: 2,
36
- question: "What does the 'FileSpreadsheet' icon represent?",
37
- options: [
38
- "A config file",
39
- "A PDF or document upload",
40
- "A database query",
41
- "A CSS file",
42
- ],
43
- answer: "A PDF or document upload",
44
- },
45
- {
46
- id: 3,
47
- question: "Which Tailwind class keeps an element sticky during scroll?",
48
- options: ["sticky", "absolute", "fixed", "relative"],
49
- answer: "sticky",
50
- },
51
- {
52
- id: 4,
53
- question: "Which prop was NOT passed earlier to OutputTypeOption?",
54
- options: ["icon", "title", "desc", "onClick"],
55
- answer: "onClick",
56
- },
57
- ];
58
 
59
  const formatTime = (seconds: number) => {
60
  const mm = Math.floor(seconds / 60)
@@ -66,31 +49,47 @@ const formatTime = (seconds: number) => {
66
 
67
  const MCQQuizPage: React.FC<MCQQuizPageProps> = ({
68
  onBack,
69
- totalTimeSeconds = 15 * 60, // default 15 minutes
 
70
  }) => {
71
- const [questions, setQuestions] = useState<QuestionItem[]>(
72
- () =>
73
- initialMockData.map((q, idx) => ({
74
- ...q,
 
 
 
 
 
 
75
  selected: null,
76
  status: idx === 0 ? "visited" : "notVisited",
77
- }))
78
- );
 
 
 
79
 
80
  const [currentIndex, setCurrentIndex] = useState(0);
81
- const [isAnswered, setIsAnswered] = useState(false); // tracks current question answered state
82
  const [showScore, setShowScore] = useState(false);
83
  const [timeLeft, setTimeLeft] = useState(totalTimeSeconds);
84
 
85
  const totalQuestions = questions.length;
86
- const currentQuestion = questions[currentIndex];
 
 
 
 
 
 
 
87
 
88
- // Initialize isAnswered for current question when index changes
89
  useEffect(() => {
90
  setIsAnswered(Boolean(currentQuestion?.selected));
91
  }, [currentQuestion]);
92
 
93
- // Timer with auto-submit
94
  useEffect(() => {
95
  if (showScore) return;
96
  if (timeLeft <= 0) {
@@ -99,34 +98,10 @@ const MCQQuizPage: React.FC<MCQQuizPageProps> = ({
99
  }
100
  const t = setInterval(() => setTimeLeft((s) => s - 1), 1000);
101
  return () => clearInterval(t);
102
- // eslint-disable-next-line react-hooks/exhaustive-deps
103
  }, [timeLeft, showScore]);
104
 
105
- const saveCurrentSelection = useCallback(
106
- (selected: string | null) => {
107
- setQuestions((prev) => {
108
- const copy = prev.map((q, idx) => {
109
- if (idx !== currentIndex) return q;
110
- return {
111
- ...q,
112
- selected,
113
- status:
114
- selected !== null
115
- ? "answered"
116
- : q.status === "markedForReview"
117
- ? "markedForReview"
118
- : "visited",
119
- };
120
- });
121
- return copy;
122
- });
123
- },
124
- [currentIndex]
125
- );
126
-
127
  const handleAnswerClick = useCallback(
128
  (option: string) => {
129
- // mark selected immediately
130
  setQuestions((prev) =>
131
  prev.map((q, idx) =>
132
  idx === currentIndex
@@ -139,28 +114,22 @@ const MCQQuizPage: React.FC<MCQQuizPageProps> = ({
139
  [currentIndex]
140
  );
141
 
142
- const goToQuestion = useCallback(
143
- (index: number) => {
144
- setQuestions((prev) =>
145
- prev.map((q, idx) => {
146
- if (idx === index) {
147
- // mark visited if not visited
148
- return {
149
- ...q,
150
- status: q.status === "notVisited" ? "visited" : q.status,
151
- };
152
- }
153
- // also ensure current question remains with existing status
154
- return q;
155
- })
156
- );
157
- setCurrentIndex(index);
158
- },
159
- []
160
- );
161
 
162
  const handleSaveAndNext = useCallback(() => {
163
- // ensure status updated even if answered or not
164
  setQuestions((prev) =>
165
  prev.map((q, idx) => {
166
  if (idx === currentIndex) {
@@ -182,7 +151,6 @@ const MCQQuizPage: React.FC<MCQQuizPageProps> = ({
182
  if (next < totalQuestions) {
183
  goToQuestion(next);
184
  } else {
185
- // at end -> submit
186
  handleSubmit();
187
  }
188
  }, [currentIndex, goToQuestion, totalQuestions]);
@@ -190,15 +158,9 @@ const MCQQuizPage: React.FC<MCQQuizPageProps> = ({
190
  const handleMarkForReview = useCallback(() => {
191
  setQuestions((prev) =>
192
  prev.map((q, idx) =>
193
- idx === currentIndex
194
- ? {
195
- ...q,
196
- status: "markedForReview",
197
- }
198
- : q
199
  )
200
  );
201
- // move to next question
202
  const next = currentIndex + 1;
203
  if (next < totalQuestions) goToQuestion(next);
204
  }, [currentIndex, goToQuestion, totalQuestions]);
@@ -215,7 +177,7 @@ const MCQQuizPage: React.FC<MCQQuizPageProps> = ({
215
  const computeScore = useCallback(() => {
216
  let s = 0;
217
  questions.forEach((q) => {
218
- if (q.selected && q.selected === q.answer) s += 1;
219
  });
220
  return s;
221
  }, [questions]);
@@ -225,13 +187,10 @@ const MCQQuizPage: React.FC<MCQQuizPageProps> = ({
225
  }, []);
226
 
227
  const handleAutoSubmit = useCallback(() => {
228
- // ensure final statuses saved and then show score
229
  setShowScore(true);
230
  }, []);
231
 
232
- // palette helpers
233
  const statusClassForPalette = (q: QuestionItem, idx: number) => {
234
- // Blue = current; Green = answered; Yellow = markedForReview; Red = visited but not answered; Gray = notVisited
235
  if (idx === currentIndex)
236
  return "bg-blue-500 text-white ring-2 ring-blue-300";
237
  switch (q.status) {
@@ -241,15 +200,14 @@ const MCQQuizPage: React.FC<MCQQuizPageProps> = ({
241
  return "bg-yellow-400 text-black";
242
  case "visited":
243
  return "bg-red-500 text-white";
244
- case "notVisited":
245
  default:
246
  return "bg-slate-300 text-slate-700";
247
  }
248
  };
249
 
250
- const score = useMemo(() => computeScore(), [computeScore]);
251
 
252
- // Render Score screen
253
  if (showScore) {
254
  return (
255
  <div className="min-h-screen bg-gray-100 flex items-center justify-center p-6">
@@ -265,31 +223,11 @@ const MCQQuizPage: React.FC<MCQQuizPageProps> = ({
265
  </div>
266
 
267
  <div className="flex justify-center gap-3">
268
- <button
269
- onClick={() => {
270
- // simple retry: reset everything
271
- setQuestions(
272
- initialMockData.map((q, idx) => ({
273
- ...q,
274
- selected: null,
275
- status: idx === 0 ? "visited" : "notVisited",
276
- }))
277
- );
278
- setCurrentIndex(0);
279
- setShowScore(false);
280
- setTimeLeft(totalTimeSeconds);
281
- setIsAnswered(false);
282
- }}
283
- className="px-6 py-3 rounded-lg bg-linear-to-r from-blue-500 to-blue-700 text-white font-medium hover:opacity-90"
284
- >
285
- Retry
286
- </button>
287
-
288
  <button
289
  onClick={onBack}
290
  className="px-6 py-3 rounded-lg border border-slate-300 text-slate-700 bg-white hover:bg-slate-50"
291
  >
292
- ← Back
293
  </button>
294
  </div>
295
  </div>
@@ -297,11 +235,23 @@ const MCQQuizPage: React.FC<MCQQuizPageProps> = ({
297
  );
298
  }
299
 
300
- // Main quiz UI
 
 
 
 
 
 
 
 
 
 
 
 
301
  return (
302
  <div className="min-h-screen bg-gray-50 p-6 lg:p-12">
303
  <div className="max-w-7xl mx-auto grid grid-cols-12 gap-6">
304
- {/* Left: Main Question area (8 cols) */}
305
  <div className="col-span-12 lg:col-span-8">
306
  <div className="mb-4 flex items-center justify-between">
307
  <div>
@@ -315,23 +265,17 @@ const MCQQuizPage: React.FC<MCQQuizPageProps> = ({
315
  <h1 className="text-2xl lg:text-3xl font-bold mt-3">
316
  Generated MCQ Quiz
317
  </h1>
318
- <p className="text-sm text-gray-500 mt-1">
319
- Mixed section — complete the test before time ends.
320
- </p>
321
  </div>
322
 
323
  <div className="hidden md:flex flex-col items-end text-sm text-gray-600">
324
- <div>Question</div>
325
  <div className="text-xl font-bold text-blue-600">
326
  {currentIndex + 1} / {totalQuestions}
327
  </div>
328
- <div className="mt-1">Score: <span className="font-semibold text-green-600">{score}</span></div>
329
  </div>
330
  </div>
331
 
332
  <div className="bg-white rounded-xl shadow p-6 mb-6 border border-slate-200">
333
  <div className="mb-6">
334
- <div className="text-sm text-gray-500 mb-2">Quant - Question</div>
335
  <h2 className="text-xl lg:text-2xl font-semibold text-slate-800">
336
  {currentQuestion.question}
337
  </h2>
@@ -340,41 +284,53 @@ const MCQQuizPage: React.FC<MCQQuizPageProps> = ({
340
  <div className="grid gap-4">
341
  {currentQuestion.options.map((opt, i) => {
342
  const isSelected = currentQuestion.selected === opt;
343
- const optionClass = isAnswered
344
- ? opt === currentQuestion.answer
345
- ? "bg-green-600/80 text-white border-green-500 shadow"
346
- : isSelected
347
- ? "bg-red-600/80 text-white border-red-500 shadow"
348
- : "bg-slate-50 border-slate-200 text-slate-700 opacity-70"
349
- : isSelected
350
- ? "bg-blue-600/80 text-white"
351
- : "bg-white hover:bg-slate-50 border border-slate-200 text-slate-800";
 
 
 
 
 
 
 
 
 
352
 
353
  return (
354
  <button
355
  key={i}
356
  onClick={() => handleAnswerClick(opt)}
357
- disabled={isAnswered && opt !== currentQuestion.answer && isSelected === false && false}
358
  className={`p-4 rounded-lg text-left border transition flex justify-between items-center ${optionClass}`}
359
  >
360
  <span>{opt}</span>
361
-
362
  {isAnswered && opt === currentQuestion.answer && (
363
  <CheckCircle className="w-5 h-5 text-green-100" />
364
  )}
365
  {isAnswered &&
366
- currentQuestion.selected === opt &&
367
  opt !== currentQuestion.answer && (
368
  <XCircle className="w-5 h-5 text-red-200" />
369
  )}
370
- {!isAnswered && isSelected && (
371
- <span className="text-sm text-blue-50">Selected</span>
372
- )}
373
  </button>
374
  );
375
  })}
376
  </div>
377
 
 
 
 
 
 
 
 
378
  {/* Bottom actions */}
379
  <div className="mt-6 flex flex-col md:flex-row gap-3 md:gap-4 items-center justify-between">
380
  <div className="flex gap-2 flex-wrap">
@@ -384,52 +340,40 @@ const MCQQuizPage: React.FC<MCQQuizPageProps> = ({
384
  >
385
  Mark for Review
386
  </button>
387
-
388
  <button
389
  onClick={handleClearResponse}
390
- className="px-4 py-2 rounded-md border border-slate-300 text-slate-700 bg-white"
 
391
  >
392
  Clear Response
393
  </button>
394
-
395
  <button
396
  onClick={handleSaveAndNext}
397
- className="px-4 py-2 rounded-md bg-linear-to-r from-blue-500 to-blue-700 text-white font-medium flex items-center gap-2"
398
  >
399
  Save & Next <ChevronRight className="w-4 h-4" />
400
  </button>
401
  </div>
402
-
403
- <div className="text-sm text-gray-600">
404
- {currentIndex === totalQuestions - 1 ? (
405
- <span className="font-medium">Finish test to submit</span>
406
- ) : (
407
- <span>{currentIndex + 1} of {totalQuestions}</span>
408
- )}
409
- </div>
410
  </div>
411
  </div>
412
  </div>
413
 
414
- {/* Right: Sidebar (4 cols) */}
415
  <aside className="col-span-12 lg:col-span-4">
416
  <div className="sticky top-6 space-y-4">
417
- {/* Timer / top card */}
418
  <div className="bg-white rounded-xl p-4 shadow border border-slate-200 text-center">
419
  <div className="text-sm text-gray-500">Time Left</div>
420
  <div className="mt-2 text-3xl font-bold text-red-600">
421
  {formatTime(Math.max(0, timeLeft))}
422
  </div>
423
- <div className="mt-3 text-xs text-gray-500">Auto-submit when timer ends</div>
424
  </div>
425
 
426
- {/* Palette Grid */}
427
  <div className="bg-white rounded-xl p-4 shadow border border-slate-200">
428
  <div className="flex items-center justify-between mb-3">
429
  <h3 className="text-sm font-semibold">Question Palette</h3>
430
- <div className="text-xs text-gray-500">Tap to jump</div>
431
  </div>
432
-
433
  <div className="grid grid-cols-5 gap-2">
434
  {questions.map((q, idx) => (
435
  <button
@@ -439,61 +383,21 @@ const MCQQuizPage: React.FC<MCQQuizPageProps> = ({
439
  q,
440
  idx
441
  )}`}
442
- title={`Q ${idx + 1} — ${q.status}`}
443
  >
444
  {idx + 1}
445
  </button>
446
  ))}
447
  </div>
448
-
449
- {/* Legend */}
450
- <div className="mt-4 text-xs text-slate-600 grid grid-cols-2 gap-2">
451
- <div className="flex items-center gap-2">
452
- <span className="w-4 h-4 bg-blue-500 rounded" /> Current
453
- </div>
454
- <div className="flex items-center gap-2">
455
- <span className="w-4 h-4 bg-green-500 rounded" /> Answered
456
- </div>
457
- <div className="flex items-center gap-2">
458
- <span className="w-4 h-4 bg-yellow-400 rounded" /> Review
459
- </div>
460
- <div className="flex items-center gap-2">
461
- <span className="w-4 h-4 bg-red-500 rounded" /> Visited
462
- </div>
463
- <div className="flex items-center gap-2">
464
- <span className="w-4 h-4 bg-slate-300 rounded" /> Not
465
- Visited
466
- </div>
467
- </div>
468
  </div>
469
 
470
- {/* Submit button */}
471
- <div className="bg-white rounded-xl p-4 shadow border border-slate-200 flex gap-3">
472
- <button
473
- onClick={() => {
474
- // quick confirm before submitting
475
- // In real app, you'd show a modal
476
- if (window.confirm("Submit test now?")) {
477
- handleSubmit();
478
- }
479
- }}
480
- className="flex-1 px-4 py-2 rounded-md bg-red-600 text-white font-semibold"
481
- >
482
- Submit Test
483
- </button>
484
-
485
- <button
486
- onClick={() => {
487
- // jump to first unanswered
488
- const idx = questions.findIndex((q) => q.status === "notVisited" || q.status === "visited");
489
- if (idx >= 0) goToQuestion(idx);
490
- else alert("No unanswered questions left.");
491
- }}
492
- className="px-3 py-2 rounded-md border border-slate-200 text-slate-700"
493
- >
494
- Next Unanswered
495
- </button>
496
- </div>
497
  </div>
498
  </aside>
499
  </div>
@@ -501,4 +405,4 @@ const MCQQuizPage: React.FC<MCQQuizPageProps> = ({
501
  );
502
  };
503
 
504
- export default MCQQuizPage;
 
1
  import React, { useCallback, useEffect, useMemo, useState } from "react";
2
+ import { ChevronRight, CheckCircle, XCircle, ArrowLeft } from "lucide-react";
3
+
4
+ // 1. Define the structure of the incoming API data
5
+ interface BackendQuestion {
6
+ question: string;
7
+ options: string[];
8
+ answer: string; // Backend sends "a", "b", "c", "d"
9
+ explanation: string;
10
+ User_response: string;
11
+ }
12
 
13
  interface MCQQuizPageProps {
14
  onBack: () => void;
 
15
  totalTimeSeconds?: number;
16
+ // 2. Add the data prop
17
+ data?: {
18
+ quiz: BackendQuestion[];
19
+ } | null;
20
  }
21
 
22
  type QStatus = "notVisited" | "visited" | "answered" | "markedForReview";
 
25
  id: number;
26
  question: string;
27
  options: string[];
28
+ answer: string; // We will convert this to the actual option text
29
+ explanation?: string; // Add explanation field
30
  // runtime fields
31
  selected?: string | null;
32
  status?: QStatus;
33
  }
34
 
35
+ // Helper to convert 'a' -> options[0]
36
+ const mapBackendAnswerToText = (key: string, options: string[]) => {
37
+ const map: Record<string, number> = { a: 0, b: 1, c: 2, d: 3 };
38
+ const idx = map[key.toLowerCase()] ?? -1;
39
+ return idx >= 0 && idx < options.length ? options[idx] : "";
40
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
  const formatTime = (seconds: number) => {
43
  const mm = Math.floor(seconds / 60)
 
49
 
50
  const MCQQuizPage: React.FC<MCQQuizPageProps> = ({
51
  onBack,
52
+ totalTimeSeconds = 15 * 60,
53
+ data, // Receive data
54
  }) => {
55
+ // 3. Initialize state with Real Data if available, else Mock
56
+ const [questions, setQuestions] = useState<QuestionItem[]>(() => {
57
+ if (data && data.quiz && data.quiz.length > 0) {
58
+ return data.quiz.map((q, idx) => ({
59
+ id: idx + 1,
60
+ question: q.question,
61
+ options: q.options,
62
+ // Convert "a" -> "Option Text"
63
+ answer: mapBackendAnswerToText(q.answer, q.options),
64
+ explanation: q.explanation,
65
  selected: null,
66
  status: idx === 0 ? "visited" : "notVisited",
67
+ }));
68
+ }
69
+ // Fallback if no data
70
+ return [];
71
+ });
72
 
73
  const [currentIndex, setCurrentIndex] = useState(0);
74
+ const [isAnswered, setIsAnswered] = useState(false);
75
  const [showScore, setShowScore] = useState(false);
76
  const [timeLeft, setTimeLeft] = useState(totalTimeSeconds);
77
 
78
  const totalQuestions = questions.length;
79
+ // Safety check if data is empty
80
+ const currentQuestion = questions[currentIndex] || {
81
+ id: 0,
82
+ question: "Loading...",
83
+ options: [],
84
+ answer: "",
85
+ status: "notVisited",
86
+ };
87
 
 
88
  useEffect(() => {
89
  setIsAnswered(Boolean(currentQuestion?.selected));
90
  }, [currentQuestion]);
91
 
92
+ // Timer
93
  useEffect(() => {
94
  if (showScore) return;
95
  if (timeLeft <= 0) {
 
98
  }
99
  const t = setInterval(() => setTimeLeft((s) => s - 1), 1000);
100
  return () => clearInterval(t);
 
101
  }, [timeLeft, showScore]);
102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  const handleAnswerClick = useCallback(
104
  (option: string) => {
 
105
  setQuestions((prev) =>
106
  prev.map((q, idx) =>
107
  idx === currentIndex
 
114
  [currentIndex]
115
  );
116
 
117
+ const goToQuestion = useCallback((index: number) => {
118
+ setQuestions((prev) =>
119
+ prev.map((q, idx) => {
120
+ if (idx === index) {
121
+ return {
122
+ ...q,
123
+ status: q.status === "notVisited" ? "visited" : q.status,
124
+ };
125
+ }
126
+ return q;
127
+ })
128
+ );
129
+ setCurrentIndex(index);
130
+ }, []);
 
 
 
 
 
131
 
132
  const handleSaveAndNext = useCallback(() => {
 
133
  setQuestions((prev) =>
134
  prev.map((q, idx) => {
135
  if (idx === currentIndex) {
 
151
  if (next < totalQuestions) {
152
  goToQuestion(next);
153
  } else {
 
154
  handleSubmit();
155
  }
156
  }, [currentIndex, goToQuestion, totalQuestions]);
 
158
  const handleMarkForReview = useCallback(() => {
159
  setQuestions((prev) =>
160
  prev.map((q, idx) =>
161
+ idx === currentIndex ? { ...q, status: "markedForReview" } : q
 
 
 
 
 
162
  )
163
  );
 
164
  const next = currentIndex + 1;
165
  if (next < totalQuestions) goToQuestion(next);
166
  }, [currentIndex, goToQuestion, totalQuestions]);
 
177
  const computeScore = useCallback(() => {
178
  let s = 0;
179
  questions.forEach((q) => {
180
+ if (q.selected === q.answer) s += 1;
181
  });
182
  return s;
183
  }, [questions]);
 
187
  }, []);
188
 
189
  const handleAutoSubmit = useCallback(() => {
 
190
  setShowScore(true);
191
  }, []);
192
 
 
193
  const statusClassForPalette = (q: QuestionItem, idx: number) => {
 
194
  if (idx === currentIndex)
195
  return "bg-blue-500 text-white ring-2 ring-blue-300";
196
  switch (q.status) {
 
200
  return "bg-yellow-400 text-black";
201
  case "visited":
202
  return "bg-red-500 text-white";
 
203
  default:
204
  return "bg-slate-300 text-slate-700";
205
  }
206
  };
207
 
208
+ const score = useMemo(() => computeScore(), [questions, computeScore]);
209
 
210
+ // --- RENDER SCORE ---
211
  if (showScore) {
212
  return (
213
  <div className="min-h-screen bg-gray-100 flex items-center justify-center p-6">
 
223
  </div>
224
 
225
  <div className="flex justify-center gap-3">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  <button
227
  onClick={onBack}
228
  className="px-6 py-3 rounded-lg border border-slate-300 text-slate-700 bg-white hover:bg-slate-50"
229
  >
230
+ ← Back to Generator
231
  </button>
232
  </div>
233
  </div>
 
235
  );
236
  }
237
 
238
+ // Handle Loading/Empty State
239
+ if (questions.length === 0) {
240
+ return (
241
+ <div className="min-h-screen flex items-center justify-center bg-gray-50 text-gray-500">
242
+ No questions available. Please try generating again.
243
+ <button onClick={onBack} className="ml-4 text-blue-500 underline">
244
+ Go Back
245
+ </button>
246
+ </div>
247
+ );
248
+ }
249
+
250
+ // --- MAIN QUIZ UI ---
251
  return (
252
  <div className="min-h-screen bg-gray-50 p-6 lg:p-12">
253
  <div className="max-w-7xl mx-auto grid grid-cols-12 gap-6">
254
+ {/* Left: Main Question area */}
255
  <div className="col-span-12 lg:col-span-8">
256
  <div className="mb-4 flex items-center justify-between">
257
  <div>
 
265
  <h1 className="text-2xl lg:text-3xl font-bold mt-3">
266
  Generated MCQ Quiz
267
  </h1>
 
 
 
268
  </div>
269
 
270
  <div className="hidden md:flex flex-col items-end text-sm text-gray-600">
 
271
  <div className="text-xl font-bold text-blue-600">
272
  {currentIndex + 1} / {totalQuestions}
273
  </div>
 
274
  </div>
275
  </div>
276
 
277
  <div className="bg-white rounded-xl shadow p-6 mb-6 border border-slate-200">
278
  <div className="mb-6">
 
279
  <h2 className="text-xl lg:text-2xl font-semibold text-slate-800">
280
  {currentQuestion.question}
281
  </h2>
 
284
  <div className="grid gap-4">
285
  {currentQuestion.options.map((opt, i) => {
286
  const isSelected = currentQuestion.selected === opt;
287
+ // Determine styling logic
288
+ let optionClass =
289
+ "bg-white hover:bg-slate-50 border border-slate-200 text-slate-800";
290
+
291
+ if (isAnswered) {
292
+ if (opt === currentQuestion.answer) {
293
+ optionClass =
294
+ "bg-green-600/80 text-white border-green-500 shadow";
295
+ } else if (isSelected) {
296
+ optionClass =
297
+ "bg-red-600/80 text-white border-red-500 shadow";
298
+ } else {
299
+ optionClass =
300
+ "bg-slate-50 border-slate-200 text-slate-700 opacity-70";
301
+ }
302
+ } else if (isSelected) {
303
+ optionClass = "bg-blue-600/80 text-white";
304
+ }
305
 
306
  return (
307
  <button
308
  key={i}
309
  onClick={() => handleAnswerClick(opt)}
310
+ disabled={isAnswered}
311
  className={`p-4 rounded-lg text-left border transition flex justify-between items-center ${optionClass}`}
312
  >
313
  <span>{opt}</span>
 
314
  {isAnswered && opt === currentQuestion.answer && (
315
  <CheckCircle className="w-5 h-5 text-green-100" />
316
  )}
317
  {isAnswered &&
318
+ isSelected &&
319
  opt !== currentQuestion.answer && (
320
  <XCircle className="w-5 h-5 text-red-200" />
321
  )}
 
 
 
322
  </button>
323
  );
324
  })}
325
  </div>
326
 
327
+ {/* Explanation Section (Only visible after answering) */}
328
+ {isAnswered && currentQuestion.explanation && (
329
+ <div className="mt-6 p-4 bg-blue-50 border border-blue-100 rounded-lg text-blue-800 text-sm">
330
+ <strong>Explanation:</strong> {currentQuestion.explanation}
331
+ </div>
332
+ )}
333
+
334
  {/* Bottom actions */}
335
  <div className="mt-6 flex flex-col md:flex-row gap-3 md:gap-4 items-center justify-between">
336
  <div className="flex gap-2 flex-wrap">
 
340
  >
341
  Mark for Review
342
  </button>
 
343
  <button
344
  onClick={handleClearResponse}
345
+ disabled={isAnswered}
346
+ className="px-4 py-2 rounded-md border border-slate-300 text-slate-700 bg-white disabled:opacity-50"
347
  >
348
  Clear Response
349
  </button>
 
350
  <button
351
  onClick={handleSaveAndNext}
352
+ className="px-4 py-2 rounded-md bg-gradient-to-r from-blue-500 to-blue-700 text-white font-medium flex items-center gap-2"
353
  >
354
  Save & Next <ChevronRight className="w-4 h-4" />
355
  </button>
356
  </div>
 
 
 
 
 
 
 
 
357
  </div>
358
  </div>
359
  </div>
360
 
361
+ {/* Right: Sidebar */}
362
  <aside className="col-span-12 lg:col-span-4">
363
  <div className="sticky top-6 space-y-4">
364
+ {/* Timer */}
365
  <div className="bg-white rounded-xl p-4 shadow border border-slate-200 text-center">
366
  <div className="text-sm text-gray-500">Time Left</div>
367
  <div className="mt-2 text-3xl font-bold text-red-600">
368
  {formatTime(Math.max(0, timeLeft))}
369
  </div>
 
370
  </div>
371
 
372
+ {/* Palette */}
373
  <div className="bg-white rounded-xl p-4 shadow border border-slate-200">
374
  <div className="flex items-center justify-between mb-3">
375
  <h3 className="text-sm font-semibold">Question Palette</h3>
 
376
  </div>
 
377
  <div className="grid grid-cols-5 gap-2">
378
  {questions.map((q, idx) => (
379
  <button
 
383
  q,
384
  idx
385
  )}`}
 
386
  >
387
  {idx + 1}
388
  </button>
389
  ))}
390
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
  </div>
392
 
393
+ <button
394
+ onClick={() => {
395
+ if (window.confirm("Submit test now?")) handleSubmit();
396
+ }}
397
+ className="w-full px-4 py-2 rounded-md bg-red-600 text-white font-semibold"
398
+ >
399
+ Submit Test
400
+ </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
401
  </div>
402
  </aside>
403
  </div>
 
405
  );
406
  };
407
 
408
+ export default MCQQuizPage;