@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
@@ -1,9 +1,85 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { CircleHelp, Lightbulb } from 'lucide-react';
4
- export function SpontaneousQaGroupClient({ questionData, }) {
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { CircleHelp, Lightbulb, Mic, Square, Volume2 } from 'lucide-react';
5
+ import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
6
+ function QuestionAudio({ url }) {
7
+ const { previewUrl, isLoading } = usePresignedFileUrl(url);
8
+ const src = previewUrl ||
9
+ (url.startsWith('http') || url.startsWith('blob:') ? url : '');
10
+ if (!src && !isLoading) {
11
+ return null;
12
+ }
13
+ return (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-blue-100 bg-blue-50/60 px-3 py-2", children: [_jsx(Volume2, { className: "h-4 w-4 shrink-0 text-blue-500" }), isLoading ? (_jsx("div", { className: "h-8 flex-1 animate-pulse rounded-md bg-blue-100" })) : (_jsx("audio", { controls: true, src: src, className: "h-8 w-full", preload: "metadata", children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." }))] }));
14
+ }
15
+ function SpeakingReplyMic() {
16
+ const [status, setStatus] = useState('idle');
17
+ const [error, setError] = useState('');
18
+ const [blobUrl, setBlobUrl] = useState('');
19
+ const recorderRef = useRef(null);
20
+ const chunksRef = useRef([]);
21
+ const streamRef = useRef(null);
22
+ useEffect(() => {
23
+ return () => {
24
+ recorderRef.current?.stop();
25
+ streamRef.current?.getTracks().forEach((track) => track.stop());
26
+ if (blobUrl) {
27
+ URL.revokeObjectURL(blobUrl);
28
+ }
29
+ };
30
+ }, [blobUrl]);
31
+ const stopStream = () => {
32
+ streamRef.current?.getTracks().forEach((track) => track.stop());
33
+ streamRef.current = null;
34
+ };
35
+ const startRecording = async () => {
36
+ setError('');
37
+ try {
38
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
39
+ streamRef.current = stream;
40
+ chunksRef.current = [];
41
+ const recorder = new MediaRecorder(stream);
42
+ recorderRef.current = recorder;
43
+ recorder.ondataavailable = (event) => {
44
+ if (event.data.size > 0) {
45
+ chunksRef.current.push(event.data);
46
+ }
47
+ };
48
+ recorder.onstop = () => {
49
+ const blob = new Blob(chunksRef.current, { type: recorder.mimeType || 'audio/webm' });
50
+ setBlobUrl((current) => {
51
+ if (current) {
52
+ URL.revokeObjectURL(current);
53
+ }
54
+ return URL.createObjectURL(blob);
55
+ });
56
+ setStatus('recorded');
57
+ stopStream();
58
+ };
59
+ recorder.start();
60
+ setStatus('recording');
61
+ }
62
+ catch {
63
+ setError('Không thể mở microphone. Hãy cho phép quyền ghi âm.');
64
+ stopStream();
65
+ }
66
+ };
67
+ const stopRecording = () => {
68
+ if (recorderRef.current && recorderRef.current.state !== 'inactive') {
69
+ recorderRef.current.stop();
70
+ }
71
+ };
72
+ return (_jsxs("div", { className: "flex flex-col items-center gap-2 rounded-lg border border-dashed border-indigo-200 bg-indigo-50/60 px-4 py-3", children: [_jsx("button", { type: "button", onClick: status === 'recording' ? stopRecording : startRecording, title: status === 'recording' ? 'Dừng ghi âm' : 'Nhấn micro để trả lời', className: `inline-flex h-12 w-12 items-center justify-center rounded-full text-white shadow-sm transition-colors ${status === 'recording'
73
+ ? 'animate-pulse bg-red-500 hover:bg-red-600'
74
+ : 'bg-indigo-600 hover:bg-indigo-700'}`, children: status === 'recording' ? _jsx(Square, { className: "h-4 w-4 fill-current" }) : _jsx(Mic, { className: "h-5 w-5" }) }), _jsx("p", { className: "text-center text-xs text-indigo-700", children: status === 'recording'
75
+ ? 'Đang ghi âm... Nhấn để dừng'
76
+ : status === 'recorded'
77
+ ? 'Đã ghi. Nhấn micro để ghi lại'
78
+ : 'Nhấn micro để trả lời bằng giọng nói' }), error && _jsx("p", { className: "text-center text-xs text-red-600", children: error }), status === 'recorded' && blobUrl && (_jsx("audio", { controls: true, src: blobUrl, className: "h-8 w-full max-w-sm", preload: "metadata", children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." }))] }));
79
+ }
80
+ export function SpontaneousQaGroupClient({ questionData, isReviewMode = false, }) {
5
81
  const parts = questionData.parts || [];
6
- 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 })), parts.map((part, partIndex) => (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "border-b border-gray-100 bg-gradient-to-r from-slate-50 to-white px-4 py-3", children: [_jsx("p", { className: "text-sm font-semibold text-slate-800", children: part.partTitle || `PART ${part.partNumber || partIndex + 1}` }), part.topic && (_jsx("p", { className: "mt-1 text-sm text-slate-600", children: part.topic }))] }), _jsx("div", { className: "space-y-3 p-4", children: part.questions.map((question, questionIndex) => (_jsxs("div", { className: "rounded-lg border border-gray-200 bg-white p-3", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-blue-100 text-blue-700", children: _jsx(CircleHelp, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", question.questionNumber || questionIndex + 1] })] }), _jsxs("span", { className: "text-xs text-gray-500", children: [question.points || 0, " \u0111i\u1EC3m"] })] }), _jsx("p", { className: "text-sm font-semibold text-gray-800", children: question.questionText || 'Câu hỏi chưa được nhập' }), question.expectedAnswers.filter((item) => item.trim() !== '').length > 0 && (_jsxs("div", { className: "mt-2 rounded-lg border border-amber-100 bg-amber-50 px-3 py-2", children: [_jsxs("div", { className: "mb-1 flex items-center gap-1 text-xs font-semibold text-amber-700", children: [_jsx(Lightbulb, { className: "h-3 w-3" }), "\u0110\u00E1p \u00E1n m\u1EABu"] }), _jsx("ul", { className: "list-disc space-y-1 pl-4 text-xs text-amber-800", children: question.expectedAnswers
82
+ 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 })), parts.map((part, partIndex) => (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "border-b border-gray-100 bg-gradient-to-r from-slate-50 to-white px-3 py-2", children: [_jsx("p", { className: "text-sm font-semibold text-slate-800", children: part.partTitle || `PART ${part.partNumber || partIndex + 1}` }), part.topic && (_jsx("p", { className: "mt-1 text-sm text-slate-600", children: part.topic }))] }), _jsx("div", { className: "space-y-3 p-3", children: part.questions.map((question, questionIndex) => (_jsxs("div", { className: "rounded-lg border border-gray-200 bg-white p-3", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-blue-100 text-blue-700", children: _jsx(CircleHelp, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", question.questionNumber || questionIndex + 1] })] }), _jsxs("span", { className: "text-xs text-gray-500", children: [question.points || 0, " \u0111i\u1EC3m"] })] }), _jsx("p", { className: "text-sm font-semibold text-gray-800", children: question.questionText || 'Câu hỏi chưa được nhập' }), question.audioUrl ? _jsx("div", { className: "mt-2", children: _jsx(QuestionAudio, { url: question.audioUrl }) }) : null, _jsx("div", { className: "mt-3", children: _jsx(SpeakingReplyMic, {}) }), isReviewMode && question.expectedAnswers.filter((item) => item.trim() !== '').length > 0 && (_jsxs("div", { className: "mt-2 rounded-lg border border-amber-100 bg-amber-50 px-3 py-2", children: [_jsxs("div", { className: "mb-1 flex items-center gap-1 text-xs font-semibold text-amber-700", children: [_jsx(Lightbulb, { className: "h-3 w-3" }), "\u0110\u00E1p \u00E1n m\u1EABu"] }), _jsx("ul", { className: "list-disc space-y-1 pl-4 text-xs text-amber-800", children: question.expectedAnswers
7
83
  .filter((item) => item.trim() !== '')
8
84
  .map((item) => (_jsx("li", { children: item }, item))) })] }))] }, `${part.partNumber}-${question.questionNumber}-${questionIndex}`))) })] }, `part-${part.partNumber}-${partIndex}`)))] }));
9
85
  }
@@ -6,11 +6,11 @@ import { Label } from '../../../../components/ui/label';
6
6
  import { Input } from '../../../../components/ui/input';
