@tinyweb_dev/oe-exam-sdk 1.0.9 → 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 (104) hide show
  1. package/dist/components/exams/take/components/QuestionRenderer.js +1 -5
  2. package/dist/components/exams/take/components/question-renderers/MoversCrossOutWordGroupRenderer.js +24 -10
  3. package/dist/components/exams/take/components/question-renderers/MoversCrosswordPuzzleRenderer.js +1 -1
  4. package/dist/components/exams/take/components/question-renderers/MoversFillInBlankGroupRenderer.js +2 -1
  5. package/dist/components/exams/take/components/speaking/renderers/SpeakingSpontaneousQaGroupRenderer.js +12 -2
  6. package/dist/components/exams/take/hooks/useSpontaneousQaGroupRendererSetup.js +2 -1
  7. package/dist/components/exams/take/utils/answer-transformers.js +16 -1
  8. package/dist/components/exams/take/utils/cross-out-word-group.transformer.js +4 -6
  9. package/dist/components/exams/take/utils/question-transformers.d.ts +5 -0
  10. package/dist/components/exams/take/utils/question-transformers.js +6 -0
  11. package/dist/components/questions/_shared/components/compact/KidChoiceRow.d.ts +12 -0
  12. package/dist/components/questions/_shared/components/compact/KidChoiceRow.js +33 -0
  13. package/dist/components/questions/_shared/components/compact/KidInstruction.d.ts +5 -0
  14. package/dist/components/questions/_shared/components/compact/KidInstruction.js +9 -0
  15. package/dist/components/questions/_shared/components/compact/KidPrompt.d.ts +5 -0
  16. package/dist/components/questions/_shared/components/compact/KidPrompt.js +9 -0
  17. package/dist/components/questions/_shared/components/compact/KidQuestionCard.d.ts +12 -0
  18. package/dist/components/questions/_shared/components/compact/KidQuestionCard.js +7 -0
  19. package/dist/components/questions/_shared/components/compact/index.d.ts +4 -0
  20. package/dist/components/questions/_shared/components/compact/index.js +4 -0
  21. package/dist/components/questions/_shared/types/crossword-puzzle.type.d.ts +5 -1
  22. package/dist/components/questions/_shared/types/spontaneous-qa-group.type.d.ts +17 -0
  23. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +18 -19
  24. package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js +8 -7
  25. package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupCreator.js +5 -4
  26. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +17 -11
  27. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +15 -42
  28. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +15 -36
  29. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +9 -30
  30. package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js +8 -7
  31. package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.js +50 -32
  32. package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupCreator.js +24 -44
  33. package/dist/components/questions/types/cross-out-word-group/index.d.ts +1 -0
  34. package/dist/components/questions/types/cross-out-word-group/index.js +1 -0
  35. package/dist/components/questions/types/cross-out-word-group/map-cross-out-word-group-data.js +9 -9
  36. package/dist/components/questions/types/cross-out-word-group/tokenize.d.ts +2 -0
  37. package/dist/components/questions/types/cross-out-word-group/tokenize.js +11 -0
  38. package/dist/components/questions/types/cross-out-word-group/transform.js +2 -4
  39. package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.d.ts +2 -1
  40. package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.js +8 -6
  41. package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleCreator.js +68 -11
  42. package/dist/components/questions/types/crossword-puzzle/transform.js +59 -30
  43. package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +26 -31
  44. package/dist/components/questions/types/fill-in-blank-group/FillInBlankAnswerKey.d.ts +6 -0
  45. package/dist/components/questions/types/fill-in-blank-group/FillInBlankAnswerKey.js +17 -0
  46. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupClient.js +32 -28
  47. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.js +109 -26
  48. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupMediaField.js +21 -11
  49. package/dist/components/questions/types/fill-in-blank-group/blank-utils.d.ts +1 -0
  50. package/dist/components/questions/types/fill-in-blank-group/blank-utils.js +4 -1
  51. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +32 -31
  52. package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.js +71 -28
  53. package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixCreator.js +6 -2
  54. package/dist/components/questions/types/find-words-in-matrix/transform.js +5 -0
  55. package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +4 -3
  56. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +11 -30
  57. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +7 -6
  58. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +2 -1
  59. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +14 -13
  60. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +4 -3
  61. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +4 -3
  62. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +11 -10
  63. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +4 -3
  64. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +13 -11
  65. package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.js +17 -14
  66. package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureClient.js +16 -12
  67. package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureGroupClient.js +14 -14
  68. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js +13 -13
  69. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupClient.js +7 -6
  70. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +8 -26
  71. package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesClient.js +2 -1
  72. package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +6 -5
  73. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardClient.js +2 -1
  74. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +5 -4
  75. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.d.ts +1 -1
  76. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.js +54 -11
  77. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupCreator.js +11 -7
  78. package/dist/components/questions/types/spontaneous-qa-group/creator-helpers.js +8 -2
  79. package/dist/components/questions/types/spontaneous-qa-group/flatten.d.ts +1 -0
  80. package/dist/components/questions/types/spontaneous-qa-group/flatten.js +4 -2
  81. package/dist/components/questions/types/spontaneous-qa-group/map-spontaneous-qa-group-data.js +5 -3
  82. package/dist/components/questions/types/spontaneous-qa-group/register.js +5 -2
  83. package/dist/components/questions/types/spontaneous-qa-group/transform.js +6 -2
  84. package/dist/components/questions/types/true-false/TrueFalseClient.js +5 -54
  85. package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.js +21 -23
  86. package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.js +8 -17
  87. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +76 -81
  88. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +17 -16
  89. package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupClient.js +31 -36
  90. package/dist/components/questions/types/word-ordering/WordOrderingClient.js +15 -14
  91. package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.js +9 -8
  92. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +16 -13
  93. package/dist/components/questions/viewer/CrossOutWordGroupViewer.js +1 -4
  94. package/dist/components/questions/viewer/FillInBlankGroupViewer.d.ts +6 -2
  95. package/dist/components/questions/viewer/FillInBlankGroupViewer.js +13 -7
  96. package/dist/components/questions/viewer/QuestionViewer.js +2 -2
  97. package/dist/components/questions/viewer/SpontaneousQaGroupViewer.d.ts +7 -1
  98. package/dist/components/questions/viewer/SpontaneousQaGroupViewer.js +14 -2
  99. package/dist/components/results/renderers/review-question-body-movers.js +1 -1
  100. package/dist/components/results/review-data.js +8 -1
  101. package/dist/shared/lib/utils/question-reverse-transform.js +31 -5
  102. package/dist/shared/types/questions/crossword-puzzle.d.ts +1 -0
  103. package/dist/shared/types/questions/find-words-in-matrix.d.ts +1 -0
  104. package/package.json +1 -1
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Mic, Volume2, CheckCircle2, Play, Pause, ListChecks, Info } from 'lucide-react';
4
4
  import { Button } from '../../../../components/ui/button';
5
5
  import { usePresignedFileUrl, useAudioPreview } from '../../../../shared/lib/hooks';
6
+ import { KidQuestionCard } from '../../_shared/components/compact';
6
7
  import { cn } from '../../../../shared/lib/utils';
7
8
  // ─── Sub-component: Custom Audio Player ──────────────────────────────────────
