@tinyweb_dev/oe-exam-sdk 1.0.6 → 1.0.8

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 (239) hide show
  1. package/dist/components/exams/ExamCreator.js +1 -16
  2. package/dist/components/exams/take/ExamTakingPageContainer.js +0 -12
  3. package/dist/components/exams/take/components/QuestionRenderer.js +2 -15
  4. package/dist/components/exams/take/components/question-renderers/index.d.ts +0 -3
  5. package/dist/components/exams/take/components/question-renderers/index.js +0 -3
  6. package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionRenderer.js +0 -4
  7. package/dist/components/exams/take/types/speaking.types.d.ts +0 -31
  8. package/dist/components/exams/take/types.d.ts +0 -65
  9. package/dist/components/exams/take/utils/answer-transformers.js +6 -60
  10. package/dist/components/exams/take/utils/question-transformers.d.ts +2 -15
  11. package/dist/components/exams/take/utils/question-transformers.js +0 -143
  12. package/dist/components/questions/_shared/components/compact/CompactCreatorHeader.d.ts +23 -0
  13. package/dist/components/questions/_shared/components/compact/CompactCreatorHeader.js +9 -0
  14. package/dist/components/questions/_shared/components/compact/CompactExampleToggle.d.ts +6 -0
  15. package/dist/components/questions/_shared/components/compact/CompactExampleToggle.js +8 -0
  16. package/dist/components/questions/_shared/components/compact/CompactExplanationToggle.d.ts +7 -0
  17. package/dist/components/questions/_shared/components/compact/CompactExplanationToggle.js +14 -0
  18. package/dist/components/questions/_shared/components/compact/CompactFieldCard.d.ts +9 -0
  19. package/dist/components/questions/_shared/components/compact/CompactFieldCard.js +7 -0
  20. package/dist/components/questions/_shared/components/compact/CompactPreviewBanner.d.ts +7 -0
  21. package/dist/components/questions/_shared/components/compact/CompactPreviewBanner.js +7 -0
  22. package/dist/components/questions/_shared/components/compact/CompactPreviewButton.d.ts +6 -0
  23. package/dist/components/questions/_shared/components/compact/CompactPreviewButton.js +7 -0
  24. package/dist/components/questions/_shared/components/compact/index.d.ts +6 -0
  25. package/dist/components/questions/_shared/components/compact/index.js +6 -0
  26. package/dist/components/questions/_shared/config/question-types.config.js +0 -24
  27. package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +0 -9
  28. package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.d.ts +2 -2
  29. package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.js +5 -1
  30. package/dist/components/questions/_shared/types/index.d.ts +0 -1
  31. package/dist/components/questions/_shared/types/index.js +0 -1
  32. package/dist/components/questions/_shared/types/look-and-choose-answer.type.d.ts +0 -13
  33. package/dist/components/questions/_shared/utils/answer-builder.js +0 -11
  34. package/dist/components/questions/components/ColorRegionCreator.js +66 -63
  35. package/dist/components/questions/components/QuestionSearchDropdown.js +0 -8
  36. package/dist/components/questions/components/QuestionTypeSelector.js +0 -2
  37. package/dist/components/questions/components/index.d.ts +1 -0
  38. package/dist/components/questions/components/index.js +1 -0
  39. package/dist/components/questions/creator/question-type-registry.js +0 -8
  40. package/dist/components/questions/groups/ArticlesGroupCard.js +2 -2
  41. package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -1
  42. package/dist/components/questions/groups/DocumentGroupCard.js +1 -1
  43. package/dist/components/questions/groups/ImageLabelGroupCard.js +1 -1
  44. package/dist/components/questions/groups/MatchAnswerGroupCard.js +2 -2
  45. package/dist/components/questions/question-bank/QuestionEditorRenderer.d.ts +0 -3
  46. package/dist/components/questions/question-bank/QuestionEditorRenderer.js +1 -10
  47. package/dist/components/questions/question-bank/editor-adapters/choice-adapters.d.ts +0 -2
  48. package/dist/components/questions/question-bank/editor-adapters/choice-adapters.js +0 -41
  49. package/dist/components/questions/question-bank/editor-adapters/index.d.ts +2 -2
  50. package/dist/components/questions/question-bank/editor-adapters/index.js +2 -2
  51. package/dist/components/questions/question-bank/editor-adapters/media-adapters.d.ts +0 -1
  52. package/dist/components/questions/question-bank/editor-adapters/media-adapters.js +0 -28
  53. package/dist/components/questions/question-bank/question-bank-picker.utils.js +0 -7
  54. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +16 -16
  55. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +36 -30
  56. package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js +1 -1
  57. package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupCreator.js +7 -6
  58. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +2 -2
  59. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +11 -7
  60. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -1
  61. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +11 -5
  62. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +5 -5
  63. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +34 -28
  64. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseAnswerGroupImageUpload.d.ts +7 -0
  65. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseAnswerGroupImageUpload.js +5 -2
  66. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +9 -5
  67. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupCreator.js +65 -62
  68. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupItemEditor.d.ts +6 -0
  69. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupItemEditor.js +145 -0
  70. package/dist/components/questions/types/choose-the-correct-answer-group/map-choose-the-correct-answer-group-data.js +6 -15
  71. package/dist/components/questions/types/choose-the-correct-answer-group/transform.js +5 -4
  72. package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js +7 -7
  73. package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupCreator.js +8 -5
  74. package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.js +2 -2
  75. package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupCreator.js +20 -16
  76. package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.js +1 -1
  77. package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleCreator.js +66 -29
  78. package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +5 -5
  79. package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +45 -37
  80. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.js +24 -20
  81. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +2 -2
  82. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +68 -65
  83. package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.js +1 -1
  84. package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixCreator.js +45 -5
  85. package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +2 -2
  86. package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +18 -14
  87. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +2 -2
  88. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +30 -30
  89. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +2 -2
  90. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +24 -31
  91. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +2 -2
  92. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +29 -32
  93. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -1
  94. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +27 -29
  95. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -1
  96. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +11 -15
  97. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -1
  98. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +22 -24
  99. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -1
  100. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +17 -24
  101. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -1
  102. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +31 -31
  103. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -1
  104. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +11 -15
  105. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -1
  106. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +14 -15
  107. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +2 -2
  108. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +27 -25
  109. package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.js +12 -12
  110. package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesCreator.js +8 -5
  111. package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureClient.js +8 -8
  112. package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureCreator.js +25 -20
  113. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js +1 -1
  114. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupCreator.js +13 -10
  115. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.js +8 -5
  116. package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +7 -11
  117. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -1
  118. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +15 -12
  119. package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesClient.js +1 -1
  120. package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesCreator.js +5 -5
  121. package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +35 -39
  122. package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +2 -2
  123. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardClient.js +1 -1
  124. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +7 -8
  125. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -1
  126. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +30 -23
  127. package/dist/components/questions/types/spontaneous-qa/SpontaneousQAClient.js +1 -1
  128. package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +26 -19
  129. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.d.ts +1 -1
  130. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.js +79 -3
  131. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupCreator.js +13 -11
  132. package/dist/components/questions/types/spontaneous-qa-group/index.d.ts +1 -0
  133. package/dist/components/questions/types/spontaneous-qa-group/index.js +1 -0
  134. package/dist/components/questions/types/spontaneous-qa-group/map-spontaneous-qa-group-data.d.ts +2 -0
  135. package/dist/components/questions/types/spontaneous-qa-group/map-spontaneous-qa-group-data.js +74 -0
  136. package/dist/components/questions/types/true-false/TrueFalseClient.js +2 -2
  137. package/dist/components/questions/types/true-false/TrueFalseCreator.js +35 -29
  138. package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.js +1 -1
  139. package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupCreator.js +11 -7
  140. package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.js +1 -1
  141. package/dist/components/questions/types/true-false-group/TrueFalseGroupCreator.js +11 -7
  142. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +2 -2
  143. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +21 -56
  144. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -1
  145. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +30 -24
  146. package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupClient.js +3 -3
  147. package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupCreator.js +12 -14
  148. package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -1
  149. package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +27 -18
  150. package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.js +7 -7
  151. package/dist/components/questions/types/word-ordering-group/WordOrderingGroupCreator.js +16 -13
  152. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +2 -2
  153. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +34 -28
  154. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +2 -2
  155. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +22 -22
  156. package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +21 -18
  157. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -1
  158. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +7 -4
  159. package/dist/components/questions/viewer/QuestionViewer.js +0 -98
  160. package/dist/components/questions/viewer/SpontaneousQaGroupViewer.js +4 -79
  161. package/dist/components/results/renderers/review-question-body-movers.js +2 -26
  162. package/dist/components/results/review-data.js +0 -4
  163. package/dist/components/ui/points-input.d.ts +1 -0
  164. package/dist/components/ui/points-input.js +12 -2
  165. package/dist/shared/constants/question-skills.js +0 -11
  166. package/dist/shared/lib/i18n/messages/en/admin.d.ts +0 -3
  167. package/dist/shared/lib/i18n/messages/en/admin.js +0 -3
  168. package/dist/shared/lib/i18n/messages/en.d.ts +0 -3
  169. package/dist/shared/lib/i18n/messages/vi/admin.d.ts +0 -3
  170. package/dist/shared/lib/i18n/messages/vi/admin.js +0 -3
  171. package/dist/shared/lib/i18n/messages/vi.d.ts +0 -3
  172. package/dist/shared/lib/utils/question-reverse-transform.js +4 -23
  173. package/dist/shared/lib/utils/question-transform-types.d.ts +1 -23
  174. package/dist/shared/lib/utils/question-transform-validation.js +0 -40
  175. package/dist/shared/lib/utils/question-transform.js +0 -9
  176. package/dist/shared/types/common.types.d.ts +1 -1
  177. package/dist/shared/types/entities/question.types.d.ts +1 -11
  178. package/dist/shared/types/questions/choose-the-correct-answer-group.d.ts +1 -1
  179. package/dist/shared/types/questions/index.d.ts +0 -3
  180. package/dist/shared/types/questions/index.js +0 -6
  181. package/package.json +1 -1
  182. package/dist/components/exams/take/components/question-renderers/MoversFillWordHintRenderer.d.ts +0 -2
  183. package/dist/components/exams/take/components/question-renderers/MoversFillWordHintRenderer.js +0 -131
  184. package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.d.ts +0 -2
  185. package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.js +0 -76
  186. package/dist/components/exams/take/components/question-renderers/MoversPictureFillBlankChooseRenderer.d.ts +0 -2
  187. package/dist/components/exams/take/components/question-renderers/MoversPictureFillBlankChooseRenderer.js +0 -68
  188. package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.d.ts +0 -16
  189. package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.js +0 -212
  190. package/dist/components/questions/_shared/types/gn-speaking-interview.type.d.ts +0 -32
  191. package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +0 -1
  192. package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.d.ts +0 -47
  193. package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +0 -1
  194. package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.d.ts +0 -2
  195. package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +0 -262
  196. package/dist/components/questions/types/gn-speaking-interview/register.d.ts +0 -2
  197. package/dist/components/questions/types/gn-speaking-interview/register.js +0 -121
  198. package/dist/components/questions/types/gn-speaking-interview/transform.d.ts +0 -2
  199. package/dist/components/questions/types/gn-speaking-interview/transform.js +0 -48
  200. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.d.ts +0 -20
  201. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +0 -72
  202. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.d.ts +0 -15
  203. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +0 -230
  204. package/dist/components/questions/types/look-picture-choose-correct-answer/index.d.ts +0 -1
  205. package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +0 -1
  206. package/dist/components/questions/types/look-picture-choose-correct-answer/register.d.ts +0 -2
  207. package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +0 -66
  208. package/dist/components/questions/types/look-picture-choose-correct-answer/transform.d.ts +0 -2
  209. package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +0 -42
  210. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.d.ts +0 -6
  211. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +0 -96
  212. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.d.ts +0 -15
  213. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +0 -238
  214. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.d.ts +0 -2
  215. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +0 -2
  216. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.d.ts +0 -2
  217. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +0 -76
  218. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.d.ts +0 -2
  219. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +0 -19
  220. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.d.ts +0 -7
  221. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +0 -134
  222. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.d.ts +0 -3
  223. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +0 -617
  224. package/dist/components/questions/types/look-picture-fill-word-hint/index.d.ts +0 -2
  225. package/dist/components/questions/types/look-picture-fill-word-hint/index.js +0 -2
  226. package/dist/components/questions/types/look-picture-fill-word-hint/register.d.ts +0 -2
  227. package/dist/components/questions/types/look-picture-fill-word-hint/register.js +0 -82
  228. package/dist/components/questions/types/look-picture-fill-word-hint/transform.d.ts +0 -2
  229. package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +0 -29
  230. package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.d.ts +0 -9
  231. package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +0 -12
  232. package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.d.ts +0 -17
  233. package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +0 -47
  234. package/dist/shared/types/questions/look-picture-choose-correct-answer.d.ts +0 -38
  235. package/dist/shared/types/questions/look-picture-choose-correct-answer.js +0 -5
  236. package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.d.ts +0 -63
  237. package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +0 -9
  238. package/dist/shared/types/questions/look-picture-fill-word-hint.d.ts +0 -57
  239. package/dist/shared/types/questions/look-picture-fill-word-hint.js +0 -7
