@tinyweb_dev/oe-exam-sdk 1.0.8 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/dist/components/exams/take/components/QuestionRenderer.js +1 -5
  2. package/dist/components/exams/take/components/question-renderers/ChooseThenAnswerGroupRenderer.js +1 -1
  3. package/dist/components/exams/take/components/question-renderers/MoversCrossOutWordGroupRenderer.js +24 -10
  4. package/dist/components/exams/take/components/question-renderers/MoversCrosswordPuzzleRenderer.js +1 -1
  5. package/dist/components/exams/take/components/question-renderers/MoversFillInBlankGroupRenderer.js +2 -1
  6. package/dist/components/exams/take/components/speaking/renderers/SpeakingSpontaneousQaGroupRenderer.js +12 -2
  7. package/dist/components/exams/take/hooks/useSpontaneousQaGroupRendererSetup.js +2 -1
  8. package/dist/components/exams/take/utils/answer-transformers.js +16 -1
  9. package/dist/components/exams/take/utils/cross-out-word-group.transformer.js +4 -6
  10. package/dist/components/exams/take/utils/question-transformers.d.ts +5 -0
  11. package/dist/components/exams/take/utils/question-transformers.js +6 -0
  12. package/dist/components/questions/_shared/components/compact/KidChoiceRow.d.ts +12 -0
  13. package/dist/components/questions/_shared/components/compact/KidChoiceRow.js +33 -0
  14. package/dist/components/questions/_shared/components/compact/KidInstruction.d.ts +5 -0
  15. package/dist/components/questions/_shared/components/compact/KidInstruction.js +9 -0
  16. package/dist/components/questions/_shared/components/compact/KidPrompt.d.ts +5 -0
  17. package/dist/components/questions/_shared/components/compact/KidPrompt.js +9 -0
  18. package/dist/components/questions/_shared/components/compact/KidQuestionCard.d.ts +12 -0
  19. package/dist/components/questions/_shared/components/compact/KidQuestionCard.js +7 -0
  20. package/dist/components/questions/_shared/components/compact/index.d.ts +4 -0
  21. package/dist/components/questions/_shared/components/compact/index.js +4 -0
  22. package/dist/components/questions/_shared/types/answer-the-question-group.type.d.ts +3 -0
  23. package/dist/components/questions/_shared/types/choose-then-answer-group.type.d.ts +17 -0
  24. package/dist/components/questions/_shared/types/cross-out-word-group.type.d.ts +1 -0
  25. package/dist/components/questions/_shared/types/crossword-puzzle.type.d.ts +5 -1
  26. package/dist/components/questions/_shared/types/spontaneous-qa-group.type.d.ts +17 -0
  27. package/dist/components/questions/_shared/types/true-false-correct-group.type.d.ts +1 -0
  28. package/dist/components/questions/_shared/types/true-false-group.type.d.ts +1 -0
  29. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +18 -19
  30. package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.d.ts +1 -1
  31. package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js +16 -17
  32. package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupCreator.js +7 -4
  33. package/dist/components/questions/types/answer-the-question-group/map-answer-the-question-group-data.js +2 -0
  34. package/dist/components/questions/types/answer-the-question-group/transform.js +12 -5
  35. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +17 -11
  36. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +15 -42
  37. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +15 -36
  38. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +9 -32
  39. package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.d.ts +1 -1
  40. package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js +76 -11
  41. package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupCreator.js +54 -6
  42. package/dist/components/questions/types/choose-then-answer-group/map-choose-then-answer-group-data.d.ts +4 -1
  43. package/dist/components/questions/types/choose-then-answer-group/map-choose-then-answer-group-data.js +39 -2
  44. package/dist/components/questions/types/choose-then-answer-group/transform.js +27 -14
  45. package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.d.ts +1 -1
  46. package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.js +76 -7
  47. package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupCreator.js +24 -44
  48. package/dist/components/questions/types/cross-out-word-group/index.d.ts +1 -0
  49. package/dist/components/questions/types/cross-out-word-group/index.js +1 -0
  50. package/dist/components/questions/types/cross-out-word-group/map-cross-out-word-group-data.js +9 -9
  51. package/dist/components/questions/types/cross-out-word-group/tokenize.d.ts +2 -0
  52. package/dist/components/questions/types/cross-out-word-group/tokenize.js +11 -0
  53. package/dist/components/questions/types/cross-out-word-group/transform.js +2 -4
  54. package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.d.ts +2 -1
  55. package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.js +20 -8
  56. package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleCreator.js +68 -11
  57. package/dist/components/questions/types/crossword-puzzle/transform.js +59 -30
  58. package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +26 -31
  59. package/dist/components/questions/types/fill-in-blank-group/FillInBlankAnswerKey.d.ts +6 -0
  60. package/dist/components/questions/types/fill-in-blank-group/FillInBlankAnswerKey.js +17 -0
  61. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupClient.js +32 -28
  62. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.js +109 -26
  63. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupMediaField.js +21 -11
  64. package/dist/components/questions/types/fill-in-blank-group/blank-utils.d.ts +1 -0
  65. package/dist/components/questions/types/fill-in-blank-group/blank-utils.js +4 -1
  66. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +32 -31
  67. package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.d.ts +12 -2
  68. package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.js +188 -46
  69. package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixCreator.js +6 -2
  70. package/dist/components/questions/types/find-words-in-matrix/transform.js +5 -0
  71. package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +4 -3
  72. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +11 -30
  73. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +7 -6
  74. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +2 -1
  75. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +14 -13
  76. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +4 -3
  77. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +4 -3
  78. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +11 -10
  79. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +4 -3
  80. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +13 -11
  81. package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.js +17 -14
  82. package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureClient.js +16 -12
  83. package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureGroupClient.js +14 -14
  84. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js +13 -13
  85. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupClient.js +7 -6
  86. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +8 -26
  87. package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesClient.js +2 -1
  88. package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +6 -5
  89. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardClient.js +2 -1
  90. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +5 -4
  91. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.d.ts +1 -1
  92. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.js +54 -11
  93. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupCreator.js +11 -7
  94. package/dist/components/questions/types/spontaneous-qa-group/creator-helpers.js +8 -2
  95. package/dist/components/questions/types/spontaneous-qa-group/flatten.d.ts +1 -0
  96. package/dist/components/questions/types/spontaneous-qa-group/flatten.js +4 -2
  97. package/dist/components/questions/types/spontaneous-qa-group/map-spontaneous-qa-group-data.js +5 -3
  98. package/dist/components/questions/types/spontaneous-qa-group/register.js +5 -2
  99. package/dist/components/questions/types/spontaneous-qa-group/transform.js +6 -2
  100. package/dist/components/questions/types/true-false/TrueFalseClient.js +5 -54
  101. package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.d.ts +1 -1
  102. package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.js +39 -19
  103. package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.d.ts +1 -1
  104. package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.js +20 -19
  105. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +76 -81
  106. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +17 -16
  107. package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupClient.js +31 -36
  108. package/dist/components/questions/types/word-ordering/WordOrderingClient.js +15 -14
  109. package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.js +9 -8
  110. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +16 -13
  111. package/dist/components/questions/viewer/AnswerTheQuestionGroupViewer.d.ts +3 -2
  112. package/dist/components/questions/viewer/AnswerTheQuestionGroupViewer.js +12 -5
  113. package/dist/components/questions/viewer/ChooseThenAnswerGroupViewer.d.ts +3 -2
  114. package/dist/components/questions/viewer/ChooseThenAnswerGroupViewer.js +4 -3
  115. package/dist/components/questions/viewer/CrossOutWordGroupViewer.d.ts +3 -1
  116. package/dist/components/questions/viewer/CrossOutWordGroupViewer.js +12 -2
  117. package/dist/components/questions/viewer/FillInBlankGroupViewer.d.ts +6 -2
  118. package/dist/components/questions/viewer/FillInBlankGroupViewer.js +13 -7
  119. package/dist/components/questions/viewer/QuestionViewer.d.ts +9 -1
  120. package/dist/components/questions/viewer/QuestionViewer.js +23 -10
  121. package/dist/components/questions/viewer/SpontaneousQaGroupViewer.d.ts +7 -1
  122. package/dist/components/questions/viewer/SpontaneousQaGroupViewer.js +14 -2
  123. package/dist/components/questions/viewer/TrueFalseCorrectGroupViewer.d.ts +3 -2
  124. package/dist/components/questions/viewer/TrueFalseCorrectGroupViewer.js +14 -4
  125. package/dist/components/questions/viewer/TrueFalseGroupViewer.d.ts +5 -2
  126. package/dist/components/questions/viewer/TrueFalseGroupViewer.js +14 -4
  127. package/dist/components/questions/viewer/WordOrderAndMatchGroupViewer.d.ts +6 -2
  128. package/dist/components/questions/viewer/WordOrderAndMatchGroupViewer.js +26 -6
  129. package/dist/components/results/renderers/ReviewAnswerTheQuestionGroupRenderer.js +1 -1
  130. package/dist/components/results/renderers/review-question-body-movers.js +1 -1
  131. package/dist/components/results/review-data.js +8 -1
  132. package/dist/shared/lib/utils/question-reverse-transform.js +39 -5
  133. package/dist/shared/types/questions/answer-the-question-group.d.ts +8 -3
  134. package/dist/shared/types/questions/answer-the-question-group.js +5 -0
  135. package/dist/shared/types/questions/choose-then-answer-group.d.ts +3 -0
  136. package/dist/shared/types/questions/crossword-puzzle.d.ts +1 -0
  137. package/dist/shared/types/questions/find-words-in-matrix.d.ts +1 -0
  138. package/package.json +1 -1
