@tinyweb_dev/oe-exam-sdk 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (485) hide show
  1. package/dist/api/exam-create.js +1 -179
  2. package/dist/api/exam-create.types.js +1 -1
  3. package/dist/api/exam-questions.js +1 -98
  4. package/dist/api/exam-questions.types.js +1 -1
  5. package/dist/api/exam-taking.js +1 -73
  6. package/dist/api/exam-taking.types.js +1 -1
  7. package/dist/api/index.js +1 -6
  8. package/dist/api/result-review.js +1 -50
  9. package/dist/api/result-review.types.js +1 -1
  10. package/dist/components/CueCardEditor.js +1 -58
  11. package/dist/components/common/ResolvedImage.js +1 -15
  12. package/dist/components/exams/CreateExamInfoForm.js +1 -35
  13. package/dist/components/exams/CreateExamPageContainer.js +1 -103
  14. package/dist/components/exams/ExamCreator.js +1 -89
  15. package/dist/components/exams/ExamPartTabs.js +1 -40
  16. package/dist/components/exams/ExamQuestionGrid.js +1 -18
  17. package/dist/components/exams/ExamQuestionsPageContainer.js +1 -90
  18. package/dist/components/exams/PartAudioUploader.js +1 -9
  19. package/dist/components/exams/TemplateSelector.js +1 -66
  20. package/dist/components/exams/api.js +1 -3
  21. package/dist/components/exams/create-exam.validation.js +1 -18
  22. package/dist/components/exams/exam-template.utils.js +1 -18
  23. package/dist/components/exams/index.js +1 -10
  24. package/dist/components/exams/take/ExamTakingPageContainer.js +1 -153
  25. package/dist/components/exams/take/GenericQuestionRenderer.js +1 -14
  26. package/dist/components/exams/take/exam-taking.utils.js +1 -88
  27. package/dist/components/exams/take/hooks/useSpeakingRecorder.js +1 -24
  28. package/dist/components/exams/take/index.js +1 -2
  29. package/dist/components/exams/take/types.js +1 -1
  30. package/dist/components/index.js +1 -42
  31. package/dist/components/questions/_shared/config/question-types.config.js +1 -333
  32. package/dist/components/questions/_shared/constants/color-palette.js +1 -21
  33. package/dist/components/questions/_shared/hooks/useAnswerGroupQuestionGroup.js +1 -105
  34. package/dist/components/questions/_shared/hooks/useBasicQuestionGroup.js +1 -222
  35. package/dist/components/questions/_shared/hooks/useCompareImagesGroup.js +1 -140
  36. package/dist/components/questions/_shared/hooks/useImageQuestionGroup.js +1 -123
  37. package/dist/components/questions/_shared/hooks/useInfoExchangeGroup.js +1 -50
  38. package/dist/components/questions/_shared/hooks/useQuestionFormState.js +1 -54
  39. package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +1 -123
  40. package/dist/components/questions/_shared/hooks/useQuestionListGroup.js +1 -121
  41. package/dist/components/questions/_shared/hooks/useQuestionOnChange.js +1 -70
  42. package/dist/components/questions/_shared/hooks/useSceneQuestionGroup.js +1 -194
  43. package/dist/components/questions/_shared/index.js +1 -7
  44. package/dist/components/questions/_shared/types/answer-the-question.type.js +1 -1
  45. package/dist/components/questions/_shared/types/arrange-letters-into-words.type.js +1 -1
  46. package/dist/components/questions/_shared/types/choose-correct-adjective.type.js +1 -3
  47. package/dist/components/questions/_shared/types/color-region.config.js +1 -1
  48. package/dist/components/questions/_shared/types/fill-in-blank.type.js +1 -1
  49. package/dist/components/questions/_shared/types/fill-missing-words-in-grid.type.js +1 -1
  50. package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +1 -1
  51. package/dist/components/questions/_shared/types/index.js +1 -12
  52. package/dist/components/questions/_shared/types/label-the-picture.type.js +1 -1
  53. package/dist/components/questions/_shared/types/listen-and-choose-from-answer-group.type.js +1 -1
  54. package/dist/components/questions/_shared/types/listen-and-choose-objects-in-scene.type.js +1 -1
  55. package/dist/components/questions/_shared/types/listen-and-drag-objects-into-scene.type.js +1 -1
  56. package/dist/components/questions/_shared/types/listen-and-speak-answer.type.js +1 -1
  57. package/dist/components/questions/_shared/types/listen-and-speak-compare-images.type.js +1 -1
  58. package/dist/components/questions/_shared/types/listen-and-speak-image-group.type.js +1 -1
  59. package/dist/components/questions/_shared/types/listen-and-speak-info-exchange.type.js +1 -1
  60. package/dist/components/questions/_shared/types/listen-and-speak-odd-one-out.type.js +1 -3
  61. package/dist/components/questions/_shared/types/listen-and-speak-question-list.type.js +1 -1
  62. package/dist/components/questions/_shared/types/listen-and-speak-with-story-images.type.js +1 -3
  63. package/dist/components/questions/_shared/types/listen-and-tick-answer.type.js +1 -1
  64. package/dist/components/questions/_shared/types/listening-drawline.type.js +1 -1
  65. package/dist/components/questions/_shared/types/look-and-choose-answer.type.js +1 -1
  66. package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +1 -1
  67. package/dist/components/questions/_shared/types/match-by-writing-answer.type.js +1 -1
  68. package/dist/components/questions/_shared/types/multiple-choice.type.js +1 -1
  69. package/dist/components/questions/_shared/types/question-group.type.js +1 -11
  70. package/dist/components/questions/_shared/types/read-and-color-objects.type.js +1 -1
  71. package/dist/components/questions/_shared/types/read-displayed-content.type.js +1 -1
  72. package/dist/components/questions/_shared/types/read-passage-and-answer-questions.type.js +1 -1
  73. package/dist/components/questions/_shared/types/speaking-describe-image.type.js +1 -1
  74. package/dist/components/questions/_shared/types/spontaneous-qa.type.js +1 -1
  75. package/dist/components/questions/_shared/types/word-fill-paragraph-with-options.type.js +1 -1
  76. package/dist/components/questions/_shared/types/word-fill-paragraph.type.js +1 -1
  77. package/dist/components/questions/_shared/types/word-fill-structured-form.type.js +1 -1
  78. package/dist/components/questions/_shared/types/word-ordering.type.js +1 -1
  79. package/dist/components/questions/_shared/types/write-a-short-letter.type.js +1 -1
  80. package/dist/components/questions/_shared/types/write-correct-verb-form.type.js +1 -3
  81. package/dist/components/questions/_shared/types/write-sentences.type.js +1 -1
  82. package/dist/components/questions/_shared/types/write-short-paragraph.type.js +1 -1
  83. package/dist/components/questions/_shared/utils/answer-builder.js +1 -79
  84. package/dist/components/questions/_shared/utils/createColorRegionRegistration.js +1 -39
  85. package/dist/components/questions/_shared/utils/id-generator.js +1 -3
  86. package/dist/components/questions/_shared/utils/question-validation.js +1 -21
  87. package/dist/components/questions/components/ColorNode.js +1 -85
  88. package/dist/components/questions/components/ColorRegionClient.js +1 -545
  89. package/dist/components/questions/components/ColorRegionCreator.js +1 -420
  90. package/dist/components/questions/components/ColorRegionNode.js +1 -59
  91. package/dist/components/questions/components/CreatorGuide.js +1 -103
  92. package/dist/components/questions/components/NameNode.js +1 -26
  93. package/dist/components/questions/components/QuestionBankOption.js +1 -46
  94. package/dist/components/questions/components/QuestionBasicInfoForm.js +1 -9
  95. package/dist/components/questions/components/QuestionCommonFields.js +1 -33
  96. package/dist/components/questions/components/QuestionSearchDropdown.js +1 -258
  97. package/dist/components/questions/components/QuestionTypeSelector.js +1 -32
  98. package/dist/components/questions/components/QuestionTypeSpecificForms.js +1 -6
  99. package/dist/components/questions/components/RegionNode.js +1 -94
  100. package/dist/components/questions/components/index.js +1 -14
  101. package/dist/components/questions/creator/QuestionCreator.js +1 -90
  102. package/dist/components/questions/creator/QuestionGroupCreator.js +1 -119
  103. package/dist/components/questions/creator/dedicated-component-router.js +1 -79
  104. package/dist/components/questions/creator/index.js +1 -3
  105. package/dist/components/questions/creator/question-type-registry.js +1 -80
  106. package/dist/components/questions/groups/ArticlesGroupCard.js +1 -121
  107. package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -50
  108. package/dist/components/questions/groups/DocumentGroupCard.js +1 -114
  109. package/dist/components/questions/groups/MatchAnswerGroupCard.js +1 -25
  110. package/dist/components/questions/index.js +1 -10
  111. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +1 -37
  112. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +1 -351
  113. package/dist/components/questions/types/answer-the-question/register.js +1 -67
  114. package/dist/components/questions/types/answer-the-question/transform.js +1 -36
  115. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +1 -141
  116. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +1 -118
  117. package/dist/components/questions/types/arrange-letters-into-words/index.js +1 -2
  118. package/dist/components/questions/types/arrange-letters-into-words/register.js +1 -4
  119. package/dist/components/questions/types/arrange-letters-into-words/transform.js +1 -24
  120. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -64
  121. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +1 -169
  122. package/dist/components/questions/types/choose-correct-adjective/index.js +1 -2
  123. package/dist/components/questions/types/choose-correct-adjective/register.js +1 -55
  124. package/dist/components/questions/types/choose-correct-adjective/transform.js +1 -16
  125. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +1 -131
  126. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +1 -581
  127. package/dist/components/questions/types/choose-the-correct-answer/index.js +1 -2
  128. package/dist/components/questions/types/choose-the-correct-answer/register.js +1 -101
  129. package/dist/components/questions/types/choose-the-correct-answer/transform.js +1 -43
  130. package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +1 -275
  131. package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +1 -448
  132. package/dist/components/questions/types/fill-in-blank/index.js +1 -2
  133. package/dist/components/questions/types/fill-in-blank/register.js +1 -138
  134. package/dist/components/questions/types/fill-in-blank/transform.js +1 -59
  135. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +1 -117
  136. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +1 -671
  137. package/dist/components/questions/types/fill-missing-words-in-grid/index.js +1 -2
  138. package/dist/components/questions/types/fill-missing-words-in-grid/register.js +1 -37
  139. package/dist/components/questions/types/fill-missing-words-in-grid/transform.js +1 -15
  140. package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +1 -262
  141. package/dist/components/questions/types/gn-speaking-interview/register.js +1 -121
  142. package/dist/components/questions/types/gn-speaking-interview/transform.js +1 -48
  143. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingClient.js +1 -16
  144. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingCreator.js +1 -27
  145. package/dist/components/questions/types/image-object-matching/index.js +1 -2
  146. package/dist/components/questions/types/image-object-matching/register.js +1 -3
  147. package/dist/components/questions/types/image-object-matching/transform.js +1 -2
  148. package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +1 -102
  149. package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +1 -344
  150. package/dist/components/questions/types/label-the-picture/index.js +1 -2
  151. package/dist/components/questions/types/label-the-picture/register.js +1 -54
  152. package/dist/components/questions/types/label-the-picture/transform.js +1 -28
  153. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +1 -78
  154. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +1 -232
  155. package/dist/components/questions/types/listen-and-choose-from-answer-group/index.js +1 -2
  156. package/dist/components/questions/types/listen-and-choose-from-answer-group/register.js +1 -82
  157. package/dist/components/questions/types/listen-and-choose-from-answer-group/transform.js +1 -27
  158. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +1 -70
  159. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +1 -386
  160. package/dist/components/questions/types/listen-and-choose-objects-in-scene/index.js +1 -2
  161. package/dist/components/questions/types/listen-and-choose-objects-in-scene/register.js +1 -100
  162. package/dist/components/questions/types/listen-and-choose-objects-in-scene/transform.js +1 -33
  163. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +1 -106
  164. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +1 -457
  165. package/dist/components/questions/types/listen-and-drag-objects-into-scene/index.js +1 -2
  166. package/dist/components/questions/types/listen-and-drag-objects-into-scene/register.js +1 -110
  167. package/dist/components/questions/types/listen-and-drag-objects-into-scene/transform.js +1 -35
  168. package/dist/components/questions/types/listen-and-speak-answer/ArrowIndicator.js +1 -8
  169. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -22
  170. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +1 -291
  171. package/dist/components/questions/types/listen-and-speak-answer/index.js +1 -2
  172. package/dist/components/questions/types/listen-and-speak-answer/register.js +1 -84
  173. package/dist/components/questions/types/listen-and-speak-answer/transform.js +1 -27
  174. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -32
  175. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +1 -295
  176. package/dist/components/questions/types/listen-and-speak-compare-images/register.js +1 -79
  177. package/dist/components/questions/types/listen-and-speak-compare-images/transform.js +1 -27
  178. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -34
  179. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +1 -285
  180. package/dist/components/questions/types/listen-and-speak-image-group/register.js +1 -81
  181. package/dist/components/questions/types/listen-and-speak-image-group/transform.js +1 -26
  182. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -38
  183. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +1 -385
  184. package/dist/components/questions/types/listen-and-speak-info-exchange/register.js +1 -87
  185. package/dist/components/questions/types/listen-and-speak-info-exchange/transform.js +1 -32
  186. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -26
  187. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +1 -247
  188. package/dist/components/questions/types/listen-and-speak-odd-one-out/register.js +1 -59
  189. package/dist/components/questions/types/listen-and-speak-odd-one-out/transform.js +1 -18
  190. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -26
  191. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +1 -256
  192. package/dist/components/questions/types/listen-and-speak-question-list/register.js +1 -81
  193. package/dist/components/questions/types/listen-and-speak-question-list/transform.js +1 -26
  194. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -26
  195. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +1 -230
  196. package/dist/components/questions/types/listen-and-speak-with-story-images/register.js +1 -56
  197. package/dist/components/questions/types/listen-and-speak-with-story-images/transform.js +1 -16
  198. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +1 -72
  199. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +1 -230
  200. package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +1 -1
  201. package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +1 -66
  202. package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +1 -42
  203. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +1 -96
  204. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +1 -238
  205. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +1 -2
  206. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +1 -76
  207. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +1 -19
  208. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +1 -134
  209. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +1 -617
  210. package/dist/components/questions/types/look-picture-fill-word-hint/index.js +1 -2
  211. package/dist/components/questions/types/look-picture-fill-word-hint/register.js +1 -82
  212. package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +1 -29
  213. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +1 -38
  214. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +1 -359
  215. package/dist/components/questions/types/match-by-writing-answer/index.js +1 -3
  216. package/dist/components/questions/types/match-by-writing-answer/register.js +1 -58
  217. package/dist/components/questions/types/match-by-writing-answer/transform.js +1 -61
  218. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsClient.js +1 -16
  219. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsCreator.js +1 -23
  220. package/dist/components/questions/types/read-and-color-objects/index.js +1 -2
  221. package/dist/components/questions/types/read-and-color-objects/register.js +1 -3
  222. package/dist/components/questions/types/read-and-color-objects/transform.js +1 -36
  223. package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +1 -125
  224. package/dist/components/questions/types/read-displayed-content/register.js +1 -64
  225. package/dist/components/questions/types/read-displayed-content/transform.js +1 -12
  226. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -64
  227. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +1 -511
  228. package/dist/components/questions/types/read-passage-and-answer-questions/index.js +1 -2
  229. package/dist/components/questions/types/read-passage-and-answer-questions/register.js +1 -58
  230. package/dist/components/questions/types/read-passage-and-answer-questions/transform.js +1 -28
  231. package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +1 -373
  232. package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +1 -41
  233. package/dist/components/questions/types/speaking-conversation/register.js +1 -110
  234. package/dist/components/questions/types/speaking-conversation/transform.js +1 -34
  235. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +1 -74
  236. package/dist/components/questions/types/speaking-cue-card/register.js +1 -52
  237. package/dist/components/questions/types/speaking-cue-card/transform.js +1 -12
  238. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -10
  239. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +1 -161
  240. package/dist/components/questions/types/speaking-describe-image/index.js +1 -2
  241. package/dist/components/questions/types/speaking-describe-image/register.js +1 -69
  242. package/dist/components/questions/types/speaking-describe-image/transform.js +1 -16
  243. package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +1 -234
  244. package/dist/components/questions/types/spontaneous-qa/register.js +1 -93
  245. package/dist/components/questions/types/spontaneous-qa/transform.js +1 -34
  246. package/dist/components/questions/types/true-false/TrueFalseClient.js +1 -75
  247. package/dist/components/questions/types/true-false/TrueFalseCreator.js +1 -244
  248. package/dist/components/questions/types/true-false/index.js +1 -2
  249. package/dist/components/questions/types/true-false/register.js +1 -77
  250. package/dist/components/questions/types/true-false/transform.js +1 -32
  251. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +1 -233
  252. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +1 -631
  253. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphWrapper.js +1 -28
  254. package/dist/components/questions/types/word-fill-paragraph/index.js +1 -2
  255. package/dist/components/questions/types/word-fill-paragraph/register.js +1 -136
  256. package/dist/components/questions/types/word-fill-paragraph/transform.js +1 -56
  257. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -138
  258. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +1 -261
  259. package/dist/components/questions/types/word-fill-structured-form/index.js +1 -2
  260. package/dist/components/questions/types/word-fill-structured-form/register.js +1 -67
  261. package/dist/components/questions/types/word-fill-structured-form/transform.js +1 -40
  262. package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -84
  263. package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +1 -192
  264. package/dist/components/questions/types/word-ordering/index.js +1 -2
  265. package/dist/components/questions/types/word-ordering/register.js +1 -46
  266. package/dist/components/questions/types/word-ordering/transform.js +1 -24
  267. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +1 -98
  268. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +1 -320
  269. package/dist/components/questions/types/write-a-short-letter/index.js +1 -2
  270. package/dist/components/questions/types/write-a-short-letter/register.js +1 -68
  271. package/dist/components/questions/types/write-a-short-letter/transform.js +1 -25
  272. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +1 -61
  273. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +1 -196
  274. package/dist/components/questions/types/write-correct-verb-form/index.js +1 -2
  275. package/dist/components/questions/types/write-correct-verb-form/register.js +1 -62
  276. package/dist/components/questions/types/write-correct-verb-form/transform.js +1 -17
  277. package/dist/components/questions/types/write-sentences/WriteSentencesClient.js +1 -22
  278. package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +1 -174
  279. package/dist/components/questions/types/write-sentences/index.js +1 -2
  280. package/dist/components/questions/types/write-sentences/register.js +1 -58
  281. package/dist/components/questions/types/write-sentences/transform.js +1 -28
  282. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -96
  283. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +1 -161
  284. package/dist/components/questions/types/write-short-paragraph/index.js +1 -2
  285. package/dist/components/questions/types/write-short-paragraph/register.js +1 -4
  286. package/dist/components/questions/types/write-short-paragraph/transform.js +1 -21
  287. package/dist/components/questions/viewer/QuestionGroupViewer.js +1 -36
  288. package/dist/components/questions/viewer/QuestionViewer.js +1 -446
  289. package/dist/components/questions/viewer/index.js +1 -2
  290. package/dist/components/results/ResultReviewPageContainer.js +1 -98
  291. package/dist/components/results/ReviewQuestionRenderer.js +1 -72
  292. package/dist/components/results/index.js +1 -2
  293. package/dist/components/results/result-review.utils.js +1 -6
  294. package/dist/components/results/review-data.js +1 -150
  295. package/dist/components/results/review-renderer.utils.js +1 -97
  296. package/dist/components/results/review-types.js +1 -1
  297. package/dist/components/themes/cambridge-yle/CambridgeYleAudioPlayer.js +1 -138
  298. package/dist/components/themes/cambridge-yle/CambridgeYleBottomNav.js +1 -12
  299. package/dist/components/themes/cambridge-yle/CambridgeYleExamLayout.js +1 -18
  300. package/dist/components/themes/cambridge-yle/CambridgeYleExampleText.js +1 -11
  301. package/dist/components/themes/cambridge-yle/CambridgeYleHeader.js +1 -11
  302. package/dist/components/themes/cambridge-yle/CambridgeYleInstructionBanner.js +1 -9
  303. package/dist/components/themes/cambridge-yle/CambridgeYleNavButton.js +1 -13
  304. package/dist/components/themes/cambridge-yle/CambridgeYlePartBanner.js +1 -8
  305. package/dist/components/themes/cambridge-yle/CambridgeYleProgressBar.js +1 -6
  306. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCard.js +1 -5
  307. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCardFullWidth.js +1 -9
  308. package/dist/components/themes/cambridge-yle/CambridgeYleSidebar.js +1 -41
  309. package/dist/components/themes/cambridge-yle/CambridgeYleTimer.js +1 -25
  310. package/dist/components/themes/cambridge-yle/index.js +1 -13
  311. package/dist/components/themes/english-certification/EcAnswerTheQuestion.js +1 -24
  312. package/dist/components/themes/english-certification/EcMatchByWritingAnswer.js +1 -88
  313. package/dist/components/themes/english-certification/EcWordFillParagraph.js +1 -55
  314. package/dist/components/themes/english-certification/EcWordFillStructuredForm.js +1 -156
  315. package/dist/components/themes/english-certification/EcWritingTask.js +1 -34
  316. package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.js +1 -114
  317. package/dist/components/themes/english-certification/EnglishCertificationBrand.js +1 -11
  318. package/dist/components/themes/english-certification/EnglishCertificationExamLayout.js +1 -21
  319. package/dist/components/themes/english-certification/EnglishCertificationFlagButton.js +1 -15
  320. package/dist/components/themes/english-certification/EnglishCertificationFooterBar.js +1 -19
  321. package/dist/components/themes/english-certification/EnglishCertificationGroupedQuestion.js +1 -148
  322. package/dist/components/themes/english-certification/EnglishCertificationHeader.js +1 -31
  323. package/dist/components/themes/english-certification/EnglishCertificationNavigationPanel.js +1 -71
  324. package/dist/components/themes/english-certification/EnglishCertificationOptionSelector.js +1 -33
  325. package/dist/components/themes/english-certification/EnglishCertificationPassagePanel.js +1 -16
  326. package/dist/components/themes/english-certification/EnglishCertificationQuestionCard.js +1 -22
  327. package/dist/components/themes/english-certification/EnglishCertificationQuestionItem.js +1 -37
  328. package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.js +1 -72
  329. package/dist/components/themes/english-certification/EnglishCertificationReadingSection.js +1 -90
  330. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestion.js +1 -18
  331. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestionList.js +1 -29
  332. package/dist/components/themes/english-certification/english-certification-theme.js +1 -58
  333. package/dist/components/themes/english-certification/grouped-question-utils.js +1 -218
  334. package/dist/components/themes/english-certification/index.js +1 -21
  335. package/dist/components/themes/index.js +1 -6
  336. package/dist/components/themes/summer-sky/SummerSkyAudioPlayer.js +1 -170
  337. package/dist/components/themes/summer-sky/SummerSkyLayout.js +1 -221
  338. package/dist/components/themes/summer-sky/SummerSkySubmitModal.js +1 -18
  339. package/dist/components/themes/summer-sky/SummerSkyWaitingRoom.js +1 -194
  340. package/dist/components/themes/types.js +1 -1
  341. package/dist/components/ui/alert-dialog.js +1 -26
  342. package/dist/components/ui/alert.js +1 -22
  343. package/dist/components/ui/avatar.js +1 -12
  344. package/dist/components/ui/badge.js +1 -38
  345. package/dist/components/ui/button.js +1 -35
  346. package/dist/components/ui/calendar.js +1 -76
  347. package/dist/components/ui/card.js +1 -16
  348. package/dist/components/ui/carousel.js +1 -91
  349. package/dist/components/ui/checkbox.js +1 -9
  350. package/dist/components/ui/collapsible.js +1 -13
  351. package/dist/components/ui/command.js +1 -34
  352. package/dist/components/ui/confirm-dialog.js +1 -49
  353. package/dist/components/ui/dialog.js +1 -23
  354. package/dist/components/ui/drawer.js +1 -35
  355. package/dist/components/ui/dropdown-menu.js +1 -33
  356. package/dist/components/ui/file-upload.js +1 -155
  357. package/dist/components/ui/form.js +1 -62
  358. package/dist/components/ui/hover-card.js +1 -10
  359. package/dist/components/ui/image-preview.js +1 -29
  360. package/dist/components/ui/input.js +1 -8
  361. package/dist/components/ui/label.js +1 -9
  362. package/dist/components/ui/language-switcher.js +1 -15
  363. package/dist/components/ui/multi-select.js +1 -90
  364. package/dist/components/ui/points-input.js +1 -33
  365. package/dist/components/ui/popover.js +1 -26
  366. package/dist/components/ui/progress.js +1 -16
  367. package/dist/components/ui/radio-group.js +1 -12
  368. package/dist/components/ui/scroll-area.js +1 -11
  369. package/dist/components/ui/select.js +1 -38
  370. package/dist/components/ui/separator.js +1 -8
  371. package/dist/components/ui/sheet.js +1 -40
  372. package/dist/components/ui/single-select.js +1 -76
  373. package/dist/components/ui/skeleton.js +1 -6
  374. package/dist/components/ui/switch.js +1 -8
  375. package/dist/components/ui/table.js +1 -20
  376. package/dist/components/ui/tabs.js +1 -17
  377. package/dist/components/ui/textarea.js +1 -8
  378. package/dist/components/ui/tooltip.js +1 -11
  379. package/dist/index.js +1 -19
  380. package/dist/mcp/config.js +1 -32
  381. package/dist/mcp/http-client.js +1 -48
  382. package/dist/mcp/index.js +1 -29
  383. package/dist/mcp/tools/_helpers.js +1 -25
  384. package/dist/mcp/tools/exams.js +1 -439
  385. package/dist/mcp/tools/index.js +1 -6
  386. package/dist/mcp/tools/results.js +1 -19
  387. package/dist/shared/constants/ApiConstant.js +1 -276
  388. package/dist/shared/constants/QueryKeyConstant.js +1 -112
  389. package/dist/shared/constants/exam-level.enum.js +1 -41
  390. package/dist/shared/constants/landings/placement-test.constants.js +1 -431
  391. package/dist/shared/constants/question-skills.js +1 -267
  392. package/dist/shared/constants/routes.js +1 -81
  393. package/dist/shared/index.js +1 -7
  394. package/dist/shared/lib/hooks/index.js +1 -10
  395. package/dist/shared/lib/hooks/useAlertDialog.js +1 -57
  396. package/dist/shared/lib/hooks/useAudioPlayCounter.js +1 -30
  397. package/dist/shared/lib/hooks/useAudioPreview.js +1 -81
  398. package/dist/shared/lib/hooks/useAudioUpload.js +1 -24
  399. package/dist/shared/lib/hooks/useConfirmDialog.js +1 -89
  400. package/dist/shared/lib/hooks/useDebouncedCallback.js +1 -32
  401. package/dist/shared/lib/hooks/useDelayedAutoPlay.js +1 -12
  402. package/dist/shared/lib/hooks/useFileUpload.js +1 -24
  403. package/dist/shared/lib/hooks/useImageUrl.js +1 -5
  404. package/dist/shared/lib/hooks/usePresignedFileUrl.js +1 -10
  405. package/dist/shared/lib/hooks/useReactHookForm.js +1 -31
  406. package/dist/shared/lib/hooks/useToast.js +1 -62
  407. package/dist/shared/lib/i18n/index.js +1 -54
  408. package/dist/shared/lib/i18n/messages/en/admin.js +1 -2104
  409. package/dist/shared/lib/i18n/messages/en/auth.js +1 -86
  410. package/dist/shared/lib/i18n/messages/en/common.js +1 -140
  411. package/dist/shared/lib/i18n/messages/en/file-upload.js +1 -18
  412. package/dist/shared/lib/i18n/messages/en/student.js +1 -133
  413. package/dist/shared/lib/i18n/messages/en/terms.js +1 -37
  414. package/dist/shared/lib/i18n/messages/en.js +1 -15
  415. package/dist/shared/lib/i18n/messages/vi/admin.js +1 -2104
  416. package/dist/shared/lib/i18n/messages/vi/auth.js +1 -86
  417. package/dist/shared/lib/i18n/messages/vi/common.js +1 -140
  418. package/dist/shared/lib/i18n/messages/vi/file-upload.js +1 -18
  419. package/dist/shared/lib/i18n/messages/vi/student.js +1 -133
  420. package/dist/shared/lib/i18n/messages/vi/terms.js +1 -37
  421. package/dist/shared/lib/i18n/messages/vi.js +1 -15
  422. package/dist/shared/lib/i18n/types.js +1 -1
  423. package/dist/shared/lib/stores/examQuestionStore.js +1 -212
  424. package/dist/shared/lib/stores/localeStore.js +1 -10
  425. package/dist/shared/lib/utils/public-page.js +1 -44
  426. package/dist/shared/lib/utils/question-transform-types.js +1 -1
  427. package/dist/shared/lib/utils/question-transform.js +1 -174
  428. package/dist/shared/lib/utils.js +1 -168
  429. package/dist/shared/lib/validation/choose-correct-answer.validation.js +1 -111
  430. package/dist/shared/lib/validation/label-the-picture.validation.js +1 -19
  431. package/dist/shared/lib/validation/listen-and-choose-from-answer-group.validation.js +1 -46
  432. package/dist/shared/lib/validation/listen-and-choose-objects-in-scene.validation.js +1 -21
  433. package/dist/shared/lib/validation/listen-and-drag-objects-into-scene.validation.js +1 -34
  434. package/dist/shared/lib/validation/listen-and-speak-answer.validation.js +1 -40
  435. package/dist/shared/lib/validation/listen-and-speak-compare-images.validation.js +1 -45
  436. package/dist/shared/lib/validation/listen-and-speak-image-group.validation.js +1 -19
  437. package/dist/shared/lib/validation/listen-and-speak-info-exchange.validation.js +1 -39
  438. package/dist/shared/lib/validation/listen-and-speak-odd-one-out.validation.js +1 -32
  439. package/dist/shared/lib/validation/listen-and-speak-question-list.validation.js +1 -19
  440. package/dist/shared/lib/validation/listen-and-speak-with-story-images.validation.js +1 -28
  441. package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +1 -12
  442. package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +1 -47
  443. package/dist/shared/lib/validation/read-and-color-objects.validation.js +1 -29
  444. package/dist/shared/lib/validation/read-displayed-content.validation.js +1 -28
  445. package/dist/shared/lib/validation/speaking-conversation.validation.js +1 -75
  446. package/dist/shared/lib/validation/speaking-describe-image.validation.js +1 -31
  447. package/dist/shared/lib/validation/spontaneous-qa.validation.js +1 -48
  448. package/dist/shared/lib/validation/word-fill-paragraph-default.validation.js +1 -38
  449. package/dist/shared/lib/validation/word-fill-paragraph-with-options.validation.js +1 -39
  450. package/dist/shared/lib/validation/word-fill-paragraph.validation.js +1 -43
  451. package/dist/shared/lib/validation/word-ordering.validation.js +1 -33
  452. package/dist/shared/types/branch.types.js +1 -6
  453. package/dist/shared/types/common.types.js +1 -50
  454. package/dist/shared/types/entities/exam-schedule.types.js +1 -5
  455. package/dist/shared/types/entities/exam.types.js +1 -1
  456. package/dist/shared/types/entities/question.types.js +1 -4
  457. package/dist/shared/types/entities/result.types.js +1 -4
  458. package/dist/shared/types/entities/user.types.js +1 -4
  459. package/dist/shared/types/exam-taking.types.js +1 -2
  460. package/dist/shared/types/history.types.js +1 -34
  461. package/dist/shared/types/index.js +1 -1
  462. package/dist/shared/types/questions/choose-correct-adjective.js +1 -4
  463. package/dist/shared/types/questions/choose-the-correct-answer.js +1 -9
  464. package/dist/shared/types/questions/fill-missing-words-in-grid.js +1 -11
  465. package/dist/shared/types/questions/index.js +1 -40
  466. package/dist/shared/types/questions/listen-and-speak-answer.js +1 -1
  467. package/dist/shared/types/questions/listen-and-speak-compare-images.js +1 -1
  468. package/dist/shared/types/questions/listen-and-speak-image-group.js +1 -1
  469. package/dist/shared/types/questions/listen-and-speak-info-exchange.js +1 -3
  470. package/dist/shared/types/questions/listen-and-speak-odd-one-out.js +1 -3
  471. package/dist/shared/types/questions/listen-and-speak-question-list.js +1 -1
  472. package/dist/shared/types/questions/listen-and-speak-with-story-images.js +1 -3
  473. package/dist/shared/types/questions/look-picture-choose-correct-answer.js +1 -5
  474. package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +1 -9
  475. package/dist/shared/types/questions/look-picture-fill-word-hint.js +1 -7
  476. package/dist/shared/types/questions/read-displayed-content.js +1 -1
  477. package/dist/shared/types/questions/speaking-conversation.js +1 -7
  478. package/dist/shared/types/questions/speaking-describe-image.js +1 -1
  479. package/dist/shared/types/questions/spontaneous-qa.js +1 -1
  480. package/dist/shared/types/questions/write-correct-verb-form.js +1 -12
  481. package/dist/shared/types/role.types.js +1 -1
  482. package/dist/shared/types/structure-management.js +1 -28
  483. package/dist/shared/types/student.types.js +1 -54
  484. package/dist/shared/types/teacher-dashboard-rooms.js +1 -2
  485. package/package.json +4 -2
