@tinyweb_dev/oe-exam-sdk 1.0.9 → 1.0.11

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 (124) hide show
  1. package/dist/components/exams/take/ExamTakingPageContainer.js +6 -0
  2. package/dist/components/exams/take/components/QuestionRenderer.js +7 -7
  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/question-renderers/MoversMatchWordToPictureGroupRenderer.d.ts +14 -0
  7. package/dist/components/exams/take/components/question-renderers/MoversMatchWordToPictureGroupRenderer.js +36 -0
  8. package/dist/components/exams/take/components/question-renderers/MoversMatchingWithLinesGroupRenderer.js +4 -0
  9. package/dist/components/exams/take/components/question-renderers/index.d.ts +1 -0
  10. package/dist/components/exams/take/components/question-renderers/index.js +1 -0
  11. package/dist/components/exams/take/components/speaking/renderers/SpeakingSpontaneousQaGroupRenderer.js +12 -2
  12. package/dist/components/exams/take/hooks/useSpontaneousQaGroupRendererSetup.js +2 -1
  13. package/dist/components/exams/take/utils/answer-transformers.js +17 -1
  14. package/dist/components/exams/take/utils/cross-out-word-group.transformer.js +4 -6
  15. package/dist/components/exams/take/utils/question-transformers.d.ts +32 -3
  16. package/dist/components/exams/take/utils/question-transformers.js +51 -2
  17. package/dist/components/questions/_shared/components/compact/KidChoiceRow.d.ts +12 -0
  18. package/dist/components/questions/_shared/components/compact/KidChoiceRow.js +33 -0
  19. package/dist/components/questions/_shared/components/compact/KidInstruction.d.ts +5 -0
  20. package/dist/components/questions/_shared/components/compact/KidInstruction.js +9 -0
  21. package/dist/components/questions/_shared/components/compact/KidPrompt.d.ts +5 -0
  22. package/dist/components/questions/_shared/components/compact/KidPrompt.js +9 -0
  23. package/dist/components/questions/_shared/components/compact/KidQuestionCard.d.ts +12 -0
  24. package/dist/components/questions/_shared/components/compact/KidQuestionCard.js +7 -0
  25. package/dist/components/questions/_shared/components/compact/index.d.ts +4 -0
  26. package/dist/components/questions/_shared/components/compact/index.js +4 -0
  27. package/dist/components/questions/_shared/types/crossword-puzzle.type.d.ts +5 -1
  28. package/dist/components/questions/_shared/types/match-word-to-picture-group.type.d.ts +1 -0
  29. package/dist/components/questions/_shared/types/match-word-to-picture-group.type.js +14 -0
  30. package/dist/components/questions/_shared/types/matching-with-lines-group.type.d.ts +2 -2
  31. package/dist/components/questions/_shared/types/spontaneous-qa-group.type.d.ts +17 -0
  32. package/dist/components/questions/components/QuestionSearchDropdown.js +1 -0
  33. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +18 -19
  34. package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js +8 -7
  35. package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupCreator.js +5 -4
  36. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +17 -11
  37. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +15 -42
  38. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +15 -36
  39. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +9 -30
  40. package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js +8 -7
  41. package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.js +50 -32
  42. package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupCreator.js +24 -44
  43. package/dist/components/questions/types/cross-out-word-group/index.d.ts +1 -0
  44. package/dist/components/questions/types/cross-out-word-group/index.js +1 -0
  45. package/dist/components/questions/types/cross-out-word-group/map-cross-out-word-group-data.js +9 -9
  46. package/dist/components/questions/types/cross-out-word-group/tokenize.d.ts +2 -0
  47. package/dist/components/questions/types/cross-out-word-group/tokenize.js +11 -0
  48. package/dist/components/questions/types/cross-out-word-group/transform.js +2 -4
  49. package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.d.ts +2 -1
  50. package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.js +8 -6
  51. package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleCreator.js +68 -11
  52. package/dist/components/questions/types/crossword-puzzle/transform.js +59 -30
  53. package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +26 -31
  54. package/dist/components/questions/types/fill-in-blank-group/FillInBlankAnswerKey.d.ts +6 -0
  55. package/dist/components/questions/types/fill-in-blank-group/FillInBlankAnswerKey.js +17 -0
  56. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupClient.js +32 -28
  57. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.js +109 -26
  58. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupMediaField.js +21 -11
  59. package/dist/components/questions/types/fill-in-blank-group/blank-utils.d.ts +1 -0
  60. package/dist/components/questions/types/fill-in-blank-group/blank-utils.js +4 -1
  61. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +32 -31
  62. package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.js +71 -28
  63. package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixCreator.js +6 -2
  64. package/dist/components/questions/types/find-words-in-matrix/transform.js +5 -0
  65. package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +4 -3
  66. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +11 -30
  67. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +7 -6
  68. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +2 -1
  69. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +14 -13
  70. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +4 -3
  71. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +4 -3
  72. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +11 -10
  73. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +4 -3
  74. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +13 -11
  75. package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.js +17 -14
  76. package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureClient.js +16 -12
  77. package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureGroupClient.js +14 -14
  78. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js +24 -35
  79. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupCreator.js +16 -3
  80. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupImageField.d.ts +2 -1
  81. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupImageField.js +25 -6
  82. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesBoard.js +1 -1
  83. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupClient.js +22 -10
  84. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.js +60 -18
  85. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesImageField.d.ts +2 -1
  86. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesImageField.js +24 -5
  87. package/dist/components/questions/types/matching-with-lines-group/map-matching-with-lines-group-data.js +8 -4
  88. package/dist/components/questions/types/matching-with-lines-group/transform.js +5 -4
  89. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +8 -26
  90. package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesClient.js +2 -1
  91. package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +6 -5
  92. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardClient.js +2 -1
  93. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +5 -4
  94. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.d.ts +1 -1
  95. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.js +54 -11
  96. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupCreator.js +11 -7
  97. package/dist/components/questions/types/spontaneous-qa-group/creator-helpers.js +8 -2
  98. package/dist/components/questions/types/spontaneous-qa-group/flatten.d.ts +1 -0
  99. package/dist/components/questions/types/spontaneous-qa-group/flatten.js +4 -2
  100. package/dist/components/questions/types/spontaneous-qa-group/map-spontaneous-qa-group-data.js +5 -3
  101. package/dist/components/questions/types/spontaneous-qa-group/register.js +5 -2
  102. package/dist/components/questions/types/spontaneous-qa-group/transform.js +6 -2
  103. package/dist/components/questions/types/true-false/TrueFalseClient.js +5 -54
  104. package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.js +21 -23
  105. package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.js +8 -17
  106. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +76 -81
  107. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +17 -16
  108. package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupClient.js +31 -36
  109. package/dist/components/questions/types/word-ordering/WordOrderingClient.js +15 -14
  110. package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.js +9 -8
  111. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +16 -13
  112. package/dist/components/questions/viewer/CrossOutWordGroupViewer.js +1 -4
  113. package/dist/components/questions/viewer/FillInBlankGroupViewer.d.ts +6 -2
  114. package/dist/components/questions/viewer/FillInBlankGroupViewer.js +13 -7
  115. package/dist/components/questions/viewer/QuestionViewer.js +2 -2
  116. package/dist/components/questions/viewer/SpontaneousQaGroupViewer.d.ts +7 -1
  117. package/dist/components/questions/viewer/SpontaneousQaGroupViewer.js +14 -2
  118. package/dist/components/results/renderers/review-question-body-movers.js +7 -3
  119. package/dist/components/results/review-data.js +20 -1
  120. package/dist/shared/lib/utils/question-reverse-transform.js +48 -7
  121. package/dist/shared/types/questions/crossword-puzzle.d.ts +1 -0
  122. package/dist/shared/types/questions/find-words-in-matrix.d.ts +1 -0
  123. package/dist/shared/types/questions/matching-with-lines-group.d.ts +2 -2
  124. package/package.json +1 -1
@@ -1,8 +1,9 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect, useCallback } from 'react';
4
- import { Check, X, RotateCcw, Sparkles } from 'lucide-react';
4
+ import { X, RotateCcw } from 'lucide-react';
5
5
  import { cn } from '../../../../shared/lib/utils';
