@tinyweb_dev/oe-exam-sdk 0.1.1 → 0.1.2

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 (485) hide show
  1. package/dist/api/exam-create.js +1 -179
  2. package/dist/api/exam-create.types.js +1 -1
  3. package/dist/api/exam-questions.js +1 -98
  4. package/dist/api/exam-questions.types.js +1 -1
  5. package/dist/api/exam-taking.js +1 -73
  6. package/dist/api/exam-taking.types.js +1 -1
  7. package/dist/api/index.js +1 -6
  8. package/dist/api/result-review.js +1 -50
  9. package/dist/api/result-review.types.js +1 -1
  10. package/dist/components/CueCardEditor.js +1 -58
  11. package/dist/components/common/ResolvedImage.js +1 -15
  12. package/dist/components/exams/CreateExamInfoForm.js +1 -35
  13. package/dist/components/exams/CreateExamPageContainer.js +1 -103
  14. package/dist/components/exams/ExamCreator.js +1 -89
  15. package/dist/components/exams/ExamPartTabs.js +1 -40
  16. package/dist/components/exams/ExamQuestionGrid.js +1 -18
  17. package/dist/components/exams/ExamQuestionsPageContainer.js +1 -90
  18. package/dist/components/exams/PartAudioUploader.js +1 -9
  19. package/dist/components/exams/TemplateSelector.js +1 -66
  20. package/dist/components/exams/api.js +1 -3
  21. package/dist/components/exams/create-exam.validation.js +1 -18
  22. package/dist/components/exams/exam-template.utils.js +1 -18
  23. package/dist/components/exams/index.js +1 -10
  24. package/dist/components/exams/take/ExamTakingPageContainer.js +1 -153
  25. package/dist/components/exams/take/GenericQuestionRenderer.js +1 -14
  26. package/dist/components/exams/take/exam-taking.utils.js +1 -88
  27. package/dist/components/exams/take/hooks/useSpeakingRecorder.js +1 -24
  28. package/dist/components/exams/take/index.js +1 -2
  29. package/dist/components/exams/take/types.js +1 -1
  30. package/dist/components/index.js +1 -42
  31. package/dist/components/questions/_shared/config/question-types.config.js +1 -333
  32. package/dist/components/questions/_shared/constants/color-palette.js +1 -21
  33. package/dist/components/questions/_shared/hooks/useAnswerGroupQuestionGroup.js +1 -105
  34. package/dist/components/questions/_shared/hooks/useBasicQuestionGroup.js +1 -222
  35. package/dist/components/questions/_shared/hooks/useCompareImagesGroup.js +1 -140
  36. package/dist/components/questions/_shared/hooks/useImageQuestionGroup.js +1 -123
  37. package/dist/components/questions/_shared/hooks/useInfoExchangeGroup.js +1 -50
  38. package/dist/components/questions/_shared/hooks/useQuestionFormState.js +1 -54
  39. package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +1 -123
  40. package/dist/components/questions/_shared/hooks/useQuestionListGroup.js +1 -121
  41. package/dist/components/questions/_shared/hooks/useQuestionOnChange.js +1 -70
  42. package/dist/components/questions/_shared/hooks/useSceneQuestionGroup.js +1 -194
  43. package/dist/components/questions/_shared/index.js +1 -7
  44. package/dist/components/questions/_shared/types/answer-the-question.type.js +1 -1
  45. package/dist/components/questions/_shared/types/arrange-letters-into-words.type.js +1 -1
  46. package/dist/components/questions/_shared/types/choose-correct-adjective.type.js +1 -3
  47. package/dist/components/questions/_shared/types/color-region.config.js +1 -1
  48. package/dist/components/questions/_shared/types/fill-in-blank.type.js +1 -1
  49. package/dist/components/questions/_shared/types/fill-missing-words-in-grid.type.js +1 -1
  50. package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +1 -1
  51. package/dist/components/questions/_shared/types/index.js +1 -12
  52. package/dist/components/questions/_shared/types/label-the-picture.type.js +1 -1
  53. package/dist/components/questions/_shared/types/listen-and-choose-from-answer-group.type.js +1 -1
  54. package/dist/components/questions/_shared/types/listen-and-choose-objects-in-scene.type.js +1 -1
  55. package/dist/components/questions/_shared/types/listen-and-drag-objects-into-scene.type.js +1 -1
  56. package/dist/components/questions/_shared/types/listen-and-speak-answer.type.js +1 -1
  57. package/dist/components/questions/_shared/types/listen-and-speak-compare-images.type.js +1 -1
  58. package/dist/components/questions/_shared/types/listen-and-speak-image-group.type.js +1 -1
  59. package/dist/components/questions/_shared/types/listen-and-speak-info-exchange.type.js +1 -1
  60. package/dist/components/questions/_shared/types/listen-and-speak-odd-one-out.type.js +1 -3
  61. package/dist/components/questions/_shared/types/listen-and-speak-question-list.type.js +1 -1
  62. package/dist/components/questions/_shared/types/listen-and-speak-with-story-images.type.js +1 -3
  63. package/dist/components/questions/_shared/types/listen-and-tick-answer.type.js +1 -1
  64. package/dist/components/questions/_shared/types/listening-drawline.type.js +1 -1
  65. package/dist/components/questions/_shared/types/look-and-choose-answer.type.js +1 -1
  66. package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +1 -1
  67. package/dist/components/questions/_shared/types/match-by-writing-answer.type.js +1 -1
  68. package/dist/components/questions/_shared/types/multiple-choice.type.js +1 -1
  69. package/dist/components/questions/_shared/types/question-group.type.js +1 -11
  70. package/dist/components/questions/_shared/types/read-and-color-objects.type.js +1 -1
  71. package/dist/components/questions/_shared/types/read-displayed-content.type.js +1 -1
  72. package/dist/components/questions/_shared/types/read-passage-and-answer-questions.type.js +1 -1
  73. package/dist/components/questions/_shared/types/speaking-describe-image.type.js +1 -1
  74. package/dist/components/questions/_shared/types/spontaneous-qa.type.js +1 -1
  75. package/dist/components/questions/_shared/types/word-fill-paragraph-with-options.type.js +1 -1
  76. package/dist/components/questions/_shared/types/word-fill-paragraph.type.js +1 -1
  77. package/dist/components/questions/_shared/types/word-fill-structured-form.type.js +1 -1
  78. package/dist/components/questions/_shared/types/word-ordering.type.js +1 -1
  79. package/dist/components/questions/_shared/types/write-a-short-letter.type.js +1 -1
  80. package/dist/components/questions/_shared/types/write-correct-verb-form.type.js +1 -3
  81. package/dist/components/questions/_shared/types/write-sentences.type.js +1 -1
  82. package/dist/components/questions/_shared/types/write-short-paragraph.type.js +1 -1
  83. package/dist/components/questions/_shared/utils/answer-builder.js +1 -79
  84. package/dist/components/questions/_shared/utils/createColorRegionRegistration.js +1 -39
  85. package/dist/components/questions/_shared/utils/id-generator.js +1 -3
  86. package/dist/components/questions/_shared/utils/question-validation.js +1 -21
  87. package/dist/components/questions/components/ColorNode.js +1 -85
  88. package/dist/components/questions/components/ColorRegionClient.js +1 -545
  89. package/dist/components/questions/components/ColorRegionCreator.js +1 -420
  90. package/dist/components/questions/components/ColorRegionNode.js +1 -59
  91. package/dist/components/questions/components/CreatorGuide.js +1 -103
  92. package/dist/components/questions/components/NameNode.js +1 -26
  93. package/dist/components/questions/components/QuestionBankOption.js +1 -46
  94. package/dist/components/questions/components/QuestionBasicInfoForm.js +1 -9
  95. package/dist/components/questions/components/QuestionCommonFields.js +1 -33
  96. package/dist/components/questions/components/QuestionSearchDropdown.js +1 -258
  97. package/dist/components/questions/components/QuestionTypeSelector.js +1 -32
  98. package/dist/components/questions/components/QuestionTypeSpecificForms.js +1 -6
  99. package/dist/components/questions/components/RegionNode.js +1 -94
  100. package/dist/components/questions/components/index.js +1 -14
  101. package/dist/components/questions/creator/QuestionCreator.js +1 -90
  102. package/dist/components/questions/creator/QuestionGroupCreator.js +1 -119
  103. package/dist/components/questions/creator/dedicated-component-router.js +1 -79
  104. package/dist/components/questions/creator/index.js +1 -3
  105. package/dist/components/questions/creator/question-type-registry.js +1 -80
  106. package/dist/components/questions/groups/ArticlesGroupCard.js +1 -121
  107. package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -50
  108. package/dist/components/questions/groups/DocumentGroupCard.js +1 -114
  109. package/dist/components/questions/groups/MatchAnswerGroupCard.js +1 -25
  110. package/dist/components/questions/index.js +1 -10
  111. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +1 -37
  112. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +1 -351
  113. package/dist/components/questions/types/answer-the-question/register.js +1 -67
  114. package/dist/components/questions/types/answer-the-question/transform.js +1 -36
  115. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +1 -141
  116. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +1 -118
  117. package/dist/components/questions/types/arrange-letters-into-words/index.js +1 -2
  118. package/dist/components/questions/types/arrange-letters-into-words/register.js +1 -4
  119. package/dist/components/questions/types/arrange-letters-into-words/transform.js +1 -24
  120. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -64
  121. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +1 -169
  122. package/dist/components/questions/types/choose-correct-adjective/index.js +1 -2
  123. package/dist/components/questions/types/choose-correct-adjective/register.js +1 -55
  124. package/dist/components/questions/types/choose-correct-adjective/transform.js +1 -16
  125. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +1 -131
  126. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +1 -581
  127. package/dist/components/questions/types/choose-the-correct-answer/index.js +1 -2
  128. package/dist/components/questions/types/choose-the-correct-answer/register.js +1 -101
  129. package/dist/components/questions/types/choose-the-correct-answer/transform.js +1 -43
  130. package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +1 -275
  131. package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +1 -448
  132. package/dist/components/questions/types/fill-in-blank/index.js +1 -2
  133. package/dist/components/questions/types/fill-in-blank/register.js +1 -138
  134. package/dist/components/questions/types/fill-in-blank/transform.js +1 -59
  135. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +1 -117
  136. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +1 -671
  137. package/dist/components/questions/types/fill-missing-words-in-grid/index.js +1 -2
  138. package/dist/components/questions/types/fill-missing-words-in-grid/register.js +1 -37
  139. package/dist/components/questions/types/fill-missing-words-in-grid/transform.js +1 -15
  140. package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +1 -262
  141. package/dist/components/questions/types/gn-speaking-interview/register.js +1 -121
  142. package/dist/components/questions/types/gn-speaking-interview/transform.js +1 -48
  143. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingClient.js +1 -16
  144. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingCreator.js +1 -27
  145. package/dist/components/questions/types/image-object-matching/index.js +1 -2
  146. package/dist/components/questions/types/image-object-matching/register.js +1 -3
  147. package/dist/components/questions/types/image-object-matching/transform.js +1 -2
  148. package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +1 -102
  149. package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +1 -344
  150. package/dist/components/questions/types/label-the-picture/index.js +1 -2
  151. package/dist/components/questions/types/label-the-picture/register.js +1 -54
  152. package/dist/components/questions/types/label-the-picture/transform.js +1 -28
  153. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +1 -78
  154. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +1 -232
  155. package/dist/components/questions/types/listen-and-choose-from-answer-group/index.js +1 -2
  156. package/dist/components/questions/types/listen-and-choose-from-answer-group/register.js +1 -82
  157. package/dist/components/questions/types/listen-and-choose-from-answer-group/transform.js +1 -27
  158. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +1 -70
  159. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +1 -386
  160. package/dist/components/questions/types/listen-and-choose-objects-in-scene/index.js +1 -2
  161. package/dist/components/questions/types/listen-and-choose-objects-in-scene/register.js +1 -100
  162. package/dist/components/questions/types/listen-and-choose-objects-in-scene/transform.js +1 -33
  163. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +1 -106
  164. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +1 -457
  165. package/dist/components/questions/types/listen-and-drag-objects-into-scene/index.js +1 -2
  166. package/dist/components/questions/types/listen-and-drag-objects-into-scene/register.js +1 -110
  167. package/dist/components/questions/types/listen-and-drag-objects-into-scene/transform.js +1 -35
  168. package/dist/components/questions/types/listen-and-speak-answer/ArrowIndicator.js +1 -8
  169. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -22
  170. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +1 -291
  171. package/dist/components/questions/types/listen-and-speak-answer/index.js +1 -2
  172. package/dist/components/questions/types/listen-and-speak-answer/register.js +1 -84
  173. package/dist/components/questions/types/listen-and-speak-answer/transform.js +1 -27
  174. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -32
  175. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +1 -295
  176. package/dist/components/questions/types/listen-and-speak-compare-images/register.js +1 -79
  177. package/dist/components/questions/types/listen-and-speak-compare-images/transform.js +1 -27
  178. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -34
  179. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +1 -285
  180. package/dist/components/questions/types/listen-and-speak-image-group/register.js +1 -81
  181. package/dist/components/questions/types/listen-and-speak-image-group/transform.js +1 -26
  182. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -38
  183. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +1 -385
  184. package/dist/components/questions/types/listen-and-speak-info-exchange/register.js +1 -87
  185. package/dist/components/questions/types/listen-and-speak-info-exchange/transform.js +1 -32
  186. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -26
  187. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +1 -247
  188. package/dist/components/questions/types/listen-and-speak-odd-one-out/register.js +1 -59
  189. package/dist/components/questions/types/listen-and-speak-odd-one-out/transform.js +1 -18
  190. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -26
  191. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +1 -256
  192. package/dist/components/questions/types/listen-and-speak-question-list/register.js +1 -81
  193. package/dist/components/questions/types/listen-and-speak-question-list/transform.js +1 -26
  194. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -26
  195. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +1 -230
  196. package/dist/components/questions/types/listen-and-speak-with-story-images/register.js +1 -56
  197. package/dist/components/questions/types/listen-and-speak-with-story-images/transform.js +1 -16
  198. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +1 -72
  199. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +1 -230
  200. package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +1 -1
  201. package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +1 -66
  202. package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +1 -42
  203. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +1 -96
  204. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +1 -238
  205. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +1 -2
  206. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +1 -76
  207. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +1 -19
  208. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +1 -134
  209. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +1 -617
  210. package/dist/components/questions/types/look-picture-fill-word-hint/index.js +1 -2
  211. package/dist/components/questions/types/look-picture-fill-word-hint/register.js +1 -82
  212. package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +1 -29
  213. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +1 -38
  214. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +1 -359
  215. package/dist/components/questions/types/match-by-writing-answer/index.js +1 -3
  216. package/dist/components/questions/types/match-by-writing-answer/register.js +1 -58
  217. package/dist/components/questions/types/match-by-writing-answer/transform.js +1 -61
  218. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsClient.js +1 -16
  219. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsCreator.js +1 -23
  220. package/dist/components/questions/types/read-and-color-objects/index.js +1 -2
  221. package/dist/components/questions/types/read-and-color-objects/register.js +1 -3
  222. package/dist/components/questions/types/read-and-color-objects/transform.js +1 -36
  223. package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +1 -125
  224. package/dist/components/questions/types/read-displayed-content/register.js +1 -64
  225. package/dist/components/questions/types/read-displayed-content/transform.js +1 -12
  226. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -64
  227. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +1 -511
  228. package/dist/components/questions/types/read-passage-and-answer-questions/index.js +1 -2
  229. package/dist/components/questions/types/read-passage-and-answer-questions/register.js +1 -58
  230. package/dist/components/questions/types/read-passage-and-answer-questions/transform.js +1 -28
  231. package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +1 -373
  232. package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +1 -41
  233. package/dist/components/questions/types/speaking-conversation/register.js +1 -110
  234. package/dist/components/questions/types/speaking-conversation/transform.js +1 -34
  235. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +1 -74
  236. package/dist/components/questions/types/speaking-cue-card/register.js +1 -52
  237. package/dist/components/questions/types/speaking-cue-card/transform.js +1 -12
  238. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -10
  239. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +1 -161
  240. package/dist/components/questions/types/speaking-describe-image/index.js +1 -2
  241. package/dist/components/questions/types/speaking-describe-image/register.js +1 -69
  242. package/dist/components/questions/types/speaking-describe-image/transform.js +1 -16
  243. package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +1 -234
  244. package/dist/components/questions/types/spontaneous-qa/register.js +1 -93
  245. package/dist/components/questions/types/spontaneous-qa/transform.js +1 -34
  246. package/dist/components/questions/types/true-false/TrueFalseClient.js +1 -75
  247. package/dist/components/questions/types/true-false/TrueFalseCreator.js +1 -244
  248. package/dist/components/questions/types/true-false/index.js +1 -2
  249. package/dist/components/questions/types/true-false/register.js +1 -77
  250. package/dist/components/questions/types/true-false/transform.js +1 -32
  251. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +1 -233
  252. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +1 -631
  253. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphWrapper.js +1 -28
  254. package/dist/components/questions/types/word-fill-paragraph/index.js +1 -2
  255. package/dist/components/questions/types/word-fill-paragraph/register.js +1 -136
  256. package/dist/components/questions/types/word-fill-paragraph/transform.js +1 -56
  257. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -138
  258. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +1 -261
  259. package/dist/components/questions/types/word-fill-structured-form/index.js +1 -2
  260. package/dist/components/questions/types/word-fill-structured-form/register.js +1 -67
  261. package/dist/components/questions/types/word-fill-structured-form/transform.js +1 -40
  262. package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -84
  263. package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +1 -192
  264. package/dist/components/questions/types/word-ordering/index.js +1 -2
  265. package/dist/components/questions/types/word-ordering/register.js +1 -46
  266. package/dist/components/questions/types/word-ordering/transform.js +1 -24
  267. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +1 -98
  268. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +1 -320
  269. package/dist/components/questions/types/write-a-short-letter/index.js +1 -2
  270. package/dist/components/questions/types/write-a-short-letter/register.js +1 -68
  271. package/dist/components/questions/types/write-a-short-letter/transform.js +1 -25
  272. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +1 -61
  273. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +1 -196
  274. package/dist/components/questions/types/write-correct-verb-form/index.js +1 -2
  275. package/dist/components/questions/types/write-correct-verb-form/register.js +1 -62
  276. package/dist/components/questions/types/write-correct-verb-form/transform.js +1 -17
  277. package/dist/components/questions/types/write-sentences/WriteSentencesClient.js +1 -22
  278. package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +1 -174
  279. package/dist/components/questions/types/write-sentences/index.js +1 -2
  280. package/dist/components/questions/types/write-sentences/register.js +1 -58
  281. package/dist/components/questions/types/write-sentences/transform.js +1 -28
  282. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -96
  283. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +1 -161
  284. package/dist/components/questions/types/write-short-paragraph/index.js +1 -2
  285. package/dist/components/questions/types/write-short-paragraph/register.js +1 -4
  286. package/dist/components/questions/types/write-short-paragraph/transform.js +1 -21
  287. package/dist/components/questions/viewer/QuestionGroupViewer.js +1 -36
  288. package/dist/components/questions/viewer/QuestionViewer.js +1 -446
  289. package/dist/components/questions/viewer/index.js +1 -2
  290. package/dist/components/results/ResultReviewPageContainer.js +1 -98
  291. package/dist/components/results/ReviewQuestionRenderer.js +1 -72
  292. package/dist/components/results/index.js +1 -2
  293. package/dist/components/results/result-review.utils.js +1 -6
  294. package/dist/components/results/review-data.js +1 -150
  295. package/dist/components/results/review-renderer.utils.js +1 -97
  296. package/dist/components/results/review-types.js +1 -1
  297. package/dist/components/themes/cambridge-yle/CambridgeYleAudioPlayer.js +1 -138
  298. package/dist/components/themes/cambridge-yle/CambridgeYleBottomNav.js +1 -12
  299. package/dist/components/themes/cambridge-yle/CambridgeYleExamLayout.js +1 -18
  300. package/dist/components/themes/cambridge-yle/CambridgeYleExampleText.js +1 -11
  301. package/dist/components/themes/cambridge-yle/CambridgeYleHeader.js +1 -11
  302. package/dist/components/themes/cambridge-yle/CambridgeYleInstructionBanner.js +1 -9
  303. package/dist/components/themes/cambridge-yle/CambridgeYleNavButton.js +1 -13
  304. package/dist/components/themes/cambridge-yle/CambridgeYlePartBanner.js +1 -8
  305. package/dist/components/themes/cambridge-yle/CambridgeYleProgressBar.js +1 -6
  306. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCard.js +1 -5
  307. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCardFullWidth.js +1 -9
  308. package/dist/components/themes/cambridge-yle/CambridgeYleSidebar.js +1 -41
  309. package/dist/components/themes/cambridge-yle/CambridgeYleTimer.js +1 -25
  310. package/dist/components/themes/cambridge-yle/index.js +1 -13
  311. package/dist/components/themes/english-certification/EcAnswerTheQuestion.js +1 -24
  312. package/dist/components/themes/english-certification/EcMatchByWritingAnswer.js +1 -88
  313. package/dist/components/themes/english-certification/EcWordFillParagraph.js +1 -55
  314. package/dist/components/themes/english-certification/EcWordFillStructuredForm.js +1 -156
  315. package/dist/components/themes/english-certification/EcWritingTask.js +1 -34
  316. package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.js +1 -114
  317. package/dist/components/themes/english-certification/EnglishCertificationBrand.js +1 -11
  318. package/dist/components/themes/english-certification/EnglishCertificationExamLayout.js +1 -21
  319. package/dist/components/themes/english-certification/EnglishCertificationFlagButton.js +1 -15
  320. package/dist/components/themes/english-certification/EnglishCertificationFooterBar.js +1 -19
  321. package/dist/components/themes/english-certification/EnglishCertificationGroupedQuestion.js +1 -148
  322. package/dist/components/themes/english-certification/EnglishCertificationHeader.js +1 -31
  323. package/dist/components/themes/english-certification/EnglishCertificationNavigationPanel.js +1 -71
  324. package/dist/components/themes/english-certification/EnglishCertificationOptionSelector.js +1 -33
  325. package/dist/components/themes/english-certification/EnglishCertificationPassagePanel.js +1 -16
  326. package/dist/components/themes/english-certification/EnglishCertificationQuestionCard.js +1 -22
  327. package/dist/components/themes/english-certification/EnglishCertificationQuestionItem.js +1 -37
  328. package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.js +1 -72
  329. package/dist/components/themes/english-certification/EnglishCertificationReadingSection.js +1 -90
  330. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestion.js +1 -18
  331. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestionList.js +1 -29
  332. package/dist/components/themes/english-certification/english-certification-theme.js +1 -58
  333. package/dist/components/themes/english-certification/grouped-question-utils.js +1 -218
  334. package/dist/components/themes/english-certification/index.js +1 -21
  335. package/dist/components/themes/index.js +1 -6
  336. package/dist/components/themes/summer-sky/SummerSkyAudioPlayer.js +1 -170
  337. package/dist/components/themes/summer-sky/SummerSkyLayout.js +1 -221
  338. package/dist/components/themes/summer-sky/SummerSkySubmitModal.js +1 -18
  339. package/dist/components/themes/summer-sky/SummerSkyWaitingRoom.js +1 -194
  340. package/dist/components/themes/types.js +1 -1
  341. package/dist/components/ui/alert-dialog.js +1 -26
  342. package/dist/components/ui/alert.js +1 -22
  343. package/dist/components/ui/avatar.js +1 -12
  344. package/dist/components/ui/badge.js +1 -38
  345. package/dist/components/ui/button.js +1 -35
  346. package/dist/components/ui/calendar.js +1 -76
  347. package/dist/components/ui/card.js +1 -16
  348. package/dist/components/ui/carousel.js +1 -91
  349. package/dist/components/ui/checkbox.js +1 -9
  350. package/dist/components/ui/collapsible.js +1 -13
  351. package/dist/components/ui/command.js +1 -34
  352. package/dist/components/ui/confirm-dialog.js +1 -49
  353. package/dist/components/ui/dialog.js +1 -23
  354. package/dist/components/ui/drawer.js +1 -35
  355. package/dist/components/ui/dropdown-menu.js +1 -33
  356. package/dist/components/ui/file-upload.js +1 -155
  357. package/dist/components/ui/form.js +1 -62
  358. package/dist/components/ui/hover-card.js +1 -10
  359. package/dist/components/ui/image-preview.js +1 -29
  360. package/dist/components/ui/input.js +1 -8
  361. package/dist/components/ui/label.js +1 -9
  362. package/dist/components/ui/language-switcher.js +1 -15
  363. package/dist/components/ui/multi-select.js +1 -90
  364. package/dist/components/ui/points-input.js +1 -33
  365. package/dist/components/ui/popover.js +1 -26
  366. package/dist/components/ui/progress.js +1 -16
  367. package/dist/components/ui/radio-group.js +1 -12
  368. package/dist/components/ui/scroll-area.js +1 -11
  369. package/dist/components/ui/select.js +1 -38
  370. package/dist/components/ui/separator.js +1 -8
  371. package/dist/components/ui/sheet.js +1 -40
  372. package/dist/components/ui/single-select.js +1 -76
  373. package/dist/components/ui/skeleton.js +1 -6
  374. package/dist/components/ui/switch.js +1 -8
  375. package/dist/components/ui/table.js +1 -20
  376. package/dist/components/ui/tabs.js +1 -17
  377. package/dist/components/ui/textarea.js +1 -8
  378. package/dist/components/ui/tooltip.js +1 -11
  379. package/dist/index.js +1 -19
  380. package/dist/mcp/config.js +1 -32
  381. package/dist/mcp/http-client.js +1 -48
  382. package/dist/mcp/index.js +1 -29
  383. package/dist/mcp/tools/_helpers.js +1 -25
  384. package/dist/mcp/tools/exams.js +1 -439
  385. package/dist/mcp/tools/index.js +1 -6
  386. package/dist/mcp/tools/results.js +1 -19
  387. package/dist/shared/constants/ApiConstant.js +1 -276
  388. package/dist/shared/constants/QueryKeyConstant.js +1 -112
  389. package/dist/shared/constants/exam-level.enum.js +1 -41
  390. package/dist/shared/constants/landings/placement-test.constants.js +1 -431
  391. package/dist/shared/constants/question-skills.js +1 -267
  392. package/dist/shared/constants/routes.js +1 -81
  393. package/dist/shared/index.js +1 -7
  394. package/dist/shared/lib/hooks/index.js +1 -10
  395. package/dist/shared/lib/hooks/useAlertDialog.js +1 -57
  396. package/dist/shared/lib/hooks/useAudioPlayCounter.js +1 -30
  397. package/dist/shared/lib/hooks/useAudioPreview.js +1 -81
  398. package/dist/shared/lib/hooks/useAudioUpload.js +1 -24
  399. package/dist/shared/lib/hooks/useConfirmDialog.js +1 -89
  400. package/dist/shared/lib/hooks/useDebouncedCallback.js +1 -32
  401. package/dist/shared/lib/hooks/useDelayedAutoPlay.js +1 -12
  402. package/dist/shared/lib/hooks/useFileUpload.js +1 -24
  403. package/dist/shared/lib/hooks/useImageUrl.js +1 -5
  404. package/dist/shared/lib/hooks/usePresignedFileUrl.js +1 -10
  405. package/dist/shared/lib/hooks/useReactHookForm.js +1 -31
  406. package/dist/shared/lib/hooks/useToast.js +1 -62
  407. package/dist/shared/lib/i18n/index.js +1 -54
  408. package/dist/shared/lib/i18n/messages/en/admin.js +1 -2104
  409. package/dist/shared/lib/i18n/messages/en/auth.js +1 -86
  410. package/dist/shared/lib/i18n/messages/en/common.js +1 -140
  411. package/dist/shared/lib/i18n/messages/en/file-upload.js +1 -18
  412. package/dist/shared/lib/i18n/messages/en/student.js +1 -133
  413. package/dist/shared/lib/i18n/messages/en/terms.js +1 -37
  414. package/dist/shared/lib/i18n/messages/en.js +1 -15
  415. package/dist/shared/lib/i18n/messages/vi/admin.js +1 -2104
  416. package/dist/shared/lib/i18n/messages/vi/auth.js +1 -86
  417. package/dist/shared/lib/i18n/messages/vi/common.js +1 -140
  418. package/dist/shared/lib/i18n/messages/vi/file-upload.js +1 -18
  419. package/dist/shared/lib/i18n/messages/vi/student.js +1 -133
  420. package/dist/shared/lib/i18n/messages/vi/terms.js +1 -37
  421. package/dist/shared/lib/i18n/messages/vi.js +1 -15
  422. package/dist/shared/lib/i18n/types.js +1 -1
  423. package/dist/shared/lib/stores/examQuestionStore.js +1 -212
  424. package/dist/shared/lib/stores/localeStore.js +1 -10
  425. package/dist/shared/lib/utils/public-page.js +1 -44
  426. package/dist/shared/lib/utils/question-transform-types.js +1 -1
  427. package/dist/shared/lib/utils/question-transform.js +1 -174
  428. package/dist/shared/lib/utils.js +1 -168
  429. package/dist/shared/lib/validation/choose-correct-answer.validation.js +1 -111
  430. package/dist/shared/lib/validation/label-the-picture.validation.js +1 -19
  431. package/dist/shared/lib/validation/listen-and-choose-from-answer-group.validation.js +1 -46
  432. package/dist/shared/lib/validation/listen-and-choose-objects-in-scene.validation.js +1 -21
  433. package/dist/shared/lib/validation/listen-and-drag-objects-into-scene.validation.js +1 -34
  434. package/dist/shared/lib/validation/listen-and-speak-answer.validation.js +1 -40
  435. package/dist/shared/lib/validation/listen-and-speak-compare-images.validation.js +1 -45
  436. package/dist/shared/lib/validation/listen-and-speak-image-group.validation.js +1 -19
  437. package/dist/shared/lib/validation/listen-and-speak-info-exchange.validation.js +1 -39
  438. package/dist/shared/lib/validation/listen-and-speak-odd-one-out.validation.js +1 -32
  439. package/dist/shared/lib/validation/listen-and-speak-question-list.validation.js +1 -19
  440. package/dist/shared/lib/validation/listen-and-speak-with-story-images.validation.js +1 -28
  441. package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +1 -12
  442. package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +1 -47
  443. package/dist/shared/lib/validation/read-and-color-objects.validation.js +1 -29
  444. package/dist/shared/lib/validation/read-displayed-content.validation.js +1 -28
  445. package/dist/shared/lib/validation/speaking-conversation.validation.js +1 -75
  446. package/dist/shared/lib/validation/speaking-describe-image.validation.js +1 -31
  447. package/dist/shared/lib/validation/spontaneous-qa.validation.js +1 -48
  448. package/dist/shared/lib/validation/word-fill-paragraph-default.validation.js +1 -38
  449. package/dist/shared/lib/validation/word-fill-paragraph-with-options.validation.js +1 -39
  450. package/dist/shared/lib/validation/word-fill-paragraph.validation.js +1 -43
  451. package/dist/shared/lib/validation/word-ordering.validation.js +1 -33
  452. package/dist/shared/types/branch.types.js +1 -6
  453. package/dist/shared/types/common.types.js +1 -50
  454. package/dist/shared/types/entities/exam-schedule.types.js +1 -5
  455. package/dist/shared/types/entities/exam.types.js +1 -1
  456. package/dist/shared/types/entities/question.types.js +1 -4
  457. package/dist/shared/types/entities/result.types.js +1 -4
  458. package/dist/shared/types/entities/user.types.js +1 -4
  459. package/dist/shared/types/exam-taking.types.js +1 -2
  460. package/dist/shared/types/history.types.js +1 -34
  461. package/dist/shared/types/index.js +1 -1
  462. package/dist/shared/types/questions/choose-correct-adjective.js +1 -4
  463. package/dist/shared/types/questions/choose-the-correct-answer.js +1 -9
  464. package/dist/shared/types/questions/fill-missing-words-in-grid.js +1 -11
  465. package/dist/shared/types/questions/index.js +1 -40
  466. package/dist/shared/types/questions/listen-and-speak-answer.js +1 -1
  467. package/dist/shared/types/questions/listen-and-speak-compare-images.js +1 -1
  468. package/dist/shared/types/questions/listen-and-speak-image-group.js +1 -1
  469. package/dist/shared/types/questions/listen-and-speak-info-exchange.js +1 -3
  470. package/dist/shared/types/questions/listen-and-speak-odd-one-out.js +1 -3
  471. package/dist/shared/types/questions/listen-and-speak-question-list.js +1 -1
  472. package/dist/shared/types/questions/listen-and-speak-with-story-images.js +1 -3
  473. package/dist/shared/types/questions/look-picture-choose-correct-answer.js +1 -5
  474. package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +1 -9
  475. package/dist/shared/types/questions/look-picture-fill-word-hint.js +1 -7
  476. package/dist/shared/types/questions/read-displayed-content.js +1 -1
  477. package/dist/shared/types/questions/speaking-conversation.js +1 -7
  478. package/dist/shared/types/questions/speaking-describe-image.js +1 -1
  479. package/dist/shared/types/questions/spontaneous-qa.js +1 -1
  480. package/dist/shared/types/questions/write-correct-verb-form.js +1 -12
  481. package/dist/shared/types/role.types.js +1 -1
  482. package/dist/shared/types/structure-management.js +1 -28
  483. package/dist/shared/types/student.types.js +1 -54
  484. package/dist/shared/types/teacher-dashboard-rooms.js +1 -2
  485. package/package.json +4 -2
