@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,457 +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, GripVertical, Target, Move, ChevronDown, ChevronUp, MapPin, ArrowRight, AlertTriangle, 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 { listenAndDragObjectsIntoSceneFormSchema, } from '../../../../shared/lib/validation/listen-and-drag-objects-into-scene.validation';
16
- import { ListenAndDragObjectsIntoSceneClient } from './ListenAndDragObjectsIntoSceneClient';
17
- import { useSceneQuestionGroup } from '../../_shared/hooks/useSceneQuestionGroup';
18
- import { generateObjectId } from '../../_shared/utils/id-generator';
19
- import { CreatorGuide } from '../../components/CreatorGuide';
20
- import { useExamQuestionStore } from '../../../../shared/lib/stores/examQuestionStore';
21
- // Drop zone color palette for visual correlation between editor and preview
22
- const DROP_ZONE_COLORS = [
23
- { bg: 'bg-blue-500', border: 'border-blue-400', light: 'bg-blue-100', text: 'text-blue-700', dot: 'bg-blue-500' },
24
- { bg: 'bg-emerald-500', border: 'border-emerald-400', light: 'bg-emerald-100', text: 'text-emerald-700', dot: 'bg-emerald-500' },
25
- { bg: 'bg-purple-500', border: 'border-purple-400', light: 'bg-purple-100', text: 'text-purple-700', dot: 'bg-purple-500' },
26
- { bg: 'bg-rose-500', border: 'border-rose-400', light: 'bg-rose-100', text: 'text-rose-700', dot: 'bg-rose-500' },
27
- { bg: 'bg-amber-500', border: 'border-amber-400', light: 'bg-amber-100', text: 'text-amber-700', dot: 'bg-amber-500' },
28
- { bg: 'bg-cyan-500', border: 'border-cyan-400', light: 'bg-cyan-100', text: 'text-cyan-700', dot: 'bg-cyan-500' },
29
- ];
30
- function getZoneColor(index) {
31
- return DROP_ZONE_COLORS[index % DROP_ZONE_COLORS.length];
32
- }
33
- /** Shared group badge component */
34
- function SharedGroupBadge({ groupNumber }) {
35
- return (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-orange-100 px-2.5 py-0.5 text-xs font-medium text-orange-700", children: [_jsx("span", { children: "\uD83D\uDD17" }), "Nh\u00F3m ", groupNumber, " \u2014 D\u00F9ng chung"] }));
36
- }
37
- /** Helper component for presigned image display */
38
- function PresignedImage({ url, alt }) {
39
- const { previewUrl } = usePresignedFileUrl(url);
40
- if (!previewUrl)
41
- return _jsx(ImageIcon, { className: "h-4 w-4 text-gray-400" });
42
- return (_jsx("img", { src: previewUrl, alt: alt, className: "h-full w-full object-contain pointer-events-none", draggable: false }));
43
- }
44
- /**
45
- * DragObjectEditor - An individual drag object editor row (sidebar objects, no position)
46
- * Enhanced with larger preview, delete confirmation, and consistent styling
47
- */
48
- function DragObjectEditor({ node, index, onUpdate, onRemove, }) {
49
- const [objectPreviewUrl, setObjectPreviewUrl] = useState('');
50
- const [confirmDelete, setConfirmDelete] = useState(false);
51
- const { previewUrl: presignedObjectUrl } = usePresignedFileUrl(node.imageUrl || '');
52
- useEffect(() => {
53
- if (presignedObjectUrl) {
54
- setObjectPreviewUrl(presignedObjectUrl);
55
- }
56
- }, [presignedObjectUrl]);
57
- const handleDelete = () => {
58
- if (confirmDelete) {
59
- onRemove(node.id);
60
- setConfirmDelete(false);
61
- }
62
- else {
63
- setConfirmDelete(true);
64
- setTimeout(() => setConfirmDelete(false), 3000);
65
- }
66
- };
67
- return (_jsx("div", { className: "rounded-lg border-2 border-gray-200 bg-white p-4 transition-all hover:border-gray-300 hover:shadow-sm", 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-orange-500 to-amber-600 text-sm font-bold text-white shadow-sm", children: index + 1 }), _jsx(GripVertical, { className: "h-4 w-4 text-gray-400" })] }), _jsx("div", { className: "flex-shrink-0", children: objectPreviewUrl ? (_jsx("div", { className: "h-20 w-20 overflow-hidden rounded-lg border-2 border-gray-200 bg-gray-50 transition-all hover:border-orange-300", children: _jsx("img", { src: objectPreviewUrl, alt: node.label || 'Object', className: "h-full w-full object-contain p-1" }) })) : (_jsx("div", { className: "flex h-20 w-20 items-center justify-center rounded-lg border-2 border-dashed border-gray-200 bg-gray-50", children: _jsx(ImageIcon, { className: "h-6 w-6 text-gray-300" }) })) }), _jsxs("div", { className: "flex-1 space-y-3", children: [_jsxs("div", { 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: lemon, kite, car...", className: "mt-1 border-gray-200" })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-xs font-medium text-gray-500", children: "H\u00ECnh \u1EA3nh \u0111\u1ED1i t\u01B0\u1EE3ng" }), _jsx("div", { className: "mt-1", children: _jsx(FileUpload, { id: `drag-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" }) })] })] }), _jsxs("div", { className: "flex flex-col items-center gap-1 pt-1", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: handleDelete, className: `transition-all ${confirmDelete
68
- ? 'bg-red-100 text-red-600 hover:bg-red-200 hover:text-red-700 ring-1 ring-red-300'
69
- : 'text-red-400 hover:bg-red-50 hover:text-red-600'}`, title: confirmDelete ? 'Nhấn lần nữa để xác nhận xóa' : 'Xóa đối tượng', children: _jsx(Trash2, { className: "h-4 w-4" }) }), confirmDelete && (_jsx("span", { className: "text-[10px] font-medium text-red-500 whitespace-nowrap", children: "Nh\u1EA5n l\u1EA1i?" }))] })] }) }));
70
- }
71
- /**
72
- * DropZoneEditor - An individual drop zone editor row
73
- * Enhanced with color-coded indicator matching the scene preview
74
- */
75
- function DropZoneEditor({ zone, index, onUpdate, onRemove, isActive, onSelect, }) {
76
- const [confirmDelete, setConfirmDelete] = useState(false);
77
- const color = getZoneColor(index);
78
- const handleDelete = () => {
79
- if (confirmDelete) {
80
- onRemove(zone.id);
81
- setConfirmDelete(false);
82
- }
83
- else {
84
- setConfirmDelete(true);
85
- setTimeout(() => setConfirmDelete(false), 3000);
86
- }
87
- };
88
- return (_jsx("div", { className: `rounded-lg border-2 border-dashed p-3 transition-all cursor-pointer ${isActive
89
- ? 'border-blue-400 bg-blue-50/50 shadow-sm ring-1 ring-blue-200'
90
- : 'border-gray-200 bg-white hover:border-blue-200 hover:bg-blue-50/20'}`, onClick: () => onSelect?.(zone.id), children: _jsxs("div", { className: "flex items-start gap-2.5", children: [_jsx("div", { className: "flex flex-col items-center gap-1 pt-1.5", children: _jsx("div", { className: `h-6 w-6 rounded-md ${color.dot} flex items-center justify-center text-[10px] font-bold text-white shadow-sm`, children: index + 1 }) }), _jsxs("div", { className: "flex-1 space-y-2", children: [_jsx(Input, { value: zone.label, onChange: (e) => onUpdate(zone.id, { label: e.target.value }), placeholder: "V\u00ED d\u1EE5: near the pond, on the tree...", className: "h-8 border-gray-200 text-sm", onClick: (e) => e.stopPropagation() }), _jsxs("div", { className: "grid grid-cols-4 gap-1.5", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-[10px] font-medium text-gray-400", children: "X%" }), _jsx(Input, { type: "number", min: 0, max: 100, value: zone.x, onChange: (e) => onUpdate(zone.id, { x: parseFloat(e.target.value) || 0 }), className: "h-7 border-gray-200 text-xs px-1 text-center", onClick: (e) => e.stopPropagation() })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-[10px] font-medium text-gray-400", children: "Y%" }), _jsx(Input, { type: "number", min: 0, max: 100, value: zone.y, onChange: (e) => onUpdate(zone.id, { y: parseFloat(e.target.value) || 0 }), className: "h-7 border-gray-200 text-xs px-1 text-center", onClick: (e) => e.stopPropagation() })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-[10px] font-medium text-gray-400", children: "W%" }), _jsx(Input, { type: "number", min: 1, max: 100, value: zone.width, onChange: (e) => onUpdate(zone.id, { width: parseFloat(e.target.value) || 10 }), className: "h-7 border-gray-200 text-xs px-1 text-center", onClick: (e) => e.stopPropagation() })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-[10px] font-medium text-gray-400", children: "H%" }), _jsx(Input, { type: "number", min: 1, max: 100, value: zone.height, onChange: (e) => onUpdate(zone.id, { height: parseFloat(e.target.value) || 10 }), className: "h-7 border-gray-200 text-xs px-1 text-center", onClick: (e) => e.stopPropagation() })] })] })] }), _jsxs("div", { className: "flex flex-col items-center pt-1", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => {
91
- e.stopPropagation();
92
- handleDelete();
93
- }, className: `h-7 w-7 p-0 transition-all ${confirmDelete
94
- ? 'bg-red-100 text-red-600 hover:bg-red-200 ring-1 ring-red-300'
95
- : 'text-red-400 hover:bg-red-50 hover:text-red-600'}`, title: confirmDelete ? 'Nhấn lần nữa để xác nhận xóa' : 'Xóa vị trí', children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }), confirmDelete && (_jsx("span", { className: "text-[9px] font-medium text-red-500 mt-0.5", children: "X\u00F3a?" }))] })] }) }));
96
- }
97
- /**
98
- * Scene preview overlay for drop zones (drag-to-position)
99
- * Enhanced with color-coded zones matching the editor list
100
- */
101
- function DropZonePreviewWithDrag({ backgroundUrl, dropZones, onZoneDrag, onZoneResize, activeZoneId, onSelectZone, }) {
102
- const containerRef = useRef(null);
103
- const [draggingId, setDraggingId] = useState(null);
104
- const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
105
- const [resizingId, setResizingId] = useState(null);
106
- const [resizeType, setResizeType] = useState(null);
107
- const [resizeStartPos, setResizeStartPos] = useState({ x: 0, y: 0 });
108
- const [resizeStartDim, setResizeStartDim] = useState({ width: 0, height: 0 });
109
- const handleMouseDown = (zoneId, zoneX, zoneY, e) => {
110
- e.preventDefault();
111
- if (e.target.dataset.resizeHandle)
112
- return;
113
- setDraggingId(zoneId);
114
- onSelectZone?.(zoneId);
115
- if (containerRef.current) {
116
- const rect = containerRef.current.getBoundingClientRect();
117
- const mouseXPercent = ((e.clientX - rect.left) / rect.width) * 100;
118
- const mouseYPercent = ((e.clientY - rect.top) / rect.height) * 100;
119
- setDragOffset({
120
- x: mouseXPercent - zoneX,
121
- y: mouseYPercent - zoneY,
122
- });
123
- }
124
- };
125
- const handleResizeMouseDown = (zoneId, type, zoneWidth, zoneHeight, e) => {
126
- e.preventDefault();
127
- e.stopPropagation();
128
- setResizingId(zoneId);
129
- setResizeType(type);
130
- setResizeStartPos({ x: e.clientX, y: e.clientY });
131
- setResizeStartDim({ width: zoneWidth, height: zoneHeight });
132
- };
133
- const handleMouseMove = useCallback((e) => {
134
- if (!containerRef.current)
135
- return;
136
- if (draggingId) {
137
- const rect = containerRef.current.getBoundingClientRect();
138
- const mouseXPercent = ((e.clientX - rect.left) / rect.width) * 100;
139
- const mouseYPercent = ((e.clientY - rect.top) / rect.height) * 100;
140
- const x = Math.max(0, Math.min(100, mouseXPercent - dragOffset.x));
141
- const y = Math.max(0, Math.min(100, mouseYPercent - dragOffset.y));
142
- onZoneDrag(draggingId, Math.round(x * 10) / 10, Math.round(y * 10) / 10);
143
- }
144
- else if (resizingId && resizeType) {
145
- const rect = containerRef.current.getBoundingClientRect();
146
- const dx = ((e.clientX - resizeStartPos.x) / rect.width) * 100;
147
- const dy = ((e.clientY - resizeStartPos.y) / rect.height) * 100;
148
- let newWidth = resizeStartDim.width;
149
- let newHeight = resizeStartDim.height;
150
- if (resizeType === 'e' || resizeType === 'se') {
151
- newWidth = Math.max(1, resizeStartDim.width + dx);
152
- }
153
- if (resizeType === 's' || resizeType === 'se') {
154
- newHeight = Math.max(1, resizeStartDim.height + dy);
155
- }
156
- onZoneResize(resizingId, Math.round(newWidth * 10) / 10, Math.round(newHeight * 10) / 10);
157
- }
158
- }, [draggingId, resizingId, resizeType, resizeStartPos, resizeStartDim, dragOffset, onZoneDrag, onZoneResize]);
159
- const handleMouseUp = useCallback(() => {
160
- setDraggingId(null);
161
- setResizingId(null);
162
- setResizeType(null);
163
- }, []);
164
- useEffect(() => {
165
- if (draggingId || resizingId) {
166
- window.addEventListener('mousemove', handleMouseMove);
167
- window.addEventListener('mouseup', handleMouseUp);
168
- return () => {
169
- window.removeEventListener('mousemove', handleMouseMove);
170
- window.removeEventListener('mouseup', handleMouseUp);
171
- };
172
- }
173
- }, [draggingId, resizingId, handleMouseMove, handleMouseUp]);
174
- 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 }), dropZones.map((zone, zoneIndex) => {
175
- const isActive = draggingId === zone.id || resizingId === zone.id || activeZoneId === zone.id;
176
- const color = getZoneColor(zoneIndex);
177
- return (_jsxs("div", { className: `absolute cursor-move rounded-md border-2 border-dashed transition-colors group ${isActive
178
- ? `${color.border} bg-blue-200/30 shadow-lg z-20`
179
- : `${color.border} bg-blue-100/20 hover:bg-blue-200/30 z-10`}`, style: {
180
- left: `${zone.x}%`,
181
- top: `${zone.y}%`,
182
- width: `${zone.width}%`,
183
- height: `${zone.height}%`,
184
- }, onMouseDown: (e) => handleMouseDown(zone.id, zone.x, zone.y, e), children: [_jsxs("div", { className: `absolute -top-5 left-0 rounded ${color.dot} px-1.5 py-0.5 text-[10px] font-medium text-white whitespace-nowrap shadow-sm`, children: [zoneIndex + 1, ". ", zone.label || `Zone ${zoneIndex + 1}`] }), _jsx("div", { className: "flex h-full w-full items-center justify-center", children: _jsx(Target, { className: "h-4 w-4 text-blue-400/60" }) }), _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(zone.id, 'e', zone.width, zone.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(zone.id, 's', zone.width, zone.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(zone.id, 'se', zone.width, zone.height, e) })] }, zone.id));
185
- })] }));
186
- }
187
- /**
188
- * ListenAndDragObjectsIntoSceneCreator
189
- *
190
- * Admin creator component for the "Listen and Drag Objects into Scene" question type.
191
- * Allows uploading a background scene, adding object images in sidebar,
192
- * defining drop zones on the scene, and selecting the correct object+dropzone pair.
193
- *
194
- * UI/UX improvements:
195
- * - Consolidated from 5 cards to 3 cards (Group Data + Question Details + Explanation)
196
- * - Side-by-side scene preview + drop zone editor panel
197
- * - Image thumbnails in correct answer selection
198
- * - Color-coded drop zone indicators
199
- * - Shared group badges on all group sections
200
- * - Delete confirmation for objects and drop zones
201
- * - Enlarged object image previews
202
- * - Answer summary card
203
- */
204
- function ListenAndDragObjectsIntoSceneCreatorContent({ initialData, onSave, onSaveToQuestionBank, onCancel, onChange, onUnsavedChangesChange, validationRef, externalErrors, groups, questionIndexInPart = 0, partId = '', }) {
205
- console.log('[ListenAndDragObjectsIntoScene_Creator] RENDER', { initialData, questionIndexInPart, partId, groups });
206
- // Get examId from global exam store for audio upload API
207
- const examId = useExamQuestionStore((state) => state.examId);
208
- // ============ SHARED GROUP DATA (via useSceneQuestionGroup hook) ============
209
- const sceneGroupInitialData = {
210
- backgroundImageUrl: initialData?.backgroundImageUrl || '',
211
- objectNodes: initialData?.objectNodes || [],
212
- dropZones: initialData?.dropZones || [],
213
- };
214
- const { isGrouped, currentGroupNumber, backgroundImageUrl, setBackgroundImageUrl, objectNodes, setObjectNodes, dropZones, setDropZones, } = useSceneQuestionGroup({
215
- partId,
216
- groups,
217
- questionIndexInPart,
218
- initialData: sceneGroupInitialData,
219
- });
220
- // ============ STATE ============
221
- // Question-specific data
222
- const [audioUrl, setAudioUrl] = useState(initialData?.audioUrl || '');
223
- const [correctObjectId, setCorrectObjectId] = useState(initialData?.correctObjectId !== undefined && initialData?.correctObjectId !== null
224
- ? String(initialData.correctObjectId)
225
- : '');
226
- const [correctDropZoneId, setCorrectDropZoneId] = useState(initialData?.correctDropZoneId !== undefined && initialData?.correctDropZoneId !== null
227
- ? String(initialData.correctDropZoneId)
228
- : '');
229
- const [points, setPoints] = useState(initialData?.points);
230
- const [explanation, setExplanation] = useState(initialData?.explanation || '');
231
- const [isBackup, setIsBackup] = useState(initialData?.isBackup || false);
232
- // UI State
233
- const [isClientMode, setIsClientMode] = useState(false);
234
- const [backgroundPreviewUrl, setBackgroundPreviewUrl] = useState('');
235
- const [audioPreviewUrl, setAudioPreviewUrl] = useState('');
236
- const [activeDropZoneId, setActiveDropZoneId] = useState(null);
237
- const [isGroupDataCollapsed, setIsGroupDataCollapsed] = useState(false);
238
- // Use react-hook-form with Zod validation
239
- const form = useReactHookForm({
240
- defaultValues: {
241
- audioUrl,
242
- backgroundImageUrl,
243
- objectNodes,
244
- dropZones,
245
- correctObjectId,
246
- correctDropZoneId,
247
- isBackup,
248
- },
249
- zodSchema: listenAndDragObjectsIntoSceneFormSchema,
250
- mode: 'onBlur',
251
- });
252
- // Refs
253
- const previousInitialDataRef = useRef(initialData);
254
- const isInitializingRef = useRef(true);
255
- const onChangeRef = useRef(onChange);
256
- const hasInitializedRef = useRef(false);
257
- // Presigned URLs
258
- const { previewUrl: presignedBgUrl } = usePresignedFileUrl(backgroundImageUrl);
259
- const { previewUrl: presignedAudioUrl } = usePresignedFileUrl(audioUrl);
260
- useEffect(() => {
261
- if (presignedBgUrl)
262
- setBackgroundPreviewUrl(presignedBgUrl);
263
- }, [presignedBgUrl]);
264
- useEffect(() => {
265
- if (presignedAudioUrl)
266
- setAudioPreviewUrl(presignedAudioUrl);
267
- }, [presignedAudioUrl]);
268
- // Update onChange ref
269
- useEffect(() => {
270
- onChangeRef.current = onChange;
271
- }, [onChange]);
272
- // Debounced onChange
273
- const debouncedOnChange = useDebouncedCallback((data) => {
274
- onChangeRef.current?.(data);
275
- }, 300);
276
- // Expose validation function via ref
277
- useEffect(() => {
278
- if (validationRef) {
279
- validationRef.current = {
280
- triggerValidation: async () => {
281
- return validateForm();
282
- },
283
- getFormData: () => ({
284
- audioUrl,
285
- backgroundImageUrl,
286
- objectNodes,
287
- dropZones,
288
- correctObjectId,
289
- correctDropZoneId,
290
- points,
291
- explanation,
292
- isBackup,
293
- }),
294
- };
295
- }
296
- }, [validationRef, audioUrl, backgroundImageUrl, objectNodes, dropZones, correctObjectId, correctDropZoneId, points, explanation, isBackup]);
297
- // Sync question-specific state from initialData
298
- useEffect(() => {
299
- if (!initialData) {
300
- if (!hasInitializedRef.current) {
301
- hasInitializedRef.current = true;
302
- isInitializingRef.current = false;
303
- }
304
- return;
305
- }
306
- const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
307
- if (!initialDataChanged && hasInitializedRef.current)
308
- return;
309
- previousInitialDataRef.current = initialData;
310
- const newAudioUrl = initialData?.audioUrl || '';
311
- const newCorrectObjectId = initialData?.correctObjectId !== undefined && initialData?.correctObjectId !== null ? String(initialData.correctObjectId) : '';
312
- const newCorrectDropZoneId = initialData?.correctDropZoneId !== undefined && initialData?.correctDropZoneId !== null ? String(initialData.correctDropZoneId) : '';
313
- const newPoints = initialData?.points || 1;
314
- const newExplanation = initialData?.explanation || '';
315
- const newIsBackup = initialData?.isBackup || false;
316
- isInitializingRef.current = true;
317
- hasInitializedRef.current = true;
318
- setAudioUrl(newAudioUrl);
319
- setCorrectObjectId(newCorrectObjectId);
320
- setCorrectDropZoneId(newCorrectDropZoneId);
321
- setPoints(newPoints);
322
- setExplanation(newExplanation);
323
- setIsBackup(newIsBackup);
324
- setTimeout(() => {
325
- isInitializingRef.current = false;
326
- }, 150);
327
- }, [initialData]);
328
- // Call onChange when form data changes
329
- useEffect(() => {
330
- if (isInitializingRef.current || !hasInitializedRef.current)
331
- return;
332
- const payload = {
333
- audioUrl,
334
- backgroundImageUrl,
335
- objectNodes,
336
- dropZones,
337
- correctObjectId,
338
- correctDropZoneId,
339
- points,
340
- explanation,
341
- isBackup,
342
- groups,
343
- groupNumber: currentGroupNumber,
344
- };
345
- debouncedOnChange(payload);
346
- }, [audioUrl, backgroundImageUrl, objectNodes, dropZones, correctObjectId, correctDropZoneId, points, explanation, isBackup, groups, currentGroupNumber, debouncedOnChange]);
347
- // ============ VALIDATION ============
348
- const validateForm = async () => {
349
- form.setValue('audioUrl', audioUrl);
350
- form.setValue('backgroundImageUrl', backgroundImageUrl);
351
- form.setValue('objectNodes', objectNodes);
352
- form.setValue('dropZones', dropZones);
353
- form.setValue('correctObjectId', String(correctObjectId));
354
- form.setValue('correctDropZoneId', String(correctDropZoneId));
355
- form.setValue('isBackup', isBackup);
356
- const isValid = await form.trigger();
357
- return isValid;
358
- };
359
- // ============ OBJECT NODE HANDLERS ============
360
- const addObjectNode = () => {
361
- const newNode = {
362
- id: generateObjectId(),
363
- imageUrl: '',
364
- label: '',
365
- };
366
- setObjectNodes([...objectNodes, newNode]);
367
- form.clearErrors();
368
- };
369
- const updateObjectNode = (id, updates) => {
370
- setObjectNodes(objectNodes.map((node) => (node.id === id ? { ...node, ...updates } : node)));
371
- form.clearErrors();
372
- };
373
- const removeObjectNode = (id) => {
374
- setObjectNodes(objectNodes.filter((node) => node.id !== id));
375
- if (correctObjectId === id) {
376
- setCorrectObjectId('');
377
- }
378
- form.clearErrors();
379
- };
380
- // ============ DROP ZONE HANDLERS ============
381
- const addDropZone = () => {
382
- const newZone = {
383
- id: generateObjectId(),
384
- label: '',
385
- x: 20 + (dropZones.length * 20) % 60,
386
- y: 30 + (dropZones.length * 15) % 40,
387
- width: 12,
388
- height: 12,
389
- };
390
- setDropZones([...dropZones, newZone]);
391
- form.clearErrors();
392
- };
393
- const updateDropZone = (id, updates) => {
394
- setDropZones(dropZones.map((zone) => (zone.id === id ? { ...zone, ...updates } : zone)));
395
- form.clearErrors();
396
- };
397
- const removeDropZone = (id) => {
398
- setDropZones(dropZones.filter((zone) => zone.id !== id));
399
- if (correctDropZoneId === id) {
400
- setCorrectDropZoneId('');
401
- }
402
- if (activeDropZoneId === id) {
403
- setActiveDropZoneId(null);
404
- }
405
- form.clearErrors();
406
- };
407
- const handleZoneDrag = useCallback((id, x, y) => {
408
- setDropZones((prev) => prev.map((zone) => (zone.id === id ? { ...zone, x, y } : zone)));
409
- }, []);
410
- const handleZoneResize = useCallback((id, width, height) => {
411
- setDropZones((prev) => prev.map((zone) => (zone.id === id ? { ...zone, width, height } : zone)));
412
- }, []);
413
- // ============ DERIVED DATA ============
414
- const selectedObject = objectNodes.find((n) => n.id === correctObjectId);
415
- const selectedDropZone = dropZones.find((z) => z.id === correctDropZoneId);
416
- const selectedDropZoneIndex = dropZones.findIndex((z) => z.id === correctDropZoneId);
417
- // ============ RENDER ============
418
- // Preview mode
419
- if (isClientMode) {
420
- 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 k\u00E9o th\u1EA3 \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(ListenAndDragObjectsIntoSceneClient, { questionData: {
421
- audioUrl: audioPreviewUrl || audioUrl,
422
- backgroundImageUrl: backgroundPreviewUrl || backgroundImageUrl,
423
- objectNodes,
424
- dropZones,
425
- }, isReviewMode: true, userAnswer: correctObjectId && correctDropZoneId ? { objectId: correctObjectId, dropZoneId: correctDropZoneId } : undefined, correctAnswer: correctObjectId && correctDropZoneId ? { objectId: correctObjectId, dropZoneId: correctDropZoneId } : undefined, explanation: explanation })] }));
426
- }
427
- // Creator mode
428
- return (_jsxs("div", { className: "space-y-6", children: [_jsx(CreatorGuide, { colorScheme: "rose", steps: [
429
- { text: 'Upload hình nền (Scene) chung cho nhóm câu hỏi' },
430
- { text: 'Thêm các đối tượng kéo-thả (draggable objects) với hình ảnh và tên' },
431
- { text: 'Thiết lập vùng thả (drop zones) trên hình nền — vị trí mà học sinh cần kéo đối tượng vào' },
432
- { text: 'Upload audio hướng dẫn cho từng câu' },
433
- { text: 'Ghép đúng đối tượng với vùng thả tương ứng', highlight: true },
434
- ], tips: ['Vùng thả nên đủ lớn và rõ ràng, tránh chồng chéo'] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-rose-100/50", children: [_jsx("div", { className: "h-1.5 bg-gradient-to-r from-rose-500 via-pink-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-rose-500 to-pink-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-rose-100 px-3 py-1 text-sm font-medium text-rose-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 || dropZones.length < 1, className: "gap-2 border-rose-200 text-rose-600 hover:bg-rose-50 hover:text-rose-700", children: [_jsx(Eye, { className: "h-4 w-4" }), "Preview"] })] }) })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-rose-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-rose-400 to-pink-400" }), _jsx(CardHeader, { className: "pb-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-rose-100", children: _jsx(ImageIcon, { className: "h-3.5 w-3.5 text-rose-600" }) }), _jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "\u1EA2nh n\u1EC1n & \u0110\u1ED1i t\u01B0\u1EE3ng k\u00E9o th\u1EA3" })] }), _jsxs(Button, { variant: "ghost", size: "sm", onClick: () => setIsGroupDataCollapsed(!isGroupDataCollapsed), className: "gap-1 text-gray-500 hover:text-gray-700", children: [isGroupDataCollapsed ? _jsx(ChevronDown, { className: "h-4 w-4" }) : _jsx(ChevronUp, { className: "h-4 w-4" }), isGroupDataCollapsed ? 'Mở rộng' : 'Thu gọn'] })] }) }), !isGroupDataCollapsed && (_jsxs(CardContent, { className: "space-y-6", children: [_jsx("div", { className: "rounded-xl border-2 p-5 bg-gradient-to-r from-orange-50 to-amber-50 border-orange-200", children: _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-orange-800", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-orange-600" }), "\u1EA2nh n\u1EC1n (Scene) ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FileUpload, { id: "drag-scene-background", label: "", accept: "image/*", value: backgroundImageUrl, onChange: (url) => {
435
- setBackgroundImageUrl(url);
436
- form.clearErrors();
437
- }, onPresignedUrlChange: setBackgroundPreviewUrl, maxSize: 10, placeholder: "Upload h\u00ECnh \u1EA3nh n\u1EC1n h\u00ECnh \u1EA3nh n\u1EC1n", autoUpload: true, prefix: "questions/scenes" }), form.formState.errors.backgroundImageUrl && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: form.formState.errors.backgroundImageUrl.message }))] }) }), backgroundPreviewUrl && (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-gray-50 overflow-hidden isolate", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 px-4 py-2.5 bg-white", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-blue-600" }), _jsx("span", { className: "text-sm font-semibold text-gray-700", children: "H\u00ECnh \u1EA3nh n\u1EC1n" }), _jsx(SharedGroupBadge, { groupNumber: currentGroupNumber })] }), _jsx("div", { className: "text-xs text-gray-500 font-medium", children: "K\u00E9o th\u1EA3 v\u1ECB tr\u00ED \u0111\u1EC3 \u0111\u1ECBnh v\u1ECB tr\u00EAn \u1EA3nh n\u1EC1n" })] }), _jsx("div", { className: "p-3 w-full", children: _jsx(DropZonePreviewWithDrag, { backgroundUrl: backgroundPreviewUrl, dropZones: dropZones, onZoneDrag: handleZoneDrag, onZoneResize: handleZoneResize, activeZoneId: activeDropZoneId, onSelectZone: setActiveDropZoneId }) })] })), !backgroundPreviewUrl && (_jsx("div", { className: "rounded-xl border border-gray-200 bg-gray-50/50 p-6", children: _jsxs("div", { className: "flex flex-col items-center gap-2 text-center", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-gray-100", children: _jsx(Target, { className: "h-5 w-5 text-gray-400" }) }), _jsx("p", { className: "text-sm text-gray-500", children: "Upload \u1EA3nh n\u1EC1n tr\u01B0\u1EDBc \u0111\u1EC3 thi\u1EBFt l\u1EADp v\u1ECB tr\u00ED tr\u00EAn h\u00ECnh \u1EA3nh n\u1EC1n." })] }) })), backgroundPreviewUrl && (_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-blue-100", children: _jsx(Target, { className: "h-3.5 w-3.5 text-blue-600" }) }), _jsxs("h3", { className: "text-base font-semibold text-gray-800", children: ["V\u1ECB tr\u00ED (", dropZones.length, " items)"] }), _jsx(SharedGroupBadge, { groupNumber: currentGroupNumber })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: addDropZone, className: "gap-2 border-blue-200 text-blue-600 hover:bg-blue-50 hover:text-blue-700", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm v\u1ECB tr\u00ED"] })] }), _jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: dropZones.length === 0 ? (_jsxs("div", { className: "col-span-full 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(Target, { className: "h-6 w-6 text-gray-400" }) }), _jsx("p", { className: "text-sm text-gray-500", children: "Ch\u01B0a c\u00F3 v\u1ECB tr\u00ED n\u00E0o. Nh\u1EA5n \u201CTh\u00EAm v\u1ECB tr\u00ED\u201D \u0111\u1EC3 b\u1EAFt \u0111\u1EA7u." })] })) : (dropZones.map((zone, index) => (_jsx(DropZoneEditor, { zone: zone, index: index, onUpdate: updateDropZone, onRemove: removeDropZone, isActive: activeDropZoneId === zone.id, onSelect: setActiveDropZoneId }, zone.id)))) }), form.formState.errors.dropZones && (_jsx("div", { className: "col-span-full mt-2 rounded-lg border border-red-200 bg-red-50 p-3", children: _jsx("p", { className: "text-sm text-red-500", children: form.formState.errors.dropZones.message || "Vui lòng nhập đầy đủ thông tin cho các vị trí." }) }))] })), _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-amber-100", children: _jsx(Move, { className: "h-3.5 w-3.5 text-amber-600" }) }), _jsxs("h3", { className: "text-base font-semibold text-gray-800", children: ["\u0110\u1ED1i t\u01B0\u1EE3ng (", objectNodes.length, " items)"] }), _jsx(SharedGroupBadge, { groupNumber: currentGroupNumber })] }), _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"] })] }), _jsxs("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 \u201CTh\u00EAm \u0111\u1ED1i t\u01B0\u1EE3ng\u201D \u0111\u1EC3 t\u1EA1o c\u00E1c \u0111\u1ED1i t\u01B0\u1EE3ng \u1EDF sidebar." })] })) : (objectNodes.map((node, index) => (_jsx(DragObjectEditor, { node: node, index: index, onUpdate: updateObjectNode, onRemove: removeObjectNode }, node.id)))), form.formState.errors.objectNodes && (_jsx("p", { className: "mt-2 text-sm text-red-500", children: form.formState.errors.objectNodes.message || "Vui lòng nhập đầy đủ thông tin cho các đối tượng." }))] })] })] }))] }), _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 pt-2" })] }) }), _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: "drag-scene-audio", label: "", accept: "audio/*", value: audioUrl, onChange: (url) => {
438
- setAudioUrl(url);
439
- form.clearErrors();
440
- }, onPresignedUrlChange: setAudioPreviewUrl, maxSize: 10, placeholder: "Upload file audio c\u00E2u h\u1ECFi", autoUpload: true, prefix: "questions/audio", examId: examId || undefined, partId: partId || undefined }), form.formState.errors.audioUrl && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: form.formState.errors.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\u1ED1i t\u01B0\u1EE3ng \u0111\u00FAng ", _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 ph\u1EA7n d\u1EEF li\u1EC7u nh\u00F3m tr\u01B0\u1EDBc khi ch\u1ECDn \u0111\u00E1p \u00E1n." })) : (_jsx(RadioGroup, { value: correctObjectId, onValueChange: (val) => {
441
- setCorrectObjectId(val);
442
- form.clearErrors();
443
- }, className: "grid gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4", children: objectNodes.map((node, index) => (_jsxs("label", { className: `flex cursor-pointer items-center gap-3 rounded-lg border-2 p-3 transition-all ${correctObjectId === node.id
444
- ? 'border-green-500 bg-green-50 ring-1 ring-green-500 shadow-sm'
445
- : 'border-gray-200 hover:border-green-300 hover:bg-green-50/50'}`, children: [_jsx(RadioGroupItem, { value: node.id, id: `correct-obj-${node.id}`, className: "flex-shrink-0" }), _jsx("div", { className: "h-12 w-12 flex-shrink-0 overflow-hidden rounded-md border border-gray-200 bg-white", children: node.imageUrl ? (_jsx(PresignedImage, { url: node.imageUrl, alt: node.label })) : (_jsx("div", { className: "flex h-full w-full items-center justify-center", children: _jsx(ImageIcon, { className: "h-4 w-4 text-gray-300" }) })) }), _jsx("div", { className: "min-w-0 flex-1", children: _jsxs("p", { className: "font-medium text-gray-900 text-sm line-clamp-2 break-all", children: [index + 1, ". ", node.label || '(Chưa đặt tên)'] }) })] }, node.id))) })), form.formState.errors.correctObjectId && (_jsx("div", { className: "rounded-lg border border-red-200 bg-red-50 p-3 mt-2", children: _jsxs("p", { className: "text-sm text-red-700", children: ["\u26A0\uFE0F ", form.formState.errors.correctObjectId.message] }) }))] }), _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-blue-100", children: _jsx(Target, { className: "h-3.5 w-3.5 text-blue-600" }) }), _jsxs(Label, { className: "text-base font-semibold text-gray-800", children: ["V\u1ECB tr\u00ED \u0111\u00FAng ", _jsx("span", { className: "text-red-500", children: "*" })] })] }), dropZones.length === 0 ? (_jsx("p", { className: "text-sm text-amber-600 italic", children: "Vui l\u00F2ng th\u00EAm v\u1ECB tr\u00ED \u1EDF ph\u1EA7n d\u1EEF li\u1EC7u nh\u00F3m tr\u01B0\u1EDBc khi ch\u1ECDn \u0111\u00E1p \u00E1n." })) : (_jsx(RadioGroup, { value: correctDropZoneId, onValueChange: (val) => {
446
- setCorrectDropZoneId(val);
447
- form.clearErrors();
448
- }, className: "grid gap-3 sm:grid-cols-2 md:grid-cols-3", children: dropZones.map((zone, index) => {
449
- const color = getZoneColor(index);
450
- return (_jsxs("label", { className: `flex cursor-pointer items-center gap-3 rounded-lg border-2 p-3 transition-all ${correctDropZoneId === zone.id
451
- ? 'border-blue-500 bg-blue-50 ring-1 ring-blue-500 shadow-sm'
452
- : 'border-gray-200 hover:border-blue-300 hover:bg-blue-50/50'}`, children: [_jsx(RadioGroupItem, { value: zone.id, id: `correct-dz-${zone.id}`, className: "flex-shrink-0" }), _jsx("div", { className: `flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md ${color.dot} text-white shadow-sm`, children: _jsx(MapPin, { className: "h-4 w-4" }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("p", { className: "font-medium text-gray-900 text-sm line-clamp-1 break-all", children: [index + 1, ". ", zone.label || '(Chưa đặt nhãn)'] }), _jsxs("p", { className: "text-[11px] text-gray-400 mt-0.5", children: ["V\u1ECB tr\u00ED: (", zone.x, "%, ", zone.y, "%) \u2014 ", zone.width, "\u00D7", zone.height, "%"] })] })] }, zone.id));
453
- }) })), form.formState.errors.correctDropZoneId && (_jsx("div", { className: "rounded-lg border border-red-200 bg-red-50 p-3 mt-2", children: _jsxs("p", { className: "text-sm text-red-700", children: ["\u26A0\uFE0F ", form.formState.errors.correctDropZoneId.message] }) }))] }), selectedObject && selectedDropZone && (_jsxs("div", { className: "rounded-xl border-2 border-green-200 bg-gradient-to-r from-green-50 to-emerald-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-green-500 text-white", children: _jsx(Lightbulb, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-sm font-semibold text-green-800", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" })] }), _jsxs("div", { className: "flex items-center gap-3 rounded-lg bg-white/80 p-3 border border-green-100", children: [_jsx("div", { className: "h-14 w-14 flex-shrink-0 overflow-hidden rounded-lg border-2 border-green-300 bg-white", children: selectedObject.imageUrl ? (_jsx(PresignedImage, { url: selectedObject.imageUrl, alt: selectedObject.label })) : (_jsx("div", { className: "flex h-full w-full items-center justify-center", children: _jsx(ImageIcon, { className: "h-5 w-5 text-gray-300" }) })) }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "font-semibold text-gray-900 text-sm", children: selectedObject.label || '(Chưa đặt tên)' }), _jsx("p", { className: "text-xs text-gray-500", children: "\u0110\u1ED1i t\u01B0\u1EE3ng" })] }), _jsx(ArrowRight, { className: "h-5 w-5 text-green-500 flex-shrink-0 mx-1" }), _jsx("div", { className: `flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg ${selectedDropZoneIndex >= 0 ? getZoneColor(selectedDropZoneIndex).dot : 'bg-blue-500'} text-white shadow-sm`, children: _jsx(MapPin, { className: "h-5 w-5" }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "font-semibold text-gray-900 text-sm", children: selectedDropZone.label || '(Chưa đặt nhãn)' }), _jsxs("p", { className: "text-xs text-gray-500", children: ["V\u1ECB tr\u00ED (", selectedDropZone.x, "%, ", selectedDropZone.y, "%)"] })] })] })] })), (!correctObjectId || !correctDropZoneId) && objectNodes.length > 0 && dropZones.length > 0 && (_jsxs("div", { className: "flex items-start gap-2.5 rounded-lg border border-amber-200 bg-amber-50 p-3", children: [_jsx(AlertTriangle, { className: "h-4 w-4 text-amber-500 flex-shrink-0 mt-0.5" }), _jsx("p", { className: "text-sm text-amber-700", children: "Vui l\u00F2ng ch\u1ECDn c\u1EA3 \u0111\u1ED1i t\u01B0\u1EE3ng \u0111\u00FAng v\u00E0 v\u1ECB tr\u00ED \u0111\u00FAng cho c\u00E2u h\u1ECFi n\u00E0y" })] }))] })] }), _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" }) })] })] }));
454
- }
455
- export function ListenAndDragObjectsIntoSceneCreator(props) {
456
- return _jsx(ListenAndDragObjectsIntoSceneCreatorContent, { ...props });
457
- }
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 l}from"react";import{Button as i}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 m,CardHeader as h,CardTitle as g}from"../../../../components/ui/card";import{PointsInput as u}from"../../../../components/ui/points-input";import{RadioGroup as b,RadioGroupItem as p}from"../../../../components/ui/radio-group";import{Pencil as x,Eye as f,Plus as N,Trash2 as v,Image as w,Volume2 as y,Lightbulb as j,GripVertical as k,Target as I,Move as U,ChevronDown as C,ChevronUp as S,MapPin as Z,ArrowRight as D,AlertTriangle as z,ListChecks as V,BookOpen as E}from"lucide-react";import{FileUpload as O}from"../../../../components/ui/file-upload";import{usePresignedFileUrl as M,useDebouncedCallback as P}from"../../../../shared/lib/hooks";import{useReactHookForm as R}from"../../../../shared/lib/hooks/useReactHookForm";import{listenAndDragObjectsIntoSceneFormSchema as T}from"../../../../shared/lib/validation/listen-and-drag-objects-into-scene.validation";import{ListenAndDragObjectsIntoSceneClient as $}from"./ListenAndDragObjectsIntoSceneClient";import{useSceneQuestionGroup as B}from"../../_shared/hooks/useSceneQuestionGroup";import{generateObjectId as A}from"../../_shared/utils/id-generator";import{CreatorGuide as X}from"../../components/CreatorGuide";import{useExamQuestionStore as q}from"../../../../shared/lib/stores/examQuestionStore";const F=[{bg:"bg-blue-500",border:"border-blue-400",light:"bg-blue-100",text:"text-blue-700",dot:"bg-blue-500"},{bg:"bg-emerald-500",border:"border-emerald-400",light:"bg-emerald-100",text:"text-emerald-700",dot:"bg-emerald-500"},{bg:"bg-purple-500",border:"border-purple-400",light:"bg-purple-100",text:"text-purple-700",dot:"bg-purple-500"},{bg:"bg-rose-500",border:"border-rose-400",light:"bg-rose-100",text:"text-rose-700",dot:"bg-rose-500"},{bg:"bg-amber-500",border:"border-amber-400",light:"bg-amber-100",text:"text-amber-700",dot:"bg-amber-500"},{bg:"bg-cyan-500",border:"border-cyan-400",light:"bg-cyan-100",text:"text-cyan-700",dot:"bg-cyan-500"}];function G(e){return F[e%F.length]}function L({groupNumber:t}){return r("span",{className:"inline-flex items-center gap-1 rounded-full bg-orange-100 px-2.5 py-0.5 text-xs font-medium text-orange-700",children:[e("span",{children:"🔗"}),"Nhóm ",t," — Dùng chung"]})}function H({url:r,alt:t}){const{previewUrl:a}=M(r);return a?e("img",{src:a,alt:t,className:"h-full w-full object-contain pointer-events-none",draggable:!1}):e(w,{className:"h-4 w-4 text-gray-400"})}function Y({node:a,index:l,onUpdate:d,onRemove:c}){const[m,h]=t(""),[g,u]=t(!1),{previewUrl:b}=M(a.imageUrl||"");n(()=>{b&&h(b)},[b]);return e("div",{className:"rounded-lg border-2 border-gray-200 bg-white p-4 transition-all hover:border-gray-300 hover:shadow-sm",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-orange-500 to-amber-600 text-sm font-bold text-white shadow-sm",children:l+1}),e(k,{className:"h-4 w-4 text-gray-400"})]}),e("div",{className:"flex-shrink-0",children:e("div",m?{className:"h-20 w-20 overflow-hidden rounded-lg border-2 border-gray-200 bg-gray-50 transition-all hover:border-orange-300",children:e("img",{src:m,alt:a.label||"Object",className:"h-full w-full object-contain p-1"})}:{className:"flex h-20 w-20 items-center justify-center rounded-lg border-2 border-dashed border-gray-200 bg-gray-50",children:e(w,{className:"h-6 w-6 text-gray-300"})})}),r("div",{className:"flex-1 space-y-3",children:[r("div",{children:[e(s,{className:"text-xs font-medium text-gray-500",children:"Tên đối tượng"}),e(o,{value:a.label,onChange:e=>d(a.id,{label:e.target.value}),placeholder:"Ví dụ: lemon, kite, car...",className:"mt-1 border-gray-200"})]}),r("div",{children:[e(s,{className:"text-xs font-medium text-gray-500",children:"Hình ảnh đối tượng"}),e("div",{className:"mt-1",children:e(O,{id:`drag-object-image-${a.id}`,label:"",accept:"image/*",value:a.imageUrl||"",onChange:e=>d(a.id,{imageUrl:e}),onPresignedUrlChange:h,maxSize:5,placeholder:"Upload hình ảnh đối tượng",autoUpload:!0,prefix:"questions/objects"})})]})]}),r("div",{className:"flex flex-col items-center gap-1 pt-1",children:[e(i,{variant:"ghost",size:"sm",onClick:()=>{g?(c(a.id),u(!1)):(u(!0),setTimeout(()=>u(!1),3e3))},className:"transition-all "+(g?"bg-red-100 text-red-600 hover:bg-red-200 hover:text-red-700 ring-1 ring-red-300":"text-red-400 hover:bg-red-50 hover:text-red-600"),title:g?"Nhấn lần nữa để xác nhận xóa":"Xóa đối tượng",children:e(v,{className:"h-4 w-4"})}),g&&e("span",{className:"text-[10px] font-medium text-red-500 whitespace-nowrap",children:"Nhấn lại?"})]})]})})}function Q({zone:a,index:n,onUpdate:l,onRemove:d,isActive:c,onSelect:m}){const[h,g]=t(!1),u=G(n);return e("div",{className:"rounded-lg border-2 border-dashed p-3 transition-all cursor-pointer "+(c?"border-blue-400 bg-blue-50/50 shadow-sm ring-1 ring-blue-200":"border-gray-200 bg-white hover:border-blue-200 hover:bg-blue-50/20"),onClick:()=>m?.(a.id),children:r("div",{className:"flex items-start gap-2.5",children:[e("div",{className:"flex flex-col items-center gap-1 pt-1.5",children:e("div",{className:`h-6 w-6 rounded-md ${u.dot} flex items-center justify-center text-[10px] font-bold text-white shadow-sm`,children:n+1})}),r("div",{className:"flex-1 space-y-2",children:[e(o,{value:a.label,onChange:e=>l(a.id,{label:e.target.value}),placeholder:"Ví dụ: near the pond, on the tree...",className:"h-8 border-gray-200 text-sm",onClick:e=>e.stopPropagation()}),r("div",{className:"grid grid-cols-4 gap-1.5",children:[r("div",{children:[e(s,{className:"text-[10px] font-medium text-gray-400",children:"X%"}),e(o,{type:"number",min:0,max:100,value:a.x,onChange:e=>l(a.id,{x:parseFloat(e.target.value)||0}),className:"h-7 border-gray-200 text-xs px-1 text-center",onClick:e=>e.stopPropagation()})]}),r("div",{children:[e(s,{className:"text-[10px] font-medium text-gray-400",children:"Y%"}),e(o,{type:"number",min:0,max:100,value:a.y,onChange:e=>l(a.id,{y:parseFloat(e.target.value)||0}),className:"h-7 border-gray-200 text-xs px-1 text-center",onClick:e=>e.stopPropagation()})]}),r("div",{children:[e(s,{className:"text-[10px] font-medium text-gray-400",children:"W%"}),e(o,{type:"number",min:1,max:100,value:a.width,onChange:e=>l(a.id,{width:parseFloat(e.target.value)||10}),className:"h-7 border-gray-200 text-xs px-1 text-center",onClick:e=>e.stopPropagation()})]}),r("div",{children:[e(s,{className:"text-[10px] font-medium text-gray-400",children:"H%"}),e(o,{type:"number",min:1,max:100,value:a.height,onChange:e=>l(a.id,{height:parseFloat(e.target.value)||10}),className:"h-7 border-gray-200 text-xs px-1 text-center",onClick:e=>e.stopPropagation()})]})]})]}),r("div",{className:"flex flex-col items-center pt-1",children:[e(i,{variant:"ghost",size:"sm",onClick:e=>{e.stopPropagation(),h?(d(a.id),g(!1)):(g(!0),setTimeout(()=>g(!1),3e3))},className:"h-7 w-7 p-0 transition-all "+(h?"bg-red-100 text-red-600 hover:bg-red-200 ring-1 ring-red-300":"text-red-400 hover:bg-red-50 hover:text-red-600"),title:h?"Nhấn lần nữa để xác nhận xóa":"Xóa vị trí",children:e(v,{className:"h-3.5 w-3.5"})}),h&&e("span",{className:"text-[9px] font-medium text-red-500 mt-0.5",children:"Xóa?"})]})]})})}function _({backgroundUrl:i,dropZones:s,onZoneDrag:o,onZoneResize:d,activeZoneId:c,onSelectZone:m}){const h=a(null),[g,u]=t(null),[b,p]=t({x:0,y:0}),[x,f]=t(null),[N,v]=t(null),[w,y]=t({x:0,y:0}),[j,k]=t({width:0,height:0}),U=(e,r,t,a,n)=>{n.preventDefault(),n.stopPropagation(),f(e),v(r),y({x:n.clientX,y:n.clientY}),k({width:t,height:a})},C=l(e=>{if(h.current)if(g){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-b.x)),l=Math.max(0,Math.min(100,a-b.y));o(g,Math.round(10*n)/10,Math.round(10*l)/10)}else if(x&&N){const r=h.current.getBoundingClientRect(),t=(e.clientX-w.x)/r.width*100,a=(e.clientY-w.y)/r.height*100;let n=j.width,l=j.height;"e"!==N&&"se"!==N||(n=Math.max(1,j.width+t)),"s"!==N&&"se"!==N||(l=Math.max(1,j.height+a)),d(x,Math.round(10*n)/10,Math.round(10*l)/10)}},[g,x,N,w,j,b,o,d]),S=l(()=>{u(null),f(null),v(null)},[]);return n(()=>{if(g||x)return window.addEventListener("mousemove",C),window.addEventListener("mouseup",S),()=>{window.removeEventListener("mousemove",C),window.removeEventListener("mouseup",S)}},[g,x,C,S]),r("div",{ref:h,className:"relative w-full select-none",children:[e("img",{src:i,alt:"Scene background",className:"w-full rounded-md object-contain",draggable:!1}),s.map((t,a)=>{const n=g===t.id||x===t.id||c===t.id,l=G(a);return r("div",{className:"absolute cursor-move rounded-md border-2 border-dashed transition-colors group "+(n?`${l.border} bg-blue-200/30 shadow-lg z-20`:`${l.border} bg-blue-100/20 hover:bg-blue-200/30 z-10`),style:{left:`${t.x}%`,top:`${t.y}%`,width:`${t.width}%`,height:`${t.height}%`},onMouseDown:e=>((e,r,t,a)=>{if(a.preventDefault(),!a.target.dataset.resizeHandle&&(u(e),m?.(e),h.current)){const e=h.current.getBoundingClientRect(),n=(a.clientX-e.left)/e.width*100,l=(a.clientY-e.top)/e.height*100;p({x:n-r,y:l-t})}})(t.id,t.x,t.y,e),children:[r("div",{className:`absolute -top-5 left-0 rounded ${l.dot} px-1.5 py-0.5 text-[10px] font-medium text-white whitespace-nowrap shadow-sm`,children:[a+1,". ",t.label||`Zone ${a+1}`]}),e("div",{className:"flex h-full w-full items-center justify-center",children:e(I,{className:"h-4 w-4 text-blue-400/60"})}),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=>U(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=>U(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=>U(t.id,"se",t.width,t.height,e)})]},t.id)})]})}function J({initialData:o,onSave:v,onSaveToQuestionBank:k,onCancel:F,onChange:J,onUnsavedChangesChange:K,validationRef:W,externalErrors:ee,groups:re,questionIndexInPart:te=0,partId:ae=""}){console.log("[ListenAndDragObjectsIntoScene_Creator] RENDER",{initialData:o,questionIndexInPart:te,partId:ae,groups:re});const ne=q(e=>e.examId),le={backgroundImageUrl:o?.backgroundImageUrl||"",objectNodes:o?.objectNodes||[],dropZones:o?.dropZones||[]},{isGrouped:ie,currentGroupNumber:se,backgroundImageUrl:oe,setBackgroundImageUrl:de,objectNodes:ce,setObjectNodes:me,dropZones:he,setDropZones:ge}=B({partId:ae,groups:re,questionIndexInPart:te,initialData:le}),[ue,be]=t(o?.audioUrl||""),[pe,xe]=t(null!=o?.correctObjectId?String(o.correctObjectId):""),[fe,Ne]=t(null!=o?.correctDropZoneId?String(o.correctDropZoneId):""),[ve,we]=t(o?.points),[ye,je]=t(o?.explanation||""),[ke,Ie]=t(o?.isBackup||!1),[Ue,Ce]=t(!1),[Se,Ze]=t(""),[De,ze]=t(""),[Ve,Ee]=t(null),[Oe,Me]=t(!1),Pe=R({defaultValues:{audioUrl:ue,backgroundImageUrl:oe,objectNodes:ce,dropZones:he,correctObjectId:pe,correctDropZoneId:fe,isBackup:ke},zodSchema:T,mode:"onBlur"}),Re=a(o),Te=a(!0),$e=a(J),Be=a(!1),{previewUrl:Ae}=M(oe),{previewUrl:Xe}=M(ue);n(()=>{Ae&&Ze(Ae)},[Ae]),n(()=>{Xe&&ze(Xe)},[Xe]),n(()=>{$e.current=J},[J]);const qe=P(e=>{$e.current?.(e)},300);n(()=>{W&&(W.current={triggerValidation:async()=>Fe(),getFormData:()=>({audioUrl:ue,backgroundImageUrl:oe,objectNodes:ce,dropZones:he,correctObjectId:pe,correctDropZoneId:fe,points:ve,explanation:ye,isBackup:ke})})},[W,ue,oe,ce,he,pe,fe,ve,ye,ke]),n(()=>{if(!o)return void(Be.current||(Be.current=!0,Te.current=!1));if(!(JSON.stringify(Re.current)!==JSON.stringify(o))&&Be.current)return;Re.current=o;const e=o?.audioUrl||"",r=null!=o?.correctObjectId?String(o.correctObjectId):"",t=null!=o?.correctDropZoneId?String(o.correctDropZoneId):"",a=o?.points||1,n=o?.explanation||"",l=o?.isBackup||!1;Te.current=!0,Be.current=!0,be(e),xe(r),Ne(t),we(a),je(n),Ie(l),setTimeout(()=>{Te.current=!1},150)},[o]),n(()=>{if(Te.current||!Be.current)return;qe({audioUrl:ue,backgroundImageUrl:oe,objectNodes:ce,dropZones:he,correctObjectId:pe,correctDropZoneId:fe,points:ve,explanation:ye,isBackup:ke,groups:re,groupNumber:se})},[ue,oe,ce,he,pe,fe,ve,ye,ke,re,se,qe]);const Fe=async()=>{Pe.setValue("audioUrl",ue),Pe.setValue("backgroundImageUrl",oe),Pe.setValue("objectNodes",ce),Pe.setValue("dropZones",he),Pe.setValue("correctObjectId",String(pe)),Pe.setValue("correctDropZoneId",String(fe)),Pe.setValue("isBackup",ke);return await Pe.trigger()},Ge=(e,r)=>{me(ce.map(t=>t.id===e?{...t,...r}:t)),Pe.clearErrors()},Le=e=>{me(ce.filter(r=>r.id!==e)),pe===e&&xe(""),Pe.clearErrors()},He=(e,r)=>{ge(he.map(t=>t.id===e?{...t,...r}:t)),Pe.clearErrors()},Ye=e=>{ge(he.filter(r=>r.id!==e)),fe===e&&Ne(""),Ve===e&&Ee(null),Pe.clearErrors()},Qe=l((e,r,t)=>{ge(a=>a.map(a=>a.id===e?{...a,x:r,y:t}:a))},[]),_e=l((e,r,t)=>{ge(a=>a.map(a=>a.id===e?{...a,width:r,height:t}:a))},[]),Je=ce.find(e=>e.id===pe),Ke=he.find(e=>e.id===fe),We=he.findIndex(e=>e.id===fe);return r("div",Ue?{className:"space-y-6",children:[e(c,{children:e(h,{children:r("div",{className:"flex items-center justify-between",children:[r("div",{children:[e(g,{children:"Nghe và kéo thả đố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(i,{variant:"outline",size:"sm",onClick:()=>Ce(!1),children:[e(x,{className:"mr-2 h-4 w-4"}),"Quay lại chỉnh sửa"]})]})})}),e($,{questionData:{audioUrl:De||ue,backgroundImageUrl:Se||oe,objectNodes:ce,dropZones:he},isReviewMode:!0,userAnswer:pe&&fe?{objectId:pe,dropZoneId:fe}:void 0,correctAnswer:pe&&fe?{objectId:pe,dropZoneId:fe}:void 0,explanation:ye})]}:{className:"space-y-6",children:[e(X,{colorScheme:"rose",steps:[{text:"Upload hình nền (Scene) chung cho nhóm câu hỏi"},{text:"Thêm các đối tượng kéo-thả (draggable objects) với hình ảnh và tên"},{text:"Thiết lập vùng thả (drop zones) trên hình nền — vị trí mà học sinh cần kéo đối tượng vào"},{text:"Upload audio hướng dẫn cho từng câu"},{text:"Ghép đúng đối tượng với vùng thả tương ứng",highlight:!0}],tips:["Vùng thả nên đủ lớn và rõ ràng, tránh chồng chéo"]}),r(c,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-rose-100/50",children:[e("div",{className:"h-1.5 bg-gradient-to-r from-rose-500 via-pink-500 to-fuchsia-500"}),e(h,{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-rose-500 to-pink-600 shadow-md",children:e(V,{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-rose-100 px-3 py-1 text-sm font-medium text-rose-700",children:[e("span",{children:"🖼️"}),r("span",{children:["Nhóm ",se]}),e("span",{className:"text-xs font-normal opacity-75 ml-1",children:"(Dùng chung)"})]})]})]}),r(i,{variant:"outline",size:"sm",onClick:()=>Ce(!0),disabled:!oe||ce.length<2||he.length<1,className:"gap-2 border-rose-200 text-rose-600 hover:bg-rose-50 hover:text-rose-700",children:[e(f,{className:"h-4 w-4"}),"Preview"]})]})})]}),r(c,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-rose-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-rose-400 to-pink-400"}),e(h,{className:"pb-3",children:r("div",{className:"flex items-center justify-between",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-rose-100",children:e(w,{className:"h-3.5 w-3.5 text-rose-600"})}),e(g,{className:"text-lg font-semibold text-gray-800",children:"Ảnh nền & Đối tượng kéo thả"})]}),r(i,{variant:"ghost",size:"sm",onClick:()=>Me(!Oe),className:"gap-1 text-gray-500 hover:text-gray-700",children:[e(Oe?C:S,{className:"h-4 w-4"}),Oe?"Mở rộng":"Thu gọn"]})]})}),!Oe&&r(m,{className:"space-y-6",children:[e("div",{className:"rounded-xl border-2 p-5 bg-gradient-to-r from-orange-50 to-amber-50 border-orange-200",children:r("div",{className:"space-y-2",children:[r(s,{className:"flex items-center gap-2 text-base font-semibold text-orange-800",children:[e(w,{className:"h-4 w-4 text-orange-600"}),"Ảnh nền (Scene) ",e("span",{className:"text-red-500",children:"*"})]}),e(O,{id:"drag-scene-background",label:"",accept:"image/*",value:oe,onChange:e=>{de(e),Pe.clearErrors()},onPresignedUrlChange:Ze,maxSize:10,placeholder:"Upload hình ảnh nền hình ảnh nền",autoUpload:!0,prefix:"questions/scenes"}),Pe.formState.errors.backgroundImageUrl&&e("p",{className:"mt-1 text-sm text-red-500",children:Pe.formState.errors.backgroundImageUrl.message})]})}),Se&&r("div",{className:"rounded-xl border border-gray-200 bg-gray-50 overflow-hidden isolate",children:[r("div",{className:"flex items-center justify-between border-b border-gray-100 px-4 py-2.5 bg-white",children:[r("div",{className:"flex items-center gap-2",children:[e(w,{className:"h-4 w-4 text-blue-600"}),e("span",{className:"text-sm font-semibold text-gray-700",children:"Hình ảnh nền"}),e(L,{groupNumber:se})]}),e("div",{className:"text-xs text-gray-500 font-medium",children:"Kéo thả vị trí để định vị trên ảnh nền"})]}),e("div",{className:"p-3 w-full",children:e(_,{backgroundUrl:Se,dropZones:he,onZoneDrag:Qe,onZoneResize:_e,activeZoneId:Ve,onSelectZone:Ee})})]}),!Se&&e("div",{className:"rounded-xl border border-gray-200 bg-gray-50/50 p-6",children:r("div",{className:"flex flex-col items-center gap-2 text-center",children:[e("div",{className:"flex h-10 w-10 items-center justify-center rounded-full bg-gray-100",children:e(I,{className:"h-5 w-5 text-gray-400"})}),e("p",{className:"text-sm text-gray-500",children:"Upload ảnh nền trước để thiết lập vị trí trên hình ảnh nền."})]})}),Se&&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-blue-100",children:e(I,{className:"h-3.5 w-3.5 text-blue-600"})}),r("h3",{className:"text-base font-semibold text-gray-800",children:["Vị trí (",he.length," items)"]}),e(L,{groupNumber:se})]}),r(i,{variant:"outline",size:"sm",onClick:()=>{const e={id:A(),label:"",x:20+20*he.length%60,y:30+15*he.length%40,width:12,height:12};ge([...he,e]),Pe.clearErrors()},className:"gap-2 border-blue-200 text-blue-600 hover:bg-blue-50 hover:text-blue-700",children:[e(N,{className:"h-4 w-4"}),"Thêm vị trí"]})]}),e("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-3",children:0===he.length?r("div",{className:"col-span-full 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(I,{className:"h-6 w-6 text-gray-400"})}),e("p",{className:"text-sm text-gray-500",children:"Chưa có vị trí nào. Nhấn “Thêm vị trí” để bắt đầu."})]}):he.map((r,t)=>e(Q,{zone:r,index:t,onUpdate:He,onRemove:Ye,isActive:Ve===r.id,onSelect:Ee},r.id))}),Pe.formState.errors.dropZones&&e("div",{className:"col-span-full mt-2 rounded-lg border border-red-200 bg-red-50 p-3",children:e("p",{className:"text-sm text-red-500",children:Pe.formState.errors.dropZones.message||"Vui lòng nhập đầy đủ thông tin cho các vị trí."})})]}),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-amber-100",children:e(U,{className:"h-3.5 w-3.5 text-amber-600"})}),r("h3",{className:"text-base font-semibold text-gray-800",children:["Đối tượng (",ce.length," items)"]}),e(L,{groupNumber:se})]}),r(i,{variant:"outline",size:"sm",onClick:()=>{const e={id:A(),imageUrl:"",label:""};me([...ce,e]),Pe.clearErrors()},className:"gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700",children:[e(N,{className:"h-4 w-4"}),"Thêm đối tượng"]})]}),r("div",{className:"space-y-3",children:[0===ce.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 ở sidebar."})]}):ce.map((r,t)=>e(Y,{node:r,index:t,onUpdate:Ge,onRemove:Le},r.id)),Pe.formState.errors.objectNodes&&e("p",{className:"mt-2 text-sm text-red-500",children:Pe.formState.errors.objectNodes.message||"Vui lòng nhập đầy đủ thông tin cho các đối tượng."})]})]})]})]}),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(h,{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(m,{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:ve,onChange:e=>we(parseFloat(e.target.value)||1)})}),e("div",{className:"flex-1 flex flex-col justify-center pt-2"})]})}),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(O,{id:"drag-scene-audio",label:"",accept:"audio/*",value:ue,onChange:e=>{be(e),Pe.clearErrors()},onPresignedUrlChange:ze,maxSize:10,placeholder:"Upload file audio câu hỏi",autoUpload:!0,prefix:"questions/audio",examId:ne||void 0,partId:ae||void 0}),Pe.formState.errors.audioUrl&&e("p",{className:"mt-1 text-sm text-red-500",children:Pe.formState.errors.audioUrl.message}),De&&e("audio",{controls:!0,className:"mt-2 w-full",src:De,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:["Đối tượng đúng ",e("span",{className:"text-red-500",children:"*"})]})]}),0===ce.length?e("p",{className:"text-sm text-amber-600 italic",children:"Vui lòng thêm đối tượng ở phần dữ liệu nhóm trước khi chọn đáp án."}):e(b,{value:pe,onValueChange:e=>{xe(e),Pe.clearErrors()},className:"grid gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4",children:ce.map((t,a)=>r("label",{className:"flex cursor-pointer items-center gap-3 rounded-lg border-2 p-3 transition-all "+(pe===t.id?"border-green-500 bg-green-50 ring-1 ring-green-500 shadow-sm":"border-gray-200 hover:border-green-300 hover:bg-green-50/50"),children:[e(p,{value:t.id,id:`correct-obj-${t.id}`,className:"flex-shrink-0"}),e("div",{className:"h-12 w-12 flex-shrink-0 overflow-hidden rounded-md border border-gray-200 bg-white",children:t.imageUrl?e(H,{url:t.imageUrl,alt:t.label}):e("div",{className:"flex h-full w-full items-center justify-center",children:e(w,{className:"h-4 w-4 text-gray-300"})})}),e("div",{className:"min-w-0 flex-1",children:r("p",{className:"font-medium text-gray-900 text-sm line-clamp-2 break-all",children:[a+1,". ",t.label||"(Chưa đặt tên)"]})})]},t.id))}),Pe.formState.errors.correctObjectId&&e("div",{className:"rounded-lg border border-red-200 bg-red-50 p-3 mt-2",children:r("p",{className:"text-sm text-red-700",children:["⚠️ ",Pe.formState.errors.correctObjectId.message]})})]}),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-blue-100",children:e(I,{className:"h-3.5 w-3.5 text-blue-600"})}),r(s,{className:"text-base font-semibold text-gray-800",children:["Vị trí đúng ",e("span",{className:"text-red-500",children:"*"})]})]}),0===he.length?e("p",{className:"text-sm text-amber-600 italic",children:"Vui lòng thêm vị trí ở phần dữ liệu nhóm trước khi chọn đáp án."}):e(b,{value:fe,onValueChange:e=>{Ne(e),Pe.clearErrors()},className:"grid gap-3 sm:grid-cols-2 md:grid-cols-3",children:he.map((t,a)=>{const n=G(a);return r("label",{className:"flex cursor-pointer items-center gap-3 rounded-lg border-2 p-3 transition-all "+(fe===t.id?"border-blue-500 bg-blue-50 ring-1 ring-blue-500 shadow-sm":"border-gray-200 hover:border-blue-300 hover:bg-blue-50/50"),children:[e(p,{value:t.id,id:`correct-dz-${t.id}`,className:"flex-shrink-0"}),e("div",{className:`flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md ${n.dot} text-white shadow-sm`,children:e(Z,{className:"h-4 w-4"})}),r("div",{className:"min-w-0 flex-1",children:[r("p",{className:"font-medium text-gray-900 text-sm line-clamp-1 break-all",children:[a+1,". ",t.label||"(Chưa đặt nhãn)"]}),r("p",{className:"text-[11px] text-gray-400 mt-0.5",children:["Vị trí: (",t.x,"%, ",t.y,"%) — ",t.width,"×",t.height,"%"]})]})]},t.id)})}),Pe.formState.errors.correctDropZoneId&&e("div",{className:"rounded-lg border border-red-200 bg-red-50 p-3 mt-2",children:r("p",{className:"text-sm text-red-700",children:["⚠️ ",Pe.formState.errors.correctDropZoneId.message]})})]}),Je&&Ke&&r("div",{className:"rounded-xl border-2 border-green-200 bg-gradient-to-r from-green-50 to-emerald-50 p-4",children:[r("div",{className:"flex items-center gap-2 mb-3",children:[e("div",{className:"flex h-5 w-5 items-center justify-center rounded-full bg-green-500 text-white",children:e(j,{className:"h-3 w-3"})}),e("span",{className:"text-sm font-semibold text-green-800",children:"Đáp án đúng"})]}),r("div",{className:"flex items-center gap-3 rounded-lg bg-white/80 p-3 border border-green-100",children:[e("div",{className:"h-14 w-14 flex-shrink-0 overflow-hidden rounded-lg border-2 border-green-300 bg-white",children:Je.imageUrl?e(H,{url:Je.imageUrl,alt:Je.label}):e("div",{className:"flex h-full w-full items-center justify-center",children:e(w,{className:"h-5 w-5 text-gray-300"})})}),r("div",{className:"min-w-0",children:[e("p",{className:"font-semibold text-gray-900 text-sm",children:Je.label||"(Chưa đặt tên)"}),e("p",{className:"text-xs text-gray-500",children:"Đối tượng"})]}),e(D,{className:"h-5 w-5 text-green-500 flex-shrink-0 mx-1"}),e("div",{className:`flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg ${We>=0?G(We).dot:"bg-blue-500"} text-white shadow-sm`,children:e(Z,{className:"h-5 w-5"})}),r("div",{className:"min-w-0",children:[e("p",{className:"font-semibold text-gray-900 text-sm",children:Ke.label||"(Chưa đặt nhãn)"}),r("p",{className:"text-xs text-gray-500",children:["Vị trí (",Ke.x,"%, ",Ke.y,"%)"]})]})]})]}),(!pe||!fe)&&ce.length>0&&he.length>0&&r("div",{className:"flex items-start gap-2.5 rounded-lg border border-amber-200 bg-amber-50 p-3",children:[e(z,{className:"h-4 w-4 text-amber-500 flex-shrink-0 mt-0.5"}),e("p",{className:"text-sm text-amber-700",children:"Vui lòng chọn cả đối tượng đúng và vị trí đúng cho câu hỏi này"})]})]})]}),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(h,{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(E,{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(m,{children:e(d,{value:ye,onChange:e=>je(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 ListenAndDragObjectsIntoSceneCreator(r){return e(J,{...r})}
@@ -1,2 +1 @@
1
- export * from './ListenAndDragObjectsIntoSceneCreator';
2
- export * from './ListenAndDragObjectsIntoSceneClient';
1
+ export*from"./ListenAndDragObjectsIntoSceneCreator";export*from"./ListenAndDragObjectsIntoSceneClient";