6
+ import { KidInstruction, KidQuestionCard } from '../../_shared/components/compact';
6
7
  export function WordOrderAndMatchGroupClient({ questionData, isReviewMode = false, userAnswers, onAnswerChange, }) {
7
8
  const { instruction, matchOptions = ['A', 'B', 'C', 'D', 'E', 'F'], items = [] } = questionData;
8
9
  // Initialize interactive state for each item
@@ -140,7 +141,7 @@ export function WordOrderAndMatchGroupClient({ questionData, isReviewMode = fals
140
141
  return next;
141
142
  });
142
143
  }, [isReviewMode, items, onAnswerChange]);
143
- return (_jsxs("div", { className: "w-full space-y-6", children: [_jsx("div", { className: "rounded-2xl border border-indigo-100 bg-gradient-to-r from-indigo-50/80 via-blue-50/50 to-white p-4 sm:p-5 shadow-sm", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "mt-0.5 rounded-lg bg-indigo-600 p-1.5 text-white shadow-sm", children: _jsx(Sparkles, { className: "h-4 w-4" }) }), _jsxs("div", { className: "flex-1 space-y-3", children: [_jsx("p", { className: "text-sm sm:text-base font-semibold text-gray-900 leading-relaxed", children: instruction || 'Put the useful phrases in order, then match them to parts A–F.' }), matchOptions.length > 0 && (_jsxs("div", { className: "flex flex-wrap items-center gap-1.5 pt-1", children: [_jsx("span", { className: "text-xs font-semibold text-gray-500 mr-1", children: "Match Options:" }), matchOptions.map((opt) => (_jsx("span", { className: "inline-flex h-7 min-w-[28px] items-center justify-center rounded-lg border border-indigo-200 bg-white px-2.5 text-xs font-bold text-indigo-700 shadow-xs", children: opt }, opt)))] }))] })] }) }), _jsx("div", { className: "space-y-4", children: items.map((item, itemIdx) => {
144
+ return (_jsxs("div", { className: "w-full space-y-6", children: [_jsx(KidInstruction, { children: instruction || 'Put the useful phrases in order, then match them to parts A–F.' }), matchOptions.length > 0 && (_jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [_jsx("span", { className: "text-xs font-semibold text-gray-500 mr-1", children: "Match Options:" }), matchOptions.map((opt) => (_jsx("span", { className: "inline-flex h-7 min-w-[28px] items-center justify-center rounded-lg border border-blue-200 bg-white px-2.5 text-xs font-bold text-blue-700 shadow-xs", children: opt }, opt)))] })), _jsx("div", { className: "space-y-4", children: items.map((item, itemIdx) => {
144
145
  const state = itemStates[itemIdx] || { placedIndices: [], selectedLabel: '' };
145
146
  const userAns = userAnswers?.[itemIdx];
146
147
  const isExample = item.isExample === true;
@@ -151,40 +152,34 @@ export function WordOrderAndMatchGroupClient({ questionData, isReviewMode = fals
151
152
  const isPhraseCorrect = normalizeStr(userPhrase) === normalizeStr(item.orderedPhrase || '');
152
153
  const isLabelCorrect = (userLabel || '').trim().toUpperCase() === (item.matchLabel || '').trim().toUpperCase();
153
154
  const isItemAllCorrect = isPhraseCorrect && isLabelCorrect;
154
- return (_jsxs("div", { className: cn('overflow-hidden rounded-xl border bg-white shadow-xs transition-all', isExample
155
- ? 'border-amber-200 bg-amber-50/30'
156
- : isReviewMode
157
- ? isItemAllCorrect
158
- ? 'border-emerald-200 bg-emerald-50/20'
159
- : 'border-rose-200 bg-rose-50/20'
160
- : 'border-gray-200 hover:border-indigo-200'), children: [_jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-gray-100 px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: cn('inline-flex h-6 items-center rounded-md px-2 text-xs font-bold', isExample
161
- ? 'bg-amber-100 text-amber-800'
162
- : 'bg-indigo-100 text-indigo-800'), children: isExample ? ' dụ (Example)' : `Câu ${item.questionNumber || itemIdx + 1}` }), !isExample && typeof item.points === 'number' && (_jsxs("span", { className: "text-xs font-medium text-gray-500", children: ["(", item.points, " ", item.points > 1 ? 'điểm' : 'điểm', ")"] }))] }), isReviewMode && !isExample && (_jsx("div", { className: "flex items-center gap-1.5", children: isItemAllCorrect ? (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-emerald-100 px-2.5 py-0.5 text-xs font-bold text-emerald-700", children: [_jsx(Check, { className: "h-3.5 w-3.5" }), " \u0110\u00FAng"] })) : (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-rose-100 px-2.5 py-0.5 text-xs font-bold text-rose-700", children: [_jsx(X, { className: "h-3.5 w-3.5" }), " Sai"] })) }))] }), _jsxs("div", { className: "grid grid-cols-1 gap-4 p-3 lg:grid-cols-12", children: [_jsxs("div", { className: "space-y-3 lg:col-span-8 xl:col-span-9", children: [_jsx("div", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider", children: "1. S\u1EAFp x\u1EBFp c\u00E1c t\u1EEB th\u00E0nh c\u00E2u ho\u00E0n ch\u1EC9nh:" }), _jsxs("div", { className: cn('min-h-[52px] rounded-xl border-2 border-dashed p-2.5 transition-colors flex flex-wrap items-center gap-1.5', state.placedIndices.length > 0
163
- ? 'border-indigo-300 bg-indigo-50/40'
164
- : 'border-gray-300 bg-gray-50/60'), children: [state.placedIndices.length === 0 ? (_jsx("span", { className: "text-xs italic text-gray-400 select-none", children: "Nh\u1EA5n v\u00E0o c\u00E1c t\u1EEB b\u00EAn d\u01B0\u1EDBi \u0111\u1EC3 gh\u00E9p c\u00E2u..." })) : (state.placedIndices.map((wordIdx, orderIdx) => {
165
- const word = item.scrambledWords[wordIdx];
166
- return (_jsxs("button", { type: "button", disabled: isReviewMode || isExample, onClick: () => handleWordClick(itemIdx, wordIdx), className: cn('group inline-flex items-center gap-1 rounded-lg px-2.5 py-1 text-xs sm:text-sm font-semibold transition-all shadow-xs', isExample
167
- ? 'bg-amber-100 text-amber-900 border border-amber-300 cursor-default'
168
- : isReviewMode
169
- ? 'bg-indigo-100 text-indigo-900 border border-indigo-200 cursor-default'
170
- : 'bg-indigo-600 text-white hover:bg-rose-500 active:scale-95 cursor-pointer'), children: [_jsx("span", { children: word }), !isReviewMode && !isExample && (_jsx(X, { className: "h-3 w-3 opacity-60 group-hover:opacity-100" }))] }, `placed-${wordIdx}-${orderIdx}`));
171
- })), state.placedIndices.length > 0 && !isReviewMode && !isExample && (_jsx("button", { type: "button", onClick: () => handleResetItem(itemIdx), className: "ml-auto inline-flex items-center gap-1 rounded-md p-1 text-xs text-gray-400 hover:bg-white hover:text-gray-700", title: "\u0110\u1EB7t l\u1EA1i", children: _jsx(RotateCcw, { className: "h-3.5 w-3.5" }) }))] }), (!isExample || !isReviewMode) && (_jsx("div", { className: "flex flex-wrap items-center gap-1.5 pt-1", children: item.scrambledWords.map((word, wordIdx) => {
172
- const isPlaced = state.placedIndices.includes(wordIdx);
173
- return (_jsx("button", { type: "button", disabled: isReviewMode || isExample || isPlaced, onClick: () => handleWordClick(itemIdx, wordIdx), className: cn('rounded-lg border px-3 py-1 text-xs sm:text-sm font-medium transition-all', isPlaced
174
- ? 'border-dashed border-gray-200 bg-gray-100 text-gray-300 opacity-50 cursor-not-allowed'
175
- : 'border-gray-300 bg-white text-gray-800 shadow-xs hover:border-indigo-400 hover:bg-indigo-50/50 active:scale-95 cursor-pointer'), children: word }, `word-${wordIdx}`));
176
- }) })), isReviewMode && !isExample && !isPhraseCorrect && (_jsxs("div", { className: "mt-2 rounded-lg bg-emerald-50 border border-emerald-200 p-2.5 text-xs text-emerald-800", children: [_jsx("span", { className: "font-bold", children: "C\u00E2u \u0111\u00FAng:" }), " ", item.orderedPhrase] }))] }), _jsxs("div", { className: "space-y-3 lg:col-span-4 xl:col-span-3 border-t lg:border-t-0 lg:border-l border-gray-100 pt-3 lg:pt-0 lg:pl-4", children: [_jsx("div", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider", children: "2. Ch\u1ECDn nh\u00E3n n\u1ED1i:" }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: matchOptions.map((opt) => {
177
- const isSelected = state.selectedLabel === opt;
178
- const isCorrectOpt = (item.matchLabel || '').trim().toUpperCase() === opt;
179
- return (_jsx("button", { type: "button", disabled: isReviewMode || isExample, onClick: () => handleLabelSelect(itemIdx, opt), className: cn('h-9 w-9 sm:h-10 sm:w-10 rounded-xl font-bold text-sm transition-all flex items-center justify-center', isReviewMode
180
- ? isCorrectOpt
181
- ? 'bg-emerald-600 text-white ring-2 ring-emerald-300'
182
- : isSelected
183
- ? 'bg-rose-500 text-white line-through'
184
- : 'bg-gray-100 text-gray-400 opacity-60'
155
+ return (_jsx(KidQuestionCard, { title: isExample ? 'Ví dụ (Example)' : `Câu ${item.questionNumber || itemIdx + 1}`, points: !isExample && typeof item.points === 'number' ? item.points : undefined, isExample: isExample, review: isReviewMode && !isExample ? (isItemAllCorrect ? 'correct' : 'wrong') : null, children: _jsxs("div", { className: "grid grid-cols-1 gap-4 lg:grid-cols-12", children: [_jsxs("div", { className: "space-y-3 lg:col-span-8 xl:col-span-9", children: [_jsx("div", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider", children: "1. S\u1EAFp x\u1EBFp c\u00E1c t\u1EEB th\u00E0nh c\u00E2u ho\u00E0n ch\u1EC9nh:" }), _jsxs("div", { className: cn('min-h-[52px] rounded-xl border-2 border-dashed p-2.5 transition-colors flex flex-wrap items-center gap-1.5', state.placedIndices.length > 0
156
+ ? 'border-blue-300 bg-blue-50/40'
157
+ : 'border-gray-300 bg-gray-50/60'), children: [state.placedIndices.length === 0 ? (_jsx("span", { className: "text-xs italic text-gray-400 select-none", children: "Nh\u1EA5n v\u00E0o c\u00E1c t\u1EEB b\u00EAn d\u01B0\u1EDBi \u0111\u1EC3 gh\u00E9p c\u00E2u..." })) : (state.placedIndices.map((wordIdx, orderIdx) => {
158
+ const word = item.scrambledWords[wordIdx];
159
+ return (_jsxs("button", { type: "button", disabled: isReviewMode || isExample, onClick: () => handleWordClick(itemIdx, wordIdx), className: cn('group inline-flex items-center gap-1 rounded-lg px-2.5 py-1 text-xs sm:text-sm font-semibold transition-all shadow-xs', isExample
160
+ ? 'bg-amber-100 text-amber-900 border border-amber-300 cursor-default'
161
+ : isReviewMode
162
+ ? isPhraseCorrect
163
+ ? 'border border-green-300 bg-green-50 text-green-800'
164
+ : 'border border-red-300 bg-red-50 text-red-800'
165
+ : 'cursor-pointer border border-blue-500 bg-blue-50 text-blue-800 ring-2 ring-blue-100 hover:bg-blue-100 active:scale-95'), children: [_jsx("span", { children: word }), !isReviewMode && !isExample && (_jsx(X, { className: "h-3 w-3 opacity-60 group-hover:opacity-100" }))] }, `placed-${wordIdx}-${orderIdx}`));
166
+ })), state.placedIndices.length > 0 && !isReviewMode && !isExample && (_jsx("button", { type: "button", onClick: () => handleResetItem(itemIdx), className: "ml-auto inline-flex items-center gap-1 rounded-md p-1 text-xs text-gray-400 hover:bg-white hover:text-gray-700", title: "\u0110\u1EB7t l\u1EA1i", children: _jsx(RotateCcw, { className: "h-3.5 w-3.5" }) }))] }), (!isExample || !isReviewMode) && (_jsx("div", { className: "flex flex-wrap items-center gap-1.5 pt-1", children: item.scrambledWords.map((word, wordIdx) => {
167
+ const isPlaced = state.placedIndices.includes(wordIdx);
168
+ return (_jsx("button", { type: "button", disabled: isReviewMode || isExample || isPlaced, onClick: () => handleWordClick(itemIdx, wordIdx), className: cn('rounded-lg border px-3 py-1 text-xs sm:text-sm font-medium transition-all', isPlaced
169
+ ? 'border-dashed border-gray-200 bg-gray-100 text-gray-300 opacity-50 cursor-not-allowed'
170
+ : 'cursor-pointer border-gray-300 bg-white text-gray-800 shadow-xs hover:border-blue-400 hover:bg-blue-50/50 active:scale-95'), children: word }, `word-${wordIdx}`));
171
+ }) })), isReviewMode && !isExample && !isPhraseCorrect && (_jsxs("div", { className: "mt-2 rounded-lg bg-green-50 border border-green-200 p-2.5 text-xs text-green-800", children: [_jsx("span", { className: "font-bold", children: "C\u00E2u \u0111\u00FAng:" }), " ", item.orderedPhrase] }))] }), _jsxs("div", { className: "space-y-3 lg:col-span-4 xl:col-span-3 border-t lg:border-t-0 lg:border-l border-gray-100 pt-3 lg:pt-0 lg:pl-4", children: [_jsx("div", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider", children: "2. Ch\u1ECDn nh\u00E3n n\u1ED1i:" }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: matchOptions.map((opt) => {
172
+ const isSelected = state.selectedLabel === opt;
173
+ const isCorrectOpt = (item.matchLabel || '').trim().toUpperCase() === opt;
174
+ return (_jsx("button", { type: "button", disabled: isReviewMode || isExample, onClick: () => handleLabelSelect(itemIdx, opt), className: cn('h-9 w-9 sm:h-10 sm:w-10 rounded-xl font-bold text-sm transition-all flex items-center justify-center', isReviewMode
175
+ ? isCorrectOpt
176
+ ? 'bg-green-600 text-white ring-2 ring-green-300'
185
177
  : isSelected
186
- ? 'bg-indigo-600 text-white shadow-md scale-105'
187
- : 'border border-gray-200 bg-white text-gray-700 hover:border-indigo-300 hover:bg-indigo-50/50 cursor-pointer'), children: opt }, `opt-${opt}`));
188
- }) }), isReviewMode && !isExample && !isLabelCorrect && (_jsxs("div", { className: "text-xs text-emerald-800", children: [_jsx("span", { className: "font-bold", children: "Nh\u00E3n \u0111\u00FAng:" }), " [", item.matchLabel, "]"] }))] })] })] }, itemIdx));
178
+ ? 'bg-red-500 text-white line-through'
179
+ : 'bg-gray-100 text-gray-400 opacity-60'
180
+ : isSelected
181
+ ? 'scale-105 border border-blue-500 bg-blue-50 text-blue-800 shadow-md ring-2 ring-blue-100'
182
+ : 'cursor-pointer border border-gray-200 bg-white text-gray-700 hover:border-blue-300 hover:bg-blue-50/50'), children: opt }, `opt-${opt}`));
183
+ }) }), isReviewMode && !isExample && !isLabelCorrect && (_jsxs("div", { className: "text-xs text-green-800", children: [_jsx("span", { className: "font-bold", children: "Nh\u00E3n \u0111\u00FAng:" }), " [", item.matchLabel, "]"] }))] })] }) }, itemIdx));
189
184
  }) })] }));
