@tinyweb_dev/oe-exam-sdk 1.0.8 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/dist/components/exams/take/components/QuestionRenderer.js +1 -5
  2. package/dist/components/exams/take/components/question-renderers/ChooseThenAnswerGroupRenderer.js +1 -1
  3. package/dist/components/exams/take/components/question-renderers/MoversCrossOutWordGroupRenderer.js +24 -10
  4. package/dist/components/exams/take/components/question-renderers/MoversCrosswordPuzzleRenderer.js +1 -1
  5. package/dist/components/exams/take/components/question-renderers/MoversFillInBlankGroupRenderer.js +2 -1
  6. package/dist/components/exams/take/components/speaking/renderers/SpeakingSpontaneousQaGroupRenderer.js +12 -2
  7. package/dist/components/exams/take/hooks/useSpontaneousQaGroupRendererSetup.js +2 -1
  8. package/dist/components/exams/take/utils/answer-transformers.js +16 -1
  9. package/dist/components/exams/take/utils/cross-out-word-group.transformer.js +4 -6
  10. package/dist/components/exams/take/utils/question-transformers.d.ts +5 -0
  11. package/dist/components/exams/take/utils/question-transformers.js +6 -0
  12. package/dist/components/questions/_shared/components/compact/KidChoiceRow.d.ts +12 -0
  13. package/dist/components/questions/_shared/components/compact/KidChoiceRow.js +33 -0
  14. package/dist/components/questions/_shared/components/compact/KidInstruction.d.ts +5 -0
  15. package/dist/components/questions/_shared/components/compact/KidInstruction.js +9 -0
  16. package/dist/components/questions/_shared/components/compact/KidPrompt.d.ts +5 -0
  17. package/dist/components/questions/_shared/components/compact/KidPrompt.js +9 -0
  18. package/dist/components/questions/_shared/components/compact/KidQuestionCard.d.ts +12 -0
  19. package/dist/components/questions/_shared/components/compact/KidQuestionCard.js +7 -0
  20. package/dist/components/questions/_shared/components/compact/index.d.ts +4 -0
  21. package/dist/components/questions/_shared/components/compact/index.js +4 -0
  22. package/dist/components/questions/_shared/types/answer-the-question-group.type.d.ts +3 -0
  23. package/dist/components/questions/_shared/types/choose-then-answer-group.type.d.ts +17 -0
  24. package/dist/components/questions/_shared/types/cross-out-word-group.type.d.ts +1 -0
  25. package/dist/components/questions/_shared/types/crossword-puzzle.type.d.ts +5 -1
  26. package/dist/components/questions/_shared/types/spontaneous-qa-group.type.d.ts +17 -0
  27. package/dist/components/questions/_shared/types/true-false-correct-group.type.d.ts +1 -0
  28. package/dist/components/questions/_shared/types/true-false-group.type.d.ts +1 -0
  29. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +18 -19
  30. package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.d.ts +1 -1
  31. package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js +16 -17
  32. package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupCreator.js +7 -4
  33. package/dist/components/questions/types/answer-the-question-group/map-answer-the-question-group-data.js +2 -0
  34. package/dist/components/questions/types/answer-the-question-group/transform.js +12 -5
  35. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +17 -11
  36. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +15 -42
  37. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +15 -36
  38. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +9 -32
  39. package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.d.ts +1 -1
  40. package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js +76 -11
  41. package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupCreator.js +54 -6
  42. package/dist/components/questions/types/choose-then-answer-group/map-choose-then-answer-group-data.d.ts +4 -1
  43. package/dist/components/questions/types/choose-then-answer-group/map-choose-then-answer-group-data.js +39 -2
  44. package/dist/components/questions/types/choose-then-answer-group/transform.js +27 -14
  45. package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.d.ts +1 -1
  46. package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.js +76 -7
  47. package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupCreator.js +24 -44
  48. package/dist/components/questions/types/cross-out-word-group/index.d.ts +1 -0
  49. package/dist/components/questions/types/cross-out-word-group/index.js +1 -0
  50. package/dist/components/questions/types/cross-out-word-group/map-cross-out-word-group-data.js +9 -9
  51. package/dist/components/questions/types/cross-out-word-group/tokenize.d.ts +2 -0
  52. package/dist/components/questions/types/cross-out-word-group/tokenize.js +11 -0
  53. package/dist/components/questions/types/cross-out-word-group/transform.js +2 -4
  54. package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.d.ts +2 -1
  55. package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.js +20 -8
  56. package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleCreator.js +68 -11
  57. package/dist/components/questions/types/crossword-puzzle/transform.js +59 -30
  58. package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +26 -31
  59. package/dist/components/questions/types/fill-in-blank-group/FillInBlankAnswerKey.d.ts +6 -0
  60. package/dist/components/questions/types/fill-in-blank-group/FillInBlankAnswerKey.js +17 -0
  61. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupClient.js +32 -28
  62. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.js +109 -26
  63. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupMediaField.js +21 -11
  64. package/dist/components/questions/types/fill-in-blank-group/blank-utils.d.ts +1 -0
  65. package/dist/components/questions/types/fill-in-blank-group/blank-utils.js +4 -1
  66. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +32 -31
  67. package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.d.ts +12 -2
  68. package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.js +188 -46
  69. package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixCreator.js +6 -2
  70. package/dist/components/questions/types/find-words-in-matrix/transform.js +5 -0
  71. package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +4 -3
  72. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +11 -30
  73. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +7 -6
  74. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +2 -1
  75. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +14 -13
  76. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +4 -3
  77. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +4 -3
  78. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +11 -10
  79. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +4 -3
  80. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +13 -11
  81. package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.js +17 -14
  82. package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureClient.js +16 -12
  83. package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureGroupClient.js +14 -14
  84. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js +13 -13
  85. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupClient.js +7 -6
  86. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +8 -26
  87. package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesClient.js +2 -1
  88. package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +6 -5
  89. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardClient.js +2 -1
  90. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +5 -4
  91. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.d.ts +1 -1
  92. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.js +54 -11
  93. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupCreator.js +11 -7
  94. package/dist/components/questions/types/spontaneous-qa-group/creator-helpers.js +8 -2
  95. package/dist/components/questions/types/spontaneous-qa-group/flatten.d.ts +1 -0
  96. package/dist/components/questions/types/spontaneous-qa-group/flatten.js +4 -2
  97. package/dist/components/questions/types/spontaneous-qa-group/map-spontaneous-qa-group-data.js +5 -3
  98. package/dist/components/questions/types/spontaneous-qa-group/register.js +5 -2
  99. package/dist/components/questions/types/spontaneous-qa-group/transform.js +6 -2
  100. package/dist/components/questions/types/true-false/TrueFalseClient.js +5 -54
  101. package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.d.ts +1 -1
  102. package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.js +39 -19
  103. package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.d.ts +1 -1
  104. package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.js +20 -19
  105. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +76 -81
  106. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +17 -16
  107. package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupClient.js +31 -36
  108. package/dist/components/questions/types/word-ordering/WordOrderingClient.js +15 -14
  109. package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.js +9 -8
  110. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +16 -13
  111. package/dist/components/questions/viewer/AnswerTheQuestionGroupViewer.d.ts +3 -2
  112. package/dist/components/questions/viewer/AnswerTheQuestionGroupViewer.js +12 -5
  113. package/dist/components/questions/viewer/ChooseThenAnswerGroupViewer.d.ts +3 -2
  114. package/dist/components/questions/viewer/ChooseThenAnswerGroupViewer.js +4 -3
  115. package/dist/components/questions/viewer/CrossOutWordGroupViewer.d.ts +3 -1
  116. package/dist/components/questions/viewer/CrossOutWordGroupViewer.js +12 -2
  117. package/dist/components/questions/viewer/FillInBlankGroupViewer.d.ts +6 -2
  118. package/dist/components/questions/viewer/FillInBlankGroupViewer.js +13 -7
  119. package/dist/components/questions/viewer/QuestionViewer.d.ts +9 -1
  120. package/dist/components/questions/viewer/QuestionViewer.js +23 -10
  121. package/dist/components/questions/viewer/SpontaneousQaGroupViewer.d.ts +7 -1
  122. package/dist/components/questions/viewer/SpontaneousQaGroupViewer.js +14 -2
  123. package/dist/components/questions/viewer/TrueFalseCorrectGroupViewer.d.ts +3 -2
  124. package/dist/components/questions/viewer/TrueFalseCorrectGroupViewer.js +14 -4
  125. package/dist/components/questions/viewer/TrueFalseGroupViewer.d.ts +5 -2
  126. package/dist/components/questions/viewer/TrueFalseGroupViewer.js +14 -4
  127. package/dist/components/questions/viewer/WordOrderAndMatchGroupViewer.d.ts +6 -2
  128. package/dist/components/questions/viewer/WordOrderAndMatchGroupViewer.js +26 -6
  129. package/dist/components/results/renderers/ReviewAnswerTheQuestionGroupRenderer.js +1 -1
  130. package/dist/components/results/renderers/review-question-body-movers.js +1 -1
  131. package/dist/components/results/review-data.js +8 -1
  132. package/dist/shared/lib/utils/question-reverse-transform.js +39 -5
  133. package/dist/shared/types/questions/answer-the-question-group.d.ts +8 -3
  134. package/dist/shared/types/questions/answer-the-question-group.js +5 -0
  135. package/dist/shared/types/questions/choose-then-answer-group.d.ts +3 -0
  136. package/dist/shared/types/questions/crossword-puzzle.d.ts +1 -0
  137. package/dist/shared/types/questions/find-words-in-matrix.d.ts +1 -0
  138. package/package.json +1 -1