8
9
  function QuestionAudioPlayer({ audioUrl }) {
@@ -13,14 +14,14 @@ function QuestionAudioPlayer({ audioUrl }) {
13
14
  // ─── Main Client Component ───────────────────────────────────────────────────
14
15
  export function ListenAndSpeakQuestionListClient({ questionData, isReviewMode = false, }) {
15
16
  const { audioUrl, expectedAnswers, activeQuestionId, questions = [], explanation } = questionData;
16
- return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsx("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gray-50 px-3 py-2", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100 text-gray-600", children: _jsx(ListChecks, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 n\u00F3i (Danh s\u00E1ch c\u00E2u h\u1ECFi)" })] }) }), _jsxs("div", { className: "p-3 space-y-3", children: [audioUrl ? (_jsx(QuestionAudioPlayer, { audioUrl: audioUrl })) : (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3", children: [_jsx(Volume2, { className: "h-4 w-4 text-gray-400" }), _jsx("span", { className: "text-xs text-gray-400", children: "Ch\u01B0a c\u00F3 audio c\u00E2u h\u1ECFi" })] })), questions.length > 0 ? (_jsx("div", { className: "rounded-xl border border-gray-100 bg-gradient-to-b from-gray-50/80 to-white p-5 space-y-2.5", children: questions.map((q, idx) => {
17
- const isActive = q.id === activeQuestionId;
18
- return (_jsxs("div", { className: cn('flex items-center gap-3 rounded-lg px-4 py-3 transition-all duration-200', isActive
19
- ? 'bg-blue-50 border-[2px] border-blue-300 shadow-sm shadow-blue-100/50'
20
- : 'bg-white border border-gray-200 hover:border-gray-300 hover:shadow-sm'), children: [_jsx("div", { className: cn('flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-lg text-xs font-bold shadow-sm', isActive
21
- ? 'bg-blue-500 text-white'
22
- : 'bg-gray-100 text-gray-500 border border-gray-200'), children: idx + 1 }), _jsx("span", { className: cn('text-sm font-medium flex-1', isActive ? 'text-blue-800' : 'text-gray-700'), children: q.text || _jsx("span", { className: "italic text-gray-400", children: "(Ch\u01B0a nh\u1EADp n\u1ED9i dung)" }) }), isActive && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "text-[10px] font-medium text-blue-500 animate-pulse", children: "\u0110ang h\u1ECFi" }), _jsx(CheckCircle2, { className: "h-4 w-4 text-blue-500" })] }))] }, q.id));
23
- }) })) : (
24
- /* Empty state */
25
- _jsxs("div", { className: "flex flex-col items-center justify-center py-10 px-4 rounded-xl border-2 border-dashed border-gray-200 bg-gray-50/50", children: [_jsx(ListChecks, { className: "h-12 w-12 text-gray-300 mb-3" }), _jsx("p", { className: "text-sm font-medium text-gray-500", children: "Ch\u01B0a c\u00F3 c\u00E2u h\u1ECFi n\u00E0o" }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: "Danh s\u00E1ch c\u00E2u h\u1ECFi s\u1EBD hi\u1EC3n th\u1ECB t\u1EA1i \u0111\u00E2y" })] })), _jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-dashed border-gray-300 bg-gray-50/80 px-4 py-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-50 ring-2 ring-indigo-100/50", children: _jsx(Mic, { className: "w-4 h-4 text-indigo-400" }) }), _jsx("p", { className: "text-xs text-gray-400", children: "H\u1ECDc sinh s\u1EBD nh\u1EA5n n\u00FAt ghi \u00E2m v\u00E0 n\u00F3i c\u00E2u tr\u1EA3 l\u1EDDi t\u1EA1i \u0111\u00E2y" })] }), isReviewMode && expectedAnswers && expectedAnswers.length > 0 && (_jsxs("div", { className: "rounded-xl border border-green-200 bg-green-50/80 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-green-600" }), _jsx("h4", { className: "text-sm font-semibold text-green-800", children: "C\u00E1c \u0111\u00E1p \u00E1n mong mu\u1ED1n" })] }), _jsx("div", { className: "space-y-2", children: expectedAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-center gap-2.5 rounded-lg bg-white/70 border border-green-100 px-3 py-2", children: [_jsx("span", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-green-100 text-green-700 text-xs font-bold", children: index + 1 }), _jsx("span", { className: "text-sm text-green-800", children: answer })] }, index))) })] })), isReviewMode && explanation && (_jsxs("div", { className: "rounded-xl border border-indigo-200 bg-indigo-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx(Info, { className: "h-4 w-4 text-indigo-600" }), _jsx("p", { className: "text-sm font-semibold text-indigo-800", children: "Gi\u1EA3i th\u00EDch" })] }), _jsx("p", { className: "text-sm text-indigo-700 leading-relaxed", children: explanation })] }))] })] })] }));
17
+ return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs(KidQuestionCard, { title: "Nghe v\u00E0 n\u00F3i (Danh s\u00E1ch c\u00E2u h\u1ECFi)", icon: _jsx(ListChecks, { className: "h-3.5 w-3.5" }), children: [audioUrl ? (_jsx(QuestionAudioPlayer, { audioUrl: audioUrl })) : (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3", children: [_jsx(Volume2, { className: "h-4 w-4 text-gray-400" }), _jsx("span", { className: "text-xs text-gray-400", children: "Ch\u01B0a c\u00F3 audio c\u00E2u h\u1ECFi" })] })), questions.length > 0 ? (_jsx("div", { className: "rounded-xl border border-gray-100 bg-gradient-to-b from-gray-50/80 to-white p-5 space-y-2.5", children: questions.map((q, idx) => {
18
+ const isActive = q.id === activeQuestionId;
19
+ return (_jsxs("div", { className: cn('flex items-center gap-3 rounded-lg px-4 py-3 transition-all duration-200', isActive
20
+ ? 'bg-blue-50 border-[2px] border-blue-300 shadow-sm shadow-blue-100/50'
21
+ : 'bg-white border border-gray-200 hover:border-gray-300 hover:shadow-sm'), children: [_jsx("div", { className: cn('flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-lg text-xs font-bold shadow-sm', isActive
22
+ ? 'bg-blue-500 text-white'
23
+ : 'bg-gray-100 text-gray-500 border border-gray-200'), children: idx + 1 }), _jsx("span", { className: cn('text-sm font-medium flex-1', isActive ? 'text-blue-800' : 'text-gray-700'), children: q.text || _jsx("span", { className: "italic text-gray-400", children: "(Ch\u01B0a nh\u1EADp n\u1ED9i dung)" }) }), isActive && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "text-[10px] font-medium text-blue-500 animate-pulse", children: "\u0110ang h\u1ECFi" }), _jsx(CheckCircle2, { className: "h-4 w-4 text-blue-500" })] }))] }, q.id));
24
+ }) })) : (
25
+ /* Empty state */
26
+ _jsxs("div", { className: "flex flex-col items-center justify-center py-10 px-4 rounded-xl border-2 border-dashed border-gray-200 bg-gray-50/50", children: [_jsx(ListChecks, { className: "h-12 w-12 text-gray-300 mb-3" }), _jsx("p", { className: "text-sm font-medium text-gray-500", children: "Ch\u01B0a c\u00F3 c\u00E2u h\u1ECFi n\u00E0o" }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: "Danh s\u00E1ch c\u00E2u h\u1ECFi s\u1EBD hi\u1EC3n th\u1ECB t\u1EA1i \u0111\u00E2y" })] })), _jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-dashed border-gray-300 bg-gray-50/80 px-4 py-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-50 ring-2 ring-indigo-100/50", children: _jsx(Mic, { className: "w-4 h-4 text-indigo-400" }) }), _jsx("p", { className: "text-xs text-gray-400", children: "H\u1ECDc sinh s\u1EBD nh\u1EA5n n\u00FAt ghi \u00E2m v\u00E0 n\u00F3i c\u00E2u tr\u1EA3 l\u1EDDi t\u1EA1i \u0111\u00E2y" })] }), isReviewMode && expectedAnswers && expectedAnswers.length > 0 && (_jsxs("div", { className: "rounded-xl border border-green-200 bg-green-50/80 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-green-600" }), _jsx("h4", { className: "text-sm font-semibold text-green-800", children: "C\u00E1c \u0111\u00E1p \u00E1n mong mu\u1ED1n" })] }), _jsx("div", { className: "space-y-2", children: expectedAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-center gap-2.5 rounded-lg bg-white/70 border border-green-100 px-3 py-2", children: [_jsx("span", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-green-100 text-green-700 text-xs font-bold", children: index + 1 }), _jsx("span", { className: "text-sm text-green-800", children: answer })] }, index))) })] })), isReviewMode && explanation && (_jsxs("div", { className: "rounded-xl border border-indigo-200 bg-indigo-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx(Info, { className: "h-4 w-4 text-indigo-600" }), _jsx("p", { className: "text-sm font-semibold text-indigo-800", children: "Gi\u1EA3i th\u00EDch" })] }), _jsx("p", { className: "text-sm text-indigo-700 leading-relaxed", children: explanation })] }))] })] }));
26
27
  }
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Mic, Volume2, CheckCircle2, Play, Pause, ImageIcon, Info, BookImage } from 'lucide-react';
4
4
  import { Button } from '../../../../components/ui/button';
5
+ import { KidQuestionCard } from '../../_shared/components/compact';
5
6
  import { usePresignedFileUrl, useAudioPreview } from '../../../../shared/lib/hooks';
6
7
  // ─── Sub-component: Custom Audio Player ──────────────────────────────────────