190
185
  }
@@ -1,9 +1,10 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect, useCallback, useMemo } from 'react';
4
- import { Check, X, ArrowUpDown, Lightbulb } from 'lucide-react';
4
+ import { Check, X, ArrowUpDown } from 'lucide-react';
5
5
  import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
6
6
  import { cn } from '../../../../shared/lib/utils';
7
+ import { KidQuestionCard } from '../../_shared/components/compact';
7
8
  function toWordList(value) {
8
9
  if (Array.isArray(value)) {
9
10
  return value.filter((item) => typeof item === 'string');
@@ -86,17 +87,17 @@ export function WordOrderingClient({ questionData, onSubmit, isReviewMode = fals
86
87
  const isWrong = isReviewMode && selectedWords.length > 0 && !isCorrect;
87
88
  const isComplete = selectedWords.length === words.length;
88
89
  const displayExplanation = explanation || questionData.explanation;
89
- return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gradient-to-br from-amber-500 to-orange-600 text-white", children: _jsx(ArrowUpDown, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "S\u1EAFp x\u1EBFp t\u1EEB" }), isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2.5 py-0.5 text-xs font-semibold text-amber-700", children: [_jsx(Lightbulb, { className: "h-3 w-3" }), "V\u00ED d\u1EE5"] }))] }), isReviewMode && isComplete && (_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: "space-y-3 p-3", children: [(imagePreviewUrl || imageUrl) && (_jsx("div", { className: "flex justify-center", children: _jsx("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-gray-50", children: isLoading ? (_jsx("div", { className: "flex h-48 w-48 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-amber-500" }) })) : (_jsx("img", { src: imagePreviewUrl || imageUrl, alt: "H\u00ECnh \u1EA3nh c\u00E2u h\u1ECFi", className: "max-h-56 w-auto object-contain p-2" })) }) })), _jsxs("div", { className: "space-y-2", children: [_jsx("span", { className: "text-xs font-medium text-gray-500 uppercase tracking-wider", children: "\u0110\u00E1p \u00E1n c\u1EE7a b\u1EA1n" }), _jsx("div", { className: "flex flex-wrap gap-2 min-h-[48px] rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 p-3", children: selectedWords.length === 0 ? (_jsx("span", { className: "text-sm text-gray-400 italic", children: "Nh\u1EA5n v\u00E0o c\u00E1c t\u1EEB b\u00EAn d\u01B0\u1EDBi \u0111\u1EC3 s\u1EAFp x\u1EBFp..." })) : (selectedWords.map((word, index) => {
90
- const isWordCorrect = isReviewMode && answer[index] === word;
91
- const isWordWrong = isReviewMode && answer[index] !== word;
92
- return (_jsxs("button", { type: "button", onClick: () => handleRemoveWord(index), disabled: isReviewMode, className: cn('inline-flex items-center gap-1 rounded-lg px-3 py-2 text-sm font-semibold transition-all shadow-sm', isReviewMode
93
- ? isWordCorrect
94
- ? 'bg-green-100 text-green-800 border border-green-300'
95
- : 'bg-red-100 text-red-800 border border-red-300'
96
- : 'bg-amber-100 text-amber-800 border border-amber-300 hover:bg-amber-200 cursor-pointer'), children: [word, isReviewMode && (isWordCorrect
97
- ? _jsx(Check, { className: "h-3.5 w-3.5 text-green-600" })
98
- : _jsx(X, { className: "h-3.5 w-3.5 text-red-600" }))] }, `selected-${index}`));
99
- })) })] }), availableWords.length > 0 && (_jsxs("div", { className: "space-y-2", children: [_jsx("span", { className: "text-xs font-medium text-gray-500 uppercase tracking-wider", children: "T\u1EEB c\u00F3 s\u1EB5n" }), _jsx("div", { className: "flex flex-wrap gap-2", children: availableWords.map((word, index) => (_jsx("button", { type: "button", onClick: () => handleSelectWord(word, index), disabled: isReviewMode, className: cn('rounded-lg border-2 px-3 py-2 text-sm font-semibold transition-all shadow-sm', isReviewMode
100
- ? 'border-gray-200 bg-gray-100 text-gray-500 cursor-default'
101
- : 'border-blue-200 bg-blue-50 text-blue-700 hover:bg-blue-100 hover:border-blue-300 cursor-pointer hover:shadow-md'), children: word }, `available-${index}`))) })] })), isReviewMode && isWrong && (_jsxs("div", { className: "rounded-lg border border-green-200 bg-green-50 p-3", children: [_jsx("span", { className: "text-xs font-medium text-green-600 uppercase tracking-wider", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" }), _jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: answer.map((word, index) => (_jsx("span", { className: "rounded-lg bg-green-100 px-3 py-1.5 text-sm font-semibold text-green-800 border border-green-300", children: word }, `correct-${index}`))) })] })), isReviewMode && displayExplanation && (_jsxs("div", { className: "rounded-lg border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-3", children: [_jsx("span", { className: "text-xs font-medium text-blue-600 uppercase tracking-wider", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-blue-800", children: displayExplanation })] }))] })] }) }));
90
+ return (_jsx("div", { className: "space-y-4", children: _jsxs(KidQuestionCard, { icon: _jsx(ArrowUpDown, { className: "h-3.5 w-3.5" }), title: "S\u1EAFp x\u1EBFp t\u1EEB", isExample: isExample, review: isReviewMode && isComplete ? (isCorrect ? 'correct' : 'wrong') : null, children: [(imagePreviewUrl || imageUrl) && (_jsx("div", { className: "flex justify-center", children: _jsx("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-gray-50", children: isLoading ? (_jsx("div", { className: "flex h-48 w-48 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-amber-500" }) })) : (_jsx("img", { src: imagePreviewUrl || imageUrl, alt: "H\u00ECnh \u1EA3nh c\u00E2u h\u1ECFi", className: "max-h-56 w-auto object-contain p-2" })) }) })), _jsxs("div", { className: "space-y-2", children: [_jsx("span", { className: "text-xs font-medium text-gray-500 uppercase tracking-wider", children: "\u0110\u00E1p \u00E1n c\u1EE7a b\u1EA1n" }), _jsx("div", { className: "flex flex-wrap gap-2 min-h-[48px] rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 p-3", children: selectedWords.length === 0 ? (_jsx("span", { className: "text-sm text-gray-400 italic", children: "Nh\u1EA5n v\u00E0o c\u00E1c t\u1EEB b\u00EAn d\u01B0\u1EDBi \u0111\u1EC3 s\u1EAFp x\u1EBFp..." })) : (selectedWords.map((word, index) => {
91
+ const isWordCorrect = isReviewMode && answer[index] === word;
92
+ const isWordWrong = isReviewMode && answer[index] !== word;
93
+ return (_jsxs("button", { type: "button", onClick: () => handleRemoveWord(index), disabled: isReviewMode, className: cn('inline-flex items-center gap-1 rounded-lg px-3 py-2 text-sm font-semibold transition-all shadow-sm', isReviewMode
94
+ ? isWordCorrect
95
+ ? 'bg-green-100 text-green-800 border border-green-300'
96
+ : 'bg-red-100 text-red-800 border border-red-300'
97
+ : 'cursor-pointer border border-blue-500 bg-blue-50 text-blue-800 ring-2 ring-blue-100 hover:bg-blue-100'), children: [word, isReviewMode && (isWordCorrect
98
+ ? _jsx(Check, { className: "h-3.5 w-3.5 text-green-600" })
99
+ : _jsx(X, { className: "h-3.5 w-3.5 text-red-600" }))] }, `selected-${index}`));
100
+ })) })] }), availableWords.length > 0 && (_jsxs("div", { className: "space-y-2", children: [_jsx("span", { className: "text-xs font-medium text-gray-500 uppercase tracking-wider", children: "T\u1EEB c\u00F3 s\u1EB5n" }), _jsx("div", { className: "flex flex-wrap gap-2", children: availableWords.map((word, index) => (_jsx("button", { type: "button", onClick: () => handleSelectWord(word, index), disabled: isReviewMode, className: cn('rounded-lg border-2 px-3 py-2 text-sm font-semibold transition-all shadow-sm', isReviewMode
101
+ ? 'border-gray-200 bg-gray-100 text-gray-500 cursor-default'
102
+ : 'border-blue-200 bg-blue-50 text-blue-700 hover:bg-blue-100 hover:border-blue-300 cursor-pointer hover:shadow-md'), children: word }, `available-${index}`))) })] })), isReviewMode && isWrong && (_jsxs("div", { className: "rounded-lg border border-green-200 bg-green-50 p-3", children: [_jsx("span", { className: "text-xs font-medium text-green-600 uppercase tracking-wider", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" }), _jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: answer.map((word, index) => (_jsx("span", { className: "rounded-lg bg-green-100 px-3 py-1.5 text-sm font-semibold text-green-800 border border-green-300", children: word }, `correct-${index}`))) })] })), isReviewMode && displayExplanation && (_jsxs("div", { className: "rounded-lg border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-3", children: [_jsx("span", { className: "text-xs font-medium text-blue-600 uppercase tracking-wider", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-blue-800", children: displayExplanation })] }))] }) }));
102
103
  }