@@ -1,28 +1 @@
1
- export const transformReadPassageAndAnswerQuestions = (question) => {
2
- const answerData = question.answer;
3
- if (!answerData?.passage || !answerData?.singleQuestion) {
4
- return { apiContent: {}, apiCorrectAnswer: {} };
5
- }
6
- const sq = answerData.singleQuestion;
7
- if (!sq.question) {
8
- return { apiContent: {}, apiCorrectAnswer: {} };
9
- }
10
- const apiContent = {
11
- question: sq.question,
12
- options: sq.options,
13
- };
14
- const passageData = {
15
- passageTitle: answerData.passageTitle || '',
16
- passage: answerData.passage,
17
- instructions: answerData.instructions || '',
18
- };
19
- return {
20
- apiContent,
21
- apiCorrectAnswer: {
22
- correctAnswer: sq.correctAnswer,
23
- },
24
- totalPoints: sq.points || 1,
25
- passageData,
26
- groupNumber: sq.groupNumber || 1,
27
- };
28
- };
1
+ export const transformReadPassageAndAnswerQuestions=s=>{const e=s.answer;if(!e?.passage||!e?.singleQuestion)return{apiContent:{},apiCorrectAnswer:{}};const t=e.singleQuestion;if(!t.question)return{apiContent:{},apiCorrectAnswer:{}};const n={question:t.question,options:t.options},r={passageTitle:e.passageTitle||"",passage:e.passage,instructions:e.instructions||""};return{apiContent:n,apiCorrectAnswer:{correctAnswer:t.correctAnswer},totalPoints:t.points||1,passageData:r,groupNumber:t.groupNumber||1}};
@@ -1,373 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useRef, useEffect, useMemo } 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 { Switch } from '../../../../components/ui/switch';
9
- import { PointsInput } from '../../../../components/ui/points-input';
10
- import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
11
- import { Badge } from '../../../../components/ui/badge';
12
- import { FileUpload } from '../../../../components/ui/file-upload';
13
- import { CreatorGuide } from '../../components/CreatorGuide';
14
- import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
15
- import { useDebouncedCallback } from '../../../../shared/lib/hooks/useDebouncedCallback';
16
- import { speakingConversationFormSchema, } from '../../../../shared/lib/validation/speaking-conversation.validation';
17
- import { Users, Mic, Volume2, MessageSquareText, BookOpen, Info, Plus, Trash2, Image, User, UserCheck, } from 'lucide-react';
18
- import { useExamQuestionStore } from '../../../../shared/lib/stores/examQuestionStore';
19
- import { generateObjectId } from '../../_shared/utils/id-generator';
20
- // ─── Helpers ─────────────────────────────────────────────────────────────────
21
- function getGroupNumberForIndex(questionIndexInPart, groups) {
22
- if (!groups || groups.length === 0 || questionIndexInPart === undefined)
23
- return 1;
24
- let cumulative = 0;
25
- for (const group of groups) {
26
- cumulative += group.questionCount;
27
- if (questionIndexInPart < cumulative)
28
- return group.number;
29
- }
30
- return groups[groups.length - 1]?.number ?? 1;
31
- }
32
- function useConversationGroup({ partId, groups, questionIndexInPart, initialData, }) {
33
- const setSharedListData = useExamQuestionStore((s) => s.setSharedListData);
34
- const getSharedListData = useExamQuestionStore((s) => s.getSharedListData);
35
- const sharedListData = useExamQuestionStore((s) => s.sharedListData);
36
- const isGrouped = Boolean(groups && groups.length > 0);
37
- const currentGroupNumber = getGroupNumberForIndex(questionIndexInPart, groups);
38
- const setGroupData = (data) => {
39
- if (!isGrouped || !partId)
40
- return;
41
- const stored = getSharedListData(partId, currentGroupNumber) || {};
42
- setSharedListData(partId, currentGroupNumber, {
43
- ...stored,
44
- conversationGroup: data,
45
- });
46
- };
47
- // Read from store (reactive via sharedListData subscription)
48
- const groupData = useMemo(() => {
49
- if (isGrouped && partId) {
50
- const stored = getSharedListData(partId, currentGroupNumber);
51
- const conv = stored?.conversationGroup;
52
- if (conv)
53
- return conv;
54
- }
55
- return initialData ?? {};
56
- // eslint-disable-next-line react-hooks/exhaustive-deps
57
- }, [sharedListData, isGrouped, partId, currentGroupNumber]);
58
- return { isGrouped, currentGroupNumber, groupData, setGroupData };
59
- }
60
- // ─── Component ────────────────────────────────────────────────────────────────
61
- export function SpeakingConversationCreator({ initialData, onSave, onChange, externalErrors, validationRef, partId, groups, questionIndexInPart, }) {
62
- const apiData = initialData;
63
- const isGrouped = Boolean(groups && groups.length > 0);
64
- const currentGroupNumber = getGroupNumberForIndex(questionIndexInPart, groups);
65
- const isFirstInGroup = useMemo(() => {
66
- if (!groups || !groups.length || questionIndexInPart === undefined)
67
- return true;
68
- let cumulative = 0;
69
- for (const group of groups) {
70
- if (questionIndexInPart === cumulative)
71
- return true;
72
- cumulative += group.questionCount;
73
- }
74
- return false;
75
- }, [groups, questionIndexInPart]);
76
- // ─── Group shared data (partnerName, partnerAvatarUrl, images) ────────────
77
- const initialGroupData = {
78
- partnerName: apiData?.partnerName ?? '',
79
- partnerAvatarUrl: apiData?.partnerAvatarUrl ?? '',
80
- images: apiData?.images ?? [],
81
- };
82
- const { groupData, setGroupData } = useConversationGroup({
83
- partId,
84
- groups,
85
- questionIndexInPart,
86
- initialData: initialGroupData,
87
- });
88
- // ─── Per-question form ────────────────────────────────────────────────────
89
- const form = useReactHookForm({
90
- defaultValues: {
91
- partnerName: groupData.partnerName ?? '',
92
- partnerAvatarUrl: groupData.partnerAvatarUrl ?? '',
93
- images: groupData.images ?? [],
94
- responder: apiData?.responder ?? 'STUDENT',
95
- interlocutorAudioUrl: apiData?.interlocutorAudioUrl ?? '',
96
- partnerAudioUrl: apiData?.partnerAudioUrl ?? '',
97
- questionText: apiData?.questionText ?? '',
98
- isBackup: apiData?.isBackup ?? false,
99
- expectedAnswers: apiData?.expectedAnswers?.length > 0 ? apiData.expectedAnswers : [''],
100
- explanation: apiData?.explanation ?? '',
101
- points: apiData?.points ?? 5,
102
- },
103
- zodSchema: speakingConversationFormSchema,
104
- mode: 'onBlur',
105
- });
106
- const previousInitialDataRef = useRef(initialData);
107
- const isInitializingRef = useRef(false);
108
- const onChangeRef = useRef(onChange);
109
- const previousPayloadRef = useRef('');
110
- useEffect(() => {
111
- onChangeRef.current = onChange;
112
- }, [onChange]);
113
- const debouncedOnChange = useDebouncedCallback((data) => {
114
- onChangeRef.current?.(data);
115
- }, 300);
116
- // Watched values
117
- const watchedResponder = form.watch('responder');
118
- const watchedInterlocutorAudioUrl = form.watch('interlocutorAudioUrl');
119
- const watchedPartnerAudioUrl = form.watch('partnerAudioUrl');
120
- const watchedQuestionText = form.watch('questionText');
121
- const watchedIsBackup = form.watch('isBackup');
122
- const watchedExpectedAnswers = form.watch('expectedAnswers') || [''];
123
- const watchedExplanation = form.watch('explanation');
124
- const watchedPoints = form.watch('points');
125
- const watchedPartnerName = form.watch('partnerName');
126
- const watchedPartnerAvatarUrl = form.watch('partnerAvatarUrl');
127
- const watchedImages = form.watch('images') || [];
128
- const isPartnerResponder = watchedResponder === 'PARTNER';
129
- // Auto-set points=0 when responder switches to PARTNER
130
- useEffect(() => {
131
- if (isPartnerResponder && form.getValues('points') !== 0) {
132
- form.setValue('points', 0, { shouldValidate: false });
133
- }
134
- }, [isPartnerResponder, form]);
135
- // Sync group data to store when first-in-group fields change
136
- useEffect(() => {
137
- if (!isFirstInGroup)
138
- return;
139
- setGroupData({
140
- partnerName: watchedPartnerName,
141
- partnerAvatarUrl: watchedPartnerAvatarUrl,
142
- images: watchedImages,
143
- });
144
- // eslint-disable-next-line react-hooks/exhaustive-deps
145
- }, [watchedPartnerName, watchedPartnerAvatarUrl, JSON.stringify(watchedImages), isFirstInGroup]);
146
- // Sync group data from store to form for non-first questions
147
- useEffect(() => {
148
- if (isFirstInGroup)
149
- return;
150
- if (groupData.partnerName !== undefined) {
151
- form.setValue('partnerName', groupData.partnerName ?? '', { shouldValidate: false });
152
- }
153
- if (groupData.partnerAvatarUrl !== undefined) {
154
- form.setValue('partnerAvatarUrl', groupData.partnerAvatarUrl ?? '', {
155
- shouldValidate: false,
156
- });
157
- }
158
- if (groupData.images !== undefined) {
159
- form.setValue('images', groupData.images ?? [], { shouldValidate: false });
160
- }
161
- // eslint-disable-next-line react-hooks/exhaustive-deps
162
- }, [groupData, isFirstInGroup]);
163
- // Expose validation ref
164
- useEffect(() => {
165
- if (validationRef) {
166
- validationRef.current = {
167
- triggerValidation: async () => {
168
- return await form.trigger();
169
- },
170
- getFormData: () => ({
171
- responder: watchedResponder,
172
- interlocutorAudioUrl: watchedInterlocutorAudioUrl,
173
- partnerAudioUrl: watchedPartnerAudioUrl,
174
- questionText: watchedQuestionText,
175
- isBackup: watchedIsBackup,
176
- expectedAnswers: watchedExpectedAnswers,
177
- explanation: watchedExplanation,
178
- points: watchedPoints,
179
- partnerName: watchedPartnerName,
180
- partnerAvatarUrl: watchedPartnerAvatarUrl,
181
- images: watchedImages,
182
- groupNumber: isGrouped ? currentGroupNumber : undefined,
183
- }),
184
- };
185
- }
186
- }, [
187
- validationRef,
188
- form,
189
- watchedResponder,
190
- watchedInterlocutorAudioUrl,
191
- watchedPartnerAudioUrl,
192
- watchedQuestionText,
193
- watchedIsBackup,
194
- watchedExpectedAnswers,
195
- watchedExplanation,
196
- watchedPoints,
197
- watchedPartnerName,
198
- watchedPartnerAvatarUrl,
199
- watchedImages,
200
- isGrouped,
201
- currentGroupNumber,
202
- ]);
203
- // Reset form when initialData changes externally
204
- useEffect(() => {
205
- const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
206
- if (!initialDataChanged)
207
- return;
208
- previousInitialDataRef.current = initialData;
209
- const normalizedData = {
210
- partnerName: apiData?.partnerName ?? groupData.partnerName ?? '',
211
- partnerAvatarUrl: apiData?.partnerAvatarUrl ?? groupData.partnerAvatarUrl ?? '',
212
- images: apiData?.images ?? groupData.images ?? [],
213
- responder: apiData?.responder ?? 'STUDENT',
214
- interlocutorAudioUrl: apiData?.interlocutorAudioUrl ?? '',
215
- partnerAudioUrl: apiData?.partnerAudioUrl ?? '',
216
- questionText: apiData?.questionText ?? '',
217
- isBackup: apiData?.isBackup ?? false,
218
- expectedAnswers: apiData?.expectedAnswers?.length > 0 ? apiData.expectedAnswers : [''],
219
- explanation: apiData?.explanation ?? '',
220
- points: apiData?.points ?? 5,
221
- };
222
- const currentFormData = {
223
- responder: watchedResponder,
224
- interlocutorAudioUrl: watchedInterlocutorAudioUrl,
225
- partnerAudioUrl: watchedPartnerAudioUrl,
226
- questionText: watchedQuestionText,
227
- isBackup: watchedIsBackup,
228
- expectedAnswers: watchedExpectedAnswers,
229
- explanation: watchedExplanation,
230
- points: watchedPoints,
231
- };
232
- const isEcho = currentFormData.responder === normalizedData.responder &&
233
- currentFormData.points === normalizedData.points &&
234
- currentFormData.explanation === normalizedData.explanation &&
235
- currentFormData.interlocutorAudioUrl === normalizedData.interlocutorAudioUrl &&
236
- currentFormData.partnerAudioUrl === normalizedData.partnerAudioUrl &&
237
- currentFormData.questionText === normalizedData.questionText &&
238
- JSON.stringify(currentFormData.expectedAnswers) ===
239
- JSON.stringify(normalizedData.expectedAnswers);
240
- const wasJustSaved = initialData?.isCompleted === true;
241
- if (isEcho && wasJustSaved)
242
- return;
243
- if (!isEcho) {
244
- isInitializingRef.current = true;
245
- form.reset(normalizedData);
246
- setTimeout(() => {
247
- isInitializingRef.current = false;
248
- }, 150);
249
- }
250
- }, [
251
- initialData,
252
- form,
253
- watchedResponder,
254
- watchedInterlocutorAudioUrl,
255
- watchedPartnerAudioUrl,
256
- watchedQuestionText,
257
- watchedIsBackup,
258
- watchedExpectedAnswers,
259
- watchedExplanation,
260
- watchedPoints,
261
- groupData,
262
- ]);
263
- // Fire onChange on form value changes
264
- useEffect(() => {
265
- const payload = {
266
- responder: watchedResponder,
267
- interlocutorAudioUrl: watchedInterlocutorAudioUrl,
268
- partnerAudioUrl: watchedPartnerAudioUrl,
269
- questionText: watchedQuestionText,
270
- isBackup: watchedIsBackup,
271
- expectedAnswers: watchedExpectedAnswers,
272
- explanation: watchedExplanation,
273
- points: isPartnerResponder ? 0 : watchedPoints,
274
- partnerName: watchedPartnerName,
275
- partnerAvatarUrl: watchedPartnerAvatarUrl,
276
- images: watchedImages,
277
- groupNumber: isGrouped ? currentGroupNumber : undefined,
278
- };
279
- if (isInitializingRef.current) {
280
- previousPayloadRef.current = JSON.stringify(payload);
281
- return;
282
- }
283
- const payloadString = JSON.stringify(payload);
284
- if (previousPayloadRef.current !== payloadString) {
285
- previousPayloadRef.current = payloadString;
286
- debouncedOnChange(payload);
287
- }
288
- }, [
289
- watchedResponder,
290
- watchedInterlocutorAudioUrl,
291
- watchedPartnerAudioUrl,
292
- watchedQuestionText,
293
- watchedIsBackup,
294
- watchedExpectedAnswers,
295
- watchedExplanation,
296
- watchedPoints,
297
- watchedPartnerName,
298
- watchedPartnerAvatarUrl,
299
- watchedImages,
300
- isPartnerResponder,
301
- isGrouped,
302
- currentGroupNumber,
303
- debouncedOnChange,
304
- ]);
305
- // ─── Expected Answers handlers ────────────────────────────────────────────
306
- const handleAddExpectedAnswer = () => {
307
- form.setValue('expectedAnswers', [...watchedExpectedAnswers, ''], { shouldValidate: false });
308
- };
309
- const handleRemoveExpectedAnswer = (index) => {
310
- const updated = [...watchedExpectedAnswers];
311
- updated.splice(index, 1);
312
- if (updated.length === 0)
313
- updated.push('');
314
- form.setValue('expectedAnswers', updated, { shouldValidate: true });
315
- };
316
- const handleExpectedAnswerChange = (index, value) => {
317
- const updated = [...watchedExpectedAnswers];
318
- updated[index] = value;
319
- form.setValue('expectedAnswers', updated, { shouldValidate: true });
320
- };
321
- // ─── Images handlers ──────────────────────────────────────────────────────
322
- const handleAddImage = () => {
323
- const updated = [
324
- ...watchedImages,
325
- { id: generateObjectId(), url: '', label: '' },
326
- ];
327
- form.setValue('images', updated, { shouldValidate: false });
328
- };
329
- const handleRemoveImage = (index) => {
330
- const updated = watchedImages.filter((_, i) => i !== index);
331
- form.setValue('images', updated, { shouldValidate: true });
332
- };
333
- const handleImageChange = (index, field, value) => {
334
- const updated = watchedImages.map((img, i) => i === index ? { ...img, [field]: value } : img);
335
- form.setValue('images', updated, { shouldValidate: true });
336
- };
337
- // ─── Render ───────────────────────────────────────────────────────────────
338
- return (_jsxs("div", { className: "space-y-6", children: [_jsx(CreatorGuide, { colorScheme: "teal", steps: [
339
- {
340
- text: 'Cấu hình Group Data (tên Partner, avatar) — chỉ câu đầu tiên trong nhóm được chỉnh sửa',
341
- },
342
- {
343
- text: 'Chọn Responder: Partner (câu mẫu) hoặc Student (học sinh trả lời)',
344
- highlight: true,
345
- },
346
- {
347
- text: 'Upload audio Interlocutor (câu hỏi) và audio Partner (câu trả lời mẫu khi responder = Partner)',
348
- },
349
- { text: 'Nhập đáp án mong muốn (dùng khi responder = Student để giáo viên tham chiếu)' },
350
- { text: 'Thêm giải thích nếu cần' },
351
- ], tips: [
352
- 'Responder = Partner: tự động set is_example=true, points=0. Học sinh chỉ nghe, không ghi âm.',
353
- 'Responder = Student: học sinh ghi âm trả lời. Cần nhập điểm >= 1.',
354
- ] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-teal-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-teal-400 to-cyan-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-teal-500 to-cyan-600 shadow-sm", children: _jsx(Users, { 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-bold text-gray-900", children: "D\u1EEF li\u1EC7u nh\u00F3m" }), isGrouped && (_jsxs(Badge, { variant: "outline", className: "border-teal-300 bg-teal-50 text-teal-700 text-xs", children: ["Nh\u00F3m ", currentGroupNumber] })), !isFirstInGroup && (_jsx(Badge, { variant: "outline", className: "border-gray-300 bg-gray-50 text-gray-500 text-xs", children: "Ch\u1EC9 \u0111\u1ECDc" }))] }), _jsx("p", { className: "text-sm text-gray-500 mt-0.5", children: isFirstInGroup
355
- ? 'Thông tin Partner và hình ảnh dùng chung cho toàn bộ nhóm câu hỏi'
356
- : 'Được quản lý bởi câu hỏi đầu tiên trong nhóm' })] })] }) }), _jsx(CardContent, { className: "space-y-5", children: isFirstInGroup ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-700", children: [_jsx(User, { className: "h-4 w-4 text-teal-500" }), "T\u00EAn nh\u00E2n v\u1EADt Partner"] }), _jsx(Input, { ...form.register('partnerName'), placeholder: "VD: Bingo", className: "border-gray-200 focus:border-teal-300 focus:ring-teal-200" }), _jsx("p", { className: "text-xs text-gray-400", children: "T\u00EAn hi\u1EC3n th\u1ECB c\u1EE7a nh\u00E2n v\u1EADt \u1EA3o (Partner) \u2014 d\u00F9ng trong giao di\u1EC7n thi." })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-700", children: [_jsx(User, { className: "h-4 w-4 text-teal-500" }), "Avatar Partner"] }), _jsx(FileUpload, { id: "partner-avatar-url", label: "", accept: "image/*", value: watchedPartnerAvatarUrl || '', onChange: (url) => form.setValue('partnerAvatarUrl', url, { shouldValidate: true }), maxSize: 5, placeholder: "Upload \u1EA3nh avatar Partner (PNG, JPG, ...)", autoUpload: true, prefix: "speaking/partner-avatar" })] }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-700", children: [_jsx(Image, { className: "h-4 w-4 text-teal-500" }), "H\u00ECnh \u1EA3nh phase (t\u00F9y ch\u1ECDn)"] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: handleAddImage, className: "gap-1.5 border-teal-200 text-teal-700 hover:bg-teal-50 text-xs", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm h\u00ECnh"] })] }), watchedImages.length === 0 ? (_jsxs("div", { className: "rounded-lg border-2 border-dashed border-teal-200 bg-teal-50/40 py-6 text-center", children: [_jsx(Image, { className: "mx-auto h-8 w-8 text-teal-300 mb-2" }), _jsx("p", { className: "text-sm text-gray-400", children: "Kh\u00F4ng c\u00F3 h\u00ECnh \u1EA3nh \u2014 d\u00F9ng cho Part 2 phases c\u00F3 tranh" })] })) : (_jsx("div", { className: "space-y-3", children: watchedImages.map((img, index) => (_jsxs("div", { className: "rounded-lg border border-gray-200 bg-gray-50 p-3 space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("span", { className: "text-xs font-semibold text-gray-600", children: ["H\u00ECnh ", index + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "h-6 w-6 text-red-400 hover:text-red-600 hover:bg-red-50", onClick: () => handleRemoveImage(index), children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) })] }), _jsx(FileUpload, { id: `image-url-${index}`, label: "", accept: "image/*", value: img.url, onChange: (url) => handleImageChange(index, 'url', url), maxSize: 10, placeholder: "Upload h\u00ECnh \u1EA3nh", autoUpload: true, prefix: "speaking/phase-images" }), _jsx(Input, { value: img.label, onChange: (e) => handleImageChange(index, 'label', e.target.value), placeholder: "Nh\u00E3n h\u00ECnh (VD: Park, Bedroom, Library ...)", className: "border-gray-200 text-sm focus:border-teal-300" })] }, img.id))) }))] })] })) : (
357
- /* Read-only group data view for non-first questions */
358
- _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-gray-100 bg-gray-50 px-4 py-3", children: [_jsx(User, { className: "h-4 w-4 text-teal-500 flex-shrink-0" }), _jsxs("div", { children: [_jsx("p", { className: "text-xs text-gray-500", children: "T\u00EAn Partner" }), _jsx("p", { className: "text-sm font-medium text-gray-800", children: groupData.partnerName || (_jsx("span", { className: "italic text-gray-400", children: "Ch\u01B0a \u0111\u1EB7t t\u00EAn" })) })] })] }), groupData.images && groupData.images.length > 0 && (_jsxs("div", { className: "rounded-lg border border-gray-100 bg-gray-50 px-4 py-3", children: [_jsxs("p", { className: "text-xs text-gray-500 mb-2", children: ["H\u00ECnh \u1EA3nh (", groupData.images.length, ")"] }), _jsx("div", { className: "flex flex-wrap gap-2", children: groupData.images.map((img) => (_jsx("span", { className: "inline-flex items-center rounded-full bg-teal-100 px-2 py-0.5 text-xs text-teal-700", children: img.label || img.id }, img.id))) })] }))] })) })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-indigo-100/50", children: [_jsx("div", { className: `h-1 bg-gradient-to-r ${isPartnerResponder ? 'from-orange-400 to-amber-400' : 'from-indigo-400 to-violet-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 shadow-sm ${isPartnerResponder ? 'bg-gradient-to-br from-orange-500 to-amber-500' : 'bg-gradient-to-br from-indigo-500 to-violet-600'}`, children: _jsx(Mic, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-lg font-bold text-gray-900", children: "N\u1ED9i dung c\u00E2u h\u1ECFi" }), isPartnerResponder && (_jsx(Badge, { className: "bg-orange-100 text-orange-700 border-orange-200 border text-xs font-semibold", children: "C\u00E2u h\u1ECFi m\u1EABu (Example)" }))] }), _jsx("p", { className: "text-sm text-gray-500 mt-0.5", children: isPartnerResponder
359
- ? 'Partner trả lời — học sinh chỉ nghe, không ghi âm'
360
- : 'Student trả lời — học sinh ghi âm câu trả lời' })] })] }) }), _jsxs(CardContent, { className: "space-y-6", children: [_jsxs("div", { className: `rounded-xl border p-4 space-y-4 ${isPartnerResponder ? 'border-orange-200 bg-orange-50/40' : 'border-indigo-200 bg-indigo-50/40'}`, children: [_jsx("div", { className: "flex items-center justify-between", children: _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-base font-semibold text-gray-800", children: "Ng\u01B0\u1EDDi tr\u1EA3 l\u1EDDi" }), _jsx("p", { className: "text-sm text-gray-500", children: "Ai s\u1EBD tr\u1EA3 l\u1EDDi c\u00E2u h\u1ECFi n\u00E0y trong b\u00E0i thi?" })] }) }), _jsxs("div", { className: "flex gap-3", children: [_jsxs("button", { type: "button", onClick: () => form.setValue('responder', 'PARTNER', { shouldValidate: true }), className: `flex flex-1 items-center gap-3 rounded-xl border-2 p-3 transition-all ${isPartnerResponder
361
- ? 'border-orange-400 bg-orange-50 shadow-sm'
362
- : 'border-gray-200 bg-white hover:border-gray-300'}`, children: [_jsx("div", { className: `flex h-9 w-9 items-center justify-center rounded-full ${isPartnerResponder ? 'bg-orange-500' : 'bg-gray-200'}`, children: _jsx(User, { className: `h-4 w-4 ${isPartnerResponder ? 'text-white' : 'text-gray-500'}` }) }), _jsxs("div", { className: "text-left", children: [_jsx("p", { className: `text-sm font-semibold ${isPartnerResponder ? 'text-orange-800' : 'text-gray-700'}`, children: "Partner tr\u1EA3 l\u1EDDi" }), _jsx("p", { className: `text-xs ${isPartnerResponder ? 'text-orange-600' : 'text-gray-500'}`, children: "C\u00E2u m\u1EABu \u2014 is_example=true, points=0" })] })] }), _jsxs("button", { type: "button", onClick: () => form.setValue('responder', 'STUDENT', { shouldValidate: true }), className: `flex flex-1 items-center gap-3 rounded-xl border-2 p-3 transition-all ${!isPartnerResponder
363
- ? 'border-indigo-400 bg-indigo-50 shadow-sm'
364
- : 'border-gray-200 bg-white hover:border-gray-300'}`, children: [_jsx("div", { className: `flex h-9 w-9 items-center justify-center rounded-full ${!isPartnerResponder ? 'bg-indigo-500' : 'bg-gray-200'}`, children: _jsx(UserCheck, { className: `h-4 w-4 ${!isPartnerResponder ? 'text-white' : 'text-gray-500'}` }) }), _jsxs("div", { className: "text-left", children: [_jsx("p", { className: `text-sm font-semibold ${!isPartnerResponder ? 'text-indigo-800' : 'text-gray-700'}`, children: "Student tr\u1EA3 l\u1EDDi" }), _jsx("p", { className: `text-xs ${!isPartnerResponder ? 'text-indigo-600' : 'text-gray-500'}`, children: "C\u00E2u th\u1EF1c \u2014 h\u1ECDc sinh ghi \u00E2m" })] })] })] }), form.hasError('responder') && (_jsx("p", { className: "text-sm text-red-600", children: form.getFieldError('responder') }))] }), _jsxs("div", { className: "w-full md:w-1/2", children: [_jsx(PointsInput, { ...form.register('points', { valueAsNumber: true }), disabled: isPartnerResponder, error: form.hasError('points') ? form.getFieldError('points') : undefined }), isPartnerResponder && (_jsx("p", { className: "mt-1.5 text-xs text-orange-600", children: "C\u00E2u h\u1ECFi m\u1EABu (Partner) lu\u00F4n c\u00F3 0 \u0111i\u1EC3m" }))] }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-700", children: [_jsx(Volume2, { className: "h-4 w-4 text-blue-500" }), "Audio Interlocutor", _jsx("span", { className: "text-xs font-normal text-gray-400", children: "(t\u00F9y ch\u1ECDn)" })] }), _jsx(FileUpload, { id: "interlocutor-audio-url", label: "", accept: "audio/*", value: watchedInterlocutorAudioUrl || '', onChange: (url) => form.setValue('interlocutorAudioUrl', url, { shouldValidate: true }), maxSize: 20, placeholder: "Upload audio c\u00E2u h\u1ECFi c\u1EE7a Interlocutor (MP3, WAV...)", autoUpload: true, prefix: "speaking/interlocutor" }), _jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-blue-100 bg-blue-50/60 px-3 py-2", children: [_jsx(Info, { className: "h-4 w-4 text-blue-500 mt-0.5 flex-shrink-0" }), _jsx("p", { className: "text-xs text-blue-700", children: "Audio c\u1EE7a Interlocutor (ng\u01B0\u1EDDi h\u1ECFi). Ph\u00E1t tr\u01B0\u1EDBc khi Partner ho\u1EB7c Student tr\u1EA3 l\u1EDDi." })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-700", children: [_jsx(Volume2, { className: "h-4 w-4 text-orange-500" }), "Audio Partner", isPartnerResponder && _jsx("span", { className: "text-red-500", children: "*" }), !isPartnerResponder && (_jsx("span", { className: "text-xs font-normal text-gray-400", children: "(t\u00F9y ch\u1ECDn)" }))] }), _jsx(FileUpload, { id: "partner-audio-url", label: "", accept: "audio/*", value: watchedPartnerAudioUrl || '', onChange: (url) => form.setValue('partnerAudioUrl', url, { shouldValidate: true }), maxSize: 20, placeholder: "Upload audio tr\u1EA3 l\u1EDDi c\u1EE7a Partner (MP3, WAV...)", autoUpload: true, prefix: "speaking/partner" }), form.hasError('partnerAudioUrl') && (_jsx("p", { className: "text-sm text-red-600", children: form.getFieldError('partnerAudioUrl') })), _jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-orange-100 bg-orange-50/60 px-3 py-2", children: [_jsx(Info, { className: "h-4 w-4 text-orange-500 mt-0.5 flex-shrink-0" }), _jsx("p", { className: "text-xs text-orange-700", children: isPartnerResponder
365
- ? 'Bắt buộc khi responder = Partner. Phát sau audio Interlocutor.'
366
- : 'Tùy chọn khi responder = Student — dùng cho Talk Together (Partner nói trước, Student trả lời sau).' })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-700", children: [_jsx(MessageSquareText, { className: "h-4 w-4 text-gray-500" }), "Text c\u00E2u h\u1ECFi (t\u00F9y ch\u1ECDn)"] }), _jsx(Textarea, { ...form.register('questionText'), placeholder: "VD: What's your name? How old are you?", rows: 2, className: "min-h-[60px] border-gray-200 focus:border-indigo-300 focus:ring-indigo-200 resize-none" }), _jsx("p", { className: "text-xs text-gray-400", children: "Ch\u1EC9 hi\u1EC3n th\u1ECB cho admin/gi\u00E1o vi\u00EAn khi review. H\u1ECDc sinh ch\u1EC9 nghe audio." })] }), _jsxs("div", { className: "flex items-center justify-between rounded-lg border border-gray-200 bg-gray-50 px-4 py-3", children: [_jsxs("div", { className: "space-y-0.5", children: [_jsx(Label, { className: "text-sm font-semibold text-gray-700", children: "C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng" }), _jsx("p", { className: "text-xs text-gray-400", children: "\u0110\u00E1nh d\u1EA5u c\u00E2u h\u1ECFi n\u00E0y l\u00E0 d\u1EF1 ph\u00F2ng (backup)" })] }), _jsx(Switch, { checked: watchedIsBackup, onCheckedChange: (checked) => form.setValue('isBackup', checked, { shouldValidate: true }) })] })] })] }), _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" }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "\u0110\u00E1p \u00E1n mong mu\u1ED1n" }), _jsx("span", { className: "inline-flex items-center rounded-full border border-green-200 bg-green-50 px-2.5 py-0.5 text-xs font-medium text-green-600", children: "Kh\u00F4ng b\u1EAFt bu\u1ED9c" })] }), _jsx("p", { className: "text-sm text-gray-500 mt-0.5", children: isPartnerResponder
367
- ? 'Câu mẫu — đáp án dùng để ghi nhớ nội dung (không chấm điểm)'
368
- : 'Đáp án tham chiếu để giáo viên chấm thủ công' })] })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: handleAddExpectedAnswer, className: "gap-1.5 border-green-200 text-green-700 hover:bg-green-50", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm \u0111\u00E1p \u00E1n"] })] }) }), _jsxs(CardContent, { className: "space-y-3", 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" }), _jsx("p", { className: "text-xs text-blue-700", children: isPartnerResponder
369
- ? 'Câu hỏi mẫu: có thể nhập nội dung Partner nói để ghi nhớ. Không dùng để chấm điểm.'
370
- : 'Giáo viên dùng các đáp án này làm tham chiếu khi chấm thủ công câu trả lời của học sinh.' })] }), watchedExpectedAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-full bg-green-100 text-xs font-semibold text-green-700 mt-1.5 flex-shrink-0", children: index + 1 }), _jsx(Textarea, { value: answer || '', onChange: (e) => handleExpectedAnswerChange(index, e.target.value), placeholder: index === 0
371
- ? 'VD: My name is Bingo. I am 8 years old.'
372
- : 'Thêm đáp án mẫu khác...', rows: 2, className: "min-h-[60px] border-gray-200 bg-white transition-all focus:border-green-300 focus:ring-green-200 resize-none flex-1" }), watchedExpectedAnswers.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "mt-1.5 h-7 w-7 text-red-400 hover:text-red-600 hover:bg-red-50 flex-shrink-0", onClick: () => handleRemoveExpectedAnswer(index), "aria-label": `Xóa đáp án ${index + 1}`, children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }, index)))] })] }), _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 th\u00EAm v\u1EC1 c\u00E2u tr\u1EA3 l\u1EDDi k\u1EF3 v\u1ECDng, d\u00F9ng khi review k\u1EBFt qu\u1EA3." })] })] }) }), _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: 'My name is...' v\u00EC c\u00E2u h\u1ECFi \u0111ang h\u1ECFi v\u1EC1 t\u00EAn v\u00E0 tu\u1ED5i.", rows: 3, className: "min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none" }) })] }), 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 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))) })] }) }))] }));
373
- }
1
+ "use client";import{jsx as e,jsxs as t,Fragment as r}from"react/jsx-runtime";import{useRef as a,useEffect as n,useMemo as i}from"react";import{Button as s}from"../../../../components/ui/button";import{Label as l}from"../../../../components/ui/label";import{Input as o}from"../../../../components/ui/input";import{Textarea as d}from"../../../../components/ui/textarea";import{Switch as c}from"../../../../components/ui/switch";import{PointsInput as h}from"../../../../components/ui/points-input";import{Card as m,CardContent as u,CardHeader as g,CardTitle as p}from"../../../../components/ui/card";import{Badge as x}from"../../../../components/ui/badge";import{FileUpload as b}from"../../../../components/ui/file-upload";import{CreatorGuide as N}from"../../components/CreatorGuide";import{useReactHookForm as f}from"../../../../shared/lib/hooks/useReactHookForm";import{useDebouncedCallback as v}from"../../../../shared/lib/hooks/useDebouncedCallback";import{speakingConversationFormSchema as y}from"../../../../shared/lib/validation/speaking-conversation.validation";import{Users as w,Mic as A,Volume2 as U,MessageSquareText as k,BookOpen as V,Info as C,Plus as P,Trash2 as T,Image as S,User as D,UserCheck as q}from"lucide-react";import{useExamQuestionStore as B}from"../../../../shared/lib/stores/examQuestionStore";import{generateObjectId as j}from"../../_shared/utils/id-generator";function I(e,t){if(!t||0===t.length||void 0===e)return 1;let r=0;for(const a of t)if(r+=a.questionCount,e<r)return a.number;return t[t.length-1]?.number??1}export function SpeakingConversationCreator({initialData:E,onSave:G,onChange:z,externalErrors:R,validationRef:J,partId:H,groups:O,questionIndexInPart:F}){const K=E,L=Boolean(O&&O.length>0),M=I(F,O),W=i(()=>{if(!O||!O.length||void 0===F)return!0;let e=0;for(const t of O){if(F===e)return!0;e+=t.questionCount}return!1},[O,F]),_={partnerName:K?.partnerName??"",partnerAvatarUrl:K?.partnerAvatarUrl??"",images:K?.images??[]},{groupData:$,setGroupData:Q}=function({partId:e,groups:t,questionIndexInPart:r,initialData:a}){const n=B(e=>e.setSharedListData),s=B(e=>e.getSharedListData),l=B(e=>e.sharedListData),o=Boolean(t&&t.length>0),d=I(r,t),c=i(()=>{if(o&&e){const t=s(e,d),r=t?.conversationGroup;if(r)return r}return a??{}},[l,o,e,d]);return{isGrouped:o,currentGroupNumber:d,groupData:c,setGroupData:t=>{if(!o||!e)return;const r=s(e,d)||{};n(e,d,{...r,conversationGroup:t})}}}({partId:H,groups:O,questionIndexInPart:F,initialData:_}),X=f({defaultValues:{partnerName:$.partnerName??"",partnerAvatarUrl:$.partnerAvatarUrl??"",images:$.images??[],responder:K?.responder??"STUDENT",interlocutorAudioUrl:K?.interlocutorAudioUrl??"",partnerAudioUrl:K?.partnerAudioUrl??"",questionText:K?.questionText??"",isBackup:K?.isBackup??!1,expectedAnswers:K?.expectedAnswers?.length>0?K.expectedAnswers:[""],explanation:K?.explanation??"",points:K?.points??5},zodSchema:y,mode:"onBlur"}),Y=a(E),Z=a(!1),ee=a(z),te=a("");n(()=>{ee.current=z},[z]);const re=v(e=>{ee.current?.(e)},300),ae=X.watch("responder"),ne=X.watch("interlocutorAudioUrl"),ie=X.watch("partnerAudioUrl"),se=X.watch("questionText"),le=X.watch("isBackup"),oe=X.watch("expectedAnswers")||[""],de=X.watch("explanation"),ce=X.watch("points"),he=X.watch("partnerName"),me=X.watch("partnerAvatarUrl"),ue=X.watch("images")||[],ge="PARTNER"===ae;n(()=>{ge&&0!==X.getValues("points")&&X.setValue("points",0,{shouldValidate:!1})},[ge,X]),n(()=>{W&&Q({partnerName:he,partnerAvatarUrl:me,images:ue})},[he,me,JSON.stringify(ue),W]),n(()=>{W||(void 0!==$.partnerName&&X.setValue("partnerName",$.partnerName??"",{shouldValidate:!1}),void 0!==$.partnerAvatarUrl&&X.setValue("partnerAvatarUrl",$.partnerAvatarUrl??"",{shouldValidate:!1}),void 0!==$.images&&X.setValue("images",$.images??[],{shouldValidate:!1}))},[$,W]),n(()=>{J&&(J.current={triggerValidation:async()=>await X.trigger(),getFormData:()=>({responder:ae,interlocutorAudioUrl:ne,partnerAudioUrl:ie,questionText:se,isBackup:le,expectedAnswers:oe,explanation:de,points:ce,partnerName:he,partnerAvatarUrl:me,images:ue,groupNumber:L?M:void 0})})},[J,X,ae,ne,ie,se,le,oe,de,ce,he,me,ue,L,M]),n(()=>{if(!(JSON.stringify(Y.current)!==JSON.stringify(E)))return;Y.current=E;const e={partnerName:K?.partnerName??$.partnerName??"",partnerAvatarUrl:K?.partnerAvatarUrl??$.partnerAvatarUrl??"",images:K?.images??$.images??[],responder:K?.responder??"STUDENT",interlocutorAudioUrl:K?.interlocutorAudioUrl??"",partnerAudioUrl:K?.partnerAudioUrl??"",questionText:K?.questionText??"",isBackup:K?.isBackup??!1,expectedAnswers:K?.expectedAnswers?.length>0?K.expectedAnswers:[""],explanation:K?.explanation??"",points:K?.points??5},t={responder:ae,interlocutorAudioUrl:ne,partnerAudioUrl:ie,questionText:se,isBackup:le,expectedAnswers:oe,explanation:de,points:ce},r=t.responder===e.responder&&t.points===e.points&&t.explanation===e.explanation&&t.interlocutorAudioUrl===e.interlocutorAudioUrl&&t.partnerAudioUrl===e.partnerAudioUrl&&t.questionText===e.questionText&&JSON.stringify(t.expectedAnswers)===JSON.stringify(e.expectedAnswers);r&&!0===E?.isCompleted||r||(Z.current=!0,X.reset(e),setTimeout(()=>{Z.current=!1},150))},[E,X,ae,ne,ie,se,le,oe,de,ce,$]),n(()=>{const e={responder:ae,interlocutorAudioUrl:ne,partnerAudioUrl:ie,questionText:se,isBackup:le,expectedAnswers:oe,explanation:de,points:ge?0:ce,partnerName:he,partnerAvatarUrl:me,images:ue,groupNumber:L?M:void 0};if(Z.current)return void(te.current=JSON.stringify(e));const t=JSON.stringify(e);te.current!==t&&(te.current=t,re(e))},[ae,ne,ie,se,le,oe,de,ce,he,me,ue,ge,L,M,re]);const pe=(e,t,r)=>{const a=ue.map((a,n)=>n===e?{...a,[t]:r}:a);X.setValue("images",a,{shouldValidate:!0})};return t("div",{className:"space-y-6",children:[e(N,{colorScheme:"teal",steps:[{text:"Cấu hình Group Data (tên Partner, avatar) — chỉ câu đầu tiên trong nhóm được chỉnh sửa"},{text:"Chọn Responder: Partner (câu mẫu) hoặc Student (học sinh trả lời)",highlight:!0},{text:"Upload audio Interlocutor (câu hỏi) và audio Partner (câu trả lời mẫu khi responder = Partner)"},{text:"Nhập đáp án mong muốn (dùng khi responder = Student để giáo viên tham chiếu)"},{text:"Thêm giải thích nếu cần"}],tips:["Responder = Partner: tự động set is_example=true, points=0. Học sinh chỉ nghe, không ghi âm.","Responder = Student: học sinh ghi âm trả lời. Cần nhập điểm >= 1."]}),t(m,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-teal-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-teal-400 to-cyan-400"}),e(g,{className:"pb-4",children:t("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-teal-500 to-cyan-600 shadow-sm",children:e(w,{className:"h-4 w-4 text-white"})}),t("div",{children:[t("div",{className:"flex items-center gap-2",children:[e(p,{className:"text-lg font-bold text-gray-900",children:"Dữ liệu nhóm"}),L&&t(x,{variant:"outline",className:"border-teal-300 bg-teal-50 text-teal-700 text-xs",children:["Nhóm ",M]}),!W&&e(x,{variant:"outline",className:"border-gray-300 bg-gray-50 text-gray-500 text-xs",children:"Chỉ đọc"})]}),e("p",{className:"text-sm text-gray-500 mt-0.5",children:W?"Thông tin Partner và hình ảnh dùng chung cho toàn bộ nhóm câu hỏi":"Được quản lý bởi câu hỏi đầu tiên trong nhóm"})]})]})}),e(u,{className:"space-y-5",children:W?t(r,{children:[t("div",{className:"space-y-2",children:[t(l,{className:"flex items-center gap-2 text-sm font-semibold text-gray-700",children:[e(D,{className:"h-4 w-4 text-teal-500"}),"Tên nhân vật Partner"]}),e(o,{...X.register("partnerName"),placeholder:"VD: Bingo",className:"border-gray-200 focus:border-teal-300 focus:ring-teal-200"}),e("p",{className:"text-xs text-gray-400",children:"Tên hiển thị của nhân vật ảo (Partner) — dùng trong giao diện thi."})]}),t("div",{className:"space-y-2",children:[t(l,{className:"flex items-center gap-2 text-sm font-semibold text-gray-700",children:[e(D,{className:"h-4 w-4 text-teal-500"}),"Avatar Partner"]}),e(b,{id:"partner-avatar-url",label:"",accept:"image/*",value:me||"",onChange:e=>X.setValue("partnerAvatarUrl",e,{shouldValidate:!0}),maxSize:5,placeholder:"Upload ảnh avatar Partner (PNG, JPG, ...)",autoUpload:!0,prefix:"speaking/partner-avatar"})]}),t("div",{className:"space-y-3",children:[t("div",{className:"flex items-center justify-between",children:[t(l,{className:"flex items-center gap-2 text-sm font-semibold text-gray-700",children:[e(S,{className:"h-4 w-4 text-teal-500"}),"Hình ảnh phase (tùy chọn)"]}),t(s,{type:"button",variant:"outline",size:"sm",onClick:()=>{const e=[...ue,{id:j(),url:"",label:""}];X.setValue("images",e,{shouldValidate:!1})},className:"gap-1.5 border-teal-200 text-teal-700 hover:bg-teal-50 text-xs",children:[e(P,{className:"h-3.5 w-3.5"}),"Thêm hình"]})]}),0===ue.length?t("div",{className:"rounded-lg border-2 border-dashed border-teal-200 bg-teal-50/40 py-6 text-center",children:[e(S,{className:"mx-auto h-8 w-8 text-teal-300 mb-2"}),e("p",{className:"text-sm text-gray-400",children:"Không có hình ảnh — dùng cho Part 2 phases có tranh"})]}):e("div",{className:"space-y-3",children:ue.map((r,a)=>t("div",{className:"rounded-lg border border-gray-200 bg-gray-50 p-3 space-y-2",children:[t("div",{className:"flex items-center justify-between",children:[t("span",{className:"text-xs font-semibold text-gray-600",children:["Hình ",a+1]}),e(s,{type:"button",variant:"ghost",size:"icon",className:"h-6 w-6 text-red-400 hover:text-red-600 hover:bg-red-50",onClick:()=>(e=>{const t=ue.filter((t,r)=>r!==e);X.setValue("images",t,{shouldValidate:!0})})(a),children:e(T,{className:"h-3.5 w-3.5"})})]}),e(b,{id:`image-url-${a}`,label:"",accept:"image/*",value:r.url,onChange:e=>pe(a,"url",e),maxSize:10,placeholder:"Upload hình ảnh",autoUpload:!0,prefix:"speaking/phase-images"}),e(o,{value:r.label,onChange:e=>pe(a,"label",e.target.value),placeholder:"Nhãn hình (VD: Park, Bedroom, Library ...)",className:"border-gray-200 text-sm focus:border-teal-300"})]},r.id))})]})]}):t("div",{className:"space-y-3",children:[t("div",{className:"flex items-center gap-3 rounded-lg border border-gray-100 bg-gray-50 px-4 py-3",children:[e(D,{className:"h-4 w-4 text-teal-500 flex-shrink-0"}),t("div",{children:[e("p",{className:"text-xs text-gray-500",children:"Tên Partner"}),e("p",{className:"text-sm font-medium text-gray-800",children:$.partnerName||e("span",{className:"italic text-gray-400",children:"Chưa đặt tên"})})]})]}),$.images&&$.images.length>0&&t("div",{className:"rounded-lg border border-gray-100 bg-gray-50 px-4 py-3",children:[t("p",{className:"text-xs text-gray-500 mb-2",children:["Hình ảnh (",$.images.length,")"]}),e("div",{className:"flex flex-wrap gap-2",children:$.images.map(t=>e("span",{className:"inline-flex items-center rounded-full bg-teal-100 px-2 py-0.5 text-xs text-teal-700",children:t.label||t.id},t.id))})]})]})})]}),t(m,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-indigo-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r "+(ge?"from-orange-400 to-amber-400":"from-indigo-400 to-violet-400")}),e(g,{className:"pb-4",children:t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-9 w-9 items-center justify-center rounded-lg shadow-sm "+(ge?"bg-gradient-to-br from-orange-500 to-amber-500":"bg-gradient-to-br from-indigo-500 to-violet-600"),children:e(A,{className:"h-4 w-4 text-white"})}),t("div",{className:"flex-1",children:[t("div",{className:"flex items-center gap-2",children:[e(p,{className:"text-lg font-bold text-gray-900",children:"Nội dung câu hỏi"}),ge&&e(x,{className:"bg-orange-100 text-orange-700 border-orange-200 border text-xs font-semibold",children:"Câu hỏi mẫu (Example)"})]}),e("p",{className:"text-sm text-gray-500 mt-0.5",children:ge?"Partner trả lời — học sinh chỉ nghe, không ghi âm":"Student trả lời — học sinh ghi âm câu trả lời"})]})]})}),t(u,{className:"space-y-6",children:[t("div",{className:"rounded-xl border p-4 space-y-4 "+(ge?"border-orange-200 bg-orange-50/40":"border-indigo-200 bg-indigo-50/40"),children:[e("div",{className:"flex items-center justify-between",children:t("div",{className:"space-y-1",children:[e(l,{className:"text-base font-semibold text-gray-800",children:"Người trả lời"}),e("p",{className:"text-sm text-gray-500",children:"Ai sẽ trả lời câu hỏi này trong bài thi?"})]})}),t("div",{className:"flex gap-3",children:[t("button",{type:"button",onClick:()=>X.setValue("responder","PARTNER",{shouldValidate:!0}),className:"flex flex-1 items-center gap-3 rounded-xl border-2 p-3 transition-all "+(ge?"border-orange-400 bg-orange-50 shadow-sm":"border-gray-200 bg-white hover:border-gray-300"),children:[e("div",{className:"flex h-9 w-9 items-center justify-center rounded-full "+(ge?"bg-orange-500":"bg-gray-200"),children:e(D,{className:"h-4 w-4 "+(ge?"text-white":"text-gray-500")})}),t("div",{className:"text-left",children:[e("p",{className:"text-sm font-semibold "+(ge?"text-orange-800":"text-gray-700"),children:"Partner trả lời"}),e("p",{className:"text-xs "+(ge?"text-orange-600":"text-gray-500"),children:"Câu mẫu — is_example=true, points=0"})]})]}),t("button",{type:"button",onClick:()=>X.setValue("responder","STUDENT",{shouldValidate:!0}),className:"flex flex-1 items-center gap-3 rounded-xl border-2 p-3 transition-all "+(ge?"border-gray-200 bg-white hover:border-gray-300":"border-indigo-400 bg-indigo-50 shadow-sm"),children:[e("div",{className:"flex h-9 w-9 items-center justify-center rounded-full "+(ge?"bg-gray-200":"bg-indigo-500"),children:e(q,{className:"h-4 w-4 "+(ge?"text-gray-500":"text-white")})}),t("div",{className:"text-left",children:[e("p",{className:"text-sm font-semibold "+(ge?"text-gray-700":"text-indigo-800"),children:"Student trả lời"}),e("p",{className:"text-xs "+(ge?"text-gray-500":"text-indigo-600"),children:"Câu thực — học sinh ghi âm"})]})]})]}),X.hasError("responder")&&e("p",{className:"text-sm text-red-600",children:X.getFieldError("responder")})]}),t("div",{className:"w-full md:w-1/2",children:[e(h,{...X.register("points",{valueAsNumber:!0}),disabled:ge,error:X.hasError("points")?X.getFieldError("points"):void 0}),ge&&e("p",{className:"mt-1.5 text-xs text-orange-600",children:"Câu hỏi mẫu (Partner) luôn có 0 điểm"})]}),t("div",{className:"space-y-2",children:[t(l,{className:"flex items-center gap-2 text-sm font-semibold text-gray-700",children:[e(U,{className:"h-4 w-4 text-blue-500"}),"Audio Interlocutor",e("span",{className:"text-xs font-normal text-gray-400",children:"(tùy chọn)"})]}),e(b,{id:"interlocutor-audio-url",label:"",accept:"audio/*",value:ne||"",onChange:e=>X.setValue("interlocutorAudioUrl",e,{shouldValidate:!0}),maxSize:20,placeholder:"Upload audio câu hỏi của Interlocutor (MP3, WAV...)",autoUpload:!0,prefix:"speaking/interlocutor"}),t("div",{className:"flex items-start gap-2 rounded-lg border border-blue-100 bg-blue-50/60 px-3 py-2",children:[e(C,{className:"h-4 w-4 text-blue-500 mt-0.5 flex-shrink-0"}),e("p",{className:"text-xs text-blue-700",children:"Audio của Interlocutor (người hỏi). Phát trước khi Partner hoặc Student trả lời."})]})]}),t("div",{className:"space-y-2",children:[t(l,{className:"flex items-center gap-2 text-sm font-semibold text-gray-700",children:[e(U,{className:"h-4 w-4 text-orange-500"}),"Audio Partner",ge&&e("span",{className:"text-red-500",children:"*"}),!ge&&e("span",{className:"text-xs font-normal text-gray-400",children:"(tùy chọn)"})]}),e(b,{id:"partner-audio-url",label:"",accept:"audio/*",value:ie||"",onChange:e=>X.setValue("partnerAudioUrl",e,{shouldValidate:!0}),maxSize:20,placeholder:"Upload audio trả lời của Partner (MP3, WAV...)",autoUpload:!0,prefix:"speaking/partner"}),X.hasError("partnerAudioUrl")&&e("p",{className:"text-sm text-red-600",children:X.getFieldError("partnerAudioUrl")}),t("div",{className:"flex items-start gap-2 rounded-lg border border-orange-100 bg-orange-50/60 px-3 py-2",children:[e(C,{className:"h-4 w-4 text-orange-500 mt-0.5 flex-shrink-0"}),e("p",{className:"text-xs text-orange-700",children:ge?"Bắt buộc khi responder = Partner. Phát sau audio Interlocutor.":"Tùy chọn khi responder = Student — dùng cho Talk Together (Partner nói trước, Student trả lời sau)."})]})]}),t("div",{className:"space-y-2",children:[t(l,{className:"flex items-center gap-2 text-sm font-semibold text-gray-700",children:[e(k,{className:"h-4 w-4 text-gray-500"}),"Text câu hỏi (tùy chọn)"]}),e(d,{...X.register("questionText"),placeholder:"VD: What's your name? How old are you?",rows:2,className:"min-h-[60px] border-gray-200 focus:border-indigo-300 focus:ring-indigo-200 resize-none"}),e("p",{className:"text-xs text-gray-400",children:"Chỉ hiển thị cho admin/giáo viên khi review. Học sinh chỉ nghe audio."})]}),t("div",{className:"flex items-center justify-between rounded-lg border border-gray-200 bg-gray-50 px-4 py-3",children:[t("div",{className:"space-y-0.5",children:[e(l,{className:"text-sm font-semibold text-gray-700",children:"Câu hỏi dự phòng"}),e("p",{className:"text-xs text-gray-400",children:"Đánh dấu câu hỏi này là dự phòng (backup)"})]}),e(c,{checked:le,onCheckedChange:e=>X.setValue("isBackup",e,{shouldValidate:!0})})]})]})]}),t(m,{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(g,{className:"pb-4",children:t("div",{className:"flex items-center justify-between",children:[t("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(k,{className:"h-4 w-4 text-white"})}),t("div",{children:[t("div",{className:"flex items-center gap-2",children:[e(p,{className:"text-lg font-semibold text-gray-800",children:"Đáp án mong muốn"}),e("span",{className:"inline-flex items-center rounded-full border border-green-200 bg-green-50 px-2.5 py-0.5 text-xs font-medium text-green-600",children:"Không bắt buộc"})]}),e("p",{className:"text-sm text-gray-500 mt-0.5",children:ge?"Câu mẫu — đáp án dùng để ghi nhớ nội dung (không chấm điểm)":"Đáp án tham chiếu để giáo viên chấm thủ công"})]})]}),t(s,{type:"button",variant:"outline",size:"sm",onClick:()=>{X.setValue("expectedAnswers",[...oe,""],{shouldValidate:!1})},className:"gap-1.5 border-green-200 text-green-700 hover:bg-green-50",children:[e(P,{className:"h-4 w-4"}),"Thêm đáp án"]})]})}),t(u,{className:"space-y-3",children:[t("div",{className:"flex items-start gap-2 rounded-lg border border-blue-100 bg-blue-50/60 px-3 py-2.5",children:[e(C,{className:"h-4 w-4 text-blue-500 mt-0.5 flex-shrink-0"}),e("p",{className:"text-xs text-blue-700",children:ge?"Câu hỏi mẫu: có thể nhập nội dung Partner nói để ghi nhớ. Không dùng để chấm điểm.":"Giáo viên dùng các đáp án này làm tham chiếu khi chấm thủ công câu trả lời của học sinh."})]}),oe.map((r,a)=>t("div",{className:"flex items-start gap-2",children:[e("div",{className:"flex h-7 w-7 items-center justify-center rounded-full bg-green-100 text-xs font-semibold text-green-700 mt-1.5 flex-shrink-0",children:a+1}),e(d,{value:r||"",onChange:e=>((e,t)=>{const r=[...oe];r[e]=t,X.setValue("expectedAnswers",r,{shouldValidate:!0})})(a,e.target.value),placeholder:0===a?"VD: My name is Bingo. I am 8 years old.":"Thêm đáp án mẫu khác...",rows:2,className:"min-h-[60px] border-gray-200 bg-white transition-all focus:border-green-300 focus:ring-green-200 resize-none flex-1"}),oe.length>1&&e(s,{type:"button",variant:"ghost",size:"icon",className:"mt-1.5 h-7 w-7 text-red-400 hover:text-red-600 hover:bg-red-50 flex-shrink-0",onClick:()=>(e=>{const t=[...oe];t.splice(e,1),0===t.length&&t.push(""),X.setValue("expectedAnswers",t,{shouldValidate:!0})})(a),"aria-label":`Xóa đáp án ${a+1}`,children:e(T,{className:"h-4 w-4"})})]},a))]})]}),t(m,{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(g,{className:"pb-4",children:t("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(V,{className:"h-4 w-4 text-white"})}),t("div",{children:[t("div",{className:"flex items-center gap-2",children:[e(p,{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 thêm về câu trả lời kỳ vọng, dùng khi review kết quả."})]})]})}),e(u,{children:e(d,{value:de||"",onChange:e=>X.setValue("explanation",e.target.value,{shouldValidate:!0}),placeholder:"VD: Câu trả lời đúng: 'My name is...' vì câu hỏi đang hỏi về tên và tuổi.",rows:3,className:"min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none"})})]}),R&&R.length>0&&e("div",{className:"flex items-start gap-3 rounded-xl border border-red-200 bg-red-50 p-4",children:t("div",{className:"text-sm",children:[e("p",{className:"font-semibold text-red-800",children:"Cần sửa các lỗi sau:"}),e("ul",{className:"mt-1 list-inside list-disc text-red-700",children:R.map((t,r)=>e("li",{children:t},r))})]})})]})}
@@ -1,41 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Mic, Volume2, CheckCircle2, Play, Pause, MessageCircle, Users } from 'lucide-react';
4
- import { Button } from '../../../../components/ui/button';
5
- import { usePresignedFileUrl, useAudioPreview } from '../../../../shared/lib/hooks';
6
- // ─── Sub-component: Audio Player ─────────────────────────────────────────────
7
- function ConversationAudioPlayer({ audioUrl, label }) {
8
- const { previewUrl } = usePresignedFileUrl(audioUrl);
9
- const { isPlaying, currentTime, duration, progressPercentage, handlePlayPause, handleProgressClick, formatTime } = useAudioPreview(previewUrl);
10
- return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "text-xs font-medium text-gray-500", children: label }), _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" })] })] }));
11
- }
12
- // ─── Sub-component: Partner Avatar ───────────────────────────────────────────
13
- function PartnerAvatar({ avatarUrl, name }) {
14
- const { previewUrl } = usePresignedFileUrl(avatarUrl || '');
15
- return (_jsxs("div", { className: "flex items-center gap-2", children: [previewUrl ? (
16
- // eslint-disable-next-line @next/next/no-img-element
17
- _jsx("img", { src: previewUrl, alt: name || 'Partner', className: "h-10 w-10 rounded-full object-cover border-2 border-indigo-200" })) : (_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-indigo-100 border-2 border-indigo-200", children: _jsx(Users, { className: "h-5 w-5 text-indigo-500" }) })), name && _jsx("span", { className: "text-sm font-medium text-gray-700", children: name })] }));
18
- }
19
- // ─── Sub-component: Image Grid ────────────────────────────────────────────────
20
- function PreviewImageItem({ url, label }) {
21
- const { previewUrl } = usePresignedFileUrl(url);
22
- return (_jsxs("div", { className: "flex flex-col items-center gap-1", children: [_jsx("div", { className: "relative w-full aspect-square rounded-lg overflow-hidden border border-gray-200 bg-gray-50", children: previewUrl ? (
23
- // eslint-disable-next-line @next/next/no-img-element
24
- _jsx("img", { src: previewUrl, alt: label || 'Image', className: "w-full h-full object-cover" })) : (_jsx("div", { className: "w-full h-full flex items-center justify-center text-gray-300 text-xs", children: "\u0110ang t\u1EA3i..." })) }), label && _jsx("span", { className: "text-xs text-center text-gray-600 font-medium", children: label })] }));
25
- }
26
- export function SpeakingConversationPreviewClient({ questionData, isReviewMode = true, }) {
27
- const isPartnerQuestion = questionData.responder === 'PARTNER';
28
- const hasImages = questionData.images && questionData.images.length > 0;
29
- const gridCols = !hasImages || questionData.images.length <= 2
30
- ? 'grid-cols-2'
31
- : questionData.images.length === 3
32
- ? 'grid-cols-3'
33
- : questionData.images.length === 4
34
- ? 'grid-cols-4'
35
- : 'grid-cols-3';
36
- return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-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(MessageCircle, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "H\u1ED9i tho\u1EA1i Speaking (KFS)" })] }), _jsxs("div", { className: `inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold ${isPartnerQuestion
37
- ? 'bg-orange-100 text-orange-700 border border-orange-200'
38
- : 'bg-green-100 text-green-700 border border-green-200'}`, children: [_jsx(Mic, { className: "h-3 w-3" }), isPartnerQuestion ? 'Bạn thi (ví dụ)' : 'Học sinh trả lời'] })] }), _jsxs("div", { className: "p-5 space-y-5", children: [(questionData.partnerName || questionData.partnerAvatarUrl) && (_jsxs("div", { className: "flex items-center gap-2 rounded-lg bg-orange-50 border border-orange-100 px-4 py-2.5", children: [_jsx("span", { className: "text-xs font-medium text-orange-600", children: "Nh\u00E2n v\u1EADt b\u1EA1n thi:" }), _jsx(PartnerAvatar, { avatarUrl: questionData.partnerAvatarUrl, name: questionData.partnerName })] })), questionData.questionText && (_jsxs("div", { className: "rounded-lg bg-blue-50 border border-blue-200 px-4 py-3", children: [_jsx("p", { className: "text-xs font-medium text-blue-600 mb-1", children: "N\u1ED9i dung c\u00E2u h\u1ECFi:" }), _jsx("p", { className: "text-sm font-semibold text-gray-800", children: questionData.questionText })] })), _jsxs("div", { className: "space-y-3", children: [questionData.interlocutorAudioUrl && (_jsx(ConversationAudioPlayer, { audioUrl: questionData.interlocutorAudioUrl, label: "Audio ng\u01B0\u1EDDi h\u1ECFi (Interlocutor)" })), questionData.partnerAudioUrl && (_jsx(ConversationAudioPlayer, { audioUrl: questionData.partnerAudioUrl, label: "Audio b\u1EA1n thi (Partner)" }))] }), hasImages && (_jsxs("div", { children: [_jsx("p", { className: "text-xs font-medium text-gray-500 mb-2", children: "H\u00ECnh \u1EA3nh th\u1EA3o lu\u1EADn:" }), _jsx("div", { className: `grid ${gridCols} gap-3`, style: { maxWidth: '500px' }, children: questionData.images.map((img) => (_jsx(PreviewImageItem, { url: img.url, label: img.label }, img.id))) })] })), _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: isPartnerQuestion
39
- ? 'Câu hỏi ví dụ — Bạn thi sẽ trả lời, học sinh lắng nghe'
40
- : 'Học sinh sẽ nhấn nút ghi âm và nói câu trả lời tại đây' })] }), 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))) })] }))] })] })] }));
41
- }
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{Mic as i,Volume2 as t,CheckCircle2 as n,Play as l,Pause as a,MessageCircle as s,Users as d}from"lucide-react";import{Button as o}from"../../../../components/ui/button";import{usePresignedFileUrl as c,useAudioPreview as m}from"../../../../shared/lib/hooks";function g({audioUrl:i,label:n}){const{previewUrl:s}=c(i),{isPlaying:d,currentTime:g,duration:h,progressPercentage:u,handlePlayPause:x,handleProgressClick:b,formatTime:p}=m(s);return r("div",{className:"flex flex-col gap-1",children:[e("span",{className:"text-xs font-medium text-gray-500",children:n}),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(o,{variant:"outline",size:"icon",onClick:x,disabled:!s,className:"h-8 w-8 border-indigo-300 text-indigo-600 hover:bg-indigo-100",children:e(d?a:l,{className:"h-3.5 w-3.5"})}),r("div",{className:"text-muted-foreground min-w-[70px] text-xs",children:[p(g)," / ",p(h)]}),e("div",{className:"bg-indigo-200 relative h-1.5 flex-1 cursor-pointer rounded-full",onClick:b,children:e("div",{className:"bg-indigo-500 absolute top-0 left-0 h-full rounded-full transition-all",style:{width:`${u}%`}})}),e(t,{className:"h-4 w-4 text-indigo-400"})]})]})}function h({avatarUrl:i,name:t}){const{previewUrl:n}=c(i||"");return r("div",{className:"flex items-center gap-2",children:[n?e("img",{src:n,alt:t||"Partner",className:"h-10 w-10 rounded-full object-cover border-2 border-indigo-200"}):e("div",{className:"flex h-10 w-10 items-center justify-center rounded-full bg-indigo-100 border-2 border-indigo-200",children:e(d,{className:"h-5 w-5 text-indigo-500"})}),t&&e("span",{className:"text-sm font-medium text-gray-700",children:t})]})}function u({url:i,label:t}){const{previewUrl:n}=c(i);return r("div",{className:"flex flex-col items-center gap-1",children:[e("div",{className:"relative w-full aspect-square rounded-lg overflow-hidden border border-gray-200 bg-gray-50",children:n?e("img",{src:n,alt:t||"Image",className:"w-full h-full object-cover"}):e("div",{className:"w-full h-full flex items-center justify-center text-gray-300 text-xs",children:"Đang tải..."})}),t&&e("span",{className:"text-xs text-center text-gray-600 font-medium",children:t})]})}export function SpeakingConversationPreviewClient({questionData:t,isReviewMode:l=!0}){const a="PARTNER"===t.responder,d=t.images&&t.images.length>0,o=!d||t.images.length<=2?"grid-cols-2":3===t.images.length?"grid-cols-3":4===t.images.length?"grid-cols-4":"grid-cols-3";return r("div",{className:"space-y-4",children:[t.isBackup&&r("div",{className:"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm",children:[e("span",{children:"🔄"})," Câu hỏi dự phòng"]}),r("div",{className:"overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm",children:[r("div",{className:"flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-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(s,{className:"h-4 w-4"})}),e("span",{className:"text-sm font-medium text-gray-700",children:"Hội thoại Speaking (KFS)"})]}),r("div",{className:"inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold "+(a?"bg-orange-100 text-orange-700 border border-orange-200":"bg-green-100 text-green-700 border border-green-200"),children:[e(i,{className:"h-3 w-3"}),a?"Bạn thi (ví dụ)":"Học sinh trả lời"]})]}),r("div",{className:"p-5 space-y-5",children:[(t.partnerName||t.partnerAvatarUrl)&&r("div",{className:"flex items-center gap-2 rounded-lg bg-orange-50 border border-orange-100 px-4 py-2.5",children:[e("span",{className:"text-xs font-medium text-orange-600",children:"Nhân vật bạn thi:"}),e(h,{avatarUrl:t.partnerAvatarUrl,name:t.partnerName})]}),t.questionText&&r("div",{className:"rounded-lg bg-blue-50 border border-blue-200 px-4 py-3",children:[e("p",{className:"text-xs font-medium text-blue-600 mb-1",children:"Nội dung câu hỏi:"}),e("p",{className:"text-sm font-semibold text-gray-800",children:t.questionText})]}),r("div",{className:"space-y-3",children:[t.interlocutorAudioUrl&&e(g,{audioUrl:t.interlocutorAudioUrl,label:"Audio người hỏi (Interlocutor)"}),t.partnerAudioUrl&&e(g,{audioUrl:t.partnerAudioUrl,label:"Audio bạn thi (Partner)"})]}),d&&r("div",{children:[e("p",{className:"text-xs font-medium text-gray-500 mb-2",children:"Hình ảnh thảo luận:"}),e("div",{className:`grid ${o} gap-3`,style:{maxWidth:"500px"},children:t.images.map(r=>e(u,{url:r.url,label:r.label},r.id))})]}),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:a?"Câu hỏi ví dụ — Bạn thi sẽ trả lời, học sinh lắng nghe":"Học sinh sẽ nhấn nút ghi âm và nói câu trả lời tại đây"})]}),l&&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,t)=>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:t+1}),e("span",{className:"text-sm text-green-800",children:i})]},t))})]})]})]})]})}