7
7
  import { Textarea } from '../../../../components/ui/textarea';
8
8
  import { PointsInput } from '../../../../components/ui/points-input';
9
- import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
9
+ import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
10
10
  import { FileUpload } from '../../../../components/ui/file-upload';
11
11
  import { useDebouncedCallback } from '../../../../shared/lib/hooks';
12
12
  import { toast } from 'sonner';
13
- import { Eye, Mic, Pencil, Plus, Trash2, Type, Volume2 } from 'lucide-react';
13
+ import { Layers, Mic, Plus, Trash2, Type, Volume2 } from 'lucide-react';
14
14
  import { SpontaneousQaGroupClient } from './SpontaneousQaGroupClient';
15
15
  import { createEmptyPart, createEmptyQuestion, normalizeParts, sumPartPoints, } from './creator-helpers';
16
16
  export function SpontaneousQaGroupCreator({ initialData, onChange, externalErrors, onUnsavedChangesChange, validationRef, }) {
@@ -165,15 +165,17 @@ export function SpontaneousQaGroupCreator({ initialData, onChange, externalError
165
165
  toast.error('TTS chưa được tích hợp trong component SDK');
166
166
  };
167
167
  if (isClientMode) {
168
- 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(SpontaneousQaGroupClient, { questionData: buildPayload(), isReviewMode: true })] }));
168
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(SpontaneousQaGroupClient, { questionData: buildPayload(), isReviewMode: true })] }));
169
169
  }
170
170
  const allErrors = [...errors, ...(externalErrors || [])];
171
- 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 h\u1ECFi \u0111\u00E1p t\u1EF1 ph\u00E1t" }) }), _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: "In pairs, ask and answer the questions." })] }), _jsxs("div", { className: "rounded-xl border border-gray-200 bg-gray-50 p-4", children: [_jsx(PointsInput, { value: sumPartPoints(parts), 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 \u0111i\u1EC3m t\u1EEBng c\u00E2u trong t\u1EA5t c\u1EA3 part." })] })] })] }), parts.map((part, partIndex) => (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between space-y-0", children: [_jsxs(CardTitle, { className: "text-base", children: ["Part ", partIndex + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removePart(partIndex), disabled: parts.length <= 1, children: _jsx(Trash2, { className: "h-4 w-4" }) })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Ti\u00EAu \u0111\u1EC1 part" }), _jsx(Input, { value: part.partTitle, onChange: (event) => updatePart(partIndex, { partTitle: event.target.value }), placeholder: "PART 1" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Ch\u1EE7 \u0111\u1EC1" }), _jsx(Input, { value: part.topic, onChange: (event) => updatePart(partIndex, { topic: event.target.value }), placeholder: "Talk about school." })] })] }), part.questions.map((question, questionIndex) => (_jsxs("div", { className: "space-y-3 rounded-xl border border-slate-200 p-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("p", { className: "text-sm font-semibold text-slate-700", children: ["C\u00E2u ", questionIndex + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeQuestion(partIndex, questionIndex), disabled: part.questions.length <= 1, children: _jsx(Trash2, { className: "h-4 w-4" }) })] }), _jsxs("div", { className: "flex w-fit rounded-lg border border-slate-200 bg-slate-100 p-1", children: [_jsxs("button", { type: "button", onClick: () => updateQuestion(partIndex, questionIndex, { inputType: 'AUDIO' }), className: `flex items-center gap-2 rounded-md px-3 py-1.5 text-xs font-medium ${question.inputType === 'AUDIO' ? 'bg-white text-blue-700 shadow-sm' : 'text-slate-500'}`, children: [_jsx(Mic, { className: "h-3.5 w-3.5" }), " Audio"] }), _jsxs("button", { type: "button", onClick: () => updateQuestion(partIndex, questionIndex, { inputType: 'TEXT' }), className: `flex items-center gap-2 rounded-md px-3 py-1.5 text-xs font-medium ${question.inputType === 'TEXT' ? 'bg-white text-emerald-700 shadow-sm' : 'text-slate-500'}`, children: [_jsx(Type, { className: "h-3.5 w-3.5" }), " Text (TTS)"] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "C\u00E2u h\u1ECFi" }), _jsx(Textarea, { value: question.questionText, onChange: (event) => updateQuestion(partIndex, questionIndex, {
172
- questionText: event.target.value,
173
- }), rows: 2, placeholder: "Do you like your timetable at school? Why?/Why not?" })] }), question.inputType === 'AUDIO' ? (_jsx(FileUpload, { id: `audio-${partIndex}-${questionIndex}`, label: "File audio c\u00E2u h\u1ECFi", accept: "audio/*", value: question.audioUrl, onChange: (url) => updateQuestion(partIndex, questionIndex, { audioUrl: url }), maxSize: 20, placeholder: "T\u1EA3i l\u00EAn file audio (MP3, WAV...)", autoUpload: true, prefix: "speaking/spontaneous-qa-group" })) : (_jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: !question.questionText.trim(), onClick: handleGenerateAudio, children: [_jsx(Volume2, { className: "mr-2 h-3.5 w-3.5" }), "T\u1EA1o Audio"] })), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "\u0110\u00E1p \u00E1n m\u1EABu" }), question.expectedAnswers.map((answer, answerIndex) => (_jsx(Input, { value: answer, onChange: (event) => updateExpectedAnswer(partIndex, questionIndex, answerIndex, event.target.value), placeholder: "\u0110\u00E1p \u00E1n m\u1EABu cho AI ch\u1EA5m" }, `ans-${partIndex}-${questionIndex}-${answerIndex}`))), _jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => addExpectedAnswer(partIndex, questionIndex), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Th\u00EAm \u0111\u00E1p \u00E1n m\u1EABu"] })] }), _jsx(PointsInput, { value: question.points, allowZero: true, onChange: (event) => {
174
- const parsed = Number.parseInt(event.target.value.replace(/[^0-9]/g, ''), 10);
175
- updateQuestion(partIndex, questionIndex, {
176
- points: Number.isNaN(parsed) ? 0 : Math.max(0, parsed),
177
- });
178
- } })] }, `q-${partIndex}-${questionIndex}`))), _jsxs(Button, { type: "button", variant: "outline", onClick: () => addQuestion(partIndex), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Th\u00EAm c\u00E2u h\u1ECFi"] })] })] }, `part-${partIndex}`))), _jsxs(Button, { type: "button", variant: "outline", onClick: addPart, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Th\u00EAm part"] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Gi\u1EA3i th\u00EDch" }), _jsx(Textarea, { value: explanation, onChange: (event) => setExplanation(event.target.value), rows: 3, placeholder: "Gi\u1EA3i th\u00EDch th\u00EAm v\u1EC1 c\u00E2u tr\u1EA3 l\u1EDDi k\u1EF3 v\u1ECDng, d\u00F9ng khi review k\u1EBFt qu\u1EA3..." })] }), 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))) }))] }));
171
+ return (_jsxs("div", { className: "space-y-3", children: [_jsxs(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(Layers, { className: "h-3.5 w-3.5" }), title: "Nh\u00F3m h\u1ECFi \u0111\u00E1p t\u1EF1 ph\u00E1t", extraActions: _jsx(PointsInput, { value: sumPartPoints(parts), allowZero: true, readOnly: true }), preview: {
172
+ onClick: () => setIsClientMode(true),
173
+ } }), 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" }), _jsx(Input, { id: "group-instruction", value: instruction, onChange: (event) => setInstruction(event.target.value), placeholder: "In pairs, ask and answer the questions.", className: "h-10 border-gray-200 bg-white" })] }), _jsx("p", { className: "text-xs text-gray-500", children: "T\u1ED5ng \u0111i\u1EC3m \u0111\u01B0\u1EE3c c\u1ED9ng t\u1EEB \u0111i\u1EC3m t\u1EEBng c\u00E2u trong t\u1EA5t c\u1EA3 part." })] }), parts.map((part, partIndex) => (_jsxs(CompactFieldCard, { header: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("p", { className: "text-sm font-semibold text-gray-800", children: ["Part ", partIndex + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removePart(partIndex), disabled: parts.length <= 1, children: _jsx(Trash2, { className: "h-4 w-4" }) })] }), children: [_jsxs("div", { className: "grid gap-2.5 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Ti\u00EAu \u0111\u1EC1 part" }), _jsx(Input, { value: part.partTitle, onChange: (event) => updatePart(partIndex, { partTitle: event.target.value }), placeholder: "PART 1" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Ch\u1EE7 \u0111\u1EC1" }), _jsx(Input, { value: part.topic, onChange: (event) => updatePart(partIndex, { topic: event.target.value }), placeholder: "Talk about school." })] })] }), part.questions.map((question, questionIndex) => (_jsxs("div", { className: "space-y-2.5 rounded-md border border-slate-200 px-3 py-2.5", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("p", { className: "text-sm font-semibold text-slate-700", children: ["C\u00E2u ", questionIndex + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeQuestion(partIndex, questionIndex), disabled: part.questions.length <= 1, children: _jsx(Trash2, { className: "h-4 w-4" }) })] }), _jsxs("div", { className: "flex w-fit rounded-lg border border-slate-200 bg-slate-100 p-1", children: [_jsxs("button", { type: "button", onClick: () => updateQuestion(partIndex, questionIndex, { inputType: 'AUDIO' }), className: `flex items-center gap-2 rounded-md px-3 py-1.5 text-xs font-medium ${question.inputType === 'AUDIO' ? 'bg-white text-blue-700 shadow-sm' : 'text-slate-500'}`, children: [_jsx(Mic, { className: "h-3.5 w-3.5" }), " Audio"] }), _jsxs("button", { type: "button", onClick: () => updateQuestion(partIndex, questionIndex, { inputType: 'TEXT' }), className: `flex items-center gap-2 rounded-md px-3 py-1.5 text-xs font-medium ${question.inputType === 'TEXT' ? 'bg-white text-emerald-700 shadow-sm' : 'text-slate-500'}`, children: [_jsx(Type, { className: "h-3.5 w-3.5" }), " Text (TTS)"] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "C\u00E2u h\u1ECFi" }), _jsx(Textarea, { value: question.questionText, onChange: (event) => updateQuestion(partIndex, questionIndex, {
174
+ questionText: event.target.value,
175
+ }), rows: 2, placeholder: "Do you like your timetable at school? Why?/Why not?" })] }), question.inputType === 'AUDIO' ? (_jsx(FileUpload, { id: `audio-${partIndex}-${questionIndex}`, label: "File audio c\u00E2u h\u1ECFi", accept: "audio/*", value: question.audioUrl, onChange: (url) => updateQuestion(partIndex, questionIndex, { audioUrl: url }), maxSize: 20, placeholder: "T\u1EA3i l\u00EAn file audio (MP3, WAV...)", autoUpload: true, prefix: "speaking/spontaneous-qa-group" })) : (_jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: !question.questionText.trim(), onClick: handleGenerateAudio, children: [_jsx(Volume2, { className: "mr-2 h-3.5 w-3.5" }), "T\u1EA1o Audio"] })), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "\u0110\u00E1p \u00E1n m\u1EABu" }), question.expectedAnswers.map((answer, answerIndex) => (_jsx(Input, { value: answer, onChange: (event) => updateExpectedAnswer(partIndex, questionIndex, answerIndex, event.target.value), placeholder: "\u0110\u00E1p \u00E1n m\u1EABu cho AI ch\u1EA5m" }, `ans-${partIndex}-${questionIndex}-${answerIndex}`))), _jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => addExpectedAnswer(partIndex, questionIndex), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Th\u00EAm \u0111\u00E1p \u00E1n m\u1EABu"] })] }), _jsx(PointsInput, { value: question.points, allowZero: true, onChange: (event) => {
176
+ const parsed = Number.parseInt(event.target.value.replace(/[^0-9]/g, ''), 10);
177
+ updateQuestion(partIndex, questionIndex, {
178
+ points: Number.isNaN(parsed) ? 0 : Math.max(0, parsed),
179
+ });
180
+ } })] }, `q-${partIndex}-${questionIndex}`))), _jsxs(Button, { type: "button", variant: "outline", onClick: () => addQuestion(partIndex), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Th\u00EAm c\u00E2u h\u1ECFi"] })] }, `part-${partIndex}`))), _jsxs(Button, { type: "button", variant: "outline", onClick: addPart, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Th\u00EAm part"] }), _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))) }))] }));
179
181
  }