@@ -1,7 +1,8 @@
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 { WordOrderingClient } from '../word-ordering/WordOrderingClient';
5
+ import { KidInstruction, KidQuestionCard } from '../../_shared/components/compact';
5
6
  export function WordOrderingGroupClient({ questionData, isReviewMode = false, userAnswers = [], onAnswerChange, }) {
6
7
  const items = questionData.items || [];
7
8
  const handleItemSubmit = (itemIndex, ordered) => {
@@ -16,11 +17,11 @@ export function WordOrderingGroupClient({ questionData, isReviewMode = false, us
16
17
  });
17
18
  onAnswerChange(next);
18
19
  };
19
- return (_jsxs("div", { className: "space-y-4", children: [questionData.instruction && (_jsx("div", { className: "rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3 text-sm font-medium text-indigo-800", children: questionData.instruction })), items.map((item, itemIndex) => (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white", 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"] })] }), _jsx("div", { className: "space-y-3 p-3", children: _jsx(WordOrderingClient, { questionData: {
20
- words: item.words,
21
- answer: item.answer,
22
- isExample: item.isExample,
23
- points: item.points,
24
- imageUrl: item.imageUrl,
25
- }, isReviewMode: isReviewMode || item.isExample, userAnswer: item.isExample ? item.answer : userAnswers[itemIndex], onSubmit: (ordered) => handleItemSubmit(itemIndex, ordered) }) })] }, `${item.questionNumber}-${itemIndex}`))), 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] }))] }));
20
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx(KidInstruction, { children: questionData.instruction }), items.map((item, itemIndex) => (_jsx(KidQuestionCard, { title: `Câu ${item.questionNumber || itemIndex + 1}`, points: item.points || 0, isExample: item.isExample, children: _jsx(WordOrderingClient, { questionData: {
21
+ words: item.words,
22
+ answer: item.answer,
23
+ isExample: item.isExample,
24
+ points: item.points,
25
+ imageUrl: item.imageUrl,
26
+ }, isReviewMode: isReviewMode || item.isExample, userAnswer: item.isExample ? item.answer : userAnswers[itemIndex], onSubmit: (ordered) => handleItemSubmit(itemIndex, ordered) }) }, `${item.questionNumber}-${itemIndex}`))), 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] }))] }));
26
27
  }