7
8
  function QuestionAudioPlayer({ audioUrl }) {
@@ -20,7 +21,7 @@ function StoryImagePreview({ imageUrl, index }) {
20
21
  // ─── Main Client Component ───────────────────────────────────────────────────
21
22
  export function ListenAndSpeakWithStoryImagesClient({ questionData, isReviewMode = false, }) {
22
23
  const { audioUrl, images, expectedAnswers = [], explanation } = questionData;
23
- return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsx("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gray-50 px-3 py-2", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100 text-gray-600", children: _jsx(BookImage, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 n\u00F3i (K\u1EC3 chuy\u1EC7n theo h\u00ECnh)" })] }) }), _jsxs("div", { className: "p-3 space-y-3", children: [audioUrl ? (_jsx(QuestionAudioPlayer, { audioUrl: audioUrl })) : (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3", children: [_jsx(Volume2, { className: "h-4 w-4 text-gray-400" }), _jsx("span", { className: "text-xs text-gray-400", children: "Ch\u01B0a c\u00F3 audio c\u00E2u h\u1ECFi" })] })), images && images.length > 0 && (_jsx("div", { className: "bg-gradient-to-br from-purple-50/80 to-fuchsia-50/60 rounded-2xl p-6 border border-purple-100/80 shadow-sm", children: _jsx("div", { className: "grid gap-4", style: {
24
- gridTemplateColumns: `repeat(${Math.min(images.length, 4)}, 1fr)`,
25
- }, children: images.map((imgUrl, index) => (_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "flex items-center gap-1.5", children: _jsx("span", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-purple-200 text-purple-800 text-xs font-bold", children: index + 1 }) }), _jsx("div", { className: "relative aspect-square rounded-xl overflow-hidden border-[3px] border-purple-300 bg-white shadow-md ring-2 ring-purple-100", children: imgUrl ? (_jsx(StoryImagePreview, { imageUrl: imgUrl, index: index })) : (_jsx("div", { className: "w-full h-full flex items-center justify-center text-gray-400", children: _jsx(ImageIcon, { className: "h-6 w-6" }) })) })] }, index))) }) })), _jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-dashed border-gray-300 bg-gray-50/80 px-4 py-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-50 ring-2 ring-indigo-100/50", children: _jsx(Mic, { className: "w-4 h-4 text-indigo-400" }) }), _jsx("p", { className: "text-xs text-gray-400", children: "H\u1ECDc sinh s\u1EBD nh\u1EA5n n\u00FAt ghi \u00E2m v\u00E0 n\u00F3i c\u00E2u tr\u1EA3 l\u1EDDi t\u1EA1i \u0111\u00E2y" })] }), isReviewMode && expectedAnswers.length > 0 && (_jsxs("div", { className: "rounded-xl border border-green-200 bg-green-50/80 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-green-600" }), _jsx("h4", { className: "text-sm font-semibold text-green-800", children: "C\u00E1c \u0111\u00E1p \u00E1n mong mu\u1ED1n" })] }), _jsx("div", { className: "space-y-2", children: expectedAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-center gap-2.5 rounded-lg bg-white/70 border border-green-100 px-3 py-2", children: [_jsx("span", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-green-100 text-green-700 text-xs font-bold", children: index + 1 }), _jsx("span", { className: "text-sm text-green-800", children: answer })] }, index))) })] })), isReviewMode && explanation && (_jsxs("div", { className: "rounded-xl border border-indigo-200 bg-indigo-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx(Info, { className: "h-4 w-4 text-indigo-600" }), _jsx("p", { className: "text-sm font-semibold text-indigo-800", children: "Gi\u1EA3i th\u00EDch" })] }), _jsx("p", { className: "text-sm text-indigo-700 leading-relaxed", children: explanation })] }))] })] })] }));
24
+ return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs(KidQuestionCard, { title: "Nghe v\u00E0 n\u00F3i (K\u1EC3 chuy\u1EC7n theo h\u00ECnh)", icon: _jsx(BookImage, { className: "h-3.5 w-3.5" }), children: [audioUrl ? (_jsx(QuestionAudioPlayer, { audioUrl: audioUrl })) : (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3", children: [_jsx(Volume2, { className: "h-4 w-4 text-gray-400" }), _jsx("span", { className: "text-xs text-gray-400", children: "Ch\u01B0a c\u00F3 audio c\u00E2u h\u1ECFi" })] })), images && images.length > 0 && (_jsx("div", { className: "bg-gradient-to-br from-purple-50/80 to-fuchsia-50/60 rounded-2xl p-6 border border-purple-100/80 shadow-sm", children: _jsx("div", { className: "grid gap-4", style: {
25
+ gridTemplateColumns: `repeat(${Math.min(images.length, 4)}, 1fr)`,
26
+ }, children: images.map((imgUrl, index) => (_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "flex items-center gap-1.5", children: _jsx("span", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-purple-200 text-purple-800 text-xs font-bold", children: index + 1 }) }), _jsx("div", { className: "relative aspect-square rounded-xl overflow-hidden border-[3px] border-purple-300 bg-white shadow-md ring-2 ring-purple-100", children: imgUrl ? (_jsx(StoryImagePreview, { imageUrl: imgUrl, index: index })) : (_jsx("div", { className: "w-full h-full flex items-center justify-center text-gray-400", children: _jsx(ImageIcon, { className: "h-6 w-6" }) })) })] }, index))) }) })), _jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-dashed border-gray-300 bg-gray-50/80 px-4 py-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-50 ring-2 ring-indigo-100/50", children: _jsx(Mic, { className: "w-4 h-4 text-indigo-400" }) }), _jsx("p", { className: "text-xs text-gray-400", children: "H\u1ECDc sinh s\u1EBD nh\u1EA5n n\u00FAt ghi \u00E2m v\u00E0 n\u00F3i c\u00E2u tr\u1EA3 l\u1EDDi t\u1EA1i \u0111\u00E2y" })] }), isReviewMode && expectedAnswers.length > 0 && (_jsxs("div", { className: "rounded-xl border border-green-200 bg-green-50/80 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-green-600" }), _jsx("h4", { className: "text-sm font-semibold text-green-800", children: "C\u00E1c \u0111\u00E1p \u00E1n mong mu\u1ED1n" })] }), _jsx("div", { className: "space-y-2", children: expectedAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-center gap-2.5 rounded-lg bg-white/70 border border-green-100 px-3 py-2", children: [_jsx("span", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-green-100 text-green-700 text-xs font-bold", children: index + 1 }), _jsx("span", { className: "text-sm text-green-800", children: answer })] }, index))) })] })), isReviewMode && explanation && (_jsxs("div", { className: "rounded-xl border border-indigo-200 bg-indigo-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx(Info, { className: "h-4 w-4 text-indigo-600" }), _jsx("p", { className: "text-sm font-semibold text-indigo-800", children: "Gi\u1EA3i th\u00EDch" })] }), _jsx("p", { className: "text-sm text-indigo-700 leading-relaxed", children: explanation })] }))] })] }));
26
27
  }
@@ -1,11 +1,11 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Card, CardContent } from '../../../../components/ui/card';
4
3
  import { Input } from '../../../../components/ui/input';
5
4
  import { Label } from '../../../../components/ui/label';
6
- import { CheckCircle2, XCircle, BookOpen, Grid3X3 } from 'lucide-react';
5
+ import { CheckCircle2, XCircle, Grid3X3 } from 'lucide-react';
7
6
  import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
8
7
  import { useState } from 'react';
8
+ import { KidQuestionCard } from '../../_shared/components/compact';
9
9
  function OptionCard({ option }) {
10
10
  const { previewUrl } = usePresignedFileUrl(option.imageUrl || '');
11
11
  const displayUrl = previewUrl || option.imageUrl;
@@ -18,7 +18,7 @@ export function MatchByWritingAnswerClient({ questionData, onSubmit, isReviewMod
18
18
  const [answer, setAnswer] = useState(userAnswer || '');
19
19
  const { previewUrl: questionImageUrl } = usePresignedFileUrl(questionData.imageUrl || '');
20
20
  if (!questionData.question) {
21
- return (_jsx(Card, { children: _jsx(CardContent, { className: "p-6", children: _jsx("p", { className: "text-gray-500", children: "Kh\u00F4ng c\u00F3 d\u1EEF li\u1EC7u c\u00E2u h\u1ECFi" }) }) }));
21
+ return (_jsx(KidQuestionCard, { title: "Gh\u00E9p \u0111\u00E1p \u00E1n b\u1EB1ng c\u00E1ch vi\u1EBFt", children: _jsx("p", { className: "text-gray-500", children: "Kh\u00F4ng c\u00F3 d\u1EEF li\u1EC7u c\u00E2u h\u1ECFi" }) }));
22
22
  }
23
23
  const isExample = questionData.isExample || false;
24
24
  const displayedAnswer = autoFillCorrectAnswers || isExample
@@ -27,12 +27,14 @@ export function MatchByWritingAnswerClient({ questionData, onSubmit, isReviewMod
27
27
  const isCorrect = isReviewMode && userAnswer !== undefined
28
28
  ? userAnswer.trim().toLowerCase() === questionData.correctAnswer.trim().toLowerCase()
29
29
  : undefined;
30
- return (_jsxs(Card, { className: "overflow-hidden border border-gray-200 bg-white shadow-sm", children: [_jsx("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-md bg-gradient-to-br from-teal-500 to-cyan-600 text-white", children: _jsx(Grid3X3, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Gh\u00E9p \u0111\u00E1p \u00E1n b\u1EB1ng c\u00E1ch vi\u1EBFt" })] }) }), _jsxs(CardContent, { className: "space-y-3 p-3", children: [questionData.options && questionData.options.length > 0 && (_jsxs("div", { className: "rounded-xl border border-purple-200 bg-gradient-to-r from-purple-50 to-violet-50 p-4", children: [_jsx("p", { className: "mb-3 text-sm font-semibold text-purple-700", children: "C\u00E1c l\u1EF1a ch\u1ECDn:" }), _jsx("div", { className: `grid gap-3 ${questionData.options.some((o) => o.imageUrl) ? 'grid-cols-2 sm:grid-cols-3 md:grid-cols-4' : 'grid-cols-1 sm:grid-cols-2'}`, children: questionData.options.map((option, index) => (_jsx(OptionCard, { option: option }, index))) })] })), _jsxs("div", { className: "rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-5", children: [isExample && (_jsxs("div", { className: "mb-2 inline-flex items-center gap-1.5 rounded-full bg-amber-100 border border-amber-300 px-3 py-1", children: [_jsx(BookOpen, { className: "h-3.5 w-3.5 text-amber-600" }), _jsx("span", { className: "text-xs font-semibold text-amber-700 uppercase tracking-wide", children: "Example" })] })), _jsxs("div", { className: "flex items-center gap-4", children: [(questionImageUrl || questionData.imageUrl) ? (_jsx("div", { className: "relative h-20 w-20 flex-shrink-0 overflow-hidden rounded-lg border border-gray-200 bg-gray-50 shadow-sm", children: _jsx("img", { src: questionImageUrl || questionData.imageUrl, alt: questionData.question, className: "h-full w-full object-contain" }) })) : null, _jsxs("div", { className: "flex-1 space-y-2", children: [_jsx(Label, { className: "text-base font-semibold text-gray-800", children: questionData.question }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Input, { value: displayedAnswer, onChange: (e) => {
31
- setAnswer(e.target.value);
32
- onSubmit?.(e.target.value);
33
- }, placeholder: "Vi\u1EBFt \u0111\u00E1p \u00E1n...", disabled: isReviewMode || isExample, className: `max-w-xs border-2 text-lg font-medium ${isReviewMode
34
- ? isCorrect
35
- ? 'border-green-400 bg-green-50 text-green-700'
36
- : 'border-red-400 bg-red-50 text-red-700'
37
- : 'border-teal-300 focus:border-teal-500 focus:ring-teal-200'}` }), isReviewMode && isCorrect !== undefined && (_jsx("span", { className: "flex items-center gap-1", children: isCorrect ? (_jsx(CheckCircle2, { className: "h-5 w-5 text-green-500" })) : (_jsx(XCircle, { className: "h-5 w-5 text-red-500" })) }))] })] })] }), isReviewMode && isCorrect === false && (_jsxs("div", { className: "mt-3 flex items-center gap-2 rounded-lg bg-green-50 border border-green-200 px-4 py-2", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-green-600" }), _jsxs("span", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsx("strong", { children: questionData.correctAnswer })] })] })), autoFillCorrectAnswers && (_jsxs("div", { className: "mt-3 flex items-center gap-2 rounded-lg bg-teal-50 border border-teal-200 px-4 py-2", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-teal-600" }), _jsxs("span", { className: "text-sm text-teal-700", children: ["\u0110\u00E1p \u00E1n: ", _jsx("strong", { children: questionData.correctAnswer })] })] }))] }), explanation && isReviewMode && (_jsxs("div", { className: "rounded-xl border border-blue-200 bg-blue-50 p-4", children: [_jsx("p", { className: "text-sm font-semibold text-blue-800 mb-1", children: "Gi\u1EA3i th\u00EDch:" }), _jsx("p", { className: "text-sm text-blue-700", children: explanation })] }))] })] }));
30
+ return (_jsxs(KidQuestionCard, { icon: _jsx(Grid3X3, { className: "h-3.5 w-3.5" }), title: "Gh\u00E9p \u0111\u00E1p \u00E1n b\u1EB1ng c\u00E1ch vi\u1EBFt", isExample: isExample, review: isReviewMode && isCorrect !== undefined ? (isCorrect ? 'correct' : 'wrong') : null, children: [questionData.options && questionData.options.length > 0 && (_jsxs("div", { className: "rounded-xl border border-purple-200 bg-gradient-to-r from-purple-50 to-violet-50 p-4", children: [_jsx("p", { className: "mb-3 text-sm font-semibold text-purple-700", children: "C\u00E1c l\u1EF1a ch\u1ECDn:" }), _jsx("div", { className: `grid gap-3 ${questionData.options.some((o) => o.imageUrl) ? 'grid-cols-2 sm:grid-cols-3 md:grid-cols-4' : 'grid-cols-1 sm:grid-cols-2'}`, children: questionData.options.map((option, index) => (_jsx(OptionCard, { option: option }, index))) })] })), _jsxs("div", { className: "rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-5", children: [_jsxs("div", { className: "flex items-center gap-4", children: [(questionImageUrl || questionData.imageUrl) ? (_jsx("div", { className: "relative h-20 w-20 flex-shrink-0 overflow-hidden rounded-lg border border-gray-200 bg-gray-50 shadow-sm", children: _jsx("img", { src: questionImageUrl || questionData.imageUrl, alt: questionData.question, className: "h-full w-full object-contain" }) })) : null, _jsxs("div", { className: "flex-1 space-y-2", children: [_jsx(Label, { className: "text-base font-semibold text-gray-800", children: questionData.question }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Input, { value: displayedAnswer, onChange: (e) => {
31
+ setAnswer(e.target.value);
32
+ onSubmit?.(e.target.value);
33
+ }, placeholder: "Vi\u1EBFt \u0111\u00E1p \u00E1n...", disabled: isReviewMode || isExample, className: `max-w-xs border-2 text-lg font-medium ${isReviewMode
34
+ ? isCorrect
35
+ ? 'border-green-400 bg-green-50 text-green-700'
36
+ : 'border-red-400 bg-red-50 text-red-700'
37
+ : displayedAnswer
38
+ ? 'border-blue-500 bg-blue-50 ring-2 ring-blue-100 focus:border-blue-500 focus:ring-blue-100'
39
+ : 'border-gray-200 bg-white focus:border-blue-500 focus:ring-blue-100'}` }), isReviewMode && isCorrect !== undefined && (_jsx("span", { className: "flex items-center gap-1", children: isCorrect ? (_jsx(CheckCircle2, { className: "h-5 w-5 text-green-500" })) : (_jsx(XCircle, { className: "h-5 w-5 text-red-500" })) }))] })] })] }), isReviewMode && isCorrect === false && (_jsxs("div", { className: "mt-3 flex items-center gap-2 rounded-lg bg-green-50 border border-green-200 px-4 py-2", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-green-600" }), _jsxs("span", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsx("strong", { children: questionData.correctAnswer })] })] })), autoFillCorrectAnswers && (_jsxs("div", { className: "mt-3 flex items-center gap-2 rounded-lg bg-blue-50 border border-blue-200 px-4 py-2", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-blue-600" }), _jsxs("span", { className: "text-sm text-blue-700", children: ["\u0110\u00E1p \u00E1n: ", _jsx("strong", { children: questionData.correctAnswer })] })] }))] }), explanation && isReviewMode && (_jsxs("div", { className: "rounded-xl border border-blue-200 bg-blue-50 p-4", children: [_jsx("p", { className: "text-sm font-semibold text-blue-800 mb-1", children: "Gi\u1EA3i th\u00EDch:" }), _jsx("p", { className: "text-sm text-blue-700", children: explanation })] }))] }));
38
40
  }
