@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,70 +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, Volume2 } from 'lucide-react';
5
- import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
6
- import { cn } from '../../../../shared/lib/utils';
7
- /**
8
- * Scene Object Button - A clickable image button overlaid on the background scene
9
- */
10
- function SceneObjectButton({ node, isSelected, isCorrect, isWrong, isReviewMode, onClick, }) {
11
- const { previewUrl: objectImageUrl } = usePresignedFileUrl(node.imageUrl || '');
12
- return (_jsxs("button", { type: "button", onClick: onClick, disabled: isReviewMode, className: cn('absolute cursor-pointer rounded-lg transition-all duration-200 overflow-hidden', 'hover:scale-105', 'focus:outline-none focus:ring-2 focus:ring-offset-1',
13
- // Default state
14
- !isSelected && !isCorrect && !isWrong && 'border-2 border-transparent hover:border-blue-300 focus:ring-blue-400',
15
- // Selected (not review mode)
16
- isSelected && !isReviewMode && 'border-3 border-blue-500 ring-2 ring-blue-200 shadow-lg shadow-blue-200/50 scale-105',
17
- // Review mode - correct
18
- isCorrect && 'border-3 border-green-500 ring-2 ring-green-200 shadow-lg shadow-green-200/50 scale-105',
19
- // Review mode - wrong (selected but not correct)
20
- isWrong && 'border-3 border-red-500 ring-2 ring-red-200 shadow-lg shadow-red-200/50 scale-105',
21
- // Disabled in review
22
- isReviewMode && 'cursor-default hover:scale-100'), style: {
23
- left: `${node.x}%`,
24
- top: `${node.y}%`,
25
- width: `${node.width}%`,
26
- height: `${node.height}%`,
27
- }, title: isReviewMode ? node.label : `Chọn ${node.label}`, children: [objectImageUrl ? (_jsx("img", { src: objectImageUrl, alt: node.label, className: "h-full w-full object-fill pointer-events-none", draggable: false })) : (_jsx("div", { className: "flex h-full w-full items-center justify-center bg-gray-100/80 text-xs text-gray-500", children: node.label })), isReviewMode && (isCorrect || isWrong) && (_jsx("div", { className: cn('absolute -top-2 -right-2 flex h-5 w-5 items-center justify-center rounded-full shadow-md', isCorrect ? 'bg-green-500' : 'bg-red-500'), style: { zIndex: 1 }, children: isCorrect ? (_jsx(Check, { className: "h-3 w-3 text-white" })) : (_jsx(X, { className: "h-3 w-3 text-white" })) }))] }));
28
- }
29
- /**
30
- * ListenAndChooseObjectsInSceneClient
31
- *
32
- * Student/preview view for the "Listen and Choose Objects in Scene" question type.
33
- * Displays a background scene image with clickable object images overlaid.
34
- * Student listens to audio and clicks the correct object.
35
- */
36
- export function ListenAndChooseObjectsInSceneClient({ questionData, onSubmit, isReviewMode = false, userAnswer, correctAnswer, explanation, }) {
37
- // State
38
- const [selectedObjectId, setSelectedObjectId] = useState(userAnswer?.objectId || '');
39
- // Presigned URLs
40
- const { previewUrl: backgroundUrl, isLoading: bgLoading } = usePresignedFileUrl(questionData.backgroundImageUrl || '');
41
- const { previewUrl: audioUrl } = usePresignedFileUrl(questionData.audioUrl || '');
42
- // Sync userAnswer
43
- useEffect(() => {
44
- if (userAnswer?.objectId) {
45
- setSelectedObjectId(userAnswer.objectId);
46
- }
47
- }, [userAnswer]);
48
- // Handle object click
49
- const handleObjectClick = (objectId) => {
50
- if (isReviewMode)
51
- return;
52
- setSelectedObjectId(objectId);
53
- onSubmit?.({ objectId });
54
- };
55
- const hasAnswered = selectedObjectId !== '';
56
- const selectedNode = questionData.objectNodes.find((n) => n.id === selectedObjectId);
57
- const isAnswerCorrect = isReviewMode && correctAnswer && selectedObjectId === correctAnswer.objectId;
58
- return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-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-blue-500 to-indigo-600 text-white shadow-sm", children: _jsx(CircleHelp, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 ch\u1ECDn \u0111\u1ED1i t\u01B0\u1EE3ng trong c\u1EA3nh" })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isAnswerCorrect
59
- ? 'bg-green-100 text-green-700'
60
- : '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: [audioUrl && (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-blue-100 bg-gradient-to-r from-blue-50 to-indigo-50 p-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-blue-500 text-white", children: _jsx(Volume2, { className: "h-4 w-4" }) }), _jsx("audio", { controls: true, className: "h-8 w-full", src: audioUrl, children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." })] })), _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: "Scene" })] }) }), _jsx("div", { className: "relative p-2", children: bgLoading ? (_jsx("div", { className: "flex h-64 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500" }) })) : backgroundUrl ? (_jsxs("div", { className: "relative w-full", children: [_jsx("img", { src: backgroundUrl, alt: "Scene background", className: "w-full rounded-md object-contain", draggable: false }), questionData.objectNodes.map((node) => {
61
- const isSelected = selectedObjectId === node.id;
62
- const isCorrectNode = isReviewMode && correctAnswer?.objectId === node.id;
63
- const isWrongNode = isReviewMode && isSelected && correctAnswer?.objectId !== node.id;
64
- return (_jsx(SceneObjectButton, { node: node, isSelected: isSelected, isCorrect: isCorrectNode, isWrong: isWrongNode, isReviewMode: isReviewMode, onClick: () => handleObjectClick(node.id) }, node.id));
65
- })] })) : (_jsx("div", { className: "flex h-64 items-center justify-center text-sm text-gray-400", children: "Ch\u01B0a c\u00F3 \u1EA3nh n\u1EC1n" })) })] }), hasAnswered && selectedNode && (_jsxs("div", { className: cn('flex items-center gap-2 rounded-lg border p-3', isReviewMode
66
- ? isAnswerCorrect
67
- ? 'border-green-200 bg-green-50'
68
- : 'border-red-200 bg-red-50'
69
- : 'border-blue-200 bg-blue-50'), children: [_jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["\u0110\u00E3 ch\u1ECDn: ", _jsx("strong", { children: selectedNode.label })] }), isReviewMode && (_jsx("span", { className: cn('text-xs font-semibold', isAnswerCorrect ? 'text-green-600' : 'text-red-600'), children: isAnswerCorrect ? '✓ Chính xác' : '✗ Không chính xác' }))] })), !isReviewMode && !hasAnswered && (_jsxs("div", { className: "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: "Nghe audio v\u00E0 nh\u1EA5p v\u00E0o \u0111\u1ED1i t\u01B0\u1EE3ng \u0111\u00FAng trong h\u00ECnh" })] })), isReviewMode && explanation && (_jsxs("div", { className: "rounded-lg border border-indigo-200 bg-indigo-50 p-4", children: [_jsx("p", { className: "text-sm font-semibold text-indigo-800", children: "Gi\u1EA3i th\u00EDch:" }), _jsx("p", { className: "mt-1 text-sm text-indigo-700", children: explanation })] }))] })] })] }));
70
- }
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useState as t,useEffect as d}from"react";import{Check as l,X as s,CircleHelp as n,Volume2 as i}from"lucide-react";import{usePresignedFileUrl as a}from"../../../../shared/lib/hooks";import{cn as o}from"../../../../shared/lib/utils";function c({node:t,isSelected:d,isCorrect:n,isWrong:i,isReviewMode:c,onClick:b}){const{previewUrl:m}=a(t.imageUrl||"");return r("button",{type:"button",onClick:b,disabled:c,className:o("absolute cursor-pointer rounded-lg transition-all duration-200 overflow-hidden","hover:scale-105","focus:outline-none focus:ring-2 focus:ring-offset-1",!d&&!n&&!i&&"border-2 border-transparent hover:border-blue-300 focus:ring-blue-400",d&&!c&&"border-3 border-blue-500 ring-2 ring-blue-200 shadow-lg shadow-blue-200/50 scale-105",n&&"border-3 border-green-500 ring-2 ring-green-200 shadow-lg shadow-green-200/50 scale-105",i&&"border-3 border-red-500 ring-2 ring-red-200 shadow-lg shadow-red-200/50 scale-105",c&&"cursor-default hover:scale-100"),style:{left:`${t.x}%`,top:`${t.y}%`,width:`${t.width}%`,height:`${t.height}%`},title:c?t.label:`Chọn ${t.label}`,children:[m?e("img",{src:m,alt:t.label,className:"h-full w-full object-fill pointer-events-none",draggable:!1}):e("div",{className:"flex h-full w-full items-center justify-center bg-gray-100/80 text-xs text-gray-500",children:t.label}),c&&(n||i)&&e("div",{className:o("absolute -top-2 -right-2 flex h-5 w-5 items-center justify-center rounded-full shadow-md",n?"bg-green-500":"bg-red-500"),style:{zIndex:1},children:e(n?l:s,{className:"h-3 w-3 text-white"})})]})}export function ListenAndChooseObjectsInSceneClient({questionData:b,onSubmit:m,isReviewMode:h=!1,userAnswer:g,correctAnswer:u,explanation:f}){const[x,p]=t(g?.objectId||""),{previewUrl:w,isLoading:v}=a(b.backgroundImageUrl||""),{previewUrl:N}=a(b.audioUrl||"");d(()=>{g?.objectId&&p(g.objectId)},[g]);const y=""!==x,j=b.objectNodes.find(e=>e.id===x),C=h&&u&&x===u.objectId;return r("div",{className:"space-y-4",children:[b.isBackup&&r("div",{className:"inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm",children:[e("span",{children:"🔄"})," Câu hỏi dự phòng"]}),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-blue-500 to-indigo-600 text-white shadow-sm",children:e(n,{className:"h-4 w-4"})}),e("span",{className:"text-sm font-medium text-gray-700",children:"Nghe và chọn đối tượng trong cảnh"})]}),h&&y&&r("div",{className:o("flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold",C?"bg-green-100 text-green-700":"bg-red-100 text-red-700"),children:[e(C?l:s,{className:"h-3.5 w-3.5"}),C?"Đúng":"Sai"]})]}),r("div",{className:"space-y-5 p-5",children:[N&&r("div",{className:"flex items-center gap-3 rounded-lg border border-blue-100 bg-gradient-to-r from-blue-50 to-indigo-50 p-3",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-blue-500 text-white",children:e(i,{className:"h-4 w-4"})}),e("audio",{controls:!0,className:"h-8 w-full",src:N,children:"Trình duyệt không hỗ trợ audio."})]}),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:"Scene"})]})}),e("div",{className:"relative p-2",children:v?e("div",{className:"flex h-64 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"})}):w?r("div",{className:"relative w-full",children:[e("img",{src:w,alt:"Scene background",className:"w-full rounded-md object-contain",draggable:!1}),b.objectNodes.map(r=>{const t=x===r.id,d=h&&u?.objectId===r.id,l=h&&t&&u?.objectId!==r.id;return e(c,{node:r,isSelected:t,isCorrect:d,isWrong:l,isReviewMode:h,onClick:()=>{return e=r.id,void(h||(p(e),m?.({objectId:e})));var e}},r.id)})]}):e("div",{className:"flex h-64 items-center justify-center text-sm text-gray-400",children:"Chưa có ảnh nền"})})]}),y&&j&&r("div",{className:o("flex items-center gap-2 rounded-lg border p-3",h?C?"border-green-200 bg-green-50":"border-red-200 bg-red-50":"border-blue-200 bg-blue-50"),children:[r("span",{className:"text-sm font-medium text-gray-700",children:["Đã chọn: ",e("strong",{children:j.label})]}),h&&e("span",{className:o("text-xs font-semibold",C?"text-green-600":"text-red-600"),children:C?"✓ Chính xác":"✗ Không chính xác"})]}),!h&&!y&&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:"Nghe audio và nhấp vào đối tượng đúng trong hình"})]}),h&&f&&r("div",{className:"rounded-lg border border-indigo-200 bg-indigo-50 p-4",children:[e("p",{className:"text-sm font-semibold text-indigo-800",children:"Giải thích:"}),e("p",{className:"mt-1 text-sm text-indigo-700",children:f})]})]})]})]})}
@@ -1,386 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useRef, useEffect, useCallback } from 'react';
4
- import { Button } from '../../../../components/ui/button';
5
- import { Label } from '../../../../components/ui/label';
6
- import { Input } from '../../../../components/ui/input';
7
- import { Textarea } from '../../../../components/ui/textarea';
8
- import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
9
- import { PointsInput } from '../../../../components/ui/points-input';
10
- import { RadioGroup, RadioGroupItem } from '../../../../components/ui/radio-group';
11
- import { Pencil, Eye, Plus, Trash2, Image as ImageIcon, Volume2, Lightbulb, MousePointerClick, GripVertical, ListChecks, BookOpen, } from 'lucide-react';
12
- import { FileUpload } from '../../../../components/ui/file-upload';
13
- import { usePresignedFileUrl, useDebouncedCallback } from '../../../../shared/lib/hooks';
14
- import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
15
- import { listenAndChooseObjectsInSceneFormSchema, } from '../../../../shared/lib/validation/listen-and-choose-objects-in-scene.validation';
16
- import { ListenAndChooseObjectsInSceneClient } from './ListenAndChooseObjectsInSceneClient';
17
- import { useSceneQuestionGroup } from '../../_shared/hooks/useSceneQuestionGroup';
18
- import { generateObjectId } from '../../_shared/utils/id-generator';
19
- import { useExamQuestionStore } from '../../../../shared/lib/stores/examQuestionStore';
20
- import { CreatorGuide } from '../../components/CreatorGuide';
21
- /**
22
- * Scene Object Editor - An individual object node editor row
23
- */
24
- function SceneObjectEditor({ node, index, isCorrect, onUpdate, onRemove, onSelectCorrect, errors, }) {
25
- const [objectPreviewUrl, setObjectPreviewUrl] = useState('');
26
- const { previewUrl: presignedObjectUrl } = usePresignedFileUrl(node.imageUrl || '');
27
- useEffect(() => {
28
- if (presignedObjectUrl) {
29
- setObjectPreviewUrl(presignedObjectUrl);
30
- }
31
- }, [presignedObjectUrl]);
32
- return (_jsx("div", { className: `rounded-lg border-2 p-4 transition-all ${isCorrect
33
- ? 'border-green-300 bg-green-50/50'
34
- : 'border-gray-200 bg-white hover:border-gray-300'}`, children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsxs("div", { className: "flex flex-col items-center gap-1 pt-1", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 text-sm font-bold text-white shadow-sm", children: index + 1 }), _jsx(GripVertical, { className: "h-4 w-4 text-gray-400" })] }), _jsxs("div", { className: "flex-1 space-y-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "flex-1", children: [_jsx(Label, { className: "text-xs font-medium text-gray-500", children: "T\u00EAn \u0111\u1ED1i t\u01B0\u1EE3ng" }), _jsx(Input, { value: node.label, onChange: (e) => onUpdate(node.id, { label: e.target.value }), placeholder: "V\u00ED d\u1EE5: cow, frog, horse...", className: `mt-1 ${errors?.label ? 'border-red-500' : 'border-gray-200'}` }), errors?.label && _jsx("p", { className: "mt-1 text-xs text-red-500", children: errors.label })] }), _jsx("div", { className: "flex items-center gap-2 pt-5" })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-xs font-medium text-gray-500", children: "H\u00ECnh \u1EA3nh \u0111\u1ED1i t\u01B0\u1EE3ng" }), _jsxs("div", { className: "mt-1 flex items-center gap-3", children: [_jsxs("div", { className: "flex-1", children: [_jsx(FileUpload, { id: `object-image-${node.id}`, label: "", accept: "image/*", value: node.imageUrl || '', onChange: (url) => onUpdate(node.id, { imageUrl: url }), onPresignedUrlChange: setObjectPreviewUrl, maxSize: 5, placeholder: "Upload h\u00ECnh \u1EA3nh \u0111\u1ED1i t\u01B0\u1EE3ng", autoUpload: true, prefix: "questions/objects" }), errors?.imageUrl && _jsx("p", { className: "mt-1 text-xs text-red-500", children: errors.imageUrl })] }), objectPreviewUrl && (_jsx("div", { className: "h-16 w-16 flex-shrink-0 overflow-hidden rounded-lg border border-gray-200 bg-gray-50", children: _jsx("img", { src: objectPreviewUrl, alt: node.label || 'Object', className: "h-full w-full object-contain" }) }))] })] }), _jsxs("div", { className: "grid grid-cols-4 gap-2", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-xs font-medium text-gray-500", children: "X (%)" }), _jsx(Input, { type: "number", min: 0, max: 100, value: node.x, onChange: (e) => onUpdate(node.id, { x: parseFloat(e.target.value) || 0 }), className: "mt-1 border-gray-200" })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-xs font-medium text-gray-500", children: "Y (%)" }), _jsx(Input, { type: "number", min: 0, max: 100, value: node.y, onChange: (e) => onUpdate(node.id, { y: parseFloat(e.target.value) || 0 }), className: "mt-1 border-gray-200" })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-xs font-medium text-gray-500", children: "Width (%)" }), _jsx(Input, { type: "number", min: 1, max: 100, value: node.width, onChange: (e) => onUpdate(node.id, { width: parseFloat(e.target.value) || 10 }), className: "mt-1 border-gray-200" })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-xs font-medium text-gray-500", children: "Height (%)" }), _jsx(Input, { type: "number", min: 1, max: 100, value: node.height, onChange: (e) => onUpdate(node.id, { height: parseFloat(e.target.value) || 10 }), className: "mt-1 border-gray-200" })] })] })] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => onRemove(node.id), className: "mt-1 text-red-400 hover:bg-red-50 hover:text-red-600", children: _jsx(Trash2, { className: "h-4 w-4" }) })] }) }));
35
- }
36
- /**
37
- * Drag overlay for positioning objects on the background image
38
- */
39
- function ScenePreviewWithDrag({ backgroundUrl, objectNodes, correctObjectId, onNodeDrag, onNodeResize, }) {
40
- const containerRef = useRef(null);
41
- const [draggingId, setDraggingId] = useState(null);
42
- const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
43
- const [resizingId, setResizingId] = useState(null);
44
- const [resizeType, setResizeType] = useState(null);
45
- const [resizeStartPos, setResizeStartPos] = useState({ x: 0, y: 0 });
46
- const [resizeStartDim, setResizeStartDim] = useState({ width: 0, height: 0 });
47
- const handleMouseDown = (nodeId, nodeX, nodeY, e) => {
48
- e.preventDefault();
49
- if (e.target.dataset.resizeHandle)
50
- return; // Ignore drag if clicking resize handle
51
- setDraggingId(nodeId);
52
- if (containerRef.current) {
53
- const rect = containerRef.current.getBoundingClientRect();
54
- const mouseXPercent = ((e.clientX - rect.left) / rect.width) * 100;
55
- const mouseYPercent = ((e.clientY - rect.top) / rect.height) * 100;
56
- setDragOffset({
57
- x: mouseXPercent - nodeX,
58
- y: mouseYPercent - nodeY,
59
- });
60
- }
61
- };
62
- const handleResizeMouseDown = (nodeId, type, nodeWidth, nodeHeight, e) => {
63
- e.preventDefault();
64
- e.stopPropagation();
65
- setResizingId(nodeId);
66
- setResizeType(type);
67
- setResizeStartPos({ x: e.clientX, y: e.clientY });
68
- setResizeStartDim({ width: nodeWidth, height: nodeHeight });
69
- };
70
- const handleMouseMove = useCallback((e) => {
71
- if (!containerRef.current)
72
- return;
73
- if (draggingId) {
74
- const rect = containerRef.current.getBoundingClientRect();
75
- const mouseXPercent = ((e.clientX - rect.left) / rect.width) * 100;
76
- const mouseYPercent = ((e.clientY - rect.top) / rect.height) * 100;
77
- const x = Math.max(0, Math.min(100, mouseXPercent - dragOffset.x));
78
- const y = Math.max(0, Math.min(100, mouseYPercent - dragOffset.y));
79
- onNodeDrag(draggingId, Math.round(x * 10) / 10, Math.round(y * 10) / 10);
80
- }
81
- else if (resizingId && resizeType) {
82
- const rect = containerRef.current.getBoundingClientRect();
83
- const dx = ((e.clientX - resizeStartPos.x) / rect.width) * 100;
84
- const dy = ((e.clientY - resizeStartPos.y) / rect.height) * 100;
85
- let newWidth = resizeStartDim.width;
86
- let newHeight = resizeStartDim.height;
87
- if (resizeType === 'e' || resizeType === 'se') {
88
- newWidth = Math.max(1, resizeStartDim.width + dx);
89
- }
90
- if (resizeType === 's' || resizeType === 'se') {
91
- newHeight = Math.max(1, resizeStartDim.height + dy);
92
- }
93
- onNodeResize(resizingId, Math.round(newWidth * 10) / 10, Math.round(newHeight * 10) / 10);
94
- }
95
- }, [draggingId, resizingId, resizeType, resizeStartPos, resizeStartDim, dragOffset, onNodeDrag, onNodeResize]);
96
- const handleMouseUp = useCallback(() => {
97
- setDraggingId(null);
98
- setResizingId(null);
99
- setResizeType(null);
100
- }, []);
101
- useEffect(() => {
102
- if (draggingId || resizingId) {
103
- window.addEventListener('mousemove', handleMouseMove);
104
- window.addEventListener('mouseup', handleMouseUp);
105
- return () => {
106
- window.removeEventListener('mousemove', handleMouseMove);
107
- window.removeEventListener('mouseup', handleMouseUp);
108
- };
109
- }
110
- }, [draggingId, resizingId, handleMouseMove, handleMouseUp]);
111
- return (_jsxs("div", { ref: containerRef, className: "relative w-full select-none", children: [_jsx("img", { src: backgroundUrl, alt: "Scene background", className: "w-full rounded-md object-contain", draggable: false }), objectNodes.map((node) => {
112
- const isCorrect = node.id === correctObjectId;
113
- const isActive = draggingId === node.id || resizingId === node.id;
114
- return (_jsxs("div", { className: `absolute cursor-move rounded-md border-2 transition-colors group ${isActive
115
- ? 'border-blue-500 shadow-lg'
116
- : isCorrect
117
- ? 'border-green-400 shadow-md'
118
- : 'border-gray-300 hover:border-blue-400'}`, style: {
119
- left: `${node.x}%`,
120
- top: `${node.y}%`,
121
- width: `${node.width}%`,
122
- height: `${node.height}%`,
123
- zIndex: isActive ? 2 : 1,
124
- }, onMouseDown: (e) => handleMouseDown(node.id, node.x, node.y, e), children: [_jsxs("div", { className: "absolute -top-5 left-0 rounded bg-black/70 px-1.5 py-0.5 text-[10px] font-medium text-white whitespace-nowrap", children: [node.label || `Obj ${objectNodes.indexOf(node) + 1}`, isCorrect && ' ✓'] }), _jsx("div", { className: "flex h-full w-full items-center justify-center overflow-hidden rounded bg-transparent", children: node.imageUrl ? (_jsx(PresignedImage, { url: node.imageUrl, alt: node.label })) : (_jsx("div", { className: "flex bg-white/30 p-2 rounded items-center justify-center", children: _jsx(ImageIcon, { className: "h-4 w-4 text-gray-400" }) })) }), _jsx("div", { "data-resize-handle": "true", className: "absolute -right-1.5 top-1/2 h-3 w-3 -translate-y-1/2 cursor-e-resize rounded-full border border-blue-500 bg-white opacity-0 transition-opacity group-hover:opacity-100 hover:opacity-100", style: { opacity: isActive ? 1 : undefined }, onMouseDown: (e) => handleResizeMouseDown(node.id, 'e', node.width, node.height, e) }), _jsx("div", { "data-resize-handle": "true", className: "absolute -bottom-1.5 left-1/2 h-3 w-3 -translate-x-1/2 cursor-s-resize rounded-full border border-blue-500 bg-white opacity-0 transition-opacity group-hover:opacity-100 hover:opacity-100", style: { opacity: isActive ? 1 : undefined }, onMouseDown: (e) => handleResizeMouseDown(node.id, 's', node.width, node.height, e) }), _jsx("div", { "data-resize-handle": "true", className: "absolute -bottom-1.5 -right-1.5 h-3 w-3 cursor-se-resize rounded-full border border-blue-500 bg-white opacity-0 transition-opacity group-hover:opacity-100 hover:opacity-100", style: { opacity: isActive ? 1 : undefined }, onMouseDown: (e) => handleResizeMouseDown(node.id, 'se', node.width, node.height, e) })] }, node.id));
125
- })] }));
126
- }
127
- /** Helper component for presigned image display */
128
- function PresignedImage({ url, alt }) {
129
- const { previewUrl } = usePresignedFileUrl(url);
130
- if (!previewUrl)
131
- return _jsx(ImageIcon, { className: "h-4 w-4 text-gray-400" });
132
- return (_jsx("img", { src: previewUrl, alt: alt, className: "h-full w-full object-fill pointer-events-none", draggable: false }));
133
- }
134
- /**
135
- * ListenAndChooseObjectsInSceneCreator
136
- *
137
- * Admin creator component for the "Listen and Choose Objects in Scene" question type.
138
- * Allows uploading a background scene, adding object images with positions,
139
- * and selecting the correct answer.
140
- */
141
- function ListenAndChooseObjectsInSceneCreatorContent({ initialData, onSave, onSaveToQuestionBank, onCancel, onChange, onUnsavedChangesChange, validationRef, externalErrors, groups, questionIndexInPart = 0, partId = '', }) {
142
- console.log('[ListenAndChooseObjects_Creator] RENDER', { initialData, questionIndexInPart, partId, groups });
143
- // Get examId from global exam store for audio upload API
144
- const examId = useExamQuestionStore((state) => state.examId);
145
- // ============ SHARED GROUP DATA (via useSceneQuestionGroup hook) ============
146
- const sceneGroupInitialData = {
147
- backgroundImageUrl: initialData?.backgroundImageUrl || '',
148
- objectNodes: initialData?.objectNodes || [],
149
- };
150
- const { isGrouped, currentGroupNumber, backgroundImageUrl, objectNodes, setBackgroundImageUrl, setObjectNodes, } = useSceneQuestionGroup({
151
- partId,
152
- groups,
153
- questionIndexInPart,
154
- initialData: sceneGroupInitialData,
155
- });
156
- // ============ STATE ============
157
- // Audio, Points, CorrectObjectId, Explanation are SPECIFIC to the question
158
- const [audioUrl, setAudioUrl] = useState(initialData?.audioUrl || '');
159
- const [correctObjectId, setCorrectObjectId] = useState(initialData?.correctObjectId || '');
160
- const [points, setPoints] = useState(initialData?.points || 1);
161
- const [explanation, setExplanation] = useState(initialData?.explanation || '');
162
- const [isBackup, setIsBackup] = useState(initialData?.isBackup || false);
163
- // UI State
164
- const [isClientMode, setIsClientMode] = useState(false);
165
- const [backgroundPreviewUrl, setBackgroundPreviewUrl] = useState('');
166
- const [audioPreviewUrl, setAudioPreviewUrl] = useState('');
167
- // Use react-hook-form with Zod validation
168
- const form = useReactHookForm({
169
- defaultValues: {
170
- audioUrl,
171
- backgroundImageUrl,
172
- objectNodes,
173
- correctObjectId,
174
- isBackup,
175
- },
176
- zodSchema: listenAndChooseObjectsInSceneFormSchema,
177
- mode: 'onBlur',
178
- });
179
- // Refs
180
- const previousInitialDataRef = useRef(initialData);
181
- const isInitializingRef = useRef(true);
182
- const onChangeRef = useRef(onChange);
183
- const hasInitializedRef = useRef(false);
184
- // Presigned URLs
185
- const { previewUrl: presignedBgUrl } = usePresignedFileUrl(backgroundImageUrl);
186
- const { previewUrl: presignedAudioUrl } = usePresignedFileUrl(audioUrl);
187
- useEffect(() => {
188
- if (presignedBgUrl)
189
- setBackgroundPreviewUrl(presignedBgUrl);
190
- }, [presignedBgUrl]);
191
- useEffect(() => {
192
- if (presignedAudioUrl)
193
- setAudioPreviewUrl(presignedAudioUrl);
194
- }, [presignedAudioUrl]);
195
- // Update onChange ref
196
- useEffect(() => {
197
- onChangeRef.current = onChange;
198
- }, [onChange]);
199
- // Debounced onChange
200
- const debouncedOnChange = useDebouncedCallback((data) => {
201
- onChangeRef.current?.(data);
202
- }, 300);
203
- // Expose validation function via ref
204
- useEffect(() => {
205
- if (validationRef) {
206
- validationRef.current = {
207
- triggerValidation: async () => {
208
- return validateForm();
209
- },
210
- getFormData: () => ({
211
- audioUrl,
212
- backgroundImageUrl,
213
- objectNodes,
214
- correctObjectId,
215
- points,
216
- explanation,
217
- isBackup,
218
- }),
219
- };
220
- }
221
- }, [validationRef, audioUrl, backgroundImageUrl, objectNodes, correctObjectId, points, explanation, isBackup]);
222
- // Sync question-specific state from initialData
223
- useEffect(() => {
224
- if (!initialData) {
225
- if (!hasInitializedRef.current) {
226
- hasInitializedRef.current = true;
227
- isInitializingRef.current = false;
228
- }
229
- return;
230
- }
231
- const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
232
- // On first mount, even if initialData hasn't changed from the ref's initial value,
233
- // we still need to initialize the flags so onChange can fire when user edits.
234
- if (!initialDataChanged && hasInitializedRef.current)
235
- return;
236
- previousInitialDataRef.current = initialData;
237
- const newAudioUrl = initialData?.audioUrl || '';
238
- const newCorrect = initialData?.correctObjectId || '';
239
- const newPoints = initialData?.points || 1;
240
- const newExplanation = initialData?.explanation || '';
241
- const newIsBackup = initialData?.isBackup || false;
242
- // Always apply new data when initialData changes (e.g. navigation)
243
- isInitializingRef.current = true;
244
- hasInitializedRef.current = true;
245
- setAudioUrl(newAudioUrl);
246
- setCorrectObjectId(newCorrect);
247
- setPoints(newPoints);
248
- setExplanation(newExplanation);
249
- setIsBackup(newIsBackup);
250
- // Give React time to batch before allowing onChange
251
- setTimeout(() => {
252
- isInitializingRef.current = false;
253
- }, 150);
254
- }, [initialData]);
255
- // Call onChange when form data changes
256
- useEffect(() => {
257
- if (isInitializingRef.current || !hasInitializedRef.current)
258
- return;
259
- const payload = {
260
- audioUrl,
261
- backgroundImageUrl,
262
- objectNodes,
263
- correctObjectId,
264
- points,
265
- explanation,
266
- isBackup,
267
- groups,
268
- groupNumber: currentGroupNumber,
269
- };
270
- debouncedOnChange(payload);
271
- }, [audioUrl, backgroundImageUrl, objectNodes, correctObjectId, points, explanation, isBackup, groups, currentGroupNumber, debouncedOnChange]);
272
- // ============ VALIDATION ============
273
- const validateForm = async () => {
274
- // Sync React state to react-hook-form before validation
275
- form.setValue('audioUrl', audioUrl);
276
- form.setValue('backgroundImageUrl', backgroundImageUrl);
277
- form.setValue('objectNodes', objectNodes);
278
- form.setValue('correctObjectId', correctObjectId);
279
- form.setValue('isBackup', isBackup);
280
- // Trigger Zod validation
281
- const isValid = await form.trigger();
282
- const formattedErrors = [];
283
- // Check custom business logic
284
- let isCustomValid = true;
285
- if (correctObjectId && !objectNodes.find((n) => n.id === correctObjectId)) {
286
- formattedErrors.push('Đáp án đúng không tồn tại trong danh sách đối tượng!');
287
- isCustomValid = false;
288
- }
289
- if (!isValid || !isCustomValid) {
290
- if (formattedErrors.length > 0) {
291
- // Set errors on form as a single merged string so we can display it simply
292
- form.setError('root', { message: formattedErrors.join('\n') });
293
- }
294
- return false;
295
- }
296
- form.clearErrors('root');
297
- return true;
298
- };
299
- const internalHandleSave = (callback) => {
300
- validateForm().then((isValid) => {
301
- if (isValid) {
302
- callback?.({
303
- audioUrl,
304
- backgroundImageUrl,
305
- objectNodes,
306
- correctObjectId,
307
- points,
308
- explanation,
309
- isBackup,
310
- groups,
311
- groupNumber: currentGroupNumber,
312
- });
313
- }
314
- });
315
- };
316
- // ============ OBJECT NODE HANDLERS ============
317
- const addObjectNode = () => {
318
- const newNode = {
319
- id: generateObjectId(),
320
- imageUrl: '',
321
- label: '',
322
- x: 20 + (objectNodes.length * 15) % 60,
323
- y: 30 + (objectNodes.length * 10) % 40,
324
- width: 12,
325
- height: 15,
326
- };
327
- setObjectNodes([...objectNodes, newNode]);
328
- form.clearErrors();
329
- };
330
- const updateObjectNode = (id, updates) => {
331
- setObjectNodes(objectNodes.map((node) => (node.id === id ? { ...node, ...updates } : node)));
332
- form.clearErrors();
333
- };
334
- const removeObjectNode = (id) => {
335
- setObjectNodes(objectNodes.filter((node) => node.id !== id));
336
- if (correctObjectId === id) {
337
- setCorrectObjectId('');
338
- }
339
- form.clearErrors();
340
- };
341
- const handleNodeDrag = useCallback((id, x, y) => {
342
- setObjectNodes((prev) => prev.map((node) => (node.id === id ? { ...node, x, y } : node)));
343
- }, []);
344
- const handleNodeResize = useCallback((id, width, height) => {
345
- setObjectNodes((prev) => prev.map((node) => (node.id === id ? { ...node, width, height } : node)));
346
- }, []);
347
- // ============ RENDER ============
348
- // Preview mode
349
- if (isClientMode) {
350
- return (_jsxs("div", { className: "space-y-6", children: [_jsx(Card, { children: _jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx(CardTitle, { children: "Nghe v\u00E0 ch\u1ECDn \u0111\u1ED1i t\u01B0\u1EE3ng (Preview)" }), _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 ch\u1EC9nh s\u1EEDa"] })] }) }) }), _jsx(ListenAndChooseObjectsInSceneClient, { questionData: {
351
- audioUrl: audioPreviewUrl || audioUrl,
352
- backgroundImageUrl: backgroundPreviewUrl || backgroundImageUrl,
353
- objectNodes,
354
- }, isReviewMode: true, userAnswer: correctObjectId ? { objectId: correctObjectId } : undefined, correctAnswer: correctObjectId ? { objectId: correctObjectId } : undefined, explanation: explanation })] }));
355
- }
356
- const formErrors = form.formState.errors;
357
- // Creator mode
358
- return (_jsxs("div", { className: "space-y-6", children: [_jsx(CreatorGuide, { colorScheme: "purple", steps: [
359
- { text: 'Upload ảnh nền (Scene) — ảnh này dùng chung cho tất cả câu hỏi trong nhóm' },
360
- { text: 'Nhấn "Thêm đối tượng" để tạo các object trên ảnh nền. Mỗi đối tượng cần có tên và hình ảnh riêng' },
361
- { text: 'Kéo thả đối tượng trên ảnh nền để điều chỉnh vị trí, hoặc nhập tọa độ X, Y, Width, Height thủ công' },
362
- { text: 'Upload audio câu hỏi — học sinh sẽ nghe audio rồi chọn đúng đối tượng trên ảnh' },
363
- { text: 'Chọn đáp án đúng cho câu hỏi này từ danh sách đối tượng đã tạo', highlight: true },
364
- { text: 'Thêm giải thích (không bắt buộc) — sẽ hiển thị sau khi học sinh trả lời' },
365
- ], tips: [
366
- 'Số đối tượng nên nhiều hơn số câu hỏi trong part để tạo lựa chọn nhiễu. Đặt tên rõ ràng và chọn hình ảnh dễ phân biệt giữa các đối tượng',
367
- 'Kéo thả trực tiếp trên ảnh nền sẽ nhanh hơn nhập tọa độ thủ công',
368
- ] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-purple-100/50", children: [_jsx("div", { className: "h-1.5 bg-gradient-to-r from-purple-500 via-violet-500 to-fuchsia-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-purple-500 to-violet-600 shadow-md", children: _jsx(ListChecks, { className: "h-5 w-5 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-xl font-bold text-gray-900", children: "Nh\u00F3m \u0111\u00E1p \u00E1n" }), _jsxs("div", { className: "mt-1.5 inline-flex items-center gap-1.5 rounded-full bg-purple-100 px-3 py-1 text-sm font-medium text-purple-700", children: [_jsx("span", { children: "\uD83D\uDDBC\uFE0F" }), _jsxs("span", { children: ["Nh\u00F3m ", currentGroupNumber] }), _jsx("span", { className: "text-xs font-normal opacity-75 ml-1", children: "(D\u00F9ng chung)" })] })] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(true), disabled: !backgroundImageUrl || objectNodes.length < 2, className: "gap-2 border-purple-200 text-purple-600 hover:bg-purple-50 hover:text-purple-700", children: [_jsx(Eye, { className: "h-4 w-4" }), "Preview"] })] }) })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-purple-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-purple-400 to-violet-400" }), _jsx(CardHeader, { className: "pb-4", 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-purple-100", children: _jsx(ImageIcon, { className: "h-3.5 w-3.5 text-purple-600" }) }), _jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "\u1EA2nh n\u1EC1n & \u0110\u1ED1i t\u01B0\u1EE3ng trong Scene" })] }) }), _jsxs(CardContent, { className: "space-y-6", children: [_jsx("div", { className: `rounded-xl border-2 p-5 bg-gradient-to-r from-blue-50 to-indigo-50 border-blue-200`, children: _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-blue-800", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-blue-600" }), "\u1EA2nh n\u1EC1n (Scene) ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FileUpload, { id: "scene-background", label: "", accept: "image/*", value: backgroundImageUrl, onChange: (url) => {
369
- setBackgroundImageUrl(url);
370
- form.clearErrors();
371
- }, onPresignedUrlChange: setBackgroundPreviewUrl, maxSize: 10, placeholder: "Upload h\u00ECnh \u1EA3nh n\u1EC1n c\u1EA3nh", autoUpload: true, prefix: "questions/scenes" }), formErrors.backgroundImageUrl && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: formErrors.backgroundImageUrl.message })), backgroundPreviewUrl && (_jsxs("div", { className: "mt-2 overflow-hidden rounded-lg border border-gray-200 bg-gray-50 isolate", children: [_jsx("div", { className: "border-b border-gray-100 px-3 py-1.5", children: _jsx("span", { className: "text-xs font-medium text-gray-500", children: "\u1EA2nh n\u1EC1n - K\u00E9o th\u1EA3 \u0111\u1ED1i t\u01B0\u1EE3ng \u0111\u1EC3 \u0111\u1ECBnh v\u1ECB" }) }), _jsx("div", { className: "p-2", children: _jsx(ScenePreviewWithDrag, { backgroundUrl: backgroundPreviewUrl, objectNodes: objectNodes, correctObjectId: correctObjectId, onNodeDrag: handleNodeDrag, onNodeResize: handleNodeResize }) })] }))] }) }), _jsxs("div", { className: "pt-4 border-t border-gray-100", children: [_jsxs("div", { className: "flex items-center justify-between mb-4", 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-indigo-100", children: _jsx(MousePointerClick, { className: "h-3.5 w-3.5 text-indigo-600" }) }), _jsxs("h3", { className: "text-lg font-semibold text-gray-800", children: ["\u0110\u1ED1i t\u01B0\u1EE3ng (", objectNodes.length, " items)"] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: addObjectNode, className: "gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm \u0111\u1ED1i t\u01B0\u1EE3ng"] })] }), _jsx("div", { className: "space-y-3", children: objectNodes.length === 0 ? (_jsxs("div", { className: "flex flex-col items-center gap-3 rounded-xl border-2 border-dashed border-gray-200 bg-gray-50/50 py-10", children: [_jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-gray-100", children: _jsx(ImageIcon, { className: "h-6 w-6 text-gray-400" }) }), _jsx("p", { className: "text-sm text-gray-500", children: "Ch\u01B0a c\u00F3 \u0111\u1ED1i t\u01B0\u1EE3ng n\u00E0o. Nh\u1EA5n \"Th\u00EAm \u0111\u1ED1i t\u01B0\u1EE3ng\" \u0111\u1EC3 t\u1EA1o c\u00E1c \u0111\u1ED1i t\u01B0\u1EE3ng chung cho nh\u00F3m." })] })) : (objectNodes.map((node, index) => (_jsx(SceneObjectEditor, { node: node, index: index, isCorrect: false, onUpdate: updateObjectNode, onRemove: removeObjectNode, onSelectCorrect: () => { }, errors: {
372
- label: formErrors.objectNodes?.[index]?.label?.message,
373
- imageUrl: formErrors.objectNodes?.[index]?.imageUrl?.message,
374
- } }, node.id)))) })] })] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-green-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-green-400 to-emerald-400" }), _jsx(CardHeader, { className: "pb-4", 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-green-100", children: _jsx(Volume2, { className: "h-4 w-4 text-green-600" }) }), _jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "Th\u00F4ng tin c\u00E2u h\u1ECFi" })] }) }), _jsxs(CardContent, { className: "space-y-6 pt-2", children: [_jsx("div", { className: "rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-4", children: _jsxs("div", { className: "flex flex-col md:flex-row gap-6", children: [_jsx("div", { className: "flex-1", children: _jsx(PointsInput, { value: points, onChange: (e) => setPoints(parseFloat(e.target.value) || 1) }) }), _jsx("div", { className: "flex-1 flex flex-col justify-center" })] }) }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(Volume2, { className: "h-4 w-4 text-indigo-500" }), "Audio c\u00E2u h\u1ECFi ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FileUpload, { id: "scene-audio", label: "", accept: "audio/*", value: audioUrl, onChange: (url) => {
375
- setAudioUrl(url);
376
- form.clearErrors();
377
- }, onPresignedUrlChange: setAudioPreviewUrl, maxSize: 10, placeholder: "Upload file audio c\u00E2u h\u1ECFi", autoUpload: true, prefix: "questions/audio", examId: examId || undefined, partId: partId || undefined }), formErrors.audioUrl && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: formErrors.audioUrl.message })), audioPreviewUrl && (_jsx("audio", { controls: true, className: "mt-2 w-full", src: audioPreviewUrl, children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." }))] }), _jsxs("div", { className: "space-y-3", 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-green-100", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5 text-green-600" }) }), _jsxs(Label, { className: "text-base font-semibold text-gray-800", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng c\u1EE7a c\u00E2u h\u1ECFi n\u00E0y ", _jsx("span", { className: "text-red-500", children: "*" })] })] }), objectNodes.length === 0 ? (_jsx("p", { className: "text-sm text-amber-600 italic", children: "Vui l\u00F2ng th\u00EAm \u0111\u1ED1i t\u01B0\u1EE3ng \u1EDF b\u00EAn tr\u00EAn tr\u01B0\u1EDBc khi ch\u1ECDn \u0111\u00E1p \u00E1n." })) : (_jsx(RadioGroup, { value: correctObjectId, onValueChange: (val) => {
378
- setCorrectObjectId(val);
379
- form.clearErrors(); // Xóa lỗi validation khi người dùng đã chọn đáp án
380
- }, className: "grid gap-3 sm:grid-cols-2 md:grid-cols-3", children: objectNodes.map((node, index) => (_jsxs("label", { className: `flex cursor-pointer items-start gap-3 rounded-lg border-2 p-3 transition-all ${correctObjectId === node.id
381
- ? 'border-green-500 bg-green-50 ring-1 ring-green-500'
382
- : 'border-gray-200 hover:border-green-300 hover:bg-green-50/50'}`, children: [_jsx(RadioGroupItem, { value: node.id, id: `correct-${node.id}`, className: "mt-1 flex-shrink-0" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("p", { className: "font-medium text-gray-900 line-clamp-1 break-all", children: [index + 1, ". ", node.label || '(Chưa đặt tên)'] }), node.imageUrl ? (_jsx("div", { className: "mt-2 h-12 w-full max-w-16 rounded overflow-hidden bg-white border border-gray-200", children: _jsx(PresignedImage, { url: node.imageUrl, alt: node.label }) })) : (_jsx("div", { className: "mt-2 text-xs text-gray-400 italic", children: "Ch\u01B0a c\u00F3 h\u00ECnh" }))] })] }, node.id))) }))] }), formErrors.correctObjectId && (_jsx("div", { className: "rounded-lg border border-red-200 bg-red-50 p-3 mt-3", children: _jsxs("p", { className: "text-sm text-red-700", children: ["\u26A0\uFE0F ", formErrors.correctObjectId.message] }) }))] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-amber-400 to-orange-400" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm", children: _jsx(BookOpen, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-lg 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-sm 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, { children: _jsx(Textarea, { 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" }) })] })] }));
383
- }
384
- export function ListenAndChooseObjectsInSceneCreator(props) {
385
- return _jsx(ListenAndChooseObjectsInSceneCreatorContent, { ...props });
386
- }
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useState as t,useRef as a,useEffect as n,useCallback as i}from"react";import{Button as l}from"../../../../components/ui/button";import{Label as s}from"../../../../components/ui/label";import{Input as o}from"../../../../components/ui/input";import{Textarea as d}from"../../../../components/ui/textarea";import{Card as c,CardContent as h,CardHeader as m,CardTitle as g}from"../../../../components/ui/card";import{PointsInput as u}from"../../../../components/ui/points-input";import{RadioGroup as p,RadioGroupItem as b}from"../../../../components/ui/radio-group";import{Pencil as x,Eye as f,Plus as v,Trash2 as N,Image as w,Volume2 as y,Lightbulb as j,MousePointerClick as U,GripVertical as C,ListChecks as k,BookOpen as I}from"lucide-react";import{FileUpload as S}from"../../../../components/ui/file-upload";import{usePresignedFileUrl as z,useDebouncedCallback as O}from"../../../../shared/lib/hooks";import{useReactHookForm as D}from"../../../../shared/lib/hooks/useReactHookForm";import{listenAndChooseObjectsInSceneFormSchema as M}from"../../../../shared/lib/validation/listen-and-choose-objects-in-scene.validation";import{ListenAndChooseObjectsInSceneClient as E}from"./ListenAndChooseObjectsInSceneClient";import{useSceneQuestionGroup as B}from"../../_shared/hooks/useSceneQuestionGroup";import{generateObjectId as R}from"../../_shared/utils/id-generator";import{useExamQuestionStore as V}from"../../../../shared/lib/stores/examQuestionStore";import{CreatorGuide as P}from"../../components/CreatorGuide";function T({node:a,index:i,isCorrect:d,onUpdate:c,onRemove:h,onSelectCorrect:m,errors:g}){const[u,p]=t(""),{previewUrl:b}=z(a.imageUrl||"");return n(()=>{b&&p(b)},[b]),e("div",{className:"rounded-lg border-2 p-4 transition-all "+(d?"border-green-300 bg-green-50/50":"border-gray-200 bg-white hover:border-gray-300"),children:r("div",{className:"flex items-start gap-3",children:[r("div",{className:"flex flex-col items-center gap-1 pt-1",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 text-sm font-bold text-white shadow-sm",children:i+1}),e(C,{className:"h-4 w-4 text-gray-400"})]}),r("div",{className:"flex-1 space-y-3",children:[r("div",{className:"flex items-center gap-3",children:[r("div",{className:"flex-1",children:[e(s,{className:"text-xs font-medium text-gray-500",children:"Tên đối tượng"}),e(o,{value:a.label,onChange:e=>c(a.id,{label:e.target.value}),placeholder:"Ví dụ: cow, frog, horse...",className:"mt-1 "+(g?.label?"border-red-500":"border-gray-200")}),g?.label&&e("p",{className:"mt-1 text-xs text-red-500",children:g.label})]}),e("div",{className:"flex items-center gap-2 pt-5"})]}),r("div",{children:[e(s,{className:"text-xs font-medium text-gray-500",children:"Hình ảnh đối tượng"}),r("div",{className:"mt-1 flex items-center gap-3",children:[r("div",{className:"flex-1",children:[e(S,{id:`object-image-${a.id}`,label:"",accept:"image/*",value:a.imageUrl||"",onChange:e=>c(a.id,{imageUrl:e}),onPresignedUrlChange:p,maxSize:5,placeholder:"Upload hình ảnh đối tượng",autoUpload:!0,prefix:"questions/objects"}),g?.imageUrl&&e("p",{className:"mt-1 text-xs text-red-500",children:g.imageUrl})]}),u&&e("div",{className:"h-16 w-16 flex-shrink-0 overflow-hidden rounded-lg border border-gray-200 bg-gray-50",children:e("img",{src:u,alt:a.label||"Object",className:"h-full w-full object-contain"})})]})]}),r("div",{className:"grid grid-cols-4 gap-2",children:[r("div",{children:[e(s,{className:"text-xs font-medium text-gray-500",children:"X (%)"}),e(o,{type:"number",min:0,max:100,value:a.x,onChange:e=>c(a.id,{x:parseFloat(e.target.value)||0}),className:"mt-1 border-gray-200"})]}),r("div",{children:[e(s,{className:"text-xs font-medium text-gray-500",children:"Y (%)"}),e(o,{type:"number",min:0,max:100,value:a.y,onChange:e=>c(a.id,{y:parseFloat(e.target.value)||0}),className:"mt-1 border-gray-200"})]}),r("div",{children:[e(s,{className:"text-xs font-medium text-gray-500",children:"Width (%)"}),e(o,{type:"number",min:1,max:100,value:a.width,onChange:e=>c(a.id,{width:parseFloat(e.target.value)||10}),className:"mt-1 border-gray-200"})]}),r("div",{children:[e(s,{className:"text-xs font-medium text-gray-500",children:"Height (%)"}),e(o,{type:"number",min:1,max:100,value:a.height,onChange:e=>c(a.id,{height:parseFloat(e.target.value)||10}),className:"mt-1 border-gray-200"})]})]})]}),e(l,{variant:"ghost",size:"sm",onClick:()=>h(a.id),className:"mt-1 text-red-400 hover:bg-red-50 hover:text-red-600",children:e(N,{className:"h-4 w-4"})})]})})}function q({backgroundUrl:l,objectNodes:s,correctObjectId:o,onNodeDrag:d,onNodeResize:c}){const h=a(null),[m,g]=t(null),[u,p]=t({x:0,y:0}),[b,x]=t(null),[f,v]=t(null),[N,y]=t({x:0,y:0}),[j,U]=t({width:0,height:0}),C=(e,r,t,a,n)=>{n.preventDefault(),n.stopPropagation(),x(e),v(r),y({x:n.clientX,y:n.clientY}),U({width:t,height:a})},k=i(e=>{if(h.current)if(m){const r=h.current.getBoundingClientRect(),t=(e.clientX-r.left)/r.width*100,a=(e.clientY-r.top)/r.height*100,n=Math.max(0,Math.min(100,t-u.x)),i=Math.max(0,Math.min(100,a-u.y));d(m,Math.round(10*n)/10,Math.round(10*i)/10)}else if(b&&f){const r=h.current.getBoundingClientRect(),t=(e.clientX-N.x)/r.width*100,a=(e.clientY-N.y)/r.height*100;let n=j.width,i=j.height;"e"!==f&&"se"!==f||(n=Math.max(1,j.width+t)),"s"!==f&&"se"!==f||(i=Math.max(1,j.height+a)),c(b,Math.round(10*n)/10,Math.round(10*i)/10)}},[m,b,f,N,j,u,d,c]),I=i(()=>{g(null),x(null),v(null)},[]);return n(()=>{if(m||b)return window.addEventListener("mousemove",k),window.addEventListener("mouseup",I),()=>{window.removeEventListener("mousemove",k),window.removeEventListener("mouseup",I)}},[m,b,k,I]),r("div",{ref:h,className:"relative w-full select-none",children:[e("img",{src:l,alt:"Scene background",className:"w-full rounded-md object-contain",draggable:!1}),s.map(t=>{const a=t.id===o,n=m===t.id||b===t.id;return r("div",{className:"absolute cursor-move rounded-md border-2 transition-colors group "+(n?"border-blue-500 shadow-lg":a?"border-green-400 shadow-md":"border-gray-300 hover:border-blue-400"),style:{left:`${t.x}%`,top:`${t.y}%`,width:`${t.width}%`,height:`${t.height}%`,zIndex:n?2:1},onMouseDown:e=>((e,r,t,a)=>{if(a.preventDefault(),!a.target.dataset.resizeHandle&&(g(e),h.current)){const e=h.current.getBoundingClientRect(),n=(a.clientX-e.left)/e.width*100,i=(a.clientY-e.top)/e.height*100;p({x:n-r,y:i-t})}})(t.id,t.x,t.y,e),children:[r("div",{className:"absolute -top-5 left-0 rounded bg-black/70 px-1.5 py-0.5 text-[10px] font-medium text-white whitespace-nowrap",children:[t.label||`Obj ${s.indexOf(t)+1}`,a&&" ✓"]}),e("div",{className:"flex h-full w-full items-center justify-center overflow-hidden rounded bg-transparent",children:t.imageUrl?e(F,{url:t.imageUrl,alt:t.label}):e("div",{className:"flex bg-white/30 p-2 rounded items-center justify-center",children:e(w,{className:"h-4 w-4 text-gray-400"})})}),e("div",{"data-resize-handle":"true",className:"absolute -right-1.5 top-1/2 h-3 w-3 -translate-y-1/2 cursor-e-resize rounded-full border border-blue-500 bg-white opacity-0 transition-opacity group-hover:opacity-100 hover:opacity-100",style:{opacity:n?1:void 0},onMouseDown:e=>C(t.id,"e",t.width,t.height,e)}),e("div",{"data-resize-handle":"true",className:"absolute -bottom-1.5 left-1/2 h-3 w-3 -translate-x-1/2 cursor-s-resize rounded-full border border-blue-500 bg-white opacity-0 transition-opacity group-hover:opacity-100 hover:opacity-100",style:{opacity:n?1:void 0},onMouseDown:e=>C(t.id,"s",t.width,t.height,e)}),e("div",{"data-resize-handle":"true",className:"absolute -bottom-1.5 -right-1.5 h-3 w-3 cursor-se-resize rounded-full border border-blue-500 bg-white opacity-0 transition-opacity group-hover:opacity-100 hover:opacity-100",style:{opacity:n?1:void 0},onMouseDown:e=>C(t.id,"se",t.width,t.height,e)})]},t.id)})]})}function F({url:r,alt:t}){const{previewUrl:a}=z(r);return a?e("img",{src:a,alt:t,className:"h-full w-full object-fill pointer-events-none",draggable:!1}):e(w,{className:"h-4 w-4 text-gray-400"})}function L({initialData:o,onSave:N,onSaveToQuestionBank:C,onCancel:L,onChange:$,onUnsavedChangesChange:A,validationRef:G,externalErrors:H,groups:X,questionIndexInPart:Y=0,partId:K=""}){console.log("[ListenAndChooseObjects_Creator] RENDER",{initialData:o,questionIndexInPart:Y,partId:K,groups:X});const Q=V(e=>e.examId),_={backgroundImageUrl:o?.backgroundImageUrl||"",objectNodes:o?.objectNodes||[]},{isGrouped:J,currentGroupNumber:W,backgroundImageUrl:Z,objectNodes:ee,setBackgroundImageUrl:re,setObjectNodes:te}=B({partId:K,groups:X,questionIndexInPart:Y,initialData:_}),[ae,ne]=t(o?.audioUrl||""),[ie,le]=t(o?.correctObjectId||""),[se,oe]=t(o?.points||1),[de,ce]=t(o?.explanation||""),[he,me]=t(o?.isBackup||!1),[ge,ue]=t(!1),[pe,be]=t(""),[xe,fe]=t(""),ve=D({defaultValues:{audioUrl:ae,backgroundImageUrl:Z,objectNodes:ee,correctObjectId:ie,isBackup:he},zodSchema:M,mode:"onBlur"}),Ne=a(o),we=a(!0),ye=a($),je=a(!1),{previewUrl:Ue}=z(Z),{previewUrl:Ce}=z(ae);n(()=>{Ue&&be(Ue)},[Ue]),n(()=>{Ce&&fe(Ce)},[Ce]),n(()=>{ye.current=$},[$]);const ke=O(e=>{ye.current?.(e)},300);n(()=>{G&&(G.current={triggerValidation:async()=>Ie(),getFormData:()=>({audioUrl:ae,backgroundImageUrl:Z,objectNodes:ee,correctObjectId:ie,points:se,explanation:de,isBackup:he})})},[G,ae,Z,ee,ie,se,de,he]),n(()=>{if(!o)return void(je.current||(je.current=!0,we.current=!1));if(!(JSON.stringify(Ne.current)!==JSON.stringify(o))&&je.current)return;Ne.current=o;const e=o?.audioUrl||"",r=o?.correctObjectId||"",t=o?.points||1,a=o?.explanation||"",n=o?.isBackup||!1;we.current=!0,je.current=!0,ne(e),le(r),oe(t),ce(a),me(n),setTimeout(()=>{we.current=!1},150)},[o]),n(()=>{if(we.current||!je.current)return;ke({audioUrl:ae,backgroundImageUrl:Z,objectNodes:ee,correctObjectId:ie,points:se,explanation:de,isBackup:he,groups:X,groupNumber:W})},[ae,Z,ee,ie,se,de,he,X,W,ke]);const Ie=async()=>{ve.setValue("audioUrl",ae),ve.setValue("backgroundImageUrl",Z),ve.setValue("objectNodes",ee),ve.setValue("correctObjectId",ie),ve.setValue("isBackup",he);const e=await ve.trigger(),r=[];let t=!0;return ie&&!ee.find(e=>e.id===ie)&&(r.push("Đáp án đúng không tồn tại trong danh sách đối tượng!"),t=!1),e&&t?(ve.clearErrors("root"),!0):(r.length>0&&ve.setError("root",{message:r.join("\n")}),!1)},Se=(e,r)=>{te(ee.map(t=>t.id===e?{...t,...r}:t)),ve.clearErrors()},ze=e=>{te(ee.filter(r=>r.id!==e)),ie===e&&le(""),ve.clearErrors()},Oe=i((e,r,t)=>{te(a=>a.map(a=>a.id===e?{...a,x:r,y:t}:a))},[]),De=i((e,r,t)=>{te(a=>a.map(a=>a.id===e?{...a,width:r,height:t}:a))},[]);if(ge)return r("div",{className:"space-y-6",children:[e(c,{children:e(m,{children:r("div",{className:"flex items-center justify-between",children:[r("div",{children:[e(g,{children:"Nghe và chọn đối tượng (Preview)"}),e("p",{className:"mt-1 text-sm text-gray-500",children:"Chế độ xem trước - Hiển thị đáp án đúng"})]}),r(l,{variant:"outline",size:"sm",onClick:()=>ue(!1),children:[e(x,{className:"mr-2 h-4 w-4"}),"Quay lại chỉnh sửa"]})]})})}),e(E,{questionData:{audioUrl:xe||ae,backgroundImageUrl:pe||Z,objectNodes:ee},isReviewMode:!0,userAnswer:ie?{objectId:ie}:void 0,correctAnswer:ie?{objectId:ie}:void 0,explanation:de})]});const Me=ve.formState.errors;return r("div",{className:"space-y-6",children:[e(P,{colorScheme:"purple",steps:[{text:"Upload ảnh nền (Scene) — ảnh này dùng chung cho tất cả câu hỏi trong nhóm"},{text:'Nhấn "Thêm đối tượng" để tạo các object trên ảnh nền. Mỗi đối tượng cần có tên và hình ảnh riêng'},{text:"Kéo thả đối tượng trên ảnh nền để điều chỉnh vị trí, hoặc nhập tọa độ X, Y, Width, Height thủ công"},{text:"Upload audio câu hỏi — học sinh sẽ nghe audio rồi chọn đúng đối tượng trên ảnh"},{text:"Chọn đáp án đúng cho câu hỏi này từ danh sách đối tượng đã tạo",highlight:!0},{text:"Thêm giải thích (không bắt buộc) — sẽ hiển thị sau khi học sinh trả lời"}],tips:["Số đối tượng nên nhiều hơn số câu hỏi trong part để tạo lựa chọn nhiễu. Đặt tên rõ ràng và chọn hình ảnh dễ phân biệt giữa các đối tượng","Kéo thả trực tiếp trên ảnh nền sẽ nhanh hơn nhập tọa độ thủ công"]}),r(c,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-purple-100/50",children:[e("div",{className:"h-1.5 bg-gradient-to-r from-purple-500 via-violet-500 to-fuchsia-500"}),e(m,{className:"pb-4",children:r("div",{className:"flex items-center justify-between",children:[r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-purple-500 to-violet-600 shadow-md",children:e(k,{className:"h-5 w-5 text-white"})}),r("div",{children:[e(g,{className:"text-xl font-bold text-gray-900",children:"Nhóm đáp án"}),r("div",{className:"mt-1.5 inline-flex items-center gap-1.5 rounded-full bg-purple-100 px-3 py-1 text-sm font-medium text-purple-700",children:[e("span",{children:"🖼️"}),r("span",{children:["Nhóm ",W]}),e("span",{className:"text-xs font-normal opacity-75 ml-1",children:"(Dùng chung)"})]})]})]}),r(l,{variant:"outline",size:"sm",onClick:()=>ue(!0),disabled:!Z||ee.length<2,className:"gap-2 border-purple-200 text-purple-600 hover:bg-purple-50 hover:text-purple-700",children:[e(f,{className:"h-4 w-4"}),"Preview"]})]})})]}),r(c,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-purple-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-purple-400 to-violet-400"}),e(m,{className:"pb-4",children:r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-purple-100",children:e(w,{className:"h-3.5 w-3.5 text-purple-600"})}),e(g,{className:"text-lg font-semibold text-gray-800",children:"Ảnh nền & Đối tượng trong Scene"})]})}),r(h,{className:"space-y-6",children:[e("div",{className:"rounded-xl border-2 p-5 bg-gradient-to-r from-blue-50 to-indigo-50 border-blue-200",children:r("div",{className:"space-y-2",children:[r(s,{className:"flex items-center gap-2 text-base font-semibold text-blue-800",children:[e(w,{className:"h-4 w-4 text-blue-600"}),"Ảnh nền (Scene) ",e("span",{className:"text-red-500",children:"*"})]}),e(S,{id:"scene-background",label:"",accept:"image/*",value:Z,onChange:e=>{re(e),ve.clearErrors()},onPresignedUrlChange:be,maxSize:10,placeholder:"Upload hình ảnh nền cảnh",autoUpload:!0,prefix:"questions/scenes"}),Me.backgroundImageUrl&&e("p",{className:"mt-1 text-sm text-red-500",children:Me.backgroundImageUrl.message}),pe&&r("div",{className:"mt-2 overflow-hidden rounded-lg border border-gray-200 bg-gray-50 isolate",children:[e("div",{className:"border-b border-gray-100 px-3 py-1.5",children:e("span",{className:"text-xs font-medium text-gray-500",children:"Ảnh nền - Kéo thả đối tượng để định vị"})}),e("div",{className:"p-2",children:e(q,{backgroundUrl:pe,objectNodes:ee,correctObjectId:ie,onNodeDrag:Oe,onNodeResize:De})})]})]})}),r("div",{className:"pt-4 border-t border-gray-100",children:[r("div",{className:"flex items-center justify-between mb-4",children:[r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-indigo-100",children:e(U,{className:"h-3.5 w-3.5 text-indigo-600"})}),r("h3",{className:"text-lg font-semibold text-gray-800",children:["Đối tượng (",ee.length," items)"]})]}),r(l,{variant:"outline",size:"sm",onClick:()=>{const e={id:R(),imageUrl:"",label:"",x:20+15*ee.length%60,y:30+10*ee.length%40,width:12,height:15};te([...ee,e]),ve.clearErrors()},className:"gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700",children:[e(v,{className:"h-4 w-4"}),"Thêm đối tượng"]})]}),e("div",{className:"space-y-3",children:0===ee.length?r("div",{className:"flex flex-col items-center gap-3 rounded-xl border-2 border-dashed border-gray-200 bg-gray-50/50 py-10",children:[e("div",{className:"flex h-12 w-12 items-center justify-center rounded-full bg-gray-100",children:e(w,{className:"h-6 w-6 text-gray-400"})}),e("p",{className:"text-sm text-gray-500",children:'Chưa có đối tượng nào. Nhấn "Thêm đối tượng" để tạo các đối tượng chung cho nhóm.'})]}):ee.map((r,t)=>e(T,{node:r,index:t,isCorrect:!1,onUpdate:Se,onRemove:ze,onSelectCorrect:()=>{},errors:{label:Me.objectNodes?.[t]?.label?.message,imageUrl:Me.objectNodes?.[t]?.imageUrl?.message}},r.id))})]})]})]}),r(c,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-green-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-green-400 to-emerald-400"}),e(m,{className:"pb-4",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-green-100",children:e(y,{className:"h-4 w-4 text-green-600"})}),e(g,{className:"text-lg font-semibold text-gray-800",children:"Thông tin câu hỏi"})]})}),r(h,{className:"space-y-6 pt-2",children:[e("div",{className:"rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-4",children:r("div",{className:"flex flex-col md:flex-row gap-6",children:[e("div",{className:"flex-1",children:e(u,{value:se,onChange:e=>oe(parseFloat(e.target.value)||1)})}),e("div",{className:"flex-1 flex flex-col justify-center"})]})}),r("div",{className:"space-y-2",children:[r(s,{className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(y,{className:"h-4 w-4 text-indigo-500"}),"Audio câu hỏi ",e("span",{className:"text-red-500",children:"*"})]}),e(S,{id:"scene-audio",label:"",accept:"audio/*",value:ae,onChange:e=>{ne(e),ve.clearErrors()},onPresignedUrlChange:fe,maxSize:10,placeholder:"Upload file audio câu hỏi",autoUpload:!0,prefix:"questions/audio",examId:Q||void 0,partId:K||void 0}),Me.audioUrl&&e("p",{className:"mt-1 text-sm text-red-500",children:Me.audioUrl.message}),xe&&e("audio",{controls:!0,className:"mt-2 w-full",src:xe,children:"Trình duyệt không hỗ trợ audio."})]}),r("div",{className:"space-y-3",children:[r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-green-100",children:e(j,{className:"h-3.5 w-3.5 text-green-600"})}),r(s,{className:"text-base font-semibold text-gray-800",children:["Đáp án đúng của câu hỏi này ",e("span",{className:"text-red-500",children:"*"})]})]}),0===ee.length?e("p",{className:"text-sm text-amber-600 italic",children:"Vui lòng thêm đối tượng ở bên trên trước khi chọn đáp án."}):e(p,{value:ie,onValueChange:e=>{le(e),ve.clearErrors()},className:"grid gap-3 sm:grid-cols-2 md:grid-cols-3",children:ee.map((t,a)=>r("label",{className:"flex cursor-pointer items-start gap-3 rounded-lg border-2 p-3 transition-all "+(ie===t.id?"border-green-500 bg-green-50 ring-1 ring-green-500":"border-gray-200 hover:border-green-300 hover:bg-green-50/50"),children:[e(b,{value:t.id,id:`correct-${t.id}`,className:"mt-1 flex-shrink-0"}),r("div",{className:"min-w-0 flex-1",children:[r("p",{className:"font-medium text-gray-900 line-clamp-1 break-all",children:[a+1,". ",t.label||"(Chưa đặt tên)"]}),t.imageUrl?e("div",{className:"mt-2 h-12 w-full max-w-16 rounded overflow-hidden bg-white border border-gray-200",children:e(F,{url:t.imageUrl,alt:t.label})}):e("div",{className:"mt-2 text-xs text-gray-400 italic",children:"Chưa có hình"})]})]},t.id))})]}),Me.correctObjectId&&e("div",{className:"rounded-lg border border-red-200 bg-red-50 p-3 mt-3",children:r("p",{className:"text-sm text-red-700",children:["⚠️ ",Me.correctObjectId.message]})})]})]}),r(c,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-amber-400 to-orange-400"}),e(m,{className:"pb-4",children:r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm",children:e(I,{className:"h-4 w-4 text-white"})}),r("div",{children:[r("div",{className:"flex items-center gap-2",children:[e(g,{className:"text-lg font-semibold text-gray-800",children:"Giải thích"}),e("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ông bắt buộc"})]}),e("p",{className:"text-sm text-gray-500 mt-0.5",children:"Giải thích cho đáp án đúng — hiển thị sau khi học sinh hoàn thành câu hỏi"})]})]})}),e(h,{children:e(d,{value:de,onChange:e=>ce(e.target.value),placeholder:"VD: Câu trả lời đúng là... vì...",rows:3,className:"min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none"})})]})]})}export function ListenAndChooseObjectsInSceneCreator(r){return e(L,{...r})}
@@ -1,2 +1 @@
1
- export * from './ListenAndChooseObjectsInSceneCreator';
2
- export * from './ListenAndChooseObjectsInSceneClient';
1
+ export*from"./ListenAndChooseObjectsInSceneCreator";export*from"./ListenAndChooseObjectsInSceneClient";