@@ -3,7 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect } from 'react';
4
4
  import { Input } from '../../../../components/ui/input';
5
5
  import { cn } from '../../../../shared/lib/utils';
6
- import { Check, X, FileText, PenLine, AlertTriangle, Lightbulb } from 'lucide-react';
6
+ import { Check, X, PenLine, AlertTriangle, Lightbulb } from 'lucide-react';
7
+ import { KidInstruction, KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
7
8
  export function WriteCorrectVerbFormClient({ questionData, onSubmit, isReviewMode = false, userAnswer = '', }) {
8
9
  // ============ STATE ============
9
10
  const [answer, setAnswer] = useState(userAnswer || '');
@@ -39,7 +40,9 @@ export function WriteCorrectVerbFormClient({ questionData, onSubmit, isReviewMod
39
40
  const parts = questionData.sentence.split(/(\{0\})/g);
40
41
  return (_jsx("div", { className: "flex flex-wrap items-center gap-1.5 text-lg leading-relaxed", children: parts.map((part, index) => {
41
42
  if (part === '{0}') {
42
- return (_jsxs("span", { className: "inline-flex items-center gap-2", children: [_jsxs("div", { className: "relative inline-flex items-center", children: [_jsx(Input, { value: questionData.correctAnswer, readOnly: true, disabled: true, className: cn('w-36 text-center font-semibold border-2 rounded-lg transition-all', 'border-green-400 bg-green-50 text-green-800 cursor-not-allowed') }), isReviewMode && (_jsx("div", { className: cn('absolute -right-7 top-1/2 -translate-y-1/2 flex h-5 w-5 items-center justify-center rounded-full', isCorrect ? 'bg-green-500' : isWrong ? 'bg-red-500' : 'bg-gray-300'), children: isCorrect ? (_jsx(Check, { className: "h-3 w-3 text-white" })) : isWrong ? (_jsx(X, { className: "h-3 w-3 text-white" })) : null }))] }), _jsxs("span", { className: "rounded-lg bg-amber-100 px-2 py-0.5 text-sm font-medium text-amber-700", children: ["(", questionData.verbHint, ")"] })] }, index));
43
+ return (_jsxs("span", { className: "inline-flex items-center gap-2", children: [_jsxs("div", { className: "relative inline-flex items-center", children: [_jsx(Input, { value: questionData.correctAnswer, readOnly: true, disabled: true, className: cn('w-36 text-sm text-center font-semibold border px-3 py-1.5 rounded-md transition-all cursor-not-allowed', isReviewMode && isWrong
44
+ ? 'border-red-400 bg-red-50 text-red-800'
45
+ : 'border-green-400 bg-green-50 text-green-800') }), isReviewMode && (_jsx("div", { className: cn('absolute -right-7 top-1/2 -translate-y-1/2 flex h-5 w-5 items-center justify-center rounded-full', isCorrect ? 'bg-green-500' : isWrong ? 'bg-red-500' : 'bg-gray-300'), children: isCorrect ? (_jsx(Check, { className: "h-3 w-3 text-white" })) : isWrong ? (_jsx(X, { className: "h-3 w-3 text-white" })) : null }))] }), _jsxs("span", { className: "rounded-lg bg-amber-100 px-2 py-0.5 text-sm font-medium text-amber-700", children: ["(", questionData.verbHint, ")"] })] }, index));
43
46
  }
44
47
  else {
45
48
  return _jsx("span", { className: "text-gray-800", children: part }, index);
@@ -47,15 +50,15 @@ export function WriteCorrectVerbFormClient({ questionData, onSubmit, isReviewMod
47
50
  }) }));
48
51
  };
49
52
  // ============ RENDER ============
50
- return (_jsxs("div", { className: "space-y-3", children: [questionData.instruction && (_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-2 px-3 py-2", children: [_jsx("div", { className: "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md bg-gradient-to-br from-blue-500 to-indigo-600 text-white", children: _jsx(FileText, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold text-blue-900", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx("p", { className: "text-sm text-blue-700", children: questionData.instruction })] })] }) })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", 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 || (!isCorrect && !isWrong)) && 'border-gray-100 bg-gradient-to-r from-gray-50 to-white'), children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: cn('flex h-7 w-7 items-center justify-center rounded-md text-white', 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 || (!isCorrect && !isWrong)) && 'bg-gradient-to-br from-violet-500 to-purple-600'), children: _jsx(PenLine, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Chia \u0111\u1ED9ng t\u1EEB \u0111\u00FAng" })] }), 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: "space-y-3 p-3", children: [_jsx("div", { className: "rounded-xl bg-gradient-to-br from-slate-50 to-gray-50 border border-gray-100 p-5", children: renderSentenceWithInput() }), isReviewMode && (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: cn('rounded-xl border-2 p-4', isCorrect
51
- ? 'border-green-200 bg-green-50'
52
- : isWrong
53
- ? 'border-red-200 bg-red-50'
54
- : 'border-gray-200 bg-gray-50'), children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-lg', isCorrect ? 'bg-green-200' : isWrong ? 'bg-red-200' : 'bg-gray-200'), children: isCorrect ? (_jsx(Check, { className: "h-3.5 w-3.5 text-green-700" })) : isWrong ? (_jsx(X, { className: "h-3.5 w-3.5 text-red-700" })) : (_jsx("span", { className: "text-xs text-gray-500", children: "?" })) }), _jsx("span", { className: "text-xs font-medium text-gray-600", children: "C\u00E2u tr\u1EA3 l\u1EDDi c\u1EE7a b\u1EA1n" })] }), _jsx("p", { className: cn('text-lg font-bold', isCorrect ? 'text-green-700' : isWrong ? 'text-red-700' : 'text-gray-400'), children: answer || '(Chưa trả lời)' })] }), _jsxs("div", { className: "rounded-xl border-2 border-green-200 bg-gradient-to-br from-green-50 to-emerald-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-lg bg-green-200", children: _jsx(Check, { className: "h-3.5 w-3.5 text-green-700" }) }), _jsx("span", { className: "text-xs font-medium text-gray-600", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" })] }), _jsx("p", { className: "text-lg font-bold text-green-700", children: questionData.correctAnswer })] })] }), _jsx("div", { className: cn('rounded-xl p-4', isCorrect
55
- ? 'bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200'
56
- : 'bg-gradient-to-r from-red-50 to-rose-50 border border-red-200'), children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: cn('flex h-10 w-10 items-center justify-center rounded-xl text-white shadow-sm', isCorrect
57
- ? 'bg-gradient-to-br from-green-500 to-emerald-600'
58
- : 'bg-gradient-to-br from-red-500 to-rose-600'), children: isCorrect ? _jsx(Check, { className: "h-5 w-5" }) : _jsx(X, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("p", { className: cn('text-sm font-semibold', isCorrect ? 'text-green-800' : 'text-red-800'), children: isCorrect ? 'Chính xác!' : 'Chưa đúng' }), _jsx("p", { className: cn('text-xs', isCorrect ? 'text-green-600' : 'text-red-600'), children: isCorrect
59
- ? 'Bạn đã chia động từ đúng.'
60
- : `Đáp án đúng là "${questionData.correctAnswer}".` })] })] }) }), questionData.explanation && (_jsx("div", { className: "rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-lg bg-blue-500 text-white", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("span", { className: "text-xs font-semibold text-blue-700 uppercase tracking-wider", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-gray-700 leading-relaxed", children: questionData.explanation })] })] }) }))] })), !isReviewMode && !hasAnswered && (_jsx("div", { className: "rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-amber-400 to-orange-500 text-white shadow-sm", children: _jsx("span", { className: "text-sm font-bold", children: "!" }) }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-amber-800", children: "Nh\u1EADp d\u1EA1ng \u0111\u00FAng c\u1EE7a \u0111\u1ED9ng t\u1EEB" }), _jsx("p", { className: "text-xs text-amber-600", children: "S\u1EED d\u1EE5ng g\u1EE3i \u00FD trong ngo\u1EB7c \u0111\u1EC3 chia \u0111\u1ED9ng t\u1EEB ph\u00F9 h\u1EE3p." })] })] }) }))] })] }), !isReviewMode && questionData.caseSensitive && (_jsx("div", { className: "overflow-hidden rounded-xl border border-yellow-200 bg-gradient-to-r from-yellow-50 to-amber-50 shadow-sm", children: _jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [_jsx("div", { className: "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md bg-gradient-to-br from-yellow-400 to-amber-500 text-white", children: _jsx(AlertTriangle, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold text-yellow-900", children: "L\u01B0u \u00FD quan tr\u1ECDng" }), _jsx("p", { className: "text-sm text-yellow-700", children: "\u0110\u00E1p \u00E1n c\u00F3 ph\u00E2n bi\u1EC7t ch\u1EEF hoa/th\u01B0\u1EDDng" })] })] }) }))] }));
53
+ return (_jsxs("div", { className: "space-y-3", children: [_jsx(KidInstruction, { children: questionData.instruction }), _jsxs(KidQuestionCard, { icon: _jsx(PenLine, { className: "h-3.5 w-3.5" }), title: "Chia \u0111\u1ED9ng t\u1EEB \u0111\u00FAng", points: questionData.points, review: isReviewMode && hasAnswered ? (isCorrect ? 'correct' : 'wrong') : null, children: [_jsx(KidPrompt, { children: renderSentenceWithInput() }), isReviewMode && (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: cn('rounded-xl border-2 p-4', isCorrect
54
+ ? 'border-green-200 bg-green-50'
55
+ : isWrong
56
+ ? 'border-red-200 bg-red-50'
57
+ : 'border-gray-200 bg-gray-50'), children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-lg', isCorrect ? 'bg-green-200' : isWrong ? 'bg-red-200' : 'bg-gray-200'), children: isCorrect ? (_jsx(Check, { className: "h-3.5 w-3.5 text-green-700" })) : isWrong ? (_jsx(X, { className: "h-3.5 w-3.5 text-red-700" })) : (_jsx("span", { className: "text-xs text-gray-500", children: "?" })) }), _jsx("span", { className: "text-xs font-medium text-gray-600", children: "C\u00E2u tr\u1EA3 l\u1EDDi c\u1EE7a b\u1EA1n" })] }), _jsx("p", { className: cn('text-lg font-bold', isCorrect ? 'text-green-700' : isWrong ? 'text-red-700' : 'text-gray-400'), children: answer || '(Chưa trả lời)' })] }), _jsxs("div", { className: "rounded-xl border-2 border-green-200 bg-gradient-to-br from-green-50 to-emerald-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-lg bg-green-200", children: _jsx(Check, { className: "h-3.5 w-3.5 text-green-700" }) }), _jsx("span", { className: "text-xs font-medium text-gray-600", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" })] }), _jsx("p", { className: "text-lg font-bold text-green-700", children: questionData.correctAnswer })] })] }), _jsx("div", { className: cn('rounded-xl p-4', isCorrect
58
+ ? 'bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200'
59
+ : 'bg-gradient-to-r from-red-50 to-rose-50 border border-red-200'), children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: cn('flex h-10 w-10 items-center justify-center rounded-xl text-white shadow-sm', isCorrect
60
+ ? 'bg-gradient-to-br from-green-500 to-emerald-600'
61
+ : 'bg-gradient-to-br from-red-500 to-rose-600'), children: isCorrect ? _jsx(Check, { className: "h-5 w-5" }) : _jsx(X, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("p", { className: cn('text-sm font-semibold', isCorrect ? 'text-green-800' : 'text-red-800'), children: isCorrect ? 'Chính xác!' : 'Chưa đúng' }), _jsx("p", { className: cn('text-xs', isCorrect ? 'text-green-600' : 'text-red-600'), children: isCorrect
62
+ ? 'Bạn đã chia động từ đúng.'
63
+ : `Đáp án đúng là "${questionData.correctAnswer}".` })] })] }) }), questionData.explanation && (_jsx("div", { className: "rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-lg bg-blue-500 text-white", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("span", { className: "text-xs font-semibold text-blue-700 uppercase tracking-wider", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-gray-700 leading-relaxed", children: questionData.explanation })] })] }) }))] })), !isReviewMode && !hasAnswered && (_jsxs("div", { className: "flex items-center gap-2 rounded-md border border-amber-200 bg-amber-50 px-2.5 py-1.5", children: [_jsx("div", { className: "flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-white", children: _jsx("span", { className: "text-[10px] font-bold", children: "!" }) }), _jsx("span", { className: "text-xs text-amber-800", children: "Nh\u1EADp d\u1EA1ng \u0111\u00FAng c\u1EE7a \u0111\u1ED9ng t\u1EEB theo g\u1EE3i \u00FD trong ngo\u1EB7c" })] }))] }), !isReviewMode && questionData.caseSensitive && (_jsx("div", { className: "overflow-hidden rounded-xl border border-yellow-200 bg-gradient-to-r from-yellow-50 to-amber-50 shadow-sm", children: _jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [_jsx("div", { className: "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md bg-gradient-to-br from-yellow-400 to-amber-500 text-white", children: _jsx(AlertTriangle, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold text-yellow-900", children: "L\u01B0u \u00FD quan tr\u1ECDng" }), _jsx("p", { className: "text-sm text-yellow-700", children: "\u0110\u00E1p \u00E1n c\u00F3 ph\u00E2n bi\u1EC7t ch\u1EEF hoa/th\u01B0\u1EDDng" })] })] }) }))] }));
61
64
  }
