@tinyweb_dev/oe-exam-sdk 0.1.1 → 0.1.3

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 (514) 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.d.ts +1 -1
  4. package/dist/api/exam-questions.js +1 -98
  5. package/dist/api/exam-questions.types.d.ts +45 -1
  6. package/dist/api/exam-questions.types.js +1 -1
  7. package/dist/api/exam-taking.js +1 -73
  8. package/dist/api/exam-taking.types.js +1 -1
  9. package/dist/api/index.js +1 -6
  10. package/dist/api/result-review.js +1 -50
  11. package/dist/api/result-review.types.js +1 -1
  12. package/dist/components/CueCardEditor.js +1 -58
  13. package/dist/components/common/ResolvedImage.js +1 -15
  14. package/dist/components/exams/CompletionDialog.d.ts +8 -0
  15. package/dist/components/exams/CompletionDialog.js +1 -0
  16. package/dist/components/exams/CreateExamInfoForm.d.ts +4 -1
  17. package/dist/components/exams/CreateExamInfoForm.js +1 -35
  18. package/dist/components/exams/CreateExamPageContainer.d.ts +3 -0
  19. package/dist/components/exams/CreateExamPageContainer.js +1 -103
  20. package/dist/components/exams/ExamCreator.d.ts +9 -3
  21. package/dist/components/exams/ExamCreator.js +1 -89
  22. package/dist/components/exams/ExamPartTabs.d.ts +1 -1
  23. package/dist/components/exams/ExamPartTabs.js +1 -40
  24. package/dist/components/exams/ExamPreviewDialog.d.ts +20 -0
  25. package/dist/components/exams/ExamPreviewDialog.js +1 -0
  26. package/dist/components/exams/ExamQuestionGrid.d.ts +1 -1
  27. package/dist/components/exams/ExamQuestionGrid.js +1 -18
  28. package/dist/components/exams/ExamQuestionsPageContainer.d.ts +3 -0
  29. package/dist/components/exams/ExamQuestionsPageContainer.js +1 -90
  30. package/dist/components/exams/ExamViewDialog.d.ts +24 -0
  31. package/dist/components/exams/ExamViewDialog.js +1 -0
  32. package/dist/components/exams/PartAudioUploader.js +1 -9
  33. package/dist/components/exams/TemplateSelector.js +1 -66
  34. package/dist/components/exams/UnsavedChangesDialog.d.ts +8 -0
  35. package/dist/components/exams/UnsavedChangesDialog.js +1 -0
  36. package/dist/components/exams/api.d.ts +1 -1
  37. package/dist/components/exams/api.js +1 -3
  38. package/dist/components/exams/create-exam.texts.d.ts +58 -0
  39. package/dist/components/exams/create-exam.texts.js +1 -0
  40. package/dist/components/exams/create-exam.validation.js +1 -18
  41. package/dist/components/exams/exam-question-status.d.ts +9 -0
  42. package/dist/components/exams/exam-question-status.js +1 -0
  43. package/dist/components/exams/exam-template.utils.js +1 -18
  44. package/dist/components/exams/index.d.ts +11 -1
  45. package/dist/components/exams/index.js +1 -10
  46. package/dist/components/exams/take/ExamTakingPageContainer.js +1 -153
  47. package/dist/components/exams/take/GenericQuestionRenderer.js +1 -14
  48. package/dist/components/exams/take/exam-taking.utils.js +1 -88
  49. package/dist/components/exams/take/hooks/useSpeakingRecorder.js +1 -24
  50. package/dist/components/exams/take/index.js +1 -2
  51. package/dist/components/exams/take/types.js +1 -1
  52. package/dist/components/index.js +1 -42
  53. package/dist/components/questions/_shared/config/question-types.config.js +1 -333
  54. package/dist/components/questions/_shared/constants/color-palette.js +1 -21
  55. package/dist/components/questions/_shared/hooks/useAnswerGroupQuestionGroup.js +1 -105
  56. package/dist/components/questions/_shared/hooks/useBasicQuestionGroup.js +1 -222
  57. package/dist/components/questions/_shared/hooks/useCompareImagesGroup.js +1 -140
  58. package/dist/components/questions/_shared/hooks/useImageQuestionGroup.js +1 -123
  59. package/dist/components/questions/_shared/hooks/useInfoExchangeGroup.js +1 -50
  60. package/dist/components/questions/_shared/hooks/useQuestionFormState.js +1 -54
  61. package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +1 -123
  62. package/dist/components/questions/_shared/hooks/useQuestionListGroup.js +1 -121
  63. package/dist/components/questions/_shared/hooks/useQuestionOnChange.js +1 -70
  64. package/dist/components/questions/_shared/hooks/useSceneQuestionGroup.js +1 -194
  65. package/dist/components/questions/_shared/index.js +1 -7
  66. package/dist/components/questions/_shared/types/answer-the-question.type.js +1 -1
  67. package/dist/components/questions/_shared/types/arrange-letters-into-words.type.js +1 -1
  68. package/dist/components/questions/_shared/types/choose-correct-adjective.type.js +1 -3
  69. package/dist/components/questions/_shared/types/color-region.config.js +1 -1
  70. package/dist/components/questions/_shared/types/fill-in-blank.type.js +1 -1
  71. package/dist/components/questions/_shared/types/fill-missing-words-in-grid.type.js +1 -1
  72. package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +1 -1
  73. package/dist/components/questions/_shared/types/index.js +1 -12
  74. package/dist/components/questions/_shared/types/label-the-picture.type.js +1 -1
  75. package/dist/components/questions/_shared/types/listen-and-choose-from-answer-group.type.js +1 -1
  76. package/dist/components/questions/_shared/types/listen-and-choose-objects-in-scene.type.js +1 -1
  77. package/dist/components/questions/_shared/types/listen-and-drag-objects-into-scene.type.js +1 -1
  78. package/dist/components/questions/_shared/types/listen-and-speak-answer.type.js +1 -1
  79. package/dist/components/questions/_shared/types/listen-and-speak-compare-images.type.js +1 -1
  80. package/dist/components/questions/_shared/types/listen-and-speak-image-group.type.js +1 -1
  81. package/dist/components/questions/_shared/types/listen-and-speak-info-exchange.type.js +1 -1
  82. package/dist/components/questions/_shared/types/listen-and-speak-odd-one-out.type.js +1 -3
  83. package/dist/components/questions/_shared/types/listen-and-speak-question-list.type.js +1 -1
  84. package/dist/components/questions/_shared/types/listen-and-speak-with-story-images.type.js +1 -3
  85. package/dist/components/questions/_shared/types/listen-and-tick-answer.type.js +1 -1
  86. package/dist/components/questions/_shared/types/listening-drawline.type.js +1 -1
  87. package/dist/components/questions/_shared/types/look-and-choose-answer.type.js +1 -1
  88. package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +1 -1
  89. package/dist/components/questions/_shared/types/match-by-writing-answer.type.js +1 -1
  90. package/dist/components/questions/_shared/types/multiple-choice.type.js +1 -1
  91. package/dist/components/questions/_shared/types/question-group.type.js +1 -11
  92. package/dist/components/questions/_shared/types/read-and-color-objects.type.js +1 -1
  93. package/dist/components/questions/_shared/types/read-displayed-content.type.js +1 -1
  94. package/dist/components/questions/_shared/types/read-passage-and-answer-questions.type.js +1 -1
  95. package/dist/components/questions/_shared/types/speaking-describe-image.type.js +1 -1
  96. package/dist/components/questions/_shared/types/spontaneous-qa.type.js +1 -1
  97. package/dist/components/questions/_shared/types/word-fill-paragraph-with-options.type.js +1 -1
  98. package/dist/components/questions/_shared/types/word-fill-paragraph.type.js +1 -1
  99. package/dist/components/questions/_shared/types/word-fill-structured-form.type.js +1 -1
  100. package/dist/components/questions/_shared/types/word-ordering.type.js +1 -1
  101. package/dist/components/questions/_shared/types/write-a-short-letter.type.js +1 -1
  102. package/dist/components/questions/_shared/types/write-correct-verb-form.type.js +1 -3
  103. package/dist/components/questions/_shared/types/write-sentences.type.js +1 -1
  104. package/dist/components/questions/_shared/types/write-short-paragraph.type.js +1 -1
  105. package/dist/components/questions/_shared/utils/answer-builder.js +1 -79
  106. package/dist/components/questions/_shared/utils/createColorRegionRegistration.js +1 -39
  107. package/dist/components/questions/_shared/utils/id-generator.js +1 -3
  108. package/dist/components/questions/_shared/utils/question-validation.js +1 -21
  109. package/dist/components/questions/components/ColorNode.js +1 -85
  110. package/dist/components/questions/components/ColorRegionClient.js +1 -545
  111. package/dist/components/questions/components/ColorRegionCreator.js +1 -420
  112. package/dist/components/questions/components/ColorRegionNode.js +1 -59
  113. package/dist/components/questions/components/CreatorGuide.js +1 -103
  114. package/dist/components/questions/components/NameNode.js +1 -26
  115. package/dist/components/questions/components/QuestionBankOption.js +1 -46
  116. package/dist/components/questions/components/QuestionBasicInfoForm.js +1 -9
  117. package/dist/components/questions/components/QuestionCommonFields.js +1 -33
  118. package/dist/components/questions/components/QuestionSearchDropdown.js +1 -258
  119. package/dist/components/questions/components/QuestionTypeSelector.js +1 -32
  120. package/dist/components/questions/components/QuestionTypeSpecificForms.js +1 -6
  121. package/dist/components/questions/components/RegionNode.js +1 -94
  122. package/dist/components/questions/components/index.js +1 -14
  123. package/dist/components/questions/creator/QuestionCreator.js +1 -90
  124. package/dist/components/questions/creator/QuestionGroupCreator.js +1 -119
  125. package/dist/components/questions/creator/dedicated-component-router.js +1 -79
  126. package/dist/components/questions/creator/index.js +1 -3
  127. package/dist/components/questions/creator/question-type-registry.js +1 -80
  128. package/dist/components/questions/groups/ArticlesGroupCard.js +1 -121
  129. package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -50
  130. package/dist/components/questions/groups/DocumentGroupCard.js +1 -114
  131. package/dist/components/questions/groups/MatchAnswerGroupCard.js +1 -25
  132. package/dist/components/questions/index.js +1 -10
  133. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +1 -37
  134. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +1 -351
  135. package/dist/components/questions/types/answer-the-question/register.js +1 -67
  136. package/dist/components/questions/types/answer-the-question/transform.js +1 -36
  137. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +1 -141
  138. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +1 -118
  139. package/dist/components/questions/types/arrange-letters-into-words/index.js +1 -2
  140. package/dist/components/questions/types/arrange-letters-into-words/register.js +1 -4
  141. package/dist/components/questions/types/arrange-letters-into-words/transform.js +1 -24
  142. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -64
  143. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +1 -169
  144. package/dist/components/questions/types/choose-correct-adjective/index.js +1 -2
  145. package/dist/components/questions/types/choose-correct-adjective/register.js +1 -55
  146. package/dist/components/questions/types/choose-correct-adjective/transform.js +1 -16
  147. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +1 -131
  148. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +1 -581
  149. package/dist/components/questions/types/choose-the-correct-answer/index.js +1 -2
  150. package/dist/components/questions/types/choose-the-correct-answer/register.js +1 -101
  151. package/dist/components/questions/types/choose-the-correct-answer/transform.js +1 -43
  152. package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +1 -275
  153. package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +1 -448
  154. package/dist/components/questions/types/fill-in-blank/index.js +1 -2
  155. package/dist/components/questions/types/fill-in-blank/register.js +1 -138
  156. package/dist/components/questions/types/fill-in-blank/transform.js +1 -59
  157. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +1 -117
  158. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +1 -671
  159. package/dist/components/questions/types/fill-missing-words-in-grid/index.js +1 -2
  160. package/dist/components/questions/types/fill-missing-words-in-grid/register.js +1 -37
  161. package/dist/components/questions/types/fill-missing-words-in-grid/transform.js +1 -15
  162. package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +1 -262
  163. package/dist/components/questions/types/gn-speaking-interview/register.js +1 -121
  164. package/dist/components/questions/types/gn-speaking-interview/transform.js +1 -48
  165. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingClient.js +1 -16
  166. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingCreator.js +1 -27
  167. package/dist/components/questions/types/image-object-matching/index.js +1 -2
  168. package/dist/components/questions/types/image-object-matching/register.js +1 -3
  169. package/dist/components/questions/types/image-object-matching/transform.js +1 -2
  170. package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +1 -102
  171. package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +1 -344
  172. package/dist/components/questions/types/label-the-picture/index.js +1 -2
  173. package/dist/components/questions/types/label-the-picture/register.js +1 -54
  174. package/dist/components/questions/types/label-the-picture/transform.js +1 -28
  175. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +1 -78
  176. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +1 -232
  177. package/dist/components/questions/types/listen-and-choose-from-answer-group/index.js +1 -2
  178. package/dist/components/questions/types/listen-and-choose-from-answer-group/register.js +1 -82
  179. package/dist/components/questions/types/listen-and-choose-from-answer-group/transform.js +1 -27
  180. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +1 -70
  181. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +1 -386
  182. package/dist/components/questions/types/listen-and-choose-objects-in-scene/index.js +1 -2
  183. package/dist/components/questions/types/listen-and-choose-objects-in-scene/register.js +1 -100
  184. package/dist/components/questions/types/listen-and-choose-objects-in-scene/transform.js +1 -33
  185. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +1 -106
  186. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +1 -457
  187. package/dist/components/questions/types/listen-and-drag-objects-into-scene/index.js +1 -2
  188. package/dist/components/questions/types/listen-and-drag-objects-into-scene/register.js +1 -110
  189. package/dist/components/questions/types/listen-and-drag-objects-into-scene/transform.js +1 -35
  190. package/dist/components/questions/types/listen-and-speak-answer/ArrowIndicator.js +1 -8
  191. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -22
  192. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +1 -291
  193. package/dist/components/questions/types/listen-and-speak-answer/index.js +1 -2
  194. package/dist/components/questions/types/listen-and-speak-answer/register.js +1 -84
  195. package/dist/components/questions/types/listen-and-speak-answer/transform.js +1 -27
  196. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -32
  197. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +1 -295
  198. package/dist/components/questions/types/listen-and-speak-compare-images/register.js +1 -79
  199. package/dist/components/questions/types/listen-and-speak-compare-images/transform.js +1 -27
  200. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -34
  201. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +1 -285
  202. package/dist/components/questions/types/listen-and-speak-image-group/register.js +1 -81
  203. package/dist/components/questions/types/listen-and-speak-image-group/transform.js +1 -26
  204. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -38
  205. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +1 -385
  206. package/dist/components/questions/types/listen-and-speak-info-exchange/register.js +1 -87
  207. package/dist/components/questions/types/listen-and-speak-info-exchange/transform.js +1 -32
  208. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -26
  209. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +1 -247
  210. package/dist/components/questions/types/listen-and-speak-odd-one-out/register.js +1 -59
  211. package/dist/components/questions/types/listen-and-speak-odd-one-out/transform.js +1 -18
  212. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -26
  213. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +1 -256
  214. package/dist/components/questions/types/listen-and-speak-question-list/register.js +1 -81
  215. package/dist/components/questions/types/listen-and-speak-question-list/transform.js +1 -26
  216. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -26
  217. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +1 -230
  218. package/dist/components/questions/types/listen-and-speak-with-story-images/register.js +1 -56
  219. package/dist/components/questions/types/listen-and-speak-with-story-images/transform.js +1 -16
  220. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +1 -72
  221. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +1 -230
  222. package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +1 -1
  223. package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +1 -66
  224. package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +1 -42
  225. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +1 -96
  226. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +1 -238
  227. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +1 -2
  228. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +1 -76
  229. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +1 -19
  230. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +1 -134
  231. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +1 -617
  232. package/dist/components/questions/types/look-picture-fill-word-hint/index.js +1 -2
  233. package/dist/components/questions/types/look-picture-fill-word-hint/register.js +1 -82
  234. package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +1 -29
  235. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +1 -38
  236. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +1 -359
  237. package/dist/components/questions/types/match-by-writing-answer/index.js +1 -3
  238. package/dist/components/questions/types/match-by-writing-answer/register.js +1 -58
  239. package/dist/components/questions/types/match-by-writing-answer/transform.js +1 -61
  240. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsClient.js +1 -16
  241. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsCreator.js +1 -23
  242. package/dist/components/questions/types/read-and-color-objects/index.js +1 -2
  243. package/dist/components/questions/types/read-and-color-objects/register.js +1 -3
  244. package/dist/components/questions/types/read-and-color-objects/transform.js +1 -36
  245. package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +1 -125
  246. package/dist/components/questions/types/read-displayed-content/register.js +1 -64
  247. package/dist/components/questions/types/read-displayed-content/transform.js +1 -12
  248. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -64
  249. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +1 -511
  250. package/dist/components/questions/types/read-passage-and-answer-questions/index.js +1 -2
  251. package/dist/components/questions/types/read-passage-and-answer-questions/register.js +1 -58
  252. package/dist/components/questions/types/read-passage-and-answer-questions/transform.js +1 -28
  253. package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +1 -373
  254. package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +1 -41
  255. package/dist/components/questions/types/speaking-conversation/register.js +1 -110
  256. package/dist/components/questions/types/speaking-conversation/transform.js +1 -34
  257. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +1 -74
  258. package/dist/components/questions/types/speaking-cue-card/register.js +1 -52
  259. package/dist/components/questions/types/speaking-cue-card/transform.js +1 -12
  260. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -10
  261. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +1 -161
  262. package/dist/components/questions/types/speaking-describe-image/index.js +1 -2
  263. package/dist/components/questions/types/speaking-describe-image/register.js +1 -69
  264. package/dist/components/questions/types/speaking-describe-image/transform.js +1 -16
  265. package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +1 -234
  266. package/dist/components/questions/types/spontaneous-qa/register.js +1 -93
  267. package/dist/components/questions/types/spontaneous-qa/transform.js +1 -34
  268. package/dist/components/questions/types/true-false/TrueFalseClient.js +1 -75
  269. package/dist/components/questions/types/true-false/TrueFalseCreator.js +1 -244
  270. package/dist/components/questions/types/true-false/index.js +1 -2
  271. package/dist/components/questions/types/true-false/register.js +1 -77
  272. package/dist/components/questions/types/true-false/transform.js +1 -32
  273. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +1 -233
  274. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +1 -631
  275. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphWrapper.js +1 -28
  276. package/dist/components/questions/types/word-fill-paragraph/index.js +1 -2
  277. package/dist/components/questions/types/word-fill-paragraph/register.js +1 -136
  278. package/dist/components/questions/types/word-fill-paragraph/transform.js +1 -56
  279. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -138
  280. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +1 -261
  281. package/dist/components/questions/types/word-fill-structured-form/index.js +1 -2
  282. package/dist/components/questions/types/word-fill-structured-form/register.js +1 -67
  283. package/dist/components/questions/types/word-fill-structured-form/transform.js +1 -40
  284. package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -84
  285. package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +1 -192
  286. package/dist/components/questions/types/word-ordering/index.js +1 -2
  287. package/dist/components/questions/types/word-ordering/register.js +1 -46
  288. package/dist/components/questions/types/word-ordering/transform.js +1 -24
  289. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +1 -98
  290. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +1 -320
  291. package/dist/components/questions/types/write-a-short-letter/index.js +1 -2
  292. package/dist/components/questions/types/write-a-short-letter/register.js +1 -68
  293. package/dist/components/questions/types/write-a-short-letter/transform.js +1 -25
  294. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +1 -61
  295. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +1 -196
  296. package/dist/components/questions/types/write-correct-verb-form/index.js +1 -2
  297. package/dist/components/questions/types/write-correct-verb-form/register.js +1 -62
  298. package/dist/components/questions/types/write-correct-verb-form/transform.js +1 -17
  299. package/dist/components/questions/types/write-sentences/WriteSentencesClient.js +1 -22
  300. package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +1 -174
  301. package/dist/components/questions/types/write-sentences/index.js +1 -2
  302. package/dist/components/questions/types/write-sentences/register.js +1 -58
  303. package/dist/components/questions/types/write-sentences/transform.js +1 -28
  304. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -96
  305. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +1 -161
  306. package/dist/components/questions/types/write-short-paragraph/index.js +1 -2
  307. package/dist/components/questions/types/write-short-paragraph/register.js +1 -4
  308. package/dist/components/questions/types/write-short-paragraph/transform.js +1 -21
  309. package/dist/components/questions/viewer/QuestionGroupViewer.js +1 -36
  310. package/dist/components/questions/viewer/QuestionViewer.js +1 -446
  311. package/dist/components/questions/viewer/index.js +1 -2
  312. package/dist/components/results/ResultReviewPageContainer.js +1 -98
  313. package/dist/components/results/ReviewQuestionRenderer.js +1 -72
  314. package/dist/components/results/index.js +1 -2
  315. package/dist/components/results/result-review.utils.js +1 -6
  316. package/dist/components/results/review-data.js +1 -150
  317. package/dist/components/results/review-renderer.utils.js +1 -97
  318. package/dist/components/results/review-types.js +1 -1
  319. package/dist/components/themes/cambridge-yle/CambridgeYleAudioPlayer.js +1 -138
  320. package/dist/components/themes/cambridge-yle/CambridgeYleBottomNav.js +1 -12
  321. package/dist/components/themes/cambridge-yle/CambridgeYleExamLayout.js +1 -18
  322. package/dist/components/themes/cambridge-yle/CambridgeYleExampleText.js +1 -11
  323. package/dist/components/themes/cambridge-yle/CambridgeYleHeader.js +1 -11
  324. package/dist/components/themes/cambridge-yle/CambridgeYleInstructionBanner.js +1 -9
  325. package/dist/components/themes/cambridge-yle/CambridgeYleNavButton.js +1 -13
  326. package/dist/components/themes/cambridge-yle/CambridgeYlePartBanner.js +1 -8
  327. package/dist/components/themes/cambridge-yle/CambridgeYleProgressBar.js +1 -6
  328. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCard.js +1 -5
  329. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCardFullWidth.js +1 -9
  330. package/dist/components/themes/cambridge-yle/CambridgeYleSidebar.js +1 -41
  331. package/dist/components/themes/cambridge-yle/CambridgeYleTimer.js +1 -25
  332. package/dist/components/themes/cambridge-yle/index.js +1 -13
  333. package/dist/components/themes/english-certification/EcAnswerTheQuestion.js +1 -24
  334. package/dist/components/themes/english-certification/EcMatchByWritingAnswer.js +1 -88
  335. package/dist/components/themes/english-certification/EcWordFillParagraph.js +1 -55
  336. package/dist/components/themes/english-certification/EcWordFillStructuredForm.js +1 -156
  337. package/dist/components/themes/english-certification/EcWritingTask.js +1 -34
  338. package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.js +1 -114
  339. package/dist/components/themes/english-certification/EnglishCertificationBrand.js +1 -11
  340. package/dist/components/themes/english-certification/EnglishCertificationExamLayout.js +1 -21
  341. package/dist/components/themes/english-certification/EnglishCertificationFlagButton.js +1 -15
  342. package/dist/components/themes/english-certification/EnglishCertificationFooterBar.js +1 -19
  343. package/dist/components/themes/english-certification/EnglishCertificationGroupedQuestion.js +1 -148
  344. package/dist/components/themes/english-certification/EnglishCertificationHeader.js +1 -31
  345. package/dist/components/themes/english-certification/EnglishCertificationNavigationPanel.js +1 -71
  346. package/dist/components/themes/english-certification/EnglishCertificationOptionSelector.js +1 -33
  347. package/dist/components/themes/english-certification/EnglishCertificationPassagePanel.js +1 -16
  348. package/dist/components/themes/english-certification/EnglishCertificationQuestionCard.js +1 -22
  349. package/dist/components/themes/english-certification/EnglishCertificationQuestionItem.js +1 -37
  350. package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.js +1 -72
  351. package/dist/components/themes/english-certification/EnglishCertificationReadingSection.js +1 -90
  352. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestion.js +1 -18
  353. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestionList.js +1 -29
  354. package/dist/components/themes/english-certification/english-certification-theme.js +1 -58
  355. package/dist/components/themes/english-certification/grouped-question-utils.js +1 -218
  356. package/dist/components/themes/english-certification/index.js +1 -21
  357. package/dist/components/themes/index.js +1 -6
  358. package/dist/components/themes/summer-sky/SummerSkyAudioPlayer.js +1 -170
  359. package/dist/components/themes/summer-sky/SummerSkyLayout.js +1 -221
  360. package/dist/components/themes/summer-sky/SummerSkySubmitModal.js +1 -18
  361. package/dist/components/themes/summer-sky/SummerSkyWaitingRoom.js +1 -194
  362. package/dist/components/themes/types.js +1 -1
  363. package/dist/components/ui/alert-dialog.js +1 -26
  364. package/dist/components/ui/alert.js +1 -22
  365. package/dist/components/ui/avatar.js +1 -12
  366. package/dist/components/ui/badge.js +1 -38
  367. package/dist/components/ui/button.js +1 -35
  368. package/dist/components/ui/calendar.js +1 -76
  369. package/dist/components/ui/card.js +1 -16
  370. package/dist/components/ui/carousel.js +1 -91
  371. package/dist/components/ui/checkbox.js +1 -9
  372. package/dist/components/ui/collapsible.js +1 -13
  373. package/dist/components/ui/command.js +1 -34
  374. package/dist/components/ui/confirm-dialog.js +1 -49
  375. package/dist/components/ui/dialog.js +1 -23
  376. package/dist/components/ui/drawer.js +1 -35
  377. package/dist/components/ui/dropdown-menu.js +1 -33
  378. package/dist/components/ui/file-upload.js +1 -155
  379. package/dist/components/ui/form.js +1 -62
  380. package/dist/components/ui/hover-card.js +1 -10
  381. package/dist/components/ui/image-preview.js +1 -29
  382. package/dist/components/ui/input.js +1 -8
  383. package/dist/components/ui/label.js +1 -9
  384. package/dist/components/ui/language-switcher.js +1 -15
  385. package/dist/components/ui/multi-select.js +1 -90
  386. package/dist/components/ui/points-input.js +1 -33
  387. package/dist/components/ui/popover.js +1 -26
  388. package/dist/components/ui/progress.js +1 -16
  389. package/dist/components/ui/radio-group.js +1 -12
  390. package/dist/components/ui/scroll-area.js +1 -11
  391. package/dist/components/ui/select.js +1 -38
  392. package/dist/components/ui/separator.js +1 -8
  393. package/dist/components/ui/sheet.js +1 -40
  394. package/dist/components/ui/single-select.js +1 -76
  395. package/dist/components/ui/skeleton.js +1 -6
  396. package/dist/components/ui/switch.js +1 -8
  397. package/dist/components/ui/table.js +1 -20
  398. package/dist/components/ui/tabs.js +1 -17
  399. package/dist/components/ui/textarea.js +1 -8
  400. package/dist/components/ui/tooltip.js +1 -11
  401. package/dist/index.d.ts +3 -3
  402. package/dist/index.js +1 -19
  403. package/dist/mcp/config.js +1 -32
  404. package/dist/mcp/http-client.js +1 -48
  405. package/dist/mcp/index.js +1 -29
  406. package/dist/mcp/tools/_helpers.js +1 -25
  407. package/dist/mcp/tools/exams.js +1 -439
  408. package/dist/mcp/tools/index.js +1 -6
  409. package/dist/mcp/tools/results.js +1 -19
  410. package/dist/shared/constants/ApiConstant.js +1 -276
  411. package/dist/shared/constants/QueryKeyConstant.js +1 -112
  412. package/dist/shared/constants/exam-level.enum.js +1 -41
  413. package/dist/shared/constants/landings/placement-test.constants.js +1 -431
  414. package/dist/shared/constants/question-skills.js +1 -267
  415. package/dist/shared/constants/routes.js +1 -81
  416. package/dist/shared/index.d.ts +3 -0
  417. package/dist/shared/index.js +1 -7
  418. package/dist/shared/lib/hooks/index.js +1 -10
  419. package/dist/shared/lib/hooks/useAlertDialog.js +1 -57
  420. package/dist/shared/lib/hooks/useAudioPlayCounter.js +1 -30
  421. package/dist/shared/lib/hooks/useAudioPreview.js +1 -81
  422. package/dist/shared/lib/hooks/useAudioUpload.js +1 -24
  423. package/dist/shared/lib/hooks/useConfirmDialog.js +1 -89
  424. package/dist/shared/lib/hooks/useDebouncedCallback.js +1 -32
  425. package/dist/shared/lib/hooks/useDelayedAutoPlay.js +1 -12
  426. package/dist/shared/lib/hooks/useFileUpload.js +1 -24
  427. package/dist/shared/lib/hooks/useImageUrl.js +1 -5
  428. package/dist/shared/lib/hooks/usePresignedFileUrl.js +1 -10
  429. package/dist/shared/lib/hooks/useReactHookForm.js +1 -31
  430. package/dist/shared/lib/hooks/useToast.js +1 -62
  431. package/dist/shared/lib/i18n/index.js +1 -54
  432. package/dist/shared/lib/i18n/messages/en/admin.js +1 -2104
  433. package/dist/shared/lib/i18n/messages/en/auth.js +1 -86
  434. package/dist/shared/lib/i18n/messages/en/common.js +1 -140
  435. package/dist/shared/lib/i18n/messages/en/file-upload.js +1 -18
  436. package/dist/shared/lib/i18n/messages/en/student.js +1 -133
  437. package/dist/shared/lib/i18n/messages/en/terms.js +1 -37
  438. package/dist/shared/lib/i18n/messages/en.js +1 -15
  439. package/dist/shared/lib/i18n/messages/vi/admin.js +1 -2104
  440. package/dist/shared/lib/i18n/messages/vi/auth.js +1 -86
  441. package/dist/shared/lib/i18n/messages/vi/common.js +1 -140
  442. package/dist/shared/lib/i18n/messages/vi/file-upload.js +1 -18
  443. package/dist/shared/lib/i18n/messages/vi/student.js +1 -133
  444. package/dist/shared/lib/i18n/messages/vi/terms.js +1 -37
  445. package/dist/shared/lib/i18n/messages/vi.js +1 -15
  446. package/dist/shared/lib/i18n/types.js +1 -1
  447. package/dist/shared/lib/stores/examQuestionStore.js +1 -212
  448. package/dist/shared/lib/stores/localeStore.js +1 -10
  449. package/dist/shared/lib/utils/public-page.js +1 -44
  450. package/dist/shared/lib/utils/question-reverse-transform.d.ts +6 -0
  451. package/dist/shared/lib/utils/question-reverse-transform.js +1 -0
  452. package/dist/shared/lib/utils/question-transform-types.js +1 -1
  453. package/dist/shared/lib/utils/question-transform-validation.d.ts +6 -0
  454. package/dist/shared/lib/utils/question-transform-validation.js +1 -0
  455. package/dist/shared/lib/utils/question-transform.d.ts +6 -2
  456. package/dist/shared/lib/utils/question-transform.js +1 -174
  457. package/dist/shared/lib/utils.js +1 -168
  458. package/dist/shared/lib/validation/choose-correct-answer.validation.js +1 -111
  459. package/dist/shared/lib/validation/label-the-picture.validation.js +1 -19
  460. package/dist/shared/lib/validation/listen-and-choose-from-answer-group.validation.js +1 -46
  461. package/dist/shared/lib/validation/listen-and-choose-objects-in-scene.validation.js +1 -21
  462. package/dist/shared/lib/validation/listen-and-drag-objects-into-scene.validation.js +1 -34
  463. package/dist/shared/lib/validation/listen-and-speak-answer.validation.js +1 -40
  464. package/dist/shared/lib/validation/listen-and-speak-compare-images.validation.js +1 -45
  465. package/dist/shared/lib/validation/listen-and-speak-image-group.validation.js +1 -19
  466. package/dist/shared/lib/validation/listen-and-speak-info-exchange.validation.js +1 -39
  467. package/dist/shared/lib/validation/listen-and-speak-odd-one-out.validation.js +1 -32
  468. package/dist/shared/lib/validation/listen-and-speak-question-list.validation.js +1 -19
  469. package/dist/shared/lib/validation/listen-and-speak-with-story-images.validation.js +1 -28
  470. package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +1 -12
  471. package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +1 -47
  472. package/dist/shared/lib/validation/read-and-color-objects.validation.js +1 -29
  473. package/dist/shared/lib/validation/read-displayed-content.validation.js +1 -28
  474. package/dist/shared/lib/validation/speaking-conversation.validation.js +1 -75
  475. package/dist/shared/lib/validation/speaking-describe-image.validation.js +1 -31
  476. package/dist/shared/lib/validation/spontaneous-qa.validation.js +1 -48
  477. package/dist/shared/lib/validation/word-fill-paragraph-default.validation.js +1 -38
  478. package/dist/shared/lib/validation/word-fill-paragraph-with-options.validation.js +1 -39
  479. package/dist/shared/lib/validation/word-fill-paragraph.validation.js +1 -43
  480. package/dist/shared/lib/validation/word-ordering.validation.js +1 -33
  481. package/dist/shared/types/branch.types.js +1 -6
  482. package/dist/shared/types/common.types.js +1 -50
  483. package/dist/shared/types/entities/exam-schedule.types.js +1 -5
  484. package/dist/shared/types/entities/exam.types.js +1 -1
  485. package/dist/shared/types/entities/question.types.js +1 -4
  486. package/dist/shared/types/entities/result.types.js +1 -4
  487. package/dist/shared/types/entities/user.types.js +1 -4
  488. package/dist/shared/types/exam-taking.types.js +1 -2
  489. package/dist/shared/types/history.types.js +1 -34
  490. package/dist/shared/types/index.js +1 -1
  491. package/dist/shared/types/questions/choose-correct-adjective.js +1 -4
  492. package/dist/shared/types/questions/choose-the-correct-answer.js +1 -9
  493. package/dist/shared/types/questions/fill-missing-words-in-grid.js +1 -11
  494. package/dist/shared/types/questions/index.js +1 -40
  495. package/dist/shared/types/questions/listen-and-speak-answer.js +1 -1
  496. package/dist/shared/types/questions/listen-and-speak-compare-images.js +1 -1
  497. package/dist/shared/types/questions/listen-and-speak-image-group.js +1 -1
  498. package/dist/shared/types/questions/listen-and-speak-info-exchange.js +1 -3
  499. package/dist/shared/types/questions/listen-and-speak-odd-one-out.js +1 -3
  500. package/dist/shared/types/questions/listen-and-speak-question-list.js +1 -1
  501. package/dist/shared/types/questions/listen-and-speak-with-story-images.js +1 -3
  502. package/dist/shared/types/questions/look-picture-choose-correct-answer.js +1 -5
  503. package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +1 -9
  504. package/dist/shared/types/questions/look-picture-fill-word-hint.js +1 -7
  505. package/dist/shared/types/questions/read-displayed-content.js +1 -1
  506. package/dist/shared/types/questions/speaking-conversation.js +1 -7
  507. package/dist/shared/types/questions/speaking-describe-image.js +1 -1
  508. package/dist/shared/types/questions/spontaneous-qa.js +1 -1
  509. package/dist/shared/types/questions/write-correct-verb-form.js +1 -12
  510. package/dist/shared/types/role.types.js +1 -1
  511. package/dist/shared/types/structure-management.js +1 -28
  512. package/dist/shared/types/student.types.js +1 -54
  513. package/dist/shared/types/teacher-dashboard-rooms.js +1 -2
  514. package/package.json +12 -3