@@ -1,16 +1 @@
1
- export const transformSpeakingDescribeImage = (question) => {
2
- const contentData = question.content;
3
- const answerData = question.answer;
4
- const expectedAnswers = answerData?.expectedAnswers || [];
5
- const apiContent = {
6
- question: contentData?.question || '',
7
- imageUrl: contentData?.imageUrl || answerData?.imageUrl || '',
8
- };
9
- const result = {
10
- apiContent,
11
- apiCorrectAnswer: {
12
- expectedAnswers: expectedAnswers,
13
- },
14
- };
15
- return result;
16
- };
1
+ export const transformSpeakingDescribeImage=e=>{const n=e.content,r=e.answer,t=r?.expectedAnswers||[];return{apiContent:{question:n?.question||"",imageUrl:n?.imageUrl||r?.imageUrl||""},apiCorrectAnswer:{expectedAnswers:t}}};
@@ -1,234 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useRef, useEffect, useState } from 'react';
4
- import { useParams } from 'next/navigation';
5
- import { Button } from '../../../../components/ui/button';
6
- import { Label } from '../../../../components/ui/label';
7
- import { Input } from '../../../../components/ui/input';
8
- import { Textarea } from '../../../../components/ui/textarea';
9
- import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
10
- import { useDebouncedCallback, usePresignedFileUrl } from '../../../../shared/lib/hooks';
11
- import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
12
- import { Mic, Type, Volume2, BookOpen, Info, Loader2, Layers } from 'lucide-react';
13
- import { toast } from 'sonner';
14
- import { FileUpload } from '../../../../components/ui/file-upload';
15
- import { CreatorGuide } from '../../components/CreatorGuide';
16
- import { spontaneousQAFormSchema, } from '../../../../shared/lib/validation/spontaneous-qa.validation';
17
- export function SpontaneousQACreator({ initialData, onSave, onChange, externalErrors, validationRef, }) {
18
- const apiData = initialData;
19
- const params = useParams();
20
- const examRouteId = params?.examId || params?.id; // Try to get examId from route
21
- // Backward compat: convert old sampleAnswer (string) / sampleAnswers to expectedAnswers (array)
22
- const initSampleAnswers = (() => {
23
- const newArr = apiData?.expectedAnswers || apiData?.correctAnswer?.expectedAnswers;
24
- if (Array.isArray(newArr) && newArr.length > 0)
25
- return newArr;
26
- const arr = apiData?.sampleAnswers || apiData?.content?.sampleAnswers;
27
- if (Array.isArray(arr) && arr.length > 0)
28
- return arr;
29
- const single = apiData?.sampleAnswer || apiData?.correctAnswer?.sampleAnswer;
30
- if (single)
31
- return [single];
32
- return [''];
33
- })();
34
- const form = useReactHookForm({
35
- defaultValues: {
36
- inputType: apiData?.inputType || apiData?.content?.inputType || 'TEXT',
37
- audioUrl: apiData?.audioUrl || apiData?.content?.audioUrl || '',
38
- questionText: apiData?.questionText || apiData?.content?.questionText || '',
39
- sampleAnswers: initSampleAnswers,
40
- explanation: apiData?.explanation || '',
41
- points: apiData?.points ?? 5,
42
- },
43
- zodSchema: spontaneousQAFormSchema,
44
- mode: 'onBlur',
45
- });
46
- const previousInitialDataRef = useRef(initialData);
47
- const isInitializingRef = useRef(false);
48
- const onChangeRef = useRef(onChange);
49
- const previousPayloadRef = useRef('');
50
- const [isGeneratingAudio, setIsGeneratingAudio] = useState(false);
51
- const [previewAudioUrl, setPreviewAudioUrl] = useState(null);
52
- const [lastGeneratedText, setLastGeneratedText] = useState(apiData?.questionText || apiData?.content?.questionText || '');
53
- useEffect(() => {
54
- onChangeRef.current = onChange;
55
- }, [onChange]);
56
- const debouncedOnChange = useDebouncedCallback((data) => {
57
- onChangeRef.current?.(data);
58
- }, 300);
59
- const watchedInputType = form.watch('inputType');
60
- const watchedAudioUrl = form.watch('audioUrl');
61
- const watchedQuestionText = form.watch('questionText');
62
- const watchedSampleAnswers = form.watch('sampleAnswers') || [''];
63
- const watchedExplanation = form.watch('explanation');
64
- const watchedPoints = form.watch('points');
65
- const { previewUrl: presignedAudioUrl } = usePresignedFileUrl(watchedAudioUrl || '');
66
- const displayAudioUrl = previewAudioUrl || presignedAudioUrl;
67
- // Expose validation ref for external trigger
68
- useEffect(() => {
69
- if (validationRef) {
70
- validationRef.current = {
71
- triggerValidation: async () => {
72
- return await form.trigger();
73
- },
74
- getFormData: () => ({
75
- inputType: watchedInputType,
76
- audioUrl: watchedAudioUrl,
77
- questionText: watchedQuestionText,
78
- sampleAnswers: watchedSampleAnswers,
79
- explanation: watchedExplanation,
80
- points: watchedPoints,
81
- }),
82
- };
83
- }
84
- }, [validationRef, form, watchedInputType, watchedAudioUrl, watchedQuestionText, watchedSampleAnswers, watchedExplanation, watchedPoints]);
85
- // Reset form when initialData changes externally
86
- useEffect(() => {
87
- const apiD = initialData;
88
- const resetSampleAnswers = (() => {
89
- const newArr = apiD?.expectedAnswers || apiD?.correctAnswer?.expectedAnswers;
90
- if (Array.isArray(newArr) && newArr.length > 0)
91
- return newArr;
92
- const arr = apiD?.sampleAnswers || apiD?.content?.sampleAnswers;
93
- if (Array.isArray(arr) && arr.length > 0)
94
- return arr;
95
- const single = apiD?.sampleAnswer || apiD?.correctAnswer?.sampleAnswer;
96
- if (single)
97
- return [single];
98
- return [''];
99
- })();
100
- const normalizedInitialData = {
101
- inputType: apiD?.inputType || apiD?.content?.inputType || 'TEXT',
102
- audioUrl: apiD?.audioUrl || apiD?.content?.audioUrl || '',
103
- questionText: apiD?.questionText || apiD?.content?.questionText || '',
104
- sampleAnswers: resetSampleAnswers,
105
- explanation: apiD?.explanation || '',
106
- points: apiD?.points ?? 5,
107
- };
108
- const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
109
- if (!initialDataChanged) {
110
- return;
111
- }
112
- previousInitialDataRef.current = initialData;
113
- const currentFormData = {
114
- inputType: watchedInputType,
115
- audioUrl: watchedAudioUrl,
116
- questionText: watchedQuestionText,
117
- sampleAnswers: watchedSampleAnswers,
118
- explanation: watchedExplanation,
119
- points: watchedPoints,
120
- };
121
- const isEchoFromOnChange = currentFormData.points === normalizedInitialData.points &&
122
- currentFormData.explanation === normalizedInitialData.explanation &&
123
- currentFormData.inputType === normalizedInitialData.inputType &&
124
- currentFormData.audioUrl === normalizedInitialData.audioUrl &&
125
- currentFormData.questionText === normalizedInitialData.questionText &&
126
- JSON.stringify(currentFormData.sampleAnswers) === JSON.stringify(normalizedInitialData.sampleAnswers);
127
- const wasJustSaved = initialData?.isCompleted === true;
128
- if (isEchoFromOnChange && wasJustSaved) {
129
- return;
130
- }
131
- const shouldReset = !isEchoFromOnChange;
132
- if (shouldReset) {
133
- isInitializingRef.current = true;
134
- form.reset(normalizedInitialData);
135
- setLastGeneratedText(normalizedInitialData.questionText);
136
- setTimeout(() => {
137
- isInitializingRef.current = false;
138
- }, 150);
139
- }
140
- }, [initialData, form, watchedInputType, watchedAudioUrl, watchedQuestionText, watchedSampleAnswers, watchedExplanation, watchedPoints]);
141
- // Debounced onChange
142
- useEffect(() => {
143
- if (isInitializingRef.current) {
144
- const currentPayload = JSON.stringify({
145
- inputType: watchedInputType,
146
- audioUrl: watchedAudioUrl,
147
- questionText: watchedQuestionText,
148
- sampleAnswers: watchedSampleAnswers,
149
- explanation: watchedExplanation,
150
- points: watchedPoints,
151
- });
152
- previousPayloadRef.current = currentPayload;
153
- return;
154
- }
155
- const payload = {
156
- inputType: watchedInputType,
157
- audioUrl: watchedAudioUrl,
158
- questionText: watchedQuestionText,
159
- sampleAnswers: watchedSampleAnswers,
160
- explanation: watchedExplanation,
161
- points: watchedPoints,
162
- };
163
- const payloadString = JSON.stringify(payload);
164
- if (previousPayloadRef.current !== payloadString) {
165
- previousPayloadRef.current = payloadString;
166
- debouncedOnChange(payload);
167
- }
168
- }, [
169
- watchedInputType,
170
- watchedAudioUrl,
171
- watchedQuestionText,
172
- watchedSampleAnswers,
173
- watchedExplanation,
174
- watchedPoints,
175
- debouncedOnChange,
176
- ]);
177
- const handleSave = form.handleSubmit((data) => {
178
- onSave?.({
179
- ...data,
180
- });
181
- });
182
- // Handlers for dynamic sample answers
183
- const handleAddSampleAnswer = () => {
184
- const current = watchedSampleAnswers;
185
- form.setValue('sampleAnswers', [...current, ''], { shouldValidate: false });
186
- };
187
- const handleRemoveSampleAnswer = (index) => {
188
- const current = [...watchedSampleAnswers];
189
- current.splice(index, 1);
190
- // Always keep at least one entry
191
- if (current.length === 0) {
192
- current.push('');
193
- }
194
- form.setValue('sampleAnswers', current, { shouldValidate: true });
195
- };
196
- const handleSampleAnswerChange = (index, value) => {
197
- const current = [...watchedSampleAnswers];
198
- current[index] = value;
199
- form.setValue('sampleAnswers', current, { shouldValidate: true });
200
- };
201
- const handleGenerateAudio = async () => {
202
- const text = form.getValues('questionText');
203
- if (!text || text.trim() === '') {
204
- toast.error('Vui lòng nhập nội dung câu hỏi trước khi tạo audio!');
205
- return;
206
- }
207
- try {
208
- setIsGeneratingAudio(true);
209
- toast.error('TTS chưa được tích hợp trong component SDK');
210
- return;
211
- }
212
- catch (error) {
213
- toast.error(error?.response?.data?.message || 'Lỗi khi tạo audio');
214
- }
215
- finally {
216
- setIsGeneratingAudio(false);
217
- }
218
- };
219
- return (_jsxs("div", { className: "space-y-6", children: [_jsx(CreatorGuide, { colorScheme: "blue", steps: [
220
- { text: 'Chọn hình thức nhập câu hỏi: Upload Audio hoặc Nhập văn bản (Text → TTS)' },
221
- { text: 'Nếu chọn Audio: upload file audio câu hỏi. Nếu chọn Text: nhập nội dung câu hỏi', highlight: true },
222
- { text: 'Nhập đáp án mẫu (tùy chọn) — AI sẽ dựa vào đáp án mẫu để chấm điểm. Có thể thêm nhiều đáp án mẫu.' },
223
- { text: 'Thêm giải thích (tùy chọn)' },
224
- ], tips: [
225
- 'Học sinh chỉ nghe câu hỏi qua audio, KHÔNG nhìn thấy text câu hỏi.',
226
- 'Nếu chọn "Nhập văn bản", hệ thống sẽ tự động chuyển đổi thành giọng nói (TTS).',
227
- ] }), _jsxs(Card, { className: "overflow-hidden border border-slate-200 bg-white shadow-sm", children: [_jsx(CardHeader, { className: "border-b border-slate-100 bg-slate-50/50 pb-4", 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(Layers, { className: "h-4 w-4" }) }), _jsx(CardTitle, { className: "text-lg font-semibold text-slate-800", children: "C\u1EA5u h\u00ECnh N\u1ED9i dung C\u00E2u h\u1ECFi & \u0110\u00E1p \u00E1n" })] }) }), _jsxs(CardContent, { className: "p-0", children: [_jsxs("div", { className: "p-6 space-y-8", children: [_jsxs("div", { className: "grid grid-cols-1 gap-6 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-1.5 animate-in fade-in zoom-in-95 duration-200", children: [_jsxs(Label, { htmlFor: "points", className: "text-sm font-semibold text-slate-700", children: ["\u0110i\u1EC3m s\u1ED1 ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Input, { id: "points", type: "number", step: "1", min: "1", ...form.register('points', { valueAsNumber: true }), className: "w-24 border-slate-200 focus:border-blue-400 focus:ring-blue-100" }), form.hasError('points') && (_jsx("p", { className: "text-sm text-red-600", children: form.getFieldError('points') }))] }), _jsxs("div", { className: "space-y-3", children: [_jsx(Label, { className: "text-sm font-semibold text-slate-700 block", children: "H\u00ECnh th\u1EE9c nh\u1EADp c\u00E2u h\u1ECFi" }), _jsxs("div", { className: "flex w-fit rounded-lg border border-slate-200 bg-slate-100 p-1", children: [_jsxs("button", { type: "button", onClick: () => form.setValue('inputType', 'AUDIO', { shouldValidate: true }), className: `flex items-center gap-2 rounded-md px-4 py-1.5 text-sm font-medium transition-all ${watchedInputType === 'AUDIO'
228
- ? 'bg-white text-blue-700 shadow-sm'
229
- : 'text-slate-500 hover:bg-slate-200/50 hover:text-slate-700'}`, children: [_jsx(Mic, { className: "h-3.5 w-3.5" }), " Upload Audio"] }), _jsxs("button", { type: "button", onClick: () => form.setValue('inputType', 'TEXT', { shouldValidate: true }), className: `flex items-center gap-2 rounded-md px-4 py-1.5 text-sm font-medium transition-all ${watchedInputType === 'TEXT'
230
- ? 'bg-white text-emerald-700 shadow-sm'
231
- : 'text-slate-500 hover:bg-slate-200/50 hover:text-slate-700'}`, children: [_jsx(Type, { className: "h-3.5 w-3.5" }), " Nh\u1EADp v\u0103n b\u1EA3n (TTS)"] })] }), form.hasError('inputType') && (_jsx("p", { className: "text-sm text-red-600 mt-1", children: form.getFieldError('inputType') }))] })] }), _jsx("hr", { className: "border-slate-100" }), _jsx("div", { className: "space-y-4", children: _jsx("div", { className: "max-w-3xl", children: watchedInputType === 'AUDIO' ? (_jsxs("div", { className: "space-y-2 animate-in fade-in zoom-in-95 duration-200", children: [_jsx(Label, { className: "text-sm font-medium text-slate-700", children: "File audio c\u00E2u h\u1ECFi" }), _jsx(FileUpload, { id: "audioUrl", label: "", accept: "audio/*", value: watchedAudioUrl || '', onChange: (url) => {
232
- form.setValue('audioUrl', url, { shouldValidate: true });
233
- }, maxSize: 20, placeholder: "T\u1EA3i l\u00EAn file audio (MP3, WAV...)", autoUpload: true, prefix: "speaking/spontaneous-qa" }), form.hasError('audioUrl') && (_jsx("p", { className: "text-sm text-red-600 mt-1", children: form.getFieldError('audioUrl') })), _jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-amber-100 bg-amber-50/60 px-3 py-2 mt-2", children: [_jsx(Info, { className: "h-4 w-4 text-amber-500 mt-0.5 flex-shrink-0" }), _jsx("p", { className: "text-xs text-amber-700", children: "Upload file audio ch\u1EE9a c\u00E2u h\u1ECFi. H\u1ECDc sinh s\u1EBD nghe audio n\u00E0y v\u00E0 tr\u1EA3 l\u1EDDi t\u1EF1 do." })] })] })) : (_jsxs("div", { className: "space-y-3 animate-in fade-in zoom-in-95 duration-200", children: [_jsx(Label, { className: "text-sm font-medium text-slate-700", children: "N\u1ED9i dung c\u00E2u h\u1ECFi (Text \u2192 TTS)" }), _jsx(Input, { ...form.register('questionText'), placeholder: "VD: What is your favorite food and why?", className: "border-slate-200 focus:border-blue-400 focus:ring-blue-100" }), form.hasError('questionText') && (_jsx("p", { className: "text-sm text-red-600 mt-1", children: form.getFieldError('questionText') })), _jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-amber-100 bg-amber-50/60 px-3 py-2", children: [_jsx(Info, { className: "h-4 w-4 text-amber-500 mt-0.5 flex-shrink-0" }), _jsx("p", { className: "text-xs text-amber-700", children: "H\u1EC7 th\u1ED1ng s\u1EBD t\u1EF1 \u0111\u1ED9ng chuy\u1EC3n v\u0103n b\u1EA3n th\u00E0nh gi\u1ECDng n\u00F3i (TTS). Click n\u00FAt \"T\u1EA1o Audio\" \u0111\u1EC3 h\u1EC7 th\u1ED1ng x\u1EED l\u00FD." })] }), _jsxs("div", { className: "flex items-center gap-4 mt-2", children: [_jsxs(Button, { type: "button", variant: "default", size: "sm", onClick: handleGenerateAudio, disabled: isGeneratingAudio || !watchedQuestionText || watchedQuestionText === lastGeneratedText, className: "gap-2 rounded-none bg-gradient-to-r from-blue-600 to-indigo-600 text-white font-bold shadow-md shadow-blue-500/30 hover:shadow-lg hover:shadow-blue-500/40 hover:-translate-y-0.5 transition-all w-fit disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none", children: [isGeneratingAudio ? (_jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" })) : (_jsx(Volume2, { className: "h-3.5 w-3.5" })), "T\u1EA1o Audio"] }), displayAudioUrl && (_jsx("audio", { controls: true, src: displayAudioUrl, className: "h-8 max-w-[250px]" }))] })] })) }) })] }), externalErrors && externalErrors.length > 0 && (_jsx("div", { className: "border-t border-red-100 bg-red-50 p-6", children: _jsx("div", { className: "flex items-start gap-3", children: _jsxs("div", { className: "text-sm", children: [_jsx("p", { className: "font-semibold text-red-800", children: "C\u1EA7n ph\u1EA3i s\u1EEDa c\u00E1c l\u1ED7i sau:" }), _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: "border border-slate-200 bg-slate-50/50 shadow-sm", children: [_jsx(CardHeader, { className: "pb-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-6 w-6 items-center justify-center rounded-md bg-slate-200/50 text-slate-600", children: _jsx(BookOpen, { className: "h-3.5 w-3.5" }) }), _jsx(CardTitle, { className: "text-base font-semibold text-slate-800", children: "Gi\u1EA3i th\u00EDch chi ti\u1EBFt" })] }), _jsx("span", { className: "text-xs font-medium text-slate-500 uppercase tracking-wider", children: "T\u00F9y ch\u1ECDn" })] }) }), _jsx(CardContent, { children: _jsx(Textarea, { value: watchedExplanation || '', onChange: (e) => form.setValue('explanation', e.target.value, { shouldValidate: true }), 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...", rows: 3, className: "resize-none border-slate-200 bg-white focus:border-slate-300 focus:ring-slate-100" }) })] })] }));
234
- }
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useRef as s,useEffect as n,useState as a}from"react";import{useParams as i}from"next/navigation";import{Button as r}from"../../../../components/ui/button";import{Label as o}from"../../../../components/ui/label";import{Input as l}from"../../../../components/ui/input";import{Textarea as c}from"../../../../components/ui/textarea";import{Card as d,CardContent as m,CardHeader as u,CardTitle as h}from"../../../../components/ui/card";import{useDebouncedCallback as p,usePresignedFileUrl as x}from"../../../../shared/lib/hooks";import{useReactHookForm as g}from"../../../../shared/lib/hooks/useReactHookForm";import{Mic as b,Type as f,Volume2 as N,BookOpen as w,Info as T,Loader2 as y,Layers as v}from"lucide-react";import{toast as A}from"sonner";import{FileUpload as U}from"../../../../components/ui/file-upload";import{CreatorGuide as q}from"../../components/CreatorGuide";import{spontaneousQAFormSchema as S}from"../../../../shared/lib/validation/spontaneous-qa.validation";export function SpontaneousQACreator({initialData:k,onSave:C,onChange:V,externalErrors:E,validationRef:O}){const F=k,z=(i(),(()=>{const e=F?.expectedAnswers||F?.correctAnswer?.expectedAnswers;if(Array.isArray(e)&&e.length>0)return e;const t=F?.sampleAnswers||F?.content?.sampleAnswers;if(Array.isArray(t)&&t.length>0)return t;const s=F?.sampleAnswer||F?.correctAnswer?.sampleAnswer;return s?[s]:[""]})()),D=g({defaultValues:{inputType:F?.inputType||F?.content?.inputType||"TEXT",audioUrl:F?.audioUrl||F?.content?.audioUrl||"",questionText:F?.questionText||F?.content?.questionText||"",sampleAnswers:z,explanation:F?.explanation||"",points:F?.points??5},zodSchema:S,mode:"onBlur"}),j=s(k),H=s(!1),J=s(V),G=s(""),[I,X]=a(!1),[K,R]=a(null),[W,B]=a(F?.questionText||F?.content?.questionText||"");n(()=>{J.current=V},[V]);const L=p(e=>{J.current?.(e)},300),M=D.watch("inputType"),P=D.watch("audioUrl"),Q=D.watch("questionText"),Y=D.watch("sampleAnswers")||[""],Z=D.watch("explanation"),$=D.watch("points"),{previewUrl:_}=x(P||""),ee=K||_;n(()=>{O&&(O.current={triggerValidation:async()=>await D.trigger(),getFormData:()=>({inputType:M,audioUrl:P,questionText:Q,sampleAnswers:Y,explanation:Z,points:$})})},[O,D,M,P,Q,Y,Z,$]),n(()=>{const e=k,t=(()=>{const t=e?.expectedAnswers||e?.correctAnswer?.expectedAnswers;if(Array.isArray(t)&&t.length>0)return t;const s=e?.sampleAnswers||e?.content?.sampleAnswers;if(Array.isArray(s)&&s.length>0)return s;const n=e?.sampleAnswer||e?.correctAnswer?.sampleAnswer;return n?[n]:[""]})(),s={inputType:e?.inputType||e?.content?.inputType||"TEXT",audioUrl:e?.audioUrl||e?.content?.audioUrl||"",questionText:e?.questionText||e?.content?.questionText||"",sampleAnswers:t,explanation:e?.explanation||"",points:e?.points??5};if(!(JSON.stringify(j.current)!==JSON.stringify(k)))return;j.current=k;const n={inputType:M,audioUrl:P,questionText:Q,sampleAnswers:Y,explanation:Z,points:$},a=n.points===s.points&&n.explanation===s.explanation&&n.inputType===s.inputType&&n.audioUrl===s.audioUrl&&n.questionText===s.questionText&&JSON.stringify(n.sampleAnswers)===JSON.stringify(s.sampleAnswers);if(a&&!0===k?.isCompleted)return;!a&&(H.current=!0,D.reset(s),B(s.questionText),setTimeout(()=>{H.current=!1},150))},[k,D,M,P,Q,Y,Z,$]),n(()=>{if(H.current){const e=JSON.stringify({inputType:M,audioUrl:P,questionText:Q,sampleAnswers:Y,explanation:Z,points:$});return void(G.current=e)}const e={inputType:M,audioUrl:P,questionText:Q,sampleAnswers:Y,explanation:Z,points:$},t=JSON.stringify(e);G.current!==t&&(G.current=t,L(e))},[M,P,Q,Y,Z,$,L]);D.handleSubmit(e=>{C?.({...e})});return t("div",{className:"space-y-6",children:[e(q,{colorScheme:"blue",steps:[{text:"Chọn hình thức nhập câu hỏi: Upload Audio hoặc Nhập văn bản (Text → TTS)"},{text:"Nếu chọn Audio: upload file audio câu hỏi. Nếu chọn Text: nhập nội dung câu hỏi",highlight:!0},{text:"Nhập đáp án mẫu (tùy chọn) — AI sẽ dựa vào đáp án mẫu để chấm điểm. Có thể thêm nhiều đáp án mẫu."},{text:"Thêm giải thích (tùy chọn)"}],tips:["Học sinh chỉ nghe câu hỏi qua audio, KHÔNG nhìn thấy text câu hỏi.",'Nếu chọn "Nhập văn bản", hệ thống sẽ tự động chuyển đổi thành giọng nói (TTS).']}),t(d,{className:"overflow-hidden border border-slate-200 bg-white shadow-sm",children:[e(u,{className:"border-b border-slate-100 bg-slate-50/50 pb-4",children:t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-7 w-7 items-center justify-center rounded-lg bg-blue-100 text-blue-700",children:e(v,{className:"h-4 w-4"})}),e(h,{className:"text-lg font-semibold text-slate-800",children:"Cấu hình Nội dung Câu hỏi & Đáp án"})]})}),t(m,{className:"p-0",children:[t("div",{className:"p-6 space-y-8",children:[t("div",{className:"grid grid-cols-1 gap-6 md:grid-cols-2",children:[t("div",{className:"space-y-1.5 animate-in fade-in zoom-in-95 duration-200",children:[t(o,{htmlFor:"points",className:"text-sm font-semibold text-slate-700",children:["Điểm số ",e("span",{className:"text-red-500",children:"*"})]}),e(l,{id:"points",type:"number",step:"1",min:"1",...D.register("points",{valueAsNumber:!0}),className:"w-24 border-slate-200 focus:border-blue-400 focus:ring-blue-100"}),D.hasError("points")&&e("p",{className:"text-sm text-red-600",children:D.getFieldError("points")})]}),t("div",{className:"space-y-3",children:[e(o,{className:"text-sm font-semibold text-slate-700 block",children:"Hình thức nhập câu hỏi"}),t("div",{className:"flex w-fit rounded-lg border border-slate-200 bg-slate-100 p-1",children:[t("button",{type:"button",onClick:()=>D.setValue("inputType","AUDIO",{shouldValidate:!0}),className:"flex items-center gap-2 rounded-md px-4 py-1.5 text-sm font-medium transition-all "+("AUDIO"===M?"bg-white text-blue-700 shadow-sm":"text-slate-500 hover:bg-slate-200/50 hover:text-slate-700"),children:[e(b,{className:"h-3.5 w-3.5"})," Upload Audio"]}),t("button",{type:"button",onClick:()=>D.setValue("inputType","TEXT",{shouldValidate:!0}),className:"flex items-center gap-2 rounded-md px-4 py-1.5 text-sm font-medium transition-all "+("TEXT"===M?"bg-white text-emerald-700 shadow-sm":"text-slate-500 hover:bg-slate-200/50 hover:text-slate-700"),children:[e(f,{className:"h-3.5 w-3.5"})," Nhập văn bản (TTS)"]})]}),D.hasError("inputType")&&e("p",{className:"text-sm text-red-600 mt-1",children:D.getFieldError("inputType")})]})]}),e("hr",{className:"border-slate-100"}),e("div",{className:"space-y-4",children:e("div",{className:"max-w-3xl",children:t("div","AUDIO"===M?{className:"space-y-2 animate-in fade-in zoom-in-95 duration-200",children:[e(o,{className:"text-sm font-medium text-slate-700",children:"File audio câu hỏi"}),e(U,{id:"audioUrl",label:"",accept:"audio/*",value:P||"",onChange:e=>{D.setValue("audioUrl",e,{shouldValidate:!0})},maxSize:20,placeholder:"Tải lên file audio (MP3, WAV...)",autoUpload:!0,prefix:"speaking/spontaneous-qa"}),D.hasError("audioUrl")&&e("p",{className:"text-sm text-red-600 mt-1",children:D.getFieldError("audioUrl")}),t("div",{className:"flex items-start gap-2 rounded-lg border border-amber-100 bg-amber-50/60 px-3 py-2 mt-2",children:[e(T,{className:"h-4 w-4 text-amber-500 mt-0.5 flex-shrink-0"}),e("p",{className:"text-xs text-amber-700",children:"Upload file audio chứa câu hỏi. Học sinh sẽ nghe audio này và trả lời tự do."})]})]}:{className:"space-y-3 animate-in fade-in zoom-in-95 duration-200",children:[e(o,{className:"text-sm font-medium text-slate-700",children:"Nội dung câu hỏi (Text → TTS)"}),e(l,{...D.register("questionText"),placeholder:"VD: What is your favorite food and why?",className:"border-slate-200 focus:border-blue-400 focus:ring-blue-100"}),D.hasError("questionText")&&e("p",{className:"text-sm text-red-600 mt-1",children:D.getFieldError("questionText")}),t("div",{className:"flex items-start gap-2 rounded-lg border border-amber-100 bg-amber-50/60 px-3 py-2",children:[e(T,{className:"h-4 w-4 text-amber-500 mt-0.5 flex-shrink-0"}),e("p",{className:"text-xs text-amber-700",children:'Hệ thống sẽ tự động chuyển văn bản thành giọng nói (TTS). Click nút "Tạo Audio" để hệ thống xử lý.'})]}),t("div",{className:"flex items-center gap-4 mt-2",children:[t(r,{type:"button",variant:"default",size:"sm",onClick:async()=>{const e=D.getValues("questionText");if(e&&""!==e.trim())try{return X(!0),void A.error("TTS chưa được tích hợp trong component SDK")}catch(e){A.error(e?.response?.data?.message||"Lỗi khi tạo audio")}finally{X(!1)}else A.error("Vui lòng nhập nội dung câu hỏi trước khi tạo audio!")},disabled:I||!Q||Q===W,className:"gap-2 rounded-none bg-gradient-to-r from-blue-600 to-indigo-600 text-white font-bold shadow-md shadow-blue-500/30 hover:shadow-lg hover:shadow-blue-500/40 hover:-translate-y-0.5 transition-all w-fit disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none",children:[I?e(y,{className:"h-3.5 w-3.5 animate-spin"}):e(N,{className:"h-3.5 w-3.5"}),"Tạo Audio"]}),ee&&e("audio",{controls:!0,src:ee,className:"h-8 max-w-[250px]"})]})]})})})]}),E&&E.length>0&&e("div",{className:"border-t border-red-100 bg-red-50 p-6",children:e("div",{className:"flex items-start gap-3",children:t("div",{className:"text-sm",children:[e("p",{className:"font-semibold text-red-800",children:"Cần phải sửa các lỗi sau:"}),e("ul",{className:"mt-1 list-inside list-disc text-red-700",children:E.map((t,s)=>e("li",{children:t},s))})]})})})]})]}),t(d,{className:"border border-slate-200 bg-slate-50/50 shadow-sm",children:[e(u,{className:"pb-3",children:t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-slate-200/50 text-slate-600",children:e(w,{className:"h-3.5 w-3.5"})}),e(h,{className:"text-base font-semibold text-slate-800",children:"Giải thích chi tiết"})]}),e("span",{className:"text-xs font-medium text-slate-500 uppercase tracking-wider",children:"Tùy chọn"})]})}),e(m,{children:e(c,{value:Z||"",onChange:e=>D.setValue("explanation",e.target.value,{shouldValidate:!0}),placeholder:"Giải thích thêm về câu trả lời kỳ vọng, dùng khi review kết quả...",rows:3,className:"resize-none border-slate-200 bg-white focus:border-slate-300 focus:ring-slate-100"})})]})]})}
@@ -1,93 +1 @@
1
- import { SpontaneousQACreator } from './SpontaneousQACreator';
2
- export const spontaneousQARegistration = {
3
- component: SpontaneousQACreator,
4
- transformInitialData: (initialData) => {
5
- let creatorData = initialData?.answer;
6
- const content = initialData?.content;
7
- const answer = initialData?.answer || initialData?.correctAnswer || initialData?.correct_answer;
8
- const points = initialData?.total_points
9
- ? parseFloat(initialData.total_points)
10
- : (initialData?.points || content?.points || answer?.points || 5);
11
- const explanation = initialData?.explanation || content?.explanation || answer?.explanation || '';
12
- // Backward compat: convert old sampleAnswer/sampleAnswers to expectedAnswers (array)
13
- const normalizeSampleAnswers = () => {
14
- const newArr = answer?.expectedAnswers;
15
- if (Array.isArray(newArr) && newArr.length > 0)
16
- return newArr;
17
- const arr = answer?.sampleAnswers;
18
- if (Array.isArray(arr) && arr.length > 0)
19
- return arr;
20
- const single = answer?.sampleAnswer;
21
- if (single)
22
- return [single];
23
- return [''];
24
- };
25
- if (content && typeof content === 'object' && content.inputType !== undefined) {
26
- creatorData = {
27
- inputType: content.inputType || 'TEXT',
28
- audioUrl: content.audioUrl || '',
29
- questionText: content.questionText || '',
30
- sampleAnswers: normalizeSampleAnswers(),
31
- explanation,
32
- points,
33
- };
34
- }
35
- else if (answer && typeof answer === 'object') {
36
- creatorData = {
37
- ...answer,
38
- inputType: answer.inputType || content?.inputType || 'TEXT',
39
- audioUrl: answer.audioUrl || content?.audioUrl || '',
40
- questionText: answer.questionText || content?.questionText || '',
41
- sampleAnswers: normalizeSampleAnswers(),
42
- explanation,
43
- points,
44
- };
45
- }
46
- return creatorData;
47
- },
48
- getExtraProps: (ctx) => ({
49
- onUnsavedChangesChange: ctx.onUnsavedChangesChange,
50
- validationRef: ctx.validationRef,
51
- }),
52
- wrapOnSave: (data, ctx) => {
53
- // Filter out empty sample answers
54
- const expectedAnswers = (data.sampleAnswers || []).filter((s) => s && s.trim() !== '');
55
- return {
56
- type: ctx.questionType,
57
- content: {
58
- ...(typeof ctx.state.content === 'object' && ctx.state.content !== null ? ctx.state.content : {}),
59
- inputType: data.inputType,
60
- audioUrl: data.audioUrl || '',
61
- questionText: data.inputType === 'TEXT' ? data.questionText : undefined,
62
- },
63
- points: data?.points ?? ctx.state.points ?? 5,
64
- level: ctx.state.level,
65
- difficulty: ctx.state.difficulty,
66
- skill: ctx.state.skill,
67
- answer: {
68
- expectedAnswers,
69
- },
70
- explanation: data?.explanation || '',
71
- };
72
- },
73
- wrapOnChange: (data, ctx) => {
74
- const expectedAnswers = data.sampleAnswers || [''];
75
- return {
76
- type: ctx.questionType,
77
- content: {
78
- ...(typeof ctx.state.content === 'object' && ctx.state.content !== null ? ctx.state.content : {}),
79
- inputType: data.inputType,
80
- audioUrl: data.audioUrl || '',
81
- questionText: data.inputType === 'TEXT' ? data.questionText : undefined,
82
- },
83
- points: data?.points ?? ctx.state.points ?? 5,
84
- level: ctx.state.level,
85
- difficulty: ctx.state.difficulty,
86
- skill: ctx.state.skill,
87
- answer: {
88
- expectedAnswers,
89
- },
90
- explanation: data?.explanation || '',
91
- };
92
- },
93
- };
1
+ import{SpontaneousQACreator as t}from"./SpontaneousQACreator";export const spontaneousQARegistration={component:t,transformInitialData:t=>{let e=t?.answer;const n=t?.content,o=t?.answer||t?.correctAnswer||t?.correct_answer,s=t?.total_points?parseFloat(t.total_points):t?.points||n?.points||o?.points||5,i=t?.explanation||n?.explanation||o?.explanation||"",a=()=>{const t=o?.expectedAnswers;if(Array.isArray(t)&&t.length>0)return t;const e=o?.sampleAnswers;if(Array.isArray(e)&&e.length>0)return e;const n=o?.sampleAnswer;return n?[n]:[""]};return n&&"object"==typeof n&&void 0!==n.inputType?e={inputType:n.inputType||"TEXT",audioUrl:n.audioUrl||"",questionText:n.questionText||"",sampleAnswers:a(),explanation:i,points:s}:o&&"object"==typeof o&&(e={...o,inputType:o.inputType||n?.inputType||"TEXT",audioUrl:o.audioUrl||n?.audioUrl||"",questionText:o.questionText||n?.questionText||"",sampleAnswers:a(),explanation:i,points:s}),e},getExtraProps:t=>({onUnsavedChangesChange:t.onUnsavedChangesChange,validationRef:t.validationRef}),wrapOnSave:(t,e)=>{const n=(t.sampleAnswers||[]).filter(t=>t&&""!==t.trim());return{type:e.questionType,content:{..."object"==typeof e.state.content&&null!==e.state.content?e.state.content:{},inputType:t.inputType,audioUrl:t.audioUrl||"",questionText:"TEXT"===t.inputType?t.questionText:void 0},points:t?.points??e.state.points??5,level:e.state.level,difficulty:e.state.difficulty,skill:e.state.skill,answer:{expectedAnswers:n},explanation:t?.explanation||""}},wrapOnChange:(t,e)=>{const n=t.sampleAnswers||[""];return{type:e.questionType,content:{..."object"==typeof e.state.content&&null!==e.state.content?e.state.content:{},inputType:t.inputType,audioUrl:t.audioUrl||"",questionText:"TEXT"===t.inputType?t.questionText:void 0},points:t?.points??e.state.points??5,level:e.state.level,difficulty:e.state.difficulty,skill:e.state.skill,answer:{expectedAnswers:n},explanation:t?.explanation||""}}};
@@ -1,34 +1 @@
1
- export const transformSpontaneousQA = (question) => {
2
- const contentData = question.content;
3
- const answerData = question.answer;
4
- const inputType = contentData?.inputType || 'TEXT';
5
- const apiContent = {
6
- inputType,
7
- // Always include audioUrl — populated when AUDIO, empty string when TEXT
8
- audioUrl: contentData?.audioUrl || '',
9
- };
10
- // Include questionText when inputType is TEXT
11
- if (inputType === 'TEXT' && contentData?.questionText) {
12
- apiContent.questionText = contentData.questionText;
13
- }
14
- // Normalize: support both expectedAnswers, sampleAnswers (array) and legacy sampleAnswer (string)
15
- const expectedAnswers = (() => {
16
- const newArr = answerData?.expectedAnswers;
17
- if (Array.isArray(newArr))
18
- return newArr.filter((s) => s && s.trim() !== '');
19
- const arr = answerData?.sampleAnswers;
20
- if (Array.isArray(arr))
21
- return arr.filter((s) => s && s.trim() !== '');
22
- const single = answerData?.sampleAnswer;
23
- if (single && single.trim() !== '')
24
- return [single];
25
- return [];
26
- })();
27
- const apiCorrectAnswer = {
28
- expectedAnswers,
29
- };
30
- return {
31
- apiContent,
32
- apiCorrectAnswer,
33
- };
34
- };
1
+ export const transformSpontaneousQA=r=>{const e=r.content,t=r.answer,n=e?.inputType||"TEXT",s={inputType:n,audioUrl:e?.audioUrl||""};"TEXT"===n&&e?.questionText&&(s.questionText=e.questionText);return{apiContent:s,apiCorrectAnswer:{expectedAnswers:(()=>{const r=t?.expectedAnswers;if(Array.isArray(r))return r.filter(r=>r&&""!==r.trim());const e=t?.sampleAnswers;if(Array.isArray(e))return e.filter(r=>r&&""!==r.trim());const n=t?.sampleAnswer;return n&&""!==n.trim()?[n]:[]})()}}};
@@ -1,75 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useEffect } from 'react';
4
- import { Check, X, CircleHelp } from 'lucide-react';
5
- import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
6
- import { cn } from '../../../../shared/lib/utils';
7
- export function TrueFalseClient({ questionData, onSubmit, isReviewMode = false, userAnswer, }) {
8
- const [selectedAnswer, setSelectedAnswer] = useState(userAnswer !== undefined ? userAnswer : undefined);
9
- // Presigned URL for image
10
- const { previewUrl: imagePreviewUrl, isLoading } = usePresignedFileUrl(questionData.imageUrl || '');
11
- // Sync userAnswer
12
- useEffect(() => {
13
- if (userAnswer !== undefined) {
14
- setSelectedAnswer(userAnswer);
15
- }
16
- }, [userAnswer]);
17
- // Handle answer change
18
- const handleAnswerChange = (value) => {
19
- if (isReviewMode)
20
- return;
21
- setSelectedAnswer(value);
22
- onSubmit?.(value);
23
- };
24
- const hasAnswered = selectedAnswer !== undefined;
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
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" }) })) : (
29
- /* eslint-disable-next-line @next/next/no-img-element */
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
- ? questionData.answer === true
32
- ? 'border-green-400 bg-green-50'
33
- : selectedAnswer === true
34
- ? 'border-red-400 bg-red-50'
35
- : 'border-gray-200 bg-gray-50'
36
- : selectedAnswer === true
37
- ? 'border-emerald-500 bg-emerald-50 shadow-sm ring-2 ring-emerald-100'
38
- : 'border-gray-200 bg-white hover:border-emerald-300 hover:bg-emerald-50/50 hover:shadow-sm'), children: [_jsx("div", { className: cn('flex h-9 w-9 items-center justify-center rounded-full text-sm font-bold transition-all', isReviewMode
39
- ? questionData.answer === true
40
- ? 'bg-green-500 text-white'
41
- : selectedAnswer === true
42
- ? 'bg-red-500 text-white'
43
- : 'bg-gray-200 text-gray-600'
44
- : selectedAnswer === true
45
- ? 'bg-emerald-500 text-white shadow-sm'
46
- : 'bg-gray-100 text-gray-500 group-hover:bg-emerald-100 group-hover:text-emerald-600'), children: _jsx(Check, { className: "h-4 w-4" }) }), _jsx("span", { className: cn('text-base font-semibold', isReviewMode && questionData.answer === true
47
- ? 'text-green-800'
48
- : isReviewMode && selectedAnswer === true
49
- ? 'text-red-800'
50
- : selectedAnswer === true
51
- ? 'text-emerald-700'
52
- : 'text-gray-600'), children: "\u0110\u00FAng (True)" }), isReviewMode && (questionData.answer === true || selectedAnswer === true) && (_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-full', questionData.answer === true ? 'bg-green-500' : 'bg-red-500'), children: questionData.answer === true ? (_jsx(Check, { className: "h-4 w-4 text-white" })) : (_jsx(X, { className: "h-4 w-4 text-white" })) }))] }), _jsxs("button", { type: "button", onClick: () => handleAnswerChange(false), disabled: isReviewMode, className: cn('group flex items-center justify-center gap-3 rounded-xl border-2 p-4 transition-all', isReviewMode
53
- ? questionData.answer === false
54
- ? 'border-green-400 bg-green-50'
55
- : selectedAnswer === false
56
- ? 'border-red-400 bg-red-50'
57
- : 'border-gray-200 bg-gray-50'
58
- : selectedAnswer === false
59
- ? 'border-red-500 bg-red-50 shadow-sm ring-2 ring-red-100'
60
- : 'border-gray-200 bg-white hover:border-red-300 hover:bg-red-50/50 hover:shadow-sm'), children: [_jsx("div", { className: cn('flex h-9 w-9 items-center justify-center rounded-full text-sm font-bold transition-all', isReviewMode
61
- ? questionData.answer === false
62
- ? 'bg-green-500 text-white'
63
- : selectedAnswer === false
64
- ? 'bg-red-500 text-white'
65
- : 'bg-gray-200 text-gray-600'
66
- : selectedAnswer === false
67
- ? 'bg-red-500 text-white shadow-sm'
68
- : 'bg-gray-100 text-gray-500 group-hover:bg-red-100 group-hover:text-red-600'), children: _jsx(X, { className: "h-4 w-4" }) }), _jsx("span", { className: cn('text-base font-semibold', isReviewMode && questionData.answer === false
69
- ? 'text-green-800'
70
- : isReviewMode && selectedAnswer === false
71
- ? 'text-red-800'
72
- : selectedAnswer === false
73
- ? 'text-red-700'
74
- : 'text-gray-600'), children: "Sai (False)" }), isReviewMode && (questionData.answer === false || selectedAnswer === false) && (_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-full', questionData.answer === false ? 'bg-green-500' : 'bg-red-500'), children: questionData.answer === false ? (_jsx(Check, { className: "h-4 w-4 text-white" })) : (_jsx(X, { className: "h-4 w-4 text-white" })) }))] })] })] }), !isReviewMode && !hasAnswered && (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-3", children: [_jsx("div", { className: "flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-white", children: _jsx("span", { className: "text-xs font-bold", children: "!" }) }), _jsx("span", { className: "text-sm text-amber-800", children: "Vui l\u00F2ng ch\u1ECDn \u0111\u00E1p \u00E1n" })] })), isReviewMode && questionData.explanation && (_jsxs("div", { className: "rounded-lg border border-blue-200 bg-blue-50 p-4", children: [_jsx("p", { className: "text-sm font-semibold text-blue-800", children: "Gi\u1EA3i th\u00EDch:" }), _jsx("p", { className: "mt-1 text-sm text-blue-700", children: questionData.explanation })] }))] })] }) }));
75
- }
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useState as t,useEffect as a}from"react";import{Check as s,X as d,CircleHelp as l}from"lucide-react";import{usePresignedFileUrl as n}from"../../../../shared/lib/hooks";import{cn as i}from"../../../../shared/lib/utils";export function TrueFalseClient({questionData:o,onSubmit:c,isReviewMode:m=!1,userAnswer:g}){const[b,h]=t(void 0!==g?g:void 0),{previewUrl:x,isLoading:u}=n(o.imageUrl||"");a(()=>{void 0!==g&&h(g)},[g]);const w=e=>{m||(h(e),c?.(e))},f=void 0!==b,p=m&&b===o.answer;return e("div",{className:"space-y-4",children:r("div",{className:"overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm",children:[r("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:[r("div",{className:"flex items-center gap-3",children:[e("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:e(l,{className:"h-4 w-4"})}),e("span",{className:"text-sm font-medium text-gray-700",children:"Đúng / Sai"})]}),m&&f&&r("div",{className:i("flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold",p?"bg-green-100 text-green-700":"bg-red-100 text-red-700"),children:[e(p?s:d,{className:"h-3.5 w-3.5"}),p?"Đúng":"Sai"]})]}),r("div",{className:"space-y-5 p-5",children:[e("div",{className:"rounded-lg border border-blue-100 bg-gradient-to-r from-blue-50 to-indigo-50 p-4",children:e("h3",{className:"text-base font-semibold leading-relaxed text-gray-800",children:o.question||"Chưa nhập câu hỏi"})}),(x||o.imageUrl)&&r("div",{className:"overflow-hidden rounded-lg border border-gray-200 bg-gradient-to-br from-slate-50 to-gray-100",children:[e("div",{className:"border-b border-gray-100 px-3 py-1.5",children:r("div",{className:"flex items-center gap-1.5",children:[e("div",{className:"h-2 w-2 rounded-full bg-red-400"}),e("div",{className:"h-2 w-2 rounded-full bg-yellow-400"}),e("div",{className:"h-2 w-2 rounded-full bg-green-400"}),e("span",{className:"ml-2 text-xs font-medium text-gray-500",children:"Hình ảnh"})]})}),e("div",{className:"flex items-center justify-center p-4",children:u?e("div",{className:"flex h-48 items-center justify-center",children:e("div",{className:"h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500"})}):e("img",{src:x||o.imageUrl,alt:"Question illustration",className:"max-h-56 w-auto rounded-md object-contain"})})]}),r("div",{className:"space-y-2.5",children:[e("span",{className:"text-xs font-medium uppercase tracking-wider text-gray-500",children:"Chọn đáp án"}),r("div",{className:"grid grid-cols-2 gap-4",children:[r("button",{type:"button",onClick:()=>w(!0),disabled:m,className:i("group flex items-center justify-center gap-3 rounded-xl border-2 p-4 transition-all",m?!0===o.answer?"border-green-400 bg-green-50":!0===b?"border-red-400 bg-red-50":"border-gray-200 bg-gray-50":!0===b?"border-emerald-500 bg-emerald-50 shadow-sm ring-2 ring-emerald-100":"border-gray-200 bg-white hover:border-emerald-300 hover:bg-emerald-50/50 hover:shadow-sm"),children:[e("div",{className:i("flex h-9 w-9 items-center justify-center rounded-full text-sm font-bold transition-all",m?!0===o.answer?"bg-green-500 text-white":!0===b?"bg-red-500 text-white":"bg-gray-200 text-gray-600":!0===b?"bg-emerald-500 text-white shadow-sm":"bg-gray-100 text-gray-500 group-hover:bg-emerald-100 group-hover:text-emerald-600"),children:e(s,{className:"h-4 w-4"})}),e("span",{className:i("text-base font-semibold",m&&!0===o.answer?"text-green-800":m&&!0===b?"text-red-800":!0===b?"text-emerald-700":"text-gray-600"),children:"Đúng (True)"}),m&&(!0===o.answer||!0===b)&&e("div",{className:i("flex h-6 w-6 items-center justify-center rounded-full",!0===o.answer?"bg-green-500":"bg-red-500"),children:!0===o.answer?e(s,{className:"h-4 w-4 text-white"}):e(d,{className:"h-4 w-4 text-white"})})]}),r("button",{type:"button",onClick:()=>w(!1),disabled:m,className:i("group flex items-center justify-center gap-3 rounded-xl border-2 p-4 transition-all",m?!1===o.answer?"border-green-400 bg-green-50":!1===b?"border-red-400 bg-red-50":"border-gray-200 bg-gray-50":!1===b?"border-red-500 bg-red-50 shadow-sm ring-2 ring-red-100":"border-gray-200 bg-white hover:border-red-300 hover:bg-red-50/50 hover:shadow-sm"),children:[e("div",{className:i("flex h-9 w-9 items-center justify-center rounded-full text-sm font-bold transition-all",m?!1===o.answer?"bg-green-500 text-white":!1===b?"bg-red-500 text-white":"bg-gray-200 text-gray-600":!1===b?"bg-red-500 text-white shadow-sm":"bg-gray-100 text-gray-500 group-hover:bg-red-100 group-hover:text-red-600"),children:e(d,{className:"h-4 w-4"})}),e("span",{className:i("text-base font-semibold",m&&!1===o.answer?"text-green-800":m&&!1===b?"text-red-800":!1===b?"text-red-700":"text-gray-600"),children:"Sai (False)"}),m&&(!1===o.answer||!1===b)&&e("div",{className:i("flex h-6 w-6 items-center justify-center rounded-full",!1===o.answer?"bg-green-500":"bg-red-500"),children:!1===o.answer?e(s,{className:"h-4 w-4 text-white"}):e(d,{className:"h-4 w-4 text-white"})})]})]})]}),!m&&!f&&r("div",{className:"flex items-center gap-2 rounded-lg border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-3",children:[e("div",{className:"flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-white",children:e("span",{className:"text-xs font-bold",children:"!"})}),e("span",{className:"text-sm text-amber-800",children:"Vui lòng chọn đáp án"})]}),m&&o.explanation&&r("div",{className:"rounded-lg border border-blue-200 bg-blue-50 p-4",children:[e("p",{className:"text-sm font-semibold text-blue-800",children:"Giải thích:"}),e("p",{className:"mt-1 text-sm text-blue-700",children:o.explanation})]})]})]})})}