@@ -13,9 +13,6 @@ function readCrossOutAnswers(value) {
13
13
  }
14
14
  export function CrossOutWordGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
15
15
  const questionData = mapQuestionToCrossOutWordGroupData(question);
16
- const storedAnswers = readCrossOutAnswers(userAnswer);
17
- const userAnswers = isReviewMode && !storedAnswers
18
- ? questionData.items.map((item) => item.correctAnswer)
19
- : storedAnswers;
16
+ const userAnswers = readCrossOutAnswers(userAnswer);
20
17
  return (_jsx(CrossOutWordGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: userAnswers, onAnswerChange: isReviewMode ? undefined : onSubmitAnswer }));
21
18
  }
@@ -3,8 +3,12 @@ import type { FillInBlankGroupStudentItemAnswer } from '../_shared/types/fill-in
3
3
  type FillInBlankGroupViewerProps = {
4
4
  question: Question;
5
5
  isReviewMode?: boolean;
6
- userAnswer?: FillInBlankGroupStudentItemAnswer[];
7
- onSubmitAnswer?: (answer: FillInBlankGroupStudentItemAnswer[]) => void;
6
+ userAnswer?: FillInBlankGroupStudentItemAnswer[] | {
7
+ answer?: FillInBlankGroupStudentItemAnswer[];
8
+ };
9
+ onSubmitAnswer?: (answer: {
10
+ answer: FillInBlankGroupStudentItemAnswer[];
11
+ } | FillInBlankGroupStudentItemAnswer[]) => void;
8
12
  };
