@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,26 +1 @@
1
- export const transformListenAndSpeakImageGroup = (question) => {
2
- const contentData = question.content;
3
- const answerData = question.answer;
4
- const questionGroup = question.questionGroup;
5
- const expectedAnswers = answerData?.expectedAnswers || [];
6
- const apiContent = {
7
- audioUrl: contentData?.audioUrl || answerData?.audioUrl || '',
8
- activeImageId: contentData?.activeImageId || answerData?.activeImageId || '',
9
- isBackup: contentData?.isBackup || answerData?.isBackup || false,
10
- };
11
- const result = {
12
- apiContent,
13
- apiCorrectAnswer: {
14
- expectedAnswers: expectedAnswers,
15
- },
16
- };
17
- if (contentData?.groupImages) {
18
- result.sceneData = { images: contentData.groupImages };
19
- result.groupNumber = contentData.groupNumber || questionGroup?.groupNumber || 1;
20
- }
21
- else if (questionGroup?.payload?.images) {
22
- result.sceneData = { images: questionGroup.payload.images };
23
- result.groupNumber = questionGroup.groupNumber;
24
- }
25
- return result;
26
- };
1
+ export const transformListenAndSpeakImageGroup=e=>{const a=e.content,r=e.answer,o=e.questionGroup,s=r?.expectedAnswers||[],u={apiContent:{audioUrl:a?.audioUrl||r?.audioUrl||"",activeImageId:a?.activeImageId||r?.activeImageId||"",isBackup:a?.isBackup||r?.isBackup||!1},apiCorrectAnswer:{expectedAnswers:s}};return a?.groupImages?(u.sceneData={images:a.groupImages},u.groupNumber=a.groupNumber||o?.groupNumber||1):o?.payload?.images&&(u.sceneData={images:o.payload.images},u.groupNumber=o.groupNumber),u};
@@ -1,38 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Volume2, Mic, CheckCircle2, Play, Pause, Info, ArrowLeftRight } from 'lucide-react';
4
- import { Button } from '../../../../components/ui/button';
5
- import { usePresignedFileUrl, useAudioPreview } from '../../../../shared/lib/hooks';
6
- // ─── Sub-component: Custom Audio Player ──────────────────────────────────────
7
- function QuestionAudioPlayer({ audioUrl }) {
8
- const { previewUrl } = usePresignedFileUrl(audioUrl);
9
- const { isPlaying, currentTime, duration, progressPercentage, handlePlayPause, handleProgressClick, formatTime, } = useAudioPreview(previewUrl);
10
- return (_jsxs("div", { className: "flex items-center gap-3 rounded-lg bg-gradient-to-r from-indigo-50 to-purple-50 border border-indigo-200 px-4 py-2.5", children: [_jsx(Button, { variant: "outline", size: "icon", onClick: handlePlayPause, disabled: !previewUrl, className: "h-8 w-8 border-indigo-300 text-indigo-600 hover:bg-indigo-100", children: isPlaying ? _jsx(Pause, { className: "h-3.5 w-3.5" }) : _jsx(Play, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "text-muted-foreground min-w-[70px] text-xs", children: [formatTime(currentTime), " / ", formatTime(duration)] }), _jsx("div", { className: "bg-indigo-200 relative h-1.5 flex-1 cursor-pointer rounded-full", onClick: handleProgressClick, children: _jsx("div", { className: "bg-indigo-500 absolute top-0 left-0 h-full rounded-full transition-all", style: { width: `${progressPercentage}%` } }) }), _jsx(Volume2, { className: "h-4 w-4 text-indigo-400" })] }));
11
- }
12
- // ─── Sub-component: Presigned Image Preview ──────────────────────────────────
13
- function ImagePreview({ imageUrl, title }) {
14
- const { previewUrl, isLoading } = usePresignedFileUrl(imageUrl);
15
- if (isLoading) {
16
- return (_jsx("div", { className: "w-full aspect-[4/3] rounded-xl border-2 border-gray-200 bg-gray-100 animate-pulse flex items-center justify-center", children: _jsx("span", { className: "text-gray-400 text-sm", children: "\u0110ang t\u1EA3i..." }) }));
17
- }
18
- if (!previewUrl)
19
- return null;
20
- return (_jsx("img", { src: previewUrl, alt: title || 'Illustration', className: "w-full rounded-xl border-2 border-gray-200 object-cover shadow-sm" }));
21
- }
22
- // ─── Sub-component: Info Table ────────────────────────────────────────────────
23
- function InfoTable({ label, title, rows, headerColor, }) {
24
- return (_jsxs("div", { className: "rounded-xl overflow-hidden border border-gray-200 shadow-sm", children: [_jsx("div", { className: `${headerColor} text-white text-xs font-bold text-center py-1.5 tracking-widest uppercase`, children: label }), _jsx("div", { className: "bg-amber-50 text-center font-semibold text-sm py-2 border-b border-gray-200", children: title }), _jsx("table", { className: "w-full text-sm", children: _jsx("tbody", { children: rows.map((row, index) => (_jsxs("tr", { className: "border-b border-gray-100 last:border-0 bg-white", children: [_jsx("td", { className: "px-3 py-2 font-medium text-gray-700 w-1/2", children: row.key }), _jsx("td", { className: `px-3 py-2 w-1/2 ${row.value === '?' ? 'text-gray-400 font-bold text-base' : 'text-gray-800'}`, children: row.value })] }, index))) }) })] }));
25
- }
26
- // ─── Main Client Component ───────────────────────────────────────────────────
27
- export function ListenAndSpeakInfoExchangeClient({ questionData, isReviewMode = false, viewMode = 'ANSWER', }) {
28
- const { audioUrl, title, imageUrl, fields, expectedAnswers = [], explanation } = questionData;
29
- const qData = questionData;
30
- // Use dual fields if available, fall back to legacy single fields
31
- const candidateRows = (qData.candidateFields || fields || [])
32
- .map((f) => ({ key: f.key, value: f.answer }));
33
- const examinerRows = (qData.examinerFields || [])
34
- .map((f) => ({ key: f.key, value: f.answer }));
35
- return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsx("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-indigo-50 to-blue-50 px-5 py-3", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-indigo-500 to-blue-600 text-white shadow-sm", children: _jsx(ArrowLeftRight, { className: "h-4 w-4" }) }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["Nghe v\u00E0 n\u00F3i (Trao \u0111\u1ED5i th\u00F4ng tin \u2014 ", viewMode === 'ANSWER' ? 'Trả lời' : 'Đặt câu hỏi', ")"] })] }) }), _jsxs("div", { className: "p-5 space-y-5", children: [audioUrl ? (_jsx(QuestionAudioPlayer, { audioUrl: audioUrl })) : (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3", children: [_jsx(Volume2, { className: "h-4 w-4 text-gray-400" }), _jsx("span", { className: "text-xs text-gray-400", children: "Ch\u01B0a c\u00F3 audio c\u00E2u h\u1ECFi" })] })), _jsxs("div", { className: "flex flex-col lg:flex-row gap-4", children: [imageUrl && (_jsx("div", { className: "lg:w-1/2", children: _jsx(ImagePreview, { imageUrl: imageUrl, title: title }) })), _jsxs("div", { className: "flex-1 space-y-4", children: [_jsx(InfoTable, { label: "CANDIDATE'S COPY", title: title, rows: candidateRows, headerColor: "bg-teal-600" }), _jsx(InfoTable, { label: "EXAMINER'S COPY", title: title, rows: examinerRows, headerColor: "bg-amber-600" })] })] }), _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: viewMode === 'ANSWER'
36
- ? 'Học sinh sẽ nhấn nút ghi âm và nói câu trả lời tại đây'
37
- : 'Học sinh sẽ nhấn nút ghi âm và đặt câu hỏi tại đây' })] }), isReviewMode && 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: viewMode === 'ANSWER' ? 'Các đáp án mong muốn' : 'Các câu hỏi mong muốn' })] }), _jsx("div", { className: "space-y-2", children: expectedAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-center gap-2.5 rounded-lg bg-white/70 border border-green-100 px-3 py-2", children: [_jsx("span", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-green-100 text-green-700 text-xs font-bold", children: index + 1 }), _jsx("span", { className: "text-sm text-green-800", children: answer })] }, index))) })] })), isReviewMode && explanation && (_jsxs("div", { className: "rounded-xl border border-indigo-200 bg-indigo-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx(Info, { className: "h-4 w-4 text-indigo-600" }), _jsx("p", { className: "text-sm font-semibold text-indigo-800", children: "Gi\u1EA3i th\u00EDch" })] }), _jsx("p", { className: "text-sm text-indigo-700 leading-relaxed", children: explanation })] }))] })] }) }));
38
- }
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{Volume2 as i,Mic as t,CheckCircle2 as a,Play as l,Pause as s,Info as d,ArrowLeftRight as n}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:t}){const{previewUrl:a}=c(t),{isPlaying:d,currentTime:n,duration:g,progressPercentage:h,handlePlayPause:x,handleProgressClick:b,formatTime:u}=m(a);return r("div",{className:"flex items-center gap-3 rounded-lg bg-gradient-to-r from-indigo-50 to-purple-50 border border-indigo-200 px-4 py-2.5",children:[e(o,{variant:"outline",size:"icon",onClick:x,disabled:!a,className:"h-8 w-8 border-indigo-300 text-indigo-600 hover:bg-indigo-100",children:e(d?s:l,{className:"h-3.5 w-3.5"})}),r("div",{className:"text-muted-foreground min-w-[70px] text-xs",children:[u(n)," / ",u(g)]}),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:`${h}%`}})}),e(i,{className:"h-4 w-4 text-indigo-400"})]})}function h({imageUrl:r,title:i}){const{previewUrl:t,isLoading:a}=c(r);return a?e("div",{className:"w-full aspect-[4/3] rounded-xl border-2 border-gray-200 bg-gray-100 animate-pulse flex items-center justify-center",children:e("span",{className:"text-gray-400 text-sm",children:"Đang tải..."})}):t?e("img",{src:t,alt:i||"Illustration",className:"w-full rounded-xl border-2 border-gray-200 object-cover shadow-sm"}):null}function x({label:i,title:t,rows:a,headerColor:l}){return r("div",{className:"rounded-xl overflow-hidden border border-gray-200 shadow-sm",children:[e("div",{className:`${l} text-white text-xs font-bold text-center py-1.5 tracking-widest uppercase`,children:i}),e("div",{className:"bg-amber-50 text-center font-semibold text-sm py-2 border-b border-gray-200",children:t}),e("table",{className:"w-full text-sm",children:e("tbody",{children:a.map((i,t)=>r("tr",{className:"border-b border-gray-100 last:border-0 bg-white",children:[e("td",{className:"px-3 py-2 font-medium text-gray-700 w-1/2",children:i.key}),e("td",{className:"px-3 py-2 w-1/2 "+("?"===i.value?"text-gray-400 font-bold text-base":"text-gray-800"),children:i.value})]},t))})})]})}export function ListenAndSpeakInfoExchangeClient({questionData:l,isReviewMode:s=!1,viewMode:o="ANSWER"}){const{audioUrl:c,title:m,imageUrl:b,fields:u,expectedAnswers:p=[],explanation:f}=l,N=l,y=(N.candidateFields||u||[]).map(e=>({key:e.key,value:e.answer})),v=(N.examinerFields||[]).map(e=>({key:e.key,value:e.answer}));return e("div",{className:"space-y-4",children:r("div",{className:"overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm",children:[e("div",{className:"flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-indigo-50 to-blue-50 px-5 py-3",children:r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-indigo-500 to-blue-600 text-white shadow-sm",children:e(n,{className:"h-4 w-4"})}),r("span",{className:"text-sm font-medium text-gray-700",children:["Nghe và nói (Trao đổi thông tin — ","ANSWER"===o?"Trả lời":"Đặt câu hỏi",")"]})]})}),r("div",{className:"p-5 space-y-5",children:[c?e(g,{audioUrl:c}):r("div",{className:"flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3",children:[e(i,{className:"h-4 w-4 text-gray-400"}),e("span",{className:"text-xs text-gray-400",children:"Chưa có audio câu hỏi"})]}),r("div",{className:"flex flex-col lg:flex-row gap-4",children:[b&&e("div",{className:"lg:w-1/2",children:e(h,{imageUrl:b,title:m})}),r("div",{className:"flex-1 space-y-4",children:[e(x,{label:"CANDIDATE'S COPY",title:m,rows:y,headerColor:"bg-teal-600"}),e(x,{label:"EXAMINER'S COPY",title:m,rows:v,headerColor:"bg-amber-600"})]})]}),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(t,{className:"w-4 h-4 text-indigo-400"})}),e("p",{className:"text-xs text-gray-400",children:"ANSWER"===o?"Học sinh sẽ nhấn nút ghi âm và nói câu trả lời tại đây":"Học sinh sẽ nhấn nút ghi âm và đặt câu hỏi tại đây"})]}),s&&p.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(a,{className:"h-4 w-4 text-green-600"}),e("h4",{className:"text-sm font-semibold text-green-800",children:"ANSWER"===o?"Các đáp án mong muốn":"Các câu hỏi mong muốn"})]}),e("div",{className:"space-y-2",children:p.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))})]}),s&&f&&r("div",{className:"rounded-xl border border-indigo-200 bg-indigo-50 p-4",children:[r("div",{className:"flex items-center gap-2 mb-2",children:[e(d,{className:"h-4 w-4 text-indigo-600"}),e("p",{className:"text-sm font-semibold text-indigo-800",children:"Giải thích"})]}),e("p",{className:"text-sm text-indigo-700 leading-relaxed",children:f})]})]})]})})}
@@ -1,385 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useEffect, useMemo, useRef, useState } from 'react';
4
- import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
5
- import { Label } from '../../../../components/ui/label';
6
- import { Button } from '../../../../components/ui/button';
7
- import { Input } from '../../../../components/ui/input';
8
- import { Textarea } from '../../../../components/ui/textarea';
9
- import { Form, FormControl, FormField, FormItem, FormMessage } from '../../../../components/ui/form';
10
- import { FileUpload } from '../../../../components/ui/file-upload';
11
- import { PointsInput } from '../../../../components/ui/points-input';
12
- // Stores & hooks
13
- import { useInfoExchangeGroup } from '../../_shared/hooks/useInfoExchangeGroup';
14
- import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
15
- import { listenAndSpeakInfoExchangeSchema, } from '../../../../shared/lib/validation/listen-and-speak-info-exchange.validation';
16
- // Utilities & Icons
17
- import { Plus, Trash2, Mic, Volume2, Table2, MessageSquareText, ImageIcon, BookOpen, Eye, Pencil, Info, ListChecks } from 'lucide-react';
18
- import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
19
- import { useDebouncedCallback } from '../../../../shared/lib/hooks/useDebouncedCallback';
20
- import { CreatorGuide } from '../../components/CreatorGuide';
21
- import { ListenAndSpeakInfoExchangeClient } from './ListenAndSpeakInfoExchangeClient';
22
- // ─── Sub-component: Image preview with presigned URL ─────────────────────────
23
- function ImagePreview({ imageUrl, label }) {
24
- const { previewUrl } = usePresignedFileUrl(imageUrl);
25
- if (!previewUrl)
26
- return null;
27
- return (_jsx("div", { className: "mt-2 rounded-xl border border-gray-200 overflow-hidden bg-white shadow-sm", children: _jsx("img", { src: previewUrl, alt: label, className: "max-h-80 w-full object-contain bg-gray-50 p-2" }) }));
28
- }
29
- const DEFAULT_FIELDS = [
30
- { key: '', answer: '' },
31
- ];
32
- export function ListenAndSpeakInfoExchangeCreator({ questionIndex, initialData, onChange, validationRef, onUnsavedChangesChange, isFirstInGroup: isFirstInGroupProp, partId, groups, questionIndexInPart, viewMode = 'ANSWER', }) {
33
- const isGrouped = Boolean(groups && groups.length > 0);
34
- const isFirstInGroup = useMemo(() => {
35
- if (isFirstInGroupProp !== undefined)
36
- return isFirstInGroupProp;
37
- if (!groups || !groups.length || questionIndexInPart === undefined)
38
- return true;
39
- let cumulative = 0;
40
- for (const group of groups) {
41
- if (questionIndexInPart === cumulative)
42
- return true;
43
- cumulative += group.questionCount;
44
- }
45
- return false;
46
- }, [isFirstInGroupProp, groups, questionIndexInPart]);
47
- // ---------------------------------------------------------------
48
- // 1. Group level state (Title, Image, CandidateFields, ExaminerFields) via shared store
49
- // ---------------------------------------------------------------
50
- const currentGroupNumber = useMemo(() => {
51
- if (groups && groups.length > 0) {
52
- if (questionIndexInPart === undefined)
53
- return 1;
54
- let cumulative = 0;
55
- for (let i = 0; i < groups.length; i++) {
56
- cumulative += groups[i].questionCount;
57
- if (questionIndexInPart < cumulative)
58
- return i + 1;
59
- }
60
- return 1;
61
- }
62
- // Fallback: use groupNumber from initialData when groups prop is not yet available
63
- const apiData = initialData;
64
- return apiData?.groupNumber ?? 1;
65
- }, [groups, questionIndexInPart, initialData]);
66
- const { title, imageUrl, fields, candidateFields, examinerFields, updateGroupData } = useInfoExchangeGroup(partId, currentGroupNumber);
67
- // Initialize group data from initialData on first question (also re-run when initialData changes)
68
- const initialDataRef = useRef(null);
69
- useEffect(() => {
70
- if (isFirstInGroup && initialData) {
71
- // Skip if initialData hasn't meaningfully changed
72
- const initDataKey = JSON.stringify({
73
- title: initialData.title,
74
- imageUrl: initialData.imageUrl,
75
- candidateFields: initialData.candidateFields,
76
- examinerFields: initialData.examinerFields,
77
- fields: initialData.fields,
78
- });
79
- if (initialDataRef.current === initDataKey)
80
- return;
81
- initialDataRef.current = initDataKey;
82
- const initCandidateFields = initialData.candidateFields && initialData.candidateFields.length > 0
83
- ? initialData.candidateFields
84
- : (initialData.fields && initialData.fields.length > 0 ? initialData.fields : DEFAULT_FIELDS);
85
- const initExaminerFields = initialData.examinerFields && initialData.examinerFields.length > 0
86
- ? initialData.examinerFields
87
- : DEFAULT_FIELDS;
88
- updateGroupData({
89
- title: initialData.title || '',
90
- imageUrl: initialData.imageUrl || '',
91
- fields: initCandidateFields, // backward compat
92
- candidateFields: initCandidateFields,
93
- examinerFields: initExaminerFields,
94
- });
95
- }
96
- }, [isFirstInGroup, initialData, updateGroupData]);
97
- // Use group store data, fallback to initialData
98
- const groupTitle = title || initialData?.title || '';
99
- const groupImageUrl = imageUrl || initialData?.imageUrl || '';
100
- const groupCandidateFields = candidateFields && candidateFields.length > 0
101
- ? candidateFields
102
- : (initialData?.candidateFields && initialData.candidateFields.length > 0
103
- ? initialData.candidateFields
104
- : (initialData?.fields && initialData.fields.length > 0 ? initialData.fields : DEFAULT_FIELDS));
105
- const groupExaminerFields = examinerFields && examinerFields.length > 0
106
- ? examinerFields
107
- : (initialData?.examinerFields && initialData.examinerFields.length > 0
108
- ? initialData.examinerFields
109
- : DEFAULT_FIELDS);
110
- // ---------------------------------------------------------------
111
- // 2. Per-question form data (Audio, ExpectedAnswers, Points, Explanation)
112
- // ---------------------------------------------------------------
113
- const form = useReactHookForm({
114
- zodSchema: listenAndSpeakInfoExchangeSchema,
115
- defaultValues: {
116
- title: groupTitle,
117
- imageUrl: groupImageUrl,
118
- fields: groupCandidateFields, // backward compat
119
- candidateFields: groupCandidateFields,
120
- examinerFields: groupExaminerFields,
121
- audioUrl: initialData?.audioUrl || '',
122
- expectedAnswers: initialData?.expectedAnswers?.length
123
- ? initialData.expectedAnswers
124
- : [''],
125
- points: initialData?.points ?? 1,
126
- explanation: initialData?.explanation || '',
127
- },
128
- });
129
- const { control, watch, setValue, trigger, clearErrors } = form;
130
- const watchedAudioUrl = watch('audioUrl');
131
- const watchedExpectedAnswers = watch('expectedAnswers');
132
- const watchedPoints = watch('points');
133
- const watchedExplanation = watch('explanation');
134
- // ---------------------------------------------------------------
135
- // Refs to prevent infinite loops (echo-detection pattern)
136
- // ---------------------------------------------------------------
137
- const previousInitialDataRef = useRef(initialData);
138
- const isInitializingRef = useRef(false);
139
- const onChangeRef = useRef(onChange);
140
- const previousPayloadRef = useRef('');
141
- useEffect(() => {
142
- onChangeRef.current = onChange;
143
- }, [onChange]);
144
- // Notify parent onChange (debounced)
145
- const debouncedOnChange = useDebouncedCallback((data) => {
146
- onChangeRef.current?.(data);
147
- }, 300);
148
- // Track unsaved changes
149
- useEffect(() => {
150
- onUnsavedChangesChange?.(form.formState.isDirty);
151
- }, [form.formState.isDirty, onUnsavedChangesChange]);
152
- // ---------------------------------------------------------------
153
- // Sync form values if initialData props change
154
- // ---------------------------------------------------------------
155
- useEffect(() => {
156
- const normalizedInitialData = {
157
- title: groupTitle,
158
- imageUrl: groupImageUrl,
159
- fields: groupCandidateFields,
160
- candidateFields: groupCandidateFields,
161
- examinerFields: groupExaminerFields,
162
- audioUrl: initialData?.audioUrl || '',
163
- expectedAnswers: initialData?.expectedAnswers?.length
164
- ? initialData.expectedAnswers
165
- : [''],
166
- points: initialData?.points ?? 1,
167
- explanation: initialData?.explanation || '',
168
- };
169
- const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
170
- if (!initialDataChanged)
171
- return;
172
- previousInitialDataRef.current = initialData;
173
- const currentFormData = {
174
- audioUrl: watchedAudioUrl,
175
- expectedAnswers: watchedExpectedAnswers,
176
- points: watchedPoints,
177
- explanation: watchedExplanation,
178
- };
179
- const isEchoFromOnChange = currentFormData.points === normalizedInitialData.points &&
180
- currentFormData.explanation === normalizedInitialData.explanation &&
181
- currentFormData.audioUrl === normalizedInitialData.audioUrl &&
182
- JSON.stringify(currentFormData.expectedAnswers) === JSON.stringify(normalizedInitialData.expectedAnswers);
183
- const wasJustSaved = initialData?.isCompleted === true;
184
- if (isEchoFromOnChange && wasJustSaved)
185
- return;
186
- const shouldReset = !isEchoFromOnChange &&
187
- (normalizedInitialData.audioUrl !== currentFormData.audioUrl ||
188
- JSON.stringify(normalizedInitialData.expectedAnswers) !== JSON.stringify(currentFormData.expectedAnswers));
189
- if (shouldReset) {
190
- isInitializingRef.current = true;
191
- form.reset(normalizedInitialData);
192
- setTimeout(() => {
193
- isInitializingRef.current = false;
194
- }, 150);
195
- }
196
- }, [initialData, form, watchedAudioUrl, watchedExpectedAnswers, watchedExplanation, watchedPoints, groupTitle, groupImageUrl, groupCandidateFields, groupExaminerFields]);
197
- // ---------------------------------------------------------------
198
- // Fire onChange when form values change
199
- // ---------------------------------------------------------------
200
- const watchedExpectedAnswersString = JSON.stringify(watchedExpectedAnswers);
201
- useEffect(() => {
202
- if (isInitializingRef.current) {
203
- const currentPayload = JSON.stringify({
204
- title: groupTitle,
205
- imageUrl: groupImageUrl,
206
- fields: groupCandidateFields,
207
- candidateFields: groupCandidateFields,
208
- examinerFields: groupExaminerFields,
209
- audioUrl: watchedAudioUrl,
210
- expectedAnswers: watchedExpectedAnswers,
211
- points: watchedPoints,
212
- explanation: watchedExplanation,
213
- groupNumber: isGrouped ? currentGroupNumber : undefined,
214
- });
215
- previousPayloadRef.current = currentPayload;
216
- return;
217
- }
218
- const payload = {
219
- title: groupTitle,
220
- imageUrl: groupImageUrl,
221
- fields: groupCandidateFields,
222
- candidateFields: groupCandidateFields,
223
- examinerFields: groupExaminerFields,
224
- audioUrl: watchedAudioUrl,
225
- expectedAnswers: watchedExpectedAnswers,
226
- points: watchedPoints,
227
- explanation: watchedExplanation,
228
- groupNumber: isGrouped ? currentGroupNumber : undefined,
229
- };
230
- const payloadString = JSON.stringify(payload);
231
- if (previousPayloadRef.current !== payloadString) {
232
- previousPayloadRef.current = payloadString;
233
- debouncedOnChange(payload);
234
- }
235
- }, [
236
- watchedAudioUrl,
237
- watchedExpectedAnswersString,
238
- watchedPoints,
239
- watchedExplanation,
240
- groupTitle,
241
- groupImageUrl,
242
- groupCandidateFields,
243
- groupExaminerFields,
244
- isGrouped,
245
- currentGroupNumber,
246
- debouncedOnChange,
247
- ]);
248
- // ---------------------------------------------------------------
249
- // Validation ref
250
- // ---------------------------------------------------------------
251
- useEffect(() => {
252
- if (validationRef) {
253
- validationRef.current = {
254
- triggerValidation: async () => {
255
- // Sync group data into form for validation
256
- setValue('title', groupTitle);
257
- setValue('imageUrl', groupImageUrl);
258
- setValue('fields', groupCandidateFields);
259
- setValue('candidateFields', groupCandidateFields);
260
- setValue('examinerFields', groupExaminerFields);
261
- const isValid = await trigger();
262
- return isValid;
263
- },
264
- getFormData: () => ({
265
- ...form.getValues(),
266
- title: groupTitle,
267
- imageUrl: groupImageUrl,
268
- fields: groupCandidateFields,
269
- candidateFields: groupCandidateFields,
270
- examinerFields: groupExaminerFields,
271
- groupNumber: isGrouped ? currentGroupNumber : undefined,
272
- }),
273
- };
274
- }
275
- }, [trigger, validationRef, form, groupTitle, groupImageUrl, groupCandidateFields, groupExaminerFields, isGrouped, currentGroupNumber, setValue]);
276
- // Track isDirty
277
- useEffect(() => {
278
- onUnsavedChangesChange?.(form.formState.isDirty);
279
- }, [form.formState.isDirty, onUnsavedChangesChange]);
280
- // ---------------------------------------------------------------
281
- // Handlers for Candidate Fields table
282
- // ---------------------------------------------------------------
283
- const handleUpdateCandidateField = (index, part, value) => {
284
- const newFields = groupCandidateFields.map((f, i) => i === index ? { ...f, [part]: value } : f);
285
- updateGroupData({ candidateFields: newFields, fields: newFields });
286
- setValue('candidateFields', newFields, { shouldDirty: true });
287
- setValue('fields', newFields, { shouldDirty: true });
288
- };
289
- const handleAddCandidateField = () => {
290
- const newFields = [...groupCandidateFields, { key: '', answer: '' }];
291
- updateGroupData({ candidateFields: newFields, fields: newFields });
292
- setValue('candidateFields', newFields, { shouldDirty: true });
293
- setValue('fields', newFields, { shouldDirty: true });
294
- };
295
- const handleRemoveCandidateField = (index) => {
296
- const newFields = groupCandidateFields.filter((_, i) => i !== index);
297
- updateGroupData({ candidateFields: newFields, fields: newFields });
298
- setValue('candidateFields', newFields, { shouldDirty: true });
299
- setValue('fields', newFields, { shouldDirty: true });
300
- };
301
- // ---------------------------------------------------------------
302
- // Handlers for Examiner Fields table
303
- // ---------------------------------------------------------------
304
- const handleUpdateExaminerField = (index, part, value) => {
305
- const newFields = groupExaminerFields.map((f, i) => i === index ? { ...f, [part]: value } : f);
306
- updateGroupData({ examinerFields: newFields });
307
- setValue('examinerFields', newFields, { shouldDirty: true });
308
- };
309
- const handleAddExaminerField = () => {
310
- const newFields = [...groupExaminerFields, { key: '', answer: '' }];
311
- updateGroupData({ examinerFields: newFields });
312
- setValue('examinerFields', newFields, { shouldDirty: true });
313
- };
314
- const handleRemoveExaminerField = (index) => {
315
- const newFields = groupExaminerFields.filter((_, i) => i !== index);
316
- updateGroupData({ examinerFields: newFields });
317
- setValue('examinerFields', newFields, { shouldDirty: true });
318
- };
319
- // ---------------------------------------------------------------
320
- // Handlers for Expected Answers
321
- // ---------------------------------------------------------------
322
- const addExpectedAnswer = () => {
323
- setValue('expectedAnswers', [...watchedExpectedAnswers, ''], { shouldValidate: true, shouldDirty: true });
324
- };
325
- const handleExpectedAnswerChange = (index, value) => {
326
- const newAnswers = [...watchedExpectedAnswers];
327
- newAnswers[index] = value;
328
- setValue('expectedAnswers', newAnswers, { shouldValidate: true, shouldDirty: true });
329
- };
330
- const removeExpectedAnswer = (index) => {
331
- if (watchedExpectedAnswers.length > 1) {
332
- const newAnswers = watchedExpectedAnswers.filter((_, i) => i !== index);
333
- setValue('expectedAnswers', newAnswers, { shouldValidate: true, shouldDirty: true });
334
- }
335
- };
336
- // ---------------------------------------------------------------
337
- // Display helpers
338
- // ---------------------------------------------------------------
339
- const displayQuestionNumber = typeof questionIndex === 'number' && !isNaN(questionIndex)
340
- ? questionIndex + 1
341
- : (typeof questionIndexInPart === 'number' && !isNaN(questionIndexInPart)
342
- ? questionIndexInPart + 1
343
- : '?');
344
- const modeLabel = viewMode === 'ANSWER' ? 'Part 2a (Examiner hỏi - Candidate trả lời)' : 'Part 2b (Candidate hỏi - Examiner trả lời)';
345
- const audioLabel = viewMode === 'ANSWER' ? 'Audio câu hỏi của Examiner' : 'Audio câu trả lời của Examiner';
346
- const answerLabel = viewMode === 'ANSWER' ? 'Đáp án mong muốn (Candidate nói gì)' : 'Câu hỏi mong muốn (Candidate hỏi gì)';
347
- const answerPlaceholder = viewMode === 'ANSWER' ? "VD: It's in King Road." : "VD: Where is Alex's house?";
348
- // Color scheme based on viewMode
349
- const modeColors = viewMode === 'ANSWER'
350
- ? { badge: 'bg-blue-50 text-blue-700 border-blue-200', icon: '📣', gradientFrom: 'from-blue-500', gradientTo: 'to-cyan-500' }
351
- : { badge: 'bg-purple-50 text-purple-700 border-purple-200', icon: '🎤', gradientFrom: 'from-purple-500', gradientTo: 'to-pink-500' };
352
- const [isClientMode, setIsClientMode] = useState(false);
353
- // ─── Preview Mode ────────────────────────────────────────────────
354
- if (isClientMode) {
355
- return (_jsxs("div", { className: "space-y-6", children: [_jsx(Card, { children: _jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsx(CardTitle, { className: "text-lg font-bold", children: "\uD83D\uDC41\uFE0F Ch\u1EBF \u0111\u1ED9 xem tr\u01B0\u1EDBc (Preview)" }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(false), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), "Quay l\u1EA1i ch\u1EC9nh s\u1EEDa"] })] }) }) }), _jsx(ListenAndSpeakInfoExchangeClient, { questionData: {
356
- audioUrl: watchedAudioUrl,
357
- title: groupTitle,
358
- imageUrl: groupImageUrl,
359
- fields: groupCandidateFields,
360
- candidateFields: groupCandidateFields,
361
- examinerFields: groupExaminerFields,
362
- expectedAnswers: watchedExpectedAnswers,
363
- points: watchedPoints,
364
- explanation: watchedExplanation,
365
- }, viewMode: viewMode })] }));
366
- }
367
- return (_jsxs("div", { className: "space-y-6", children: [_jsx(CreatorGuide, { colorScheme: "blue", steps: [
368
- { text: 'Nhập tiêu đề chung cho bảng thông tin (ví dụ: Shopping List, Holiday Plans)' },
369
- { text: 'Upload hình ảnh minh họa nếu có' },
370
- { text: 'Thiết lập bảng thông tin Candidate và Examiner — mỗi bảng có các cặp key/value' },
371
- { text: 'Upload audio câu hỏi cho từng câu' },
372
- { text: 'Chọn chế độ: ANSWER (trả lời) hoặc ASK (đặt câu hỏi)' },
373
- { text: 'Nhập đáp án/câu hỏi mong muốn và giải thích' },
374
- ], tips: ['Trường có giá trị "?" là trường học sinh cần tìm câu trả lời'] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-blue-500 via-indigo-500 to-purple-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 shadow-sm", children: _jsx(ListChecks, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsxs(CardTitle, { className: "text-lg font-bold text-gray-900", children: ["Trao \u0111\u1ED5i th\u00F4ng tin \u2014 C\u00E2u #", displayQuestionNumber] }), _jsx("p", { className: "text-sm text-gray-500", children: modeLabel })] })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setIsClientMode(true), disabled: !watchedAudioUrl, className: "gap-2 border-blue-200 text-blue-600 hover:bg-blue-50 disabled:opacity-50", children: [_jsx(Eye, { className: "h-4 w-4" }), "Preview"] })] }) })] }), _jsxs("div", { className: `inline-flex items-center gap-2 px-3 py-1.5 rounded-full text-xs font-semibold border ${modeColors.badge}`, children: [modeColors.icon, " ", viewMode === 'ANSWER' ? 'Examiner → Candidate' : 'Candidate → Examiner', _jsx("span", { className: "text-gray-400", children: "|" }), " ", modeLabel] }), isFirstInGroup ? (_jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-teal-100/50", children: [_jsx("div", { className: "h-1.5 bg-gradient-to-r from-teal-500 via-emerald-500 to-cyan-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-teal-500 to-emerald-600 shadow-md", children: _jsx(Table2, { className: "h-5 w-5 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: "Th\u00F4ng tin chung" }), isGrouped && (_jsxs("span", { className: "inline-flex items-center rounded-full border border-teal-300 bg-teal-100 px-2.5 py-0.5 text-xs font-semibold text-teal-700", children: ["Nh\u00F3m ", currentGroupNumber] }))] }), _jsx("p", { className: "text-sm text-gray-500", children: "D\u00F9ng chung cho t\u1EA5t c\u1EA3 c\u00E2u h\u1ECFi trong nh\u00F3m \u2014 Ti\u00EAu \u0111\u1EC1, h\u00ECnh \u1EA3nh, v\u00E0 2 b\u1EA3ng th\u00F4ng tin" })] })] }) }), _jsxs(CardContent, { className: "space-y-5", children: [_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-800", children: [_jsx(MessageSquareText, { className: "h-4 w-4 text-teal-500" }), "Ti\u00EAu \u0111\u1EC1 ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Input, { value: groupTitle, onChange: (e) => {
375
- updateGroupData({ title: e.target.value });
376
- setValue('title', e.target.value, { shouldDirty: true });
377
- }, placeholder: "VD: Katy's house", className: "border-gray-200 bg-white transition-all focus:border-teal-300 focus:ring-teal-200" })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-800", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-teal-500" }), "H\u00ECnh \u1EA3nh minh ho\u1EA1"] }), _jsx(FileUpload, { accept: "image/*", value: groupImageUrl, onChange: (url) => {
378
- updateGroupData({ imageUrl: url });
379
- setValue('imageUrl', url, { shouldDirty: true });
380
- }, placeholder: "Upload h\u00ECnh \u1EA3nh", label: "", autoUpload: true, prefix: "info-exchange" }), groupImageUrl && (_jsx(ImagePreview, { imageUrl: groupImageUrl, label: "H\u00ECnh \u1EA3nh minh ho\u1EA1" }))] }), _jsxs("div", { className: "space-y-5", children: [_jsx("div", { className: "space-y-3", children: _jsxs("div", { className: "rounded-xl border border-teal-200 overflow-hidden shadow-sm bg-white", children: [_jsx("div", { className: "bg-gradient-to-r from-teal-600 to-emerald-600 px-4 py-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2.5", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-white/20 backdrop-blur-sm", children: _jsx(Table2, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsx("h4", { className: "text-sm font-bold text-white tracking-wide", children: "Candidate's Copy" }), _jsx("p", { className: "text-[10px] text-teal-100 font-medium", children: "B\u1EA3ng th\u00F4ng tin h\u1ECDc sinh nh\u00ECn th\u1EA5y" })] })] }), _jsx("div", { className: "flex items-center gap-2", children: _jsxs("span", { className: "inline-flex items-center rounded-full bg-white/20 backdrop-blur-sm px-2 py-0.5 text-[10px] font-bold text-white", children: [groupCandidateFields.length, " d\u00F2ng"] }) })] }) }), _jsx("div", { className: "bg-teal-50/60 border-b border-teal-100 px-4 py-2", children: _jsx("p", { className: "text-xs font-semibold text-teal-800 text-center", children: groupTitle || '(Chưa nhập tiêu đề)' }) }), _jsxs("div", { className: "grid grid-cols-[40px_1fr_1fr_36px] bg-gradient-to-r from-teal-50/80 to-emerald-50/80 border-b border-teal-100", children: [_jsx("div", { className: "px-2 py-2 text-[10px] font-bold text-teal-600 uppercase tracking-wider text-center", children: "#" }), _jsx("div", { className: "px-2 py-2 text-[10px] font-bold text-teal-600 uppercase tracking-wider", children: "Key" }), _jsx("div", { className: "px-2 py-2 text-[10px] font-bold text-teal-600 uppercase tracking-wider", children: "Value" }), _jsx("div", {})] }), _jsx("div", { className: "divide-y divide-gray-100", children: groupCandidateFields.map((field, index) => (_jsxs("div", { className: "grid grid-cols-[40px_1fr_1fr_36px] items-center bg-white hover:bg-teal-50/30 transition-colors group/row", children: [_jsx("div", { className: "px-2 py-2 flex justify-center", children: _jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-teal-100 text-teal-700 font-bold text-[11px]", children: index + 1 }) }), _jsx("div", { className: "px-1.5 py-1.5", children: _jsx(Input, { value: field.key, onChange: (e) => handleUpdateCandidateField(index, 'key', e.target.value), placeholder: "VD: Where", className: "h-8 text-sm border-gray-200 bg-white focus:border-teal-400 focus:ring-teal-200 transition-all" }) }), _jsx("div", { className: "px-1.5 py-1.5", children: _jsx(Input, { value: field.answer, onChange: (e) => handleUpdateCandidateField(index, 'answer', e.target.value), placeholder: "VD: King Road", className: "h-8 text-sm border-gray-200 bg-white focus:border-teal-400 focus:ring-teal-200 transition-all" }) }), _jsx("div", { className: "px-1 py-1.5 flex justify-center", children: _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => handleRemoveCandidateField(index), disabled: groupCandidateFields.length <= 1, className: "h-7 w-7 text-gray-300 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/row:opacity-100 transition-all disabled:opacity-0 disabled:group-hover/row:opacity-30", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }) })] }, index))) }), _jsx("div", { className: "border-t border-dashed border-teal-200 bg-teal-50/30", children: _jsxs("button", { type: "button", onClick: handleAddCandidateField, className: "w-full flex items-center justify-center gap-1.5 py-2.5 text-xs font-semibold text-teal-600 hover:text-teal-700 hover:bg-teal-50 transition-colors cursor-pointer", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm d\u00F2ng"] }) })] }) }), _jsx("div", { className: "space-y-3", children: _jsxs("div", { className: "rounded-xl border border-amber-200 overflow-hidden shadow-sm bg-white", children: [_jsx("div", { className: "bg-gradient-to-r from-amber-600 to-orange-600 px-4 py-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2.5", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-white/20 backdrop-blur-sm", children: _jsx(Table2, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsx("h4", { className: "text-sm font-bold text-white tracking-wide", children: "Examiner's Copy" }), _jsx("p", { className: "text-[10px] text-amber-100 font-medium", children: "B\u1EA3ng th\u00F4ng tin gi\u00E1m kh\u1EA3o nh\u00ECn th\u1EA5y" })] })] }), _jsx("div", { className: "flex items-center gap-2", children: _jsxs("span", { className: "inline-flex items-center rounded-full bg-white/20 backdrop-blur-sm px-2 py-0.5 text-[10px] font-bold text-white", children: [groupExaminerFields.length, " d\u00F2ng"] }) })] }) }), _jsx("div", { className: "bg-amber-50/60 border-b border-amber-100 px-4 py-2", children: _jsx("p", { className: "text-xs font-semibold text-amber-800 text-center", children: groupTitle || '(Chưa nhập tiêu đề)' }) }), _jsxs("div", { className: "grid grid-cols-[40px_1fr_1fr_36px] bg-gradient-to-r from-amber-50/80 to-orange-50/80 border-b border-amber-100", children: [_jsx("div", { className: "px-2 py-2 text-[10px] font-bold text-amber-600 uppercase tracking-wider text-center", children: "#" }), _jsx("div", { className: "px-2 py-2 text-[10px] font-bold text-amber-600 uppercase tracking-wider", children: "Key" }), _jsx("div", { className: "px-2 py-2 text-[10px] font-bold text-amber-600 uppercase tracking-wider", children: "Value" }), _jsx("div", {})] }), _jsx("div", { className: "divide-y divide-gray-100", children: groupExaminerFields.map((field, index) => (_jsxs("div", { className: "grid grid-cols-[40px_1fr_1fr_36px] items-center bg-white hover:bg-amber-50/30 transition-colors group/row", children: [_jsx("div", { className: "px-2 py-2 flex justify-center", children: _jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-amber-100 text-amber-700 font-bold text-[11px]", children: index + 1 }) }), _jsx("div", { className: "px-1.5 py-1.5", children: _jsx(Input, { value: field.key, onChange: (e) => handleUpdateExaminerField(index, 'key', e.target.value), placeholder: "VD: Where", className: "h-8 text-sm border-gray-200 bg-white focus:border-amber-400 focus:ring-amber-200 transition-all" }) }), _jsx("div", { className: "px-1.5 py-1.5", children: _jsx(Input, { value: field.answer, onChange: (e) => handleUpdateExaminerField(index, 'answer', e.target.value), placeholder: "VD: King Road", className: "h-8 text-sm border-gray-200 bg-white focus:border-amber-400 focus:ring-amber-200 transition-all" }) }), _jsx("div", { className: "px-1 py-1.5 flex justify-center", children: _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => handleRemoveExaminerField(index), disabled: groupExaminerFields.length <= 1, className: "h-7 w-7 text-gray-300 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/row:opacity-100 transition-all disabled:opacity-0 disabled:group-hover/row:opacity-30", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }) })] }, index))) }), _jsx("div", { className: "border-t border-dashed border-amber-200 bg-amber-50/30", children: _jsxs("button", { type: "button", onClick: handleAddExaminerField, className: "w-full flex items-center justify-center gap-1.5 py-2.5 text-xs font-semibold text-amber-600 hover:text-amber-700 hover:bg-amber-50 transition-colors cursor-pointer", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm d\u00F2ng"] }) })] }) })] })] })] })) : (
381
- /* Read-only view of the shared group data for non-first questions */
382
- _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow shadow-teal-100/30", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-teal-400 via-emerald-400 to-cyan-400" }), _jsx(CardHeader, { className: "pb-3", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-teal-100 shadow-sm", children: _jsx(Table2, { className: "h-4 w-4 text-teal-600" }) }), _jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-base font-semibold text-gray-800", children: "Th\u00F4ng tin chung" }), _jsxs("span", { className: "inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-500", children: ["Nh\u00F3m ", currentGroupNumber, " \u2014 Ch\u1EC9 \u0111\u1ECDc"] })] }), _jsx("p", { className: "text-xs text-gray-400 mt-0.5", children: "\u0110\u01B0\u1EE3c qu\u1EA3n l\u00FD b\u1EDFi c\u00E2u h\u1ECFi \u0111\u1EA7u ti\u00EAn trong nh\u00F3m" })] })] }) }), _jsx(CardContent, { className: "pt-0", children: _jsxs("div", { className: "space-y-3", children: [groupTitle && (_jsxs("div", { className: "flex items-center gap-2 px-3 py-2 rounded-lg bg-teal-50/50 border border-teal-100", children: [_jsx(MessageSquareText, { className: "h-4 w-4 text-teal-500 shrink-0" }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: groupTitle })] })), groupImageUrl && (_jsx(ImagePreview, { imageUrl: groupImageUrl, label: groupTitle || 'Hình ảnh minh hoạ' })), _jsxs("div", { className: "space-y-4", children: [groupCandidateFields.length > 0 && (_jsxs("div", { className: "rounded-xl overflow-hidden border border-teal-200 shadow-sm", children: [_jsx("div", { className: "bg-gradient-to-r from-teal-600 to-emerald-600 px-3 py-2.5", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded bg-white/20", children: _jsx(Table2, { className: "h-3 w-3 text-white" }) }), _jsx("span", { className: "text-xs font-bold text-white tracking-wide", children: "Candidate's Copy" })] }), _jsxs("span", { className: "text-[10px] font-semibold text-teal-100 bg-white/15 rounded-full px-1.5 py-0.5", children: [groupCandidateFields.length, " d\u00F2ng"] })] }) }), _jsx("div", { className: "bg-teal-50/60 text-center font-semibold text-xs py-2 border-b border-teal-100 text-teal-800", children: groupTitle || '(Chưa nhập tiêu đề)' }), _jsxs("div", { className: "grid grid-cols-2 bg-teal-50/40 border-b border-teal-100", children: [_jsx("div", { className: "px-3 py-1.5 text-[10px] font-bold text-teal-600 uppercase tracking-wider", children: "Key" }), _jsx("div", { className: "px-3 py-1.5 text-[10px] font-bold text-teal-600 uppercase tracking-wider", children: "Value" })] }), _jsx("div", { className: "divide-y divide-gray-100", children: groupCandidateFields.map((row, index) => (_jsxs("div", { className: "grid grid-cols-2 bg-white hover:bg-teal-50/20 transition-colors", children: [_jsx("div", { className: "px-3 py-2 text-sm font-medium text-gray-700", children: row.key || _jsx("span", { className: "text-gray-300 italic", children: '(Chưa nhập)' }) }), _jsx("div", { className: "px-3 py-2 text-sm text-gray-800", children: row.answer || _jsx("span", { className: "text-gray-300", children: '—' }) })] }, index))) })] })), groupExaminerFields.length > 0 && (_jsxs("div", { className: "rounded-xl overflow-hidden border border-amber-200 shadow-sm", children: [_jsx("div", { className: "bg-gradient-to-r from-amber-600 to-orange-600 px-3 py-2.5", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded bg-white/20", children: _jsx(Table2, { className: "h-3 w-3 text-white" }) }), _jsx("span", { className: "text-xs font-bold text-white tracking-wide", children: "Examiner's Copy" })] }), _jsxs("span", { className: "text-[10px] font-semibold text-amber-100 bg-white/15 rounded-full px-1.5 py-0.5", children: [groupExaminerFields.length, " d\u00F2ng"] })] }) }), _jsx("div", { className: "bg-amber-50/60 text-center font-semibold text-xs py-2 border-b border-amber-100 text-amber-800", children: groupTitle || '(Chưa nhập tiêu đề)' }), _jsxs("div", { className: "grid grid-cols-2 bg-amber-50/40 border-b border-amber-100", children: [_jsx("div", { className: "px-3 py-1.5 text-[10px] font-bold text-amber-600 uppercase tracking-wider", children: "Key" }), _jsx("div", { className: "px-3 py-1.5 text-[10px] font-bold text-amber-600 uppercase tracking-wider", children: "Value" })] }), _jsx("div", { className: "divide-y divide-gray-100", children: groupExaminerFields.map((row, index) => (_jsxs("div", { className: "grid grid-cols-2 bg-white hover:bg-amber-50/20 transition-colors", children: [_jsx("div", { className: "px-3 py-2 text-sm font-medium text-gray-700", children: row.key || _jsx("span", { className: "text-gray-300 italic", children: '(Chưa nhập)' }) }), _jsx("div", { className: "px-3 py-2 text-sm text-gray-800", children: row.answer || _jsx("span", { className: "text-gray-300", children: '—' }) })] }, index))) })] }))] }), groupCandidateFields.length === 0 && groupExaminerFields.length === 0 && (_jsx("div", { className: "text-center py-4 text-sm text-gray-400 italic", children: "Ch\u01B0a c\u00F3 d\u1EEF li\u1EC7u b\u1EA3ng th\u00F4ng tin" }))] }) })] })), _jsxs(Form, { ...form, children: [_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 from-indigo-500 via-purple-500 to-pink-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-indigo-500 to-purple-600 shadow-sm", children: _jsx(Mic, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsxs(CardTitle, { className: "text-lg font-bold text-gray-900", children: ["C\u00E2u h\u1ECFi #", displayQuestionNumber] }), _jsx("p", { className: "text-sm text-gray-500", children: viewMode === 'ANSWER'
383
- ? 'Học sinh nghe câu hỏi của Examiner và trả lời'
384
- : 'Học sinh đặt câu hỏi cho Examiner' })] })] }) }), _jsxs(CardContent, { className: "space-y-6", children: [_jsx("div", { className: "rounded-xl border border-gray-200 bg-gray-50 p-4", children: _jsx("div", { className: "flex flex-col md:flex-row gap-6", children: _jsx("div", { className: "flex-1", children: _jsx(PointsInput, { ...form.register('points', { valueAsNumber: true }), error: form.formState.errors?.points?.message }) }) }) }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-800", children: [_jsx(Volume2, { className: "h-4 w-4 text-indigo-500" }), audioLabel, " ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FormField, { control: control, name: "audioUrl", render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormControl, { children: _jsx(FileUpload, { id: `audio-info-exchange-${displayQuestionNumber}`, label: "", accept: "audio/*", value: field.value, onChange: (url) => { field.onChange(url); clearErrors('audioUrl'); }, maxSize: 10, placeholder: "T\u1EA3i l\u00EAn file \u00E2m thanh (mp3, wav...)", autoUpload: true, prefix: "info-exchange" }) }), _jsx(FormMessage, {})] })) })] })] })] }), _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 shrink-0 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: [_jsx(CardTitle, { className: "text-lg font-bold text-gray-900", children: "\u0110\u00E1p \u00E1n mong mu\u1ED1n" }), _jsxs("p", { className: "text-sm text-gray-500", children: [watchedExpectedAnswers.filter(a => a.trim()).length, " \u0111\u00E1p \u00E1n \u0111\u00E3 nh\u1EADp"] })] })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addExpectedAnswer, className: "gap-2 border-green-200 text-green-600 hover:bg-green-50", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm \u0111\u00E1p \u00E1n"] })] }) }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-blue-100 bg-blue-50/60 px-3 py-2 text-xs text-blue-700", children: [_jsx(Info, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }), _jsx("span", { children: "Ch\u1EA5m th\u1EE7 c\u00F4ng: gi\u00E1o vi\u00EAn tham kh\u1EA3o \u0111\u00E1p \u00E1n \u0111\u1EC3 ch\u1EA5m \u00B7 Ch\u1EA5m t\u1EF1 \u0111\u1ED9ng: AI so s\u00E1nh gi\u1ECDng n\u00F3i h\u1ECDc sinh v\u1EDBi \u0111\u00E1p \u00E1n." })] }), _jsx(FormField, { control: control, name: "expectedAnswers", render: () => (_jsxs(FormItem, { children: [_jsx("div", { className: "space-y-3", children: watchedExpectedAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-start gap-3 group/answer", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-green-100 text-green-700 text-sm font-bold mt-1", children: index + 1 }), _jsxs("div", { className: "flex-1", children: [_jsx(Input, { placeholder: answerPlaceholder, value: answer || '', onChange: (e) => handleExpectedAnswerChange(index, e.target.value), className: "border-gray-200 focus:border-green-300 focus:ring-green-200" }), form.formState.errors?.expectedAnswers?.[index]?.message && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: form.formState.errors.expectedAnswers[index]?.message }))] }), watchedExpectedAnswers.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeExpectedAnswer(index), className: "mt-0.5 h-8 w-8 rounded-full text-gray-400 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/answer:opacity-100 transition-opacity", "aria-label": `Xóa đáp án ${index + 1}`, children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }, index))) }), form.formState.errors?.expectedAnswers?.message && (_jsx(FormMessage, {}))] })) })] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-amber-400 to-orange-400" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm", children: _jsx(BookOpen, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch" }), _jsx("span", { className: "inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600", children: "Kh\u00F4ng b\u1EAFt bu\u1ED9c" })] }), _jsx("p", { className: "text-sm text-gray-500 mt-0.5", children: "Gi\u1EA3i th\u00EDch cho \u0111\u00E1p \u00E1n \u0111\u00FAng \u2014 hi\u1EC3n th\u1ECB sau khi h\u1ECDc sinh ho\u00E0n th\u00E0nh c\u00E2u h\u1ECFi" })] })] }) }), _jsx(CardContent, { children: _jsx(Textarea, { ...form.register('explanation'), placeholder: "VD: C\u00E2u tr\u1EA3 l\u1EDDi \u0111\u00FAng l\u00E0... v\u00EC...", className: "min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none", rows: 3 }) })] })] })] }));
385
- }
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useEffect as r,useMemo as a,useRef as i,useState as l}from"react";import{Card as s,CardContent as n,CardHeader as d,CardTitle as c}from"../../../../components/ui/card";import{Label as o}from"../../../../components/ui/label";import{Button as m}from"../../../../components/ui/button";import{Input as h}from"../../../../components/ui/input";import{Textarea as g}from"../../../../components/ui/textarea";import{Form as p,FormControl as x,FormField as b,FormItem as u,FormMessage as f}from"../../../../components/ui/form";import{FileUpload as N}from"../../../../components/ui/file-upload";import{PointsInput as v}from"../../../../components/ui/points-input";import{useInfoExchangeGroup as y}from"../../_shared/hooks/useInfoExchangeGroup";import{usePresignedFileUrl as w}from"../../../../shared/lib/hooks";import{listenAndSpeakInfoExchangeSchema as k}from"../../../../shared/lib/validation/listen-and-speak-info-exchange.validation";import{Plus as C,Trash2 as F,Mic as U,Volume2 as A,Table2 as S,MessageSquareText as D,ImageIcon as j,BookOpen as E,Eye as V,Pencil as T,Info as R,ListChecks as _}from"lucide-react";import{useReactHookForm as W}from"../../../../shared/lib/hooks/useReactHookForm";import{useDebouncedCallback as I}from"../../../../shared/lib/hooks/useDebouncedCallback";import{CreatorGuide as J}from"../../components/CreatorGuide";import{ListenAndSpeakInfoExchangeClient as K}from"./ListenAndSpeakInfoExchangeClient";function O({imageUrl:t,label:r}){const{previewUrl:a}=w(t);return a?e("div",{className:"mt-2 rounded-xl border border-gray-200 overflow-hidden bg-white shadow-sm",children:e("img",{src:a,alt:r,className:"max-h-80 w-full object-contain bg-gray-50 p-2"})}):null}const z=[{key:"",answer:""}];export function ListenAndSpeakInfoExchangeCreator({questionIndex:w,initialData:H,onChange:q,validationRef:G,onUnsavedChangesChange:P,isFirstInGroup:B,partId:L,groups:$,questionIndexInPart:M,viewMode:Q="ANSWER"}){const X=Boolean($&&$.length>0),Y=a(()=>{if(void 0!==B)return B;if(!$||!$.length||void 0===M)return!0;let e=0;for(const t of $){if(M===e)return!0;e+=t.questionCount}return!1},[B,$,M]),Z=a(()=>{if($&&$.length>0){if(void 0===M)return 1;let e=0;for(let t=0;t<$.length;t++)if(e+=$[t].questionCount,M<e)return t+1;return 1}const e=H;return e?.groupNumber??1},[$,M,H]),{title:ee,imageUrl:te,fields:re,candidateFields:ae,examinerFields:ie,updateGroupData:le}=y(L,Z),se=i(null);r(()=>{if(Y&&H){const e=JSON.stringify({title:H.title,imageUrl:H.imageUrl,candidateFields:H.candidateFields,examinerFields:H.examinerFields,fields:H.fields});if(se.current===e)return;se.current=e;const t=H.candidateFields&&H.candidateFields.length>0?H.candidateFields:H.fields&&H.fields.length>0?H.fields:z,r=H.examinerFields&&H.examinerFields.length>0?H.examinerFields:z;le({title:H.title||"",imageUrl:H.imageUrl||"",fields:t,candidateFields:t,examinerFields:r})}},[Y,H,le]);const ne=ee||H?.title||"",de=te||H?.imageUrl||"",ce=ae&&ae.length>0?ae:H?.candidateFields&&H.candidateFields.length>0?H.candidateFields:H?.fields&&H.fields.length>0?H.fields:z,oe=ie&&ie.length>0?ie:H?.examinerFields&&H.examinerFields.length>0?H.examinerFields:z,me=W({zodSchema:k,defaultValues:{title:ne,imageUrl:de,fields:ce,candidateFields:ce,examinerFields:oe,audioUrl:H?.audioUrl||"",expectedAnswers:H?.expectedAnswers?.length?H.expectedAnswers:[""],points:H?.points??1,explanation:H?.explanation||""}}),{control:he,watch:ge,setValue:pe,trigger:xe,clearErrors:be}=me,ue=ge("audioUrl"),fe=ge("expectedAnswers"),Ne=ge("points"),ve=ge("explanation"),ye=i(H),we=i(!1),ke=i(q),Ce=i("");r(()=>{ke.current=q},[q]);const Fe=I(e=>{ke.current?.(e)},300);r(()=>{P?.(me.formState.isDirty)},[me.formState.isDirty,P]),r(()=>{const e={title:ne,imageUrl:de,fields:ce,candidateFields:ce,examinerFields:oe,audioUrl:H?.audioUrl||"",expectedAnswers:H?.expectedAnswers?.length?H.expectedAnswers:[""],points:H?.points??1,explanation:H?.explanation||""};if(!(JSON.stringify(ye.current)!==JSON.stringify(H)))return;ye.current=H;const t={audioUrl:ue,expectedAnswers:fe,points:Ne,explanation:ve},r=t.points===e.points&&t.explanation===e.explanation&&t.audioUrl===e.audioUrl&&JSON.stringify(t.expectedAnswers)===JSON.stringify(e.expectedAnswers);if(r&&!0===H?.isCompleted)return;!r&&(e.audioUrl!==t.audioUrl||JSON.stringify(e.expectedAnswers)!==JSON.stringify(t.expectedAnswers))&&(we.current=!0,me.reset(e),setTimeout(()=>{we.current=!1},150))},[H,me,ue,fe,ve,Ne,ne,de,ce,oe]);const Ue=JSON.stringify(fe);r(()=>{if(we.current){const e=JSON.stringify({title:ne,imageUrl:de,fields:ce,candidateFields:ce,examinerFields:oe,audioUrl:ue,expectedAnswers:fe,points:Ne,explanation:ve,groupNumber:X?Z:void 0});return void(Ce.current=e)}const e={title:ne,imageUrl:de,fields:ce,candidateFields:ce,examinerFields:oe,audioUrl:ue,expectedAnswers:fe,points:Ne,explanation:ve,groupNumber:X?Z:void 0},t=JSON.stringify(e);Ce.current!==t&&(Ce.current=t,Fe(e))},[ue,Ue,Ne,ve,ne,de,ce,oe,X,Z,Fe]),r(()=>{G&&(G.current={triggerValidation:async()=>{pe("title",ne),pe("imageUrl",de),pe("fields",ce),pe("candidateFields",ce),pe("examinerFields",oe);return await xe()},getFormData:()=>({...me.getValues(),title:ne,imageUrl:de,fields:ce,candidateFields:ce,examinerFields:oe,groupNumber:X?Z:void 0})})},[xe,G,me,ne,de,ce,oe,X,Z,pe]),r(()=>{P?.(me.formState.isDirty)},[me.formState.isDirty,P]);const Ae=(e,t,r)=>{const a=ce.map((a,i)=>i===e?{...a,[t]:r}:a);le({candidateFields:a,fields:a}),pe("candidateFields",a,{shouldDirty:!0}),pe("fields",a,{shouldDirty:!0})},Se=(e,t,r)=>{const a=oe.map((a,i)=>i===e?{...a,[t]:r}:a);le({examinerFields:a}),pe("examinerFields",a,{shouldDirty:!0})},De="number"!=typeof w||isNaN(w)?"number"!=typeof M||isNaN(M)?"?":M+1:w+1,je="ANSWER"===Q?"Part 2a (Examiner hỏi - Candidate trả lời)":"Part 2b (Candidate hỏi - Examiner trả lời)",Ee="ANSWER"===Q?"Audio câu hỏi của Examiner":"Audio câu trả lời của Examiner",Ve="ANSWER"===Q?"VD: It's in King Road.":"VD: Where is Alex's house?",Te="ANSWER"===Q?{badge:"bg-blue-50 text-blue-700 border-blue-200",icon:"📣",gradientFrom:"from-blue-500",gradientTo:"to-cyan-500"}:{badge:"bg-purple-50 text-purple-700 border-purple-200",icon:"🎤",gradientFrom:"from-purple-500",gradientTo:"to-pink-500"},[Re,_e]=l(!1);return t("div",Re?{className:"space-y-6",children:[e(s,{children:e(d,{children:t("div",{className:"flex items-center justify-between",children:[e(c,{className:"text-lg font-bold",children:"👁️ Chế độ xem trước (Preview)"}),t(m,{variant:"outline",size:"sm",onClick:()=>_e(!1),children:[e(T,{className:"mr-2 h-4 w-4"}),"Quay lại chỉnh sửa"]})]})})}),e(K,{questionData:{audioUrl:ue,title:ne,imageUrl:de,fields:ce,candidateFields:ce,examinerFields:oe,expectedAnswers:fe,points:Ne,explanation:ve},viewMode:Q})]}:{className:"space-y-6",children:[e(J,{colorScheme:"blue",steps:[{text:"Nhập tiêu đề chung cho bảng thông tin (ví dụ: Shopping List, Holiday Plans)"},{text:"Upload hình ảnh minh họa nếu có"},{text:"Thiết lập bảng thông tin Candidate và Examiner — mỗi bảng có các cặp key/value"},{text:"Upload audio câu hỏi cho từng câu"},{text:"Chọn chế độ: ANSWER (trả lời) hoặc ASK (đặt câu hỏi)"},{text:"Nhập đáp án/câu hỏi mong muốn và giải thích"}],tips:['Trường có giá trị "?" là trường học sinh cần tìm câu trả lời']}),t(s,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-blue-500 via-indigo-500 to-purple-500"}),e(d,{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 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 shadow-sm",children:e(_,{className:"h-4 w-4 text-white"})}),t("div",{children:[t(c,{className:"text-lg font-bold text-gray-900",children:["Trao đổi thông tin — Câu #",De]}),e("p",{className:"text-sm text-gray-500",children:je})]})]}),t(m,{type:"button",variant:"outline",size:"sm",onClick:()=>_e(!0),disabled:!ue,className:"gap-2 border-blue-200 text-blue-600 hover:bg-blue-50 disabled:opacity-50",children:[e(V,{className:"h-4 w-4"}),"Preview"]})]})})]}),t("div",{className:`inline-flex items-center gap-2 px-3 py-1.5 rounded-full text-xs font-semibold border ${Te.badge}`,children:[Te.icon," ","ANSWER"===Q?"Examiner → Candidate":"Candidate → Examiner",e("span",{className:"text-gray-400",children:"|"})," ",je]}),t(s,Y?{className:"overflow-hidden border-0 bg-white shadow-lg shadow-teal-100/50",children:[e("div",{className:"h-1.5 bg-gradient-to-r from-teal-500 via-emerald-500 to-cyan-500"}),e(d,{className:"pb-4",children:t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-teal-500 to-emerald-600 shadow-md",children:e(S,{className:"h-5 w-5 text-white"})}),t("div",{className:"flex-1",children:[t("div",{className:"flex items-center gap-2",children:[e(c,{className:"text-lg font-bold text-gray-900",children:"Thông tin chung"}),X&&t("span",{className:"inline-flex items-center rounded-full border border-teal-300 bg-teal-100 px-2.5 py-0.5 text-xs font-semibold text-teal-700",children:["Nhóm ",Z]})]}),e("p",{className:"text-sm text-gray-500",children:"Dùng chung cho tất cả câu hỏi trong nhóm — Tiêu đề, hình ảnh, và 2 bảng thông tin"})]})]})}),t(n,{className:"space-y-5",children:[t("div",{className:"space-y-2",children:[t(o,{className:"flex items-center gap-2 text-sm font-semibold text-gray-800",children:[e(D,{className:"h-4 w-4 text-teal-500"}),"Tiêu đề ",e("span",{className:"text-red-500",children:"*"})]}),e(h,{value:ne,onChange:e=>{le({title:e.target.value}),pe("title",e.target.value,{shouldDirty:!0})},placeholder:"VD: Katy's house",className:"border-gray-200 bg-white transition-all focus:border-teal-300 focus:ring-teal-200"})]}),t("div",{className:"space-y-2",children:[t(o,{className:"flex items-center gap-2 text-sm font-semibold text-gray-800",children:[e(j,{className:"h-4 w-4 text-teal-500"}),"Hình ảnh minh hoạ"]}),e(N,{accept:"image/*",value:de,onChange:e=>{le({imageUrl:e}),pe("imageUrl",e,{shouldDirty:!0})},placeholder:"Upload hình ảnh",label:"",autoUpload:!0,prefix:"info-exchange"}),de&&e(O,{imageUrl:de,label:"Hình ảnh minh hoạ"})]}),t("div",{className:"space-y-5",children:[e("div",{className:"space-y-3",children:t("div",{className:"rounded-xl border border-teal-200 overflow-hidden shadow-sm bg-white",children:[e("div",{className:"bg-gradient-to-r from-teal-600 to-emerald-600 px-4 py-3",children:t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-2.5",children:[e("div",{className:"flex h-7 w-7 items-center justify-center rounded-lg bg-white/20 backdrop-blur-sm",children:e(S,{className:"h-4 w-4 text-white"})}),t("div",{children:[e("h4",{className:"text-sm font-bold text-white tracking-wide",children:"Candidate's Copy"}),e("p",{className:"text-[10px] text-teal-100 font-medium",children:"Bảng thông tin học sinh nhìn thấy"})]})]}),e("div",{className:"flex items-center gap-2",children:t("span",{className:"inline-flex items-center rounded-full bg-white/20 backdrop-blur-sm px-2 py-0.5 text-[10px] font-bold text-white",children:[ce.length," dòng"]})})]})}),e("div",{className:"bg-teal-50/60 border-b border-teal-100 px-4 py-2",children:e("p",{className:"text-xs font-semibold text-teal-800 text-center",children:ne||"(Chưa nhập tiêu đề)"})}),t("div",{className:"grid grid-cols-[40px_1fr_1fr_36px] bg-gradient-to-r from-teal-50/80 to-emerald-50/80 border-b border-teal-100",children:[e("div",{className:"px-2 py-2 text-[10px] font-bold text-teal-600 uppercase tracking-wider text-center",children:"#"}),e("div",{className:"px-2 py-2 text-[10px] font-bold text-teal-600 uppercase tracking-wider",children:"Key"}),e("div",{className:"px-2 py-2 text-[10px] font-bold text-teal-600 uppercase tracking-wider",children:"Value"}),e("div",{})]}),e("div",{className:"divide-y divide-gray-100",children:ce.map((r,a)=>t("div",{className:"grid grid-cols-[40px_1fr_1fr_36px] items-center bg-white hover:bg-teal-50/30 transition-colors group/row",children:[e("div",{className:"px-2 py-2 flex justify-center",children:e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-teal-100 text-teal-700 font-bold text-[11px]",children:a+1})}),e("div",{className:"px-1.5 py-1.5",children:e(h,{value:r.key,onChange:e=>Ae(a,"key",e.target.value),placeholder:"VD: Where",className:"h-8 text-sm border-gray-200 bg-white focus:border-teal-400 focus:ring-teal-200 transition-all"})}),e("div",{className:"px-1.5 py-1.5",children:e(h,{value:r.answer,onChange:e=>Ae(a,"answer",e.target.value),placeholder:"VD: King Road",className:"h-8 text-sm border-gray-200 bg-white focus:border-teal-400 focus:ring-teal-200 transition-all"})}),e("div",{className:"px-1 py-1.5 flex justify-center",children:e(m,{type:"button",variant:"ghost",size:"icon",onClick:()=>(e=>{const t=ce.filter((t,r)=>r!==e);le({candidateFields:t,fields:t}),pe("candidateFields",t,{shouldDirty:!0}),pe("fields",t,{shouldDirty:!0})})(a),disabled:ce.length<=1,className:"h-7 w-7 text-gray-300 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/row:opacity-100 transition-all disabled:opacity-0 disabled:group-hover/row:opacity-30",children:e(F,{className:"h-3.5 w-3.5"})})})]},a))}),e("div",{className:"border-t border-dashed border-teal-200 bg-teal-50/30",children:t("button",{type:"button",onClick:()=>{const e=[...ce,{key:"",answer:""}];le({candidateFields:e,fields:e}),pe("candidateFields",e,{shouldDirty:!0}),pe("fields",e,{shouldDirty:!0})},className:"w-full flex items-center justify-center gap-1.5 py-2.5 text-xs font-semibold text-teal-600 hover:text-teal-700 hover:bg-teal-50 transition-colors cursor-pointer",children:[e(C,{className:"h-3.5 w-3.5"}),"Thêm dòng"]})})]})}),e("div",{className:"space-y-3",children:t("div",{className:"rounded-xl border border-amber-200 overflow-hidden shadow-sm bg-white",children:[e("div",{className:"bg-gradient-to-r from-amber-600 to-orange-600 px-4 py-3",children:t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-2.5",children:[e("div",{className:"flex h-7 w-7 items-center justify-center rounded-lg bg-white/20 backdrop-blur-sm",children:e(S,{className:"h-4 w-4 text-white"})}),t("div",{children:[e("h4",{className:"text-sm font-bold text-white tracking-wide",children:"Examiner's Copy"}),e("p",{className:"text-[10px] text-amber-100 font-medium",children:"Bảng thông tin giám khảo nhìn thấy"})]})]}),e("div",{className:"flex items-center gap-2",children:t("span",{className:"inline-flex items-center rounded-full bg-white/20 backdrop-blur-sm px-2 py-0.5 text-[10px] font-bold text-white",children:[oe.length," dòng"]})})]})}),e("div",{className:"bg-amber-50/60 border-b border-amber-100 px-4 py-2",children:e("p",{className:"text-xs font-semibold text-amber-800 text-center",children:ne||"(Chưa nhập tiêu đề)"})}),t("div",{className:"grid grid-cols-[40px_1fr_1fr_36px] bg-gradient-to-r from-amber-50/80 to-orange-50/80 border-b border-amber-100",children:[e("div",{className:"px-2 py-2 text-[10px] font-bold text-amber-600 uppercase tracking-wider text-center",children:"#"}),e("div",{className:"px-2 py-2 text-[10px] font-bold text-amber-600 uppercase tracking-wider",children:"Key"}),e("div",{className:"px-2 py-2 text-[10px] font-bold text-amber-600 uppercase tracking-wider",children:"Value"}),e("div",{})]}),e("div",{className:"divide-y divide-gray-100",children:oe.map((r,a)=>t("div",{className:"grid grid-cols-[40px_1fr_1fr_36px] items-center bg-white hover:bg-amber-50/30 transition-colors group/row",children:[e("div",{className:"px-2 py-2 flex justify-center",children:e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-amber-100 text-amber-700 font-bold text-[11px]",children:a+1})}),e("div",{className:"px-1.5 py-1.5",children:e(h,{value:r.key,onChange:e=>Se(a,"key",e.target.value),placeholder:"VD: Where",className:"h-8 text-sm border-gray-200 bg-white focus:border-amber-400 focus:ring-amber-200 transition-all"})}),e("div",{className:"px-1.5 py-1.5",children:e(h,{value:r.answer,onChange:e=>Se(a,"answer",e.target.value),placeholder:"VD: King Road",className:"h-8 text-sm border-gray-200 bg-white focus:border-amber-400 focus:ring-amber-200 transition-all"})}),e("div",{className:"px-1 py-1.5 flex justify-center",children:e(m,{type:"button",variant:"ghost",size:"icon",onClick:()=>(e=>{const t=oe.filter((t,r)=>r!==e);le({examinerFields:t}),pe("examinerFields",t,{shouldDirty:!0})})(a),disabled:oe.length<=1,className:"h-7 w-7 text-gray-300 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/row:opacity-100 transition-all disabled:opacity-0 disabled:group-hover/row:opacity-30",children:e(F,{className:"h-3.5 w-3.5"})})})]},a))}),e("div",{className:"border-t border-dashed border-amber-200 bg-amber-50/30",children:t("button",{type:"button",onClick:()=>{const e=[...oe,{key:"",answer:""}];le({examinerFields:e}),pe("examinerFields",e,{shouldDirty:!0})},className:"w-full flex items-center justify-center gap-1.5 py-2.5 text-xs font-semibold text-amber-600 hover:text-amber-700 hover:bg-amber-50 transition-colors cursor-pointer",children:[e(C,{className:"h-3.5 w-3.5"}),"Thêm dòng"]})})]})})]})]})]}:{className:"overflow-hidden border-0 bg-white shadow shadow-teal-100/30",children:[e("div",{className:"h-1 bg-gradient-to-r from-teal-400 via-emerald-400 to-cyan-400"}),e(d,{className:"pb-3",children:t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-teal-100 shadow-sm",children:e(S,{className:"h-4 w-4 text-teal-600"})}),t("div",{className:"flex-1",children:[t("div",{className:"flex items-center gap-2",children:[e(c,{className:"text-base font-semibold text-gray-800",children:"Thông tin chung"}),t("span",{className:"inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-500",children:["Nhóm ",Z," — Chỉ đọc"]})]}),e("p",{className:"text-xs text-gray-400 mt-0.5",children:"Được quản lý bởi câu hỏi đầu tiên trong nhóm"})]})]})}),e(n,{className:"pt-0",children:t("div",{className:"space-y-3",children:[ne&&t("div",{className:"flex items-center gap-2 px-3 py-2 rounded-lg bg-teal-50/50 border border-teal-100",children:[e(D,{className:"h-4 w-4 text-teal-500 shrink-0"}),e("span",{className:"text-sm font-medium text-gray-700",children:ne})]}),de&&e(O,{imageUrl:de,label:ne||"Hình ảnh minh hoạ"}),t("div",{className:"space-y-4",children:[ce.length>0&&t("div",{className:"rounded-xl overflow-hidden border border-teal-200 shadow-sm",children:[e("div",{className:"bg-gradient-to-r from-teal-600 to-emerald-600 px-3 py-2.5",children:t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-5 w-5 items-center justify-center rounded bg-white/20",children:e(S,{className:"h-3 w-3 text-white"})}),e("span",{className:"text-xs font-bold text-white tracking-wide",children:"Candidate's Copy"})]}),t("span",{className:"text-[10px] font-semibold text-teal-100 bg-white/15 rounded-full px-1.5 py-0.5",children:[ce.length," dòng"]})]})}),e("div",{className:"bg-teal-50/60 text-center font-semibold text-xs py-2 border-b border-teal-100 text-teal-800",children:ne||"(Chưa nhập tiêu đề)"}),t("div",{className:"grid grid-cols-2 bg-teal-50/40 border-b border-teal-100",children:[e("div",{className:"px-3 py-1.5 text-[10px] font-bold text-teal-600 uppercase tracking-wider",children:"Key"}),e("div",{className:"px-3 py-1.5 text-[10px] font-bold text-teal-600 uppercase tracking-wider",children:"Value"})]}),e("div",{className:"divide-y divide-gray-100",children:ce.map((r,a)=>t("div",{className:"grid grid-cols-2 bg-white hover:bg-teal-50/20 transition-colors",children:[e("div",{className:"px-3 py-2 text-sm font-medium text-gray-700",children:r.key||e("span",{className:"text-gray-300 italic",children:"(Chưa nhập)"})}),e("div",{className:"px-3 py-2 text-sm text-gray-800",children:r.answer||e("span",{className:"text-gray-300",children:"—"})})]},a))})]}),oe.length>0&&t("div",{className:"rounded-xl overflow-hidden border border-amber-200 shadow-sm",children:[e("div",{className:"bg-gradient-to-r from-amber-600 to-orange-600 px-3 py-2.5",children:t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-5 w-5 items-center justify-center rounded bg-white/20",children:e(S,{className:"h-3 w-3 text-white"})}),e("span",{className:"text-xs font-bold text-white tracking-wide",children:"Examiner's Copy"})]}),t("span",{className:"text-[10px] font-semibold text-amber-100 bg-white/15 rounded-full px-1.5 py-0.5",children:[oe.length," dòng"]})]})}),e("div",{className:"bg-amber-50/60 text-center font-semibold text-xs py-2 border-b border-amber-100 text-amber-800",children:ne||"(Chưa nhập tiêu đề)"}),t("div",{className:"grid grid-cols-2 bg-amber-50/40 border-b border-amber-100",children:[e("div",{className:"px-3 py-1.5 text-[10px] font-bold text-amber-600 uppercase tracking-wider",children:"Key"}),e("div",{className:"px-3 py-1.5 text-[10px] font-bold text-amber-600 uppercase tracking-wider",children:"Value"})]}),e("div",{className:"divide-y divide-gray-100",children:oe.map((r,a)=>t("div",{className:"grid grid-cols-2 bg-white hover:bg-amber-50/20 transition-colors",children:[e("div",{className:"px-3 py-2 text-sm font-medium text-gray-700",children:r.key||e("span",{className:"text-gray-300 italic",children:"(Chưa nhập)"})}),e("div",{className:"px-3 py-2 text-sm text-gray-800",children:r.answer||e("span",{className:"text-gray-300",children:"—"})})]},a))})]})]}),0===ce.length&&0===oe.length&&e("div",{className:"text-center py-4 text-sm text-gray-400 italic",children:"Chưa có dữ liệu bảng thông tin"})]})})]}),t(p,{...me,children:[t(s,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-indigo-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500"}),e(d,{className:"pb-4",children:t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-indigo-500 to-purple-600 shadow-sm",children:e(U,{className:"h-4 w-4 text-white"})}),t("div",{children:[t(c,{className:"text-lg font-bold text-gray-900",children:["Câu hỏi #",De]}),e("p",{className:"text-sm text-gray-500",children:"ANSWER"===Q?"Học sinh nghe câu hỏi của Examiner và trả lời":"Học sinh đặt câu hỏi cho Examiner"})]})]})}),t(n,{className:"space-y-6",children:[e("div",{className:"rounded-xl border border-gray-200 bg-gray-50 p-4",children:e("div",{className:"flex flex-col md:flex-row gap-6",children:e("div",{className:"flex-1",children:e(v,{...me.register("points",{valueAsNumber:!0}),error:me.formState.errors?.points?.message})})})}),t("div",{className:"space-y-2",children:[t(o,{className:"flex items-center gap-2 text-sm font-semibold text-gray-800",children:[e(A,{className:"h-4 w-4 text-indigo-500"}),Ee," ",e("span",{className:"text-red-500",children:"*"})]}),e(b,{control:he,name:"audioUrl",render:({field:r})=>t(u,{children:[e(x,{children:e(N,{id:`audio-info-exchange-${De}`,label:"",accept:"audio/*",value:r.value,onChange:e=>{r.onChange(e),be("audioUrl")},maxSize:10,placeholder:"Tải lên file âm thanh (mp3, wav...)",autoUpload:!0,prefix:"info-exchange"})}),e(f,{})]})})]})]})]}),t(s,{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(d,{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 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-green-500 to-emerald-600 shadow-sm",children:e(D,{className:"h-4 w-4 text-white"})}),t("div",{children:[e(c,{className:"text-lg font-bold text-gray-900",children:"Đáp án mong muốn"}),t("p",{className:"text-sm text-gray-500",children:[fe.filter(e=>e.trim()).length," đáp án đã nhập"]})]})]}),t(m,{type:"button",variant:"outline",size:"sm",onClick:()=>{pe("expectedAnswers",[...fe,""],{shouldValidate:!0,shouldDirty:!0})},className:"gap-2 border-green-200 text-green-600 hover:bg-green-50",children:[e(C,{className:"h-4 w-4"}),"Thêm đáp án"]})]})}),t(n,{className:"space-y-4",children:[t("div",{className:"flex items-start gap-2 rounded-lg border border-blue-100 bg-blue-50/60 px-3 py-2 text-xs text-blue-700",children:[e(R,{className:"mt-0.5 h-3.5 w-3.5 shrink-0"}),e("span",{children:"Chấm thủ công: giáo viên tham khảo đáp án để chấm · Chấm tự động: AI so sánh giọng nói học sinh với đáp án."})]}),e(b,{control:he,name:"expectedAnswers",render:()=>t(u,{children:[e("div",{className:"space-y-3",children:fe.map((r,a)=>t("div",{className:"flex items-start gap-3 group/answer",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-green-100 text-green-700 text-sm font-bold mt-1",children:a+1}),t("div",{className:"flex-1",children:[e(h,{placeholder:Ve,value:r||"",onChange:e=>((e,t)=>{const r=[...fe];r[e]=t,pe("expectedAnswers",r,{shouldValidate:!0,shouldDirty:!0})})(a,e.target.value),className:"border-gray-200 focus:border-green-300 focus:ring-green-200"}),me.formState.errors?.expectedAnswers?.[a]?.message&&e("p",{className:"mt-1 text-sm text-red-500",children:me.formState.errors.expectedAnswers[a]?.message})]}),fe.length>1&&e(m,{type:"button",variant:"ghost",size:"sm",onClick:()=>(e=>{if(fe.length>1){const t=fe.filter((t,r)=>r!==e);pe("expectedAnswers",t,{shouldValidate:!0,shouldDirty:!0})}})(a),className:"mt-0.5 h-8 w-8 rounded-full text-gray-400 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/answer:opacity-100 transition-opacity","aria-label":`Xóa đáp án ${a+1}`,children:e(F,{className:"h-4 w-4"})})]},a))}),me.formState.errors?.expectedAnswers?.message&&e(f,{})]})})]})]}),t(s,{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(d,{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(E,{className:"h-4 w-4 text-white"})}),t("div",{children:[t("div",{className:"flex items-center gap-2",children:[e(c,{className:"text-lg font-semibold text-gray-800",children:"Giải thích"}),e("span",{className:"inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600",children:"Không bắt buộc"})]}),e("p",{className:"text-sm text-gray-500 mt-0.5",children:"Giải thích cho đáp án đúng — hiển thị sau khi học sinh hoàn thành câu hỏi"})]})]})}),e(n,{children:e(g,{...me.register("explanation"),placeholder:"VD: Câu trả lời đúng là... vì...",className:"min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none",rows:3})})]})]})]})}