@@ -28,6 +28,7 @@ function createEmptyQuestion(questionNumber) {
28
28
  expectedAnswers: [''],
29
29
  points: 1,
30
30
  questionNumber,
31
+ isExample: false,
31
32
  };
32
33
  }
33
34
  function createEmptyPart(partNumber) {
@@ -43,12 +44,14 @@ function mapQuestion(item, index, fallbackAnswers) {
43
44
  const content = asRecord(record.content);
44
45
  const correctAnswer = asRecord(record.correctAnswer);
45
46
  const expected = normalizeExpectedAnswers(correctAnswer.expectedAnswers ?? fallbackAnswers);
47
+ const isExample = record.isExample === true;
46
48
  return {
47
49
  questionText: asString(content.questionText) || asString(record.questionText),
48
50
  inputType: asString(content.inputType) === 'AUDIO' ? 'AUDIO' : 'TEXT',
49
51
  audioUrl: asString(content.audioUrl),
50
52
  expectedAnswers: expected,
51
- points: asNumber(record.points, 1),
53
+ isExample,
54
+ points: isExample ? 0 : asNumber(record.points, 1),
52
55
  questionNumber: asNumber(record.questionNumber, index + 1),
53
56
  };
54
57
  }
@@ -65,7 +68,7 @@ function mapPart(item, index) {
65
68
  };
66
69
  }
67
70
  function sumPoints(parts) {
68
- return parts.reduce((total, part) => (total + part.questions.reduce((partTotal, question) => partTotal + (question.points || 0), 0)), 0);
71
+ return parts.reduce((total, part) => (total + part.questions.reduce((partTotal, question) => (partTotal + (question.isExample ? 0 : (question.points || 0))), 0)), 0);
69
72
  }