@@ -1,69 +1,211 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useMemo, useState } from 'react';
3
4
  import { Card } from '../../../../components/ui/card';
4
5
  import { Badge } from '../../../../components/ui/badge';
5
- // 10 Distinct pastel highlighter colors for word selections
6
+ import { KidInstruction, KidQuestionCard } from '../../_shared/components/compact';
6
7
  const WORD_COLORS = [
7
- { bg: 'bg-amber-100 text-amber-900 border-amber-400', badge: 'bg-amber-500 text-white', stroke: '#f59e0b' },
8
- { bg: 'bg-emerald-100 text-emerald-900 border-emerald-400', badge: 'bg-emerald-500 text-white', stroke: '#10b981' },
9
- { bg: 'bg-sky-100 text-sky-900 border-sky-400', badge: 'bg-sky-500 text-white', stroke: '#0ea5e9' },
10
- { bg: 'bg-purple-100 text-purple-900 border-purple-400', badge: 'bg-purple-500 text-white', stroke: '#a855f7' },
11
- { bg: 'bg-rose-100 text-rose-900 border-rose-400', badge: 'bg-rose-500 text-white', stroke: '#f43f5e' },
12
- { bg: 'bg-indigo-100 text-indigo-900 border-indigo-400', badge: 'bg-indigo-500 text-white', stroke: '#6366f1' },
13
- { bg: 'bg-teal-100 text-teal-900 border-teal-400', badge: 'bg-teal-500 text-white', stroke: '#14b8a6' },
14
- { bg: 'bg-yellow-100 text-yellow-900 border-yellow-400', badge: 'bg-yellow-500 text-white', stroke: '#eab308' },
15
- { bg: 'bg-pink-100 text-pink-900 border-pink-400', badge: 'bg-pink-500 text-white', stroke: '#ec4899' },
16
- { bg: 'bg-cyan-100 text-cyan-900 border-cyan-400', badge: 'bg-cyan-500 text-white', stroke: '#06b6d4' },
8
+ { bg: 'bg-amber-100 text-amber-900 border-amber-400', badge: 'bg-amber-500 text-white' },
9
+ { bg: 'bg-emerald-100 text-emerald-900 border-emerald-400', badge: 'bg-emerald-500 text-white' },
10
+ { bg: 'bg-sky-100 text-sky-900 border-sky-400', badge: 'bg-sky-500 text-white' },
11
+ { bg: 'bg-purple-100 text-purple-900 border-purple-400', badge: 'bg-purple-500 text-white' },
12
+ { bg: 'bg-rose-100 text-rose-900 border-rose-400', badge: 'bg-rose-500 text-white' },
13
+ { bg: 'bg-indigo-100 text-indigo-900 border-indigo-400', badge: 'bg-indigo-500 text-white' },
14
+ { bg: 'bg-teal-100 text-teal-900 border-teal-400', badge: 'bg-teal-500 text-white' },
15
+ { bg: 'bg-yellow-100 text-yellow-900 border-yellow-400', badge: 'bg-yellow-500 text-white' },
16
+ { bg: 'bg-pink-100 text-pink-900 border-pink-400', badge: 'bg-pink-500 text-white' },
17
+ { bg: 'bg-cyan-100 text-cyan-900 border-cyan-400', badge: 'bg-cyan-500 text-white' },
17
18
  ];
