@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,84 +1 @@
1
- import { ListenAndSpeakAnswerCreator } from './ListenAndSpeakAnswerCreator';
2
- export const listenAndSpeakAnswerRegistration = {
3
- component: ListenAndSpeakAnswerCreator,
4
- transformInitialData: (initialData) => {
5
- let creatorData = initialData?.answer;
6
- const content = initialData?.content;
7
- const answer = initialData?.answer || initialData?.correctAnswer || initialData?.correct_answer;
8
- const points = initialData?.total_points
9
- ? parseFloat(initialData.total_points)
10
- : (initialData?.points || content?.points || answer?.points || 1);
11
- const explanation = initialData?.explanation || content?.explanation || answer?.explanation || '';
12
- if (content && typeof content === 'object' && content.audioUrl !== undefined) {
13
- const expectedAnswers = answer?.expectedAnswers || [];
14
- creatorData = {
15
- audioUrl: content.audioUrl || '',
16
- expectedAnswers: expectedAnswers,
17
- arrowNode: content.arrowNode,
18
- backgroundImageUrl: answer?.backgroundImageUrl || content.backgroundImageUrl || '',
19
- groupNumber: answer?.groupNumber || content.groupNumber || 1,
20
- explanation,
21
- points,
22
- isBackup: content.isBackup || false,
23
- };
24
- }
25
- else if (answer && typeof answer === 'object' && answer.expectedAnswers !== undefined) {
26
- creatorData = {
27
- ...answer,
28
- audioUrl: answer.audioUrl || content?.audioUrl || '',
29
- arrowNode: answer.arrowNode || content?.arrowNode,
30
- backgroundImageUrl: answer.backgroundImageUrl || content?.backgroundImageUrl || '',
31
- groupNumber: answer.groupNumber || content?.groupNumber || 1,
32
- explanation,
33
- points,
34
- isBackup: content?.isBackup || false,
35
- };
36
- }
37
- return creatorData;
38
- },
39
- getExtraProps: (ctx) => ({
40
- onUnsavedChangesChange: ctx.onUnsavedChangesChange,
41
- validationRef: ctx.validationRef,
42
- groups: ctx.groups,
43
- questionIndexInPart: ctx.questionIndexInPart,
44
- partId: ctx.partId,
45
- }),
46
- wrapOnSave: (data, ctx) => ({
47
- type: ctx.questionType,
48
- content: {
49
- ...(typeof ctx.state.content === 'object' && ctx.state.content !== null ? ctx.state.content : {}),
50
- audioUrl: data.audioUrl,
51
- arrowNode: data.arrowNode,
52
- backgroundImageUrl: data.backgroundImageUrl,
53
- groupNumber: data.groupNumber,
54
- isBackup: data.isBackup || false,
55
- },
56
- points: data?.points ?? ctx.state.points ?? 1,
57
- level: ctx.state.level,
58
- difficulty: ctx.state.difficulty,
59
- skill: ctx.state.skill,
60
- answer: {
61
- expectedAnswers: data.expectedAnswers,
62
- },
63
- explanation: data?.explanation || '',
64
- }),
65
- wrapOnChange: (data, ctx) => ({
66
- type: ctx.questionType,
67
- content: {
68
- ...(typeof ctx.state.content === 'object' && ctx.state.content !== null ? ctx.state.content : {}),
69
- audioUrl: data.audioUrl,
70
- arrowNode: data.arrowNode,
71
- backgroundImageUrl: data.backgroundImageUrl,
72
- groupNumber: data.groupNumber,
73
- isBackup: data.isBackup || false,
74
- },
75
- points: data?.points ?? ctx.state.points ?? 1,
76
- level: ctx.state.level,
77
- difficulty: ctx.state.difficulty,
78
- skill: ctx.state.skill,
79
- answer: {
80
- expectedAnswers: data.expectedAnswers,
81
- },
82
- explanation: data?.explanation || '',
83
- }),
84
- };
1
+ import{ListenAndSpeakAnswerCreator as e}from"./ListenAndSpeakAnswerCreator";export const listenAndSpeakAnswerRegistration={component:e,transformInitialData:e=>{let t=e?.answer;const o=e?.content,a=e?.answer||e?.correctAnswer||e?.correct_answer,n=e?.total_points?parseFloat(e.total_points):e?.points||o?.points||a?.points||1,r=e?.explanation||o?.explanation||a?.explanation||"";if(o&&"object"==typeof o&&void 0!==o.audioUrl){const e=a?.expectedAnswers||[];t={audioUrl:o.audioUrl||"",expectedAnswers:e,arrowNode:o.arrowNode,backgroundImageUrl:a?.backgroundImageUrl||o.backgroundImageUrl||"",groupNumber:a?.groupNumber||o.groupNumber||1,explanation:r,points:n,isBackup:o.isBackup||!1}}else a&&"object"==typeof a&&void 0!==a.expectedAnswers&&(t={...a,audioUrl:a.audioUrl||o?.audioUrl||"",arrowNode:a.arrowNode||o?.arrowNode,backgroundImageUrl:a.backgroundImageUrl||o?.backgroundImageUrl||"",groupNumber:a.groupNumber||o?.groupNumber||1,explanation:r,points:n,isBackup:o?.isBackup||!1});return t},getExtraProps:e=>({onUnsavedChangesChange:e.onUnsavedChangesChange,validationRef:e.validationRef,groups:e.groups,questionIndexInPart:e.questionIndexInPart,partId:e.partId}),wrapOnSave:(e,t)=>({type:t.questionType,content:{..."object"==typeof t.state.content&&null!==t.state.content?t.state.content:{},audioUrl:e.audioUrl,arrowNode:e.arrowNode,backgroundImageUrl:e.backgroundImageUrl,groupNumber:e.groupNumber,isBackup:e.isBackup||!1},points:e?.points??t.state.points??1,level:t.state.level,difficulty:t.state.difficulty,skill:t.state.skill,answer:{expectedAnswers:e.expectedAnswers},explanation:e?.explanation||""}),wrapOnChange:(e,t)=>({type:t.questionType,content:{..."object"==typeof t.state.content&&null!==t.state.content?t.state.content:{},audioUrl:e.audioUrl,arrowNode:e.arrowNode,backgroundImageUrl:e.backgroundImageUrl,groupNumber:e.groupNumber,isBackup:e.isBackup||!1},points:e?.points??t.state.points??1,level:t.state.level,difficulty:t.state.difficulty,skill:t.state.skill,answer:{expectedAnswers:e.expectedAnswers},explanation:e?.explanation||""})};
@@ -1,27 +1 @@
1
- export const transformListenAndSpeakAnswer = (question) => {
2
- const contentData = question.content;
3
- const answerData = question.answer;
4
- const questionGroup = question.questionGroup;
5
- // No strict early return - let validation handle missing expectedAnswers
6
- const expectedAnswers = answerData?.expectedAnswers || [];
7
- const apiContent = {
8
- audioUrl: contentData?.audioUrl || answerData?.audioUrl || '',
9
- arrowNode: contentData?.arrowNode || answerData?.arrowNode || { x: 40, y: 30, width: 10, height: 20 },
10
- isBackup: contentData?.isBackup || answerData?.isBackup || false,
11
- };
12
- const result = {
13
- apiContent,
14
- apiCorrectAnswer: {
15
- expectedAnswers: expectedAnswers,
16
- },
17
- };
18
- if (contentData?.backgroundImageUrl) {
19
- result.sceneData = { backgroundImageUrl: contentData.backgroundImageUrl };
20
- result.groupNumber = contentData.groupNumber || questionGroup?.groupNumber || 1;
21
- }
22
- else if (questionGroup?.payload) {
23
- result.sceneData = questionGroup.payload;
24
- result.groupNumber = questionGroup.groupNumber;
25
- }
26
- return result;
27
- };
1
+ export const transformListenAndSpeakAnswer=r=>{const e=r.content,a=r.answer,o=r.questionGroup,u=a?.expectedAnswers||[],n={apiContent:{audioUrl:e?.audioUrl||a?.audioUrl||"",arrowNode:e?.arrowNode||a?.arrowNode||{x:40,y:30,width:10,height:20},isBackup:e?.isBackup||a?.isBackup||!1},apiCorrectAnswer:{expectedAnswers:u}};return e?.backgroundImageUrl?(n.sceneData={backgroundImageUrl:e.backgroundImageUrl},n.groupNumber=e.groupNumber||o?.groupNumber||1):o?.payload&&(n.sceneData=o.payload,n.groupNumber=o.groupNumber),n};
@@ -1,32 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Mic, Volume2, CheckCircle2, Play, Pause, ImageIcon, Info, GitCompareArrows } from 'lucide-react';
4
- import { Button } from '../../../../components/ui/button';
5
- import { useCompareImagesGroup } from '../../_shared/hooks/useCompareImagesGroup';
6
- import { usePresignedFileUrl, useAudioPreview } from '../../../../shared/lib/hooks';
7
- // ─── Sub-component: Custom Audio Player ──────────────────────────────────────
8
- function QuestionAudioPlayer({ audioUrl }) {
9
- const { previewUrl } = usePresignedFileUrl(audioUrl);
10
- const { isPlaying, currentTime, duration, progressPercentage, handlePlayPause, handleProgressClick, formatTime, } = useAudioPreview(previewUrl);
11
- return (_jsxs("div", { className: "flex items-center gap-3 rounded-lg bg-gradient-to-r from-indigo-50 to-purple-50 border border-indigo-200 px-4 py-2.5", children: [_jsx(Button, { variant: "outline", size: "icon", onClick: handlePlayPause, disabled: !previewUrl, className: "h-8 w-8 border-indigo-300 text-indigo-600 hover:bg-indigo-100", children: isPlaying ? _jsx(Pause, { className: "h-3.5 w-3.5" }) : _jsx(Play, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "text-muted-foreground min-w-[70px] text-xs", children: [formatTime(currentTime), " / ", formatTime(duration)] }), _jsx("div", { className: "bg-indigo-200 relative h-1.5 flex-1 cursor-pointer rounded-full", onClick: handleProgressClick, children: _jsx("div", { className: "bg-indigo-500 absolute top-0 left-0 h-full rounded-full transition-all", style: { width: `${progressPercentage}%` } }) }), _jsx(Volume2, { className: "h-4 w-4 text-indigo-400" })] }));
12
- }
13
- // ─── Sub-component: Presigned image preview ──────────────────────────────────
14
- function CompareImagePreview({ imageUrl, label }) {
15
- const { previewUrl } = usePresignedFileUrl(imageUrl);
16
- if (!previewUrl) {
17
- return (_jsx("div", { className: "flex h-full w-full items-center justify-center bg-gray-100 text-gray-400", children: _jsx(ImageIcon, { className: "h-8 w-8" }) }));
18
- }
19
- return (_jsx("img", { src: previewUrl, alt: label, className: "object-contain w-full h-full" }));
20
- }
21
- // ─── Main Client Component ───────────────────────────────────────────────────
22
- export function ListenAndSpeakCompareImagesClient({ questionData, isReviewMode = false, }) {
23
- const { audioUrl, expectedAnswers = [], explanation } = questionData;
24
- // Retrieve shared group data which should have been populated by the store when the exam loaded
25
- const { candidateImageUrl: storeCandidateImageUrl, examinerImageUrl: storeExaminerImageUrl } = useCompareImagesGroup(undefined, // partId not needed for read-only client
26
- questionData.groupNumber);
27
- // Use questionData images as primary source (Creator preview passes them directly),
28
- // fall back to store values (exam-taking mode populates the store)
29
- const candidateImageUrl = questionData.candidateImageUrl || storeCandidateImageUrl;
30
- const examinerImageUrl = questionData.examinerImageUrl || storeExaminerImageUrl;
31
- return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsx("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-indigo-50 to-blue-50 px-5 py-3", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-indigo-500 to-blue-600 text-white shadow-sm", children: _jsx(GitCompareArrows, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 n\u00F3i (So s\u00E1nh h\u00ECnh \u1EA3nh)" })] }) }), _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" })] })), (candidateImageUrl || examinerImageUrl) && (_jsxs("div", { className: "space-y-4 bg-gradient-to-br from-amber-100 to-orange-100 p-5 rounded-2xl border border-amber-200 shadow-sm", children: [candidateImageUrl && (_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "flex justify-center", children: _jsx("span", { className: "inline-block rounded-lg border-2 border-amber-400 bg-gradient-to-r from-gray-600 to-gray-700 px-8 py-2 text-sm font-bold uppercase tracking-widest text-white shadow-lg ring-1 ring-amber-300/50", children: "Candidate's Copy" }) }), _jsx("div", { className: "w-full rounded-xl overflow-hidden shadow-md bg-white border border-amber-200", children: _jsx(CompareImagePreview, { imageUrl: candidateImageUrl, label: "Candidate's Copy" }) })] })), examinerImageUrl && (_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "flex justify-center", children: _jsx("span", { className: "inline-block rounded-lg border-2 border-orange-400 bg-gradient-to-r from-gray-600 to-gray-700 px-8 py-2 text-sm font-bold uppercase tracking-widest text-white shadow-lg ring-1 ring-orange-300/50", children: "Examiner's Copy" }) }), _jsx("div", { className: "w-full rounded-xl overflow-hidden shadow-md bg-white border border-amber-200", children: _jsx(CompareImagePreview, { imageUrl: examinerImageUrl, label: "Examiner's Copy" }) })] }))] })), _jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-dashed border-gray-300 bg-gray-50/80 px-4 py-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-50 ring-2 ring-indigo-100/50", children: _jsx(Mic, { className: "w-4 h-4 text-indigo-400" }) }), _jsx("p", { className: "text-xs text-gray-400", children: "H\u1ECDc sinh s\u1EBD nh\u1EA5n n\u00FAt ghi \u00E2m v\u00E0 n\u00F3i c\u00E2u tr\u1EA3 l\u1EDDi t\u1EA1i \u0111\u00E2y" })] }), isReviewMode && expectedAnswers.length > 0 && (_jsxs("div", { className: "rounded-xl border border-green-200 bg-green-50/80 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-green-600" }), _jsx("h4", { className: "text-sm font-semibold text-green-800", children: "C\u00E1c \u0111\u00E1p \u00E1n mong mu\u1ED1n" })] }), _jsx("div", { className: "space-y-2", children: 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 })] }))] })] })] }));
32
- }
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{Mic as i,Volume2 as a,CheckCircle2 as d,Play as n,Pause as s,ImageIcon as l,Info as t,GitCompareArrows as o}from"lucide-react";import{Button as c}from"../../../../components/ui/button";import{useCompareImagesGroup as m}from"../../_shared/hooks/useCompareImagesGroup";import{usePresignedFileUrl as g,useAudioPreview as h}from"../../../../shared/lib/hooks";function b({audioUrl:i}){const{previewUrl:d}=g(i),{isPlaying:l,currentTime:t,duration:o,progressPercentage:m,handlePlayPause:b,handleProgressClick:x,formatTime:p}=h(d);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(c,{variant:"outline",size:"icon",onClick:b,disabled:!d,className:"h-8 w-8 border-indigo-300 text-indigo-600 hover:bg-indigo-100",children:e(l?s:n,{className:"h-3.5 w-3.5"})}),r("div",{className:"text-muted-foreground min-w-[70px] text-xs",children:[p(t)," / ",p(o)]}),e("div",{className:"bg-indigo-200 relative h-1.5 flex-1 cursor-pointer rounded-full",onClick:x,children:e("div",{className:"bg-indigo-500 absolute top-0 left-0 h-full rounded-full transition-all",style:{width:`${m}%`}})}),e(a,{className:"h-4 w-4 text-indigo-400"})]})}function x({imageUrl:r,label:i}){const{previewUrl:a}=g(r);return a?e("img",{src:a,alt:i,className:"object-contain w-full h-full"}):e("div",{className:"flex h-full w-full items-center justify-center bg-gray-100 text-gray-400",children:e(l,{className:"h-8 w-8"})})}export function ListenAndSpeakCompareImagesClient({questionData:n,isReviewMode:s=!1}){const{audioUrl:l,expectedAnswers:c=[],explanation:g}=n,{candidateImageUrl:h,examinerImageUrl:p}=m(void 0,n.groupNumber),u=n.candidateImageUrl||h,f=n.examinerImageUrl||p;return r("div",{className:"space-y-4",children:[n.isBackup&&r("div",{className:"inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm",children:[e("span",{children:"🔄"})," Câu hỏi dự phòng"]}),r("div",{className:"overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm",children:[e("div",{className:"flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-indigo-50 to-blue-50 px-5 py-3",children:r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-indigo-500 to-blue-600 text-white shadow-sm",children:e(o,{className:"h-4 w-4"})}),e("span",{className:"text-sm font-medium text-gray-700",children:"Nghe và nói (So sánh hình ảnh)"})]})}),r("div",{className:"p-5 space-y-5",children:[l?e(b,{audioUrl:l}):r("div",{className:"flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3",children:[e(a,{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"})]}),(u||f)&&r("div",{className:"space-y-4 bg-gradient-to-br from-amber-100 to-orange-100 p-5 rounded-2xl border border-amber-200 shadow-sm",children:[u&&r("div",{className:"space-y-2",children:[e("div",{className:"flex justify-center",children:e("span",{className:"inline-block rounded-lg border-2 border-amber-400 bg-gradient-to-r from-gray-600 to-gray-700 px-8 py-2 text-sm font-bold uppercase tracking-widest text-white shadow-lg ring-1 ring-amber-300/50",children:"Candidate's Copy"})}),e("div",{className:"w-full rounded-xl overflow-hidden shadow-md bg-white border border-amber-200",children:e(x,{imageUrl:u,label:"Candidate's Copy"})})]}),f&&r("div",{className:"space-y-2",children:[e("div",{className:"flex justify-center",children:e("span",{className:"inline-block rounded-lg border-2 border-orange-400 bg-gradient-to-r from-gray-600 to-gray-700 px-8 py-2 text-sm font-bold uppercase tracking-widest text-white shadow-lg ring-1 ring-orange-300/50",children:"Examiner's Copy"})}),e("div",{className:"w-full rounded-xl overflow-hidden shadow-md bg-white border border-amber-200",children:e(x,{imageUrl:f,label:"Examiner's Copy"})})]})]}),r("div",{className:"flex items-center gap-3 rounded-lg border border-dashed border-gray-300 bg-gray-50/80 px-4 py-3",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-50 ring-2 ring-indigo-100/50",children:e(i,{className:"w-4 h-4 text-indigo-400"})}),e("p",{className:"text-xs text-gray-400",children:"Học sinh sẽ nhấn nút ghi âm và nói câu trả lời tại đây"})]}),s&&c.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(d,{className:"h-4 w-4 text-green-600"}),e("h4",{className:"text-sm font-semibold text-green-800",children:"Các đáp án mong muốn"})]}),e("div",{className:"space-y-2",children:c.map((i,a)=>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:a+1}),e("span",{className:"text-sm text-green-800",children:i})]},a))})]}),s&&g&&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(t,{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:g})]})]})]})]})}
@@ -1,295 +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 { useCompareImagesGroup } from '../../_shared/hooks/useCompareImagesGroup';
14
- import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
15
- import { useDebouncedCallback, usePresignedFileUrl } from '../../../../shared/lib/hooks';
16
- import { listenAndSpeakCompareImagesSchema } from '../../../../shared/lib/validation/listen-and-speak-compare-images.validation';
17
- // Icons
18
- import { Plus, Trash2, Mic, Volume2, ImageIcon, MessageSquareText, ListChecks, BookOpen, Eye, Pencil, Info } from 'lucide-react';
19
- import { CreatorGuide } from '../../components/CreatorGuide';
20
- import { ListenAndSpeakCompareImagesClient } from './ListenAndSpeakCompareImagesClient';
21
- // ─── Sub-component: Image preview with presigned URL ─────────────────────────
22
- function ImagePreview({ imageUrl, label }) {
23
- const { previewUrl } = usePresignedFileUrl(imageUrl);
24
- if (!previewUrl)
25
- return null;
26
- 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: "h-72 w-full object-contain bg-gray-50 p-2" }) }));
27
- }
28
- export function ListenAndSpeakCompareImagesCreator({ questionIndex, initialData, onChange, onUnsavedChangesChange, validationRef, isFirstInGroup: isFirstInGroupProp, partId, groups, questionIndexInPart, }) {
29
- // ---------------------------------------------------------------
30
- // Determine if this is the first question in the group
31
- // ---------------------------------------------------------------
32
- const isFirstInGroup = useMemo(() => {
33
- if (isFirstInGroupProp !== undefined)
34
- return isFirstInGroupProp;
35
- if (!groups || !groups.length || questionIndexInPart === undefined)
36
- return true;
37
- let cumulative = 0;
38
- for (const group of groups) {
39
- if (questionIndexInPart === cumulative)
40
- return true;
41
- cumulative += group.questionCount;
42
- }
43
- return false;
44
- }, [isFirstInGroupProp, groups, questionIndexInPart]);
45
- // ---------------------------------------------------------------
46
- // 1. Group level state (Shared Compare Images)
47
- // ---------------------------------------------------------------
48
- const computedGroupNumber = useMemo(() => {
49
- if (groups && groups.length > 0) {
50
- if (questionIndexInPart === undefined)
51
- return 1;
52
- let cumulative = 0;
53
- for (let i = 0; i < groups.length; i++) {
54
- if (questionIndexInPart < cumulative + groups[i].questionCount) {
55
- return i + 1;
56
- }
57
- cumulative += groups[i].questionCount;
58
- }
59
- return 1;
60
- }
61
- // Fallback: use groupNumber from initialData when groups prop is not yet available
62
- const apiData = initialData;
63
- return apiData?.groupNumber ?? apiData?.questionGroup?.groupNumber ?? undefined;
64
- }, [groups, questionIndexInPart, initialData]);
65
- const apiData = initialData;
66
- const { candidateImageUrl, examinerImageUrl, updateCandidateImage, updateExaminerImage, isValid: isGroupHookValid, } = useCompareImagesGroup(partId, computedGroupNumber, {
67
- candidateImageUrl: apiData?.candidateImageUrl,
68
- examinerImageUrl: apiData?.examinerImageUrl,
69
- });
70
- // Group image validation errors (inline under each input)
71
- const [groupImageErrors, setGroupImageErrors] = useState({});
72
- // ---------------------------------------------------------------
73
- // 2. Question level form data
74
- // ---------------------------------------------------------------
75
- const form = useReactHookForm({
76
- zodSchema: listenAndSpeakCompareImagesSchema,
77
- defaultValues: {
78
- candidateImageUrl: apiData?.candidateImageUrl || candidateImageUrl || '',
79
- examinerImageUrl: apiData?.examinerImageUrl || examinerImageUrl || '',
80
- audioUrl: apiData?.audioUrl || '',
81
- expectedAnswers: apiData?.expectedAnswers?.length > 0 ? apiData.expectedAnswers : [''],
82
- points: apiData?.points ?? 1,
83
- explanation: apiData?.explanation || '',
84
- isBackup: apiData?.isBackup || false,
85
- },
86
- });
87
- const { control, watch, setValue, trigger, clearErrors } = form;
88
- const watchedAudioUrl = watch('audioUrl');
89
- const watchedExpectedAnswers = watch('expectedAnswers');
90
- const watchedPoints = watch('points');
91
- const watchedExplanation = watch('explanation');
92
- const watchedIsBackup = watch('isBackup');
93
- // ---------------------------------------------------------------
94
- // Refs to prevent infinite loops
95
- // ---------------------------------------------------------------
96
- const previousInitialDataRef = useRef(initialData);
97
- const isInitializingRef = useRef(false);
98
- const onChangeRef = useRef(onChange);
99
- const previousPayloadRef = useRef('');
100
- useEffect(() => {
101
- onChangeRef.current = onChange;
102
- }, [onChange]);
103
- // Debounced onChange
104
- const debouncedOnChange = useDebouncedCallback((data) => {
105
- onChangeRef.current?.(data);
106
- }, 300);
107
- // ---------------------------------------------------------------
108
- // Connect to external validation trigger
109
- // ---------------------------------------------------------------
110
- useEffect(() => {
111
- if (validationRef) {
112
- validationRef.current = {
113
- triggerValidation: async () => {
114
- // If first in group, also validate that images are set
115
- if (isFirstInGroup) {
116
- const hasCandidate = !!candidateImageUrl;
117
- const hasExaminer = !!examinerImageUrl;
118
- const errors = {};
119
- if (!hasCandidate)
120
- errors.candidate = "Vui lòng tải lên Candidate's Copy Image!";
121
- if (!hasExaminer)
122
- errors.examiner = "Vui lòng tải lên Examiner's Copy Image!";
123
- setGroupImageErrors(errors);
124
- if (!hasCandidate || !hasExaminer) {
125
- // Also trigger form validation to show other errors
126
- await trigger();
127
- return false;
128
- }
129
- }
130
- else {
131
- setGroupImageErrors({});
132
- }
133
- return await trigger();
134
- },
135
- getFormData: () => ({
136
- ...form.getValues(),
137
- candidateImageUrl: candidateImageUrl || '',
138
- examinerImageUrl: examinerImageUrl || '',
139
- groupNumber: isFirstInGroup ? displayGroupNumber : undefined,
140
- }),
141
- };
142
- }
143
- }, [validationRef, form, trigger, isFirstInGroup, candidateImageUrl, examinerImageUrl, setGroupImageErrors]);
144
- // Clear group image errors when images are uploaded
145
- useEffect(() => {
146
- if (candidateImageUrl && groupImageErrors.candidate) {
147
- setGroupImageErrors(prev => ({ ...prev, candidate: undefined }));
148
- }
149
- if (examinerImageUrl && groupImageErrors.examiner) {
150
- setGroupImageErrors(prev => ({ ...prev, examiner: undefined }));
151
- }
152
- }, [candidateImageUrl, examinerImageUrl]); // eslint-disable-line react-hooks/exhaustive-deps
153
- // ---------------------------------------------------------------
154
- // Sync form values if initialData props change
155
- // ---------------------------------------------------------------
156
- useEffect(() => {
157
- const normalizedInitialData = {
158
- candidateImageUrl: apiData?.candidateImageUrl || '',
159
- examinerImageUrl: apiData?.examinerImageUrl || '',
160
- audioUrl: apiData?.audioUrl || '',
161
- expectedAnswers: apiData?.expectedAnswers?.length > 0 ? apiData.expectedAnswers : [''],
162
- points: apiData?.points ?? 1,
163
- explanation: apiData?.explanation || '',
164
- isBackup: apiData?.isBackup || false,
165
- };
166
- const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
167
- if (!initialDataChanged)
168
- return;
169
- previousInitialDataRef.current = initialData;
170
- const currentFormData = {
171
- audioUrl: watchedAudioUrl,
172
- expectedAnswers: watchedExpectedAnswers,
173
- points: watchedPoints,
174
- explanation: watchedExplanation,
175
- isBackup: watchedIsBackup,
176
- };
177
- const isEchoFromOnChange = currentFormData.points === normalizedInitialData.points &&
178
- currentFormData.explanation === normalizedInitialData.explanation &&
179
- currentFormData.audioUrl === normalizedInitialData.audioUrl &&
180
- JSON.stringify(currentFormData.expectedAnswers) === JSON.stringify(normalizedInitialData.expectedAnswers);
181
- const wasJustSaved = initialData?.isCompleted === true;
182
- if (isEchoFromOnChange && wasJustSaved)
183
- return;
184
- const shouldReset = !isEchoFromOnChange &&
185
- (normalizedInitialData.audioUrl !== currentFormData.audioUrl ||
186
- JSON.stringify(normalizedInitialData.expectedAnswers) !== JSON.stringify(currentFormData.expectedAnswers));
187
- if (shouldReset) {
188
- isInitializingRef.current = true;
189
- form.reset(normalizedInitialData);
190
- setTimeout(() => {
191
- isInitializingRef.current = false;
192
- }, 150);
193
- }
194
- }, [initialData, form, watchedAudioUrl, watchedExpectedAnswers, watchedExplanation, watchedPoints, watchedIsBackup]);
195
- // ---------------------------------------------------------------
196
- // Fire onChange when form values change
197
- // ---------------------------------------------------------------
198
- const watchedExpectedAnswersString = JSON.stringify(watchedExpectedAnswers);
199
- useEffect(() => {
200
- if (isInitializingRef.current) {
201
- const currentPayload = JSON.stringify({
202
- audioUrl: watchedAudioUrl,
203
- expectedAnswers: watchedExpectedAnswers,
204
- points: watchedPoints,
205
- explanation: watchedExplanation,
206
- isBackup: watchedIsBackup,
207
- candidateImageUrl,
208
- examinerImageUrl,
209
- });
210
- previousPayloadRef.current = currentPayload;
211
- return;
212
- }
213
- const payload = {
214
- audioUrl: watchedAudioUrl,
215
- expectedAnswers: watchedExpectedAnswers,
216
- points: watchedPoints,
217
- explanation: watchedExplanation,
218
- isBackup: watchedIsBackup,
219
- candidateImageUrl,
220
- examinerImageUrl,
221
- };
222
- const payloadString = JSON.stringify(payload);
223
- if (previousPayloadRef.current !== payloadString) {
224
- previousPayloadRef.current = payloadString;
225
- debouncedOnChange(payload);
226
- }
227
- }, [
228
- watchedAudioUrl,
229
- watchedExpectedAnswersString,
230
- watchedPoints,
231
- watchedExplanation,
232
- watchedIsBackup,
233
- candidateImageUrl,
234
- examinerImageUrl,
235
- debouncedOnChange,
236
- ]);
237
- // Track unsaved changes
238
- useEffect(() => {
239
- if (onUnsavedChangesChange) {
240
- onUnsavedChangesChange(form.formState.isDirty);
241
- }
242
- }, [form.formState.isDirty, onUnsavedChangesChange]);
243
- // ---------------------------------------------------------------
244
- // Handlers
245
- // ---------------------------------------------------------------
246
- const addExpectedAnswer = () => {
247
- const currentAnswers = form.getValues('expectedAnswers');
248
- form.setValue('expectedAnswers', [...currentAnswers, ''], { shouldValidate: true, shouldDirty: true });
249
- };
250
- const handleExpectedAnswerChange = (index, value) => {
251
- const newAnswers = [...watchedExpectedAnswers];
252
- newAnswers[index] = value;
253
- form.setValue('expectedAnswers', newAnswers, { shouldValidate: true, shouldDirty: true });
254
- };
255
- const removeExpectedAnswer = (index) => {
256
- if (watchedExpectedAnswers.length > 1) {
257
- const newAnswers = watchedExpectedAnswers.filter((_, i) => i !== index);
258
- form.setValue('expectedAnswers', newAnswers, { shouldValidate: true, shouldDirty: true });
259
- }
260
- };
261
- // Compute display question number safely
262
- const displayQuestionNumber = typeof questionIndex === 'number' && !isNaN(questionIndex)
263
- ? questionIndex + 1
264
- : (typeof questionIndexInPart === 'number' && !isNaN(questionIndexInPart)
265
- ? questionIndexInPart + 1
266
- : '?');
267
- // Compute group number for display (reuse the computed group number)
268
- const displayGroupNumber = computedGroupNumber ?? 1;
269
- // ---------------------------------------------------------------
270
- // Preview mode
271
- // ---------------------------------------------------------------
272
- const [isClientMode, setIsClientMode] = useState(false);
273
- if (isClientMode) {
274
- const questionData = {
275
- audioUrl: watchedAudioUrl,
276
- expectedAnswers: watchedExpectedAnswers,
277
- explanation: watchedExplanation,
278
- points: watchedPoints,
279
- isBackup: watchedIsBackup,
280
- groupNumber: displayGroupNumber,
281
- candidateImageUrl: candidateImageUrl || '',
282
- examinerImageUrl: examinerImageUrl || '',
283
- };
284
- return (_jsxs("div", { className: "space-y-6", children: [_jsx(Card, { children: _jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx(CardTitle, { children: "Nghe v\u00E0 n\u00F3i \u2014 So s\u00E1nh h\u00ECnh \u1EA3nh (Preview)" }), _jsx("p", { className: "mt-1 text-sm text-gray-500", children: "Preview mode" })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(false), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), "Quay l\u1EA1i Edit Mode"] })] }) }) }), _jsx(ListenAndSpeakCompareImagesClient, { questionData: questionData, isReviewMode: true })] }));
285
- }
286
- return (_jsxs("div", { className: "space-y-6", children: [_jsx(CreatorGuide, { colorScheme: "blue", steps: [
287
- { text: 'Upload 2 hình ảnh so sánh: Hình thí sinh (Candidate) và Hình giám khảo (Examiner) — dùng chung cho nhóm' },
288
- { text: 'Câu hỏi đầu tiên trong nhóm sẽ quản lý hình ảnh chung, các câu sau tham chiếu' },
289
- { text: 'Upload audio câu hỏi cho từng câu — giám khảo hỏi về sự khác biệt giữa 2 hình' },
290
- { text: 'Nhập các đáp án mong muốn mô tả sự khác biệt', highlight: true },
291
- { text: 'Thêm giải thích cho từng điểm khác biệt' },
292
- ], tips: ['Chọn hình có sự khác biệt rõ ràng nhưng không quá dễ phát hiện'] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50", children: [_jsx("div", { className: "h-1.5 bg-gradient-to-r from-blue-500 via-indigo-500 to-purple-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-blue-500 to-indigo-600 shadow-md", children: _jsx(ListChecks, { className: "h-5 w-5 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-xl font-bold text-gray-900", children: "Nh\u00F3m \u0111\u00E1p \u00E1n" }), _jsxs("div", { className: "mt-1.5 inline-flex items-center gap-1.5 rounded-full bg-blue-100 px-3 py-1 text-sm font-medium text-blue-700", children: [_jsx("span", { children: "\uD83D\uDDBC\uFE0F" }), _jsxs("span", { children: ["Nh\u00F3m ", displayGroupNumber] }), _jsx("span", { className: "text-xs font-normal opacity-75 ml-1", children: "(D\u00F9ng chung)" })] })] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(true), disabled: !watchedAudioUrl || watchedExpectedAnswers.filter((a) => a.trim()).length === 0, className: "gap-2 border-blue-200 text-blue-600 hover:bg-blue-50 hover:text-blue-700", children: [_jsx(Eye, { className: "h-4 w-4" }), "Preview"] })] }) })] }), isFirstInGroup ? (_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-2", children: [_jsx("div", { className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-amber-100", children: _jsx(ImageIcon, { className: "h-3.5 w-3.5 text-amber-600" }) }), _jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "H\u00ECnh \u1EA3nh so s\u00E1nh" })] }) }), _jsx(CardContent, { className: "space-y-4", children: _jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-800", children: ["\uD83D\uDCF7 Candidate's Copy Image ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FileUpload, { id: `candidate-img-group-${displayGroupNumber}`, label: "", accept: "image/*", value: candidateImageUrl || '', onChange: (url) => updateCandidateImage(url), maxSize: 5, placeholder: "Upload Candidate's Copy", autoUpload: true, prefix: "questions/compare-images" }), candidateImageUrl && (_jsx(ImagePreview, { imageUrl: candidateImageUrl, label: "Candidate's Copy" })), !candidateImageUrl && (_jsxs("div", { className: `flex flex-col items-center justify-center rounded-xl border-2 border-dashed ${groupImageErrors.candidate ? 'border-red-300 bg-red-50/50' : 'border-amber-200 bg-amber-50/50'} py-6 px-4 text-center`, children: [_jsx(ImageIcon, { className: `h-8 w-8 ${groupImageErrors.candidate ? 'text-red-400' : 'text-amber-400'} mb-2` }), _jsx("p", { className: "text-xs text-gray-500", children: "H\u00ECnh \u1EA3nh d\u00E0nh cho th\u00ED sinh" })] })), groupImageErrors.candidate && (_jsx("p", { className: "text-sm font-medium text-destructive", children: groupImageErrors.candidate }))] }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-800", children: ["\uD83D\uDCF7 Examiner's Copy Image ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FileUpload, { id: `examiner-img-group-${displayGroupNumber}`, label: "", accept: "image/*", value: examinerImageUrl || '', onChange: (url) => updateExaminerImage(url), maxSize: 5, placeholder: "Upload Examiner's Copy", autoUpload: true, prefix: "questions/compare-images" }), examinerImageUrl && (_jsx(ImagePreview, { imageUrl: examinerImageUrl, label: "Examiner's Copy" })), !examinerImageUrl && (_jsxs("div", { className: `flex flex-col items-center justify-center rounded-xl border-2 border-dashed ${groupImageErrors.examiner ? 'border-red-300 bg-red-50/50' : 'border-orange-200 bg-orange-50/50'} py-6 px-4 text-center`, children: [_jsx(ImageIcon, { className: `h-8 w-8 ${groupImageErrors.examiner ? 'text-red-400' : 'text-orange-400'} mb-2` }), _jsx("p", { className: "text-xs text-gray-500", children: "H\u00ECnh \u1EA3nh d\u00E0nh cho gi\u00E1m kh\u1EA3o" })] })), groupImageErrors.examiner && (_jsx("p", { className: "text-sm font-medium text-destructive", children: groupImageErrors.examiner }))] })] }) })] })) : (
293
- /* Read-only view of shared images for non-first questions in group */
294
- _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow shadow-amber-100/30", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-amber-400 to-orange-400" }), _jsx(CardHeader, { className: "pb-3", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-amber-100", children: _jsx(ImageIcon, { className: "h-3.5 w-3.5 text-amber-600" }) }), _jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "H\u00ECnh \u1EA3nh so s\u00E1nh" }), _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 ", displayGroupNumber, " \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: candidateImageUrl || examinerImageUrl ? (_jsxs("div", { className: "space-y-4", children: [candidateImageUrl && (_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "flex justify-center", children: _jsx("span", { className: "inline-block rounded-lg border-2 border-amber-400 bg-gradient-to-r from-gray-600 to-gray-700 px-8 py-2 text-sm font-bold uppercase tracking-widest text-white shadow-lg ring-1 ring-amber-300/50", children: "Candidate's Copy" }) }), _jsx(ImagePreview, { imageUrl: candidateImageUrl, label: "Candidate's Copy" })] })), examinerImageUrl && (_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "flex justify-center", children: _jsx("span", { className: "inline-block rounded-lg border-2 border-orange-400 bg-gradient-to-r from-gray-600 to-gray-700 px-8 py-2 text-sm font-bold uppercase tracking-widest text-white shadow-lg ring-1 ring-orange-300/50", children: "Examiner's Copy" }) }), _jsx(ImagePreview, { imageUrl: examinerImageUrl, label: "Examiner's Copy" })] }))] })) : (_jsx("div", { className: "text-center py-4 text-sm text-gray-400 italic", children: "Ch\u01B0a c\u00F3 h\u00ECnh \u1EA3nh n\u00E0o trong nh\u00F3m" })) })] })), _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-400 to-purple-400" }), _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: [_jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "C\u00E2u h\u1ECFi Nghe & So s\u00E1nh" }), _jsx("p", { className: "text-sm text-gray-500", children: "H\u1ECDc sinh nghe \u00E2m thanh v\u00E0 n\u00F3i \u0111i\u1EC3m kh\u00E1c bi\u1EC7t gi\u1EEFa hai h\u00ECnh \u1EA3nh" })] })] }) }), _jsxs(CardContent, { className: "space-y-6", children: [_jsx("div", { className: "rounded-xl border border-gray-200 bg-gray-50 p-4", children: _jsxs("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 }) }), _jsx("div", { className: "flex-1 flex flex-col justify-center pt-2" })] }) }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-800", children: [_jsx(Volume2, { className: "h-4 w-4 text-indigo-500" }), "\u00C2m thanh c\u00E2u h\u1ECFi ", _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-compare-${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: "listen-and-speak" }) }), _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 items-center justify-center rounded-lg bg-gradient-to-br from-green-500 to-emerald-600 shadow-sm", children: _jsx(MessageSquareText, { className: "h-4 w-4 text-white" }) }), _jsx("div", { children: _jsxs(CardTitle, { className: "text-lg font-semibold text-gray-800", children: ["\u0110\u00E1p \u00E1n mong mu\u1ED1n (", watchedExpectedAnswers.filter((a) => a.trim()).length, ")"] }) })] }), _jsxs(Button, { 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.5", children: [_jsx(Info, { className: "h-4 w-4 text-blue-500 mt-0.5 flex-shrink-0" }), _jsxs("div", { className: "text-xs text-blue-700 space-y-1", children: [_jsx("p", { children: "Nh\u1EADp c\u00E1c c\u00E2u tr\u1EA3 l\u1EDDi mong mu\u1ED1n. C\u00F3 th\u1EC3 th\u00EAm nhi\u1EC1u \u0111\u00E1p \u00E1n n\u1EBFu c\u00F3 nhi\u1EC1u c\u00E1ch tr\u1EA3 l\u1EDDi \u0111\u00FAng." }), _jsxs("p", { children: [_jsx("span", { className: "font-semibold", children: "\u2022 Ch\u1EA5m th\u1EE7 c\u00F4ng:" }), " Gi\u00E1o vi\u00EAn d\u00F9ng c\u00E1c \u0111\u00E1p \u00E1n n\u00E0y l\u00E0m tham kh\u1EA3o \u0111\u1EC3 ch\u1EA5m \u0111i\u1EC3m."] }), _jsxs("p", { children: [_jsx("span", { className: "font-semibold", children: "\u2022 Ch\u1EA5m t\u1EF1 \u0111\u1ED9ng (AI):" }), " H\u1EC7 th\u1ED1ng d\u00F9ng AI so s\u00E1nh gi\u1ECDng n\u00F3i c\u1EE7a h\u1ECDc sinh v\u1EDBi c\u00E1c \u0111\u00E1p \u00E1n \u0111\u1EC3 t\u1EF1 \u0111\u1ED9ng ch\u1EA5m \u0111i\u1EC3m."] })] })] }), _jsxs("div", { className: "space-y-3", children: [watchedExpectedAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-start gap-3 group/answer", children: [_jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-green-100 text-green-700 font-semibold shadow-sm text-sm", children: index + 1 }), _jsxs("div", { className: "flex-1", children: [_jsx(Input, { placeholder: `VD: ${index === 0 ? 'The tree is bigger in picture A' : 'The dog is brown in picture B'}`, 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: "text-red-400 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/answer:opacity-100 transition-opacity", children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }, index))), form.formState.errors?.expectedAnswers && !Array.isArray(form.formState.errors.expectedAnswers) && (_jsx("p", { className: "text-sm text-red-500", children: form.formState.errors.expectedAnswers.message }))] })] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-amber-400 to-orange-400" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm", children: _jsx(BookOpen, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch" }), _jsx("span", { className: "inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600", children: "Kh\u00F4ng b\u1EAFt bu\u1ED9c" })] }), _jsx("p", { className: "text-sm text-gray-500 mt-0.5", children: "Gi\u1EA3i th\u00EDch cho \u0111\u00E1p \u00E1n \u0111\u00FAng \u2014 hi\u1EC3n th\u1ECB sau khi h\u1ECDc sinh ho\u00E0n th\u00E0nh c\u00E2u h\u1ECFi" })] })] }) }), _jsx(CardContent, { children: _jsx(Textarea, { ...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 }) })] })] })] }));
295
- }
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useEffect as a,useMemo as t,useRef as i,useState as n}from"react";import{Card as s,CardContent as l,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 u,FormField as x,FormItem as f,FormMessage as b}from"../../../../components/ui/form";import{FileUpload as N}from"../../../../components/ui/file-upload";import{PointsInput as v}from"../../../../components/ui/points-input";import{useCompareImagesGroup as w}from"../../_shared/hooks/useCompareImagesGroup";import{useReactHookForm as y}from"../../../../shared/lib/hooks/useReactHookForm";import{useDebouncedCallback as C,usePresignedFileUrl as U}from"../../../../shared/lib/hooks";import{listenAndSpeakCompareImagesSchema as k}from"../../../../shared/lib/validation/listen-and-speak-compare-images.validation";import{Plus as I,Trash2 as A,Mic as S,Volume2 as j,ImageIcon as V,MessageSquareText as q,ListChecks as D,BookOpen as B,Eye as E,Pencil as z,Info as H}from"lucide-react";import{CreatorGuide as J}from"../../components/CreatorGuide";import{ListenAndSpeakCompareImagesClient as O}from"./ListenAndSpeakCompareImagesClient";function G({imageUrl:r,label:a}){const{previewUrl:t}=U(r);return t?e("div",{className:"mt-2 rounded-xl border border-gray-200 overflow-hidden bg-white shadow-sm",children:e("img",{src:t,alt:a,className:"h-72 w-full object-contain bg-gray-50 p-2"})}):null}export function ListenAndSpeakCompareImagesCreator({questionIndex:U,initialData:$,onChange:T,onUnsavedChangesChange:P,validationRef:F,isFirstInGroup:R,partId:L,groups:M,questionIndexInPart:K}){const Q=t(()=>{if(void 0!==R)return R;if(!M||!M.length||void 0===K)return!0;let e=0;for(const r of M){if(K===e)return!0;e+=r.questionCount}return!1},[R,M,K]),_=t(()=>{if(M&&M.length>0){if(void 0===K)return 1;let e=0;for(let r=0;r<M.length;r++){if(K<e+M[r].questionCount)return r+1;e+=M[r].questionCount}return 1}const e=$;return e?.groupNumber??e?.questionGroup?.groupNumber??void 0},[M,K,$]),W=$,{candidateImageUrl:X,examinerImageUrl:Y,updateCandidateImage:Z,updateExaminerImage:ee,isValid:re}=w(L,_,{candidateImageUrl:W?.candidateImageUrl,examinerImageUrl:W?.examinerImageUrl}),[ae,te]=n({}),ie=y({zodSchema:k,defaultValues:{candidateImageUrl:W?.candidateImageUrl||X||"",examinerImageUrl:W?.examinerImageUrl||Y||"",audioUrl:W?.audioUrl||"",expectedAnswers:W?.expectedAnswers?.length>0?W.expectedAnswers:[""],points:W?.points??1,explanation:W?.explanation||"",isBackup:W?.isBackup||!1}}),{control:ne,watch:se,setValue:le,trigger:de,clearErrors:ce}=ie,oe=se("audioUrl"),me=se("expectedAnswers"),he=se("points"),ge=se("explanation"),pe=se("isBackup"),ue=i($),xe=i(!1),fe=i(T),be=i("");a(()=>{fe.current=T},[T]);const Ne=C(e=>{fe.current?.(e)},300);a(()=>{F&&(F.current={triggerValidation:async()=>{if(Q){const e=!!X,r=!!Y,a={};if(e||(a.candidate="Vui lòng tải lên Candidate's Copy Image!"),r||(a.examiner="Vui lòng tải lên Examiner's Copy Image!"),te(a),!e||!r)return await de(),!1}else te({});return await de()},getFormData:()=>({...ie.getValues(),candidateImageUrl:X||"",examinerImageUrl:Y||"",groupNumber:Q?ye:void 0})})},[F,ie,de,Q,X,Y,te]),a(()=>{X&&ae.candidate&&te(e=>({...e,candidate:void 0})),Y&&ae.examiner&&te(e=>({...e,examiner:void 0}))},[X,Y]),a(()=>{const e={candidateImageUrl:W?.candidateImageUrl||"",examinerImageUrl:W?.examinerImageUrl||"",audioUrl:W?.audioUrl||"",expectedAnswers:W?.expectedAnswers?.length>0?W.expectedAnswers:[""],points:W?.points??1,explanation:W?.explanation||"",isBackup:W?.isBackup||!1};if(!(JSON.stringify(ue.current)!==JSON.stringify($)))return;ue.current=$;const r={audioUrl:oe,expectedAnswers:me,points:he,explanation:ge,isBackup:pe},a=r.points===e.points&&r.explanation===e.explanation&&r.audioUrl===e.audioUrl&&JSON.stringify(r.expectedAnswers)===JSON.stringify(e.expectedAnswers);if(a&&!0===$?.isCompleted)return;!a&&(e.audioUrl!==r.audioUrl||JSON.stringify(e.expectedAnswers)!==JSON.stringify(r.expectedAnswers))&&(xe.current=!0,ie.reset(e),setTimeout(()=>{xe.current=!1},150))},[$,ie,oe,me,ge,he,pe]);const ve=JSON.stringify(me);a(()=>{if(xe.current){const e=JSON.stringify({audioUrl:oe,expectedAnswers:me,points:he,explanation:ge,isBackup:pe,candidateImageUrl:X,examinerImageUrl:Y});return void(be.current=e)}const e={audioUrl:oe,expectedAnswers:me,points:he,explanation:ge,isBackup:pe,candidateImageUrl:X,examinerImageUrl:Y},r=JSON.stringify(e);be.current!==r&&(be.current=r,Ne(e))},[oe,ve,he,ge,pe,X,Y,Ne]),a(()=>{P&&P(ie.formState.isDirty)},[ie.formState.isDirty,P]);const we="number"!=typeof U||isNaN(U)?"number"!=typeof K||isNaN(K)?"?":K+1:U+1,ye=_??1,[Ce,Ue]=n(!1);if(Ce){const a={audioUrl:oe,expectedAnswers:me,explanation:ge,points:he,isBackup:pe,groupNumber:ye,candidateImageUrl:X||"",examinerImageUrl:Y||""};return r("div",{className:"space-y-6",children:[e(s,{children:e(d,{children:r("div",{className:"flex items-center justify-between",children:[r("div",{children:[e(c,{children:"Nghe và nói — So sánh hình ảnh (Preview)"}),e("p",{className:"mt-1 text-sm text-gray-500",children:"Preview mode"})]}),r(m,{variant:"outline",size:"sm",onClick:()=>Ue(!1),children:[e(z,{className:"mr-2 h-4 w-4"}),"Quay lại Edit Mode"]})]})})}),e(O,{questionData:a,isReviewMode:!0})]})}return r("div",{className:"space-y-6",children:[e(J,{colorScheme:"blue",steps:[{text:"Upload 2 hình ảnh so sánh: Hình thí sinh (Candidate) và Hình giám khảo (Examiner) — dùng chung cho nhóm"},{text:"Câu hỏi đầu tiên trong nhóm sẽ quản lý hình ảnh chung, các câu sau tham chiếu"},{text:"Upload audio câu hỏi cho từng câu — giám khảo hỏi về sự khác biệt giữa 2 hình"},{text:"Nhập các đáp án mong muốn mô tả sự khác biệt",highlight:!0},{text:"Thêm giải thích cho từng điểm khác biệt"}],tips:["Chọn hình có sự khác biệt rõ ràng nhưng không quá dễ phát hiện"]}),r(s,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50",children:[e("div",{className:"h-1.5 bg-gradient-to-r from-blue-500 via-indigo-500 to-purple-500"}),e(d,{className:"pb-4",children:r("div",{className:"flex items-center justify-between",children:[r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-blue-500 to-indigo-600 shadow-md",children:e(D,{className:"h-5 w-5 text-white"})}),r("div",{children:[e(c,{className:"text-xl font-bold text-gray-900",children:"Nhóm đáp án"}),r("div",{className:"mt-1.5 inline-flex items-center gap-1.5 rounded-full bg-blue-100 px-3 py-1 text-sm font-medium text-blue-700",children:[e("span",{children:"🖼️"}),r("span",{children:["Nhóm ",ye]}),e("span",{className:"text-xs font-normal opacity-75 ml-1",children:"(Dùng chung)"})]})]})]}),r(m,{variant:"outline",size:"sm",onClick:()=>Ue(!0),disabled:!oe||0===me.filter(e=>e.trim()).length,className:"gap-2 border-blue-200 text-blue-600 hover:bg-blue-50 hover:text-blue-700",children:[e(E,{className:"h-4 w-4"}),"Preview"]})]})})]}),r(s,Q?{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:r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-amber-100",children:e(V,{className:"h-3.5 w-3.5 text-amber-600"})}),e(c,{className:"text-lg font-semibold text-gray-800",children:"Hình ảnh so sánh"})]})}),e(l,{className:"space-y-4",children:r("div",{className:"space-y-6",children:[r("div",{className:"space-y-2",children:[r(o,{className:"flex items-center gap-2 text-sm font-semibold text-gray-800",children:["📷 Candidate's Copy Image ",e("span",{className:"text-red-500",children:"*"})]}),e(N,{id:`candidate-img-group-${ye}`,label:"",accept:"image/*",value:X||"",onChange:e=>Z(e),maxSize:5,placeholder:"Upload Candidate's Copy",autoUpload:!0,prefix:"questions/compare-images"}),X&&e(G,{imageUrl:X,label:"Candidate's Copy"}),!X&&r("div",{className:`flex flex-col items-center justify-center rounded-xl border-2 border-dashed ${ae.candidate?"border-red-300 bg-red-50/50":"border-amber-200 bg-amber-50/50"} py-6 px-4 text-center`,children:[e(V,{className:`h-8 w-8 ${ae.candidate?"text-red-400":"text-amber-400"} mb-2`}),e("p",{className:"text-xs text-gray-500",children:"Hình ảnh dành cho thí sinh"})]}),ae.candidate&&e("p",{className:"text-sm font-medium text-destructive",children:ae.candidate})]}),r("div",{className:"space-y-2",children:[r(o,{className:"flex items-center gap-2 text-sm font-semibold text-gray-800",children:["📷 Examiner's Copy Image ",e("span",{className:"text-red-500",children:"*"})]}),e(N,{id:`examiner-img-group-${ye}`,label:"",accept:"image/*",value:Y||"",onChange:e=>ee(e),maxSize:5,placeholder:"Upload Examiner's Copy",autoUpload:!0,prefix:"questions/compare-images"}),Y&&e(G,{imageUrl:Y,label:"Examiner's Copy"}),!Y&&r("div",{className:`flex flex-col items-center justify-center rounded-xl border-2 border-dashed ${ae.examiner?"border-red-300 bg-red-50/50":"border-orange-200 bg-orange-50/50"} py-6 px-4 text-center`,children:[e(V,{className:`h-8 w-8 ${ae.examiner?"text-red-400":"text-orange-400"} mb-2`}),e("p",{className:"text-xs text-gray-500",children:"Hình ảnh dành cho giám khảo"})]}),ae.examiner&&e("p",{className:"text-sm font-medium text-destructive",children:ae.examiner})]})]})})]}:{className:"overflow-hidden border-0 bg-white shadow shadow-amber-100/30",children:[e("div",{className:"h-1 bg-gradient-to-r from-amber-400 to-orange-400"}),e(d,{className:"pb-3",children:r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-amber-100",children:e(V,{className:"h-3.5 w-3.5 text-amber-600"})}),r("div",{className:"flex-1",children:[r("div",{className:"flex items-center gap-2",children:[e(c,{className:"text-lg font-semibold text-gray-800",children:"Hình ảnh so sánh"}),r("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 ",ye," — 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(l,{className:"pt-0",children:X||Y?r("div",{className:"space-y-4",children:[X&&r("div",{className:"space-y-2",children:[e("div",{className:"flex justify-center",children:e("span",{className:"inline-block rounded-lg border-2 border-amber-400 bg-gradient-to-r from-gray-600 to-gray-700 px-8 py-2 text-sm font-bold uppercase tracking-widest text-white shadow-lg ring-1 ring-amber-300/50",children:"Candidate's Copy"})}),e(G,{imageUrl:X,label:"Candidate's Copy"})]}),Y&&r("div",{className:"space-y-2",children:[e("div",{className:"flex justify-center",children:e("span",{className:"inline-block rounded-lg border-2 border-orange-400 bg-gradient-to-r from-gray-600 to-gray-700 px-8 py-2 text-sm font-bold uppercase tracking-widest text-white shadow-lg ring-1 ring-orange-300/50",children:"Examiner's Copy"})}),e(G,{imageUrl:Y,label:"Examiner's Copy"})]})]}):e("div",{className:"text-center py-4 text-sm text-gray-400 italic",children:"Chưa có hình ảnh nào trong nhóm"})})]}),r(p,{...ie,children:[r(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-400 to-purple-400"}),e(d,{className:"pb-4",children:r("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(S,{className:"h-4 w-4 text-white"})}),r("div",{children:[e(c,{className:"text-lg font-semibold text-gray-800",children:"Câu hỏi Nghe & So sánh"}),e("p",{className:"text-sm text-gray-500",children:"Học sinh nghe âm thanh và nói điểm khác biệt giữa hai hình ảnh"})]})]})}),r(l,{className:"space-y-6",children:[e("div",{className:"rounded-xl border border-gray-200 bg-gray-50 p-4",children:r("div",{className:"flex flex-col md:flex-row gap-6",children:[e("div",{className:"flex-1",children:e(v,{...ie.register("points",{valueAsNumber:!0}),error:ie.formState.errors?.points?.message})}),e("div",{className:"flex-1 flex flex-col justify-center pt-2"})]})}),r("div",{className:"space-y-2",children:[r(o,{className:"flex items-center gap-2 text-sm font-semibold text-gray-800",children:[e(j,{className:"h-4 w-4 text-indigo-500"}),"Âm thanh câu hỏi ",e("span",{className:"text-red-500",children:"*"})]}),e(x,{control:ne,name:"audioUrl",render:({field:a})=>r(f,{children:[e(u,{children:e(N,{id:`audio-compare-${we}`,label:"",accept:"audio/*",value:a.value,onChange:e=>{a.onChange(e),ce("audioUrl")},maxSize:10,placeholder:"Tải lên file âm thanh (mp3, wav...)",autoUpload:!0,prefix:"listen-and-speak"})}),e(b,{})]})})]})]})]}),r(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:r("div",{className:"flex items-center justify-between",children:[r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-green-500 to-emerald-600 shadow-sm",children:e(q,{className:"h-4 w-4 text-white"})}),e("div",{children:r(c,{className:"text-lg font-semibold text-gray-800",children:["Đáp án mong muốn (",me.filter(e=>e.trim()).length,")"]})})]}),r(m,{type:"button",variant:"outline",size:"sm",onClick:()=>{const e=ie.getValues("expectedAnswers");ie.setValue("expectedAnswers",[...e,""],{shouldValidate:!0,shouldDirty:!0})},className:"gap-2 border-green-200 text-green-600 hover:bg-green-50",children:[e(I,{className:"h-4 w-4"}),"Thêm đáp án"]})]})}),r(l,{className:"space-y-4",children:[r("div",{className:"flex items-start gap-2 rounded-lg border border-blue-100 bg-blue-50/60 px-3 py-2.5",children:[e(H,{className:"h-4 w-4 text-blue-500 mt-0.5 flex-shrink-0"}),r("div",{className:"text-xs text-blue-700 space-y-1",children:[e("p",{children:"Nhập các câu trả lời mong muốn. Có thể thêm nhiều đáp án nếu có nhiều cách trả lời đúng."}),r("p",{children:[e("span",{className:"font-semibold",children:"• Chấm thủ công:"})," Giáo viên dùng các đáp án này làm tham khảo để chấm điểm."]}),r("p",{children:[e("span",{className:"font-semibold",children:"• Chấm tự động (AI):"})," Hệ thống dùng AI so sánh giọng nói của học sinh với các đáp án để tự động chấm điểm."]})]})]}),r("div",{className:"space-y-3",children:[me.map((a,t)=>r("div",{className:"flex items-start gap-3 group/answer",children:[e("div",{className:"flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-green-100 text-green-700 font-semibold shadow-sm text-sm",children:t+1}),r("div",{className:"flex-1",children:[e(h,{placeholder:"VD: "+(0===t?"The tree is bigger in picture A":"The dog is brown in picture B"),value:a||"",onChange:e=>((e,r)=>{const a=[...me];a[e]=r,ie.setValue("expectedAnswers",a,{shouldValidate:!0,shouldDirty:!0})})(t,e.target.value),className:"border-gray-200 focus:border-green-300 focus:ring-green-200"}),ie.formState.errors?.expectedAnswers?.[t]?.message&&e("p",{className:"mt-1 text-sm text-red-500",children:ie.formState.errors.expectedAnswers[t]?.message})]}),me.length>1&&e(m,{type:"button",variant:"ghost",size:"sm",onClick:()=>(e=>{if(me.length>1){const r=me.filter((r,a)=>a!==e);ie.setValue("expectedAnswers",r,{shouldValidate:!0,shouldDirty:!0})}})(t),className:"text-red-400 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/answer:opacity-100 transition-opacity",children:e(A,{className:"h-4 w-4"})})]},t)),ie.formState.errors?.expectedAnswers&&!Array.isArray(ie.formState.errors.expectedAnswers)&&e("p",{className:"text-sm text-red-500",children:ie.formState.errors.expectedAnswers.message})]})]})]}),r(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:r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm",children:e(B,{className:"h-4 w-4 text-white"})}),r("div",{children:[r("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(l,{children:e(g,{...ie.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})})]})]})]})}