@@ -1,2 +1,3 @@
1
1
  export * from './SpontaneousQaGroupCreator';
2
2
  export * from './SpontaneousQaGroupClient';
3
+ export * from './map-spontaneous-qa-group-data';
@@ -1,2 +1,3 @@
1
1
  export * from './SpontaneousQaGroupCreator';
2
2
  export * from './SpontaneousQaGroupClient';
3
+ export * from './map-spontaneous-qa-group-data';
@@ -0,0 +1,2 @@
1
+ import type { SpontaneousQaGroupData } from '../../_shared/types/spontaneous-qa-group.type';
2
+ export declare function mapQuestionToSpontaneousQaGroupData(question: Record<string, unknown>): SpontaneousQaGroupData;
@@ -0,0 +1,74 @@
1
+ function isRecord(value) {
2
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
3
+ }
4
+ function asRecord(value) {
5
+ return isRecord(value) ? value : {};
6
+ }
7
+ function asString(value, fallback = '') {
8
+ return typeof value === 'string' ? value : fallback;
9
+ }
10
+ function asNumber(value, fallback) {
11
+ return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
12
+ }
13
+ function normalizeExpectedAnswers(value) {
14
+ if (!Array.isArray(value)) {
15
+ return [];
16
+ }
17
+ return value
18
+ .filter((item) => typeof item === 'string')
19
+ .map((item) => item.trim())
20
+ .filter((item) => item !== '');
21
+ }
22
+ function mapQuestion(item, index) {
23
+ const record = asRecord(item);
24
+ const content = asRecord(record.content);
25
+ const correctAnswer = asRecord(record.correctAnswer);
26
+ return {
27
+ questionText: asString(content.questionText) ||
28
+ asString(content.question) ||
29
+ asString(record.questionText),
30
+ inputType: asString(content.inputType) === 'AUDIO' ? 'AUDIO' : 'TEXT',
31
+ audioUrl: asString(content.audioUrl),
32
+ expectedAnswers: normalizeExpectedAnswers(correctAnswer.expectedAnswers),
33
+ points: asNumber(record.points, 1),
34
+ questionNumber: asNumber(record.questionNumber, index + 1),
35
+ };
36
+ }
37
+ function mapPart(item, index) {
38
+ const record = asRecord(item);
39
+ const questions = Array.isArray(record.questions) ? record.questions : [];
40
+ return {
41
+ partNumber: asNumber(record.partNumber, index + 1),
42
+ partTitle: asString(record.partTitle, `PART ${index + 1}`),
43
+ topic: asString(record.topic),
44
+ questions: questions.length > 0
45
+ ? questions.map((question, questionIndex) => mapQuestion(question, questionIndex))
46
+ : [],
47
+ };
48
+ }
49
+ export function mapQuestionToSpontaneousQaGroupData(question) {
50
+ const content = asRecord(question.content);
51
+ const list = asRecord(question.spontaneousQAList ?? content.spontaneousQAList ?? content);
52
+ const itemsRaw = Array.isArray(list.items)
53
+ ? list.items
54
+ : Array.isArray(content.items)
55
+ ? content.items
56
+ : [];
57
+ const hasNestedParts = itemsRaw.some((item) => Array.isArray(asRecord(item).questions));
58
+ const parts = hasNestedParts
59
+ ? itemsRaw.map((item, index) => mapPart(item, index))
60
+ : [mapPart({
61
+ partNumber: 1,
62
+ partTitle: '',
63
+ topic: '',
64
+ questions: itemsRaw,
65
+ }, 0)];
66
+ return {
67
+ instruction: asString(asRecord(list.meta).instruction) ||
68
+ asString(asRecord(content.meta).instruction) ||
69
+ 'Talk about the topic.',
70
+ parts,
71
+ explanation: asString(question.explanation),
72
+ points: parts.reduce((total, part) => (total + part.questions.reduce((partTotal, item) => partTotal + (item.points || 0), 0)), 0),
73
+ };
74
+ }
@@ -23,9 +23,9 @@ export function TrueFalseClient({ questionData, onSubmit, isReviewMode = false,
23
23
  };
24
24
  const hasAnswered = selectedAnswer !== undefined;
25
25
  const isAnswerCorrect = isReviewMode && selectedAnswer === questionData.answer;
26
- return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-5 py-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-emerald-500 to-teal-600 text-white shadow-sm", children: _jsx(CircleHelp, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "\u0110\u00FAng / Sai" })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isAnswerCorrect
26
+ return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-emerald-500 to-teal-600 text-white shadow-sm", children: _jsx(CircleHelp, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "\u0110\u00FAng / Sai" })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isAnswerCorrect
27
27
  ? 'bg-green-100 text-green-700'
28
- : 'bg-red-100 text-red-700'), children: [isAnswerCorrect ? (_jsx(Check, { className: "h-3.5 w-3.5" })) : (_jsx(X, { className: "h-3.5 w-3.5" })), isAnswerCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "space-y-5 p-5", children: [_jsx("div", { className: "rounded-lg border border-blue-100 bg-gradient-to-r from-blue-50 to-indigo-50 p-4", children: _jsx("h3", { className: "text-base font-semibold leading-relaxed text-gray-800", children: questionData.question || 'Chưa nhập câu hỏi' }) }), (imagePreviewUrl || questionData.imageUrl) && (_jsxs("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-gradient-to-br from-slate-50 to-gray-100", children: [_jsx("div", { className: "border-b border-gray-100 px-3 py-1.5", children: _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "h-2 w-2 rounded-full bg-red-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-yellow-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-green-400" }), _jsx("span", { className: "ml-2 text-xs font-medium text-gray-500", children: "H\u00ECnh \u1EA3nh" })] }) }), _jsx("div", { className: "flex items-center justify-center p-4", children: isLoading ? (_jsx("div", { className: "flex h-48 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500" }) })) : (
28
+ : 'bg-red-100 text-red-700'), children: [isAnswerCorrect ? (_jsx(Check, { className: "h-3.5 w-3.5" })) : (_jsx(X, { className: "h-3.5 w-3.5" })), isAnswerCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "space-y-3 p-3", children: [_jsx("div", { className: "rounded-lg border border-blue-100 bg-gradient-to-r from-blue-50 to-indigo-50 p-4", children: _jsx("h3", { className: "text-base font-semibold leading-relaxed text-gray-800", children: questionData.question || 'Chưa nhập câu hỏi' }) }), (imagePreviewUrl || questionData.imageUrl) && (_jsxs("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-gradient-to-br from-slate-50 to-gray-100", children: [_jsx("div", { className: "border-b border-gray-100 px-3 py-1.5", children: _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "h-2 w-2 rounded-full bg-red-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-yellow-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-green-400" }), _jsx("span", { className: "ml-2 text-xs font-medium text-gray-500", children: "H\u00ECnh \u1EA3nh" })] }) }), _jsx("div", { className: "flex items-center justify-center p-4", children: isLoading ? (_jsx("div", { className: "flex h-48 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500" }) })) : (
29
29
  /* eslint-disable-next-line @next/next/no-img-element */
30
30
  _jsx("img", { src: imagePreviewUrl || questionData.imageUrl, alt: "Question illustration", className: "max-h-56 w-auto rounded-md object-contain" })) })] })), _jsxs("div", { className: "space-y-2.5", children: [_jsx("span", { className: "text-xs font-medium uppercase tracking-wider text-gray-500", children: "Ch\u1ECDn \u0111\u00E1p \u00E1n" }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("button", { type: "button", onClick: () => handleAnswerChange(true), disabled: isReviewMode, className: cn('group flex items-center justify-center gap-3 rounded-xl border-2 p-4 transition-all', isReviewMode
31
31
  ? questionData.answer === true
@@ -2,14 +2,11 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { ImagePreview } from '../../../../components/ui/image-preview';
4
4
  import { useState, useRef, useEffect } from 'react';
5
- import { Button } from '../../../../components/ui/button';
6
5
  import { Label } from '../../../../components/ui/label';
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 { Checkbox } from '../../../../components/ui/checkbox';
11
7
  import { usePresignedFileUrl, useDebouncedCallback } from '../../../../shared/lib/hooks';
12
- import { Pencil, Eye, ImageIcon, MessageSquare, HelpCircle, BookOpen, Check, X } from 'lucide-react';
8
+ import { ImageIcon, MessageSquare, HelpCircle, Check, X } from 'lucide-react';
9
+ import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
13
10
  import { FileUpload } from '../../../../components/ui/file-upload';
14
11
  import { TrueFalseClient } from './TrueFalseClient';
15
12
  import { cn } from '../../../../shared/lib/utils';
@@ -211,33 +208,42 @@ function TrueFalseCreatorContent({ initialData, onSave, onCancel, onChange, exte
211
208
  groupNumber: currentGroupNumber,
212
209
  ...(effectiveGroupType === 'ARTICLES' ? { articles } : {}),
213
210
  };
214
- return (_jsxs("div", { className: "space-y-4", children: [_jsx(Card, { children: _jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx(CardTitle, { children: "\u0110\u00FAng/Sai (Xem tr\u01B0\u1EDBc)" }), _jsx("p", { className: "mt-1 text-sm text-gray-500", children: "Ch\u1EBF \u0111\u1ED9 xem tr\u01B0\u1EDBc - Hi\u1EC3n th\u1ECB \u0111\u00E1p \u00E1n \u0111\u00FAng" })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(false), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), "Quay l\u1EA1i Edit Mode"] })] }) }) }), _jsx(TrueFalseClient, { questionData: questionData, isReviewMode: true, userAnswer: answer })] }));
211
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(TrueFalseClient, { questionData: questionData, isReviewMode: true, userAnswer: answer })] }));
215
212
  }