18
- export function FindWordsInMatrixClient({ content, correctAnswer, showSolution = false, }) {
19
+ function cleanWord(value) {
20
+ return (value || '').replace(/[\s\-_]/g, '').toUpperCase();
21
+ }
22
+ function getStraightLinePath(start, end) {
23
+ const dr = end.row - start.row;
24
+ const dc = end.col - start.col;
25
+ const stepR = dr === 0 ? 0 : dr > 0 ? 1 : -1;
26
+ const stepC = dc === 0 ? 0 : dc > 0 ? 1 : -1;
27
+ if (dr === 0 && dc !== 0) {
28
+ return Array.from({ length: Math.abs(dc) + 1 }, (_, i) => ({
29
+ row: start.row,
30
+ col: start.col + i * stepC,
31
+ }));
32
+ }
33
+ if (dc === 0 && dr !== 0) {
34
+ return Array.from({ length: Math.abs(dr) + 1 }, (_, i) => ({
35
+ row: start.row + i * stepR,
36
+ col: start.col,
37
+ }));
38
+ }
39
+ if (Math.abs(dr) === Math.abs(dc) && dr !== 0) {
40
+ return Array.from({ length: Math.abs(dr) + 1 }, (_, i) => ({
41
+ row: start.row + i * stepR,
42
+ col: start.col + i * stepC,
43
+ }));
44
+ }
45
+ return dr === 0 && dc === 0 ? [start] : [];
46
+ }
47
+ function readSelections(value) {
48
+ if (Array.isArray(value)) {
49
+ return value;
50
+ }
51
+ if (value && typeof value === 'object' && Array.isArray(value.words)) {
52
+ return value.words;
53
+ }
54
+ return [];
55
+ }
56
+ function pathLetters(grid, path) {
57
+ return path
58
+ .map((cell) => (grid[cell.row - 1]?.[cell.col - 1] || '').toUpperCase())
59
+ .join('');
60
+ }
61
+ export function FindWordsInMatrixClient({ content, correctAnswer, showSolution = false, userAnswer, onAnswerChange, }) {
19
62
  const rows = content.gridSize?.rows || content.grid?.length || 14;
20
63
  const cols = content.gridSize?.cols || content.grid?.[0]?.length || 14;
21
64
  const grid = content.grid || [];
22
65
  const wordBank = content.wordBank || [];
23
66
  const isCategorize = content.type === 'CATEGORIZE' && Array.isArray(content.categories) && content.categories.length > 0;
24
67
  const categories = content.categories || [];
25
- // Build cell highlight map
26
- const cellColorMap = {};
27
- // Example selection
28
- if (content.exampleSelection?.path) {
29
- for (const c of content.exampleSelection.path) {
30
- cellColorMap[`${c.row}-${c.col}`] = { colorIdx: 0, word: 'Example' };
68
+ const canSelect = !showSolution && Boolean(onAnswerChange);
69
+ const selections = readSelections(userAnswer);
70
+ const [activeWordId, setActiveWordId] = useState(null);
71
+ const [tappedPath, setTappedPath] = useState([]);
72
+ const playableWords = wordBank.filter((word) => !word.isExample);
73
+ const activeWord = playableWords.find((word) => word.id === activeWordId) ||
74
+ playableWords.find((word) => !selections.some((selection) => selection.wordId === word.id || cleanWord(selection.word) === cleanWord(word.word))) ||
75
+ null;
76
+ const targetChars = cleanWord(activeWord?.word).split('').filter(Boolean);
77
+ const tappedChars = tappedPath.map((cell) => (grid[cell.row - 1]?.[cell.col - 1] || '').toUpperCase());
78
+ const isReverseMode = tappedChars.length > 1
79
+ && targetChars.length > 1
80
+ && tappedChars[0] === targetChars[targetChars.length - 1]
81
+ && tappedChars[1] === targetChars[targetChars.length - 2];
82
+ const activeCharSequence = isReverseMode ? [...targetChars].reverse() : targetChars;
83
+ const nextExpectedChar = tappedPath.length < activeCharSequence.length
84
+ ? activeCharSequence[tappedPath.length]
85
+ : null;
86
+ const cellColorMap = useMemo(() => {
87
+ const map = {};
88
+ if (content.exampleSelection?.path) {
89
+ for (const cell of content.exampleSelection.path) {
90
+ map[`${cell.row}-${cell.col}`] = { colorIdx: 0, word: 'Example' };
91
+ }
31
92
  }
32
- }
33
- // Solution paths if visible
34
- if (showSolution && correctAnswer?.words) {
35
- correctAnswer.words.forEach((item, idx) => {
36
- const colorIdx = (idx + 1) % WORD_COLORS.length;
37
- item.path?.forEach((c) => {
38
- cellColorMap[`${c.row}-${c.col}`] = { colorIdx, word: item.word || item.wordId };
93
+ const source = showSolution && correctAnswer?.words ? correctAnswer.words : selections;
94
+ source.forEach((item, index) => {
95
+ const colorIdx = (index + 1) % WORD_COLORS.length;
96
+ item.path?.forEach((cell) => {
97
+ map[`${cell.row}-${cell.col}`] = {
98
+ colorIdx,
99
+ word: item.word || item.wordId || '',
100
+ };
39
101
  });
40
102
  });
41
- }
42
- return (_jsxs("div", { className: "space-y-3", children: [content.title && (_jsx("h3", { className: "text-lg font-bold text-gray-800 dark:text-gray-100", children: content.title })), isCategorize ? (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4", children: categories.map((cat) => {
43
- const wordsInCat = wordBank.filter((w) => w.categoryId === cat.id);
44
- return (_jsxs(Card, { className: "p-3.5 bg-gray-50/70 dark:bg-gray-800/40 border", children: [_jsxs("div", { className: "font-bold text-sm text-gray-800 dark:text-gray-200 border-b pb-1.5 mb-2.5 flex items-center justify-between", children: [_jsx("span", { children: cat.name }), _jsx(Badge, { variant: "outline", className: "text-xs", children: wordsInCat.length })] }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: wordsInCat.map((w, idx) => {
45
- const isExample = Boolean(w.isExample);
46
- return (_jsxs(Badge, { variant: "outline", className: `px-2.5 py-1 text-xs font-medium ${isExample
47
- ? 'line-through bg-amber-50 text-amber-800 border-amber-300'
48
- : 'bg-white dark:bg-gray-800'}`, children: [w.word, isExample && _jsx("span", { className: "ml-1 text-amber-600 font-bold", children: "(Ex)" })] }, w.id || idx));
49
- }) })] }, cat.id));
50
- }) })) : (_jsxs(Card, { className: "p-4 bg-gray-50 dark:bg-gray-800/50 border-dashed border-2", children: [_jsx("div", { className: "text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-3 flex items-center gap-2", children: _jsxs("span", { children: ["Word Bank (", wordBank.length, " words)"] }) }), _jsx("div", { className: "flex flex-wrap gap-2", children: wordBank.map((w, idx) => {
51
- const isExample = Boolean(w.isExample);
52
- return (_jsxs(Badge, { variant: "outline", className: `px-3 py-1.5 text-sm font-medium transition-all ${isExample
53
- ? 'line-through bg-amber-50 text-amber-800 border-amber-300'
54
- : 'bg-white dark:bg-gray-800 hover:shadow-sm'}`, children: [w.word, isExample && (_jsx("span", { className: "ml-1.5 text-xs text-amber-600 font-bold no-underline", children: "(Example)" }))] }, w.id || idx));
55
- }) })] })), _jsx("div", { className: "overflow-x-auto pb-2 flex justify-center", children: _jsx("div", { className: "inline-grid gap-1 p-3 bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-800", style: {
56
- gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`,
57
- }, children: Array.from({ length: rows }).map((_, rIdx) => {
103
+ return map;
104
+ }, [content.exampleSelection, correctAnswer?.words, selections, showSolution]);
105
+ const emit = (next) => {
106
+ onAnswerChange?.({ words: next });
107
+ };
108
+ const completeWord = (path) => {
109
+ if (!activeWord)
110
+ return;
111
+ const target = cleanWord(activeWord.word);
112
+ emit([
113
+ ...selections.filter((selection) => selection.wordId !== activeWord.id && cleanWord(selection.word) !== target),
114
+ {
115
+ wordId: activeWord.id,
116
+ word: activeWord.word,
117
+ categoryId: activeWord.categoryId,
118
+ path,
119
+ },
120
+ ]);
121
+ setTappedPath([]);
122
+ setActiveWordId(null);
123
+ };
124
+ const handleWordClick = (wordId) => {
125
+ if (!canSelect)
126
+ return;
127
+ const already = selections.find((selection) => selection.wordId === wordId);
128
+ if (already) {
129
+ emit(selections.filter((selection) => selection.wordId !== wordId));
130
+ setActiveWordId(null);
131
+ setTappedPath([]);
132
+ return;
133
+ }
134
+ setActiveWordId(wordId);
135
+ setTappedPath([]);
136
+ };
137
+ const handleCellClick = (row, col) => {
138
+ if (!canSelect || !activeWord || targetChars.length === 0)
139
+ return;
140
+ const char = (grid[row - 1]?.[col - 1] || '').toUpperCase();
141
+ const last = tappedPath[tappedPath.length - 1];
142
+ if (last && last.row === row && last.col === col) {
143
+ setTappedPath((prev) => prev.slice(0, -1));
144
+ return;
145
+ }
146
+ if (tappedPath.length === 0) {
147
+ if (char === targetChars[0] || char === targetChars[targetChars.length - 1]) {
148
+ setTappedPath([{ row, col }]);
149
+ }
150
+ return;
151
+ }
152
+ const existingIdx = tappedPath.findIndex((cell) => cell.row === row && cell.col === col);
153
+ if (existingIdx >= 0) {
154
+ setTappedPath((prev) => prev.slice(0, existingIdx + 1));
155
+ return;
156
+ }
157
+ const straightPath = getStraightLinePath(tappedPath[0], { row, col });
158
+ const target = targetChars.join('');
159
+ const letters = pathLetters(grid, straightPath);
160
+ if (straightPath.length === targetChars.length
161
+ && (letters === target || [...letters].reverse().join('') === target)) {
162
+ completeWord(straightPath);
163
+ return;
164
+ }
165
+ const prev = tappedPath[tappedPath.length - 1];
166
+ const isAdjacent = Math.abs(row - prev.row) <= 1 && Math.abs(col - prev.col) <= 1;
167
+ if (isAdjacent && char === nextExpectedChar) {
168
+ const nextPath = [...tappedPath, { row, col }];
169
+ if (nextPath.length === activeCharSequence.length) {
170
+ completeWord(nextPath);
171
+ return;
172
+ }
173
+ setTappedPath(nextPath);
174
+ }
175
+ };
176
+ const renderWordBadge = (word, index) => {
177
+ const isExample = Boolean(word.isExample);
178
+ const isFound = selections.some((selection) => selection.wordId === word.id || cleanWord(selection.word) === cleanWord(word.word));
179
+ const isActive = activeWord?.id === word.id;
180
+ return (_jsxs("button", { type: "button", disabled: !canSelect || isExample, onClick: () => handleWordClick(word.id), className: `rounded-full border px-3 py-1.5 text-sm font-medium ${isExample
181
+ ? 'border-amber-300 bg-amber-50 text-amber-800 line-through'
182
+ : isFound
183
+ ? 'border-emerald-400 bg-emerald-50 text-emerald-800'
184
+ : isActive
185
+ ? 'border-blue-500 bg-blue-50 text-blue-800'
186
+ : 'border-gray-200 bg-white'}`, children: [word.word, isExample ? _jsx("span", { className: "ml-1 font-bold", children: "(Ex)" }) : null] }, word.id || index));
187
+ };
188
+ const questionInstruction = content.instruction?.trim();
189
+ return (_jsxs(KidQuestionCard, { title: content.title || 'Tìm từ trong lưới', children: [questionInstruction ? (_jsx(KidInstruction, { children: questionInstruction })) : null, canSelect ? (_jsx("p", { className: "text-xs font-medium text-slate-600", children: activeWord ? (_jsxs(_Fragment, { children: ["\u0110ang t\u00ECm ", _jsx("strong", { children: activeWord.word }), ' — ', tappedPath.length === 0
190
+ ? `bấm chữ cái đầu (${targetChars[0]}) hoặc cuối (${targetChars[targetChars.length - 1]}) trên lưới.`
191
+ : nextExpectedChar
192
+ ? `tiếp theo bấm chữ ${nextExpectedChar}, hoặc bấm thẳng ô cuối của từ.`
193
+ : 'bấm ô cuối để hoàn thành từ.'] })) : (_jsx(_Fragment, { children: "Ch\u1ECDn m\u1ED9t t\u1EEB trong danh s\u00E1ch, r\u1ED3i b\u1EA5m t\u1EEBng ch\u1EEF tr\u00EAn l\u01B0\u1EDBi (ho\u1EB7c \u00F4 \u0111\u1EA7u r\u1ED3i \u00F4 cu\u1ED1i)." })) })) : null, isCategorize ? (_jsx("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3", children: categories.map((cat) => {
194
+ const wordsInCat = wordBank.filter((word) => word.categoryId === cat.id);
195
+ return (_jsxs(Card, { className: "border bg-gray-50/70 p-3.5", children: [_jsxs("div", { className: "mb-2.5 flex items-center justify-between border-b pb-1.5 text-sm font-bold", children: [_jsx("span", { children: cat.name }), _jsx(Badge, { variant: "outline", className: "text-xs", children: wordsInCat.length })] }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: wordsInCat.map((word, index) => renderWordBadge(word, index)) })] }, cat.id));
196
+ }) })) : (_jsxs(Card, { className: "border-2 border-dashed bg-gray-50 p-4", children: [_jsxs("div", { className: "mb-3 text-xs font-semibold uppercase tracking-wider text-gray-500", children: ["Word Bank (", wordBank.length, " words)"] }), _jsx("div", { className: "flex flex-wrap gap-2", children: wordBank.map((word, index) => renderWordBadge(word, index)) })] })), _jsx("div", { className: "flex justify-center overflow-x-auto pb-2", children: _jsx("div", { className: "inline-grid gap-1 rounded-xl border border-gray-200 bg-white p-3 shadow-sm", style: { gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))` }, children: Array.from({ length: rows }).map((_, rIdx) => {
58
197
  const r = rIdx + 1;
59
198
  return Array.from({ length: cols }).map((_, cIdx) => {
60
199
  const c = cIdx + 1;
61
200
  const char = grid[rIdx]?.[cIdx] || '';
62
201
  const highlight = cellColorMap[`${r}-${c}`];
63
202
  const colorConfig = highlight ? WORD_COLORS[highlight.colorIdx] : null;
64
- return (_jsx("div", { className: `h-9 w-9 sm:h-11 sm:w-11 flex items-center justify-center font-bold text-sm sm:text-base select-none rounded-md transition-colors border ${colorConfig
65
- ? `${colorConfig.bg} font-extrabold shadow-sm scale-95`
66
- : 'border-gray-100 dark:border-gray-800 bg-gray-50/50 dark:bg-gray-800/30 text-gray-700 dark:text-gray-300'}`, title: highlight ? `${highlight.word} (${r},${c})` : `(${r},${c})`, children: char || '·' }, `${r}-${c}`));
203
+ const isTapped = tappedPath.some((cell) => cell.row === r && cell.col === c);
204
+ return (_jsx("button", { type: "button", disabled: !canSelect, onClick: () => handleCellClick(r, c), className: `flex h-9 w-9 items-center justify-center rounded-md border text-sm font-bold sm:h-11 sm:w-11 sm:text-base ${colorConfig
205
+ ? `${colorConfig.bg} shadow-sm`
206
+ : isTapped
207
+ ? 'border-blue-500 bg-blue-50 text-blue-800'
208
+ : 'border-gray-100 bg-gray-50/50 text-gray-700'}`, title: highlight ? `${highlight.word} (${r},${c})` : `(${r},${c})`, children: char || '·' }, `${r}-${c}`));
67
209
  });
68
210
  }) }) })] }));
69
211
  }
@@ -70,7 +70,9 @@ export function FindWordsInMatrixCreator({ initialData, onChange, onUnsavedChang
70
70
  return {
71
71
  type,
72
72
  title: content.title || '',
73
- instruction: initialData?.instruction || 'Find these words in the wordsearch box.',
73
+ instruction: content.instruction
74
+ || initialData?.instruction
75
+ || 'Find these words in the wordsearch box.',
74
76
  rows,
75
77
  cols,
76
78
  grid,
@@ -94,6 +96,7 @@ export function FindWordsInMatrixCreator({ initialData, onChange, onUnsavedChang
94
96
  content: {
95
97
  type: next.type,
96
98
  title: next.title,
99
+ instruction: next.instruction,
97
100
  gridSize: { rows: next.rows, cols: next.cols },
98
101
  grid: next.grid,
99
102
  ...(next.type === 'CATEGORIZE' ? { categories: next.categories } : {}),
@@ -239,6 +242,7 @@ export function FindWordsInMatrixCreator({ initialData, onChange, onUnsavedChang
239
242
  return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(FindWordsInMatrixClient, { content: {
240
243
  type: state.type,
241
244
  title: state.title,
245
+ instruction: state.instruction,
242
246
  gridSize: { rows: state.rows, cols: state.cols },
243
247
  grid: state.grid,
244
248
  ...(state.type === 'CATEGORIZE' ? { categories: state.categories } : {}),
@@ -275,7 +279,7 @@ export function FindWordsInMatrixCreator({ initialData, onChange, onUnsavedChang
275
279
  ? 'border-blue-500 bg-blue-50/60 dark:bg-blue-950/30 text-blue-900 dark:text-blue-200 ring-2 ring-blue-400'
276
280
  : 'border-gray-200 hover:bg-gray-50 dark:border-gray-700 text-gray-700 dark:text-gray-300'}`, children: [_jsx("div", { className: "p-2 rounded-lg bg-blue-100 dark:bg-blue-900 text-blue-600", children: _jsx(ListFilter, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("div", { className: "font-bold text-sm", children: "1. Danh s\u00E1ch t\u1EEB (Word Bank)" }), _jsx("div", { className: "text-xs text-gray-500", children: "T\u00ECm c\u00E1c t\u1EEB c\u00F3 s\u1EB5n trong b\u1EA3ng ch\u1EEF c\u00E1i (VD: Gadgets)." })] })] }), _jsxs("button", { type: "button", onClick: () => updateState((p) => ({ ...p, type: 'CATEGORIZE' })), className: `flex items-center gap-3 p-3 rounded-xl border text-left transition-all ${state.type === 'CATEGORIZE'
277
281
  ? 'border-emerald-500 bg-emerald-50/60 dark:bg-emerald-950/30 text-emerald-900 dark:text-emerald-200 ring-2 ring-emerald-400'
278
- : 'border-gray-200 hover:bg-gray-50 dark:border-gray-700 text-gray-700 dark:text-gray-300'}`, children: [_jsx("div", { className: "p-2 rounded-lg bg-emerald-100 dark:bg-emerald-900 text-emerald-600", children: _jsx(Layers, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("div", { className: "font-bold text-sm", children: "2. T\u00ECm t\u1EEB & Ph\u00E2n nh\u00F3m (Categorize)" }), _jsx("div", { className: "text-xs text-gray-500", children: "T\u00ECm t\u1EEB \u1EA9n v\u00E0 x\u1EBFp v\u00E0o c\u00E1c nh\u00F3m ti\u00EAu \u0111\u1EC1 (VD: Plants / Animals)." })] })] })] })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4 pt-2 border-t", children: [_jsxs("div", { className: "md:col-span-2", children: [_jsx(Label, { className: "font-semibold", children: "Ti\u00EAu \u0111\u1EC1 b\u00E0i t\u1EADp" }), _jsx(Input, { value: state.title || '', onChange: (e) => updateState((p) => ({ ...p, title: e.target.value })), placeholder: state.type === 'CATEGORIZE' ? 'VD: Plants and Animals Word Search' : 'VD: Technology Gadgets Word Search', className: "mt-1" })] }), _jsxs("div", { children: [_jsx(Label, { className: "font-semibold", children: "K\u00EDch th\u01B0\u1EDBc ma tr\u1EADn (H\u00E0ng x C\u1ED9t)" }), _jsxs("div", { className: "flex items-center gap-2 mt-1", children: [_jsx(Input, { type: "number", min: 4, max: 25, value: state.rows, onChange: (e) => handleResize(Number(e.target.value), state.cols), className: "w-20 text-center font-bold" }), _jsx("span", { className: "font-bold text-gray-400", children: "\u00D7" }), _jsx(Input, { type: "number", min: 4, max: 25, value: state.cols, onChange: (e) => handleResize(state.rows, Number(e.target.value)), className: "w-20 text-center font-bold" })] })] })] })] }), state.type === 'CATEGORIZE' && (_jsxs(Card, { className: "overflow-hidden border-0 bg-white p-4 space-y-3 shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsxs("h4", { className: "font-bold text-sm text-emerald-900 dark:text-emerald-300 flex items-center gap-1.5", children: [_jsx(Layers, { className: "h-4 w-4 text-emerald-600" }), "C\u00E1c nh\u00F3m danh m\u1EE5c (Categories / Headings)"] }), _jsx("p", { className: "text-xs text-gray-500", children: "T\u1EA1o c\u00E1c c\u1ED9t ti\u00EAu \u0111\u1EC1 \u0111\u1EC3 h\u1ECDc sinh ph\u00E2n lo\u1EA1i t\u1EEB v\u00E0o (t\u1ED1i thi\u1EC3u 2 nh\u00F3m)." })] }), _jsxs(Button, { type: "button", size: "sm", variant: "outline", onClick: handleAddCategory, className: "gap-1 text-xs", children: [_jsx(FolderPlus, { className: "h-3.5 w-3.5" }), " Th\u00EAm nh\u00F3m"] })] }), _jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3", children: state.categories.map((cat, idx) => (_jsxs("div", { className: "flex items-center gap-2 p-2.5 bg-white dark:bg-gray-900 rounded-lg border shadow-xs", children: [_jsx(Badge, { variant: "secondary", className: "font-mono text-xs", children: idx + 1 }), _jsx(Input, { value: cat.name, onChange: (e) => {
282
+ : 'border-gray-200 hover:bg-gray-50 dark:border-gray-700 text-gray-700 dark:text-gray-300'}`, children: [_jsx("div", { className: "p-2 rounded-lg bg-emerald-100 dark:bg-emerald-900 text-emerald-600", children: _jsx(Layers, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("div", { className: "font-bold text-sm", children: "2. T\u00ECm t\u1EEB & Ph\u00E2n nh\u00F3m (Categorize)" }), _jsx("div", { className: "text-xs text-gray-500", children: "T\u00ECm t\u1EEB \u1EA9n v\u00E0 x\u1EBFp v\u00E0o c\u00E1c nh\u00F3m ti\u00EAu \u0111\u1EC1 (VD: Plants / Animals)." })] })] })] })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4 pt-2 border-t", children: [_jsxs("div", { className: "md:col-span-2", children: [_jsx(Label, { className: "font-semibold", children: "Ti\u00EAu \u0111\u1EC1 b\u00E0i t\u1EADp" }), _jsx(Input, { value: state.title || '', onChange: (e) => updateState((p) => ({ ...p, title: e.target.value })), placeholder: state.type === 'CATEGORIZE' ? 'VD: Plants and Animals Word Search' : 'VD: Technology Gadgets Word Search', className: "mt-1" })] }), _jsxs("div", { className: "md:col-span-3", children: [_jsx(Label, { className: "font-semibold", children: "H\u01B0\u1EDBng d\u1EABn (Instruction)" }), _jsx(Input, { value: state.instruction || '', onChange: (e) => updateState((p) => ({ ...p, instruction: e.target.value })), placeholder: "\u2605 Find nine food items in the word search.", className: "mt-1" })] }), _jsxs("div", { children: [_jsx(Label, { className: "font-semibold", children: "K\u00EDch th\u01B0\u1EDBc ma tr\u1EADn (H\u00E0ng x C\u1ED9t)" }), _jsxs("div", { className: "flex items-center gap-2 mt-1", children: [_jsx(Input, { type: "number", min: 4, max: 25, value: state.rows, onChange: (e) => handleResize(Number(e.target.value), state.cols), className: "w-20 text-center font-bold" }), _jsx("span", { className: "font-bold text-gray-400", children: "\u00D7" }), _jsx(Input, { type: "number", min: 4, max: 25, value: state.cols, onChange: (e) => handleResize(state.rows, Number(e.target.value)), className: "w-20 text-center font-bold" })] })] })] })] }), state.type === 'CATEGORIZE' && (_jsxs(Card, { className: "overflow-hidden border-0 bg-white p-4 space-y-3 shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsxs("h4", { className: "font-bold text-sm text-emerald-900 dark:text-emerald-300 flex items-center gap-1.5", children: [_jsx(Layers, { className: "h-4 w-4 text-emerald-600" }), "C\u00E1c nh\u00F3m danh m\u1EE5c (Categories / Headings)"] }), _jsx("p", { className: "text-xs text-gray-500", children: "T\u1EA1o c\u00E1c c\u1ED9t ti\u00EAu \u0111\u1EC1 \u0111\u1EC3 h\u1ECDc sinh ph\u00E2n lo\u1EA1i t\u1EEB v\u00E0o (t\u1ED1i thi\u1EC3u 2 nh\u00F3m)." })] }), _jsxs(Button, { type: "button", size: "sm", variant: "outline", onClick: handleAddCategory, className: "gap-1 text-xs", children: [_jsx(FolderPlus, { className: "h-3.5 w-3.5" }), " Th\u00EAm nh\u00F3m"] })] }), _jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3", children: state.categories.map((cat, idx) => (_jsxs("div", { className: "flex items-center gap-2 p-2.5 bg-white dark:bg-gray-900 rounded-lg border shadow-xs", children: [_jsx(Badge, { variant: "secondary", className: "font-mono text-xs", children: idx + 1 }), _jsx(Input, { value: cat.name, onChange: (e) => {
279
283
  const val = e.target.value;
280
284
  updateState((p) => ({
281
285
  ...p,
@@ -4,11 +4,15 @@ export const transformFindWordsInMatrix = (question) => {
4
4
  let content;
5
5
  let correctAnswer;
6
6
  const modeType = contentData?.type || (Array.isArray(contentData?.categories) && contentData.categories.length > 0 ? 'CATEGORIZE' : 'WORD_BANK');
7
+ const instruction = typeof contentData?.instruction === 'string' && contentData.instruction.trim()
8
+ ? contentData.instruction.trim()
9
+ : undefined;
7
10
  if (contentData?.gridSize && Array.isArray(contentData?.grid) && Array.isArray(contentData?.wordBank)) {
8
11
  // Already in API shape
9
12
  content = {
10
13
  type: modeType,
11
14
  title: contentData.title || '',
15
+ ...(instruction ? { instruction } : {}),
12
16
  gridSize: {
13
17
  rows: Number(contentData.gridSize.rows) || 14,
14
18
  cols: Number(contentData.gridSize.cols) || 14,
@@ -61,6 +65,7 @@ export const transformFindWordsInMatrix = (question) => {
61
65
  content = {
62
66
  type: modeType,
63
67
  title: contentData.title || '',
68
+ ...(instruction ? { instruction } : {}),
64
69
  gridSize: { rows, cols },
65
70
  grid,
66
71
  ...(modeType === 'CATEGORIZE' ? { categories } : {}),
@@ -5,6 +5,7 @@ import { cn } from '../../../../shared/lib/utils';
5
5
  import { Input } from '../../../../components/ui/input';
6
6
  import { Lightbulb } from 'lucide-react';
7
7
  import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
8
+ import { KidInstruction, KidQuestionCard } from '../../_shared/components/compact';
8
9
  export function LabelThePictureClient({ questionData, userAnswer = {}, isReviewMode = false, explanation, onSubmit, }) {
9
10
  const { imageUrl, wordBank, labels } = questionData;
10
11
  // Initialize answers with pre-filled values
@@ -68,7 +69,7 @@ export function LabelThePictureClient({ questionData, userAnswer = {}, isReviewM
68
69
  });
69
70
  // Sort labels by number
70
71
  const sortedLabels = [...labels].sort((a, b) => a.number - b.number);
71
- return (_jsxs("div", { className: "space-y-3", children: [_jsx("div", { className: "flex items-center justify-between", children: _jsxs("div", { children: [_jsx("h2", { className: "text-lg font-semibold text-gray-900", children: "Label the Picture" }), _jsx("p", { className: "text-sm text-gray-500", children: "Nh\u00ECn tranh v\u00E0 \u0111i\u1EC1n t\u1EEB v\u00E0o c\u00E1c v\u1ECB tr\u00ED t\u01B0\u01A1ng \u1EE9ng" })] }) }), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsx("div", { className: "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: "h-2.5 w-2.5 rounded-full bg-red-400" }), _jsx("div", { className: "h-2.5 w-2.5 rounded-full bg-yellow-400" }), _jsx("div", { className: "h-2.5 w-2.5 rounded-full bg-green-400" }), _jsx("span", { className: "ml-3 text-sm font-medium text-gray-500", children: "H\u00ECnh \u1EA3nh" })] }) }), _jsx("div", { className: "flex justify-center bg-gradient-to-br from-slate-50 to-gray-100 p-4", children: _jsxs("div", { className: "relative max-h-[400px] w-full max-w-2xl overflow-hidden rounded-lg", children: [isLoadingUrl && (_jsx("div", { className: "flex h-64 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500" }) })), !isLoadingUrl && (previewUrl || imageUrl) ? (
72
+ return (_jsxs(KidQuestionCard, { title: "Label the Picture", children: [_jsx(KidInstruction, { children: "Nh\u00ECn tranh v\u00E0 \u0111i\u1EC1n t\u1EEB v\u00E0o c\u00E1c v\u1ECB tr\u00ED t\u01B0\u01A1ng \u1EE9ng" }), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsx("div", { className: "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: "h-2.5 w-2.5 rounded-full bg-red-400" }), _jsx("div", { className: "h-2.5 w-2.5 rounded-full bg-yellow-400" }), _jsx("div", { className: "h-2.5 w-2.5 rounded-full bg-green-400" }), _jsx("span", { className: "ml-3 text-sm font-medium text-gray-500", children: "H\u00ECnh \u1EA3nh" })] }) }), _jsx("div", { className: "flex justify-center bg-gradient-to-br from-slate-50 to-gray-100 p-4", children: _jsxs("div", { className: "relative max-h-[400px] w-full max-w-2xl overflow-hidden rounded-lg", children: [isLoadingUrl && (_jsx("div", { className: "flex h-64 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500" }) })), !isLoadingUrl && (previewUrl || imageUrl) ? (
72
73
  /* eslint-disable-next-line @next/next/no-img-element */
73
74
  _jsx("img", { src: previewUrl || imageUrl, alt: "Label the picture", className: "h-full w-full object-contain", onError: (e) => {
74
75
  console.error('Failed to load image:', imageUrl, 'previewUrl:', previewUrl);
@@ -82,8 +83,8 @@ export function LabelThePictureClient({ questionData, userAnswer = {}, isReviewM
82
83
  return (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-lg border-2 px-3 py-1.5 text-sm font-semibold transition-all', isUsedAsPreFilled
83
84
  ? 'border-blue-300 bg-blue-50 text-blue-700'
84
85
  : isUsedByUser
85
- ? 'border-green-300 bg-green-50 text-green-700'
86
- : 'border-gray-200 bg-white text-gray-700 hover:border-gray-300 hover:shadow-sm'), children: [word, isUsedAsPreFilled && (_jsx("span", { className: "rounded bg-blue-200 px-1 py-0.5 text-xs", children: "s\u1EB5n" })), isUsedByUser && !isUsedAsPreFilled && (_jsx("span", { className: "rounded bg-green-200 px-1 py-0.5 text-xs", children: "\u2713" }))] }, index));
86
+ ? 'border-blue-500 bg-blue-50 text-blue-800 ring-2 ring-blue-100'
87
+ : 'border-gray-200 bg-white text-gray-700 hover:border-gray-300 hover:shadow-sm'), children: [word, isUsedAsPreFilled && (_jsx("span", { className: "rounded bg-blue-200 px-1 py-0.5 text-xs", children: "s\u1EB5n" })), isUsedByUser && !isUsedAsPreFilled && (_jsx("span", { className: "rounded bg-blue-200 px-1 py-0.5 text-xs", children: "\u2713" }))] }, index));
87
88
  }) }) })] })), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("svg", { className: "h-5 w-5 text-blue-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" }) }), _jsx("h3", { className: "text-sm font-semibold text-gray-800", children: "\u0110i\u1EC1n t\u1EEB v\u00E0o c\u00E1c v\u1ECB tr\u00ED" })] }), _jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-3", children: sortedLabels.map((label) => {
88
89
  const userAnswerValue = answers[label.id] || '';
89
90
  const correctAnswer = label.correctAnswer.trim().toLowerCase();
@@ -1,10 +1,11 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect } from 'react';
4
- import { Check, X, Play, Pause, Volume2, ImageIcon } from 'lucide-react';
4
+ import { Play, Pause, Volume2, ImageIcon } from 'lucide-react';
5
5
  import { Button } from '../../../../components/ui/button';
6
6
  import { usePresignedFileUrl, useAudioPreview } from '../../../../shared/lib/hooks';
7
7
  import { cn } from '../../../../shared/lib/utils';
8
+ import { KidChoiceRow, KidQuestionCard, optionLetter } from '../../_shared/components/compact';
8
9
  // ─── Sub-component: Audio player ─────────────────────────────────────────────
9
10
  function QuestionAudioPlayer({ audioUrl }) {
10
11
  const { previewUrl } = usePresignedFileUrl(audioUrl);
@@ -47,32 +48,12 @@ export function ListenAndChooseFromAnswerGroupClient({ questionData, onSubmit, i
47
48
  const hasAnswered = !!selectedAnswer;
48
49
  const selectedOption = questionData.options.find((o) => o.id === selectedAnswer);
49
50
  const isAnswerCorrect = isReviewMode && selectedAnswer === resolvedCorrectAnswer;
50
- 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: [_jsxs("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(Volume2, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 ch\u1ECDn trong nh\u00F3m \u0111\u00E1p \u00E1n" })] }), isReviewMode && hasAnswered && (_jsx("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isAnswerCorrect
51
- ? 'bg-green-100 text-green-700'
52
- : 'bg-red-100 text-red-700'), children: isAnswerCorrect ? (_jsxs(_Fragment, { children: [_jsx(Check, { className: "h-3.5 w-3.5" }), " \u0110\u00FAng"] })) : (_jsxs(_Fragment, { children: [_jsx(X, { className: "h-3.5 w-3.5" }), " Sai"] })) }))] }), _jsxs("div", { className: "p-3 space-y-3", children: [questionData.audioUrl && (_jsx(QuestionAudioPlayer, { audioUrl: questionData.audioUrl })), _jsxs("div", { className: "space-y-2", children: [_jsx("span", { className: "text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng" }), _jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-3", children: questionData.options.map((option, optIndex) => {
53
- const { isSelected, isCorrect, isWrong } = getOptionStatus(option.id);
54
- return (_jsxs("button", { type: "button", onClick: () => handleOptionSelect(option.id), disabled: isReviewMode, className: cn('group relative flex flex-col items-center gap-2 rounded-xl border-2 p-3 transition-all', isReviewMode
55
- ? isCorrect
56
- ? 'border-green-400 bg-green-50 shadow-md'
57
- : isWrong
58
- ? 'border-red-400 bg-red-50'
59
- : 'border-gray-200 bg-gray-50'
60
- : isSelected
61
- ? 'border-blue-500 bg-blue-50 shadow-md ring-2 ring-blue-100'
62
- : 'border-gray-200 bg-white hover:border-gray-400 hover:shadow-sm cursor-pointer'), children: [_jsx("div", { className: cn('flex h-7 w-7 items-center justify-center rounded-lg text-sm font-bold shadow-sm', isReviewMode
63
- ? isCorrect
64
- ? 'bg-green-500 text-white'
65
- : isWrong
66
- ? 'bg-red-500 text-white'
67
- : 'bg-gray-200 text-gray-600'
68
- : isSelected
69
- ? 'bg-blue-500 text-white'
70
- : 'bg-gray-100 text-gray-600'), children: String.fromCharCode(65 + optIndex) }), _jsx(OptionImage, { imageUrl: option.imageUrl }), option.label && (_jsx("p", { className: cn('text-xs text-center font-medium', isReviewMode
71
- ? isCorrect ? 'text-green-700' : isWrong ? 'text-red-700' : 'text-gray-600'
72
- : isSelected ? 'text-blue-700' : 'text-gray-600'), children: option.label })), isReviewMode && (isCorrect || isWrong) && (_jsx("div", { className: cn('absolute top-1 right-1 flex h-5 w-5 items-center justify-center rounded-full shadow-sm', isCorrect ? 'bg-green-500' : 'bg-red-500'), children: isCorrect ? (_jsx(Check, { className: "h-3.5 w-3.5 text-white" })) : (_jsx(X, { className: "h-3.5 w-3.5 text-white" })) })), !isReviewMode && isSelected && (_jsx("div", { className: "absolute top-1 right-1 flex h-5 w-5 items-center justify-center rounded-full bg-blue-500 shadow-sm", children: _jsx(Check, { className: "h-3.5 w-3.5 text-white" }) }))] }, option.id));
73
- }) })] }), hasAnswered && selectedOption && (_jsxs("div", { className: cn('flex items-center gap-2 rounded-lg border p-3', isReviewMode
74
- ? isAnswerCorrect
75
- ? 'border-green-200 bg-green-50'
76
- : 'border-red-200 bg-red-50'
77
- : 'border-blue-200 bg-blue-50'), children: [_jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["\u0110\u00E3 ch\u1ECDn: ", _jsx("strong", { children: selectedOption.label || selectedOption.id })] }), isReviewMode && (_jsx("span", { className: cn('text-xs font-semibold', isAnswerCorrect ? 'text-green-600' : 'text-red-600'), children: isAnswerCorrect ? '✓ Chính xác' : '✗ Không chính xác' }))] })), !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", 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: "H\u00E3y ch\u1ECDn m\u1ED9t \u0111\u00E1p \u00E1n" })] })), isReviewMode && explanation && (_jsxs("div", { className: "rounded-lg border border-indigo-200 bg-indigo-50 p-4", children: [_jsx("p", { className: "text-sm font-semibold text-indigo-800", children: "Gi\u1EA3i th\u00EDch:" }), _jsx("p", { className: "mt-1 text-sm text-indigo-700", children: explanation })] }))] })] })] }));
51
+ 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, { icon: _jsx(Volume2, { className: "h-3.5 w-3.5" }), title: "Nghe v\u00E0 ch\u1ECDn trong nh\u00F3m \u0111\u00E1p \u00E1n", review: isReviewMode && hasAnswered ? (isAnswerCorrect ? 'correct' : 'wrong') : null, children: [questionData.audioUrl && (_jsx(QuestionAudioPlayer, { audioUrl: questionData.audioUrl })), _jsx("div", { className: "space-y-1.5", children: questionData.options.map((option, optIndex) => {
52
+ const { isSelected, isCorrect } = getOptionStatus(option.id);
53
+ return (_jsx(KidChoiceRow, { letter: optionLetter(optIndex), selected: isSelected, isReviewMode: isReviewMode, isCorrect: isCorrect, onClick: () => handleOptionSelect(option.id), children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(OptionImage, { imageUrl: option.imageUrl }), option.label ? _jsx("span", { children: option.label }) : null] }) }, option.id));
54
+ }) }), hasAnswered && selectedOption && (_jsxs("div", { className: cn('flex items-center gap-2 rounded-lg border p-3', isReviewMode
55
+ ? isAnswerCorrect
56
+ ? 'border-green-200 bg-green-50'
57
+ : 'border-red-200 bg-red-50'
58
+ : 'border-blue-200 bg-blue-50'), children: [_jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["\u0110\u00E3 ch\u1ECDn: ", _jsx("strong", { children: selectedOption.label || selectedOption.id })] }), isReviewMode && (_jsx("span", { className: cn('text-xs font-semibold', isAnswerCorrect ? 'text-green-600' : 'text-red-600'), children: isAnswerCorrect ? '✓ Chính xác' : '✗ Không chính xác' }))] })), !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", 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: "H\u00E3y ch\u1ECDn m\u1ED9t \u0111\u00E1p \u00E1n" })] })), isReviewMode && explanation && (_jsxs("div", { className: "rounded-lg border border-indigo-200 bg-indigo-50 p-4", children: [_jsx("p", { className: "text-sm font-semibold text-indigo-800", children: "Gi\u1EA3i th\u00EDch:" }), _jsx("p", { className: "mt-1 text-sm text-indigo-700", children: explanation })] }))] })] }));
78
59
  }
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Mic, Volume2, CheckCircle2, Play, Pause, 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 { ArrowIndicatorSVG } from './ArrowIndicator';
7
8
  // ─── Sub-component: Custom Audio Player ──────────────────────────────────────