9
13
  export declare function FillInBlankGroupViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: FillInBlankGroupViewerProps): import("react").JSX.Element;
10
14
  export {};
@@ -2,13 +2,19 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { FillInBlankGroupClient } from '../types/fill-in-blank-group/FillInBlankGroupClient';
4
4
  import { mapQuestionToFillInBlankGroupData } from '../types/fill-in-blank-group/map-fill-in-blank-group-data';
5
+ function readFillInBlankAnswers(value) {
6
+ if (Array.isArray(value)) {
7
+ return value;
8
+ }
9
+ if (value && typeof value === 'object' && Array.isArray(value.answer)) {
10
+ return value.answer;
11
+ }
12
+ return undefined;
13
+ }
5
14
  export function FillInBlankGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
6
15
  const questionData = mapQuestionToFillInBlankGroupData(question);
7
- const studentAnswers = Array.isArray(userAnswer) ? userAnswer : undefined;
8
- const reviewAnswers = isReviewMode && !studentAnswers
9
- ? questionData.items.map((item) => ({
10
- answers: item.answers.map((alts) => alts[0] || ''),
11
- }))
12
- : studentAnswers;
13
- return (_jsx(FillInBlankGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: reviewAnswers, onAnswerChange: isReviewMode ? undefined : onSubmitAnswer }));
16
+ const userAnswers = readFillInBlankAnswers(userAnswer);
17
+ return (_jsx(FillInBlankGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: userAnswers, onAnswerChange: isReviewMode
18
+ ? undefined
19
+ : (answers) => onSubmitAnswer?.({ answer: answers }) }));
14
20
  }
@@ -484,7 +484,7 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
484
484
  const answers = raw.answers && typeof raw.answers === 'object' && !Array.isArray(raw.answers)
485
485
  ? raw.answers
486
486
  : raw;