@@ -1,8 +1,9 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Lightbulb, Star } from 'lucide-react';
3
+ import { Lightbulb } from 'lucide-react';
4
4
  import { cn } from '../../../../shared/lib/utils';
5
5
  import { assembleMatchColumnsSentence, getMatchColumnsChoiceColumns, getMatchColumnsStemColumn, } from './map-match-columns-to-make-sentences-data';
6
+ import { KidChoiceRow, KidInstruction, KidPrompt, KidQuestionCard, optionLetter, } from '../../_shared/components/compact';
6
7
  function emptyAnswer(stemFragmentId) {
7
8
  return { fragmentIds: stemFragmentId ? [stemFragmentId] : [], sentence: '' };
8
9
  }
@@ -50,7 +51,7 @@ export function MatchColumnsToMakeSentencesClient({ questionData, isReviewMode =
50
51
  });
51
52
  onAnswerChange?.(next);
52
53
  };
53
- return (_jsxs("div", { className: "space-y-4", children: [(questionData.title || questionData.instruction) && (_jsxs("div", { className: "rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3", children: [questionData.title && (_jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-indigo-500", children: questionData.title })), questionData.instruction && (_jsx("p", { className: "text-sm font-medium text-indigo-800", children: questionData.instruction }))] })), _jsx("div", { className: "overflow-x-auto rounded-xl border border-gray-200 bg-white", children: _jsxs("table", { className: "min-w-full text-sm", children: [_jsx("thead", { children: _jsx("tr", { className: "bg-slate-50 text-left text-xs uppercase tracking-wide text-slate-500", children: columns.map((column) => (_jsxs("th", { className: "px-3 py-2 font-semibold", children: [column.label || column.id, column.role === 'stem' ? ' (stem)' : ''] }, column.id))) }) }), _jsx("tbody", { children: _jsx("tr", { className: "align-top", children: columns.map((column) => (_jsx("td", { className: "px-3 py-2", children: _jsx("ul", { className: "space-y-1", children: column.fragments.map((fragment) => (_jsxs("li", { className: "text-slate-700", children: [_jsxs("span", { className: "mr-1 font-semibold text-slate-500", children: [fragment.id, "."] }), fragment.text || '—'] }, fragment.id))) }) }, `${column.id}-bank`))) }) })] }) }), _jsx("div", { className: "space-y-3", children: items.map((item, itemIndex) => {
54
+ return (_jsxs("div", { className: "space-y-4", children: [questionData.title ? _jsx(KidPrompt, { children: questionData.title }) : null, _jsx(KidInstruction, { children: questionData.instruction }), _jsx("div", { className: "overflow-x-auto rounded-xl border border-gray-200 bg-white", children: _jsxs("table", { className: "min-w-full text-sm", children: [_jsx("thead", { children: _jsx("tr", { className: "bg-slate-50 text-left text-xs uppercase tracking-wide text-slate-500", children: columns.map((column) => (_jsxs("th", { className: "px-3 py-2 font-semibold", children: [column.label || column.id, column.role === 'stem' ? ' (stem)' : ''] }, column.id))) }) }), _jsx("tbody", { children: _jsx("tr", { className: "align-top", children: columns.map((column) => (_jsx("td", { className: "px-3 py-2", children: _jsx("ul", { className: "space-y-1", children: column.fragments.map((fragment) => (_jsxs("li", { className: "text-slate-700", children: [_jsxs("span", { className: "mr-1 font-semibold text-slate-500", children: [fragment.id, "."] }), fragment.text || '—'] }, fragment.id))) }) }, `${column.id}-bank`))) }) })] }) }), _jsx("div", { className: "space-y-3", children: items.map((item, itemIndex) => {
54
55
  const current = resolveAnswer(itemIndex);
55
56
  const canSelect = canEdit && !item.isExample;
56
57
  const stemText = stemColumn?.fragments.find((fragment) => fragment.id === item.stemFragmentId)?.text
@@ -58,17 +59,19 @@ export function MatchColumnsToMakeSentencesClient({ questionData, isReviewMode =
58
59
  || '—';
59
60
  const isCorrect = isReviewMode &&
60
61
  current.sentence.trim().toLowerCase() === item.sentence.trim().toLowerCase();
61
- 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 px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_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: [_jsxs("div", { className: "grid gap-3 md:grid-cols-[minmax(0,1fr)_repeat(2,minmax(0,1fr))]", children: [_jsx("div", { className: "rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm font-semibold text-slate-800", children: stemText }), choiceColumns.map((column, choiceIndex) => {
62
- const selectedId = current.fragmentIds[choiceIndex + 1] || '';
63
- return (_jsxs("select", { className: cn('rounded-lg border-2 px-3 py-2 text-sm', isReviewMode && selectedId === item.fragmentIds[choiceIndex + 1]
64
- ? 'border-green-400 bg-green-50'
65
- : selectedId
66
- ? 'border-blue-400 bg-blue-50'
67
- : 'border-gray-200 bg-white'), disabled: !canSelect, value: selectedId, onChange: (event) => handleChoiceChange(itemIndex, choiceIndex, event.target.value), children: [_jsxs("option", { value: "", children: ["Ch\u1ECDn ", column.label || column.id] }), column.fragments.map((fragment) => (_jsxs("option", { value: fragment.id, children: [fragment.id, ". ", fragment.text] }, fragment.id)))] }, `${itemIndex}-${column.id}`));
68
- })] }), _jsx("p", { className: cn('text-sm', isReviewMode
69
- ? isCorrect
70
- ? 'text-green-700'
71
- : 'text-red-700'
72
- : 'text-slate-600'), children: current.sentence || 'Chưa ghép câu' }), isReviewMode && !item.isExample && (_jsxs("p", { className: "text-xs text-slate-500", children: ["\u0110\u00E1p \u00E1n: ", item.sentence] }))] })] }, `${item.questionNumber}-${itemIndex}`));
62
+ return (_jsxs(KidQuestionCard, { title: `Câu ${item.questionNumber || itemIndex + 1}`, points: item.points || 0, isExample: item.isExample, review: isReviewMode && !item.isExample ? (isCorrect ? 'correct' : 'wrong') : null, children: [_jsx(KidPrompt, { children: stemText }), choiceColumns.map((column, choiceIndex) => {
63
+ const selectedId = current.fragmentIds[choiceIndex + 1] || '';
64
+ const correctId = item.fragmentIds[choiceIndex + 1] || '';
65
+ return (_jsxs("div", { className: "space-y-1.5", children: [_jsx("p", { className: "text-xs font-medium uppercase tracking-wider text-gray-500", children: column.label || column.id }), column.fragments.map((fragment, fragmentIndex) => {
66
+ const isSelected = selectedId === fragment.id;
67
+ return (_jsxs(KidChoiceRow, { letter: optionLetter(fragmentIndex), selected: isSelected, isReviewMode: isReviewMode, isCorrect: isReviewMode && fragment.id === correctId, disabled: !canSelect, onClick: canSelect
68
+ ? () => handleChoiceChange(itemIndex, choiceIndex, isSelected ? '' : fragment.id)
69
+ : undefined, children: [fragment.id, ". ", fragment.text] }, fragment.id));
70
+ })] }, `${itemIndex}-${column.id}`));
71
+ }), _jsx("p", { className: cn('text-sm', isReviewMode
72
+ ? isCorrect
73
+ ? 'text-green-700'
74
+ : 'text-red-700'
75
+ : 'text-slate-600'), children: current.sentence || 'Chưa ghép câu' }), isReviewMode && !item.isExample && (_jsxs("p", { className: "text-xs text-slate-500", children: ["\u0110\u00E1p \u00E1n: ", item.sentence] }))] }, `${item.questionNumber}-${itemIndex}`));
73
76
  }) }), 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] }))] }));
74
77
  }
@@ -1,17 +1,16 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Card, CardContent } from '../../../../components/ui/card';
4
- import { Label } from '../../../../components/ui/label';
5
- import { BookOpen, CheckCircle2, XCircle } from 'lucide-react';
3
+ import { CheckCircle2 } from 'lucide-react';
6
4
  import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
7
5
  import { MATCH_WORD_TO_PICTURE_DEFAULT_INSTRUCTION, } from '../../_shared/types/match-word-to-picture.type';
8
6
  import { useState } from 'react';