8
9
  function QuestionAudioPlayer({ audioUrl }) {
@@ -13,10 +14,10 @@ function QuestionAudioPlayer({ audioUrl }) {
13
14
  // ─── Main Client Component ───────────────────────────────────────────────────
14
15
  export function ListenAndSpeakAnswerClient({ questionData, isReviewMode = false, }) {
15
16
  const { previewUrl: backgroundUrl } = usePresignedFileUrl(questionData.backgroundImageUrl || '');
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(Mic, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 tr\u1EA3 l\u1EDDi" })] }) }), _jsxs("div", { className: "p-3 space-y-3", children: [questionData.audioUrl ? (_jsx(QuestionAudioPlayer, { audioUrl: questionData.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" })] })), backgroundUrl && questionData.arrowNode && (_jsxs("div", { className: "relative w-full max-w-3xl mx-auto rounded-xl overflow-hidden shadow-sm border border-gray-200 bg-gray-50", children: [_jsx("img", { src: backgroundUrl, alt: "Background", className: "w-full object-contain pointer-events-none" }), _jsx("div", { className: "absolute", style: {
17
- left: `${questionData.arrowNode.x}%`,
18
- top: `${questionData.arrowNode.y}%`,
19
- width: `${questionData.arrowNode.width}%`,
20
- height: `${questionData.arrowNode.height}%`,
21
- }, children: _jsx("div", { className: "flex h-full w-full items-center justify-center overflow-hidden p-[6%]", children: _jsx(ArrowIndicatorSVG, { idSuffix: "-client", animated: true, className: "w-full h-full" }) }) })] })), _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 && questionData.expectedAnswers && questionData.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: questionData.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 && questionData.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: questionData.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 tr\u1EA3 l\u1EDDi", icon: _jsx(Mic, { className: "h-3.5 w-3.5" }), children: [questionData.audioUrl ? (_jsx(QuestionAudioPlayer, { audioUrl: questionData.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" })] })), backgroundUrl && questionData.arrowNode && (_jsxs("div", { className: "relative w-full max-w-3xl mx-auto rounded-xl overflow-hidden shadow-sm border border-gray-200 bg-gray-50", children: [_jsx("img", { src: backgroundUrl, alt: "Background", className: "w-full object-contain pointer-events-none" }), _jsx("div", { className: "absolute", style: {
18
+ left: `${questionData.arrowNode.x}%`,
19
+ top: `${questionData.arrowNode.y}%`,
20
+ width: `${questionData.arrowNode.width}%`,
21
+ height: `${questionData.arrowNode.height}%`,
22
+ }, children: _jsx("div", { className: "flex h-full w-full items-center justify-center overflow-hidden p-[6%]", children: _jsx(ArrowIndicatorSVG, { idSuffix: "-client", animated: true, className: "w-full h-full" }) }) })] })), _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 && questionData.expectedAnswers && questionData.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: questionData.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 && questionData.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: questionData.explanation })] }))] })] }));
22
23
  }