@@ -3,19 +3,15 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useState } from 'react';
4
4
  import { Button } from '../../../../components/ui/button';
5
5
  import { Label } from '../../../../components/ui/label';
6
- import { Input } from '../../../../components/ui/input';
7
6
  import { Textarea } from '../../../../components/ui/textarea';
8
- import { PointsInput } from '../../../../components/ui/points-input';
9
- import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
10
- import { Switch } from '../../../../components/ui/switch';
11
- import { FileUpload } from '../../../../components/ui/file-upload';
12
7
  import { RichTextEditor } from '../../../../components/shared/RichTextEditor';
13
8
  import { useDebouncedCallback } from '../../../../shared/lib/hooks';
14
- import { Eye, ImageIcon, Pencil, Plus, Star, Trash2, Type } from 'lucide-react';
9
+ import { BookOpen, HelpCircle, ImageIcon, Plus, Trash2, Volume2 } from 'lucide-react';
10
+ import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
15
11
  import { ChooseTheCorrectAnswerGroupClient } from './ChooseTheCorrectAnswerGroupClient';
16
- import { ChooseAnswerGroupImageUploadItem, ChooseAnswerGroupOptionImageField, } from './ChooseAnswerGroupImageUpload';
17
- import { fromChooseAnswerGroupImageUrls, getChooseAnswerGroupOptionImageSrc, isChooseAnswerGroupOptionFilled, toChooseAnswerGroupImageUrls, toEditableChooseAnswerGroupImageUrls, } from '../../_shared/types/choose-the-correct-answer-group.type';
18
- const OPTION_LABELS = ['A', 'B', 'C', 'D', 'E', 'F'];
12
+ import { ChooseAnswerGroupAudioUploadItem, ChooseAnswerGroupImageUploadItem, } from './ChooseAnswerGroupImageUpload';
13
+ import { ChooseTheCorrectAnswerGroupItemEditor } from './ChooseTheCorrectAnswerGroupItemEditor';
14
+ import { fromChooseAnswerGroupImageUrls, isChooseAnswerGroupOptionFilled, toChooseAnswerGroupImageUrls, toEditableChooseAnswerGroupImageUrls, } from '../../_shared/types/choose-the-correct-answer-group.type';
19
15
  function createEmptyItem(questionNumber) {
20
16
  return {
21
17
  question: '',
@@ -23,7 +19,6 @@ function createEmptyItem(questionNumber) {
23
19
  options: [
24
20
  { id: 'opt-1', text: '' },
25
21
  { id: 'opt-2', text: '' },
26
- { id: 'opt-3', text: '' },
27
22
  ],
28
23
  correctAnswer: 'opt-1',
29
24
  isExample: false,
@@ -37,6 +32,7 @@ function normalizeItems(rawItems) {
37
32
  }
38
33
  return rawItems.map((item, index) => {
39
34
  const imageUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(item.imageUrl));
35
+ const audioUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(item.audioUrl));
40
36
  return {
41
37
  question: item.question || '',
42
38
  optionType: item.optionType === 'image' ? 'image' : 'text',
@@ -52,44 +48,45 @@ function normalizeItems(rawItems) {
52
48
  points: item.isExample ? 0 : (typeof item.points === 'number' ? item.points : 1),
53
49
  questionNumber: item.questionNumber || index + 1,
54
50
  ...(imageUrl !== undefined ? { imageUrl } : {}),
55
- ...(item.audioUrl ? { audioUrl: item.audioUrl } : {}),
51
+ ...(audioUrl !== undefined ? { audioUrl } : {}),
56
52
  };
57
53
  });
58
54
  }
59
55
  function sumPoints(items) {
60
56
  return items.reduce((total, item) => (item.isExample ? total : total + (typeof item.points === 'number' ? item.points : 1)), 0);
61
57
  }
62
- function parsePointsValue(raw, allowZero) {
63
- const parsed = Number.parseInt(raw.replace(/[^0-9]/g, ''), 10);
64
- if (Number.isNaN(parsed)) {
65
- return allowZero ? 0 : 1;
66
- }
67
- return allowZero ? Math.max(0, parsed) : Math.max(1, parsed);
68
- }
69
58
  export function ChooseTheCorrectAnswerGroupCreator({ initialData, onChange, externalErrors, onUnsavedChangesChange, validationRef, }) {
70
59
  const [instruction, setInstruction] = useState(initialData?.instruction || 'Choose the correct answer.');
71
60
  const [passage, setPassage] = useState(initialData?.passage || '');
72
- const [audioUrl, setAudioUrl] = useState(initialData?.audioUrl || '');
73
- const [imageUrl, setImageUrl] = useState(toChooseAnswerGroupImageUrls(initialData?.imageUrl)[0] || '');
61
+ const [imageUrls, setImageUrls] = useState(() => (toEditableChooseAnswerGroupImageUrls(initialData?.imageUrl)));
62
+ const [audioUrls, setAudioUrls] = useState(() => (toEditableChooseAnswerGroupImageUrls(initialData?.audioUrl)));
74
63
  const [items, setItems] = useState(() => normalizeItems(initialData?.items));
75
64
  const [explanation, setExplanation] = useState(initialData?.explanation || '');
76
65
  const [isClientMode, setIsClientMode] = useState(false);
77
66
  const [errors, setErrors] = useState([]);
67
+ const [isPassageVisible, setIsPassageVisible] = useState(() => {
68
+ const html = initialData?.passage;
69
+ return Boolean(html && html !== '<p></p>' && html !== '<p><br></p>' && html.trim());
70
+ });
78
71
  const onChangeRef = useRef(onChange);
79
72
  const previousPayloadRef = useRef('');
80
73
  useEffect(() => {
81
74
  onChangeRef.current = onChange;
82
75
  }, [onChange]);
83
76
  const isContentEmpty = (html) => !html || html === '<p></p>' || html === '<p><br></p>' || html.trim() === '';
84
- const buildPayload = (nextInstruction = instruction, nextPassage = passage, nextItems = items, nextExplanation = explanation, nextAudioUrl = audioUrl, nextImageUrl = imageUrl) => ({
85
- instruction: nextInstruction,
86
- ...(!isContentEmpty(nextPassage) ? { passage: nextPassage.trim() } : {}),
87
- ...(nextAudioUrl.trim() ? { audioUrl: nextAudioUrl.trim() } : {}),
88
- ...(nextImageUrl.trim() ? { imageUrl: nextImageUrl.trim() } : {}),
89
- items: nextItems,
90
- explanation: nextExplanation,
91
- points: sumPoints(nextItems),
92
- });
77
+ const buildPayload = (nextInstruction = instruction, nextPassage = passage, nextItems = items, nextExplanation = explanation, nextAudioUrls = audioUrls, nextImageUrls = imageUrls) => {
78
+ const nextAudioUrl = fromChooseAnswerGroupImageUrls(nextAudioUrls);
79
+ const nextImageUrl = fromChooseAnswerGroupImageUrls(nextImageUrls);
80
+ return {
81
+ instruction: nextInstruction,
82
+ ...(!isContentEmpty(nextPassage) ? { passage: nextPassage.trim() } : {}),
83
+ ...(nextAudioUrl !== undefined ? { audioUrl: nextAudioUrl } : {}),
84
+ ...(nextImageUrl !== undefined ? { imageUrl: nextImageUrl } : {}),
85
+ items: nextItems,
86
+ explanation: nextExplanation,
87
+ points: sumPoints(nextItems),
88
+ };
89
+ };
93
90
  const debouncedOnChange = useDebouncedCallback((payload) => {
94
91
  onChangeRef.current?.(payload);
95
92
  }, 300);
@@ -102,7 +99,7 @@ export function ChooseTheCorrectAnswerGroupCreator({ initialData, onChange, exte
102
99
  previousPayloadRef.current = serialized;
103
100
  onUnsavedChangesChange?.(true);
104
101
  debouncedOnChange(payload);
105
- }, [instruction, passage, audioUrl, imageUrl, items, explanation, debouncedOnChange, onUnsavedChangesChange]);
102
+ }, [instruction, passage, audioUrls, imageUrls, items, explanation, debouncedOnChange, onUnsavedChangesChange]);
106
103
  const validate = () => {
107
104
  const nextErrors = [];
108
105
  if (!instruction.trim()) {
@@ -112,9 +109,6 @@ export function ChooseTheCorrectAnswerGroupCreator({ initialData, onChange, exte
112
109
  nextErrors.push('Cần ít nhất một câu hỏi.');
113
110
  }
114
111
  items.forEach((item, index) => {
115
- if (!item.question.trim()) {
116
- nextErrors.push(`Câu ${index + 1}: nội dung câu hỏi là bắt buộc.`);
117
- }
118
112
  const filledOptions = item.options.filter((option) => (isChooseAnswerGroupOptionFilled(option, item.optionType)));
119
113
  if (filledOptions.length < 2) {
120
114
  nextErrors.push(`Câu ${index + 1}: cần ít nhất 2 đáp án.`);
@@ -141,15 +135,6 @@ export function ChooseTheCorrectAnswerGroupCreator({ initialData, onChange, exte
141
135
  const updateItem = (index, patch) => {
142
136
  setItems((current) => current.map((item, itemIndex) => (itemIndex === index ? { ...item, ...patch } : item)));
143
137
  };
144
- const updateOption = (itemIndex, optionIndex, patch) => {
145
- setItems((current) => current.map((item, index) => {
146
- if (index !== itemIndex) {
147
- return item;
148
- }
149
- const options = item.options.map((option, currentOptionIndex) => (currentOptionIndex === optionIndex ? { ...option, ...patch } : option));
150
- return { ...item, options };
151
- }));
152
- };
153
138
  const addItem = () => {
154
139
  setItems((current) => [...current, createEmptyItem(current.length + 1)]);
155
140
  };
@@ -164,26 +149,44 @@ export function ChooseTheCorrectAnswerGroupCreator({ initialData, onChange, exte
164
149
  });
165
150
  };
166
151
  if (isClientMode) {
167
- return (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "flex justify-end", children: _jsxs(Button, { type: "button", variant: "outline", onClick: () => setIsClientMode(false), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), "Ch\u1EC9nh s\u1EEDa"] }) }), _jsx(ChooseTheCorrectAnswerGroupClient, { questionData: buildPayload(), isReviewMode: true })] }));
152
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(ChooseTheCorrectAnswerGroupClient, { questionData: buildPayload(), isReviewMode: true })] }));
168
153
  }