7
+ import { KidChoiceRow, KidInstruction, KidQuestionCard, optionLetter, } from '../../_shared/components/compact';
9
8
  export function MatchWordToPictureClient({ questionData, onSubmit, isReviewMode = false, userAnswer, autoFillCorrectAnswers = false, explanation, }) {
10
9
  const [answer, setAnswer] = useState(userAnswer || '');
11
10
  const { previewUrl: questionImageUrl } = usePresignedFileUrl(questionData.imageUrl || '');
12
11
  const wordBank = questionData.wordBank || [];
13
12
  if (!questionData.imageUrl) {
14
- return (_jsx(Card, { children: _jsx(CardContent, { className: "p-6", children: _jsx("p", { className: "text-gray-500", children: "Kh\u00F4ng c\u00F3 d\u1EEF li\u1EC7u c\u00E2u h\u1ECFi" }) }) }));
13
+ return (_jsx(KidQuestionCard, { title: "Gh\u00E9p t\u1EEB v\u1EDBi tranh", children: _jsx("p", { className: "text-gray-500", children: "Kh\u00F4ng c\u00F3 d\u1EEF li\u1EC7u c\u00E2u h\u1ECFi" }) }));
15
14
  }
16
15
  const isExample = questionData.isExample || false;
17
16
  const displayedAnswer = autoFillCorrectAnswers || isExample
@@ -20,12 +19,17 @@ export function MatchWordToPictureClient({ questionData, onSubmit, isReviewMode
20
19
  const isCorrect = isReviewMode && userAnswer !== undefined
21
20
  ? userAnswer.trim().toLowerCase() === questionData.correctAnswer.trim().toLowerCase()
22
21
  : undefined;
23
- return (_jsx(Card, { className: "overflow-hidden border-0 bg-white shadow-sm", children: _jsxs(CardContent, { className: "space-y-3 p-3", children: [_jsx("p", { className: "text-sm font-semibold uppercase tracking-wide text-violet-700", children: questionData.instruction || MATCH_WORD_TO_PICTURE_DEFAULT_INSTRUCTION }), wordBank.length > 0 && (_jsxs("div", { className: "rounded-xl border border-violet-200 bg-gradient-to-r from-violet-50 to-indigo-50 p-4", children: [_jsx("p", { className: "mb-3 text-sm font-semibold text-violet-700", children: "Word bank:" }), _jsx("div", { className: "flex flex-wrap gap-2", children: wordBank.map((word, index) => (_jsx("span", { className: "rounded-full border border-violet-200 bg-white px-3 py-1 text-sm font-semibold text-slate-700", children: word }, `${word}-${index}`))) })] })), _jsxs("div", { className: "rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-5", children: [isExample && (_jsxs("div", { className: "mb-2 inline-flex items-center gap-1.5 rounded-full border border-amber-300 bg-amber-100 px-3 py-1", children: [_jsx(BookOpen, { className: "h-3.5 w-3.5 text-amber-600" }), _jsx("span", { className: "text-xs font-semibold uppercase tracking-wide text-amber-700", children: "Example" })] })), _jsxs("div", { className: "flex items-center gap-4", children: [_jsx("div", { className: "relative h-24 w-24 flex-shrink-0 overflow-hidden rounded-lg border border-gray-200 bg-gray-50 shadow-sm", children: _jsx("img", { src: questionImageUrl || questionData.imageUrl, alt: questionData.correctAnswer || 'Picture', className: "h-full w-full object-contain" }) }), _jsxs("div", { className: "flex-1 space-y-2", children: [_jsx(Label, { className: "text-base font-semibold text-gray-800", children: "Ch\u1ECDn t\u1EEB ph\u00F9 h\u1EE3p" }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("select", { value: displayedAnswer, onChange: (event) => {
24
- setAnswer(event.target.value);
25
- onSubmit?.(event.target.value);
26
- }, disabled: isReviewMode || isExample, className: `h-10 max-w-xs rounded-md border-2 bg-white px-3 text-sm font-medium ${isReviewMode
27
- ? isCorrect
28
- ? 'border-green-400 bg-green-50 text-green-700'
29
- : 'border-red-400 bg-red-50 text-red-700'
30
- : 'border-violet-300 focus:border-violet-500 focus:ring-violet-200'}`, children: [_jsx("option", { value: "", children: "-- Ch\u1ECDn t\u1EEB --" }), wordBank.map((word, index) => (_jsx("option", { value: word, children: word }, `${word}-${index}`)))] }), isReviewMode && isCorrect !== undefined && (_jsx("span", { className: "flex items-center gap-1", children: isCorrect ? (_jsx(CheckCircle2, { className: "h-5 w-5 text-green-500" })) : (_jsx(XCircle, { className: "h-5 w-5 text-red-500" })) }))] })] })] }), isReviewMode && isCorrect === false && (_jsxs("div", { className: "mt-3 flex items-center gap-2 rounded-lg border border-green-200 bg-green-50 px-4 py-2", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-green-600" }), _jsxs("span", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsx("strong", { children: questionData.correctAnswer })] })] }))] }), explanation && isReviewMode && (_jsxs("div", { className: "rounded-xl border border-blue-200 bg-blue-50 p-4", children: [_jsx("p", { className: "mb-1 text-sm font-semibold text-blue-800", children: "Gi\u1EA3i th\u00EDch:" }), _jsx("p", { className: "text-sm text-blue-700", children: explanation })] }))] }) }));
22
+ const canSelect = !isReviewMode && !isExample;
23
+ const handleSelect = (word) => {
24
+ if (!canSelect)
25
+ return;
26
+ const next = displayedAnswer === word ? '' : word;
27
+ setAnswer(next);
28
+ onSubmit?.(next);
29
+ };
30
+ return (_jsxs(KidQuestionCard, { title: "Gh\u00E9p t\u1EEB v\u1EDBi tranh", isExample: isExample, review: isReviewMode && isCorrect !== undefined ? (isCorrect ? 'correct' : 'wrong') : null, children: [_jsx(KidInstruction, { children: questionData.instruction || MATCH_WORD_TO_PICTURE_DEFAULT_INSTRUCTION }), wordBank.length > 0 && (_jsxs("div", { className: "rounded-xl border border-blue-100 bg-blue-50/60 p-3", children: [_jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wider text-blue-700", children: "Word bank" }), _jsx("div", { className: "flex flex-wrap gap-2", children: wordBank.map((word, index) => (_jsx("span", { className: "rounded-full border border-gray-200 bg-white px-3 py-1 text-sm font-semibold text-slate-700", children: word }, `${word}-${index}`))) })] })), _jsxs("div", { className: "flex items-center gap-4", children: [_jsx("div", { className: "relative h-24 w-24 flex-shrink-0 overflow-hidden rounded-lg border border-gray-200 bg-gray-50 shadow-sm", children: _jsx("img", { src: questionImageUrl || questionData.imageUrl, alt: questionData.correctAnswer || 'Picture', className: "h-full w-full object-contain" }) }), _jsx("p", { className: "text-sm font-semibold text-gray-800", children: "Ch\u1ECDn t\u1EEB ph\u00F9 h\u1EE3p" })] }), _jsx("div", { className: "space-y-1.5", children: wordBank.map((word, index) => {
31
+ const isSelected = displayedAnswer === word;
32
+ const isWordCorrect = word.trim().toLowerCase() === questionData.correctAnswer.trim().toLowerCase();
33
+ return (_jsx(KidChoiceRow, { letter: optionLetter(index), selected: isSelected, isReviewMode: isReviewMode, isCorrect: isReviewMode && isWordCorrect, disabled: !canSelect, onClick: canSelect ? () => handleSelect(word) : undefined, children: word }, `${word}-${index}`));
34
+ }) }), isReviewMode && isCorrect === false && (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-green-200 bg-green-50 px-4 py-2", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-green-600" }), _jsxs("span", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsx("strong", { children: questionData.correctAnswer })] })] })), explanation && isReviewMode && (_jsxs("div", { className: "rounded-xl border border-blue-200 bg-blue-50 p-4", children: [_jsx("p", { className: "mb-1 text-sm font-semibold text-blue-800", children: "Gi\u1EA3i th\u00EDch:" }), _jsx("p", { className: "text-sm text-blue-700", children: explanation })] }))] }));
31
35
  }
@@ -1,8 +1,9 @@
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';
3
+ import { Lightbulb } from 'lucide-react';
4
4
  import { cn } from '../../../../shared/lib/utils';
5
5
  import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
6
+ import { KidChoiceRow, KidInstruction, KidQuestionCard, optionLetter, } from '../../_shared/components/compact';
6
7
  function normalizeWord(value) {
7
8
  return value.trim().toLowerCase();
8
9
  }
@@ -19,7 +20,7 @@ function ClientImageItem({ url, alt }) {
19
20
  if (!src && !isLoading) {
20
21
  return (_jsx("div", { className: "flex h-28 w-28 items-center justify-center rounded-lg border border-gray-200 bg-gray-50 text-xs text-gray-400", children: "Kh\u00F4ng t\u1EA3i \u0111\u01B0\u1EE3c \u1EA3nh" }));
21
22
  }
22
- return (_jsx("div", { className: "relative h-28 w-28 flex-shrink-0 overflow-hidden rounded-lg border border-gray-200 bg-gray-50", children: isLoading ? (_jsx("div", { className: "flex h-full w-full items-center justify-center", children: _jsx("div", { className: "h-6 w-6 animate-spin rounded-full border-2 border-gray-200 border-t-violet-500" }) })) : (_jsx("img", { src: src, alt: alt || 'Picture', className: "h-full w-full object-contain" })) }));
23
+ return (_jsx("div", { className: "relative h-28 w-28 flex-shrink-0 overflow-hidden rounded-lg border border-gray-200 bg-gray-50", children: isLoading ? (_jsx("div", { className: "flex h-full w-full items-center justify-center", children: _jsx("div", { className: "h-6 w-6 animate-spin rounded-full border-2 border-gray-200 border-t-blue-500" }) })) : (_jsx("img", { src: src, alt: alt || 'Picture', className: "h-full w-full object-contain" })) }));
23
24
  }
24
25
  export function MatchWordToPictureGroupClient({ questionData, isReviewMode = false, userAnswers = [], onAnswerChange, }) {
25
26
  const items = questionData.items || [];
@@ -40,11 +41,11 @@ export function MatchWordToPictureGroupClient({ questionData, isReviewMode = fal
40
41
  usedWords.add(normalizeWord(value));
41
42
  }
42
43
  });
43
- 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 })), wordBank.length > 0 && (_jsxs("div", { className: "rounded-xl border border-violet-200 bg-gradient-to-r from-violet-50 to-indigo-50 p-4", children: [_jsx("p", { className: "mb-3 text-sm font-semibold text-violet-700", children: "Word bank:" }), _jsx("div", { className: "flex flex-wrap gap-2", children: wordBank.map((word, index) => {
44
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx(KidInstruction, { children: questionData.instruction }), wordBank.length > 0 && (_jsxs("div", { className: "rounded-xl border border-blue-100 bg-blue-50/60 p-3", children: [_jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wider text-blue-700", children: "Word bank" }), _jsx("div", { className: "flex flex-wrap gap-2", children: wordBank.map((word, index) => {
44
45
  const isUsed = usedWords.has(normalizeWord(word));
45
46
  return (_jsx("span", { className: cn('rounded-full border px-3 py-1 text-sm font-semibold', isUsed
46
- ? 'border-emerald-200 bg-emerald-50 text-emerald-700'
47
- : 'border-violet-200 bg-white text-slate-700'), children: word }, `${word}-${index}`));
47
+ ? 'border-blue-500 bg-blue-50 text-blue-800 ring-2 ring-blue-100'
48
+ : 'border-gray-200 bg-white text-slate-700'), children: word }, `${word}-${index}`));
48
49
  }) })] })), items.map((item, itemIndex) => {
49
50
  const selectedWord = item.isExample
50
51
  ? item.correctAnswer
@@ -52,14 +53,13 @@ export function MatchWordToPictureGroupClient({ questionData, isReviewMode = fal
52
53
  const canSelect = !isReviewMode && !item.isExample && Boolean(onAnswerChange);
53
54
  const isCorrect = normalizeWord(selectedWord) === normalizeWord(item.correctAnswer) &&
54
55
  Boolean(item.correctAnswer);
55
- 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-4 py-2.5", 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-violet-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: "flex flex-col gap-4 p-4 sm:flex-row sm:items-center", children: [_jsx(ClientImageItem, { url: item.imageUrl, alt: `Hình ảnh câu ${item.questionNumber || itemIndex + 1}` }), _jsxs("div", { className: "min-w-0 flex-1 space-y-2", children: [_jsx("p", { className: "text-sm font-semibold text-gray-800", children: "Ch\u1ECDn t\u1EEB ph\u00F9 h\u1EE3p" }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("select", { value: selectedWord, disabled: !canSelect, onChange: (event) => handleSelect(itemIndex, event.target.value), className: cn('h-10 max-w-xs rounded-md border-2 bg-white px-3 text-sm font-medium', isReviewMode && isCorrect
56
- ? 'border-green-400 bg-green-50 text-green-700'
57
- : isReviewMode && selectedWord && !isCorrect
58
- ? 'border-red-400 bg-red-50 text-red-700'
59
- : 'border-violet-300 focus:border-violet-500 focus:ring-violet-200', !canSelect && 'cursor-default opacity-80'), children: [_jsx("option", { value: "", children: "-- Ch\u1ECDn t\u1EEB --" }), wordBank.map((word, wordIndex) => {
60
- const isUsedByOther = usedWords.has(normalizeWord(word)) &&
61
- normalizeWord(word) !== normalizeWord(selectedWord);
62
- return (_jsx("option", { value: word, disabled: isUsedByOther, children: word }, `${word}-${wordIndex}`));
63
- })] }), isReviewMode && !item.isExample && (_jsx("span", { className: "flex items-center gap-1", children: isCorrect ? (_jsx(Check, { className: "h-5 w-5 text-green-500" })) : (_jsx(X, { className: "h-5 w-5 text-red-500" })) }))] }), isReviewMode && !item.isExample && !isCorrect && (_jsxs("p", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsx("strong", { children: item.correctAnswer || '—' })] }))] })] })] }, `${item.questionNumber}-${itemIndex}`));
56
+ return (_jsx(KidQuestionCard, { title: `Câu ${item.questionNumber || itemIndex + 1}`, points: item.points || 0, isExample: item.isExample, review: isReviewMode && !item.isExample ? (isCorrect ? 'correct' : 'wrong') : null, children: _jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-start", children: [_jsx(ClientImageItem, { url: item.imageUrl, alt: `Hình ảnh câu ${item.questionNumber || itemIndex + 1}` }), _jsxs("div", { className: "min-w-0 flex-1 space-y-1.5", children: [_jsx("p", { className: "text-sm font-semibold text-gray-800", children: "Ch\u1ECDn t\u1EEB ph\u00F9 h\u1EE3p" }), wordBank.map((word, wordIndex) => {
57
+ const isSelected = normalizeWord(word) === normalizeWord(selectedWord);
58
+ const isUsedByOther = usedWords.has(normalizeWord(word)) && !isSelected;
59
+ const isWordCorrect = normalizeWord(word) === normalizeWord(item.correctAnswer);
60
+ return (_jsx(KidChoiceRow, { letter: optionLetter(wordIndex), selected: isSelected, isReviewMode: isReviewMode, isCorrect: isReviewMode && isWordCorrect, disabled: !canSelect || isUsedByOther, onClick: canSelect && !isUsedByOther
61
+ ? () => handleSelect(itemIndex, isSelected ? '' : word)
62
+ : undefined, children: word }, `${word}-${wordIndex}`));
63
+ }), isReviewMode && !item.isExample && !isCorrect && (_jsxs("p", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsx("strong", { children: item.correctAnswer || '—' })] }))] })] }) }, `${item.questionNumber}-${itemIndex}`));
64
64
  }), 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] }))] }));