@@ -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, GitCompareArrows } from 'lucide-react';
4
4
  import { Button } from '../../../../components/ui/button';
5
+ import { KidQuestionCard } from '../../_shared/components/compact';
5
6
  import { useCompareImagesGroup } from '../../_shared/hooks/useCompareImagesGroup';
6
7
  import { usePresignedFileUrl, useAudioPreview } from '../../../../shared/lib/hooks';
7
8
  // ─── Sub-component: Custom Audio Player ──────────────────────────────────────
@@ -28,5 +29,5 @@ export function ListenAndSpeakCompareImagesClient({ questionData, isReviewMode =
28
29
  // fall back to store values (exam-taking mode populates the store)
29
30
  const candidateImageUrl = questionData.candidateImageUrl || storeCandidateImageUrl;
30
31
  const examinerImageUrl = questionData.examinerImageUrl || storeExaminerImageUrl;
31
- 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(GitCompareArrows, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 n\u00F3i (So s\u00E1nh h\u00ECnh \u1EA3nh)" })] }) }), _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" })] })), (candidateImageUrl || examinerImageUrl) && (_jsxs("div", { className: "space-y-4 bg-gradient-to-br from-amber-100 to-orange-100 p-5 rounded-2xl border border-amber-200 shadow-sm", children: [candidateImageUrl && (_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "flex justify-center", children: _jsx("span", { className: "inline-block rounded-lg border-2 border-amber-400 bg-gradient-to-r from-gray-600 to-gray-700 px-8 py-2 text-sm font-bold uppercase tracking-widest text-white shadow-lg ring-1 ring-amber-300/50", children: "Candidate's Copy" }) }), _jsx("div", { className: "w-full rounded-xl overflow-hidden shadow-md bg-white border border-amber-200", children: _jsx(CompareImagePreview, { imageUrl: candidateImageUrl, label: "Candidate's Copy" }) })] })), examinerImageUrl && (_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "flex justify-center", children: _jsx("span", { className: "inline-block rounded-lg border-2 border-orange-400 bg-gradient-to-r from-gray-600 to-gray-700 px-8 py-2 text-sm font-bold uppercase tracking-widest text-white shadow-lg ring-1 ring-orange-300/50", children: "Examiner's Copy" }) }), _jsx("div", { className: "w-full rounded-xl overflow-hidden shadow-md bg-white border border-amber-200", children: _jsx(CompareImagePreview, { imageUrl: examinerImageUrl, label: "Examiner's Copy" }) })] }))] })), _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 })] }))] })] })] }));
32
+ 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 (So s\u00E1nh h\u00ECnh \u1EA3nh)", icon: _jsx(GitCompareArrows, { 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" })] })), (candidateImageUrl || examinerImageUrl) && (_jsxs("div", { className: "space-y-4 bg-gradient-to-br from-amber-100 to-orange-100 p-5 rounded-2xl border border-amber-200 shadow-sm", children: [candidateImageUrl && (_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "flex justify-center", children: _jsx("span", { className: "inline-block rounded-lg border-2 border-amber-400 bg-gradient-to-r from-gray-600 to-gray-700 px-8 py-2 text-sm font-bold uppercase tracking-widest text-white shadow-lg ring-1 ring-amber-300/50", children: "Candidate's Copy" }) }), _jsx("div", { className: "w-full rounded-xl overflow-hidden shadow-md bg-white border border-amber-200", children: _jsx(CompareImagePreview, { imageUrl: candidateImageUrl, label: "Candidate's Copy" }) })] })), examinerImageUrl && (_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "flex justify-center", children: _jsx("span", { className: "inline-block rounded-lg border-2 border-orange-400 bg-gradient-to-r from-gray-600 to-gray-700 px-8 py-2 text-sm font-bold uppercase tracking-widest text-white shadow-lg ring-1 ring-orange-300/50", children: "Examiner's Copy" }) }), _jsx("div", { className: "w-full rounded-xl overflow-hidden shadow-md bg-white border border-amber-200", children: _jsx(CompareImagePreview, { imageUrl: examinerImageUrl, label: "Examiner's Copy" }) })] }))] })), _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 })] }))] })] }));
32
33
  }