216
213
  // Creator UI
217
- return (_jsxs("div", { className: "space-y-4", children: [isGrouped && effectiveGroupType === 'ARTICLES' && (_jsx(ArticlesGroupCard, { groupNumber: currentGroupNumber, articleCount: articleCount, articles: articles, onArticlesChange: setArticles, title: groupTitle, onTitleChange: setGroupTitle, isReadOnly: !isFirstInGroup, uploadIdPrefix: `true-false-${partId ?? 'articles'}` })), isGrouped && effectiveGroupType === 'BASIC' && (_jsx(BasicQuestionGroupCard, { groupNumber: currentGroupNumber, title: groupTitle, subtitle: groupSubtitle, content: groupContent, imageUrl: groupImageUrl, onTitleChange: setGroupTitle, onSubtitleChange: setGroupSubtitle, onContentChange: setGroupContent, onImageChange: setGroupImageUrl, groupConfig: groupType ? groupConfig : undefined, isReadOnly: !isFirstInGroup, uploadIdPrefix: `true-false-${partId ?? 'group'}` })), isGrouped && effectiveGroupType === 'DOCUMENT' && (_jsx(DocumentGroupCard, { groupNumber: currentGroupNumber, title: groupTitle, subtitle: groupSubtitle, content: groupContent, imageUrl: groupImageUrl, onTitleChange: setGroupTitle, onSubtitleChange: setGroupSubtitle, onContentChange: setGroupContent, onImageChange: setGroupImageUrl, groupConfig: groupType ? groupConfig : undefined, isReadOnly: !isFirstInGroup, uploadIdPrefix: `true-false-${partId ?? 'document'}` })), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50", children: [_jsx(CardHeader, { className: "px-4 py-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-blue-600 shadow-sm", children: _jsx(HelpCircle, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-base font-bold text-gray-900", children: "\u0110\u00FAng / Sai" }), _jsx("p", { className: "text-xs text-gray-500", children: "Ch\u1ECDn \u0111\u00E1p \u00E1n \u0110\u00FAng ho\u1EB7c Sai cho c\u00E2u h\u1ECFi" })] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [headerExtra, _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(true), disabled: !question, className: "gap-2 border-teal-200 text-teal-600 hover:bg-teal-50 hover:text-teal-700", children: [_jsx(Eye, { className: "h-4 w-4" }), "Xem tr\u01B0\u1EDBc"] })] })] }) }), _jsxs(CardContent, { className: "space-y-4 px-4 pb-4", children: [_jsxs("div", { className: "flex items-center gap-6", children: [_jsx(PointsInput, { value: points, onChange: (e) => setPoints(parseFloat(e.target.value) || 1) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Checkbox, { id: "isExample", checked: isExample, onCheckedChange: (checked) => setIsExample(!!checked) }), _jsx(Label, { htmlFor: "isExample", className: "flex items-center gap-1.5 cursor-pointer text-sm font-medium text-gray-700", children: "\u0110\u00E1nh d\u1EA5u l\u00E0 c\u00E2u h\u1ECFi Example" })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-teal-500" }), "H\u00ECnh \u1EA3nh ", _jsx("span", { className: "text-sm font-normal text-gray-500", children: "(t\u00F9y ch\u1ECDn)" })] }), _jsx(FileUpload, { id: "question-image", label: "", accept: "image/*", value: imageUrl || '', onChange: (url) => setImageUrl(url), onPresignedUrlChange: setImagePreviewUrl, maxSize: 5, placeholder: "Upload \u1EA3nh ho\u1EB7c paste URL", autoUpload: true, prefix: "questions" }), displayPreviewUrl && (_jsx(ImagePreview, { src: displayPreviewUrl, alt: "Preview", className: "mt-2 h-48 w-full max-w-md rounded-lg border border-gray-200" }))] }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { htmlFor: "question", className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(MessageSquare, { className: "h-4 w-4 text-teal-500" }), "C\u00E2u h\u1ECFi ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Textarea, { id: "question", value: question, onChange: (e) => {
218
- setQuestion(e.target.value);
219
- if (validationErrors.question) {
220
- setValidationErrors((prev) => ({ ...prev, question: undefined }));
214
+ return (_jsxs("div", { className: "space-y-3", children: [isGrouped && effectiveGroupType === 'ARTICLES' && (_jsx(ArticlesGroupCard, { groupNumber: currentGroupNumber, articleCount: articleCount, articles: articles, onArticlesChange: setArticles, title: groupTitle, onTitleChange: setGroupTitle, isReadOnly: !isFirstInGroup, uploadIdPrefix: `true-false-${partId ?? 'articles'}` })), isGrouped && effectiveGroupType === 'BASIC' && (_jsx(BasicQuestionGroupCard, { groupNumber: currentGroupNumber, title: groupTitle, subtitle: groupSubtitle, content: groupContent, imageUrl: groupImageUrl, onTitleChange: setGroupTitle, onSubtitleChange: setGroupSubtitle, onContentChange: setGroupContent, onImageChange: setGroupImageUrl, groupConfig: groupType ? groupConfig : undefined, isReadOnly: !isFirstInGroup, uploadIdPrefix: `true-false-${partId ?? 'group'}` })), isGrouped && effectiveGroupType === 'DOCUMENT' && (_jsx(DocumentGroupCard, { groupNumber: currentGroupNumber, title: groupTitle, subtitle: groupSubtitle, content: groupContent, imageUrl: groupImageUrl, onTitleChange: setGroupTitle, onSubtitleChange: setGroupSubtitle, onContentChange: setGroupContent, onImageChange: setGroupImageUrl, groupConfig: groupType ? groupConfig : undefined, isReadOnly: !isFirstInGroup, uploadIdPrefix: `true-false-${partId ?? 'document'}` })), _jsxs(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(HelpCircle, { className: "h-3.5 w-3.5" }), title: "\u0110\u00FAng / Sai", points: {
215
+ value: points,
216
+ onValueChange: setPoints,
217
+ }, example: {
218
+ checked: isExample,
219
+ onChange: setIsExample,
220
+ }, headerExtra: headerExtra, preview: {
221
+ onClick: () => setIsClientMode(true),
222
+ disabled: !question,
223
+ } }), children: [_jsxs("div", { className: "space-y-1.5", children: [_jsxs(Label, { className: "flex items-center gap-1.5 text-sm font-semibold text-gray-800", children: [_jsx(ImageIcon, { className: "h-3.5 w-3.5 text-teal-500" }), "H\u00ECnh \u1EA3nh ", _jsx("span", { className: "text-xs font-normal text-gray-500", children: "(t\u00F9y ch\u1ECDn)" })] }), _jsx(FileUpload, { id: "question-image", label: "", accept: "image/*", value: imageUrl || '', onChange: (url) => setImageUrl(url), onPresignedUrlChange: setImagePreviewUrl, maxSize: 5, placeholder: "Upload \u1EA3nh ho\u1EB7c paste URL", autoUpload: true, prefix: "questions" }), displayPreviewUrl && (_jsx(ImagePreview, { src: displayPreviewUrl, alt: "Preview", className: "mt-2 h-48 w-full max-w-md rounded-lg border border-gray-200" }))] }), _jsxs("div", { className: "space-y-1.5", children: [_jsxs(Label, { htmlFor: "question", className: "flex items-center gap-1.5 text-sm font-semibold text-gray-800", children: [_jsx(MessageSquare, { className: "h-3.5 w-3.5 text-teal-500" }), "C\u00E2u h\u1ECFi ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Textarea, { id: "question", value: question, onChange: (e) => {
224
+ setQuestion(e.target.value);
225
+ if (validationErrors.question) {
226
+ setValidationErrors((prev) => ({ ...prev, question: undefined }));
227
+ }
228
+ }, placeholder: "Nh\u1EADp c\u00E2u h\u1ECFi... (vd: Paris is the capital of France.)", className: cn('min-h-[100px] border-gray-200 bg-white transition-all focus:border-teal-300 focus:ring-teal-200', validationErrors.question && 'border-red-400 focus:border-red-500 focus:ring-red-200'), rows: 3 }), validationErrors.question && (_jsx("p", { className: "text-sm text-red-600", children: validationErrors.question }))] }), _jsxs("div", { className: "space-y-3", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(HelpCircle, { className: "h-4 w-4 text-emerald-500" }), "\u0110\u00E1p \u00E1n ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("button", { type: "button", onClick: () => {
229
+ setAnswer(true);
230
+ if (validationErrors.answer) {
231
+ setValidationErrors((prev) => ({ ...prev, answer: undefined }));
221
232
  }
222
- }, placeholder: "Nh\u1EADp c\u00E2u h\u1ECFi... (vd: Paris is the capital of France.)", className: cn('min-h-[100px] border-gray-200 bg-white transition-all focus:border-teal-300 focus:ring-teal-200', validationErrors.question && 'border-red-400 focus:border-red-500 focus:ring-red-200'), rows: 3 }), validationErrors.question && (_jsx("p", { className: "text-sm text-red-600", children: validationErrors.question }))] }), _jsxs("div", { className: "space-y-3", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(HelpCircle, { className: "h-4 w-4 text-emerald-500" }), "\u0110\u00E1p \u00E1n ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("button", { type: "button", onClick: () => {
223
- setAnswer(true);
224
- if (validationErrors.answer) {
225
- setValidationErrors((prev) => ({ ...prev, answer: undefined }));
226
- }
227
- }, className: cn('group flex items-center justify-center gap-2 rounded-lg border-2 px-4 py-2.5 text-center transition-all', answer === true
228
- ? 'border-emerald-500 bg-emerald-50 shadow-sm ring-1 ring-emerald-100'
229
- : 'border-gray-200 bg-white hover:border-emerald-300 hover:bg-emerald-50/50'), children: [_jsx("div", { className: cn('flex h-7 w-7 items-center justify-center rounded-full transition-all', answer === true
230
- ? 'bg-emerald-500 text-white'
231
- : 'bg-gray-100 text-gray-500 group-hover:bg-emerald-100 group-hover:text-emerald-600'), children: _jsx(Check, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: cn('text-sm font-semibold', answer === true ? 'text-emerald-700' : 'text-gray-600'), children: "\u0110\u00FAng (True)" })] }), _jsxs("button", { type: "button", onClick: () => {
232
- setAnswer(false);
233
- if (validationErrors.answer) {
234
- setValidationErrors((prev) => ({ ...prev, answer: undefined }));
235
- }
236
- }, className: cn('group flex items-center justify-center gap-2 rounded-lg border-2 px-4 py-2.5 text-center transition-all', answer === false
237
- ? 'border-red-500 bg-red-50 shadow-sm ring-1 ring-red-100'
238
- : 'border-gray-200 bg-white hover:border-red-300 hover:bg-red-50/50'), children: [_jsx("div", { className: cn('flex h-7 w-7 items-center justify-center rounded-full transition-all', answer === false
239
- ? 'bg-red-500 text-white'
240
- : 'bg-gray-100 text-gray-500 group-hover:bg-red-100 group-hover:text-red-600'), children: _jsx(X, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: cn('text-sm font-semibold', answer === false ? 'text-red-700' : 'text-gray-600'), children: "Sai (False)" })] })] }), validationErrors.answer && (_jsx("p", { className: "text-sm text-red-600", children: validationErrors.answer }))] }), externalErrors && externalErrors.length > 0 && (_jsxs("div", { className: "flex items-start gap-3 rounded-xl border border-red-200 bg-gradient-to-r from-red-50 to-rose-50 p-4", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-red-100", children: _jsx("span", { className: "text-lg", children: "\u274C" }) }), _jsxs("div", { className: "text-sm", children: [_jsx("p", { className: "font-semibold text-red-800", children: "L\u1ED7i:" }), _jsx("ul", { className: "mt-1 list-inside list-disc text-red-700", children: externalErrors.map((error, index) => (_jsx("li", { children: error }, index))) })] })] }))] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50", children: [_jsx(CardHeader, { className: "px-4 py-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm", children: _jsx(BookOpen, { className: "h-3.5 w-3.5 text-white" }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-base font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch" }), _jsx("span", { className: "inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600", children: "Kh\u00F4ng b\u1EAFt bu\u1ED9c" })] }), _jsx("p", { className: "text-xs text-gray-500 mt-0.5", children: "Gi\u1EA3i th\u00EDch cho \u0111\u00E1p \u00E1n \u0111\u00FAng \u2014 hi\u1EC3n th\u1ECB sau khi h\u1ECDc sinh ho\u00E0n th\u00E0nh c\u00E2u h\u1ECFi" })] })] }) }), _jsx(CardContent, { className: "px-4 pb-4", children: _jsx(Textarea, { id: "explanation", value: explanation, onChange: (e) => setExplanation(e.target.value), placeholder: "VD: C\u00E2u tr\u1EA3 l\u1EDDi \u0111\u00FAng l\u00E0... v\u00EC...", rows: 3, className: "min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none" }) })] })] }));
233
+ }, className: cn('group flex items-center justify-center gap-2 rounded-lg border-2 px-4 py-2.5 text-center transition-all', answer === true
234
+ ? 'border-emerald-500 bg-emerald-50 shadow-sm ring-1 ring-emerald-100'
235
+ : 'border-gray-200 bg-white hover:border-emerald-300 hover:bg-emerald-50/50'), children: [_jsx("div", { className: cn('flex h-7 w-7 items-center justify-center rounded-full transition-all', answer === true
236
+ ? 'bg-emerald-500 text-white'
237
+ : 'bg-gray-100 text-gray-500 group-hover:bg-emerald-100 group-hover:text-emerald-600'), children: _jsx(Check, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: cn('text-sm font-semibold', answer === true ? 'text-emerald-700' : 'text-gray-600'), children: "\u0110\u00FAng (True)" })] }), _jsxs("button", { type: "button", onClick: () => {
238
+ setAnswer(false);
239
+ if (validationErrors.answer) {
240
+ setValidationErrors((prev) => ({ ...prev, answer: undefined }));
241
+ }
242
+ }, className: cn('group flex items-center justify-center gap-2 rounded-lg border-2 px-4 py-2.5 text-center transition-all', answer === false
243
+ ? 'border-red-500 bg-red-50 shadow-sm ring-1 ring-red-100'
244
+ : 'border-gray-200 bg-white hover:border-red-300 hover:bg-red-50/50'), children: [_jsx("div", { className: cn('flex h-7 w-7 items-center justify-center rounded-full transition-all', answer === false
245
+ ? 'bg-red-500 text-white'
246
+ : 'bg-gray-100 text-gray-500 group-hover:bg-red-100 group-hover:text-red-600'), children: _jsx(X, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: cn('text-sm font-semibold', answer === false ? 'text-red-700' : 'text-gray-600'), children: "Sai (False)" })] })] }), validationErrors.answer && (_jsx("p", { className: "text-sm text-red-600", children: validationErrors.answer }))] }), externalErrors && externalErrors.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 rounded-md border border-red-200 bg-red-50 p-2 text-xs text-red-700", children: [_jsx("span", { className: "font-semibold text-red-800", children: "L\u1ED7i:" }), _jsx("span", { children: externalErrors.join(' · ') })] }))] }), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation })] }));
241
247
  }
242
248
  export function TrueFalseCreator(props) {
243
249
  return _jsx(TrueFalseCreatorContent, { ...props });
@@ -7,7 +7,7 @@ export function TrueFalseCorrectGroupClient({ questionData, isReviewMode = false
7
7
  return (_jsxs("div", { className: "space-y-4", children: [questionData.instruction && (_jsx("div", { className: "rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3 text-sm font-medium text-indigo-800", children: questionData.instruction })), items.map((item, itemIndex) => {
8
8
  const userValue = userAnswers[itemIndex];
9
9
  const correctIsTrue = item.isTrue === true;
10
- return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-4 py-2.5", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 text-white", children: _jsx(CircleHelp, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", item.questionNumber || itemIndex + 1] }), item.isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-semibold text-amber-700", children: [_jsx(Star, { className: "h-3 w-3" }), "Example"] }))] }), _jsxs("span", { className: "text-xs text-gray-500", children: [item.points || 0, " \u0111i\u1EC3m"] })] }), _jsxs("div", { className: "space-y-3 p-4", children: [_jsx("p", { className: "text-sm font-semibold leading-relaxed text-gray-800", children: item.question || 'Câu hỏi chưa được nhập' }), _jsx("div", { className: "flex items-center gap-3", children: [
10
+ return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 text-white", children: _jsx(CircleHelp, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", item.questionNumber || itemIndex + 1] }), item.isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-semibold text-amber-700", children: [_jsx(Star, { className: "h-3 w-3" }), "Example"] }))] }), _jsxs("span", { className: "text-xs text-gray-500", children: [item.points || 0, " \u0111i\u1EC3m"] })] }), _jsxs("div", { className: "space-y-3 p-3", children: [_jsx("p", { className: "text-sm font-semibold leading-relaxed text-gray-800", children: item.question || 'Câu hỏi chưa được nhập' }), _jsx("div", { className: "flex items-center gap-3", children: [
11
11
  { value: true, label: 'True' },
12
12
  { value: false, label: 'False' },
13
13
  ].map((opt) => {
@@ -9,7 +9,8 @@ import { PointsInput } from '../../../../components/ui/points-input';
9
9
  import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
10
10
  import { Switch } from '../../../../components/ui/switch';
11
11
  import { useDebouncedCallback } from '../../../../shared/lib/hooks';
12
- import { Eye, Pencil, Plus, Star, Trash2 } from 'lucide-react';
12
+ import { HelpCircle, Plus, Star, Trash2 } from 'lucide-react';
13
+ import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
13
14
  import { TrueFalseCorrectGroupClient } from './TrueFalseCorrectGroupClient';
14
15
  import { TRUE_FALSE_CORRECT_GROUP_DEFAULT_INSTRUCTION } from '../../_shared/types/true-false-correct-group.type';
15
16
  function parsePointsValue(raw, allowZero = false) {
@@ -135,10 +136,13 @@ export function TrueFalseCorrectGroupCreator({ initialData, onChange, externalEr
135
136
  };
136
137
  const currentPayload = buildPayload();
137
138
  const displayErrors = externalErrors && externalErrors.length > 0 ? externalErrors : errors;
138
- return (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-lg font-medium text-gray-900", children: "T\u1EA1o c\u00E2u h\u1ECFi \u0110\u00FAng / Sai + S\u1EEDa c\u00E2u (Nh\u00F3m)" }), _jsx("p", { className: "text-sm text-gray-500", children: "Tick/Cross t\u1EEBng c\u00E2u. C\u00E2u False b\u1EAFt bu\u1ED9c vi\u1EBFt l\u1EA1i c\u00E2u \u0111\u00FAng." })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setIsClientMode((prev) => !prev), className: "gap-2", children: [isClientMode ? _jsx(Pencil, { className: "h-4 w-4" }) : _jsx(Eye, { className: "h-4 w-4" }), isClientMode ? 'Chế độ chỉnh sửa' : 'Xem trước học sinh'] })] }), displayErrors.length > 0 && (_jsx("div", { className: "rounded-lg bg-red-50 p-4 border border-red-200", children: _jsx("div", { className: "flex", children: _jsxs("div", { className: "ml-3", children: [_jsx("h3", { className: "text-sm font-medium text-red-800", children: "C\u1EA7n s\u1EEDa c\u00E1c l\u1ED7i sau:" }), _jsx("div", { className: "mt-2 text-sm text-red-700", children: _jsx("ul", { className: "list-disc space-y-1 pl-5", children: displayErrors.map((err, i) => (_jsx("li", { children: err }, i))) }) })] }) }) })), isClientMode ? (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base font-semibold", children: "Xem tr\u01B0\u1EDBc hi\u1EC3n th\u1ECB h\u1ECDc sinh" }) }), _jsx(CardContent, { children: _jsx(TrueFalseCorrectGroupClient, { questionData: currentPayload }) })] })) : (_jsxs("div", { className: "space-y-6", children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base font-semibold", children: "H\u01B0\u1EDBng d\u1EABn chung (Instruction)" }) }), _jsx(CardContent, { className: "space-y-4", children: _jsxs("div", { children: [_jsx(Label, { htmlFor: "instruction", children: "H\u01B0\u1EDBng d\u1EABn b\u00E0i l\u00E0m" }), _jsx(Input, { id: "instruction", value: instruction, onChange: (e) => setInstruction(e.target.value), placeholder: TRUE_FALSE_CORRECT_GROUP_DEFAULT_INSTRUCTION, className: "mt-1" })] }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs(CardTitle, { className: "text-base font-semibold", children: ["Danh s\u00E1ch c\u00E2u h\u1ECFi (", items.length, ")"] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addItem, className: "gap-2", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm c\u00E2u h\u1ECFi"] })] }), _jsx(CardContent, { className: "space-y-4", children: items.map((item, index) => (_jsxs("div", { className: "space-y-4 rounded-lg border border-gray-200 bg-gray-50/50 p-4 transition-colors hover:border-gray-300", children: [_jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full bg-blue-100 text-xs font-semibold text-blue-700", children: item.questionNumber || index + 1 }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u h\u1ECFi ", index + 1] })] }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: `example-${index}`, checked: item.isExample || false, onCheckedChange: (checked) => updateItem(index, {
139
- isExample: checked,
140
- points: checked ? 0 : 1,
141
- }) }), _jsxs(Label, { htmlFor: `example-${index}`, className: "flex items-center gap-1 text-xs cursor-pointer", children: [_jsx(Star, { className: "h-3 w-3 text-amber-500" }), "C\u00E2u m\u1EABu (Example)"] })] }), !item.isExample && (_jsx("div", { className: "w-24", children: _jsx(PointsInput, { value: item.points, allowZero: true, onChange: (event) => updateItem(index, {
142
- points: parsePointsValue(event.target.value, true),
143
- }) }) })), items.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeItem(index), className: "text-red-500 hover:bg-red-50 hover:text-red-600", children: _jsx(Trash2, { className: "h-4 w-4" }) }))] })] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: `question-${index}`, children: "N\u1ED9i dung c\u00E2u h\u1ECFi" }), _jsx(Textarea, { id: `question-${index}`, value: item.question, onChange: (e) => updateItem(index, { question: e.target.value }), placeholder: "Nh\u1EADp n\u1ED9i dung c\u00E2u h\u1ECFi...", className: "mt-1 bg-white", rows: 2 })] }), _jsxs("div", { children: [_jsx(Label, { className: "mb-2 block text-xs font-medium text-gray-700", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsx(Button, { type: "button", variant: item.isTrue === true ? 'default' : 'outline', size: "sm", onClick: () => setItemTruth(index, true), className: "w-24", children: "True" }), _jsx(Button, { type: "button", variant: item.isTrue === false ? 'default' : 'outline', size: "sm", onClick: () => setItemTruth(index, false), className: "w-24", children: "False" })] })] }), item.isTrue === false && (_jsxs("div", { children: [_jsx(Label, { htmlFor: `correction-${index}`, children: "C\u00E2u s\u1EEDa \u0111\u00FAng" }), _jsx(Textarea, { id: `correction-${index}`, value: item.correction, onChange: (e) => updateItem(index, { correction: e.target.value }), placeholder: "Vi\u1EBFt l\u1EA1i c\u00E2u \u0111\u00FAng...", className: "mt-1 bg-white", rows: 2 })] }))] }, index))) })] }), _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 }) })] })] }))] }));
139
+ if (isClientMode) {
140
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(TrueFalseCorrectGroupClient, { questionData: currentPayload })] }));
141
+ }
142
+ return (_jsxs("div", { className: "space-y-3", children: [displayErrors.length > 0 && (_jsx("div", { className: "rounded-md border border-red-200 bg-red-50 p-2 text-xs text-red-700", children: _jsx("ul", { className: "list-disc space-y-1 pl-4", children: displayErrors.map((err, i) => (_jsx("li", { children: err }, i))) }) })), _jsx(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(HelpCircle, { className: "h-3.5 w-3.5" }), title: "\u0110\u00FAng / Sai + S\u1EEDa c\u00E2u (Nh\u00F3m)", preview: { onClick: () => setIsClientMode(true) } }), children: _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "instruction", className: "text-sm font-semibold text-gray-800", children: "H\u01B0\u1EDBng d\u1EABn b\u00E0i l\u00E0m" }), _jsx(Input, { id: "instruction", value: instruction, onChange: (e) => setInstruction(e.target.value), placeholder: TRUE_FALSE_CORRECT_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 h\u1ECFi (", 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 h\u1ECFi"] })] }) }), _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("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full bg-blue-100 text-xs font-semibold text-blue-700", children: item.questionNumber || index + 1 }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u h\u1ECFi ", index + 1] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: `example-${index}`, checked: item.isExample || false, onCheckedChange: (checked) => updateItem(index, {
143
+ isExample: checked,
144
+ points: checked ? 0 : 1,
145
+ }) }), _jsxs(Label, { htmlFor: `example-${index}`, className: "flex cursor-pointer items-center gap-1 text-xs", children: [_jsx(Star, { className: "h-3 w-3 text-amber-500" }), "C\u00E2u m\u1EABu (Example)"] })] }), !item.isExample && (_jsx("div", { className: "w-24", children: _jsx(PointsInput, { value: item.points, allowZero: true, onChange: (event) => updateItem(index, {
146
+ points: parsePointsValue(event.target.value, true),
147
+ }) }) })), 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 hover:text-red-600", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] })] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: `question-${index}`, children: "N\u1ED9i dung c\u00E2u h\u1ECFi" }), _jsx(Textarea, { id: `question-${index}`, value: item.question, onChange: (e) => updateItem(index, { question: e.target.value }), placeholder: "Nh\u1EADp n\u1ED9i dung c\u00E2u h\u1ECFi...", className: "mt-1 min-h-[56px] border-gray-200 bg-white", rows: 2 })] }), _jsxs("div", { children: [_jsx(Label, { className: "mb-1.5 block text-xs font-medium text-gray-700", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { type: "button", variant: item.isTrue === true ? 'default' : 'outline', size: "sm", onClick: () => setItemTruth(index, true), className: "h-8 w-24", children: "True" }), _jsx(Button, { type: "button", variant: item.isTrue === false ? 'default' : 'outline', size: "sm", onClick: () => setItemTruth(index, false), className: "h-8 w-24", children: "False" })] })] }), item.isTrue === false && (_jsxs("div", { children: [_jsx(Label, { htmlFor: `correction-${index}`, children: "C\u00E2u s\u1EEDa \u0111\u00FAng" }), _jsx(Textarea, { id: `correction-${index}`, value: item.correction, onChange: (e) => updateItem(index, { correction: e.target.value }), placeholder: "Vi\u1EBFt l\u1EA1i c\u00E2u \u0111\u00FAng...", className: "mt-1 min-h-[56px] border-gray-200 bg-white", rows: 2 })] }))] }, index))), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(initialData?.explanation) })] })] })] }));
144
148
  }
@@ -7,7 +7,7 @@ export function TrueFalseGroupClient({ questionData, isReviewMode = false, userA
7
7
  return (_jsxs("div", { className: "space-y-4", children: [questionData.instruction && (_jsx("div", { className: "rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3 text-sm font-medium text-indigo-800", children: questionData.instruction })), items.map((item, itemIndex) => {
8
8
  const userValue = userAnswers[itemIndex];
9
9
  const correctAnswer = item.correctAnswer;
10
- return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-4 py-2.5", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 text-white", children: _jsx(CircleHelp, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", item.questionNumber || itemIndex + 1] }), item.isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-semibold text-amber-700", children: [_jsx(Star, { className: "h-3 w-3" }), "Example"] }))] }), _jsxs("span", { className: "text-xs text-gray-500", children: [item.points || 0, " \u0111i\u1EC3m"] })] }), _jsxs("div", { className: "space-y-3 p-4", children: [_jsx("p", { className: "text-sm font-semibold leading-relaxed text-gray-800", children: item.question || 'Câu hỏi chưa được nhập' }), _jsx("div", { className: "flex items-center gap-3", children: [
10
+ return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 text-white", children: _jsx(CircleHelp, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", item.questionNumber || itemIndex + 1] }), item.isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-semibold text-amber-700", children: [_jsx(Star, { className: "h-3 w-3" }), "Example"] }))] }), _jsxs("span", { className: "text-xs text-gray-500", children: [item.points || 0, " \u0111i\u1EC3m"] })] }), _jsxs("div", { className: "space-y-3 p-3", children: [_jsx("p", { className: "text-sm font-semibold leading-relaxed text-gray-800", children: item.question || 'Câu hỏi chưa được nhập' }), _jsx("div", { className: "flex items-center gap-3", children: [
11
11
  { value: true, label: 'True' },
12
12
  { value: false, label: 'False' },
13
13
  ].map((opt) => {
@@ -9,7 +9,8 @@ import { PointsInput } from '../../../../components/ui/points-input';
9
9
  import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
10
10
  import { Switch } from '../../../../components/ui/switch';
11
11
  import { useDebouncedCallback } from '../../../../shared/lib/hooks';
12
- import { Eye, Pencil, Plus, Star, Trash2 } from 'lucide-react';
12
+ import { HelpCircle, Plus, Star, Trash2 } from 'lucide-react';
13
+ import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
13
14
  import { TrueFalseGroupClient } from './TrueFalseGroupClient';
14
15
  function parsePointsValue(raw, allowZero = false) {
15
16
  if (raw === '')
@@ -120,10 +121,13 @@ export function TrueFalseGroupCreator({ initialData, onChange, externalErrors, o
120
121
  };
121
122
  const currentPayload = buildPayload();
122
123
  const displayErrors = externalErrors && externalErrors.length > 0 ? externalErrors : errors;
123
- return (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-lg font-medium text-gray-900", children: "T\u1EA1o c\u00E2u h\u1ECFi \u0110\u00FAng / Sai (Nh\u00F3m)" }), _jsx("p", { className: "text-sm text-gray-500", children: "Th\u00EAm danh s\u00E1ch c\u00E1c c\u00E2u h\u1ECFi True/False trong 1 nh\u00F3m" })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setIsClientMode((prev) => !prev), className: "gap-2", children: [isClientMode ? _jsx(Pencil, { className: "h-4 w-4" }) : _jsx(Eye, { className: "h-4 w-4" }), isClientMode ? 'Chế độ chỉnh sửa' : 'Xem trước học sinh'] })] }), displayErrors.length > 0 && (_jsx("div", { className: "rounded-lg bg-red-50 p-4 border border-red-200", children: _jsx("div", { className: "flex", children: _jsxs("div", { className: "ml-3", children: [_jsx("h3", { className: "text-sm font-medium text-red-800", children: "C\u1EA7n s\u1EEDa c\u00E1c l\u1ED7i sau:" }), _jsx("div", { className: "mt-2 text-sm text-red-700", children: _jsx("ul", { className: "list-disc space-y-1 pl-5", children: displayErrors.map((err, i) => (_jsx("li", { children: err }, i))) }) })] }) }) })), isClientMode ? (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base font-semibold", children: "Xem tr\u01B0\u1EDBc hi\u1EC3n th\u1ECB h\u1ECDc sinh" }) }), _jsx(CardContent, { children: _jsx(TrueFalseGroupClient, { questionData: currentPayload }) })] })) : (_jsxs("div", { className: "space-y-6", children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base font-semibold", children: "H\u01B0\u1EDBng d\u1EABn chung (Instruction)" }) }), _jsx(CardContent, { className: "space-y-4", children: _jsxs("div", { children: [_jsx(Label, { htmlFor: "instruction", children: "H\u01B0\u1EDBng d\u1EABn b\u00E0i l\u00E0m" }), _jsx(Input, { id: "instruction", value: instruction, onChange: (e) => setInstruction(e.target.value), placeholder: "VD: Tick True or False.", className: "mt-1" })] }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs(CardTitle, { className: "text-base font-semibold", children: ["Danh s\u00E1ch c\u00E2u h\u1ECFi (", items.length, ")"] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addItem, className: "gap-2", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm c\u00E2u h\u1ECFi"] })] }), _jsx(CardContent, { className: "space-y-4", children: items.map((item, index) => (_jsxs("div", { className: "space-y-4 rounded-lg border border-gray-200 bg-gray-50/50 p-4 transition-colors hover:border-gray-300", children: [_jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full bg-blue-100 text-xs font-semibold text-blue-700", children: item.questionNumber || index + 1 }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u h\u1ECFi ", index + 1] })] }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: `example-${index}`, checked: item.isExample || false, onCheckedChange: (checked) => updateItem(index, {
124
- isExample: checked,
125
- points: checked ? 0 : 1,
126
- }) }), _jsxs(Label, { htmlFor: `example-${index}`, className: "flex items-center gap-1 text-xs cursor-pointer", children: [_jsx(Star, { className: "h-3 w-3 text-amber-500" }), "C\u00E2u m\u1EABu (Example)"] })] }), !item.isExample && (_jsx("div", { className: "w-24", children: _jsx(PointsInput, { value: item.points, allowZero: true, onChange: (event) => updateItem(index, {
127
- points: parsePointsValue(event.target.value, true),
128
- }) }) })), items.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeItem(index), className: "text-red-500 hover:bg-red-50 hover:text-red-600", children: _jsx(Trash2, { className: "h-4 w-4" }) }))] })] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: `question-${index}`, children: "N\u1ED9i dung c\u00E2u h\u1ECFi" }), _jsx(Textarea, { id: `question-${index}`, value: item.question, onChange: (e) => updateItem(index, { question: e.target.value }), placeholder: "Nh\u1EADp n\u1ED9i dung c\u00E2u h\u1ECFi...", className: "mt-1 bg-white", rows: 2 })] }), _jsxs("div", { children: [_jsx(Label, { className: "mb-2 block text-xs font-medium text-gray-700", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsx(Button, { type: "button", variant: item.correctAnswer === true ? 'default' : 'outline', size: "sm", onClick: () => updateItem(index, { correctAnswer: true }), className: "w-24", children: "True" }), _jsx(Button, { type: "button", variant: item.correctAnswer === false ? 'default' : 'outline', size: "sm", onClick: () => updateItem(index, { correctAnswer: false }), className: "w-24", children: "False" })] })] })] }, index))) })] }), _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 }) })] })] }))] }));
124
+ if (isClientMode) {
125
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(TrueFalseGroupClient, { questionData: currentPayload })] }));
126
+ }
127
+ return (_jsxs("div", { className: "space-y-3", children: [displayErrors.length > 0 && (_jsx("div", { className: "rounded-md border border-red-200 bg-red-50 p-2 text-xs text-red-700", children: _jsx("ul", { className: "list-disc space-y-1 pl-4", children: displayErrors.map((err, i) => (_jsx("li", { children: err }, i))) }) })), _jsx(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(HelpCircle, { className: "h-3.5 w-3.5" }), title: "\u0110\u00FAng / Sai (Nh\u00F3m)", preview: { onClick: () => setIsClientMode(true) } }), children: _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "instruction", className: "text-sm font-semibold text-gray-800", children: "H\u01B0\u1EDBng d\u1EABn b\u00E0i l\u00E0m" }), _jsx(Input, { id: "instruction", value: instruction, onChange: (e) => setInstruction(e.target.value), placeholder: "VD: Tick True or False.", 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 h\u1ECFi (", 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 h\u1ECFi"] })] }) }), _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("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full bg-blue-100 text-xs font-semibold text-blue-700", children: item.questionNumber || index + 1 }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u h\u1ECFi ", index + 1] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: `example-${index}`, checked: item.isExample || false, onCheckedChange: (checked) => updateItem(index, {
128
+ isExample: checked,
129
+ points: checked ? 0 : 1,
130
+ }) }), _jsxs(Label, { htmlFor: `example-${index}`, className: "flex cursor-pointer items-center gap-1 text-xs", children: [_jsx(Star, { className: "h-3 w-3 text-amber-500" }), "C\u00E2u m\u1EABu (Example)"] })] }), !item.isExample && (_jsx("div", { className: "w-24", children: _jsx(PointsInput, { value: item.points, allowZero: true, onChange: (event) => updateItem(index, {
131
+ points: parsePointsValue(event.target.value, true),
132
+ }) }) })), 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 hover:text-red-600", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] })] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: `question-${index}`, children: "N\u1ED9i dung c\u00E2u h\u1ECFi" }), _jsx(Textarea, { id: `question-${index}`, value: item.question, onChange: (e) => updateItem(index, { question: e.target.value }), placeholder: "Nh\u1EADp n\u1ED9i dung c\u00E2u h\u1ECFi...", className: "mt-1 min-h-[56px] border-gray-200 bg-white", rows: 2 })] }), _jsxs("div", { children: [_jsx(Label, { className: "mb-1.5 block text-xs font-medium text-gray-700", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { type: "button", variant: item.correctAnswer === true ? 'default' : 'outline', size: "sm", onClick: () => updateItem(index, { correctAnswer: true }), className: "h-8 w-24", children: "True" }), _jsx(Button, { type: "button", variant: item.correctAnswer === false ? 'default' : 'outline', size: "sm", onClick: () => updateItem(index, { correctAnswer: false }), className: "h-8 w-24", children: "False" })] })] })] }, index))), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(initialData?.explanation) })] })] })] }));
129
133
  }