65
65
  }
@@ -1,9 +1,10 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { BookOpen, Check, CircleHelp, Lightbulb, Star, Volume2, X } from 'lucide-react';
3
+ import { BookOpen, Lightbulb, Volume2 } from 'lucide-react';
4
4
  import { cn } from '../../../../shared/lib/utils';
5
5
  import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
6
6
  import { toMatchWordToPictureImageUrls } from '../../_shared/types/match-word-to-picture-group.type';
7
+ import { KidChoiceRow, KidInstruction, KidPrompt, KidQuestionCard, optionLetter, } from '../../_shared/components/compact';
7
8
  function normalizeWord(value) {
8
9
  return value.trim().toLowerCase();
9
10
  }
@@ -69,11 +70,11 @@ export function MatchWordToPictureGroupClient({ questionData, isReviewMode = fal
69
70
  usedWords.add(normalizeWord(value));
70
71
  }
71
72
  });
72
- return (_jsxs("div", { className: "space-y-4", children: [(questionData.title || questionData.instruction) && (_jsxs("div", { className: "rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3", children: [questionData.title && (_jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-indigo-500", children: questionData.title })), questionData.instruction && (_jsx("p", { className: "text-sm font-medium text-indigo-800", children: questionData.instruction }))] })), questionData.audioUrl ? _jsx(ClientAudio, { url: questionData.audioUrl }) : null, toMatchWordToPictureImageUrls(questionData.imageUrl).length > 0 ? (_jsx("div", { className: "flex flex-wrap justify-center gap-3 rounded-lg border border-slate-100 bg-slate-50 p-3", children: toMatchWordToPictureImageUrls(questionData.imageUrl).map((url, imageIndex) => (_jsx(ClientImageItem, { url: url, alt: `Hình ảnh bài (${imageIndex + 1})`, large: true }, `mwtp-cover-${imageIndex}`))) })) : null, questionData.passage ? (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-white p-5 shadow-xs", children: [_jsxs("div", { className: "mb-2.5 flex items-center gap-2 text-xs font-bold uppercase tracking-wider text-indigo-700", children: [_jsx(BookOpen, { className: "h-4 w-4" }), _jsx("span", { children: "B\u00E0i \u0111\u1ECDc (Reading Passage)" })] }), _jsx("div", { className: "prose prose-sm max-w-none leading-relaxed text-gray-800", dangerouslySetInnerHTML: { __html: questionData.passage } })] })) : null, wordBank.length > 0 && (_jsxs("div", { className: "rounded-xl border border-violet-200 bg-gradient-to-r from-violet-50 to-indigo-50 p-4", children: [_jsx("p", { className: "mb-3 text-sm font-semibold text-violet-700", children: "Word bank:" }), _jsx("div", { className: "flex flex-wrap gap-2", children: wordBank.map((word, index) => {
73
+ return (_jsxs("div", { className: "space-y-4", children: [questionData.title ? _jsx(KidPrompt, { children: questionData.title }) : null, _jsx(KidInstruction, { children: questionData.instruction }), questionData.audioUrl ? _jsx(ClientAudio, { url: questionData.audioUrl }) : null, toMatchWordToPictureImageUrls(questionData.imageUrl).length > 0 ? (_jsx("div", { className: "flex flex-wrap justify-center gap-3 rounded-lg border border-slate-100 bg-slate-50 p-3", children: toMatchWordToPictureImageUrls(questionData.imageUrl).map((url, imageIndex) => (_jsx(ClientImageItem, { url: url, alt: `Hình ảnh bài (${imageIndex + 1})`, large: true }, `mwtp-cover-${imageIndex}`))) })) : null, questionData.passage ? (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-white p-5 shadow-xs", children: [_jsxs("div", { className: "mb-2.5 flex items-center gap-2 text-xs font-bold uppercase tracking-wider text-indigo-700", children: [_jsx(BookOpen, { className: "h-4 w-4" }), _jsx("span", { children: "B\u00E0i \u0111\u1ECDc (Reading Passage)" })] }), _jsx("div", { className: "prose prose-sm max-w-none leading-relaxed text-gray-800", dangerouslySetInnerHTML: { __html: questionData.passage } })] })) : null, wordBank.length > 0 && (_jsxs("div", { className: "rounded-xl border border-blue-100 bg-blue-50/60 p-3", children: [_jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wider text-blue-700", children: "Word bank" }), _jsx("div", { className: "flex flex-wrap gap-2", children: wordBank.map((word, index) => {
73
74
  const isUsed = usedWords.has(normalizeWord(word));
74
75
  return (_jsx("span", { className: cn('rounded-full border px-3 py-1 text-sm font-semibold', isUsed
75
- ? 'border-emerald-200 bg-emerald-50 text-emerald-700'
76
- : 'border-violet-200 bg-white text-slate-700'), children: word }, `${word}-${index}`));
76
+ ? 'border-blue-500 bg-blue-50 text-blue-800 ring-2 ring-blue-100'
77
+ : 'border-gray-200 bg-white text-slate-700'), children: word }, `${word}-${index}`));
77
78
  }) })] })), items.map((item, itemIndex) => {
78
79
  const selectedWord = item.isExample
79
80
  ? item.correctAnswer
@@ -81,14 +82,13 @@ export function MatchWordToPictureGroupClient({ questionData, isReviewMode = fal
81
82
  const canSelect = !isReviewMode && !item.isExample && Boolean(onAnswerChange);
82
83
  const isCorrect = normalizeWord(selectedWord) === normalizeWord(item.correctAnswer) &&
83
84
  Boolean(item.correctAnswer);
84
- 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-violet-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: "flex flex-col space-y-3 p-3 sm:flex-row sm:items-center sm:space-y-0 sm:gap-3", children: [toMatchWordToPictureImageUrls(item.imageUrl).length > 0 ? (_jsx("div", { className: "flex flex-wrap gap-2", children: toMatchWordToPictureImageUrls(item.imageUrl).map((url, imageIndex) => (_jsx(ClientImageItem, { url: url, alt: `Hình ảnh câu ${item.questionNumber || itemIndex + 1}${imageIndex > 0 ? ` (${imageIndex + 1})` : ''}` }, `${item.questionNumber}-${imageIndex}`))) })) : null, _jsxs("div", { className: "min-w-0 flex-1 space-y-2", children: [_jsxs("p", { className: "text-sm font-semibold text-gray-800", children: [item.questionNumber || itemIndex + 1, ". Ch\u1ECDn t\u1EEB ph\u00F9 h\u1EE3p"] }), _jsxs("div", { className: "flex items-center gap-3", children: [item.isExample ? (_jsx("span", { className: "text-sm italic text-gray-800 underline decoration-gray-400", children: item.correctAnswer })) : (_jsxs("select", { value: selectedWord, disabled: !canSelect, onChange: (event) => handleSelect(itemIndex, event.target.value), className: cn('h-10 max-w-xs rounded-md border-2 bg-white px-3 text-sm font-medium', isReviewMode && isCorrect
85
- ? 'border-green-400 bg-green-50 text-green-700'
86
- : isReviewMode && selectedWord && !isCorrect
87
- ? 'border-red-400 bg-red-50 text-red-700'
88
- : 'border-violet-300 focus:border-violet-500 focus:ring-violet-200', !canSelect && 'cursor-default opacity-80'), children: [_jsx("option", { value: "", children: "-- Ch\u1ECDn t\u1EEB --" }), wordBank.map((word, wordIndex) => {
89
- const isUsedByOther = usedWords.has(normalizeWord(word)) &&
90
- normalizeWord(word) !== normalizeWord(selectedWord);
91
- return (_jsx("option", { value: word, disabled: isUsedByOther, children: word }, `${word}-${wordIndex}`));
92
- })] })), isReviewMode && !item.isExample && (_jsx("span", { className: "flex items-center gap-1", children: isCorrect ? (_jsx(Check, { className: "h-5 w-5 text-green-500" })) : (_jsx(X, { className: "h-5 w-5 text-red-500" })) }))] }), isReviewMode && !item.isExample && !isCorrect && (_jsxs("p", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsx("strong", { children: item.correctAnswer || '—' })] }))] })] })] }, `${item.questionNumber}-${itemIndex}`));
85
+ return (_jsx(KidQuestionCard, { title: `Câu ${item.questionNumber || itemIndex + 1}`, points: item.points || 0, isExample: item.isExample, review: isReviewMode && !item.isExample ? (isCorrect ? 'correct' : 'wrong') : null, children: _jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-start", children: [toMatchWordToPictureImageUrls(item.imageUrl).length > 0 ? (_jsx("div", { className: "flex flex-wrap gap-2", children: toMatchWordToPictureImageUrls(item.imageUrl).map((url, imageIndex) => (_jsx(ClientImageItem, { url: url, alt: `Hình ảnh câu ${item.questionNumber || itemIndex + 1}${imageIndex > 0 ? ` (${imageIndex + 1})` : ''}` }, `${item.questionNumber}-${imageIndex}`))) })) : null, _jsxs("div", { className: "min-w-0 flex-1 space-y-1.5", children: [_jsx("p", { className: "text-sm font-semibold text-gray-800", children: "Ch\u1ECDn t\u1EEB ph\u00F9 h\u1EE3p" }), item.isExample ? (_jsx("span", { className: "text-sm italic text-gray-800 underline decoration-gray-400", children: item.correctAnswer })) : (wordBank.map((word, wordIndex) => {
86
+ const isSelected = normalizeWord(word) === normalizeWord(selectedWord);
87
+ const isUsedByOther = usedWords.has(normalizeWord(word)) && !isSelected;
88
+ const isWordCorrect = normalizeWord(word) === normalizeWord(item.correctAnswer);
89
+ return (_jsx(KidChoiceRow, { letter: optionLetter(wordIndex), selected: isSelected, isReviewMode: isReviewMode, isCorrect: isReviewMode && isWordCorrect, disabled: !canSelect || isUsedByOther, onClick: canSelect && !isUsedByOther
90
+ ? () => handleSelect(itemIndex, isSelected ? '' : word)
91
+ : undefined, children: word }, `${word}-${wordIndex}`));
92
+ })), isReviewMode && !item.isExample && !isCorrect && (_jsxs("p", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsx("strong", { children: item.correctAnswer || '—' })] }))] })] }) }, `${item.questionNumber}-${itemIndex}`));
93
93
  }), 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] }))] }));