70
73
  export const spontaneousQaGroupRegistration = {
71
74
  component: SpontaneousQaGroupCreator,
@@ -9,14 +9,18 @@ function normalizeExpectedAnswers(value) {
9
9
  .filter((item) => item !== '');
10
10
  }
11
11
  function sumPoints(parts) {
12
- return parts.reduce((total, part) => (total + part.questions.reduce((partTotal, question) => (partTotal + (typeof question.points === 'number' ? question.points : 1)), 0)), 0);
12
+ return parts.reduce((total, part) => (total + part.questions.reduce((partTotal, question) => (partTotal + (question.isExample
13
+ ? 0
14
+ : typeof question.points === 'number' ? question.points : 1)), 0)), 0);
13
15
  }
14
16
  function toApiQuestion(question, index) {
15
17
  const inputType = question.inputType === 'AUDIO' ? 'AUDIO' : 'TEXT';
18
+ const isExample = question.isExample === true;
16
19
  return {
17
20
  questionType: CHILD_QUESTION_TYPE,
18
21
  questionNumber: question.questionNumber || index + 1,
19
- points: typeof question.points === 'number' ? question.points : 1,
22
+ points: isExample ? 0 : typeof question.points === 'number' ? question.points : 1,
23
+ ...(isExample ? { isExample: true } : {}),
20
24
  content: {
21
25
  inputType,
22
26
  questionText: question.questionText || '',
@@ -1,20 +1,17 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect } from 'react';
4
- import { Check, X, CircleHelp } from 'lucide-react';
4
+ import { Lightbulb } from 'lucide-react';
5
5
  import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
6
- import { cn } from '../../../../shared/lib/utils';
6
+ import { KidChoiceRow, KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
7
7
  export function TrueFalseClient({ questionData, onSubmit, isReviewMode = false, userAnswer, }) {
8
8
  const [selectedAnswer, setSelectedAnswer] = useState(userAnswer !== undefined ? userAnswer : undefined);
9
- // Presigned URL for image
10
9
  const { previewUrl: imagePreviewUrl, isLoading } = usePresignedFileUrl(questionData.imageUrl || '');
11
- // Sync userAnswer
12
10
  useEffect(() => {
13
11
  if (userAnswer !== undefined) {
14
12
  setSelectedAnswer(userAnswer);
15
13
  }
16
14
  }, [userAnswer]);
17
- // Handle answer change
18
15
  const handleAnswerChange = (value) => {
19
16
  if (isReviewMode)
20
17
  return;
@@ -23,53 +20,7 @@ export function TrueFalseClient({ questionData, onSubmit, isReviewMode = false,
23
20
  };
24
21
  const hasAnswered = selectedAnswer !== undefined;
25
22
  const isAnswerCorrect = isReviewMode && selectedAnswer === questionData.answer;
26
- return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-emerald-500 to-teal-600 text-white shadow-sm", children: _jsx(CircleHelp, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "\u0110\u00FAng / Sai" })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isAnswerCorrect
27
- ? 'bg-green-100 text-green-700'
28
- : 'bg-red-100 text-red-700'), children: [isAnswerCorrect ? (_jsx(Check, { className: "h-3.5 w-3.5" })) : (_jsx(X, { className: "h-3.5 w-3.5" })), isAnswerCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "space-y-3 p-3", children: [_jsx("div", { className: "rounded-lg border border-blue-100 bg-gradient-to-r from-blue-50 to-indigo-50 p-4", children: _jsx("h3", { className: "text-base font-semibold leading-relaxed text-gray-800", children: questionData.question || 'Chưa nhập câu hỏi' }) }), (imagePreviewUrl || questionData.imageUrl) && (_jsxs("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-gradient-to-br from-slate-50 to-gray-100", children: [_jsx("div", { className: "border-b border-gray-100 px-3 py-1.5", children: _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "h-2 w-2 rounded-full bg-red-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-yellow-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-green-400" }), _jsx("span", { className: "ml-2 text-xs font-medium text-gray-500", children: "H\u00ECnh \u1EA3nh" })] }) }), _jsx("div", { className: "flex items-center justify-center p-4", children: isLoading ? (_jsx("div", { className: "flex h-48 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500" }) })) : (
29
- /* eslint-disable-next-line @next/next/no-img-element */
30
- _jsx("img", { src: imagePreviewUrl || questionData.imageUrl, alt: "Question illustration", className: "max-h-56 w-auto rounded-md object-contain" })) })] })), _jsxs("div", { className: "space-y-2.5", children: [_jsx("span", { className: "text-xs font-medium uppercase tracking-wider text-gray-500", children: "Ch\u1ECDn \u0111\u00E1p \u00E1n" }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("button", { type: "button", onClick: () => handleAnswerChange(true), disabled: isReviewMode, className: cn('group flex items-center justify-center gap-3 rounded-xl border-2 p-4 transition-all', isReviewMode
31
- ? questionData.answer === true
32
- ? 'border-green-400 bg-green-50'
33
- : selectedAnswer === true
34
- ? 'border-red-400 bg-red-50'
35
- : 'border-gray-200 bg-gray-50'
36
- : selectedAnswer === true
37
- ? 'border-emerald-500 bg-emerald-50 shadow-sm ring-2 ring-emerald-100'
38
- : 'border-gray-200 bg-white hover:border-emerald-300 hover:bg-emerald-50/50 hover:shadow-sm'), children: [_jsx("div", { className: cn('flex h-9 w-9 items-center justify-center rounded-full text-sm font-bold transition-all', isReviewMode
39
- ? questionData.answer === true
40
- ? 'bg-green-500 text-white'
41
- : selectedAnswer === true
42
- ? 'bg-red-500 text-white'
43
- : 'bg-gray-200 text-gray-600'
44
- : selectedAnswer === true
45
- ? 'bg-emerald-500 text-white shadow-sm'
46
- : 'bg-gray-100 text-gray-500 group-hover:bg-emerald-100 group-hover:text-emerald-600'), children: _jsx(Check, { className: "h-4 w-4" }) }), _jsx("span", { className: cn('text-base font-semibold', isReviewMode && questionData.answer === true
47
- ? 'text-green-800'
48
- : isReviewMode && selectedAnswer === true
49
- ? 'text-red-800'
50
- : selectedAnswer === true
51
- ? 'text-emerald-700'
52
- : 'text-gray-600'), children: "\u0110\u00FAng (True)" }), isReviewMode && (questionData.answer === true || selectedAnswer === true) && (_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-full', questionData.answer === true ? 'bg-green-500' : 'bg-red-500'), children: questionData.answer === true ? (_jsx(Check, { className: "h-4 w-4 text-white" })) : (_jsx(X, { className: "h-4 w-4 text-white" })) }))] }), _jsxs("button", { type: "button", onClick: () => handleAnswerChange(false), disabled: isReviewMode, className: cn('group flex items-center justify-center gap-3 rounded-xl border-2 p-4 transition-all', isReviewMode
53
- ? questionData.answer === false
54
- ? 'border-green-400 bg-green-50'
55
- : selectedAnswer === false
56
- ? 'border-red-400 bg-red-50'
57
- : 'border-gray-200 bg-gray-50'
58
- : selectedAnswer === false
59
- ? 'border-red-500 bg-red-50 shadow-sm ring-2 ring-red-100'
60
- : 'border-gray-200 bg-white hover:border-red-300 hover:bg-red-50/50 hover:shadow-sm'), children: [_jsx("div", { className: cn('flex h-9 w-9 items-center justify-center rounded-full text-sm font-bold transition-all', isReviewMode
61
- ? questionData.answer === false
62
- ? 'bg-green-500 text-white'
63
- : selectedAnswer === false
64
- ? 'bg-red-500 text-white'
65
- : 'bg-gray-200 text-gray-600'
66
- : selectedAnswer === false
67
- ? 'bg-red-500 text-white shadow-sm'
68
- : 'bg-gray-100 text-gray-500 group-hover:bg-red-100 group-hover:text-red-600'), children: _jsx(X, { className: "h-4 w-4" }) }), _jsx("span", { className: cn('text-base font-semibold', isReviewMode && questionData.answer === false
69
- ? 'text-green-800'
70
- : isReviewMode && selectedAnswer === false
71
- ? 'text-red-800'
72
- : selectedAnswer === false
73
- ? 'text-red-700'
74
- : 'text-gray-600'), children: "Sai (False)" }), isReviewMode && (questionData.answer === false || selectedAnswer === false) && (_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-full', questionData.answer === false ? 'bg-green-500' : 'bg-red-500'), children: questionData.answer === false ? (_jsx(Check, { className: "h-4 w-4 text-white" })) : (_jsx(X, { className: "h-4 w-4 text-white" })) }))] })] })] }), !isReviewMode && !hasAnswered && (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-3", children: [_jsx("div", { className: "flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-white", children: _jsx("span", { className: "text-xs font-bold", children: "!" }) }), _jsx("span", { className: "text-sm text-amber-800", children: "Vui l\u00F2ng ch\u1ECDn \u0111\u00E1p \u00E1n" })] })), isReviewMode && questionData.explanation && (_jsxs("div", { className: "rounded-lg border border-blue-200 bg-blue-50 p-4", children: [_jsx("p", { className: "text-sm font-semibold text-blue-800", children: "Gi\u1EA3i th\u00EDch:" }), _jsx("p", { className: "mt-1 text-sm text-blue-700", children: questionData.explanation })] }))] })] }) }));
23
+ return (_jsxs("div", { className: "space-y-4", children: [_jsxs(KidQuestionCard, { title: "\u0110\u00FAng / Sai", isExample: questionData.isExample, review: isReviewMode && hasAnswered ? (isAnswerCorrect ? 'correct' : 'wrong') : null, children: [_jsx(KidPrompt, { children: questionData.question || 'Chưa nhập câu hỏi' }), (imagePreviewUrl || questionData.imageUrl) && (_jsxs("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-gradient-to-br from-slate-50 to-gray-100", children: [_jsx("div", { className: "border-b border-gray-100 px-3 py-1.5", children: _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "h-2 w-2 rounded-full bg-red-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-yellow-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-green-400" }), _jsx("span", { className: "ml-2 text-xs font-medium text-gray-500", children: "H\u00ECnh \u1EA3nh" })] }) }), _jsx("div", { className: "flex items-center justify-center p-4", children: isLoading ? (_jsx("div", { className: "flex h-48 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500" }) })) : (
24
+ /* eslint-disable-next-line @next/next/no-img-element */
25
+ _jsx("img", { src: imagePreviewUrl || questionData.imageUrl, alt: "H\u00ECnh \u1EA3nh c\u00E2u h\u1ECFi", className: "max-h-56 w-auto rounded-md object-contain" })) })] })), _jsxs("div", { className: "space-y-1.5", children: [_jsx(KidChoiceRow, { letter: "T", selected: selectedAnswer === true, isReviewMode: isReviewMode, isCorrect: isReviewMode && questionData.answer === true, onClick: () => handleAnswerChange(true), children: "\u0110\u00FAng" }), _jsx(KidChoiceRow, { letter: "F", selected: selectedAnswer === false, isReviewMode: isReviewMode, isCorrect: isReviewMode && questionData.answer === false, onClick: () => handleAnswerChange(false), children: "Sai" })] }), !isReviewMode && !hasAnswered && (_jsxs("div", { className: "flex items-center gap-2 rounded-md border border-amber-200 bg-amber-50 px-2.5 py-1.5", children: [_jsx("div", { className: "flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-white", children: _jsx("span", { className: "text-[10px] font-bold", children: "!" }) }), _jsx("span", { className: "text-xs text-amber-800", children: "Vui l\u00F2ng ch\u1ECDn \u0111\u00E1p \u00E1n" })] }))] }), isReviewMode && questionData.explanation && (_jsxs("div", { className: "flex items-start gap-2 rounded-md border border-amber-200 bg-amber-50 px-3 py-2", children: [_jsx(Lightbulb, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-amber-600" }), _jsxs("p", { className: "text-xs text-gray-700", children: [_jsx("span", { className: "font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch: " }), questionData.explanation] })] }))] }));
75
26
  }
@@ -1,2 +1,2 @@
1
1
  import type { TrueFalseCorrectGroupClientProps } from '../../_shared/types/true-false-correct-group.type';
2
- export declare function TrueFalseCorrectGroupClient({ questionData, isReviewMode, userAnswers, }: TrueFalseCorrectGroupClientProps): import("react").JSX.Element;
2
+ export declare function TrueFalseCorrectGroupClient({ questionData, isReviewMode, userAnswers, onAnswerChange, }: TrueFalseCorrectGroupClientProps): import("react").JSX.Element;
@@ -1,25 +1,45 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Check, CircleHelp, Lightbulb, Star, X } from 'lucide-react';
4
- import { cn } from '../../../../shared/lib/utils';
5
- export function TrueFalseCorrectGroupClient({ questionData, isReviewMode = false, userAnswers = [], }) {
3
+ import { Lightbulb } from 'lucide-react';
4
+ import { KidChoiceRow, KidInstruction, KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
5
+ function emptyAnswer() {
6
+ return { isTrue: null, correction: '' };
7
+ }
8
+ export function TrueFalseCorrectGroupClient({ questionData, isReviewMode = false, userAnswers = [], onAnswerChange, }) {
6
9
  const items = questionData.items || [];
7
- return (_jsxs("div", { className: "space-y-4", children: [questionData.instruction && (_jsx("div", { className: "rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3 text-sm font-medium text-indigo-800", children: questionData.instruction })), items.map((item, itemIndex) => {
8
- const userValue = userAnswers[itemIndex];
10
+ const emit = (itemIndex, nextItem) => {
11
+ const item = items[itemIndex];
12
+ if (isReviewMode || item?.isExample || !onAnswerChange) {
13
+ return;
14
+ }
15
+ const next = items.map((_, index) => userAnswers[index] ?? emptyAnswer());
16
+ next[itemIndex] = nextItem;
17
+ onAnswerChange(next);
18
+ };
19
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx(KidInstruction, { children: questionData.instruction }), items.map((item, itemIndex) => {
9
20
  const correctIsTrue = item.isTrue === true;
10
- return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 text-white", children: _jsx(CircleHelp, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", item.questionNumber || itemIndex + 1] }), item.isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-semibold text-amber-700", children: [_jsx(Star, { className: "h-3 w-3" }), "Example"] }))] }), _jsxs("span", { className: "text-xs text-gray-500", children: [item.points || 0, " \u0111i\u1EC3m"] })] }), _jsxs("div", { className: "space-y-3 p-3", children: [_jsx("p", { className: "text-sm font-semibold leading-relaxed text-gray-800", children: item.question || 'Câu hỏi chưa được nhập' }), _jsx("div", { className: "flex items-center gap-3", children: [
11
- { value: true, label: 'True' },
12
- { value: false, label: 'False' },
13
- ].map((opt) => {
14
- const isCorrect = opt.value === correctIsTrue;
15
- const isSelected = userValue?.isTrue === opt.value;
16
- return (_jsxs("div", { className: cn('flex flex-1 items-center justify-center gap-2 rounded-lg border-2 px-4 py-2 text-sm font-semibold transition-colors', isReviewMode && isCorrect
17
- ? 'border-green-500 bg-green-50 text-green-700'
18
- : isReviewMode && isSelected && !isCorrect
19
- ? 'border-red-500 bg-red-50 text-red-700'
20
- : isSelected
21
- ? 'border-blue-500 bg-blue-50 text-blue-700'
22
- : 'border-gray-200 bg-white text-gray-700'), children: [opt.value ? _jsx(Check, { className: "h-4 w-4" }) : _jsx(X, { className: "h-4 w-4" }), _jsx("span", { children: opt.label })] }, String(opt.value)));
23
- }) }), !correctIsTrue && (_jsxs("div", { className: "rounded-lg border border-amber-100 bg-amber-50/70 px-3 py-2", children: [_jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-amber-800", children: "C\u00E2u \u0111\u00FAng" }), _jsx("p", { className: "mt-1 text-sm text-amber-900", children: item.correction || 'Chưa nhập câu sửa' }), isReviewMode && userValue && userValue.isTrue === false && (_jsxs("p", { className: "mt-2 text-xs text-slate-600", children: ["H\u1ECDc sinh: ", userValue.correction || '(trống)'] }))] }))] })] }, `${item.questionNumber}-${itemIndex}`));
21
+ const userValue = item.isExample
22
+ ? { isTrue: item.isTrue, correction: item.correction }
23
+ : userAnswers[itemIndex];
24
+ const canSelect = !isReviewMode && !item.isExample && Boolean(onAnswerChange);
25
+ const showStudentCorrection = userValue?.isTrue === false || (isReviewMode && !correctIsTrue);
26
+ const hasAnswered = userValue?.isTrue === true || userValue?.isTrue === false;
27
+ const isAnswerCorrect = userValue?.isTrue === correctIsTrue;
28
+ return (_jsxs(KidQuestionCard, { title: `Câu ${item.questionNumber || itemIndex + 1}`, points: item.points || 0, isExample: item.isExample, review: isReviewMode && !item.isExample && hasAnswered
29
+ ? (isAnswerCorrect ? 'correct' : 'wrong')
30
+ : null, children: [_jsx(KidPrompt, { children: item.question || 'Câu hỏi chưa được nhập' }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(KidChoiceRow, { letter: "T", selected: userValue?.isTrue === true, isReviewMode: isReviewMode, isCorrect: isReviewMode && correctIsTrue, disabled: !canSelect, onClick: canSelect
31
+ ? () => emit(itemIndex, {
32
+ isTrue: true,
33
+ correction: '',
34
+ })
35
+ : undefined, children: "\u0110\u00FAng" }), _jsx(KidChoiceRow, { letter: "F", selected: userValue?.isTrue === false, isReviewMode: isReviewMode, isCorrect: isReviewMode && !correctIsTrue, disabled: !canSelect, onClick: canSelect
36
+ ? () => emit(itemIndex, {
37
+ isTrue: false,
38
+ correction: userValue?.correction || '',
39
+ })
40
+ : undefined, children: "Sai" })] }), showStudentCorrection && canSelect ? (_jsxs("div", { className: "rounded-lg border border-slate-200 bg-white px-3 py-2", children: [_jsx("label", { className: "mb-1.5 block text-xs font-semibold uppercase tracking-wide text-slate-500", children: "S\u1EEDa c\u00E2u sai" }), _jsx("textarea", { value: userValue?.correction || '', onChange: (event) => emit(itemIndex, {
41
+ isTrue: false,
42
+ correction: event.target.value,
43
+ }), placeholder: "Vi\u1EBFt c\u00E2u \u0111\u00FAng...", rows: 2, className: "w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm text-slate-800 outline-none ring-blue-200 focus:border-blue-400 focus:ring-2" })] })) : null, isReviewMode && !correctIsTrue ? (_jsxs("div", { className: "rounded-lg border border-amber-100 bg-amber-50/70 px-3 py-2", children: [_jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-amber-800", children: "C\u00E2u \u0111\u00FAng" }), _jsx("p", { className: "mt-1 text-sm text-amber-900", children: item.correction || 'Chưa nhập câu sửa' }), userValue && userValue.isTrue === false ? (_jsxs("p", { className: "mt-2 text-xs text-slate-600", children: ["H\u1ECDc sinh: ", userValue.correction || '(trống)'] })) : null] })) : null] }, `${item.questionNumber}-${itemIndex}`));
24
44
  }), isReviewMode && questionData.explanation && (_jsxs("div", { className: "flex items-start gap-2 rounded-xl border border-amber-100 bg-amber-50 px-4 py-3 text-sm text-amber-800", children: [_jsx(Lightbulb, { className: "mt-0.5 h-4 w-4 flex-shrink-0" }), questionData.explanation] }))] }));
25
45
  }
@@ -1,2 +1,2 @@
1
1
  import type { TrueFalseGroupClientProps } from '../../_shared/types/true-false-group.type';
2
- export declare function TrueFalseGroupClient({ questionData, isReviewMode, userAnswers, }: TrueFalseGroupClientProps): import("react").JSX.Element;
2
+ export declare function TrueFalseGroupClient({ questionData, isReviewMode, userAnswers, onAnswerChange, }: TrueFalseGroupClientProps): import("react").JSX.Element;
@@ -1,25 +1,26 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Check, CircleHelp, Lightbulb, Star, X } from 'lucide-react';
4
- import { cn } from '../../../../shared/lib/utils';
5
- export function TrueFalseGroupClient({ questionData, isReviewMode = false, userAnswers = [], }) {
3
+ import { Lightbulb } from 'lucide-react';
4
+ import { KidChoiceRow, KidInstruction, KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
5
+ export function TrueFalseGroupClient({ questionData, isReviewMode = false, userAnswers = [], onAnswerChange, }) {
6
6
  const items = questionData.items || [];
7
- return (_jsxs("div", { className: "space-y-4", children: [questionData.instruction && (_jsx("div", { className: "rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3 text-sm font-medium text-indigo-800", children: questionData.instruction })), items.map((item, itemIndex) => {
8
- const userValue = userAnswers[itemIndex];
7
+ const handleSelect = (itemIndex, value) => {
8
+ const item = items[itemIndex];
9
+ if (isReviewMode || item?.isExample || !onAnswerChange) {
10
+ return;
11
+ }
12
+ const next = items.map((_, index) => userAnswers[index]);
13
+ next[itemIndex] = value;
14
+ onAnswerChange(next);
15
+ };
16
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx(KidInstruction, { children: questionData.instruction }), items.map((item, itemIndex) => {
17
+ const userValue = item.isExample ? item.correctAnswer : userAnswers[itemIndex];
9
18
  const correctAnswer = item.correctAnswer;
10
- return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 text-white", children: _jsx(CircleHelp, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", item.questionNumber || itemIndex + 1] }), item.isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-semibold text-amber-700", children: [_jsx(Star, { className: "h-3 w-3" }), "Example"] }))] }), _jsxs("span", { className: "text-xs text-gray-500", children: [item.points || 0, " \u0111i\u1EC3m"] })] }), _jsxs("div", { className: "space-y-3 p-3", children: [_jsx("p", { className: "text-sm font-semibold leading-relaxed text-gray-800", children: item.question || 'Câu hỏi chưa được nhập' }), _jsx("div", { className: "flex items-center gap-3", children: [
11
- { value: true, label: 'True' },
12
- { value: false, label: 'False' },
13
- ].map((opt) => {
14
- const isCorrect = opt.value === correctAnswer;
15
- const isSelected = opt.value === userValue;
16
- return (_jsxs("div", { className: cn('flex flex-1 items-center justify-center gap-2 rounded-lg border-2 px-4 py-2 text-sm font-semibold transition-colors', isReviewMode && isCorrect
17
- ? 'border-green-500 bg-green-50 text-green-700'
18
- : isReviewMode && isSelected && !isCorrect
19
- ? 'border-red-500 bg-red-50 text-red-700'
20
- : isSelected
21
- ? 'border-blue-500 bg-blue-50 text-blue-700'
22
- : 'border-gray-200 bg-white text-gray-700'), children: [opt.value ? _jsx(Check, { className: "h-4 w-4" }) : _jsx(X, { className: "h-4 w-4" }), _jsx("span", { children: opt.label })] }, String(opt.value)));
23
- }) })] })] }, `${item.questionNumber}-${itemIndex}`));
19
+ const canSelect = !isReviewMode && !item.isExample && Boolean(onAnswerChange);
20
+ const hasAnswered = userValue !== undefined;
21
+ const isAnswerCorrect = userValue === correctAnswer;
22
+ return (_jsxs(KidQuestionCard, { title: `Câu ${item.questionNumber || itemIndex + 1}`, points: item.points || 0, isExample: item.isExample, review: isReviewMode && !item.isExample && hasAnswered
23
+ ? (isAnswerCorrect ? 'correct' : 'wrong')
24
+ : null, children: [_jsx(KidPrompt, { children: item.question || 'Câu hỏi chưa được nhập' }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(KidChoiceRow, { letter: "T", selected: userValue === true, isReviewMode: isReviewMode, isCorrect: isReviewMode && correctAnswer === true, disabled: !canSelect, onClick: canSelect ? () => handleSelect(itemIndex, true) : undefined, children: "\u0110\u00FAng" }), _jsx(KidChoiceRow, { letter: "F", selected: userValue === false, isReviewMode: isReviewMode, isCorrect: isReviewMode && correctAnswer === false, disabled: !canSelect, onClick: canSelect ? () => handleSelect(itemIndex, false) : undefined, children: "Sai" })] })] }, `${item.questionNumber}-${itemIndex}`));
24
25
  }), isReviewMode && questionData.explanation && (_jsxs("div", { className: "flex items-start gap-2 rounded-xl border border-amber-100 bg-amber-50 px-4 py-3 text-sm text-amber-800", children: [_jsx(Lightbulb, { className: "mt-0.5 h-4 w-4 flex-shrink-0" }), questionData.explanation] }))] }));
25
26
  }
@@ -4,6 +4,7 @@ import { useState, useEffect, useCallback, useMemo } from 'react';
4
4
  import { Check, X, FileText, Star, Mail } from 'lucide-react';
5
5
  import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
6
6
  import { cn } from '../../../../shared/lib/utils';
7
+ import { KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
7
8
  function parseParagraph(paragraph) {
8
9
  const segments = [];
9
10
  const regex = /\{(\d+)\}/g;
@@ -144,90 +145,84 @@ export function WordFillParagraphClient({ questionData, onSubmit, isReviewMode =
144
145
  .map(idx => ({ blankIdx: idx, answer: answers[idx] || '' }))
145
146
  .filter(e => e.answer);
146
147
  }, [allBlanks, isExampleBlank, answers]);
147
- // ── Render ────────────────────────────────────────────────────────────────
148
- const headerGradient = withOptions
149
- ? 'bg-gradient-to-br from-emerald-500 to-teal-600'
150
- : isDefaultMode
151
- ? 'bg-gradient-to-br from-indigo-500 to-blue-600'
152
- : 'bg-gradient-to-br from-teal-500 to-cyan-600';
153
- return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: `flex h-8 w-8 items-center justify-center rounded-lg ${headerGradient} text-white shadow-sm`, children: _jsx(FileText, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "\u0110i\u1EC1n t\u1EEB v\u00E0o \u0111o\u1EA1n v\u0103n" })] }), isReviewMode && allFilled && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isAllCorrect ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'), children: [isAllCorrect ? _jsx(Check, { className: "h-3.5 w-3.5" }) : _jsx(X, { className: "h-3.5 w-3.5" }), isAllCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "space-y-3 p-3", children: [title && (_jsx("h2", { className: "text-center text-xl font-bold text-gray-900", children: title })), exampleEntries.length > 0 && (_jsxs("div", { className: "flex flex-wrap items-center gap-2 rounded-lg border border-amber-200 bg-amber-50 px-4 py-2.5", children: [_jsxs("div", { className: "flex items-center gap-1.5 text-xs font-semibold text-amber-700 shrink-0", children: [_jsx(Star, { className: "h-3.5 w-3.5 fill-amber-400 text-amber-400" }), "\u0110\u00E3 \u0111i\u1EC1n s\u1EB5n (Example):"] }), exampleEntries.map(({ blankIdx, answer }) => (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-md border border-amber-300 bg-white px-2 py-0.5 text-xs font-semibold text-amber-800", children: [_jsx("span", { className: "text-amber-500", children: '{' + blankIdx + '}' }), answer] }, blankIdx)))] })), (imagePreviewUrl || imageUrl) && (_jsx("div", { className: "flex justify-center", children: _jsx("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-gray-50", children: imageLoading ? (_jsx("div", { className: "flex h-48 w-48 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-teal-500" }) })) : (_jsx("img", { src: imagePreviewUrl || imageUrl, alt: title, className: "max-h-56 w-auto object-contain p-2" })) }) })), _jsxs("div", { className: (emailFrom || emailTo)
154
- ? 'overflow-hidden rounded-xl border border-gray-300 bg-white shadow-sm'
155
- : 'rounded-lg border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50', children: [(emailFrom || emailTo) && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center gap-1.5 border-b border-gray-200 bg-gray-100 px-4 py-2.5", children: [_jsx("span", { className: "h-3 w-3 rounded-full bg-red-400" }), _jsx("span", { className: "h-3 w-3 rounded-full bg-yellow-400" }), _jsx("span", { className: "h-3 w-3 rounded-full bg-green-400" }), _jsxs("div", { className: "ml-3 flex items-center gap-1.5 text-xs text-gray-400", children: [_jsx(Mail, { className: "h-3.5 w-3.5" }), _jsx("span", { children: "Email" })] })] }), _jsxs("div", { className: "border-b border-gray-200 bg-gray-50 px-3 py-2 space-y-1.5 text-sm", children: [emailFrom && (_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("span", { className: "w-10 shrink-0 text-xs font-semibold uppercase tracking-wide text-gray-400", children: "From" }), _jsx("span", { className: "font-medium text-gray-800", children: emailFrom })] })), emailTo && (_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("span", { className: "w-10 shrink-0 text-xs font-semibold uppercase tracking-wide text-gray-400", children: "To" }), _jsx("span", { className: "font-medium text-gray-800", children: emailTo })] }))] })] })), _jsx("div", { className: `text-base leading-loose text-gray-800 whitespace-pre-wrap ${(emailFrom || emailTo) ? 'p-5 bg-white' : 'p-5 bg-gradient-to-r from-gray-50 to-slate-50'}`, children: segments.map((segment, idx) => {
156
- if (segment.type === 'text') {
157
- return _jsx("span", { children: segment.value }, idx);
158
- }
159
- const blankIdx = segment.index;
160
- const filled = filledAnswers[blankIdx];
161
- const isCorrectFill = isReviewMode && filled === answers[blankIdx];
162
- const isWrongFill = isReviewMode && !!filled && filled !== answers[blankIdx];
163
- const example = isExampleBlank(blankIdx);
164
- // ── Example blank: always shown pre-filled ──────────────────
165
- if (example) {
166
- return _jsx(ExampleBadge, { text: answers[blankIdx] || '___' }, idx);
167
- }
168
- // ── WITH_OPTIONS blank ──────────────────────────────────────
169
- if (withOptions) {
170
- const isExpanded = expandedBlank === blankIdx;
171
- const blankOptions = wordBank[blankIdx]?.options || [];
172
- return (_jsxs("span", { className: "relative mx-1 inline-flex flex-col items-center align-middle", children: [_jsxs("button", { type: "button", onClick: () => !isReviewMode && setExpandedBlank(isExpanded ? null : blankIdx), disabled: isReviewMode, className: cn('inline-flex min-w-[80px] items-center rounded border-2 border-dashed px-3 py-0.5 text-center text-sm font-semibold transition-all', isReviewMode
173
- ? isCorrectFill
174
- ? 'border-green-400 bg-green-50 text-green-800'
175
- : isWrongFill
176
- ? 'border-red-400 bg-red-50 text-red-800'
177
- : 'border-gray-300 bg-gray-50 text-gray-400'
178
- : isExpanded
179
- ? 'border-teal-500 bg-teal-50 text-teal-700 ring-2 ring-teal-200'
180
- : filled
181
- ? 'cursor-pointer border-teal-300 bg-teal-50 text-teal-700 hover:bg-teal-100'
182
- : 'cursor-pointer border-gray-300 bg-white text-gray-400 hover:border-teal-300 hover:bg-teal-50'), children: [filled || `(${blankIdx + 1})`, isReviewMode && filled && (isCorrectFill
183
- ? _jsx(Check, { className: "ml-1 h-3.5 w-3.5 text-green-600" })
184
- : _jsx(X, { className: "ml-1 h-3.5 w-3.5 text-red-600" }))] }), isExpanded && !isReviewMode && blankOptions.length > 0 && (_jsx("div", { className: "absolute left-1/2 top-full z-10 mt-1 flex -translate-x-1/2 gap-1 rounded-lg border border-teal-200 bg-white p-1.5 shadow-lg", children: blankOptions.map((opt, optIdx) => (_jsx("button", { type: "button", onClick: (e) => {
185
- e.stopPropagation();
186
- handleSelectOption(blankIdx, opt);
187
- setExpandedBlank(null);
188
- }, className: cn('whitespace-nowrap rounded-md px-2.5 py-1 text-sm font-medium transition-all', filled === opt
189
- ? 'bg-teal-500 text-white'
190
- : 'bg-gray-50 text-gray-700 hover:bg-teal-100 hover:text-teal-700'), children: opt }, optIdx))) }))] }, idx));
191
- }
192
- // ── WITH_WORD_BANK or DEFAULT blank ─────────────────────────
193
- if (isDefaultMode) {
194
- // DEFAULT: read-only inputs in review, otherwise show filled/empty state
195
- return (_jsxs("span", { className: cn('mx-1 inline-flex min-w-[80px] items-center justify-center rounded border-2 px-3 py-0.5 text-sm font-semibold align-middle', isReviewMode
148
+ return (_jsx("div", { className: "space-y-4", children: _jsxs(KidQuestionCard, { icon: _jsx(FileText, { className: "h-3.5 w-3.5" }), title: "\u0110i\u1EC1n t\u1EEB v\u00E0o \u0111o\u1EA1n v\u0103n", points: questionData.points, isExample: questionData.isExample, review: isReviewMode && allFilled ? (isAllCorrect ? 'correct' : 'wrong') : null, children: [title && (_jsx(KidPrompt, { children: _jsx("h2", { className: "text-sm font-semibold text-gray-800", children: title }) })), exampleEntries.length > 0 && (_jsxs("div", { className: "flex flex-wrap items-center gap-2 rounded-lg border border-amber-200 bg-amber-50 px-4 py-2.5", children: [_jsxs("div", { className: "flex items-center gap-1.5 text-xs font-semibold text-amber-700 shrink-0", children: [_jsx(Star, { className: "h-3.5 w-3.5 fill-amber-400 text-amber-400" }), "\u0110\u00E3 \u0111i\u1EC1n s\u1EB5n (Example):"] }), exampleEntries.map(({ blankIdx, answer }) => (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-md border border-amber-300 bg-white px-2 py-0.5 text-xs font-semibold text-amber-800", children: [_jsx("span", { className: "text-amber-500", children: '{' + blankIdx + '}' }), answer] }, blankIdx)))] })), (imagePreviewUrl || imageUrl) && (_jsx("div", { className: "flex justify-center", children: _jsx("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-gray-50", children: imageLoading ? (_jsx("div", { className: "flex h-48 w-48 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-teal-500" }) })) : (_jsx("img", { src: imagePreviewUrl || imageUrl, alt: title, className: "max-h-56 w-auto object-contain p-2" })) }) })), _jsxs("div", { className: (emailFrom || emailTo)
149
+ ? 'overflow-hidden rounded-xl border border-gray-300 bg-white shadow-sm'
150
+ : 'rounded-lg border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50', children: [(emailFrom || emailTo) && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center gap-1.5 border-b border-gray-200 bg-gray-100 px-4 py-2.5", children: [_jsx("span", { className: "h-3 w-3 rounded-full bg-red-400" }), _jsx("span", { className: "h-3 w-3 rounded-full bg-yellow-400" }), _jsx("span", { className: "h-3 w-3 rounded-full bg-green-400" }), _jsxs("div", { className: "ml-3 flex items-center gap-1.5 text-xs text-gray-400", children: [_jsx(Mail, { className: "h-3.5 w-3.5" }), _jsx("span", { children: "Email" })] })] }), _jsxs("div", { className: "border-b border-gray-200 bg-gray-50 px-3 py-2 space-y-1.5 text-sm", children: [emailFrom && (_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("span", { className: "w-10 shrink-0 text-xs font-semibold uppercase tracking-wide text-gray-400", children: "From" }), _jsx("span", { className: "font-medium text-gray-800", children: emailFrom })] })), emailTo && (_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("span", { className: "w-10 shrink-0 text-xs font-semibold uppercase tracking-wide text-gray-400", children: "To" }), _jsx("span", { className: "font-medium text-gray-800", children: emailTo })] }))] })] })), _jsx("div", { className: `text-base leading-loose text-gray-800 whitespace-pre-wrap ${(emailFrom || emailTo) ? 'p-5 bg-white' : 'p-5 bg-gradient-to-r from-gray-50 to-slate-50'}`, children: segments.map((segment, idx) => {
151
+ if (segment.type === 'text') {
152
+ return _jsx("span", { children: segment.value }, idx);
153
+ }
154
+ const blankIdx = segment.index;
155
+ const filled = filledAnswers[blankIdx];
156
+ const isCorrectFill = isReviewMode && filled === answers[blankIdx];
157
+ const isWrongFill = isReviewMode && !!filled && filled !== answers[blankIdx];
158
+ const example = isExampleBlank(blankIdx);
159
+ // ── Example blank: always shown pre-filled ──────────────────
160
+ if (example) {
161
+ return _jsx(ExampleBadge, { text: answers[blankIdx] || '___' }, idx);
162
+ }
163
+ // ── WITH_OPTIONS blank ──────────────────────────────────────
164
+ if (withOptions) {
165
+ const isExpanded = expandedBlank === blankIdx;
166
+ const blankOptions = wordBank[blankIdx]?.options || [];
167
+ return (_jsxs("span", { className: "relative mx-1 inline-flex flex-col items-center align-middle", children: [_jsxs("button", { type: "button", onClick: () => !isReviewMode && setExpandedBlank(isExpanded ? null : blankIdx), disabled: isReviewMode, className: cn('inline-flex min-w-[80px] items-center rounded-md border border-dashed px-3 py-1.5 text-center text-sm font-semibold transition-all', isReviewMode
196
168
  ? isCorrectFill
197
169
  ? 'border-green-400 bg-green-50 text-green-800'
198
170
  : isWrongFill
199
171
  ? 'border-red-400 bg-red-50 text-red-800'
200
- : 'border-dashed border-gray-300 bg-gray-50 text-gray-400'
201
- : filled
202
- ? 'border-indigo-300 bg-indigo-50 text-indigo-800'
203
- : 'border-dashed border-gray-300 bg-white text-gray-400'), children: [filled || `(${blankIdx + 1})`, isReviewMode && filled && (isCorrectFill
204
- ? _jsx(Check, { className: "ml-1 h-3.5 w-3.5 text-green-600" })
205
- : _jsx(X, { className: "ml-1 h-3.5 w-3.5 text-red-600" }))] }, idx));
206
- }
207
- // WITH_WORD_BANK input field
208
- return (_jsxs("span", { className: "mx-1 inline-flex items-center align-middle", children: [_jsx("input", { type: "text", value: filled || '', onChange: (e) => handleInputChange(blankIdx, e.target.value), disabled: isReviewMode, placeholder: `(${blankIdx + 1})`, className: cn('inline-flex min-w-[80px] max-w-[120px] rounded border-2 border-dashed px-3 py-0.5 text-center text-sm font-semibold transition-all outline-none', isReviewMode
209
- ? isCorrectFill
210
- ? 'border-green-400 bg-green-50 text-green-800'
211
- : isWrongFill
212
- ? 'border-red-400 bg-red-50 text-red-800'
213
- : 'border-gray-300 bg-gray-50 text-gray-400'
172
+ : 'border-gray-300 bg-gray-50 text-gray-400'
173
+ : isExpanded
174
+ ? 'border-teal-500 bg-teal-50 text-teal-700 ring-2 ring-teal-200'
214
175
  : filled
215
- ? 'border-teal-300 bg-teal-50 text-teal-700'
216
- : 'border-gray-300 bg-white text-gray-400 placeholder:text-gray-300', 'focus:border-teal-500 focus:bg-teal-50 focus:text-teal-700 focus:ring-2 focus:ring-teal-200', isReviewMode ? 'cursor-default' : 'cursor-text') }), isReviewMode && filled && (_jsx("span", { className: "ml-1", children: isCorrectFill
217
- ? _jsx(Check, { className: "h-3.5 w-3.5 text-green-600" })
218
- : _jsx(X, { className: "h-3.5 w-3.5 text-red-600" }) }))] }, idx));
219
- }) })] }), !withOptions && !isDefaultMode && (_jsxs("div", { className: "space-y-2", children: [_jsx("span", { className: "text-xs font-medium uppercase tracking-wider text-gray-500", children: "Word Bank (Reference)" }), _jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4", children: wordBank.map((item, index) => {
220
- const isUsed = usedWords.has(item.word);
221
- const isExampleWord = item.isExample;
222
- const hasImage = item.imageUrl && item.imageUrl.trim() !== '';
223
- return (_jsxs("div", { className: cn('flex flex-col items-center gap-2 rounded-xl border-2 p-3', isExampleWord
224
- ? 'border-amber-300 bg-amber-50'
225
- : isUsed
226
- ? 'border-emerald-200 bg-emerald-50 opacity-60'
227
- : 'border-gray-200 bg-gray-50'), children: [hasImage && _jsx(WordBankImage, { imageUrl: item.imageUrl }), _jsxs("div", { className: "flex flex-col items-center gap-1", children: [_jsx("span", { className: cn('text-sm font-semibold', isExampleWord ? 'text-amber-700' : isUsed ? 'text-emerald-700' : 'text-gray-700'), children: item.word }), isExampleWord && (_jsxs("span", { className: "flex items-center gap-0.5 rounded-full bg-amber-100 px-1.5 py-0.5 text-[10px] font-semibold text-amber-600", children: [_jsx(Star, { className: "h-2.5 w-2.5 fill-amber-400 text-amber-400" }), "example"] })), isUsed && !isExampleWord && (_jsx("span", { className: "flex items-center gap-0.5 rounded-full bg-emerald-100 px-1.5 py-0.5 text-[10px] font-semibold text-emerald-600", children: "used" }))] })] }, index));
228
- }) })] })), isReviewMode && !isAllCorrect && (_jsxs("div", { className: "rounded-lg border border-green-200 bg-green-50 p-3", children: [_jsx("span", { className: "text-xs font-medium uppercase tracking-wider text-green-600", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" }), _jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: answers.map((answer, index) => {
229
- if (isExampleBlank(index))
230
- return null;
231
- return (_jsxs("span", { className: "rounded-lg border border-green-300 bg-green-100 px-3 py-1.5 text-sm font-semibold text-green-800", children: ['{' + index + '}', ": ", answer] }, index));
232
- }) })] })), isReviewMode && displayExplanation && (_jsxs("div", { className: "rounded-lg border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-3", children: [_jsx("span", { className: "text-xs font-medium uppercase tracking-wider text-blue-600", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-blue-800", children: displayExplanation })] }))] })] }) }));
176
+ ? 'cursor-pointer border-teal-300 bg-teal-50 text-teal-700 hover:bg-teal-100'
177
+ : 'cursor-pointer border-gray-300 bg-white text-gray-400 hover:border-teal-300 hover:bg-teal-50'), children: [filled || `(${blankIdx + 1})`, isReviewMode && filled && (isCorrectFill
178
+ ? _jsx(Check, { className: "ml-1 h-3.5 w-3.5 text-green-600" })
179
+ : _jsx(X, { className: "ml-1 h-3.5 w-3.5 text-red-600" }))] }), isExpanded && !isReviewMode && blankOptions.length > 0 && (_jsx("div", { className: "absolute left-1/2 top-full z-10 mt-1 flex -translate-x-1/2 gap-1 rounded-lg border border-teal-200 bg-white p-1.5 shadow-lg", children: blankOptions.map((opt, optIdx) => (_jsx("button", { type: "button", onClick: (e) => {
180
+ e.stopPropagation();
181
+ handleSelectOption(blankIdx, opt);
182
+ setExpandedBlank(null);
183
+ }, className: cn('whitespace-nowrap rounded-md px-2.5 py-1 text-sm font-medium transition-all', filled === opt
184
+ ? 'bg-teal-500 text-white'
185
+ : 'bg-gray-50 text-gray-700 hover:bg-teal-100 hover:text-teal-700'), children: opt }, optIdx))) }))] }, idx));
186
+ }
187
+ // ── WITH_WORD_BANK or DEFAULT blank ─────────────────────────
188
+ if (isDefaultMode) {
189
+ // DEFAULT: read-only inputs in review, otherwise show filled/empty state
190
+ return (_jsxs("span", { className: cn('mx-1 inline-flex min-w-[80px] items-center justify-center rounded-md border px-3 py-1.5 text-sm font-semibold align-middle', isReviewMode
191
+ ? isCorrectFill
192
+ ? 'border-green-400 bg-green-50 text-green-800'
193
+ : isWrongFill
194
+ ? 'border-red-400 bg-red-50 text-red-800'
195
+ : 'border-dashed border-gray-300 bg-gray-50 text-gray-400'
196
+ : filled
197
+ ? 'border-indigo-300 bg-indigo-50 text-indigo-800'
198
+ : 'border-dashed border-gray-300 bg-white text-gray-400'), children: [filled || `(${blankIdx + 1})`, isReviewMode && filled && (isCorrectFill
199
+ ? _jsx(Check, { className: "ml-1 h-3.5 w-3.5 text-green-600" })
200
+ : _jsx(X, { className: "ml-1 h-3.5 w-3.5 text-red-600" }))] }, idx));
201
+ }
202
+ // WITH_WORD_BANK input field
203
+ return (_jsxs("span", { className: "mx-1 inline-flex items-center align-middle", children: [_jsx("input", { type: "text", value: filled || '', onChange: (e) => handleInputChange(blankIdx, e.target.value), disabled: isReviewMode, placeholder: `(${blankIdx + 1})`, className: cn('inline-flex min-w-[80px] max-w-[120px] rounded-md border border-dashed px-3 py-1.5 text-center text-sm font-semibold transition-all outline-none', isReviewMode
204
+ ? isCorrectFill
205
+ ? 'border-green-400 bg-green-50 text-green-800'
206
+ : isWrongFill
207
+ ? 'border-red-400 bg-red-50 text-red-800'
208
+ : 'border-gray-300 bg-gray-50 text-gray-400'
209
+ : filled
210
+ ? 'border-teal-300 bg-teal-50 text-teal-700'
211
+ : 'border-gray-300 bg-white text-gray-400 placeholder:text-gray-300', 'focus:border-teal-500 focus:bg-teal-50 focus:text-teal-700 focus:ring-2 focus:ring-teal-200', isReviewMode ? 'cursor-default' : 'cursor-text') }), isReviewMode && filled && (_jsx("span", { className: "ml-1", children: isCorrectFill
212
+ ? _jsx(Check, { className: "h-3.5 w-3.5 text-green-600" })
213
+ : _jsx(X, { className: "h-3.5 w-3.5 text-red-600" }) }))] }, idx));
214
+ }) })] }), !withOptions && !isDefaultMode && (_jsxs("div", { className: "space-y-2", children: [_jsx("span", { className: "text-xs font-medium uppercase tracking-wider text-gray-500", children: "Word Bank (Reference)" }), _jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4", children: wordBank.map((item, index) => {
215
+ const isUsed = usedWords.has(item.word);
216
+ const isExampleWord = item.isExample;
217
+ const hasImage = item.imageUrl && item.imageUrl.trim() !== '';
218
+ return (_jsxs("div", { className: cn('flex flex-col items-center gap-2 rounded-xl border-2 p-3', isExampleWord
219
+ ? 'border-amber-300 bg-amber-50'
220
+ : isUsed
221
+ ? 'border-emerald-200 bg-emerald-50 opacity-60'
222
+ : 'border-gray-200 bg-gray-50'), children: [hasImage && _jsx(WordBankImage, { imageUrl: item.imageUrl }), _jsxs("div", { className: "flex flex-col items-center gap-1", children: [_jsx("span", { className: cn('text-sm font-semibold', isExampleWord ? 'text-amber-700' : isUsed ? 'text-emerald-700' : 'text-gray-700'), children: item.word }), isExampleWord && (_jsxs("span", { className: "flex items-center gap-0.5 rounded-full bg-amber-100 px-1.5 py-0.5 text-[10px] font-semibold text-amber-600", children: [_jsx(Star, { className: "h-2.5 w-2.5 fill-amber-400 text-amber-400" }), "example"] })), isUsed && !isExampleWord && (_jsx("span", { className: "flex items-center gap-0.5 rounded-full bg-emerald-100 px-1.5 py-0.5 text-[10px] font-semibold text-emerald-600", children: "used" }))] })] }, index));
223
+ }) })] })), isReviewMode && !isAllCorrect && (_jsxs("div", { className: "rounded-lg border border-green-200 bg-green-50 p-3", children: [_jsx("span", { className: "text-xs font-medium uppercase tracking-wider text-green-600", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" }), _jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: answers.map((answer, index) => {
224
+ if (isExampleBlank(index))
225
+ return null;
226
+ return (_jsxs("span", { className: "rounded-lg border border-green-300 bg-green-100 px-3 py-1.5 text-sm font-semibold text-green-800", children: ['{' + index + '}', ": ", answer] }, index));
227
+ }) })] })), isReviewMode && displayExplanation && (_jsxs("div", { className: "rounded-lg border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-3", children: [_jsx("span", { className: "text-xs font-medium uppercase tracking-wider text-blue-600", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-blue-800", children: displayExplanation })] }))] }) }));
233
228
  }