@@ -1,110 +1 @@
1
- import { ListenAndDragObjectsIntoSceneCreator } from './ListenAndDragObjectsIntoSceneCreator';
2
- export const listenAndDragObjectsIntoSceneRegistration = {
3
- component: ListenAndDragObjectsIntoSceneCreator,
4
- transformInitialData: (initialData) => {
5
- if (!initialData)
6
- return undefined;
7
- const d = initialData;
8
- // Group scene data
9
- // Use data from questionGroup.payload first (API format), fallback to answer (reverse-transformed)
10
- const backgroundImageUrl = d?.questionGroup?.payload?.backgroundImageUrl ||
11
- d?.answer?.backgroundImageUrl ||
12
- d?.content?.backgroundImageUrl ||
13
- d?.backgroundImageUrl || '';
14
- const objectNodes = (d?.questionGroup?.payload?.objectNodes?.length > 0 && d?.questionGroup?.payload?.objectNodes) ||
15
- (d?.answer?.objectNodes?.length > 0 && d?.answer?.objectNodes) ||
16
- (d?.content?.objectNodes?.length > 0 && d?.content?.objectNodes) ||
17
- d?.objectNodes || [];
18
- const dropZones = (d?.questionGroup?.payload?.dropZones?.length > 0 && d?.questionGroup?.payload?.dropZones) ||
19
- (d?.answer?.dropZones?.length > 0 && d?.answer?.dropZones) ||
20
- (d?.content?.dropZones?.length > 0 && d?.content?.dropZones) ||
21
- d?.dropZones || [];
22
- // Question specific data - prioritize content (API) then answer (reverse-transformed)
23
- const audioUrl = d?.content?.audioUrl || d?.answer?.audioUrl || d?.audioUrl || '';
24
- // Correct object ID from multiple possible locations
25
- const correctObjectId = d?.answer?.correctObjectId ||
26
- d?.correctAnswer?.objectId ||
27
- d?.answer?.objectId ||
28
- d?.content?.correctObjectId ||
29
- d?.correctObjectId || '';
30
- // Correct drop zone ID from multiple possible locations
31
- const correctDropZoneId = d?.answer?.correctDropZoneId ||
32
- d?.correctAnswer?.dropZoneId ||
33
- d?.answer?.dropZoneId ||
34
- d?.content?.correctDropZoneId ||
35
- d?.correctDropZoneId || '';
36
- // Points and explanation
37
- const points = d?.points || d?.answer?.points || 1;
38
- const explanation = d?.explanation || d?.answer?.explanation || d?.content?.explanation || '';
39
- const isBackup = d?.content?.isBackup || d?.isBackup || false;
40
- console.log('[LISTEN_AND_DRAG_OBJECTS_INTO_SCENE register] transformInitialData:', {
41
- correctObjectId,
42
- correctDropZoneId,
43
- audioUrl: audioUrl ? '✓' : '✗',
44
- backgroundImageUrl: backgroundImageUrl ? '✓' : '✗',
45
- objectNodesCount: objectNodes?.length || 0,
46
- dropZonesCount: dropZones?.length || 0,
47
- });
48
- return {
49
- audioUrl,
50
- backgroundImageUrl,
51
- objectNodes,
52
- dropZones,
53
- correctObjectId,
54
- correctDropZoneId,
55
- points,
56
- explanation,
57
- isBackup,
58
- isCompleted: d?.isCompleted,
59
- };
60
- },
61
- getExtraProps: (ctx) => ({
62
- groups: ctx.groups,
63
- questionIndexInPart: ctx.questionIndexInPart,
64
- partId: ctx.partId,
65
- onUnsavedChangesChange: ctx.onUnsavedChangesChange,
66
- validationRef: ctx.validationRef,
67
- }),
68
- wrapOnSave: (data, ctx) => ({
69
- type: ctx.questionType,
70
- content: {
71
- ...(typeof ctx.state.content === 'object' && ctx.state.content !== null ? ctx.state.content : {}),
72
- isBackup: data.isBackup || false,
73
- },
74
- answer: {
75
- audioUrl: data.audioUrl,
76
- backgroundImageUrl: data.backgroundImageUrl,
77
- objectNodes: data.objectNodes,
78
- dropZones: data.dropZones,
79
- correctObjectId: data.correctObjectId,
80
- correctDropZoneId: data.correctDropZoneId,
81
- groupNumber: data.groupNumber,
82
- },
83
- points: data?.points ?? ctx.state.points ?? 1,
84
- level: ctx.state.level,
85
- difficulty: ctx.state.difficulty,
86
- skill: ctx.state.skill,
87
- explanation: data?.explanation || '',
88
- }),
89
- wrapOnChange: (data, ctx) => ({
90
- type: ctx.questionType,
91
- content: {
92
- ...(typeof ctx.state.content === 'object' && ctx.state.content !== null ? ctx.state.content : {}),
93
- isBackup: data.isBackup || false,
94
- },
95
- answer: {
96
- audioUrl: data.audioUrl,
97
- backgroundImageUrl: data.backgroundImageUrl,
98
- objectNodes: data.objectNodes,
99
- dropZones: data.dropZones,
100
- correctObjectId: data.correctObjectId,
101
- correctDropZoneId: data.correctDropZoneId,
102
- groupNumber: data.groupNumber,
103
- },
104
- points: data?.points ?? ctx.state.points ?? 1,
105
- level: ctx.state.level,
106
- difficulty: ctx.state.difficulty,
107
- skill: ctx.state.skill,
108
- explanation: data?.explanation || '',
109
- }),
110
- };
1
+ import{ListenAndDragObjectsIntoSceneCreator as e}from"./ListenAndDragObjectsIntoSceneCreator";export const listenAndDragObjectsIntoSceneRegistration={component:e,transformInitialData:e=>{if(!e)return;const o=e,t=o?.questionGroup?.payload?.backgroundImageUrl||o?.answer?.backgroundImageUrl||o?.content?.backgroundImageUrl||o?.backgroundImageUrl||"",n=o?.questionGroup?.payload?.objectNodes?.length>0&&o?.questionGroup?.payload?.objectNodes||o?.answer?.objectNodes?.length>0&&o?.answer?.objectNodes||o?.content?.objectNodes?.length>0&&o?.content?.objectNodes||o?.objectNodes||[],r=o?.questionGroup?.payload?.dropZones?.length>0&&o?.questionGroup?.payload?.dropZones||o?.answer?.dropZones?.length>0&&o?.answer?.dropZones||o?.content?.dropZones?.length>0&&o?.content?.dropZones||o?.dropZones||[],a=o?.content?.audioUrl||o?.answer?.audioUrl||o?.audioUrl||"",c=o?.answer?.correctObjectId||o?.correctAnswer?.objectId||o?.answer?.objectId||o?.content?.correctObjectId||o?.correctObjectId||"",s=o?.answer?.correctDropZoneId||o?.correctAnswer?.dropZoneId||o?.answer?.dropZoneId||o?.content?.correctDropZoneId||o?.correctDropZoneId||"",d=o?.points||o?.answer?.points||1,p=o?.explanation||o?.answer?.explanation||o?.content?.explanation||"",l=o?.content?.isBackup||o?.isBackup||!1;return console.log("[LISTEN_AND_DRAG_OBJECTS_INTO_SCENE register] transformInitialData:",{correctObjectId:c,correctDropZoneId:s,audioUrl:a?"✓":"✗",backgroundImageUrl:t?"✓":"✗",objectNodesCount:n?.length||0,dropZonesCount:r?.length||0}),{audioUrl:a,backgroundImageUrl:t,objectNodes:n,dropZones:r,correctObjectId:c,correctDropZoneId:s,points:d,explanation:p,isBackup:l,isCompleted:o?.isCompleted}},getExtraProps:e=>({groups:e.groups,questionIndexInPart:e.questionIndexInPart,partId:e.partId,onUnsavedChangesChange:e.onUnsavedChangesChange,validationRef:e.validationRef}),wrapOnSave:(e,o)=>({type:o.questionType,content:{..."object"==typeof o.state.content&&null!==o.state.content?o.state.content:{},isBackup:e.isBackup||!1},answer:{audioUrl:e.audioUrl,backgroundImageUrl:e.backgroundImageUrl,objectNodes:e.objectNodes,dropZones:e.dropZones,correctObjectId:e.correctObjectId,correctDropZoneId:e.correctDropZoneId,groupNumber:e.groupNumber},points:e?.points??o.state.points??1,level:o.state.level,difficulty:o.state.difficulty,skill:o.state.skill,explanation:e?.explanation||""}),wrapOnChange:(e,o)=>({type:o.questionType,content:{..."object"==typeof o.state.content&&null!==o.state.content?o.state.content:{},isBackup:e.isBackup||!1},answer:{audioUrl:e.audioUrl,backgroundImageUrl:e.backgroundImageUrl,objectNodes:e.objectNodes,dropZones:e.dropZones,correctObjectId:e.correctObjectId,correctDropZoneId:e.correctDropZoneId,groupNumber:e.groupNumber},points:e?.points??o.state.points??1,level:o.state.level,difficulty:o.state.difficulty,skill:o.state.skill,explanation:e?.explanation||""})};
@@ -1,35 +1 @@
1
- export const transformListenAndDragObjectsIntoScene = (question) => {
2
- const answerData = question.answer;
3
- const apiContent = {};
4
- if (answerData?.audioUrl) {
5
- apiContent.audioUrl = answerData.audioUrl;
6
- }
7
- const sceneData = {
8
- backgroundImageUrl: answerData.backgroundImageUrl,
9
- objectNodes: (answerData.objectNodes || []).map((node) => ({
10
- id: node.id,
11
- imageUrl: node.imageUrl,
12
- label: node.label,
13
- })),
14
- dropZones: (answerData.dropZones || []).map((zone) => ({
15
- id: zone.id,
16
- label: zone.label,
17
- x: zone.x,
18
- y: zone.y,
19
- width: zone.width,
20
- height: zone.height,
21
- })),
22
- };
23
- const apiCorrectAnswer = {
24
- objectId: answerData.correctObjectId || '',
25
- dropZoneId: answerData.correctDropZoneId || '',
26
- };
27
- // Get group number directly from the component payload
28
- const groupNumber = answerData.groupNumber || 1;
29
- return {
30
- apiContent,
31
- apiCorrectAnswer,
32
- sceneData,
33
- groupNumber,
34
- };
35
- };
1
+ export const transformListenAndDragObjectsIntoScene=e=>{const r=e.answer,o={};r?.audioUrl&&(o.audioUrl=r.audioUrl);const t={backgroundImageUrl:r.backgroundImageUrl,objectNodes:(r.objectNodes||[]).map(e=>({id:e.id,imageUrl:e.imageUrl,label:e.label})),dropZones:(r.dropZones||[]).map(e=>({id:e.id,label:e.label,x:e.x,y:e.y,width:e.width,height:e.height}))};return{apiContent:o,apiCorrectAnswer:{objectId:r.correctObjectId||"",dropZoneId:r.correctDropZoneId||""},sceneData:t,groupNumber:r.groupNumber||1}};
@@ -1,8 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- export function ArrowIndicatorSVG({ idSuffix = '', animated = false, className = '', }) {
4
- const gradientId = `arrow-grad${idSuffix}`;
5
- const shadowId = `arrow-shadow${idSuffix}`;
6
- const glowId = `arrow-glow${idSuffix}`;
7
- return (_jsxs("svg", { viewBox: "0 0 64 80", className: className, xmlns: "http://www.w3.org/2000/svg", style: { filter: `url(#${shadowId})` }, children: [_jsxs("defs", { children: [_jsxs("linearGradient", { id: gradientId, x1: "0.5", y1: "0", x2: "0.5", y2: "1", children: [_jsx("stop", { offset: "0%", stopColor: "#FF4D6A" }), _jsx("stop", { offset: "45%", stopColor: "#E8364F" }), _jsx("stop", { offset: "100%", stopColor: "#C41E3A" })] }), _jsx("filter", { id: shadowId, x: "-20%", y: "-10%", width: "140%", height: "130%", children: _jsx("feDropShadow", { dx: "0", dy: "2", stdDeviation: "3", floodColor: "#C41E3A", floodOpacity: "0.35" }) }), animated && (_jsxs("filter", { id: glowId, x: "-30%", y: "-20%", width: "160%", height: "150%", children: [_jsx("feGaussianBlur", { stdDeviation: "4", result: "blur" }), _jsx("feFlood", { floodColor: "#FF4D6A", floodOpacity: "0.4", result: "color" }), _jsx("feComposite", { in: "color", in2: "blur", operator: "in", result: "glow" }), _jsxs("feMerge", { children: [_jsx("feMergeNode", { in: "glow" }), _jsx("feMergeNode", { in: "SourceGraphic" })] })] }))] }), _jsxs("g", { style: animated ? { filter: `url(#${glowId})` } : undefined, children: [_jsx("path", { d: "\n M32 72\n C31 72 30 71.5 29.2 70.5\n L6 44\n C4 41.5 5.5 38 8.5 38\n L19 38\n C20.1 38 21 37.1 21 36\n L21 12\n C21 8.7 23.7 6 27 6\n L37 6\n C40.3 6 43 8.7 43 12\n L43 36\n C43 37.1 43.9 38 45 38\n L55.5 38\n C58.5 38 60 41.5 58 44\n L34.8 70.5\n C34 71.5 33 72 32 72\n Z\n ", fill: `url(#${gradientId})`, stroke: "#a01830", strokeWidth: "1", strokeOpacity: "0.25" }), _jsx("path", { d: "\n M28 10\n C26.3 10 25 11.3 25 13\n L25 36\n C25 37.7 23.7 39 22 39\n L12 39\n L30 63\n L30 36\n C30 34.5 28 33 26 33\n L12 33\n L12 39\n L22 39\n C23.7 39 25 37.7 25 36\n L25 13\n C25 11.3 26.3 10 28 10\n Z\n ", fill: "white", opacity: "0.12" }), _jsx("rect", { x: "25", y: "8", width: "10", height: "20", rx: "5", fill: "white", opacity: "0.18" }), _jsx("circle", { cx: "28", cy: "14", r: "2.5", fill: "white", opacity: "0.3" })] }), animated && (_jsxs("circle", { cx: "32", cy: "72", r: "4", fill: "#FF4D6A", opacity: "0.6", children: [_jsx("animate", { attributeName: "r", values: "3;8;3", dur: "2s", repeatCount: "indefinite" }), _jsx("animate", { attributeName: "opacity", values: "0.5;0;0.5", dur: "2s", repeatCount: "indefinite" })] }))] }));
8
- }
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";export function ArrowIndicatorSVG({idSuffix:i="",animated:r=!1,className:o=""}){const n=`arrow-grad${i}`,l=`arrow-shadow${i}`,a=`arrow-glow${i}`;return t("svg",{viewBox:"0 0 64 80",className:o,xmlns:"http://www.w3.org/2000/svg",style:{filter:`url(#${l})`},children:[t("defs",{children:[t("linearGradient",{id:n,x1:"0.5",y1:"0",x2:"0.5",y2:"1",children:[e("stop",{offset:"0%",stopColor:"#FF4D6A"}),e("stop",{offset:"45%",stopColor:"#E8364F"}),e("stop",{offset:"100%",stopColor:"#C41E3A"})]}),e("filter",{id:l,x:"-20%",y:"-10%",width:"140%",height:"130%",children:e("feDropShadow",{dx:"0",dy:"2",stdDeviation:"3",floodColor:"#C41E3A",floodOpacity:"0.35"})}),r&&t("filter",{id:a,x:"-30%",y:"-20%",width:"160%",height:"150%",children:[e("feGaussianBlur",{stdDeviation:"4",result:"blur"}),e("feFlood",{floodColor:"#FF4D6A",floodOpacity:"0.4",result:"color"}),e("feComposite",{in:"color",in2:"blur",operator:"in",result:"glow"}),t("feMerge",{children:[e("feMergeNode",{in:"glow"}),e("feMergeNode",{in:"SourceGraphic"})]})]})]}),t("g",{style:r?{filter:`url(#${a})`}:void 0,children:[e("path",{d:"\n M32 72\n C31 72 30 71.5 29.2 70.5\n L6 44\n C4 41.5 5.5 38 8.5 38\n L19 38\n C20.1 38 21 37.1 21 36\n L21 12\n C21 8.7 23.7 6 27 6\n L37 6\n C40.3 6 43 8.7 43 12\n L43 36\n C43 37.1 43.9 38 45 38\n L55.5 38\n C58.5 38 60 41.5 58 44\n L34.8 70.5\n C34 71.5 33 72 32 72\n Z\n ",fill:`url(#${n})`,stroke:"#a01830",strokeWidth:"1",strokeOpacity:"0.25"}),e("path",{d:"\n M28 10\n C26.3 10 25 11.3 25 13\n L25 36\n C25 37.7 23.7 39 22 39\n L12 39\n L30 63\n L30 36\n C30 34.5 28 33 26 33\n L12 33\n L12 39\n L22 39\n C23.7 39 25 37.7 25 36\n L25 13\n C25 11.3 26.3 10 28 10\n Z\n ",fill:"white",opacity:"0.12"}),e("rect",{x:"25",y:"8",width:"10",height:"20",rx:"5",fill:"white",opacity:"0.18"}),e("circle",{cx:"28",cy:"14",r:"2.5",fill:"white",opacity:"0.3"})]}),r&&t("circle",{cx:"32",cy:"72",r:"4",fill:"#FF4D6A",opacity:"0.6",children:[e("animate",{attributeName:"r",values:"3;8;3",dur:"2s",repeatCount:"indefinite"}),e("animate",{attributeName:"opacity",values:"0.5;0;0.5",dur:"2s",repeatCount:"indefinite"})]})]})}
@@ -1,22 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Mic, Volume2, CheckCircle2, Play, Pause, Info } from 'lucide-react';
4
- import { Button } from '../../../../components/ui/button';
5
- import { usePresignedFileUrl, useAudioPreview } from '../../../../shared/lib/hooks';
6
- import { ArrowIndicatorSVG } from './ArrowIndicator';
7
- // ─── Sub-component: Custom Audio Player ──────────────────────────────────────
8
- function QuestionAudioPlayer({ audioUrl }) {
9
- const { previewUrl } = usePresignedFileUrl(audioUrl);
10
- const { isPlaying, currentTime, duration, progressPercentage, handlePlayPause, handleProgressClick, formatTime, } = useAudioPreview(previewUrl);
11
- return (_jsxs("div", { className: "flex items-center gap-3 rounded-lg bg-gradient-to-r from-indigo-50 to-purple-50 border border-indigo-200 px-4 py-2.5", children: [_jsx(Button, { variant: "outline", size: "icon", onClick: handlePlayPause, disabled: !previewUrl, className: "h-8 w-8 border-indigo-300 text-indigo-600 hover:bg-indigo-100", children: isPlaying ? _jsx(Pause, { className: "h-3.5 w-3.5" }) : _jsx(Play, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "text-muted-foreground min-w-[70px] text-xs", children: [formatTime(currentTime), " / ", formatTime(duration)] }), _jsx("div", { className: "bg-indigo-200 relative h-1.5 flex-1 cursor-pointer rounded-full", onClick: handleProgressClick, children: _jsx("div", { className: "bg-indigo-500 absolute top-0 left-0 h-full rounded-full transition-all", style: { width: `${progressPercentage}%` } }) }), _jsx(Volume2, { className: "h-4 w-4 text-indigo-400" })] }));
12
- }
13
- // ─── Main Client Component ───────────────────────────────────────────────────
14
- export function ListenAndSpeakAnswerClient({ questionData, isReviewMode = false, }) {
15
- const { previewUrl: backgroundUrl } = usePresignedFileUrl(questionData.backgroundImageUrl || '');
16
- return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsx("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-indigo-50 to-blue-50 px-5 py-3", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-indigo-500 to-blue-600 text-white shadow-sm", children: _jsx(Mic, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 tr\u1EA3 l\u1EDDi" })] }) }), _jsxs("div", { className: "p-5 space-y-5", children: [questionData.audioUrl ? (_jsx(QuestionAudioPlayer, { audioUrl: questionData.audioUrl })) : (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3", children: [_jsx(Volume2, { className: "h-4 w-4 text-gray-400" }), _jsx("span", { className: "text-xs text-gray-400", children: "Ch\u01B0a c\u00F3 audio c\u00E2u h\u1ECFi" })] })), backgroundUrl && questionData.arrowNode && (_jsxs("div", { className: "relative w-full max-w-3xl mx-auto rounded-xl overflow-hidden shadow-sm border border-gray-200 bg-gray-50", children: [_jsx("img", { src: backgroundUrl, alt: "Background", className: "w-full object-contain pointer-events-none" }), _jsx("div", { className: "absolute", style: {
17
- left: `${questionData.arrowNode.x}%`,
18
- top: `${questionData.arrowNode.y}%`,
19
- width: `${questionData.arrowNode.width}%`,
20
- height: `${questionData.arrowNode.height}%`,
21
- }, children: _jsx("div", { className: "flex h-full w-full items-center justify-center overflow-hidden p-[6%]", children: _jsx(ArrowIndicatorSVG, { idSuffix: "-client", animated: true, className: "w-full h-full" }) }) })] })), _jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-dashed border-gray-300 bg-gray-50/80 px-4 py-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-50 ring-2 ring-indigo-100/50", children: _jsx(Mic, { className: "w-4 h-4 text-indigo-400" }) }), _jsx("p", { className: "text-xs text-gray-400", children: "H\u1ECDc sinh s\u1EBD nh\u1EA5n n\u00FAt ghi \u00E2m v\u00E0 n\u00F3i c\u00E2u tr\u1EA3 l\u1EDDi t\u1EA1i \u0111\u00E2y" })] }), isReviewMode && questionData.expectedAnswers && questionData.expectedAnswers.length > 0 && (_jsxs("div", { className: "rounded-xl border border-green-200 bg-green-50/80 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-green-600" }), _jsx("h4", { className: "text-sm font-semibold text-green-800", children: "C\u00E1c \u0111\u00E1p \u00E1n mong mu\u1ED1n" })] }), _jsx("div", { className: "space-y-2", children: questionData.expectedAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-center gap-2.5 rounded-lg bg-white/70 border border-green-100 px-3 py-2", children: [_jsx("span", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-green-100 text-green-700 text-xs font-bold", children: index + 1 }), _jsx("span", { className: "text-sm text-green-800", children: answer })] }, index))) })] })), isReviewMode && questionData.explanation && (_jsxs("div", { className: "rounded-xl border border-indigo-200 bg-indigo-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx(Info, { className: "h-4 w-4 text-indigo-600" }), _jsx("p", { className: "text-sm font-semibold text-indigo-800", children: "Gi\u1EA3i th\u00EDch" })] }), _jsx("p", { className: "text-sm text-indigo-700 leading-relaxed", children: questionData.explanation })] }))] })] })] }));
22
- }
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{Mic as i,Volume2 as d,CheckCircle2 as n,Play as t,Pause as a,Info as s}from"lucide-react";import{Button as l}from"../../../../components/ui/button";import{usePresignedFileUrl as o,useAudioPreview as c}from"../../../../shared/lib/hooks";import{ArrowIndicatorSVG as m}from"./ArrowIndicator";function g({audioUrl:i}){const{previewUrl:n}=o(i),{isPlaying:s,currentTime:m,duration:g,progressPercentage:h,handlePlayPause:x,handleProgressClick:u,formatTime:p}=c(n);return r("div",{className:"flex items-center gap-3 rounded-lg bg-gradient-to-r from-indigo-50 to-purple-50 border border-indigo-200 px-4 py-2.5",children:[e(l,{variant:"outline",size:"icon",onClick:x,disabled:!n,className:"h-8 w-8 border-indigo-300 text-indigo-600 hover:bg-indigo-100",children:e(s?a:t,{className:"h-3.5 w-3.5"})}),r("div",{className:"text-muted-foreground min-w-[70px] text-xs",children:[p(m)," / ",p(g)]}),e("div",{className:"bg-indigo-200 relative h-1.5 flex-1 cursor-pointer rounded-full",onClick:u,children:e("div",{className:"bg-indigo-500 absolute top-0 left-0 h-full rounded-full transition-all",style:{width:`${h}%`}})}),e(d,{className:"h-4 w-4 text-indigo-400"})]})}export function ListenAndSpeakAnswerClient({questionData:t,isReviewMode:a=!1}){const{previewUrl:l}=o(t.backgroundImageUrl||"");return r("div",{className:"space-y-4",children:[t.isBackup&&r("div",{className:"inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm",children:[e("span",{children:"🔄"})," Câu hỏi dự phòng"]}),r("div",{className:"overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm",children:[e("div",{className:"flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-indigo-50 to-blue-50 px-5 py-3",children:r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-indigo-500 to-blue-600 text-white shadow-sm",children:e(i,{className:"h-4 w-4"})}),e("span",{className:"text-sm font-medium text-gray-700",children:"Nghe và trả lời"})]})}),r("div",{className:"p-5 space-y-5",children:[t.audioUrl?e(g,{audioUrl:t.audioUrl}):r("div",{className:"flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3",children:[e(d,{className:"h-4 w-4 text-gray-400"}),e("span",{className:"text-xs text-gray-400",children:"Chưa có audio câu hỏi"})]}),l&&t.arrowNode&&r("div",{className:"relative w-full max-w-3xl mx-auto rounded-xl overflow-hidden shadow-sm border border-gray-200 bg-gray-50",children:[e("img",{src:l,alt:"Background",className:"w-full object-contain pointer-events-none"}),e("div",{className:"absolute",style:{left:`${t.arrowNode.x}%`,top:`${t.arrowNode.y}%`,width:`${t.arrowNode.width}%`,height:`${t.arrowNode.height}%`},children:e("div",{className:"flex h-full w-full items-center justify-center overflow-hidden p-[6%]",children:e(m,{idSuffix:"-client",animated:!0,className:"w-full h-full"})})})]}),r("div",{className:"flex items-center gap-3 rounded-lg border border-dashed border-gray-300 bg-gray-50/80 px-4 py-3",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-50 ring-2 ring-indigo-100/50",children:e(i,{className:"w-4 h-4 text-indigo-400"})}),e("p",{className:"text-xs text-gray-400",children:"Học sinh sẽ nhấn nút ghi âm và nói câu trả lời tại đây"})]}),a&&t.expectedAnswers&&t.expectedAnswers.length>0&&r("div",{className:"rounded-xl border border-green-200 bg-green-50/80 p-4",children:[r("div",{className:"flex items-center gap-2 mb-3",children:[e(n,{className:"h-4 w-4 text-green-600"}),e("h4",{className:"text-sm font-semibold text-green-800",children:"Các đáp án mong muốn"})]}),e("div",{className:"space-y-2",children:t.expectedAnswers.map((i,d)=>r("div",{className:"flex items-center gap-2.5 rounded-lg bg-white/70 border border-green-100 px-3 py-2",children:[e("span",{className:"flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-green-100 text-green-700 text-xs font-bold",children:d+1}),e("span",{className:"text-sm text-green-800",children:i})]},d))})]}),a&&t.explanation&&r("div",{className:"rounded-xl border border-indigo-200 bg-indigo-50 p-4",children:[r("div",{className:"flex items-center gap-2 mb-2",children:[e(s,{className:"h-4 w-4 text-indigo-600"}),e("p",{className:"text-sm font-semibold text-indigo-800",children:"Giải thích"})]}),e("p",{className:"text-sm text-indigo-700 leading-relaxed",children:t.explanation})]})]})]})]})}
@@ -1,291 +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 { PointsInput } from '../../../../components/ui/points-input';
9
- import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
10
- import { useDebouncedCallback, usePresignedFileUrl } from '../../../../shared/lib/hooks';
11
- import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
12
- import { Pencil, Eye, Plus, Trash2, Mic, Volume2, ImageIcon, Locate, MessageSquareText, Info, ListChecks, BookOpen } from 'lucide-react';
13
- import { FileUpload } from '../../../../components/ui/file-upload';
14
- import { useSceneQuestionGroup } from '../../_shared/hooks/useSceneQuestionGroup';
15
- import { ListenAndSpeakAnswerClient } from './ListenAndSpeakAnswerClient';
16
- import { ArrowIndicatorSVG } from './ArrowIndicator';
17
- import { listenAndSpeakAnswerFormSchema, } from '../../../../shared/lib/validation/listen-and-speak-answer.validation';
18
- import { CreatorGuide } from '../../components/CreatorGuide';
19
- function ArrowPreviewWithDrag({ backgroundUrl, arrowNode, onNodeUpdate, }) {
20
- const containerRef = useRef(null);
21
- const [isDragging, setIsDragging] = useState(false);
22
- const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
23
- const [isResizing, setIsResizing] = useState(false);
24
- const [resizeType, setResizeType] = useState(null);
25
- const [resizeStartPos, setResizeStartPos] = useState({ x: 0, y: 0 });
26
- const [resizeStartDim, setResizeStartDim] = useState({ width: 0, height: 0 });
27
- const handleMouseDown = (e) => {
28
- e.preventDefault();
29
- if (e.target.dataset.resizeHandle)
30
- return;
31
- setIsDragging(true);
32
- if (containerRef.current) {
33
- const rect = containerRef.current.getBoundingClientRect();
34
- const mouseXPercent = ((e.clientX - rect.left) / rect.width) * 100;
35
- const mouseYPercent = ((e.clientY - rect.top) / rect.height) * 100;
36
- setDragOffset({
37
- x: mouseXPercent - arrowNode.x,
38
- y: mouseYPercent - arrowNode.y,
39
- });
40
- }
41
- };
42
- const handleResizeMouseDown = (type, e) => {
43
- e.preventDefault();
44
- e.stopPropagation();
45
- setIsResizing(true);
46
- setResizeType(type);
47
- setResizeStartPos({ x: e.clientX, y: e.clientY });
48
- setResizeStartDim({ width: arrowNode.width, height: arrowNode.height });
49
- };
50
- const handleMouseMove = useCallback((e) => {
51
- if (!containerRef.current)
52
- return;
53
- if (isDragging) {
54
- const rect = containerRef.current.getBoundingClientRect();
55
- const mouseXPercent = ((e.clientX - rect.left) / rect.width) * 100;
56
- const mouseYPercent = ((e.clientY - rect.top) / rect.height) * 100;
57
- const x = Math.max(0, Math.min(100 - arrowNode.width, mouseXPercent - dragOffset.x));
58
- const y = Math.max(0, Math.min(100 - arrowNode.height, mouseYPercent - dragOffset.y));
59
- onNodeUpdate({ x: Math.round(x * 10) / 10, y: Math.round(y * 10) / 10 });
60
- }
61
- else if (isResizing && resizeType) {
62
- const rect = containerRef.current.getBoundingClientRect();
63
- const dx = ((e.clientX - resizeStartPos.x) / rect.width) * 100;
64
- const dy = ((e.clientY - resizeStartPos.y) / rect.height) * 100;
65
- let newWidth = resizeStartDim.width;
66
- let newHeight = resizeStartDim.height;
67
- if (resizeType === 'e' || resizeType === 'se') {
68
- newWidth = Math.max(1, Math.min(100 - arrowNode.x, resizeStartDim.width + dx));
69
- }
70
- if (resizeType === 's' || resizeType === 'se') {
71
- newHeight = Math.max(1, Math.min(100 - arrowNode.y, resizeStartDim.height + dy));
72
- }
73
- onNodeUpdate({ width: Math.round(newWidth * 10) / 10, height: Math.round(newHeight * 10) / 10 });
74
- }
75
- }, [isDragging, isResizing, resizeType, resizeStartPos, resizeStartDim, dragOffset, arrowNode, onNodeUpdate]);
76
- const handleMouseUp = useCallback(() => {
77
- setIsDragging(false);
78
- setIsResizing(false);
79
- setResizeType(null);
80
- }, []);
81
- useEffect(() => {
82
- if (isDragging || isResizing) {
83
- window.addEventListener('mousemove', handleMouseMove);
84
- window.addEventListener('mouseup', handleMouseUp);
85
- return () => {
86
- window.removeEventListener('mousemove', handleMouseMove);
87
- window.removeEventListener('mouseup', handleMouseUp);
88
- };
89
- }
90
- }, [isDragging, isResizing, handleMouseMove, handleMouseUp]);
91
- return (_jsxs("div", { ref: containerRef, className: "relative w-full select-none overflow-hidden rounded-md", children: [_jsx("img", { src: backgroundUrl, alt: "Scene background", className: "w-full rounded-md object-contain", draggable: false }), _jsxs("div", { className: `absolute cursor-move rounded-lg border-2 transition-all duration-150 group ${isDragging || isResizing ? 'border-indigo-400 shadow-md' : 'border-indigo-300/60 border-dashed hover:border-indigo-400 hover:border-solid hover:shadow-sm'}`, style: {
92
- left: `${arrowNode.x}%`,
93
- top: `${arrowNode.y}%`,
94
- width: `${arrowNode.width}%`,
95
- height: `${arrowNode.height}%`,
96
- }, onMouseDown: handleMouseDown, children: [_jsx("div", { className: "absolute -top-6 left-1/2 -translate-x-1/2 rounded-md bg-indigo-600/90 px-2 py-0.5 text-[10px] font-medium text-white whitespace-nowrap opacity-0 transition-opacity group-hover:opacity-100 shadow-sm backdrop-blur-sm", children: "M\u0169i t\u00EAn ch\u1EC9 th\u1ECB" }), _jsx("div", { className: "flex h-full w-full items-center justify-center overflow-hidden rounded-md p-[8%]", children: _jsx(ArrowIndicatorSVG, { idSuffix: "-creator", className: "w-full h-full" }) }), _jsx("div", { "data-resize-handle": "true", className: "absolute -right-1 top-1/2 h-2.5 w-2.5 -translate-y-1/2 cursor-e-resize rounded-full border border-indigo-400 bg-white shadow-sm opacity-0 transition-opacity group-hover:opacity-100 hover:opacity-100", onMouseDown: (e) => handleResizeMouseDown('e', e) }), _jsx("div", { "data-resize-handle": "true", className: "absolute -bottom-1 left-1/2 h-2.5 w-2.5 -translate-x-1/2 cursor-s-resize rounded-full border border-indigo-400 bg-white shadow-sm opacity-0 transition-opacity group-hover:opacity-100 hover:opacity-100", onMouseDown: (e) => handleResizeMouseDown('s', e) }), _jsx("div", { "data-resize-handle": "true", className: "absolute -bottom-1 -right-1 h-2.5 w-2.5 cursor-se-resize rounded-full border border-indigo-400 bg-white shadow-sm opacity-0 transition-opacity group-hover:opacity-100 hover:opacity-100", onMouseDown: (e) => handleResizeMouseDown('se', e) })] }), _jsxs("div", { className: "absolute bottom-2 right-2 rounded-md bg-black/50 px-2 py-1 text-[10px] font-mono text-white/80 backdrop-blur-sm", style: { zIndex: 1 }, children: ["x:", arrowNode.x, "% y:", arrowNode.y, "% w:", arrowNode.width, "% h:", arrowNode.height, "%"] })] }));
97
- }
98
- export function ListenAndSpeakAnswerCreator({ initialData, onSave, onCancel, onChange, externalErrors, validationRef, groups, questionIndexInPart = 0, partId = '', }) {
99
- const apiData = initialData;
100
- // Use group hook for scene background
101
- const sceneGroupInitialData = {
102
- backgroundImageUrl: apiData?.backgroundImageUrl || '',
103
- };
104
- const { currentGroupNumber, backgroundImageUrl, setBackgroundImageUrl, } = useSceneQuestionGroup({
105
- partId,
106
- groups,
107
- questionIndexInPart,
108
- initialData: sceneGroupInitialData,
109
- });
110
- const { previewUrl: backgroundPreviewUrl } = usePresignedFileUrl(backgroundImageUrl);
111
- const form = useReactHookForm({
112
- defaultValues: {
113
- audioUrl: apiData?.content?.audioUrl || apiData?.audioUrl || '',
114
- expectedAnswers: (apiData?.correctAnswer?.expectedAnswers || apiData?.expectedAnswers)?.length > 0
115
- ? (apiData?.correctAnswer?.expectedAnswers || apiData?.expectedAnswers)
116
- : [''],
117
- explanation: apiData?.explanation || '',
118
- points: apiData?.points ?? 1,
119
- backgroundImageUrl: backgroundImageUrl,
120
- arrowNode: apiData?.content?.arrowNode || apiData?.arrowNode || { x: 40, y: 30, width: 10, height: 20 },
121
- isBackup: apiData?.isBackup || false,
122
- },
123
- zodSchema: listenAndSpeakAnswerFormSchema,
124
- mode: 'onBlur',
125
- });
126
- const [isClientMode, setIsClientMode] = useState(false);
127
- const previousInitialDataRef = useRef(initialData);
128
- const isInitializingRef = useRef(false);
129
- const onChangeRef = useRef(onChange);
130
- const previousPayloadRef = useRef('');
131
- useEffect(() => {
132
- onChangeRef.current = onChange;
133
- }, [onChange]);
134
- const debouncedOnChange = useDebouncedCallback((data) => {
135
- onChangeRef.current?.(data);
136
- }, 300);
137
- const watchedAudioUrl = form.watch('audioUrl');
138
- const watchedExpectedAnswers = form.watch('expectedAnswers');
139
- const watchedExplanation = form.watch('explanation');
140
- const watchedPoints = form.watch('points');
141
- const watchedArrowNode = form.watch('arrowNode');
142
- const watchedIsBackup = form.watch('isBackup');
143
- // Presigned URL for audio playback (must be after form init so watchedAudioUrl is available)
144
- const { previewUrl: audioPreviewUrl } = usePresignedFileUrl(watchedAudioUrl || '');
145
- useEffect(() => {
146
- if (validationRef) {
147
- validationRef.current = {
148
- triggerValidation: async () => {
149
- return await form.trigger();
150
- },
151
- getFormData: () => ({
152
- audioUrl: watchedAudioUrl,
153
- expectedAnswers: watchedExpectedAnswers,
154
- explanation: watchedExplanation,
155
- points: watchedPoints,
156
- backgroundImageUrl,
157
- arrowNode: watchedArrowNode,
158
- isBackup: watchedIsBackup,
159
- groupNumber: currentGroupNumber,
160
- }),
161
- };
162
- }
163
- }, [validationRef, form, watchedAudioUrl, watchedExpectedAnswers, watchedExplanation, watchedPoints, watchedIsBackup, backgroundImageUrl, watchedArrowNode, currentGroupNumber]);
164
- useEffect(() => {
165
- const apiData = initialData;
166
- const normalizedInitialData = {
167
- audioUrl: apiData?.content?.audioUrl || apiData?.audioUrl || '',
168
- expectedAnswers: (apiData?.correctAnswer?.expectedAnswers || apiData?.expectedAnswers)?.length > 0
169
- ? (apiData?.correctAnswer?.expectedAnswers || apiData?.expectedAnswers)
170
- : [''],
171
- explanation: apiData?.explanation || '',
172
- points: apiData?.points ?? 1,
173
- isBackup: apiData?.isBackup || false,
174
- };
175
- const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
176
- if (!initialDataChanged) {
177
- return;
178
- }
179
- previousInitialDataRef.current = initialData;
180
- const currentFormData = {
181
- audioUrl: watchedAudioUrl,
182
- expectedAnswers: watchedExpectedAnswers,
183
- explanation: watchedExplanation,
184
- points: watchedPoints,
185
- isBackup: watchedIsBackup,
186
- };
187
- const isEchoFromOnChange = currentFormData.points === normalizedInitialData.points &&
188
- currentFormData.explanation === normalizedInitialData.explanation &&
189
- currentFormData.audioUrl === normalizedInitialData.audioUrl &&
190
- JSON.stringify(currentFormData.expectedAnswers) === JSON.stringify(normalizedInitialData.expectedAnswers);
191
- const wasJustSaved = initialData?.isCompleted === true;
192
- if (isEchoFromOnChange && wasJustSaved) {
193
- return;
194
- }
195
- const shouldReset = !isEchoFromOnChange &&
196
- (normalizedInitialData.audioUrl !== currentFormData.audioUrl ||
197
- JSON.stringify(normalizedInitialData.expectedAnswers) !== JSON.stringify(currentFormData.expectedAnswers));
198
- if (shouldReset) {
199
- isInitializingRef.current = true;
200
- form.reset(normalizedInitialData);
201
- setTimeout(() => {
202
- isInitializingRef.current = false;
203
- }, 150);
204
- }
205
- }, [initialData, form, watchedAudioUrl, watchedExpectedAnswers, watchedExplanation, watchedPoints, watchedIsBackup]);
206
- const watchedExpectedAnswersString = JSON.stringify(watchedExpectedAnswers);
207
- const watchedArrowNodeString = JSON.stringify(watchedArrowNode);
208
- useEffect(() => {
209
- if (isInitializingRef.current) {
210
- const currentPayload = JSON.stringify({
211
- audioUrl: watchedAudioUrl,
212
- expectedAnswers: watchedExpectedAnswers,
213
- explanation: watchedExplanation,
214
- points: watchedPoints,
215
- backgroundImageUrl,
216
- arrowNode: watchedArrowNode,
217
- isBackup: watchedIsBackup,
218
- groupNumber: currentGroupNumber,
219
- });
220
- previousPayloadRef.current = currentPayload;
221
- return;
222
- }
223
- const payload = {
224
- audioUrl: watchedAudioUrl,
225
- expectedAnswers: watchedExpectedAnswers,
226
- explanation: watchedExplanation,
227
- points: watchedPoints,
228
- backgroundImageUrl,
229
- arrowNode: watchedArrowNode,
230
- isBackup: watchedIsBackup,
231
- groupNumber: currentGroupNumber,
232
- };
233
- const payloadString = JSON.stringify(payload);
234
- if (previousPayloadRef.current !== payloadString) {
235
- previousPayloadRef.current = payloadString;
236
- debouncedOnChange(payload);
237
- }
238
- }, [
239
- watchedAudioUrl,
240
- watchedExpectedAnswersString,
241
- watchedExplanation,
242
- watchedPoints,
243
- watchedIsBackup,
244
- watchedArrowNodeString,
245
- backgroundImageUrl,
246
- currentGroupNumber,
247
- debouncedOnChange,
248
- ]);
249
- const handleSave = form.handleSubmit((data) => {
250
- onSave?.({
251
- ...data,
252
- backgroundImageUrl,
253
- groupNumber: currentGroupNumber,
254
- });
255
- });
256
- if (isClientMode) {
257
- const questionData = {
258
- audioUrl: watchedAudioUrl,
259
- expectedAnswers: watchedExpectedAnswers,
260
- explanation: watchedExplanation,
261
- points: watchedPoints,
262
- backgroundImageUrl,
263
- arrowNode: watchedArrowNode,
264
- };
265
- 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 tr\u1EA3 l\u1EDDi (Preview)" }), _jsx("p", { className: "mt-1 text-sm text-gray-500", children: "Preview mode" })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(false), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), "Quay l\u1EA1i Edit Mode"] })] }) }) }), _jsx(ListenAndSpeakAnswerClient, { questionData: questionData, isReviewMode: true })] }));
266
- }
267
- return (_jsxs("div", { className: "space-y-6", children: [_jsx(CreatorGuide, { colorScheme: "blue", steps: [
268
- { text: 'Upload hình nền chung cho nhóm — giám khảo sẽ chỉ vào hình và đặt câu hỏi' },
269
- { text: 'Đặt mũi tên chỉ vào vị trí trên hình nền — kéo để di chuyển, kéo góc để thay đổi kích thước' },
270
- { text: 'Upload audio câu hỏi cho từng câu' },
271
- { text: 'Nhập các đáp án mong muốn — có thể nhiều đáp án', highlight: true },
272
- { text: 'Thêm giải thích (không bắt buộc) cho đáp án đúng' },
273
- ], tips: ['Mũi tên giúp học sinh biết giám khảo đang hỏi về phần nào của hình'] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50", children: [_jsx("div", { className: "h-1.5 bg-gradient-to-r from-blue-500 via-indigo-500 to-purple-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-blue-500 to-indigo-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-blue-100 px-3 py-1 text-sm font-medium text-blue-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: !watchedAudioUrl || watchedExpectedAnswers.filter((a) => a.trim()).length === 0, className: "gap-2 border-blue-200 text-blue-600 hover:bg-blue-50 hover:text-blue-700", children: [_jsx(Eye, { className: "h-4 w-4" }), "Preview"] })] }) })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-blue-400 to-indigo-400" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-blue-100", children: _jsx(ImageIcon, { className: "h-3.5 w-3.5 text-blue-600" }) }), _jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "H\u00ECnh \u1EA3nh n\u1EC1n & M\u0169i t\u00EAn ch\u1EC9 th\u1ECB" })] }) }), _jsx(CardContent, { className: "space-y-6", children: _jsxs("div", { className: "rounded-xl border border-gray-200 bg-gray-50 p-4", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-blue-800", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-blue-600" }), "H\u00ECnh \u1EA3nh n\u1EC1n"] }), _jsxs("div", { className: "mt-2", children: [_jsx(FileUpload, { id: "scene-background", label: "", accept: "image/*", value: backgroundImageUrl, onChange: (url) => {
274
- setBackgroundImageUrl(url);
275
- form.setValue('backgroundImageUrl', url, { shouldValidate: true });
276
- }, maxSize: 10, placeholder: "Upload h\u00ECnh \u1EA3nh n\u1EC1n", autoUpload: true, prefix: "questions/scenes" }), form.hasError('backgroundImageUrl') && (_jsx("p", { className: "text-sm text-red-600 mt-1", children: form.getFieldError('backgroundImageUrl') }))] }), backgroundPreviewUrl && (_jsxs("div", { className: "mt-4 overflow-hidden rounded-lg border border-gray-200 bg-white", children: [_jsxs("div", { className: "border-b border-gray-100 px-3 py-2 bg-gray-50 flex items-center gap-2", children: [_jsx(Locate, { className: "h-4 w-4 text-blue-500" }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "K\u00E9o th\u1EA3 v\u00E0 \u0111i\u1EC1u ch\u1EC9nh k\u00EDch th\u01B0\u1EDBc m\u0169i t\u00EAn \u0111\u1EC3 ch\u1EC9 v\u00E0o \u0111\u1ED1i t\u01B0\u1EE3ng" })] }), _jsx("div", { className: "p-2", children: _jsx(ArrowPreviewWithDrag, { backgroundUrl: backgroundPreviewUrl, arrowNode: watchedArrowNode, onNodeUpdate: (updates) => {
277
- form.setValue('arrowNode', { ...watchedArrowNode, ...updates }, { shouldValidate: true });
278
- } }) })] }))] }) })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-indigo-100/50", children: [_jsx("div", { className: "h-1.5 bg-gradient-to-r from-indigo-500 to-blue-500" }), _jsx(CardHeader, { className: "pb-4", 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-indigo-500 to-blue-600 shadow-md", children: _jsx(Mic, { className: "h-5 w-5 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-xl font-bold text-gray-900", children: "Nghe v\u00E0 tr\u1EA3 l\u1EDDi" }), _jsx("p", { className: "text-sm text-gray-500", children: "H\u1ECDc sinh nghe \u00E2m thanh v\u00E0 thu \u00E2m c\u00E2u tr\u1EA3 l\u1EDDi" })] })] }) }), _jsxs(CardContent, { className: "space-y-6", children: [_jsx("div", { className: "rounded-xl border border-gray-200 bg-gray-50 p-4", children: _jsx("div", { className: "flex flex-col gap-4", children: _jsx("div", { className: "w-full md:w-1/2", children: _jsx(PointsInput, { ...form.register('points', { valueAsNumber: true }), error: form.hasError('points') ? form.getFieldError('points') : undefined }) }) }) }), _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(FileUpload, { id: "audioUrl", label: "", accept: "audio/*", value: watchedAudioUrl || '', onChange: (url) => {
279
- form.setValue('audioUrl', url, { shouldValidate: true });
280
- }, maxSize: 10, placeholder: "T\u1EA3i l\u00EAn file \u00E2m thanh (mp3, wav...)", autoUpload: true, prefix: "listen-and-speak" }), form.hasError('audioUrl') && (_jsx("p", { className: "text-sm text-red-600", children: form.getFieldError('audioUrl') })), audioPreviewUrl && (_jsx("audio", { controls: true, className: "mt-2 w-full", src: audioPreviewUrl, children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." }))] }), externalErrors && externalErrors.length > 0 && (_jsx("div", { className: "flex items-start gap-3 rounded-xl border border-red-200 bg-red-50 p-4", children: _jsxs("div", { className: "text-sm", children: [_jsx("p", { className: "font-semibold text-red-800", children: "C\u1EA7n ph\u1EA3i s\u1EEDa c\u00E1c l\u1ED7i sau:" }), _jsx("ul", { className: "mt-1 list-inside list-disc text-red-700", children: externalErrors.map((error, index) => (_jsx("li", { children: error }, index))) })] }) }))] })] }), _jsxs(Card, { className: "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-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-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-green-500 to-emerald-600 shadow-sm", children: _jsx(MessageSquareText, { className: "h-4 w-4 text-white" }) }), _jsx("div", { children: _jsxs(CardTitle, { className: "text-lg font-semibold text-gray-800", children: ["\u0110\u00E1p \u00E1n mong mu\u1ED1n (", watchedExpectedAnswers.filter((a) => a.trim()).length, ")"] }) })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => {
281
- const currentAnswers = form.getValues('expectedAnswers');
282
- form.setValue('expectedAnswers', [...currentAnswers, '']);
283
- }, className: "gap-2 border-green-200 text-green-600 hover:bg-green-50", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm \u0111\u00E1p \u00E1n"] })] }) }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-blue-100 bg-blue-50/60 px-3 py-2.5", children: [_jsx(Info, { className: "h-4 w-4 text-blue-500 mt-0.5 flex-shrink-0" }), _jsxs("div", { className: "text-xs text-blue-700 space-y-1", children: [_jsx("p", { children: "Nh\u1EADp c\u00E1c c\u00E2u tr\u1EA3 l\u1EDDi mong mu\u1ED1n. C\u00F3 th\u1EC3 th\u00EAm nhi\u1EC1u \u0111\u00E1p \u00E1n n\u1EBFu c\u00F3 nhi\u1EC1u c\u00E1ch tr\u1EA3 l\u1EDDi \u0111\u00FAng." }), _jsxs("p", { children: [_jsx("span", { className: "font-semibold", children: "\u2022 Ch\u1EA5m th\u1EE7 c\u00F4ng:" }), " Gi\u00E1o vi\u00EAn d\u00F9ng c\u00E1c \u0111\u00E1p \u00E1n n\u00E0y l\u00E0m tham kh\u1EA3o \u0111\u1EC3 ch\u1EA5m \u0111i\u1EC3m."] }), _jsxs("p", { children: [_jsx("span", { className: "font-semibold", children: "\u2022 Ch\u1EA5m t\u1EF1 \u0111\u1ED9ng (AI):" }), " H\u1EC7 th\u1ED1ng d\u00F9ng AI so s\u00E1nh gi\u1ECDng n\u00F3i c\u1EE7a h\u1ECDc sinh v\u1EDBi c\u00E1c \u0111\u00E1p \u00E1n \u0111\u1EC3 t\u1EF1 \u0111\u1ED9ng ch\u1EA5m \u0111i\u1EC3m."] })] })] }), _jsxs("div", { className: "space-y-3", children: [watchedExpectedAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-start gap-3 group/answer", children: [_jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-green-100 text-green-700 font-semibold shadow-sm text-sm", children: index + 1 }), _jsxs("div", { className: "flex-1", children: [_jsx(Input, { value: answer || '', onChange: (e) => {
284
- const newAnswers = [...watchedExpectedAnswers];
285
- newAnswers[index] = e.target.value;
286
- form.setValue('expectedAnswers', newAnswers, { shouldValidate: true });
287
- }, placeholder: `Ví dụ: I have a dog...`, className: "border-gray-200 focus:border-green-300 focus:ring-green-200" }), form.formState.errors?.expectedAnswers?.[index]?.message && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: form.formState.errors.expectedAnswers[index]?.message }))] }), watchedExpectedAnswers.length > 1 && (_jsx(Button, { variant: "ghost", size: "sm", onClick: () => {
288
- const newAnswers = watchedExpectedAnswers.filter((_, i) => i !== index);
289
- form.setValue('expectedAnswers', newAnswers, { shouldValidate: true });
290
- }, className: "text-red-400 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/answer:opacity-100 transition-opacity", children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }, index))), form.formState.errors?.expectedAnswers && !Array.isArray(form.formState.errors.expectedAnswers) && (_jsx("p", { className: "text-sm text-red-500", children: form.formState.errors.expectedAnswers.message }))] })] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-amber-400 to-orange-400" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm", children: _jsx(BookOpen, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch" }), _jsx("span", { className: "inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600", children: "Kh\u00F4ng b\u1EAFt bu\u1ED9c" })] }), _jsx("p", { className: "text-sm text-gray-500 mt-0.5", children: "Gi\u1EA3i th\u00EDch cho \u0111\u00E1p \u00E1n \u0111\u00FAng \u2014 hi\u1EC3n th\u1ECB sau khi h\u1ECDc sinh ho\u00E0n th\u00E0nh c\u00E2u h\u1ECFi" })] })] }) }), _jsx(CardContent, { children: _jsx(Textarea, { value: watchedExplanation, onChange: (e) => form.setValue('explanation', e.target.value, { shouldValidate: true }), 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" }) })] })] }));
291
- }
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useState as t,useRef as a,useEffect as i,useCallback as n}from"react";import{Button as s}from"../../../../components/ui/button";import{Label as o}from"../../../../components/ui/label";import{Input as l}from"../../../../components/ui/input";import{Textarea as d}from"../../../../components/ui/textarea";import{PointsInput as c}from"../../../../components/ui/points-input";import{Card as h,CardContent as m,CardHeader as u,CardTitle as g}from"../../../../components/ui/card";import{useDebouncedCallback as p,usePresignedFileUrl as x}from"../../../../shared/lib/hooks";import{useReactHookForm as b}from"../../../../shared/lib/hooks/useReactHookForm";import{Pencil as w,Eye as f,Plus as N,Trash2 as v,Mic as y,Volume2 as k,ImageIcon as A,Locate as U,MessageSquareText as S,Info as I,ListChecks as C,BookOpen as M}from"lucide-react";import{FileUpload as V}from"../../../../components/ui/file-upload";import{useSceneQuestionGroup as z}from"../../_shared/hooks/useSceneQuestionGroup";import{ListenAndSpeakAnswerClient as j}from"./ListenAndSpeakAnswerClient";import{ArrowIndicatorSVG as B}from"./ArrowIndicator";import{listenAndSpeakAnswerFormSchema as D}from"../../../../shared/lib/validation/listen-and-speak-answer.validation";import{CreatorGuide as E}from"../../components/CreatorGuide";function J({backgroundUrl:s,arrowNode:o,onNodeUpdate:l}){const d=a(null),[c,h]=t(!1),[m,u]=t({x:0,y:0}),[g,p]=t(!1),[x,b]=t(null),[w,f]=t({x:0,y:0}),[N,v]=t({width:0,height:0}),y=(e,r)=>{r.preventDefault(),r.stopPropagation(),p(!0),b(e),f({x:r.clientX,y:r.clientY}),v({width:o.width,height:o.height})},k=n(e=>{if(d.current)if(c){const r=d.current.getBoundingClientRect(),t=(e.clientX-r.left)/r.width*100,a=(e.clientY-r.top)/r.height*100,i=Math.max(0,Math.min(100-o.width,t-m.x)),n=Math.max(0,Math.min(100-o.height,a-m.y));l({x:Math.round(10*i)/10,y:Math.round(10*n)/10})}else if(g&&x){const r=d.current.getBoundingClientRect(),t=(e.clientX-w.x)/r.width*100,a=(e.clientY-w.y)/r.height*100;let i=N.width,n=N.height;"e"!==x&&"se"!==x||(i=Math.max(1,Math.min(100-o.x,N.width+t))),"s"!==x&&"se"!==x||(n=Math.max(1,Math.min(100-o.y,N.height+a))),l({width:Math.round(10*i)/10,height:Math.round(10*n)/10})}},[c,g,x,w,N,m,o,l]),A=n(()=>{h(!1),p(!1),b(null)},[]);return i(()=>{if(c||g)return window.addEventListener("mousemove",k),window.addEventListener("mouseup",A),()=>{window.removeEventListener("mousemove",k),window.removeEventListener("mouseup",A)}},[c,g,k,A]),r("div",{ref:d,className:"relative w-full select-none overflow-hidden rounded-md",children:[e("img",{src:s,alt:"Scene background",className:"w-full rounded-md object-contain",draggable:!1}),r("div",{className:"absolute cursor-move rounded-lg border-2 transition-all duration-150 group "+(c||g?"border-indigo-400 shadow-md":"border-indigo-300/60 border-dashed hover:border-indigo-400 hover:border-solid hover:shadow-sm"),style:{left:`${o.x}%`,top:`${o.y}%`,width:`${o.width}%`,height:`${o.height}%`},onMouseDown:e=>{if(e.preventDefault(),!e.target.dataset.resizeHandle&&(h(!0),d.current)){const r=d.current.getBoundingClientRect(),t=(e.clientX-r.left)/r.width*100,a=(e.clientY-r.top)/r.height*100;u({x:t-o.x,y:a-o.y})}},children:[e("div",{className:"absolute -top-6 left-1/2 -translate-x-1/2 rounded-md bg-indigo-600/90 px-2 py-0.5 text-[10px] font-medium text-white whitespace-nowrap opacity-0 transition-opacity group-hover:opacity-100 shadow-sm backdrop-blur-sm",children:"Mũi tên chỉ thị"}),e("div",{className:"flex h-full w-full items-center justify-center overflow-hidden rounded-md p-[8%]",children:e(B,{idSuffix:"-creator",className:"w-full h-full"})}),e("div",{"data-resize-handle":"true",className:"absolute -right-1 top-1/2 h-2.5 w-2.5 -translate-y-1/2 cursor-e-resize rounded-full border border-indigo-400 bg-white shadow-sm opacity-0 transition-opacity group-hover:opacity-100 hover:opacity-100",onMouseDown:e=>y("e",e)}),e("div",{"data-resize-handle":"true",className:"absolute -bottom-1 left-1/2 h-2.5 w-2.5 -translate-x-1/2 cursor-s-resize rounded-full border border-indigo-400 bg-white shadow-sm opacity-0 transition-opacity group-hover:opacity-100 hover:opacity-100",onMouseDown:e=>y("s",e)}),e("div",{"data-resize-handle":"true",className:"absolute -bottom-1 -right-1 h-2.5 w-2.5 cursor-se-resize rounded-full border border-indigo-400 bg-white shadow-sm opacity-0 transition-opacity group-hover:opacity-100 hover:opacity-100",onMouseDown:e=>y("se",e)})]}),r("div",{className:"absolute bottom-2 right-2 rounded-md bg-black/50 px-2 py-1 text-[10px] font-mono text-white/80 backdrop-blur-sm",style:{zIndex:1},children:["x:",o.x,"% y:",o.y,"% w:",o.width,"% h:",o.height,"%"]})]})}export function ListenAndSpeakAnswerCreator({initialData:n,onSave:B,onCancel:O,onChange:G,externalErrors:H,validationRef:L,groups:P,questionIndexInPart:R=0,partId:F=""}){const T=n,q={backgroundImageUrl:T?.backgroundImageUrl||""},{currentGroupNumber:X,backgroundImageUrl:Y,setBackgroundImageUrl:$}=z({partId:F,groups:P,questionIndexInPart:R,initialData:q}),{previewUrl:K}=x(Y),Q=b({defaultValues:{audioUrl:T?.content?.audioUrl||T?.audioUrl||"",expectedAnswers:(T?.correctAnswer?.expectedAnswers||T?.expectedAnswers)?.length>0?T?.correctAnswer?.expectedAnswers||T?.expectedAnswers:[""],explanation:T?.explanation||"",points:T?.points??1,backgroundImageUrl:Y,arrowNode:T?.content?.arrowNode||T?.arrowNode||{x:40,y:30,width:10,height:20},isBackup:T?.isBackup||!1},zodSchema:D,mode:"onBlur"}),[_,W]=t(!1),Z=a(n),ee=a(!1),re=a(G),te=a("");i(()=>{re.current=G},[G]);const ae=p(e=>{re.current?.(e)},300),ie=Q.watch("audioUrl"),ne=Q.watch("expectedAnswers"),se=Q.watch("explanation"),oe=Q.watch("points"),le=Q.watch("arrowNode"),de=Q.watch("isBackup"),{previewUrl:ce}=x(ie||"");i(()=>{L&&(L.current={triggerValidation:async()=>await Q.trigger(),getFormData:()=>({audioUrl:ie,expectedAnswers:ne,explanation:se,points:oe,backgroundImageUrl:Y,arrowNode:le,isBackup:de,groupNumber:X})})},[L,Q,ie,ne,se,oe,de,Y,le,X]),i(()=>{const e=n,r={audioUrl:e?.content?.audioUrl||e?.audioUrl||"",expectedAnswers:(e?.correctAnswer?.expectedAnswers||e?.expectedAnswers)?.length>0?e?.correctAnswer?.expectedAnswers||e?.expectedAnswers:[""],explanation:e?.explanation||"",points:e?.points??1,isBackup:e?.isBackup||!1};if(!(JSON.stringify(Z.current)!==JSON.stringify(n)))return;Z.current=n;const t={audioUrl:ie,expectedAnswers:ne,explanation:se,points:oe,isBackup:de},a=t.points===r.points&&t.explanation===r.explanation&&t.audioUrl===r.audioUrl&&JSON.stringify(t.expectedAnswers)===JSON.stringify(r.expectedAnswers);if(a&&!0===n?.isCompleted)return;!a&&(r.audioUrl!==t.audioUrl||JSON.stringify(r.expectedAnswers)!==JSON.stringify(t.expectedAnswers))&&(ee.current=!0,Q.reset(r),setTimeout(()=>{ee.current=!1},150))},[n,Q,ie,ne,se,oe,de]);const he=JSON.stringify(ne),me=JSON.stringify(le);i(()=>{if(ee.current){const e=JSON.stringify({audioUrl:ie,expectedAnswers:ne,explanation:se,points:oe,backgroundImageUrl:Y,arrowNode:le,isBackup:de,groupNumber:X});return void(te.current=e)}const e={audioUrl:ie,expectedAnswers:ne,explanation:se,points:oe,backgroundImageUrl:Y,arrowNode:le,isBackup:de,groupNumber:X},r=JSON.stringify(e);te.current!==r&&(te.current=r,ae(e))},[ie,he,se,oe,de,me,Y,X,ae]);Q.handleSubmit(e=>{B?.({...e,backgroundImageUrl:Y,groupNumber:X})});if(_){const t={audioUrl:ie,expectedAnswers:ne,explanation:se,points:oe,backgroundImageUrl:Y,arrowNode:le};return r("div",{className:"space-y-6",children:[e(h,{children:e(u,{children:r("div",{className:"flex items-center justify-between",children:[r("div",{children:[e(g,{children:"Nghe và trả lời (Preview)"}),e("p",{className:"mt-1 text-sm text-gray-500",children:"Preview mode"})]}),r(s,{variant:"outline",size:"sm",onClick:()=>W(!1),children:[e(w,{className:"mr-2 h-4 w-4"}),"Quay lại Edit Mode"]})]})})}),e(j,{questionData:t,isReviewMode:!0})]})}return r("div",{className:"space-y-6",children:[e(E,{colorScheme:"blue",steps:[{text:"Upload hình nền chung cho nhóm — giám khảo sẽ chỉ vào hình và đặt câu hỏi"},{text:"Đặt mũi tên chỉ vào vị trí trên hình nền — kéo để di chuyển, kéo góc để thay đổi kích thước"},{text:"Upload audio câu hỏi cho từng câu"},{text:"Nhập các đáp án mong muốn — có thể nhiều đáp án",highlight:!0},{text:"Thêm giải thích (không bắt buộc) cho đáp án đúng"}],tips:["Mũi tên giúp học sinh biết giám khảo đang hỏi về phần nào của hình"]}),r(h,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50",children:[e("div",{className:"h-1.5 bg-gradient-to-r from-blue-500 via-indigo-500 to-purple-500"}),e(u,{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-blue-500 to-indigo-600 shadow-md",children:e(C,{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-blue-100 px-3 py-1 text-sm font-medium text-blue-700",children:[e("span",{children:"🖼️"}),r("span",{children:["Nhóm ",X]}),e("span",{className:"text-xs font-normal opacity-75 ml-1",children:"(Dùng chung)"})]})]})]}),r(s,{variant:"outline",size:"sm",onClick:()=>W(!0),disabled:!ie||0===ne.filter(e=>e.trim()).length,className:"gap-2 border-blue-200 text-blue-600 hover:bg-blue-50 hover:text-blue-700",children:[e(f,{className:"h-4 w-4"}),"Preview"]})]})})]}),r(h,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-blue-400 to-indigo-400"}),e(u,{className:"pb-4",children:r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-blue-100",children:e(A,{className:"h-3.5 w-3.5 text-blue-600"})}),e(g,{className:"text-lg font-semibold text-gray-800",children:"Hình ảnh nền & Mũi tên chỉ thị"})]})}),e(m,{className:"space-y-6",children:r("div",{className:"rounded-xl border border-gray-200 bg-gray-50 p-4",children:[r(o,{className:"flex items-center gap-2 text-base font-semibold text-blue-800",children:[e(A,{className:"h-4 w-4 text-blue-600"}),"Hình ảnh nền"]}),r("div",{className:"mt-2",children:[e(V,{id:"scene-background",label:"",accept:"image/*",value:Y,onChange:e=>{$(e),Q.setValue("backgroundImageUrl",e,{shouldValidate:!0})},maxSize:10,placeholder:"Upload hình ảnh nền",autoUpload:!0,prefix:"questions/scenes"}),Q.hasError("backgroundImageUrl")&&e("p",{className:"text-sm text-red-600 mt-1",children:Q.getFieldError("backgroundImageUrl")})]}),K&&r("div",{className:"mt-4 overflow-hidden rounded-lg border border-gray-200 bg-white",children:[r("div",{className:"border-b border-gray-100 px-3 py-2 bg-gray-50 flex items-center gap-2",children:[e(U,{className:"h-4 w-4 text-blue-500"}),e("span",{className:"text-sm font-medium text-gray-700",children:"Kéo thả và điều chỉnh kích thước mũi tên để chỉ vào đối tượng"})]}),e("div",{className:"p-2",children:e(J,{backgroundUrl:K,arrowNode:le,onNodeUpdate:e=>{Q.setValue("arrowNode",{...le,...e},{shouldValidate:!0})}})})]})]})})]}),r(h,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-indigo-100/50",children:[e("div",{className:"h-1.5 bg-gradient-to-r from-indigo-500 to-blue-500"}),e(u,{className:"pb-4",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-indigo-500 to-blue-600 shadow-md",children:e(y,{className:"h-5 w-5 text-white"})}),r("div",{children:[e(g,{className:"text-xl font-bold text-gray-900",children:"Nghe và trả lời"}),e("p",{className:"text-sm text-gray-500",children:"Học sinh nghe âm thanh và thu âm câu trả lời"})]})]})}),r(m,{className:"space-y-6",children:[e("div",{className:"rounded-xl border border-gray-200 bg-gray-50 p-4",children:e("div",{className:"flex flex-col gap-4",children:e("div",{className:"w-full md:w-1/2",children:e(c,{...Q.register("points",{valueAsNumber:!0}),error:Q.hasError("points")?Q.getFieldError("points"):void 0})})})}),r("div",{className:"space-y-2",children:[r(o,{className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(k,{className:"h-4 w-4 text-indigo-500"}),"Audio câu hỏi"]}),e(V,{id:"audioUrl",label:"",accept:"audio/*",value:ie||"",onChange:e=>{Q.setValue("audioUrl",e,{shouldValidate:!0})},maxSize:10,placeholder:"Tải lên file âm thanh (mp3, wav...)",autoUpload:!0,prefix:"listen-and-speak"}),Q.hasError("audioUrl")&&e("p",{className:"text-sm text-red-600",children:Q.getFieldError("audioUrl")}),ce&&e("audio",{controls:!0,className:"mt-2 w-full",src:ce,children:"Trình duyệt không hỗ trợ audio."})]}),H&&H.length>0&&e("div",{className:"flex items-start gap-3 rounded-xl border border-red-200 bg-red-50 p-4",children:r("div",{className:"text-sm",children:[e("p",{className:"font-semibold text-red-800",children:"Cần phải sửa các lỗi sau:"}),e("ul",{className:"mt-1 list-inside list-disc text-red-700",children:H.map((r,t)=>e("li",{children:r},t))})]})})]})]}),r(h,{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-500"}),e(u,{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-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-green-500 to-emerald-600 shadow-sm",children:e(S,{className:"h-4 w-4 text-white"})}),e("div",{children:r(g,{className:"text-lg font-semibold text-gray-800",children:["Đáp án mong muốn (",ne.filter(e=>e.trim()).length,")"]})})]}),r(s,{variant:"outline",size:"sm",onClick:()=>{const e=Q.getValues("expectedAnswers");Q.setValue("expectedAnswers",[...e,""])},className:"gap-2 border-green-200 text-green-600 hover:bg-green-50",children:[e(N,{className:"h-4 w-4"}),"Thêm đáp án"]})]})}),r(m,{className:"space-y-4",children:[r("div",{className:"flex items-start gap-2 rounded-lg border border-blue-100 bg-blue-50/60 px-3 py-2.5",children:[e(I,{className:"h-4 w-4 text-blue-500 mt-0.5 flex-shrink-0"}),r("div",{className:"text-xs text-blue-700 space-y-1",children:[e("p",{children:"Nhập các câu trả lời mong muốn. Có thể thêm nhiều đáp án nếu có nhiều cách trả lời đúng."}),r("p",{children:[e("span",{className:"font-semibold",children:"• Chấm thủ công:"})," Giáo viên dùng các đáp án này làm tham khảo để chấm điểm."]}),r("p",{children:[e("span",{className:"font-semibold",children:"• Chấm tự động (AI):"})," Hệ thống dùng AI so sánh giọng nói của học sinh với các đáp án để tự động chấm điểm."]})]})]}),r("div",{className:"space-y-3",children:[ne.map((t,a)=>r("div",{className:"flex items-start gap-3 group/answer",children:[e("div",{className:"flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-green-100 text-green-700 font-semibold shadow-sm text-sm",children:a+1}),r("div",{className:"flex-1",children:[e(l,{value:t||"",onChange:e=>{const r=[...ne];r[a]=e.target.value,Q.setValue("expectedAnswers",r,{shouldValidate:!0})},placeholder:"Ví dụ: I have a dog...",className:"border-gray-200 focus:border-green-300 focus:ring-green-200"}),Q.formState.errors?.expectedAnswers?.[a]?.message&&e("p",{className:"mt-1 text-sm text-red-500",children:Q.formState.errors.expectedAnswers[a]?.message})]}),ne.length>1&&e(s,{variant:"ghost",size:"sm",onClick:()=>{const e=ne.filter((e,r)=>r!==a);Q.setValue("expectedAnswers",e,{shouldValidate:!0})},className:"text-red-400 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/answer:opacity-100 transition-opacity",children:e(v,{className:"h-4 w-4"})})]},a)),Q.formState.errors?.expectedAnswers&&!Array.isArray(Q.formState.errors.expectedAnswers)&&e("p",{className:"text-sm text-red-500",children:Q.formState.errors.expectedAnswers.message})]})]})]}),r(h,{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(u,{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(M,{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:se,onChange:e=>Q.setValue("explanation",e.target.value,{shouldValidate:!0}),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"})})]})]})}
@@ -1,2 +1 @@
1
- export * from './ListenAndSpeakAnswerCreator';
2
- export * from './ListenAndSpeakAnswerClient';
1
+ export*from"./ListenAndSpeakAnswerCreator";export*from"./ListenAndSpeakAnswerClient";