94
94
  }
@@ -4,6 +4,7 @@ import { BookOpen, Lightbulb } from 'lucide-react';
4
4
  import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
5
5
  import { ResolvedImage } from '../../../../components/common/ResolvedImage';
6
6
  import { MatchingWithLinesBoard } from './MatchingWithLinesBoard';
7
+ import { KidInstruction, KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
7
8
  function ClientAudio({ url }) {
8
9
  const { previewUrl, isLoading } = usePresignedFileUrl(url);
9
10
  const src = previewUrl ||
@@ -32,10 +33,10 @@ export function MatchingWithLinesGroupClient({ questionData, isReviewMode = fals
32
33
  }
33
34
  });
34
35
  };
35
- return (_jsxs("div", { className: "space-y-4", children: [(questionData.title || questionData.instruction) && (_jsxs("div", { className: "rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3", children: [questionData.title && (_jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-indigo-500", children: questionData.title })), questionData.instruction && (_jsx("p", { className: "text-sm font-medium text-indigo-800", children: questionData.instruction }))] })), questionData.passage ? (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-white p-5 shadow-xs", children: [_jsxs("div", { className: "mb-2.5 flex items-center gap-2 text-xs font-bold uppercase tracking-wider text-indigo-700", children: [_jsx(BookOpen, { className: "h-4 w-4" }), _jsx("span", { children: "B\u00E0i \u0111\u1ECDc (Reading Passage)" })] }), _jsx("div", { className: "prose prose-sm max-w-none leading-relaxed text-gray-800", dangerouslySetInnerHTML: { __html: questionData.passage } })] })) : null, questionData.audioUrl ? _jsx(ClientAudio, { url: questionData.audioUrl }) : null, questionData.imageUrl ? (_jsx("div", { className: "flex justify-center rounded-lg border border-slate-100 bg-slate-50 p-3", children: _jsx(ResolvedImage, { src: questionData.imageUrl, alt: "Match passage image", className: "max-h-44 w-auto rounded-md object-contain" }) })) : null, _jsx(MatchingWithLinesBoard, { leftItems: items.map((item) => ({
36
- imageUrl: item.imageUrl,
37
- text: item.text,
38
- isExample: item.isExample,
39
- questionNumber: item.questionNumber,
40
- })), rightItems: rightItems, values: values, correctValues: isReviewMode ? correctValues : undefined, onChange: handleChange, isReviewMode: isReviewMode, disabled: isReviewMode || !canEdit }), 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] }))] }));
36
+ return (_jsxs("div", { className: "space-y-4", children: [questionData.title ? _jsx(KidPrompt, { children: questionData.title }) : null, _jsx(KidInstruction, { children: questionData.instruction }), questionData.passage ? (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-white p-5 shadow-xs", children: [_jsxs("div", { className: "mb-2.5 flex items-center gap-2 text-xs font-bold uppercase tracking-wider text-indigo-700", children: [_jsx(BookOpen, { className: "h-4 w-4" }), _jsx("span", { children: "B\u00E0i \u0111\u1ECDc (Reading Passage)" })] }), _jsx("div", { className: "prose prose-sm max-w-none leading-relaxed text-gray-800", dangerouslySetInnerHTML: { __html: questionData.passage } })] })) : null, questionData.audioUrl ? _jsx(ClientAudio, { url: questionData.audioUrl }) : null, questionData.imageUrl ? (_jsx("div", { className: "flex justify-center rounded-lg border border-slate-100 bg-slate-50 p-3", children: _jsx(ResolvedImage, { src: questionData.imageUrl, alt: "Match passage image", className: "max-h-44 w-auto rounded-md object-contain" }) })) : null, _jsx(KidQuestionCard, { title: "N\u1ED1i b\u1EB1ng \u0111\u01B0\u1EDDng k\u1EBB", children: _jsx(MatchingWithLinesBoard, { leftItems: items.map((item) => ({
37
+ imageUrl: item.imageUrl,
38
+ text: item.text,
39
+ isExample: item.isExample,
40
+ questionNumber: item.questionNumber,
41
+ })), rightItems: rightItems, values: values, correctValues: isReviewMode ? correctValues : undefined, onChange: handleChange, isReviewMode: isReviewMode, disabled: isReviewMode || !canEdit }) }), 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] }))] }));
41
42
  }
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect, useRef } from 'react';
4
- import { cn } from '../../../../shared/lib/utils';
5
- import { Check, X, BookOpen, FileText, ClipboardList, Lightbulb } from 'lucide-react';
4
+ import { Check, BookOpen, Lightbulb } from 'lucide-react';
5
+ import { KidChoiceRow, KidInstruction, KidPrompt, KidQuestionCard, optionLetter } from '../../_shared/components/compact';
6
6
  export function ReadPassageAndAnswerQuestionsClient({ questionData, userAnswer, isReviewMode = false, onSubmit, autoFillCorrectAnswers = false, explanation: explanationProp, }) {
7
7
  const { passage, passageTitle, instructions, singleQuestion, explanation: dataExplanation } = questionData;
8
8
  const explanation = explanationProp || dataExplanation;
@@ -38,28 +38,10 @@ export function ReadPassageAndAnswerQuestionsClient({ questionData, userAnswer,
38
38
  const isCorrect = isReviewMode && selectedAnswer === singleQuestion.correctAnswer;
39
39
  const isWrong = isReviewMode && selectedAnswer >= 0 && selectedAnswer !== singleQuestion.correctAnswer;
40
40
  const hasAnswered = selectedAnswer >= 0;
41
- return (_jsxs("div", { className: "space-y-3", children: [instructions && (_jsx("div", { className: "overflow-hidden rounded-xl border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 shadow-sm", children: _jsxs("div", { className: "flex items-center gap-3 px-3 py-2", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 text-white shadow-sm", children: _jsx(FileText, { className: "h-4 w-4" }) }), _jsx("p", { className: "text-sm font-medium text-blue-900", children: instructions })] }) })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-3 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", 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(BookOpen, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "\u0110o\u1EA1n v\u0103n \u0111\u1ECDc hi\u1EC3u" })] }), _jsxs("div", { className: "p-3 space-y-3", children: [passageTitle && (_jsx("h3", { className: "text-center text-lg font-bold text-gray-900 border-b border-gray-100 pb-3", children: passageTitle })), _jsx("div", { className: "rounded-lg bg-gradient-to-br from-slate-50 to-gray-50 p-5 border border-gray-100", children: _jsx("p", { className: "whitespace-pre-wrap text-base leading-relaxed text-gray-700", children: passage }) })] })] }), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm transition-all hover:shadow-md", children: [_jsxs("div", { className: cn('flex items-center justify-between border-b px-3 py-2', isReviewMode && isCorrect && 'border-green-200 bg-gradient-to-r from-green-50 to-emerald-50', isReviewMode && isWrong && 'border-red-200 bg-gradient-to-r from-red-50 to-rose-50', !isReviewMode && 'border-gray-100 bg-gradient-to-r from-gray-50 to-white'), children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: cn('flex h-8 w-8 items-center justify-center rounded-lg text-sm font-bold text-white shadow-sm', isReviewMode && isCorrect && 'bg-gradient-to-br from-green-500 to-emerald-600', isReviewMode && isWrong && 'bg-gradient-to-br from-red-500 to-rose-600', !isReviewMode && 'bg-gradient-to-br from-indigo-500 to-purple-600'), children: _jsx(ClipboardList, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-800", children: singleQuestion.question })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isCorrect ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'), children: [isCorrect ? _jsx(Check, { className: "h-3.5 w-3.5" }) : _jsx(X, { className: "h-3.5 w-3.5" }), isCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "p-3 space-y-3", children: [singleQuestion.options.map((option, optIndex) => {
42
- const isSelected = selectedAnswer === optIndex;
43
- const isCorrectOption = optIndex === singleQuestion.correctAnswer;
44
- const letter = String.fromCharCode(65 + optIndex);
45
- const optText = typeof option === 'string' ? option : option?.text ?? option?.imageUrl ?? option?.content ?? '';
46
- return (_jsxs("button", { type: "button", onClick: () => handleAnswerChange(optIndex), disabled: isReviewMode, className: cn('group flex w-full items-center gap-3 rounded-lg border-2 p-3.5 text-left transition-all', isReviewMode
47
- ? isCorrectOption
48
- ? 'border-green-400 bg-green-50'
49
- : isSelected
50
- ? 'border-red-400 bg-red-50'
51
- : 'border-gray-200 bg-gray-50'
52
- : isSelected
53
- ? 'border-indigo-500 bg-indigo-50 shadow-sm ring-2 ring-indigo-100'
54
- : 'border-gray-200 bg-white hover:border-gray-300 hover:bg-gray-50 hover:shadow-sm'), children: [_jsx("div", { className: cn('flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg text-sm font-bold transition-all', isReviewMode
55
- ? isCorrectOption
56
- ? 'bg-green-500 text-white'
57
- : isSelected
58
- ? 'bg-red-500 text-white'
59
- : 'bg-gray-200 text-gray-600'
60
- : isSelected
61
- ? 'bg-gradient-to-br from-indigo-500 to-purple-600 text-white shadow-sm'
62
- : 'bg-gray-100 text-gray-600 group-hover:bg-gray-200'), children: letter }), _jsx("span", { className: cn('flex-1 text-sm font-medium', isReviewMode && isCorrectOption ? 'text-green-800' :
63
- isReviewMode && isSelected && !isCorrectOption ? 'text-red-800' : 'text-gray-700'), children: optText }), isReviewMode && (isCorrectOption || isSelected) && (_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-full', isCorrectOption ? 'bg-green-500' : 'bg-red-500'), children: isCorrectOption ? _jsx(Check, { className: "h-4 w-4 text-white" }) : _jsx(X, { className: "h-4 w-4 text-white" }) }))] }, optIndex));
64
- }), !isReviewMode && !hasAnswered && (_jsxs("div", { className: "rounded-lg border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-3 flex items-center gap-2 mt-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 m\u1ED9t \u0111\u00E1p \u00E1n" })] })), isReviewMode && isWrong && (_jsx("div", { className: "rounded-lg bg-gradient-to-r from-green-50 to-emerald-50 p-4 border border-green-200 mt-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Check, { className: "h-4 w-4 text-green-600" }), _jsxs("span", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsxs("strong", { children: [String.fromCharCode(65 + singleQuestion.correctAnswer), ". ", typeof singleQuestion.options[singleQuestion.correctAnswer] === 'string' ? singleQuestion.options[singleQuestion.correctAnswer] : singleQuestion.options[singleQuestion.correctAnswer]?.text ?? ''] })] })] }) }))] })] }), isReviewMode && explanation && (_jsxs("div", { className: "overflow-hidden rounded-xl border border-blue-200 bg-gradient-to-br from-blue-50 to-indigo-50 shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-blue-100 bg-white/50 px-4 py-3", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-amber-400 to-orange-500 text-white shadow-sm", children: _jsx(Lightbulb, { className: "h-4 w-4" }) }), _jsx("h4", { className: "text-sm font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch" })] }), _jsx("div", { className: "p-4", children: _jsx("p", { className: "text-sm text-gray-700 leading-relaxed", children: explanation }) })] }))] }));
41
+ return (_jsxs("div", { className: "space-y-3", children: [_jsx(KidInstruction, { children: instructions }), _jsxs(KidQuestionCard, { icon: _jsx(BookOpen, { className: "h-3.5 w-3.5" }), title: "\u0110o\u1EA1n v\u0103n \u0111\u1ECDc hi\u1EC3u", children: [passageTitle && (_jsx("h3", { className: "text-center text-sm font-bold text-gray-900", children: passageTitle })), _jsx("p", { className: "whitespace-pre-wrap text-sm leading-relaxed text-gray-700", children: passage })] }), _jsxs(KidQuestionCard, { title: "Ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng", review: isReviewMode && hasAnswered ? (isCorrect ? 'correct' : 'wrong') : null, children: [_jsx(KidPrompt, { children: singleQuestion.question }), _jsx("div", { className: "space-y-1.5", children: singleQuestion.options.map((option, optIndex) => {
42
+ const isSelected = selectedAnswer === optIndex;
43
+ const isCorrectOption = optIndex === singleQuestion.correctAnswer;
44
+ const optText = typeof option === 'string' ? option : option?.text ?? option?.imageUrl ?? option?.content ?? '';
45
+ return (_jsx(KidChoiceRow, { letter: optionLetter(optIndex), selected: isSelected, isReviewMode: isReviewMode, isCorrect: isReviewMode && isCorrectOption, onClick: () => handleAnswerChange(optIndex), children: optText }, optIndex));
46
+ }) }), !isReviewMode && !hasAnswered && (_jsxs("div", { className: "rounded-lg border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-3 flex items-center gap-2 mt-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 m\u1ED9t \u0111\u00E1p \u00E1n" })] })), isReviewMode && isWrong && (_jsx("div", { className: "rounded-lg bg-gradient-to-r from-green-50 to-emerald-50 p-4 border border-green-200 mt-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Check, { className: "h-4 w-4 text-green-600" }), _jsxs("span", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsxs("strong", { children: [String.fromCharCode(65 + singleQuestion.correctAnswer), ". ", typeof singleQuestion.options[singleQuestion.correctAnswer] === 'string' ? singleQuestion.options[singleQuestion.correctAnswer] : singleQuestion.options[singleQuestion.correctAnswer]?.text ?? ''] })] })] }) }))] }), isReviewMode && explanation && (_jsxs("div", { className: "overflow-hidden rounded-xl border border-blue-200 bg-gradient-to-br from-blue-50 to-indigo-50 shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-blue-100 bg-white/50 px-4 py-3", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-amber-400 to-orange-500 text-white shadow-sm", children: _jsx(Lightbulb, { className: "h-4 w-4" }) }), _jsx("h4", { className: "text-sm font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch" })] }), _jsx("div", { className: "p-4", children: _jsx("p", { className: "text-sm text-gray-700 leading-relaxed", children: explanation }) })] }))] }));
65
47
  }
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Lightbulb } from 'lucide-react';
4
4
  import { SortWordsIntoCategoriesBoard } from './SortWordsIntoCategoriesBoard';