169
154
  const allErrors = [...errors, ...(externalErrors || [])];
170
- return (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "flex justify-end", children: _jsxs(Button, { type: "button", variant: "outline", onClick: () => setIsClientMode(true), children: [_jsx(Eye, { className: "mr-2 h-4 w-4" }), "Xem tr\u01B0\u1EDBc"] }) }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Nh\u00F3m ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng" }) }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "group-instruction", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx(Input, { id: "group-instruction", value: instruction, onChange: (event) => setInstruction(event.target.value), placeholder: "Choose the correct answer." })] }), _jsx("div", { className: "space-y-2", children: _jsx(FileUpload, { id: "choose-answer-group-audio", label: "Audio chung (tu\u1EF3 ch\u1ECDn)", accept: "audio/*", value: audioUrl, onChange: setAudioUrl, maxSize: 20, placeholder: "Upload file audio ho\u1EB7c paste URL", autoUpload: true, prefix: "questions", showPlayButton: true }) }), _jsx("div", { className: "space-y-2", children: _jsx(FileUpload, { id: "choose-answer-group-image", label: "H\u00ECnh \u1EA3nh chung (tu\u1EF3 ch\u1ECDn)", accept: "image/*", value: imageUrl, onChange: setImageUrl, maxSize: 5, placeholder: "Upload \u1EA3nh nh\u00F3m ho\u1EB7c paste URL", autoUpload: true, prefix: "questions" }) }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "N\u1ED9i dung b\u00E0i \u0111\u1ECDc (Passage - Tu\u1EF3 ch\u1ECDn)" }), _jsx(RichTextEditor, { value: passage, onChange: setPassage, minHeightClassName: "min-h-[160px]" })] }), _jsxs("div", { className: "rounded-xl border border-gray-200 bg-gray-50 p-4", children: [_jsx(PointsInput, { value: sumPoints(items), allowZero: true, readOnly: true }), _jsx("p", { className: "mt-2 text-xs text-gray-500", children: "T\u1ED5ng \u0111i\u1EC3m \u0111\u01B0\u1EE3c c\u1ED9ng t\u1EEB c\u00E1c c\u00E2u kh\u00F4ng ph\u1EA3i example." })] })] })] }), items.map((item, itemIndex) => (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between space-y-0", children: [_jsxs(CardTitle, { className: "text-base", children: ["C\u00E2u ", itemIndex + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeItem(itemIndex), disabled: items.length <= 1, children: _jsx(Trash2, { className: "h-4 w-4" }) })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between rounded-lg border border-amber-100 bg-amber-50 px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-amber-800", children: [_jsx(Star, { className: "h-4 w-4" }), "C\u00E2u v\u00ED d\u1EE5"] }), _jsx(Switch, { checked: item.isExample === true, onCheckedChange: (checked) => updateItem(itemIndex, {
171
- isExample: checked,
172
- points: checked ? 0 : (item.points || 1),
173
- }) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "C\u00E2u h\u1ECFi" }), _jsx(Textarea, { value: item.question, onChange: (event) => updateItem(itemIndex, { question: event.target.value }), rows: 2, placeholder: "A place for someone to stay or live is called" })] }), _jsx("div", { className: "space-y-2", children: _jsx(FileUpload, { id: `choose-answer-item-audio-${itemIndex}`, label: "Audio c\u00E2u h\u1ECFi (tu\u1EF3 ch\u1ECDn)", accept: "audio/*", value: item.audioUrl || '', onChange: (url) => updateItem(itemIndex, { audioUrl: url }), maxSize: 20, placeholder: "Upload file audio ho\u1EB7c paste URL", autoUpload: true, prefix: "questions", showPlayButton: true }) }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Label, { className: "flex items-center gap-2", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-teal-500" }), "H\u00ECnh \u1EA3nh (tu\u1EF3 ch\u1ECDn)"] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => {
174
- const nextUrls = [...toEditableChooseAnswerGroupImageUrls(item.imageUrl), ''];
175
- updateItem(itemIndex, { imageUrl: nextUrls });
176
- }, className: "gap-1.5 border-teal-200 text-teal-600 hover:bg-teal-50 hover:text-teal-700", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm h\u00ECnh \u1EA3nh"] })] }), toEditableChooseAnswerGroupImageUrls(item.imageUrl).length === 0 ? (_jsx("div", { className: "rounded-lg border border-dashed border-gray-200 bg-gray-50/50 p-3 text-xs text-gray-500", children: "Ch\u01B0a c\u00F3 h\u00ECnh \u1EA3nh. Nh\u1EA5n \"Th\u00EAm h\u00ECnh \u1EA3nh\" \u0111\u1EC3 upload \u1EA3nh minh ho\u1EA1 cho c\u00E2u h\u1ECFi." })) : (_jsx("div", { className: "space-y-3", children: toEditableChooseAnswerGroupImageUrls(item.imageUrl).map((imageUrl, imageIndex) => (_jsx(ChooseAnswerGroupImageUploadItem, { index: imageIndex, itemIndex: itemIndex, imageUrl: imageUrl, onChange: (url) => {
177
- const nextUrls = toEditableChooseAnswerGroupImageUrls(item.imageUrl);
178
- nextUrls[imageIndex] = url;
179
- updateItem(itemIndex, { imageUrl: nextUrls });
180
- }, onRemove: () => {
181
- const nextUrls = toEditableChooseAnswerGroupImageUrls(item.imageUrl)
182
- .filter((_, idx) => idx !== imageIndex);
183
- updateItem(itemIndex, { imageUrl: fromChooseAnswerGroupImageUrls(nextUrls) });
184
- } }, `${itemIndex}-${imageIndex}`))) }))] }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Label, { children: "\u0110\u00E1p \u00E1n" }), _jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-3 py-1.5", children: [_jsx(Type, { className: `h-3.5 w-3.5 ${item.optionType === 'text' ? 'text-indigo-600' : 'text-gray-400'}` }), _jsx("span", { className: `text-xs font-medium ${item.optionType === 'text' ? 'text-indigo-600' : 'text-gray-400'}`, children: "Text" }), _jsx(Switch, { checked: item.optionType === 'image', onCheckedChange: (checked) => updateItem(itemIndex, {
185
- optionType: checked ? 'image' : 'text',
186
- }) }), _jsx(ImageIcon, { className: `h-3.5 w-3.5 ${item.optionType === 'image' ? 'text-indigo-600' : 'text-gray-400'}` }), _jsx("span", { className: `text-xs font-medium ${item.optionType === 'image' ? 'text-indigo-600' : 'text-gray-400'}`, children: "\u1EA2nh" })] })] }), item.options.map((option, optionIndex) => (_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("input", { type: "radio", name: `correct-${itemIndex}`, checked: item.correctAnswer === option.id, onChange: () => updateItem(itemIndex, { correctAnswer: option.id }), className: "mt-2" }), _jsx("span", { className: "mt-2 w-6 text-sm font-semibold text-gray-600", children: OPTION_LABELS[optionIndex] || optionIndex + 1 }), item.optionType === 'image' ? (_jsx(ChooseAnswerGroupOptionImageField, { itemIndex: itemIndex, optionIndex: optionIndex, imageUrl: getChooseAnswerGroupOptionImageSrc(option), onChange: (url) => updateOption(itemIndex, optionIndex, { imageUrl: url, text: '' }) })) : (_jsx(Input, { value: option.text, onChange: (event) => updateOption(itemIndex, optionIndex, { text: event.target.value }), placeholder: `Đáp án ${OPTION_LABELS[optionIndex]}` }))] }, option.id)))] }), !item.isExample && (_jsx(PointsInput, { value: item.points, allowZero: true, onChange: (event) => updateItem(itemIndex, {
187
- points: parsePointsValue(event.target.value, true),
188
- }) }))] })] }, `item-${itemIndex}`))), _jsxs(Button, { type: "button", variant: "outline", onClick: addItem, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Th\u00EAm c\u00E2u h\u1ECFi"] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Gi\u1EA3i th\u00EDch" }) }), _jsx(CardContent, { children: _jsx(Textarea, { id: "explanation", value: explanation, onChange: (event) => setExplanation(event.target.value), placeholder: "VD: C\u00E2u tr\u1EA3 l\u1EDDi \u0111\u00FAng l\u00E0... v\u00EC...", rows: 3 }) })] }), allErrors.length > 0 && (_jsx("div", { className: "rounded-xl border border-red-200 bg-red-50 p-4 text-sm text-red-700", children: allErrors.map((error) => (_jsx("p", { children: error }, error))) }))] }));
155
+ return (_jsxs("div", { className: "space-y-3", children: [_jsxs(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(HelpCircle, { className: "h-3.5 w-3.5" }), title: "Nh\u00F3m ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng", points: {
156
+ value: sumPoints(items),
157
+ onValueChange: () => undefined,
158
+ allowZero: true,
159
+ }, preview: { onClick: () => setIsClientMode(true) } }), children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "group-instruction", className: "text-sm font-semibold text-gray-800", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsxs("div", { className: "relative", children: [_jsx(Textarea, { id: "group-instruction", value: instruction, onChange: (event) => setInstruction(event.target.value), placeholder: "Choose the correct answer.", rows: 2, className: "min-h-[56px] border-gray-200 pb-11" }), _jsxs("div", { className: "absolute bottom-1.5 right-1.5 z-10 flex items-center gap-1 rounded-md border border-indigo-100 bg-white/95 p-0.5 shadow-sm", children: [_jsx("button", { type: "button", title: "Th\u00EAm h\u00ECnh \u1EA3nh", onClick: () => {
160
+ const hasEmpty = imageUrls.some((url) => !url.trim());
161
+ if (imageUrls.length === 0 || !hasEmpty) {
162
+ setImageUrls([...imageUrls, '']);
163
+ }
164
+ }, className: `inline-flex h-8 w-8 items-center justify-center rounded-md transition-colors ${imageUrls.length > 0
165
+ ? 'bg-indigo-600 text-white shadow-sm'
166
+ : 'bg-indigo-50 text-indigo-600 hover:bg-indigo-100'}`, children: _jsx(ImageIcon, { className: "h-4 w-4" }) }), _jsx("button", { type: "button", title: "Th\u00EAm audio", onClick: () => {
167
+ const hasEmpty = audioUrls.some((url) => !url.trim());
168
+ if (audioUrls.length === 0 || !hasEmpty) {
169
+ setAudioUrls([...audioUrls, '']);
170
+ }
171
+ }, className: `inline-flex h-8 w-8 items-center justify-center rounded-md transition-colors ${audioUrls.length > 0
172
+ ? 'bg-violet-600 text-white shadow-sm'
173
+ : 'bg-violet-50 text-violet-600 hover:bg-violet-100'}`, children: _jsx(Volume2, { className: "h-4 w-4" }) })] })] })] }), imageUrls.length > 0 && (_jsxs("div", { className: "space-y-2 rounded-md border border-gray-200 bg-slate-50/80 p-2", children: [imageUrls.map((url, imageIndex) => (_jsx(ChooseAnswerGroupImageUploadItem, { index: imageIndex, itemIndex: -1, imageUrl: url, onChange: (nextUrl) => {
174
+ setImageUrls((current) => current.map((item, idx) => (idx === imageIndex ? nextUrl : item)));
175
+ }, onRemove: () => {
176
+ setImageUrls((current) => current.filter((_, idx) => idx !== imageIndex));
177
+ } }, `group-image-${imageIndex}`))), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setImageUrls((current) => (current.length ? [...current, ''] : [''])), className: "h-8 gap-1.5 border-dashed border-indigo-300 text-indigo-600 hover:bg-indigo-50 hover:text-indigo-700", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm \u1EA3nh"] })] })), audioUrls.length > 0 && (_jsxs("div", { className: "space-y-2 rounded-md border border-gray-200 bg-slate-50/80 p-2", children: [audioUrls.map((url, audioIndex) => (_jsx(ChooseAnswerGroupAudioUploadItem, { index: audioIndex, itemIndex: -1, audioUrl: url, onChange: (nextUrl) => {
178
+ setAudioUrls((current) => current.map((item, idx) => (idx === audioIndex ? nextUrl : item)));
179
+ }, onRemove: () => {
180
+ setAudioUrls((current) => current.filter((_, idx) => idx !== audioIndex));
181
+ } }, `group-audio-${audioIndex}`))), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setAudioUrls((current) => (current.length ? [...current, ''] : [''])), className: "h-8 gap-1.5 border-dashed border-violet-300 text-violet-600 hover:bg-violet-50 hover:text-violet-700", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm audio"] })] })), !isPassageVisible ? (_jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setIsPassageVisible(true), className: "h-8 gap-1.5 border-gray-200 px-2.5 text-xs text-gray-600 hover:bg-gray-50", children: [_jsx(BookOpen, { className: "h-3.5 w-3.5" }), "Th\u00EAm n\u1ED9i dung b\u00E0i \u0111\u1ECDc"] })) : (_jsxs("div", { className: "relative space-y-1.5 pr-10", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setIsPassageVisible(false), className: "absolute right-0 top-0 z-10 h-7 px-2 text-xs text-gray-500 hover:text-gray-700", children: "\u1EA8n" }), _jsxs(Label, { className: "text-sm font-semibold text-gray-800", children: ["N\u1ED9i dung b\u00E0i \u0111\u1ECDc ", _jsx("span", { className: "text-xs font-normal text-gray-500", children: "(tu\u1EF3 ch\u1ECDn)" })] }), _jsx(RichTextEditor, { value: passage, onChange: setPassage, variant: "compact", minHeightClassName: "min-h-[72px]" })] }))] }), items.map((item, itemIndex) => (_jsx(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(HelpCircle, { className: "h-3.5 w-3.5" }), title: `Câu ${itemIndex + 1}`, points: !item.isExample ? {
182
+ value: item.points,
183
+ allowZero: true,
184
+ onValueChange: (value) => updateItem(itemIndex, { points: value }),
185
+ } : undefined, example: {
186
+ checked: item.isExample === true,
187
+ onChange: (checked) => updateItem(itemIndex, {
188
+ isExample: checked,
189
+ points: checked ? 0 : (item.points || 1),
190
+ }),
191
+ }, extraActions: _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeItem(itemIndex), disabled: items.length <= 1, className: "h-8 w-8 text-gray-400 hover:bg-red-50 hover:text-red-600", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }) }), children: _jsx(ChooseTheCorrectAnswerGroupItemEditor, { item: item, itemIndex: itemIndex, onChange: (patch) => updateItem(itemIndex, patch) }) }, `item-${itemIndex}`))), _jsxs(Button, { type: "button", variant: "outline", onClick: addItem, className: "h-8 gap-1.5 border-dashed border-green-200 px-2.5 text-xs text-green-600 hover:bg-green-50 hover:text-green-700", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm c\u00E2u h\u1ECFi"] }), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(initialData?.explanation) }), allErrors.length > 0 && (_jsx("div", { className: "rounded-md border border-red-200 bg-red-50 p-2 text-xs text-red-700", children: allErrors.map((error) => (_jsx("p", { children: error }, error))) }))] }));
189
192
  }
@@ -0,0 +1,6 @@
1
+ import type { ChooseTheCorrectAnswerGroupItemData } from '../../_shared/types/choose-the-correct-answer-group.type';
2
+ export declare function ChooseTheCorrectAnswerGroupItemEditor({ item, itemIndex, onChange, }: {
3
+ item: ChooseTheCorrectAnswerGroupItemData;
4
+ itemIndex: number;
5
+ onChange: (patch: Partial<ChooseTheCorrectAnswerGroupItemData>) => void;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,145 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useRef, useState } from 'react';
4
+ import { Button } from '../../../../components/ui/button';
5
+ import { Label } from '../../../../components/ui/label';
6
+ import { Input } from '../../../../components/ui/input';
7
+ import { Textarea } from '../../../../components/ui/textarea';
8
+ import { Switch } from '../../../../components/ui/switch';
9
+ import { RichTextEditor } from '../../../../components/shared/RichTextEditor';
10
+ import { hasRichTextFormatting, htmlToPlainEditorText } from '../../../../shared/lib/rich-text';
11
+ import { CheckCircle2, ImageIcon, Pilcrow, Plus, Trash2, Type, Volume2 } from 'lucide-react';
12
+ import { ChooseAnswerGroupAudioUploadItem, ChooseAnswerGroupImageUploadItem, ChooseAnswerGroupOptionImageField, } from './ChooseAnswerGroupImageUpload';
13
+ import { fromChooseAnswerGroupImageUrls, getChooseAnswerGroupOptionImageSrc, toEditableChooseAnswerGroupImageUrls, } from '../../_shared/types/choose-the-correct-answer-group.type';
14
+ function optionLetter(index) {
15
+ return String.fromCharCode(65 + index);
16
+ }
17
+ function nextOptionId(options) {
18
+ const used = new Set(options.map((option) => option.id));
19
+ let next = 1;
20
+ while (used.has(`opt-${next}`)) {
21
+ next += 1;
22
+ }
23
+ return `opt-${next}`;
24
+ }
25
+ function shouldStartInTipTapMode(item) {
26
+ if (hasRichTextFormatting(item.question)) {
27
+ return true;
28
+ }
29
+ return item.options.some((option) => hasRichTextFormatting(option.text));
30
+ }
31
+ function emptyOptions(count) {
32
+ return Array.from({ length: count }, (_, index) => ({
33
+ id: `opt-${index + 1}`,
34
+ text: '',
35
+ }));
36
+ }
37
+ function ChooseAnswerTextInput({ value, onChange, isTipTapMode, placeholder, compact = false, className, }) {
38
+ if (isTipTapMode) {
39
+ return (_jsx(RichTextEditor, { value: value, onChange: onChange, variant: "compact", minHeightClassName: compact ? `min-h-[56px] ${className ?? ''}` : `min-h-[72px] ${className ?? ''}` }));
40
+ }
41
+ if (compact) {
42
+ return (_jsx(Input, { value: htmlToPlainEditorText(value), onChange: (event) => onChange(event.target.value), placeholder: placeholder, className: `h-10 border-gray-200 bg-white ${className ?? ''}` }));
43
+ }
44
+ return (_jsx(Textarea, { value: htmlToPlainEditorText(value), onChange: (event) => onChange(event.target.value), placeholder: placeholder, rows: 2, className: `min-h-[56px] border-gray-200 bg-white ${className ?? ''}` }));
45
+ }
46
+ export function ChooseTheCorrectAnswerGroupItemEditor({ item, itemIndex, onChange, }) {
47
+ const [isTipTapMode, setIsTipTapMode] = useState(() => shouldStartInTipTapMode(item));
48
+ const textOptionsCacheRef = useRef(null);
49
+ const imageOptionsCacheRef = useRef(null);
50
+ const imageUrls = toEditableChooseAnswerGroupImageUrls(item.imageUrl);
51
+ const audioUrls = toEditableChooseAnswerGroupImageUrls(item.audioUrl);
52
+ const hasCorrectAnswer = item.options.some((option) => option.id === item.correctAnswer);
53
+ const updateOption = (optionIndex, patch) => {
54
+ onChange({
55
+ options: item.options.map((option, index) => (index === optionIndex ? { ...option, ...patch } : option)),
56
+ });
57
+ };
58
+ const switchOptionType = (nextType) => {
59
+ if (nextType === item.optionType) {
60
+ return;
61
+ }
62
+ if (item.optionType === 'text') {
63
+ textOptionsCacheRef.current = { options: item.options, correctAnswer: item.correctAnswer };
64
+ }
65
+ else {
66
+ imageOptionsCacheRef.current = { options: item.options, correctAnswer: item.correctAnswer };
67
+ }
68
+ const targetCache = nextType === 'text' ? textOptionsCacheRef.current : imageOptionsCacheRef.current;
69
+ if (targetCache) {
70
+ onChange({
71
+ optionType: nextType,
72
+ options: targetCache.options,
73
+ correctAnswer: targetCache.correctAnswer,
74
+ });
75
+ return;
76
+ }
77
+ const nextOptions = emptyOptions(item.options.length);
78
+ onChange({
79
+ optionType: nextType,
80
+ options: nextOptions,
81
+ correctAnswer: nextOptions[0]?.id || 'opt-1',
82
+ });
83
+ };
84
+ return (_jsxs("div", { className: "space-y-2.5", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs(Label, { className: "text-sm font-semibold text-gray-800", children: ["C\u00E2u h\u1ECFi ", _jsx("span", { className: "text-xs font-normal text-gray-500", children: "(tu\u1EF3 ch\u1ECDn)" })] }), _jsxs("button", { type: "button", onClick: () => setIsTipTapMode((enabled) => !enabled), title: isTipTapMode ? 'Chuyển về ô nhập thường' : 'Bật định dạng chữ (in đậm, danh sách, bảng)', className: `inline-flex h-8 items-center gap-1.5 rounded-md border px-2.5 text-xs font-medium transition-colors select-none ${isTipTapMode
85
+ ? 'border-indigo-300 bg-indigo-50 text-indigo-700'
86
+ : 'border-gray-200 bg-white text-gray-400 hover:text-gray-600'}`, children: [_jsx(Pilcrow, { className: "h-3.5 w-3.5" }), "\u0110\u1ECBnh d\u1EA1ng"] })] }), _jsxs("div", { className: "relative", children: [_jsx(ChooseAnswerTextInput, { value: item.question, onChange: (question) => onChange({ question }), isTipTapMode: isTipTapMode, placeholder: "Nh\u1EADp c\u00E2u h\u1ECFi...", className: "pb-11" }), _jsxs("div", { className: "absolute bottom-1.5 right-1.5 z-10 flex items-center gap-1 rounded-md border border-indigo-100 bg-white/95 p-0.5 shadow-sm", children: [_jsx("button", { type: "button", title: "Th\u00EAm h\u00ECnh \u1EA3nh", onClick: () => {
87
+ const current = toEditableChooseAnswerGroupImageUrls(item.imageUrl);
88
+ const hasEmpty = current.some((url) => !url.trim());
89
+ if (current.length === 0 || !hasEmpty) {
90
+ onChange({ imageUrl: [...current, ''] });
91
+ }
92
+ }, className: `inline-flex h-8 w-8 items-center justify-center rounded-md transition-colors ${imageUrls.length > 0
93
+ ? 'bg-indigo-600 text-white shadow-sm'
94
+ : 'bg-indigo-50 text-indigo-600 hover:bg-indigo-100'}`, children: _jsx(ImageIcon, { className: "h-4 w-4" }) }), _jsx("button", { type: "button", title: "Th\u00EAm audio", onClick: () => {
95
+ const current = toEditableChooseAnswerGroupImageUrls(item.audioUrl);
96
+ const hasEmpty = current.some((url) => !url.trim());
97
+ if (current.length === 0 || !hasEmpty) {
98
+ onChange({ audioUrl: [...current, ''] });
99
+ }
100
+ }, className: `inline-flex h-8 w-8 items-center justify-center rounded-md transition-colors ${audioUrls.length > 0
101
+ ? 'bg-violet-600 text-white shadow-sm'
102
+ : 'bg-violet-50 text-violet-600 hover:bg-violet-100'}`, children: _jsx(Volume2, { className: "h-4 w-4" }) })] })] })] }), imageUrls.length > 0 && (_jsxs("div", { className: "space-y-2 rounded-md border border-gray-200 bg-slate-50/80 p-2", children: [imageUrls.map((imageUrl, imageIndex) => (_jsx(ChooseAnswerGroupImageUploadItem, { index: imageIndex, itemIndex: itemIndex, imageUrl: imageUrl, onChange: (url) => {
103
+ const nextUrls = toEditableChooseAnswerGroupImageUrls(item.imageUrl);
104
+ nextUrls[imageIndex] = url;
105
+ onChange({ imageUrl: nextUrls });
106
+ }, onRemove: () => {
107
+ const nextUrls = toEditableChooseAnswerGroupImageUrls(item.imageUrl)
108
+ .filter((_, idx) => idx !== imageIndex);
109
+ onChange({ imageUrl: fromChooseAnswerGroupImageUrls(nextUrls) });
110
+ } }, `${itemIndex}-image-${imageIndex}`))), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => {
111
+ const current = toEditableChooseAnswerGroupImageUrls(item.imageUrl);
112
+ onChange({ imageUrl: current.length ? [...current, ''] : [''] });
113
+ }, className: "h-8 gap-1.5 border-dashed border-indigo-300 text-indigo-600 hover:bg-indigo-50 hover:text-indigo-700", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm \u1EA3nh"] })] })), audioUrls.length > 0 && (_jsxs("div", { className: "space-y-2 rounded-md border border-gray-200 bg-slate-50/80 p-2", children: [audioUrls.map((audioUrl, audioIndex) => (_jsx(ChooseAnswerGroupAudioUploadItem, { index: audioIndex, itemIndex: itemIndex, audioUrl: audioUrl, onChange: (url) => {
114
+ const nextUrls = toEditableChooseAnswerGroupImageUrls(item.audioUrl);
115
+ nextUrls[audioIndex] = url;
116
+ onChange({ audioUrl: nextUrls });
117
+ }, onRemove: () => {
118
+ const nextUrls = toEditableChooseAnswerGroupImageUrls(item.audioUrl)
119
+ .filter((_, idx) => idx !== audioIndex);
120
+ onChange({ audioUrl: fromChooseAnswerGroupImageUrls(nextUrls) });
121
+ } }, `${itemIndex}-audio-${audioIndex}`))), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => {
122
+ const current = toEditableChooseAnswerGroupImageUrls(item.audioUrl);
123
+ onChange({ audioUrl: current.length ? [...current, ''] : [''] });
124
+ }, className: "h-8 gap-1.5 border-dashed border-violet-300 text-violet-600 hover:bg-violet-50 hover:text-violet-700", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm audio"] })] })), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Label, { className: "text-sm font-semibold text-gray-800", children: ["\u0110\u00E1p \u00E1n ", _jsxs("span", { className: "text-xs font-normal text-gray-500", children: ["(", item.options.length, " \u0111\u00E1p \u00E1n)"] })] }), _jsxs("div", { className: "flex h-8 items-center gap-1.5 rounded-md border border-gray-200 bg-gray-50 px-2.5", children: [_jsx(Type, { className: `h-3.5 w-3.5 ${item.optionType === 'text' ? 'text-indigo-600' : 'text-gray-400'}` }), _jsx("span", { className: `text-xs font-medium ${item.optionType === 'text' ? 'text-indigo-600' : 'text-gray-400'}`, children: "Text" }), _jsx(Switch, { checked: item.optionType === 'image', className: item.optionType === 'image' ? '!bg-indigo-600' : undefined, onCheckedChange: (checked) => switchOptionType(checked ? 'image' : 'text') }), _jsx(ImageIcon, { className: `h-3.5 w-3.5 ${item.optionType === 'image' ? 'text-indigo-600' : 'text-gray-400'}` }), _jsx("span", { className: `text-xs font-medium ${item.optionType === 'image' ? 'text-indigo-600' : 'text-gray-400'}`, children: "\u1EA2nh" })] })] }), item.options.map((option, optionIndex) => {
125
+ const isCorrect = option.id === item.correctAnswer;
126
+ const letter = optionLetter(optionIndex);
127
+ return (_jsx("div", { className: `group relative rounded-md px-3 py-2.5 transition-all duration-200 ${isCorrect
128
+ ? 'border-2 border-green-400 bg-gradient-to-r from-green-50 to-emerald-50 shadow-sm shadow-green-100'
129
+ : 'border border-gray-200 bg-white hover:border-gray-300 hover:shadow-sm'}`, children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: `flex h-8 w-8 flex-shrink-0 cursor-pointer items-center justify-center rounded-full text-xs font-bold leading-none transition-all ${isCorrect
130
+ ? 'bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-sm'
131
+ : 'bg-gray-100 text-gray-800 hover:bg-indigo-100 hover:text-indigo-600'}`, onClick: () => onChange({ correctAnswer: option.id }), children: letter }), _jsx("div", { className: "flex-1 space-y-2", children: item.optionType === 'image' ? (_jsx(ChooseAnswerGroupOptionImageField, { itemIndex: itemIndex, optionIndex: optionIndex, imageUrl: getChooseAnswerGroupOptionImageSrc(option), onChange: (url) => updateOption(optionIndex, { imageUrl: url, text: '' }) })) : (_jsx(ChooseAnswerTextInput, { value: option.text, onChange: (text) => updateOption(optionIndex, { text }), isTipTapMode: isTipTapMode, compact: true, placeholder: `Nhập đáp án ${letter}...` })) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => onChange({ correctAnswer: option.id }), className: `h-7 w-7 ${isCorrect ? 'text-green-600 hover:text-green-700' : 'text-gray-400 hover:text-green-600'}`, title: "Ch\u1ECDn l\u00E0m \u0111\u00E1p \u00E1n \u0111\u00FAng", children: _jsx(CheckCircle2, { className: "h-3.5 w-3.5" }) }), item.options.length > 2 && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => {
132
+ const nextOptions = item.options.filter((_, index) => index !== optionIndex);
133
+ onChange({
134
+ options: nextOptions,
135
+ correctAnswer: isCorrect
136
+ ? (nextOptions[0]?.id || 'opt-1')
137
+ : item.correctAnswer,
138
+ });
139
+ }, className: "h-7 w-7 text-red-500 hover:bg-red-50 hover:text-red-600", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] })] }) }, option.id));
140
+ }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => {
141
+ onChange({
142
+ options: [...item.options, { id: nextOptionId(item.options), text: '' }],
143
+ });
144
+ }, className: "h-8 w-full gap-1.5 border-dashed border-green-200 px-2.5 text-xs text-green-600 hover:bg-green-50 hover:text-green-700", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm \u0111\u00E1p \u00E1n"] }), !hasCorrectAnswer && (_jsx("div", { className: "flex items-center gap-2 rounded-md border border-amber-200 bg-amber-50 p-2 text-xs text-amber-800", children: _jsx("span", { children: "Ch\u01B0a ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng. Click ch\u1EEF c\u00E1i ho\u1EB7c \u2713 \u0111\u1EC3 ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng." }) }))] })] }));
145
+ }
@@ -18,13 +18,6 @@ function asOptionId(value) {
18
18
  }
19
19
  return '';
20
20
  }
21
- function asOptionalUrl(value) {
22
- if (typeof value !== 'string') {
23
- return undefined;
24
- }
25
- const trimmed = value.trim();
26
- return trimmed !== '' ? trimmed : undefined;
27
- }
28
21
  export function createEmptyChooseAnswerGroupItem(questionNumber) {
29
22
  return {
30
23
  question: '',
@@ -32,7 +25,6 @@ export function createEmptyChooseAnswerGroupItem(questionNumber) {
32
25
  options: [
33
26
  { id: 'opt-1', text: '' },
34
27
  { id: 'opt-2', text: '' },
35
- { id: 'opt-3', text: '' },
36
28
  ],
37
29
  correctAnswer: 'opt-1',
38
30
  isExample: false,
@@ -64,11 +56,10 @@ export function mapChooseAnswerGroupItem(item, index, fallbackAnswer) {
64
56
  : [
65
57
  { id: 'opt-1', text: '' },
66
58
  { id: 'opt-2', text: '' },
67
- { id: 'opt-3', text: '' },
68
59
  ];
69
60
  const isExample = record.isExample === true;
70
61
  const imageUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(content.imageUrl));
71
- const audioUrl = asOptionalUrl(content.audioUrl);
62
+ const audioUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(content.audioUrl));
72
63
  return {
73
64
  question: asString(content.question) || asString(record.question),
74
65
  optionType: asString(content.optionType) === 'image' ? 'image' : 'text',
@@ -78,7 +69,7 @@ export function mapChooseAnswerGroupItem(item, index, fallbackAnswer) {
78
69
  points: isExample ? 0 : typeof record.points === 'number' ? record.points : 1,
79
70
  questionNumber: typeof record.questionNumber === 'number' ? record.questionNumber : index + 1,
80
71
  ...(imageUrl !== undefined ? { imageUrl } : {}),
81
- ...(audioUrl ? { audioUrl } : {}),
72
+ ...(audioUrl !== undefined ? { audioUrl } : {}),
82
73
  };
83
74
  }
84
75
  export function mapQuestionToChooseAnswerGroupData(question) {
@@ -92,12 +83,12 @@ export function mapQuestionToChooseAnswerGroupData(question) {
92
83
  : [];
93
84
  if (Array.isArray(content.items)) {
94
85
  const items = content.items.map((item, index) => mapChooseAnswerGroupItem(item, index, fallbackAnswers[index]));
95
- const audioUrl = asOptionalUrl(asRecord(content.meta).audioUrl);
86
+ const audioUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(asRecord(content.meta).audioUrl));
96
87
  const imageUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(asRecord(content.meta).imageUrl));
97
88
  return {
98
89
  instruction: asString(asRecord(content.meta).instruction) || 'Choose the correct answer.',
99
90
  passage: asString(asRecord(content.meta).passage),
100
- ...(audioUrl ? { audioUrl } : {}),
91
+ ...(audioUrl !== undefined ? { audioUrl } : {}),
101
92
  ...(imageUrl !== undefined ? { imageUrl } : {}),
102
93
  items,
103
94
  explanation,
@@ -105,12 +96,12 @@ export function mapQuestionToChooseAnswerGroupData(question) {
105
96
  };
106
97
  }
107
98
  if (Array.isArray(answer.items)) {
108
- const audioUrl = asOptionalUrl(answer.audioUrl);
99
+ const audioUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(answer.audioUrl));
109
100
  const imageUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(answer.imageUrl));
110
101
  return {
111
102
  instruction: asString(answer.instruction, 'Choose the correct answer.'),
112
103
  passage: asString(answer.passage),
113
- ...(audioUrl ? { audioUrl } : {}),
104
+ ...(audioUrl !== undefined ? { audioUrl } : {}),
114
105
  ...(imageUrl !== undefined ? { imageUrl } : {}),
115
106
  items: answer.items.map((item, index) => mapChooseAnswerGroupItem(item, index, fallbackAnswers[index])),
116
107
  explanation,
@@ -26,7 +26,7 @@ export const transformChooseTheCorrectAnswerGroup = (question) => {
26
26
  }));
27
27
  const correctAnswer = item.correctAnswer || options[0]?.id || 'opt-1';
28
28
  const imageUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(item.imageUrl));
29
- const audioUrl = typeof item.audioUrl === 'string' ? item.audioUrl.trim() : '';
29
+ const audioUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(item.audioUrl));
30
30
  return {
31
31
  questionType: CHILD_QUESTION_TYPE,
32
32
  ...(item.isExample ? { isExample: true } : {}),
@@ -35,7 +35,7 @@ export const transformChooseTheCorrectAnswerGroup = (question) => {
35
35
  options,
36
36
  optionType: item.optionType || 'text',
37
37
  ...(imageUrl !== undefined ? { imageUrl } : {}),
38
- ...(audioUrl ? { audioUrl } : {}),
38
+ ...(audioUrl !== undefined ? { audioUrl } : {}),
39
39
  },
40
40
  correctAnswer: { answer: correctAnswer },
41
41
  points: item.isExample ? 0 : (typeof item.points === 'number' ? item.points : 1),
@@ -48,8 +48,9 @@ export const transformChooseTheCorrectAnswerGroup = (question) => {
48
48
  if (answerData?.passage && answerData.passage.trim()) {
49
49
  meta.passage = answerData.passage.trim();
50
50
  }
51
- if (answerData?.audioUrl && answerData.audioUrl.trim()) {
52
- meta.audioUrl = answerData.audioUrl.trim();
51
+ const groupAudioUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(answerData?.audioUrl));
52
+ if (groupAudioUrl !== undefined) {
53
+ meta.audioUrl = groupAudioUrl;
53
54
  }
54
55
  const groupImageUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(answerData?.imageUrl));
55
56
  if (groupImageUrl !== undefined) {
@@ -23,12 +23,12 @@ export function ChooseThenAnswerGroupClient({ questionData, isReviewMode = false
23
23
  text: next.text ?? current.text,
24
24
  });
25
25
  };
26
- return (_jsxs("div", { className: "space-y-2 rounded-xl border border-gray-200 bg-white p-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "flex h-6 w-6 items-center justify-center rounded-full bg-slate-800 text-xs font-bold text-white", children: 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-[10px] font-semibold uppercase text-amber-800", children: [_jsx(Star, { className: "h-3 w-3" }), "Example"] }))] }), _jsxs("p", { className: "flex flex-wrap items-center gap-1.5 text-[15px] leading-relaxed text-slate-800", children: [_jsx("span", { children: parts[0] || '' }), _jsx("span", { className: "inline-flex items-center gap-1", children: item.options.map((option, optionIndex) => {
27
- const selected = current.optionId === option.id;
28
- const showReview = isReviewMode && !item.isExample && Boolean(expectedId);
29
- const isCorrect = showReview && option.id === expectedId;
30
- const isWrong = showReview && selected && option.id !== expectedId;
31
- return (_jsxs("span", { className: "inline-flex items-center gap-1", children: [optionIndex > 0 && _jsx("span", { className: "text-slate-400", children: "|" }), _jsx("button", { type: "button", disabled: !canEdit || item.isExample, onClick: () => setAnswer({ optionId: option.id }), className: cn('rounded-md border px-2.5 py-1 text-sm transition-colors', selected && !isCorrect && !isWrong && 'border-black bg-black text-white', !selected && !isCorrect && !isWrong && 'border-gray-300 bg-white text-gray-900', isCorrect && 'border-emerald-500 bg-emerald-50 text-emerald-900', isWrong && 'border-red-500 bg-red-50 text-red-900', (!canEdit || item.isExample) && 'cursor-default'), children: option.text || option.id })] }, option.id));
32
- }) }), _jsx("span", { children: parts.slice(1).join('{0}') })] }), _jsx("textarea", { value: current.text, disabled: !canEdit || item.isExample, onChange: (event) => setAnswer({ text: event.target.value }), placeholder: "Answer for you", rows: 3, className: "w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm text-slate-800 outline-none focus:border-slate-400 disabled:bg-slate-50" })] }, `${item.questionNumber}-${itemIndex}`));
26
+ return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-gray-100 px-3 py-2", children: [_jsx("span", { className: "flex h-6 w-6 items-center justify-center rounded-full bg-slate-800 text-xs font-bold text-white", children: 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-[10px] font-semibold uppercase text-amber-800", children: [_jsx(Star, { className: "h-3 w-3" }), "Example"] }))] }), _jsxs("div", { className: "space-y-3 p-3", children: [_jsxs("p", { className: "flex flex-wrap items-center gap-1.5 text-[15px] leading-relaxed text-slate-800", children: [_jsx("span", { children: parts[0] || '' }), _jsx("span", { className: "inline-flex items-center gap-1", children: item.options.map((option, optionIndex) => {
27
+ const selected = current.optionId === option.id;
28
+ const showReview = isReviewMode && !item.isExample && Boolean(expectedId);
29
+ const isCorrect = showReview && option.id === expectedId;
30
+ const isWrong = showReview && selected && option.id !== expectedId;
31
+ return (_jsxs("span", { className: "inline-flex items-center gap-1", children: [optionIndex > 0 && _jsx("span", { className: "text-slate-400", children: "|" }), _jsx("button", { type: "button", disabled: !canEdit || item.isExample, onClick: () => setAnswer({ optionId: option.id }), className: cn('rounded-md border px-2.5 py-1 text-sm transition-colors', selected && !isCorrect && !isWrong && 'border-black bg-black text-white', !selected && !isCorrect && !isWrong && 'border-gray-300 bg-white text-gray-900', isCorrect && 'border-emerald-500 bg-emerald-50 text-emerald-900', isWrong && 'border-red-500 bg-red-50 text-red-900', (!canEdit || item.isExample) && 'cursor-default'), children: option.text || option.id })] }, option.id));
32
+ }) }), _jsx("span", { children: parts.slice(1).join('{0}') })] }), _jsx("textarea", { value: current.text, disabled: !canEdit || item.isExample, onChange: (event) => setAnswer({ text: event.target.value }), placeholder: "Answer for you", rows: 3, className: "w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm text-slate-800 outline-none focus:border-slate-400 disabled:bg-slate-50" })] })] }, `${item.questionNumber}-${itemIndex}`));
33
33
  }), 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] }))] }));
34
34
  }
@@ -1,15 +1,15 @@
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 { useEffect, useRef, useState } from 'react';
4
4
  import { Button } from '../../../../components/ui/button';
5
5
  import { Label } from '../../../../components/ui/label';
6
6
  import { Input } from '../../../../components/ui/input';
7
7
  import { PointsInput } from '../../../../components/ui/points-input';
8
- import { Textarea } from '../../../../components/ui/textarea';
9
8
  import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
10
9
  import { Switch } from '../../../../components/ui/switch';
11
10
  import { useDebouncedCallback } from '../../../../shared/lib/hooks';
12
- import { Eye, Pencil, Plus, Star, Trash2 } from 'lucide-react';
11
+ import { ListChecks, Plus, Star, Trash2 } from 'lucide-react';
12
+ import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
13
13
  import { ChooseThenAnswerGroupClient } from './ChooseThenAnswerGroupClient';
14
14
  import { createEmptyItem, createEmptyOption, sumGradablePoints, } from './map-choose-then-answer-group-data';
15
15
  import { CHOOSE_THEN_ANSWER_GROUP_DEFAULT_INSTRUCTION } from '../../_shared/types/choose-then-answer-group.type';
@@ -168,7 +168,10 @@ export function ChooseThenAnswerGroupCreator({ initialData, onChange, externalEr
168
168
  })));
169
169
  };
170
170
  const displayErrors = [...errors, ...(externalErrors || [])];
171
- return (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "flex justify-end", children: _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setIsClientMode((v) => !v), children: [isClientMode ? _jsx(Pencil, { className: "mr-2 h-4 w-4" }) : _jsx(Eye, { className: "mr-2 h-4 w-4" }), isClientMode ? 'Sửa' : 'Xem trước'] }) }), isClientMode ? (_jsx(ChooseThenAnswerGroupClient, { questionData: buildPayload(), isReviewMode: true })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsxs("div", { children: [_jsx(Label, { htmlFor: "cta-title", children: "Ti\u00EAu \u0111\u1EC1 (optional)" }), _jsx(Input, { id: "cta-title", value: title, onChange: (event) => setTitle(event.target.value), placeholder: "GRAMMAR" })] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: "cta-instruction", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx(Input, { id: "cta-instruction", value: instruction, onChange: (event) => setInstruction(event.target.value), placeholder: CHOOSE_THEN_ANSWER_GROUP_DEFAULT_INSTRUCTION })] })] }), items.map((item, index) => (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between space-y-0", children: [_jsxs(CardTitle, { className: "text-base", children: ["C\u00E2u ", index + 1] }), items.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeItem(index), children: _jsx(Trash2, { className: "h-4 w-4 text-red-500" }) }))] }), _jsxs(CardContent, { className: "space-y-3", children: [_jsx(Input, { value: item.question, onChange: (event) => updateItem(index, { question: event.target.value }), placeholder: "Did you eat {0} for lunch today?" }), _jsxs("p", { className: "text-xs text-slate-500", children: ["D\u00F9ng ", '{0}', " cho ch\u1ED7 hi\u1EC7n chip ch\u1ECDn."] }), item.options.map((option, optionIndex) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { value: option.text, onChange: (event) => updateOption(index, optionIndex, event.target.value), placeholder: `Lựa chọn ${optionIndex + 1}` }), item.options.length > 2 && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeOption(index, optionIndex), children: _jsx(Trash2, { className: "h-4 w-4 text-red-500" }) }))] }, option.id))), _jsxs(Button, { type: "button", size: "sm", variant: "outline", onClick: () => addOption(index), children: [_jsx(Plus, { className: "mr-1 h-4 w-4" }), " Th\u00EAm l\u1EF1a ch\u1ECDn"] }), _jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsx("select", { className: "h-10 rounded-md border border-input bg-background px-3 text-sm", value: item.optionId, onChange: (event) => updateItem(index, { optionId: event.target.value }), children: item.options.map((option) => (_jsx("option", { value: option.id, children: option.text || option.id }, option.id))) }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Switch, { id: `cta-example-${index}`, checked: item.isExample === true, onCheckedChange: (checked) => updateItem(index, { isExample: checked }) }), _jsxs(Label, { htmlFor: `cta-example-${index}`, className: "flex cursor-pointer items-center gap-1 text-xs", children: [_jsx(Star, { className: "h-3 w-3 text-amber-500" }), "Example"] })] }), !item.isExample && (_jsx(PointsInput, { value: item.points, allowZero: true, onChange: (event) => updateItem(index, {
171
+ if (isClientMode) {
172
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(ChooseThenAnswerGroupClient, { questionData: buildPayload(), isReviewMode: true })] }));
173
+ }
174
+ return (_jsxs("div", { className: "space-y-3", children: [displayErrors.length > 0 && (_jsx("ul", { className: "list-disc space-y-1 rounded-md border border-red-200 bg-red-50 p-2 pl-6 text-xs text-red-700", children: displayErrors.map((error) => (_jsx("li", { children: error }, error))) })), _jsx(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(ListChecks, { className: "h-3.5 w-3.5" }), title: "Ch\u1ECDn r\u1ED3i tr\u1EA3 l\u1EDDi (Nh\u00F3m)", preview: { onClick: () => setIsClientMode(true) } }), children: _jsxs("div", { className: "grid gap-2.5 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "cta-title", className: "text-sm font-semibold text-gray-800", children: "Ti\u00EAu \u0111\u1EC1 (optional)" }), _jsx(Input, { id: "cta-title", value: title, onChange: (event) => setTitle(event.target.value), placeholder: "GRAMMAR", className: "h-10 border-gray-200 bg-white" })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "cta-instruction", className: "text-sm font-semibold text-gray-800", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx(Input, { id: "cta-instruction", value: instruction, onChange: (event) => setInstruction(event.target.value), placeholder: CHOOSE_THEN_ANSWER_GROUP_DEFAULT_INSTRUCTION, className: "h-10 border-gray-200 bg-white" })] })] }) }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-sm", children: [_jsx(CardHeader, { className: "px-3 py-2", children: _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs(CardTitle, { className: "text-sm font-semibold text-gray-800", children: ["Danh s\u00E1ch c\u00E2u (", items.length, ")"] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addItem, className: "h-8 gap-1.5 px-2.5 text-xs", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), " Th\u00EAm c\u00E2u"] })] }) }), _jsxs(CardContent, { className: "space-y-2.5 px-3 pb-3", children: [items.map((item, index) => (_jsxs("div", { className: "space-y-2.5 rounded-md border border-gray-200 bg-white px-3 py-2.5", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", index + 1] }), items.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeItem(index), className: "h-7 w-7 text-red-500 hover:bg-red-50", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] }), _jsx(Input, { value: item.question, onChange: (event) => updateItem(index, { question: event.target.value }), placeholder: "Did you eat {0} for lunch today?", className: "h-10 border-gray-200 bg-white" }), _jsxs("p", { className: "text-xs text-slate-500", children: ["D\u00F9ng ", '{0}', " cho ch\u1ED7 hi\u1EC7n chip ch\u1ECDn."] }), item.options.map((option, optionIndex) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { value: option.text, onChange: (event) => updateOption(index, optionIndex, event.target.value), placeholder: `Lựa chọn ${optionIndex + 1}`, className: "h-10 border-gray-200 bg-white" }), item.options.length > 2 && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeOption(index, optionIndex), className: "h-7 w-7 text-red-500 hover:bg-red-50", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] }, option.id))), _jsxs(Button, { type: "button", size: "sm", variant: "outline", onClick: () => addOption(index), className: "h-8 gap-1.5 px-2.5 text-xs", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), " Th\u00EAm l\u1EF1a ch\u1ECDn"] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("select", { className: "h-10 rounded-md border border-input bg-background px-3 text-sm", value: item.optionId, onChange: (event) => updateItem(index, { optionId: event.target.value }), children: item.options.map((option) => (_jsx("option", { value: option.id, children: option.text || option.id }, option.id))) }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Switch, { id: `cta-example-${index}`, checked: item.isExample === true, onCheckedChange: (checked) => updateItem(index, { isExample: checked }) }), _jsxs(Label, { htmlFor: `cta-example-${index}`, className: "flex cursor-pointer items-center gap-1 text-xs", children: [_jsx(Star, { className: "h-3 w-3 text-amber-500" }), "Example"] })] }), !item.isExample && (_jsx(PointsInput, { value: item.points, allowZero: true, onChange: (event) => updateItem(index, {
172
175
  points: Number.parseInt(event.target.value, 10) || 1,
173
- }) }))] })] })] }, `${item.questionNumber}-${index}`))), _jsxs(Button, { type: "button", variant: "outline", onClick: addItem, children: [_jsx(Plus, { className: "mr-1 h-4 w-4" }), " Th\u00EAm c\u00E2u"] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: "cta-explanation", children: "Gi\u1EA3i th\u00EDch" }), _jsx(Textarea, { id: "cta-explanation", value: explanation, onChange: (event) => setExplanation(event.target.value) })] })] })), displayErrors.length > 0 && (_jsx("ul", { className: "list-disc space-y-1 rounded-md border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700", children: displayErrors.map((error) => (_jsx("li", { children: error }, error))) }))] }));
176
+ }) }))] })] }, `${item.questionNumber}-${index}`))), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(initialData?.explanation) })] })] })] }));
174
177
  }