487
- return (_jsx(CrosswordPuzzleClient, { content: question.content, correctAnswer: question.correctAnswer ?? question.answer, isReviewMode: isReviewMode, userAnswers: answers, onAnswerChange: isReviewMode
487
+ return (_jsx(CrosswordPuzzleClient, { content: question.content, correctAnswer: question.correctAnswer ?? question.answer, isReviewMode: isReviewMode, questionNumber: question.questionNumber, userAnswers: answers, onAnswerChange: isReviewMode
488
488
  ? undefined
489
489
  : (key, value) => {
490
490
  onSubmitAnswer?.({ answers: { ...answers, [key]: value } });
@@ -521,7 +521,7 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
521
521
  case 'SPONTANEOUS_QA':
522
522
  return (_jsx(SpontaneousQAClient, { questionData: mapQuestionToSpontaneousQAData(question), isReviewMode: isReviewMode }));
523
523
  case 'SPONTANEOUS_QA_GROUP':
524
- return (_jsx(SpontaneousQaGroupViewer, { question: question, isReviewMode: isReviewMode }));
524
+ return (_jsx(SpontaneousQaGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, gradeDetails: gradeDetails, disabled: !onSubmitAnswer }));
525
525
  case 'WORD_ORDER_AND_MATCH_GROUP':
526
526
  case 'WORD_ORDER_AND_MATCH':
527
527
  return (_jsx(WordOrderAndMatchGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
@@ -1,5 +1,11 @@
1
1
  import type { Question } from '../../../shared/types/entities/question.types';
2
- export declare function SpontaneousQaGroupViewer({ question, isReviewMode, }: {
2
+ import type { SpontaneousQaGroupGradeDetail } from '../_shared/types/spontaneous-qa-group.type';
3
+ export declare function SpontaneousQaGroupViewer({ question, isReviewMode, userAnswer, onItemRecorded, uploadingIndex, gradeDetails, disabled, }: {
3
4
  question: Question;
4
5
  isReviewMode?: boolean;
6
+ userAnswer?: unknown;
7
+ onItemRecorded?: (flattenIndex: number, blob: Blob) => void;
8
+ uploadingIndex?: number | null;
9
+ gradeDetails?: SpontaneousQaGroupGradeDetail[];
10
+ disabled?: boolean;
5
11
  }): import("react").JSX.Element;
@@ -2,6 +2,18 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { SpontaneousQaGroupClient } from '../types/spontaneous-qa-group/SpontaneousQaGroupClient';
4
4
  import { mapQuestionToSpontaneousQaGroupData } from '../types/spontaneous-qa-group/map-spontaneous-qa-group-data';
5
- export function SpontaneousQaGroupViewer({ question, isReviewMode = false, }) {
6
- return (_jsx(SpontaneousQaGroupClient, { questionData: mapQuestionToSpontaneousQaGroupData(question), isReviewMode: isReviewMode }));
5
+ function readStudentAnswer(value) {
6
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
7
+ return undefined;
8
+ }
9
+ const keys = value.audioKeys;
10
+ if (!Array.isArray(keys)) {
11
+ return undefined;
12
+ }
13
+ return {
14
+ audioKeys: keys.map((key) => (typeof key === 'string' && key.trim() !== '' ? key : null)),
15
+ };
16
+ }
17
+ export function SpontaneousQaGroupViewer({ question, isReviewMode = false, userAnswer, onItemRecorded, uploadingIndex, gradeDetails, disabled, }) {
18
+ return (_jsx(SpontaneousQaGroupClient, { questionData: mapQuestionToSpontaneousQaGroupData(question), isReviewMode: isReviewMode, userAnswer: readStudentAnswer(userAnswer), onItemRecorded: isReviewMode ? undefined : onItemRecorded, uploadingIndex: uploadingIndex, gradeDetails: isReviewMode ? undefined : gradeDetails, disabled: disabled }));
7
19
  }
@@ -4,8 +4,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
4
4
  * Movers fallback review cases (isReviewMode=true).
5
5
  * Source: oe-exam-fe ReviewQuestionRenderer.tsx
6
6
  */
7
- import { MoversAnswerTheQuestionRenderer, MoversGridFillRenderer, MoversLabelThePictureRenderer, MoversMatchByWritingAnswerRenderer, MoversMatchWordToPictureRenderer, MoversReadingPassageRenderer, MoversTrueFalseRenderer, MoversTrueFalseCorrectGroupRenderer, MoversFillInBlankGroupRenderer, MoversMatchingWithLinesGroupRenderer, MoversCrossOutWordGroupRenderer, MoversWordFillParagraphRenderer, MoversWordFillStructuredFormRenderer, MoversWordOrderingRenderer, MoversWordOrderingGroupRenderer, MoversWriteSentencesRenderer, MoversWritingRenderer, MoversCrosswordPuzzleRenderer, ChooseThenAnswerGroupRenderer, SortWordsIntoCategoriesRenderer, } from '../../../components/exams/take/components/question-renderers';
8
- import { transformAnswerTheQuestion, transformGridFill, transformLabelPicture, transformMatchByWritingAnswer, transformMatchWordToPicture, transformReadingPassage, transformTrueFalse, transformTrueFalseCorrectGroup, transformFillInBlankGroup, transformMatchingWithLinesGroup, transformWordFillParagraph, transformWordFillStructuredForm, transformWordOrdering, transformWordOrderingGroupTake, transformWriteSentences, transformWriteShortLetter, transformCrosswordPuzzle, transformChooseThenAnswerGroup, transformSortWordsIntoCategories, } from '../../../components/exams/take/utils/question-transformers';
7
+ import { MoversAnswerTheQuestionRenderer, MoversGridFillRenderer, MoversLabelThePictureRenderer, MoversMatchByWritingAnswerRenderer, MoversMatchWordToPictureRenderer, MoversMatchWordToPictureGroupRenderer, MoversReadingPassageRenderer, MoversTrueFalseRenderer, MoversTrueFalseCorrectGroupRenderer, MoversFillInBlankGroupRenderer, MoversMatchingWithLinesGroupRenderer, MoversCrossOutWordGroupRenderer, MoversWordFillParagraphRenderer, MoversWordFillStructuredFormRenderer, MoversWordOrderingRenderer, MoversWordOrderingGroupRenderer, MoversWriteSentencesRenderer, MoversWritingRenderer, MoversCrosswordPuzzleRenderer, ChooseThenAnswerGroupRenderer, SortWordsIntoCategoriesRenderer, } from '../../../components/exams/take/components/question-renderers';
8
+ import { transformAnswerTheQuestion, transformGridFill, transformLabelPicture, transformMatchByWritingAnswer, transformMatchWordToPicture, transformMatchWordToPictureGroup, transformReadingPassage, transformTrueFalse, transformTrueFalseCorrectGroup, transformFillInBlankGroup, transformMatchingWithLinesGroup, transformWordFillParagraph, transformWordFillStructuredForm, transformWordOrdering, transformWordOrderingGroupTake, transformWriteSentences, transformWriteShortLetter, transformCrosswordPuzzle, transformChooseThenAnswerGroup, transformSortWordsIntoCategories, } from '../../../components/exams/take/utils/question-transformers';
9
9
  import { normalizeCrossOutWordGroupAnswer, transformCrossOutWordGroup, } from '../../../components/exams/take/utils/cross-out-word-group.transformer';
10
10
  function asApiQuestions(questions) {
11
11
  return questions;
@@ -183,6 +183,10 @@ export function renderMoversReviewBody(ctx) {
183
183
  const data = transformMatchByWritingAnswer(apiQuestions);
184
184
  return (_jsx(MoversMatchByWritingAnswerRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, audioUrl: part.audioUrl, options: data.options, questions: data.questions, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true, viewMode: data.viewMode, groupImageUrl: data.groupImageUrl, correctAnswerTextMap: correctAnswerTextMap, isCorrectMap: isCorrectMap }));
185
185
  }
186
+ case 'MATCH_WORD_TO_PICTURE_GROUP': {
187
+ const data = transformMatchWordToPictureGroup(apiQuestions);
188
+ return (_jsx(MoversMatchWordToPictureGroupRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: data.questions.filter((q) => !q.isExample).length, audioUrl: part.audioUrl, data: data, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true }));
189
+ }
186
190
  case 'MATCH_WORD_TO_PICTURE': {
187
191
  const data = transformMatchWordToPicture(apiQuestions);
188
192
  return (_jsx(MoversMatchWordToPictureRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, wordBank: data.wordBank, questions: data.questions, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true, correctAnswerTextMap: correctAnswerTextMap, isCorrectMap: isCorrectMap }));
@@ -218,7 +222,7 @@ export function renderMoversReviewBody(ctx) {
218
222
  case 'CROSSWORD_PUZZLE': {
219
223
  const data = transformCrosswordPuzzle(apiQuestions);
220
224
  const firstQuestion = apiQuestions[0];
221
- return (_jsx(MoversCrosswordPuzzleRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, title: data.title, gridSize: data.gridSize, clues: data.clues, answers: answers, correctAnswers: data.correctAnswers, onAnswerChange: handleAnswerChange, isReviewMode: true, caseSensitive: firstQuestion?.correct_answer?.caseSensitive || false }));
225
+ return (_jsx(MoversCrosswordPuzzleRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, title: data.title, gridSize: data.gridSize, markedColumn: data.markedColumn, hiddenWord: data.hiddenWord, clues: data.clues, answers: answers, correctAnswers: data.correctAnswers, onAnswerChange: handleAnswerChange, isReviewMode: true, caseSensitive: data.caseSensitive || firstQuestion?.correct_answer?.caseSensitive || false }));
222
226
  }
223
227
  default:
224
228
  return null;
@@ -11,6 +11,7 @@ const FLATTENED_ANSWER_TYPES = new Set([
11
11
  'CHOOSE_THEN_ANSWER_GROUP',
12
12
  'FILL_IN_BLANK_GROUP',
13
13
  'MATCHING_WITH_LINES_GROUP',
14
+ 'MATCH_WORD_TO_PICTURE_GROUP',
14
15
  ]);
15
16
  export function transformResultAnswersForReview(answers, templateParts = []) {
16
17
  const groups = groupAnswers(answers);
@@ -140,13 +141,20 @@ function addCorrectAnswerMaps(result, answer) {
140
141
  texts.forEach((text, index) => { result.correctAnswerTextMap[`${answer.questionId}-blank-${index}`] = text; });
141
142
  if (answer.questionType === 'LABEL_THE_PICTURE' && isRecord(correct.labels))
142
143
  Object.entries(correct.labels).forEach(([id, value]) => { result.correctAnswerTextMap[id] = String(value); });
143
- if ((answer.questionType === 'FILL_MISSING_WORDS_IN_GRID' || answer.questionType === 'CROSSWORD_PUZZLE') && isRecord(correct.cells || correct.answers)) {
144
+ if (answer.questionType === 'FILL_MISSING_WORDS_IN_GRID' && isRecord(correct.cells || correct.answers)) {
144
145
  const rawCells = (correct.cells || correct.answers);
145
146
  result.cellAnswersMap[answer.questionId] = {
146
147
  cells: Object.fromEntries(Object.entries(rawCells).map(([k, v]) => [k, { correctAnswer: String(v) }])),
147
148
  caseSensitive: correct.caseSensitive === true,
148
149
  };
149
150
  }
151
+ if (answer.questionType === 'CROSSWORD_PUZZLE' && isRecord(correct.cells)) {
152
+ const rawCells = correct.cells;
153
+ result.cellAnswersMap[answer.questionId] = {
154
+ cells: Object.fromEntries(Object.entries(rawCells).map(([k, v]) => [k, { correctAnswer: String(v) }])),
155
+ caseSensitive: correct.caseSensitive === true,
156
+ };
157
+ }
150
158
  }
151
159
  function transformStudentAnswer(questionId, questionType, value) {
152
160
  if (value == null)
@@ -275,6 +283,17 @@ function transformStudentAnswer(questionId, questionType, value) {
275
283
  typeof item === 'string' ? item : '',
276
284
  ]));
277
285
  }
286
+ if (questionType === 'MATCH_WORD_TO_PICTURE_GROUP') {
287
+ const answer = Array.isArray(record.answer)
288
+ ? record.answer
289
+ : Array.isArray(value)
290
+ ? value
291
+ : [];
292
+ return Object.fromEntries(answer.map((item, index) => [
293
+ `${questionId}-item-${index}`,
294
+ typeof item === 'string' ? item : '',
295
+ ]));
296
+ }
278
297
  if (questionType === 'CHOOSE_THE_CORRECT_ANSWER' || questionType === 'CHOOSE_CORRECT_ADJECTIVE') {
279
298
  const rawAns = record.answer ?? record.optionId ?? record.optionIds;
280
299
  const ids = getOptionIds(questionId, rawAns);