5
5
  import { buildPlacements, padPlacements } from './map-sort-words-into-categories-data';
6
+ import { KidInstruction, KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
6
7
  export function SortWordsIntoCategoriesClient({ questionData, isReviewMode = false, userPlacements, onPlacementsChange, }) {
7
8
  const correctPlacements = buildPlacements(questionData.words);
8
9
  const exampleLocked = questionData.words.map((word) => ({
@@ -20,5 +21,5 @@ export function SortWordsIntoCategoriesClient({ questionData, isReviewMode = fal
20
21
  return item;
21
22
  });
22
23
  const canEdit = Boolean(onPlacementsChange) && !isReviewMode;
23
- return (_jsxs("div", { className: "space-y-4", children: [(questionData.title || questionData.instruction) && (_jsxs("div", { className: "rounded-xl border border-slate-200 bg-slate-50 px-3 py-2", children: [questionData.title && (_jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-slate-500", children: questionData.title })), questionData.instruction && (_jsx("p", { className: "text-sm font-medium text-slate-800", children: questionData.instruction }))] })), _jsx(SortWordsIntoCategoriesBoard, { categories: questionData.categories, words: questionData.words, placements: placements, correctPlacements: isReviewMode ? correctPlacements : undefined, onChange: canEdit ? onPlacementsChange : undefined, disabled: !canEdit, isReviewMode: isReviewMode }), 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] }))] }));
24
+ return (_jsxs("div", { className: "space-y-4", children: [questionData.title ? _jsx(KidPrompt, { children: questionData.title }) : null, _jsx(KidInstruction, { children: questionData.instruction }), _jsx(KidQuestionCard, { title: "S\u1EAFp x\u1EBFp t\u1EEB v\u00E0o nh\u00F3m", children: _jsx(SortWordsIntoCategoriesBoard, { categories: questionData.categories, words: questionData.words, placements: placements, correctPlacements: isReviewMode ? correctPlacements : undefined, onChange: canEdit ? onPlacementsChange : undefined, disabled: !canEdit, isReviewMode: isReviewMode }) }), 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] }))] }));
24
25
  }