@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,238 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useRef, useEffect } from 'react';
4
- import { Button } from '../../../../components/ui/button';
5
- import { Label } from '../../../../components/ui/label';
6
- import { Input } from '../../../../components/ui/input';
7
- import { Textarea } from '../../../../components/ui/textarea';
8
- import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
9
- import { PointsInput } from '../../../../components/ui/points-input';
10
- import { RadioGroup } from '../../../../components/ui/radio-group';
11
- import { Pencil, Eye, Plus, Trash2, ImageIcon, CheckCircle2, Lightbulb, MessageSquare, BookOpen } from 'lucide-react';
12
- import { FileUpload } from '../../../../components/ui/file-upload';
13
- import { ImagePreview } from '../../../../components/ui/image-preview';
14
- import { usePresignedFileUrl, useDebouncedCallback } from '../../../../shared/lib/hooks';
15
- import { LookPictureFillBlankChooseAnswerClient } from './LookPictureFillBlankChooseAnswerClient';
16
- import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
17
- import { lookPictureFillBlankChooseAnswerFormSchema, } from '../../../../shared/lib/validation/look-picture-fill-blank-choose-answer.validation';
18
- function LookPictureFillBlankChooseAnswerContent({ initialData, onSave, onCancel, onChange, onUnsavedChangesChange, optionCount = 4, validationRef, }) {
19
- // Helper to normalize options - ensure they are always string[]
20
- const normalizeOptions = (opts, defaultCount) => {
21
- if (!opts || opts.length === 0) {
22
- return Array.from({ length: defaultCount }, () => '');
23
- }
24
- // Options should always be string[] - convert each to string for safety
25
- return opts.map((opt) => String(opt || ''));
26
- };
27
- // Use react-hook-form with Zod validation
28
- // initialData comes from the router in flat format:
29
- // { imageUrl, question, options: ["A", "B", "C"], correctAnswer: 1, explanation, points }
30
- const apiData = initialData;
31
- const rawOptions = apiData?.options || [];
32
- const defaultValues = {
33
- imageUrl: apiData?.imageUrl || '',
34
- question: apiData?.question || '',
35
- options: normalizeOptions(rawOptions, optionCount),
36
- correctAnswer: apiData?.correctAnswer ?? -1,
37
- explanation: apiData?.explanation || '',
38
- points: apiData?.points ?? 1,
39
- };
40
- const form = useReactHookForm({
41
- defaultValues,
42
- zodSchema: lookPictureFillBlankChooseAnswerFormSchema,
43
- mode: 'onBlur',
44
- });
45
- // UI State
46
- const [isClientMode, setIsClientMode] = useState(false);
47
- const [imagePreviewUrl, setImagePreviewUrl] = useState('');
48
- // Track previous initialData to detect changes
49
- const previousInitialDataRef = useRef(initialData);
50
- const isInitializingRef = useRef(false);
51
- const onChangeRef = useRef(onChange);
52
- const previousPayloadRef = useRef('');
53
- // Update onChange ref when it changes
54
- useEffect(() => {
55
- onChangeRef.current = onChange;
56
- }, [onChange]);
57
- // Debounced onChange callback to prevent excessive re-renders
58
- const debouncedOnChange = useDebouncedCallback((data) => {
59
- onChangeRef.current?.(data);
60
- }, 300);
61
- // Watch form values - MUST be declared before useEffects that use them
62
- const watchedImageUrl = form.watch('imageUrl');
63
- const watchedQuestion = form.watch('question');
64
- const watchedOptions = form.watch('options');
65
- const watchedCorrectAnswer = form.watch('correctAnswer');
66
- const watchedExplanation = form.watch('explanation');
67
- const watchedPoints = form.watch('points');
68
- // Custom hooks for presigned URL
69
- const { previewUrl: generatedPreviewUrl } = usePresignedFileUrl(watchedImageUrl);
70
- const displayPreviewUrl = imagePreviewUrl || generatedPreviewUrl || '';
71
- // NOTE: Status tracking is now handled entirely by ExamCreator via localStorage
72
- // No need to track originalSavedData in Creator component
73
- // NOTE: Unsaved changes tracking has been REMOVED from Creator component
74
- // Status is now managed entirely by ExamCreator via localStorage (exam_question_status_{id})
75
- // The onChange callback triggers handleQuestionDraftChange in ExamCreator which updates localStorage
76
- // Expose validation function and getFormData via ref
77
- useEffect(() => {
78
- if (validationRef) {
79
- validationRef.current = {
80
- triggerValidation: async () => {
81
- const result = await form.trigger();
82
- // Also check correctAnswer
83
- if (watchedCorrectAnswer === -1) {
84
- form.setError('correctAnswer', { message: 'Vui lòng chọn đáp án đúng!' });
85
- return false;
86
- }
87
- return result;
88
- },
89
- // Return current form data immediately (not debounced)
90
- // This allows parent to get fresh data at save time
91
- getFormData: () => ({
92
- imageUrl: watchedImageUrl,
93
- question: watchedQuestion,
94
- options: watchedOptions,
95
- correctAnswer: watchedCorrectAnswer,
96
- explanation: watchedExplanation,
97
- points: watchedPoints,
98
- }),
99
- };
100
- }
101
- }, [validationRef, form, watchedCorrectAnswer, watchedImageUrl, watchedQuestion, watchedOptions, watchedExplanation, watchedPoints]);
102
- // Sync state from initialData when it changes from EXTERNAL sources only
103
- // initialData comes from the router already in flat format
104
- useEffect(() => {
105
- const apiData = initialData;
106
- const normalizedInitialData = {
107
- imageUrl: apiData?.imageUrl || '',
108
- question: apiData?.question || '',
109
- options: normalizeOptions(apiData?.options || [], optionCount),
110
- correctAnswer: apiData?.correctAnswer ?? -1,
111
- explanation: apiData?.explanation || '',
112
- points: apiData?.points ?? 1,
113
- };
114
- const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
115
- if (!initialDataChanged) {
116
- return;
117
- }
118
- previousInitialDataRef.current = initialData;
119
- const currentFormData = {
120
- imageUrl: watchedImageUrl,
121
- question: watchedQuestion,
122
- options: watchedOptions,
123
- correctAnswer: watchedCorrectAnswer,
124
- explanation: watchedExplanation,
125
- points: watchedPoints,
126
- };
127
- const isEchoFromOnChange = currentFormData.imageUrl === normalizedInitialData.imageUrl &&
128
- currentFormData.question === normalizedInitialData.question &&
129
- currentFormData.correctAnswer === normalizedInitialData.correctAnswer &&
130
- currentFormData.points === normalizedInitialData.points &&
131
- currentFormData.explanation === normalizedInitialData.explanation &&
132
- JSON.stringify(currentFormData.options) === JSON.stringify(normalizedInitialData.options);
133
- const wasJustSaved = initialData?.isCompleted === true;
134
- if (isEchoFromOnChange && wasJustSaved) {
135
- return;
136
- }
137
- const shouldReset = !isEchoFromOnChange &&
138
- normalizedInitialData.correctAnswer !== currentFormData.correctAnswer;
139
- if (shouldReset && normalizedInitialData.correctAnswer !== -1) {
140
- isInitializingRef.current = true;
141
- form.reset(normalizedInitialData);
142
- setTimeout(() => {
143
- isInitializingRef.current = false;
144
- }, 150);
145
- }
146
- }, [initialData, optionCount, form, watchedImageUrl, watchedQuestion, watchedOptions, watchedCorrectAnswer, watchedExplanation, watchedPoints]);
147
- // Convert to payload format and call onChange
148
- // NOTE: We need to stringify options for proper change detection since array reference doesn't change
149
- const watchedOptionsString = JSON.stringify(watchedOptions);
150
- useEffect(() => {
151
- if (isInitializingRef.current) {
152
- const currentPayload = JSON.stringify({
153
- imageUrl: watchedImageUrl,
154
- question: watchedQuestion,
155
- options: watchedOptions,
156
- correctAnswer: watchedCorrectAnswer,
157
- explanation: watchedExplanation,
158
- points: watchedPoints,
159
- });
160
- previousPayloadRef.current = currentPayload;
161
- return;
162
- }
163
- const payload = {
164
- imageUrl: watchedImageUrl,
165
- question: watchedQuestion,
166
- options: watchedOptions,
167
- correctAnswer: watchedCorrectAnswer,
168
- explanation: watchedExplanation,
169
- points: watchedPoints,
170
- };
171
- const payloadString = JSON.stringify(payload);
172
- if (previousPayloadRef.current !== payloadString) {
173
- previousPayloadRef.current = payloadString;
174
- debouncedOnChange(payload);
175
- }
176
- }, [
177
- watchedImageUrl,
178
- watchedQuestion,
179
- watchedOptionsString, // Use stringified version for proper change detection
180
- watchedCorrectAnswer,
181
- watchedExplanation,
182
- watchedPoints,
183
- ]);
184
- // Handle save with validation
185
- const handleSave = form.handleSubmit((data) => {
186
- // Additional validation for correctAnswer
187
- if (data.correctAnswer === -1 || data.correctAnswer >= data.options.length) {
188
- form.setError('correctAnswer', { message: 'Vui lòng chọn đáp án đúng!' });
189
- return;
190
- }
191
- // NOTE: Save status is now managed by ExamCreator via localStorage
192
- onSave?.(data);
193
- });
194
- // Client mode preview
195
- if (isClientMode) {
196
- const questionData = {
197
- imageUrl: displayPreviewUrl || watchedImageUrl,
198
- question: watchedQuestion,
199
- options: watchedOptions.map((text, index) => ({ id: `opt-${index}`, text })),
200
- };
201
- return (_jsx("div", { className: "space-y-6", children: _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx(CardTitle, { children: "Nh\u00ECn tranh, \u0111i\u1EC1n ch\u1ED7 tr\u1ED1ng (Preview)" }), _jsx("p", { className: "mt-1 text-sm text-gray-500", children: "Ch\u1EBF \u0111\u1ED9 xem tr\u01B0\u1EDBc - Hi\u1EC3n th\u1ECB \u0111\u00E1p \u00E1n \u0111\u00FAng" })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(false), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), "Quay l\u1EA1i Edit Mode"] })] }) }), _jsx(CardContent, { children: _jsx(LookPictureFillBlankChooseAnswerClient, { questionData: questionData, isReviewMode: true, userAnswer: watchedCorrectAnswer !== -1 ? `opt-${watchedCorrectAnswer}` : undefined, correctAnswer: watchedCorrectAnswer !== -1 ? `opt-${watchedCorrectAnswer}` : undefined, explanation: watchedExplanation }) })] }) }));
202
- }
203
- // Creator UI
204
- return (_jsxs("div", { className: "space-y-6", children: [_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: [_jsx("div", { className: "space-y-1", 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(ImageIcon, { className: "h-5 w-5 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-xl font-bold text-gray-900", children: "Nh\u00ECn tranh, \u0111i\u1EC1n ch\u1ED7 tr\u1ED1ng" }), _jsx("p", { className: "text-sm text-gray-500", children: "T\u1EA1o c\u00E2u h\u1ECFi v\u1EDBi h\u00ECnh \u1EA3nh v\u00E0 c\u00E1c l\u1EF1a ch\u1ECDn \u0111\u00E1p \u00E1n" })] })] }) }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(true), disabled: !watchedQuestion || watchedOptions.filter((o) => typeof o === 'string' && o.trim()).length < 2, className: "gap-2 border-indigo-200 text-indigo-600 hover:bg-indigo-50 hover:text-indigo-700", children: [_jsx(Eye, { className: "h-4 w-4" }), "Xem tr\u01B0\u1EDBc"] })] }) }), _jsxs(CardContent, { className: "space-y-6", children: [_jsx("div", { className: "rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-4", children: _jsx(PointsInput, { ...form.register('points', { valueAsNumber: true }), error: form.hasError('points') ? form.getFieldError('points') : undefined }) }), _jsx("div", { className: "rounded-xl border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-5 shadow-sm", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-blue-100", children: _jsx(Lightbulb, { className: "h-4 w-4 text-blue-600" }) }), _jsxs("div", { className: "text-sm text-blue-900", children: [_jsx("p", { className: "font-semibold text-blue-800", children: "H\u01B0\u1EDBng d\u1EABn t\u1EA1o c\u00E2u h\u1ECFi" }), _jsxs("ul", { className: "mt-2 space-y-1.5 text-blue-700", children: [_jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-blue-400" }), "S\u1EED d\u1EE5ng ", _jsx("code", { className: "mx-1 rounded bg-blue-100 px-1.5 py-0.5 font-mono text-blue-800", children: '{0}' }), " \u0111\u1EC3 \u0111\u00E1nh d\u1EA5u ch\u1ED7 tr\u1ED1ng"] }), _jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-blue-400" }), "V\u00ED d\u1EE5: \u201CThe cat is ", '{0}', " the chair.\u201D v\u1EDBi \u0111\u00E1p \u00E1n \u201Cunder\u201D"] })] })] })] }) }), _jsxs("div", { children: [_jsx(FileUpload, { id: "question-image", label: "H\u00ECnh \u1EA3nh", accept: "image/*", value: watchedImageUrl, onChange: (url) => {
205
- form.setValue('imageUrl', url, { shouldValidate: true });
206
- }, onPresignedUrlChange: setImagePreviewUrl, maxSize: 5, placeholder: "Upload \u1EA3nh ho\u1EB7c paste URL", required: true, autoUpload: true, prefix: "questions" }), form.hasError('imageUrl') && (_jsx("p", { className: "mt-1 text-sm text-red-600", children: form.getFieldError('imageUrl') })), displayPreviewUrl && (_jsx(ImagePreview, { src: displayPreviewUrl, alt: "Preview", className: "mt-2 h-48 w-full max-w-md rounded-lg border border-gray-200" }))] }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { htmlFor: "question", className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(MessageSquare, { className: "h-4 w-4 text-indigo-500" }), "C\u00E2u h\u1ECFi ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Textarea, { id: "question", ...form.register('question'), placeholder: "V\u00ED d\u1EE5: The cat is {0} the chair.", rows: 2, className: `border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 ${form.hasError('question') ? 'border-red-400 focus:border-red-500 focus:ring-red-200' : ''}` }), form.hasError('question') && (_jsx("p", { className: "mt-1 text-sm text-red-600", children: form.getFieldError('question') })), _jsxs("p", { className: "mt-1 text-xs text-gray-500", children: ["S\u1EED d\u1EE5ng ", '{0}', " \u0111\u1EC3 \u0111\u00E1nh d\u1EA5u ch\u1ED7 tr\u1ED1ng"] })] })] })] }), _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 justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-indigo-100", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5 text-indigo-600" }) }), _jsxs(CardTitle, { className: "text-lg font-semibold text-gray-800", children: ["C\u00E1c l\u1EF1a ch\u1ECDn ", _jsxs("span", { className: "text-sm font-normal text-gray-500", children: ["(", watchedOptions.length, " \u0111\u00E1p \u00E1n)"] })] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => {
207
- const currentOptions = form.getValues('options');
208
- form.setValue('options', [...currentOptions, '']);
209
- }, className: "gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm \u0111\u00E1p \u00E1n"] })] }) }), _jsxs(CardContent, { className: "space-y-4", children: [_jsx(RadioGroup, { value: watchedCorrectAnswer.toString(), onValueChange: (value) => {
210
- form.setValue('correctAnswer', Number(value), { shouldValidate: true });
211
- }, children: watchedOptions.map((option, index) => {
212
- const optionError = form.formState.errors.options?.[index];
213
- const isCorrect = watchedCorrectAnswer === index;
214
- const optionLetter = String.fromCharCode(65 + index);
215
- return (_jsx("div", { className: `group relative rounded-xl border-2 p-4 transition-all duration-200 ${isCorrect
216
- ? 'border-green-300 bg-gradient-to-r from-green-50 to-emerald-50 shadow-md shadow-green-100'
217
- : 'border-gray-200 bg-white hover:border-gray-300 hover:shadow-sm'}`, children: _jsxs("div", { className: "flex items-start gap-4", children: [_jsx("div", { className: `flex h-10 w-10 flex-shrink-0 cursor-pointer items-center justify-center rounded-xl font-bold transition-all ${isCorrect
218
- ? 'bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-md'
219
- : 'bg-gray-100 text-gray-600 hover:bg-indigo-100 hover:text-indigo-600'}`, onClick: () => form.setValue('correctAnswer', index, { shouldValidate: true }), children: optionLetter }), _jsxs("div", { className: "flex-1 space-y-2", children: [_jsx(Input, { id: `option-input-${index}`, ...form.register(`options.${index}`), placeholder: `Nhập đáp án ${optionLetter}...`, className: `border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 ${optionError ? 'border-red-400 focus:border-red-500 focus:ring-red-200' : ''}` }), optionError && (_jsx("p", { className: "text-sm text-red-600", children: optionError.message })), isCorrect && (_jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full bg-green-100 px-3 py-1 text-xs font-semibold text-green-700", children: [_jsx(CheckCircle2, { className: "h-3.5 w-3.5" }), "\u0110\u00E1p \u00E1n \u0111\u00FAng"] })), isCorrect && option && watchedQuestion.includes('{0}') && (_jsxs("div", { className: "mt-2 rounded-lg border border-blue-200 bg-blue-50/50 px-4 py-3", children: [_jsxs("div", { className: "mb-1.5 flex items-center gap-1.5 text-xs font-semibold text-blue-600", children: [_jsx(Eye, { className: "h-3.5 w-3.5" }), "Xem tr\u01B0\u1EDBc c\u00E2u ho\u00E0n ch\u1EC9nh"] }), _jsx("div", { className: "text-sm text-gray-700", children: watchedQuestion.split('{0}').map((part, idx, array) => (_jsxs("span", { children: [part, idx < array.length - 1 && (_jsx("span", { className: "mx-0.5 rounded bg-green-100 px-1.5 py-0.5 font-semibold text-green-700", children: option }))] }, idx))) })] }))] }), _jsxs("div", { className: "flex items-center gap-2", children: [!isCorrect && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => form.setValue('correctAnswer', index, { shouldValidate: true }), className: "text-gray-400 hover:text-green-600", title: "Ch\u1ECDn l\u00E0m \u0111\u00E1p \u00E1n \u0111\u00FAng", children: _jsx(CheckCircle2, { className: "h-4 w-4" }) })), watchedOptions.length > 2 && (_jsx(Button, { variant: "ghost", size: "sm", onClick: () => {
220
- const currentOptions = form.getValues('options');
221
- const newOptions = currentOptions.filter((_, i) => i !== index);
222
- form.setValue('options', newOptions);
223
- // Adjust correctAnswer if needed
224
- const currentCorrect = form.getValues('correctAnswer');
225
- if (currentCorrect === index) {
226
- form.setValue('correctAnswer', -1);
227
- }
228
- else if (currentCorrect > index) {
229
- form.setValue('correctAnswer', currentCorrect - 1);
230
- }
231
- }, className: "text-red-500 hover:bg-red-50 hover:text-red-600", children: _jsx(Trash2, { className: "h-4 w-4" }) }))] })] }) }, index));
232
- }) }), watchedCorrectAnswer === -1 && (_jsxs("div", { className: "flex items-start gap-3 rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-amber-100", children: _jsx("span", { className: "text-lg", children: "\u26A0\uFE0F" }) }), _jsxs("div", { className: "text-sm text-amber-800", children: [_jsx("p", { className: "font-semibold", children: "Ch\u01B0a ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng" }), _jsx("p", { className: "mt-0.5 text-amber-700", children: "Click v\u00E0o \u00F4 ch\u1EEF c\u00E1i ho\u1EB7c icon \u2713 \u0111\u1EC3 ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng" })] })] })), form.hasError('correctAnswer') && (_jsx("div", { className: "rounded-xl border border-red-200 bg-red-50 p-4", children: _jsx("p", { className: "text-sm text-red-600", children: form.getFieldError('correctAnswer') }) }))] })] }), _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...", rows: 3, className: "min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none" }) })] })] }));
233
- }
234
- export function LookPictureFillBlankChooseAnswerCreator(props) {
235
- return _jsx(LookPictureFillBlankChooseAnswerContent, { ...props });
236
- }
237
- // Legacy export for backward compatibility
238
- export const LookPictureFillBlankChooseAnswer = LookPictureFillBlankChooseAnswerCreator;
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useState as t,useRef as n,useEffect as i}from"react";import{Button as s}from"../../../../components/ui/button";import{Label as o}from"../../../../components/ui/label";import{Input as a}from"../../../../components/ui/input";import{Textarea as l}from"../../../../components/ui/textarea";import{Card as c,CardContent as d,CardHeader as m,CardTitle as h}from"../../../../components/ui/card";import{PointsInput as g}from"../../../../components/ui/points-input";import{RadioGroup as p}from"../../../../components/ui/radio-group";import{Pencil as u,Eye as x,Plus as b,Trash2 as f,ImageIcon as N,CheckCircle2 as w,Lightbulb as v,MessageSquare as y,BookOpen as A}from"lucide-react";import{FileUpload as C}from"../../../../components/ui/file-upload";import{ImagePreview as k}from"../../../../components/ui/image-preview";import{usePresignedFileUrl as V,useDebouncedCallback as q}from"../../../../shared/lib/hooks";import{LookPictureFillBlankChooseAnswerClient as U}from"./LookPictureFillBlankChooseAnswerClient";import{useReactHookForm as S}from"../../../../shared/lib/hooks/useReactHookForm";import{lookPictureFillBlankChooseAnswerFormSchema as j}from"../../../../shared/lib/validation/look-picture-fill-blank-choose-answer.validation";function E({initialData:E,onSave:F,onCancel:z,onChange:J,onUnsavedChangesChange:O,optionCount:P=4,validationRef:$}){const B=(e,r)=>e&&0!==e.length?e.map(e=>String(e||"")):Array.from({length:r},()=>""),L=E,T=L?.options||[],D={imageUrl:L?.imageUrl||"",question:L?.question||"",options:B(T,P),correctAnswer:L?.correctAnswer??-1,explanation:L?.explanation||"",points:L?.points??1},H=S({defaultValues:D,zodSchema:j,mode:"onBlur"}),[R,G]=t(!1),[M,X]=t(""),K=n(E),Q=n(!1),I=n(J),W=n("");i(()=>{I.current=J},[J]);const Y=q(e=>{I.current?.(e)},300),Z=H.watch("imageUrl"),_=H.watch("question"),ee=H.watch("options"),re=H.watch("correctAnswer"),te=H.watch("explanation"),ne=H.watch("points"),{previewUrl:ie}=V(Z),se=M||ie||"";i(()=>{$&&($.current={triggerValidation:async()=>{const e=await H.trigger();return-1===re?(H.setError("correctAnswer",{message:"Vui lòng chọn đáp án đúng!"}),!1):e},getFormData:()=>({imageUrl:Z,question:_,options:ee,correctAnswer:re,explanation:te,points:ne})})},[$,H,re,Z,_,ee,te,ne]),i(()=>{const e=E,r={imageUrl:e?.imageUrl||"",question:e?.question||"",options:B(e?.options||[],P),correctAnswer:e?.correctAnswer??-1,explanation:e?.explanation||"",points:e?.points??1};if(!(JSON.stringify(K.current)!==JSON.stringify(E)))return;K.current=E;const t={imageUrl:Z,question:_,options:ee,correctAnswer:re,explanation:te,points:ne},n=t.imageUrl===r.imageUrl&&t.question===r.question&&t.correctAnswer===r.correctAnswer&&t.points===r.points&&t.explanation===r.explanation&&JSON.stringify(t.options)===JSON.stringify(r.options);if(n&&!0===E?.isCompleted)return;!n&&r.correctAnswer!==t.correctAnswer&&-1!==r.correctAnswer&&(Q.current=!0,H.reset(r),setTimeout(()=>{Q.current=!1},150))},[E,P,H,Z,_,ee,re,te,ne]);const oe=JSON.stringify(ee);i(()=>{if(Q.current){const e=JSON.stringify({imageUrl:Z,question:_,options:ee,correctAnswer:re,explanation:te,points:ne});return void(W.current=e)}const e={imageUrl:Z,question:_,options:ee,correctAnswer:re,explanation:te,points:ne},r=JSON.stringify(e);W.current!==r&&(W.current=r,Y(e))},[Z,_,oe,re,te,ne]);H.handleSubmit(e=>{-1===e.correctAnswer||e.correctAnswer>=e.options.length?H.setError("correctAnswer",{message:"Vui lòng chọn đáp án đúng!"}):F?.(e)});if(R){const t={imageUrl:se||Z,question:_,options:ee.map((e,r)=>({id:`opt-${r}`,text:e}))};return e("div",{className:"space-y-6",children:r(c,{children:[e(m,{children:r("div",{className:"flex items-center justify-between",children:[r("div",{children:[e(h,{children:"Nhìn tranh, điền chỗ trống (Preview)"}),e("p",{className:"mt-1 text-sm text-gray-500",children:"Chế độ xem trước - Hiển thị đáp án đúng"})]}),r(s,{variant:"outline",size:"sm",onClick:()=>G(!1),children:[e(u,{className:"mr-2 h-4 w-4"}),"Quay lại Edit Mode"]})]})}),e(d,{children:e(U,{questionData:t,isReviewMode:!0,userAnswer:-1!==re?`opt-${re}`:void 0,correctAnswer:-1!==re?`opt-${re}`:void 0,explanation:te})})]})})}return r("div",{className:"space-y-6",children:[r(c,{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(m,{className:"pb-4",children:r("div",{className:"flex items-center justify-between",children:[e("div",{className:"space-y-1",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(N,{className:"h-5 w-5 text-white"})}),r("div",{children:[e(h,{className:"text-xl font-bold text-gray-900",children:"Nhìn tranh, điền chỗ trống"}),e("p",{className:"text-sm text-gray-500",children:"Tạo câu hỏi với hình ảnh và các lựa chọn đáp án"})]})]})}),r(s,{variant:"outline",size:"sm",onClick:()=>G(!0),disabled:!_||ee.filter(e=>"string"==typeof e&&e.trim()).length<2,className:"gap-2 border-indigo-200 text-indigo-600 hover:bg-indigo-50 hover:text-indigo-700",children:[e(x,{className:"h-4 w-4"}),"Xem trước"]})]})}),r(d,{className:"space-y-6",children:[e("div",{className:"rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-4",children:e(g,{...H.register("points",{valueAsNumber:!0}),error:H.hasError("points")?H.getFieldError("points"):void 0})}),e("div",{className:"rounded-xl border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-5 shadow-sm",children:r("div",{className:"flex items-start gap-3",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-blue-100",children:e(v,{className:"h-4 w-4 text-blue-600"})}),r("div",{className:"text-sm text-blue-900",children:[e("p",{className:"font-semibold text-blue-800",children:"Hướng dẫn tạo câu hỏi"}),r("ul",{className:"mt-2 space-y-1.5 text-blue-700",children:[r("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-blue-400"}),"Sử dụng ",e("code",{className:"mx-1 rounded bg-blue-100 px-1.5 py-0.5 font-mono text-blue-800",children:"{0}"})," để đánh dấu chỗ trống"]}),r("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-blue-400"}),"Ví dụ: “The cat is ","{0}"," the chair.” với đáp án “under”"]})]})]})]})}),r("div",{children:[e(C,{id:"question-image",label:"Hình ảnh",accept:"image/*",value:Z,onChange:e=>{H.setValue("imageUrl",e,{shouldValidate:!0})},onPresignedUrlChange:X,maxSize:5,placeholder:"Upload ảnh hoặc paste URL",required:!0,autoUpload:!0,prefix:"questions"}),H.hasError("imageUrl")&&e("p",{className:"mt-1 text-sm text-red-600",children:H.getFieldError("imageUrl")}),se&&e(k,{src:se,alt:"Preview",className:"mt-2 h-48 w-full max-w-md rounded-lg border border-gray-200"})]}),r("div",{className:"space-y-2",children:[r(o,{htmlFor:"question",className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(y,{className:"h-4 w-4 text-indigo-500"}),"Câu hỏi ",e("span",{className:"text-red-500",children:"*"})]}),e(l,{id:"question",...H.register("question"),placeholder:"Ví dụ: The cat is {0} the chair.",rows:2,className:"border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 "+(H.hasError("question")?"border-red-400 focus:border-red-500 focus:ring-red-200":"")}),H.hasError("question")&&e("p",{className:"mt-1 text-sm text-red-600",children:H.getFieldError("question")}),r("p",{className:"mt-1 text-xs text-gray-500",children:["Sử dụng ","{0}"," để đánh dấu chỗ trống"]})]})]})]}),r(c,{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(m,{className:"pb-4",children:r("div",{className:"flex items-center justify-between",children:[r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-indigo-100",children:e(v,{className:"h-3.5 w-3.5 text-indigo-600"})}),r(h,{className:"text-lg font-semibold text-gray-800",children:["Các lựa chọn ",r("span",{className:"text-sm font-normal text-gray-500",children:["(",ee.length," đáp án)"]})]})]}),r(s,{variant:"outline",size:"sm",onClick:()=>{const e=H.getValues("options");H.setValue("options",[...e,""])},className:"gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700",children:[e(b,{className:"h-4 w-4"}),"Thêm đáp án"]})]})}),r(d,{className:"space-y-4",children:[e(p,{value:re.toString(),onValueChange:e=>{H.setValue("correctAnswer",Number(e),{shouldValidate:!0})},children:ee.map((t,n)=>{const i=H.formState.errors.options?.[n],o=re===n,l=String.fromCharCode(65+n);return e("div",{className:"group relative rounded-xl border-2 p-4 transition-all duration-200 "+(o?"border-green-300 bg-gradient-to-r from-green-50 to-emerald-50 shadow-md shadow-green-100":"border-gray-200 bg-white hover:border-gray-300 hover:shadow-sm"),children:r("div",{className:"flex items-start gap-4",children:[e("div",{className:"flex h-10 w-10 flex-shrink-0 cursor-pointer items-center justify-center rounded-xl font-bold transition-all "+(o?"bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-md":"bg-gray-100 text-gray-600 hover:bg-indigo-100 hover:text-indigo-600"),onClick:()=>H.setValue("correctAnswer",n,{shouldValidate:!0}),children:l}),r("div",{className:"flex-1 space-y-2",children:[e(a,{id:`option-input-${n}`,...H.register(`options.${n}`),placeholder:`Nhập đáp án ${l}...`,className:"border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 "+(i?"border-red-400 focus:border-red-500 focus:ring-red-200":"")}),i&&e("p",{className:"text-sm text-red-600",children:i.message}),o&&r("span",{className:"inline-flex items-center gap-1.5 rounded-full bg-green-100 px-3 py-1 text-xs font-semibold text-green-700",children:[e(w,{className:"h-3.5 w-3.5"}),"Đáp án đúng"]}),o&&t&&_.includes("{0}")&&r("div",{className:"mt-2 rounded-lg border border-blue-200 bg-blue-50/50 px-4 py-3",children:[r("div",{className:"mb-1.5 flex items-center gap-1.5 text-xs font-semibold text-blue-600",children:[e(x,{className:"h-3.5 w-3.5"}),"Xem trước câu hoàn chỉnh"]}),e("div",{className:"text-sm text-gray-700",children:_.split("{0}").map((n,i,s)=>r("span",{children:[n,i<s.length-1&&e("span",{className:"mx-0.5 rounded bg-green-100 px-1.5 py-0.5 font-semibold text-green-700",children:t})]},i))})]})]}),r("div",{className:"flex items-center gap-2",children:[!o&&e(s,{type:"button",variant:"ghost",size:"sm",onClick:()=>H.setValue("correctAnswer",n,{shouldValidate:!0}),className:"text-gray-400 hover:text-green-600",title:"Chọn làm đáp án đúng",children:e(w,{className:"h-4 w-4"})}),ee.length>2&&e(s,{variant:"ghost",size:"sm",onClick:()=>{const e=H.getValues("options").filter((e,r)=>r!==n);H.setValue("options",e);const r=H.getValues("correctAnswer");r===n?H.setValue("correctAnswer",-1):r>n&&H.setValue("correctAnswer",r-1)},className:"text-red-500 hover:bg-red-50 hover:text-red-600",children:e(f,{className:"h-4 w-4"})})]})]})},n)})}),-1===re&&r("div",{className:"flex items-start gap-3 rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-amber-100",children:e("span",{className:"text-lg",children:"⚠️"})}),r("div",{className:"text-sm text-amber-800",children:[e("p",{className:"font-semibold",children:"Chưa chọn đáp án đúng"}),e("p",{className:"mt-0.5 text-amber-700",children:"Click vào ô chữ cái hoặc icon ✓ để chọn đáp án đúng"})]})]}),H.hasError("correctAnswer")&&e("div",{className:"rounded-xl border border-red-200 bg-red-50 p-4",children:e("p",{className:"text-sm text-red-600",children:H.getFieldError("correctAnswer")})})]})]}),r(c,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-amber-400 to-orange-400"}),e(m,{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(A,{className:"h-4 w-4 text-white"})}),r("div",{children:[r("div",{className:"flex items-center gap-2",children:[e(h,{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(d,{children:e(l,{...H.register("explanation"),placeholder:"VD: Câu trả lời đúng là... vì...",rows:3,className:"min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none"})})]})]})}export function LookPictureFillBlankChooseAnswerCreator(r){return e(E,{...r})}export const LookPictureFillBlankChooseAnswer=LookPictureFillBlankChooseAnswerCreator;
@@ -1,2 +1 @@
1
- export * from './LookPictureFillBlankChooseAnswerCreator';
2
- export * from './LookPictureFillBlankChooseAnswerClient';
1
+ export*from"./LookPictureFillBlankChooseAnswerCreator";export*from"./LookPictureFillBlankChooseAnswerClient";
@@ -1,76 +1 @@
1
- import { LookPictureFillBlankChooseAnswer } from './LookPictureFillBlankChooseAnswerCreator';
2
- export const lookPictureFillBlankChooseAnswerRegistration = {
3
- component: LookPictureFillBlankChooseAnswer,
4
- transformInitialData: (initialData) => {
5
- let creatorData = undefined;
6
- const content = initialData?.content;
7
- const answer = initialData?.answer;
8
- const apiPoints = initialData?.total_points
9
- ? parseFloat(initialData.total_points)
10
- : (initialData?.points || answer?.points || 1);
11
- const explanation = initialData?.explanation || answer?.explanation || '';
12
- const normalizeOptions = (opts) => {
13
- if (!Array.isArray(opts) || opts.length === 0)
14
- return [];
15
- return opts.map((opt) => String(opt || ''));
16
- };
17
- const extractCorrectAnswer = () => {
18
- if (typeof answer?.correctAnswer === 'number')
19
- return answer.correctAnswer;
20
- if (typeof initialData?.correctAnswer === 'object' && initialData?.correctAnswer?.answer !== undefined)
21
- return initialData.correctAnswer.answer;
22
- if (typeof initialData?.correct_answer?.answer === 'number')
23
- return initialData.correct_answer.answer;
24
- if (typeof answer?.answer === 'number')
25
- return answer.answer;
26
- return -1;
27
- };
28
- // Priority 1: Data from store
29
- const hasStoreData = answer?.imageUrl !== undefined || answer?.question !== undefined || answer?.options;
30
- if (hasStoreData) {
31
- creatorData = {
32
- imageUrl: answer.imageUrl || '',
33
- question: answer.question || '',
34
- options: normalizeOptions(answer.options || []),
35
- correctAnswer: extractCorrectAnswer(),
36
- explanation,
37
- points: answer.points || apiPoints,
38
- };
39
- }
40
- // Priority 2: Raw API content format
41
- else if (content && (content.imageUrl !== undefined || content.question !== undefined || content.options)) {
42
- creatorData = {
43
- imageUrl: content.imageUrl || '',
44
- question: content.question || '',
45
- options: normalizeOptions(content.options || []),
46
- correctAnswer: extractCorrectAnswer(),
47
- explanation,
48
- points: apiPoints,
49
- };
50
- }
51
- return creatorData;
52
- },
53
- getExtraProps: (ctx) => ({
54
- optionCount: ctx.optionCount ?? undefined,
55
- onUnsavedChangesChange: ctx.onUnsavedChangesChange,
56
- validationRef: ctx.validationRef,
57
- }),
58
- wrapOnSave: (data, ctx) => ({
59
- type: ctx.questionType,
60
- content: ctx.state.content,
61
- points: data?.points ?? ctx.state.points ?? 1,
62
- level: ctx.state.level,
63
- difficulty: ctx.state.difficulty,
64
- skill: ctx.state.skill,
65
- answer: data,
66
- }),
67
- wrapOnChange: (data, ctx) => ({
68
- type: ctx.questionType,
69
- content: ctx.state.content,
70
- points: data?.points ?? ctx.state.points ?? 1,
71
- level: ctx.state.level,
72
- difficulty: ctx.state.difficulty,
73
- skill: ctx.state.skill,
74
- answer: data,
75
- }),
76
- };
1
+ import{LookPictureFillBlankChooseAnswer as t}from"./LookPictureFillBlankChooseAnswerCreator";export const lookPictureFillBlankChooseAnswerRegistration={component:t,transformInitialData:t=>{let e;const n=t?.content,o=t?.answer,s=t?.total_points?parseFloat(t.total_points):t?.points||o?.points||1,i=t?.explanation||o?.explanation||"",r=t=>Array.isArray(t)&&0!==t.length?t.map(t=>String(t||"")):[],a=()=>"number"==typeof o?.correctAnswer?o.correctAnswer:"object"==typeof t?.correctAnswer&&void 0!==t?.correctAnswer?.answer?t.correctAnswer.answer:"number"==typeof t?.correct_answer?.answer?t.correct_answer.answer:"number"==typeof o?.answer?o.answer:-1;return void 0!==o?.imageUrl||void 0!==o?.question||o?.options?e={imageUrl:o.imageUrl||"",question:o.question||"",options:r(o.options||[]),correctAnswer:a(),explanation:i,points:o.points||s}:n&&(void 0!==n.imageUrl||void 0!==n.question||n.options)&&(e={imageUrl:n.imageUrl||"",question:n.question||"",options:r(n.options||[]),correctAnswer:a(),explanation:i,points:s}),e},getExtraProps:t=>({optionCount:t.optionCount??void 0,onUnsavedChangesChange:t.onUnsavedChangesChange,validationRef:t.validationRef}),wrapOnSave:(t,e)=>({type:e.questionType,content:e.state.content,points:t?.points??e.state.points??1,level:e.state.level,difficulty:e.state.difficulty,skill:e.state.skill,answer:t}),wrapOnChange:(t,e)=>({type:e.questionType,content:e.state.content,points:t?.points??e.state.points??1,level:e.state.level,difficulty:e.state.difficulty,skill:e.state.skill,answer:t})};
@@ -1,19 +1 @@
1
- // LOOK_PICTURE_FILL_BLANK_CHOOSE_ANSWER - single question format (same as ChooseTheCorrectAnswer but with image)
2
- // API format: content { imageUrl, question, options: string[] }, correct_answer { answer: number }
3
- export const transformLookPictureFillBlankChooseAnswer = (question) => {
4
- const answerData = question.answer;
5
- if (answerData?.imageUrl !== undefined || answerData?.question !== undefined || answerData?.options) {
6
- const apiContent = {
7
- imageUrl: answerData.imageUrl || '',
8
- question: answerData.question || '',
9
- options: answerData.options || [],
10
- };
11
- return {
12
- apiContent,
13
- apiCorrectAnswer: {
14
- answer: answerData.correctAnswer ?? -1,
15
- },
16
- };
17
- }
18
- return { apiContent: {}, apiCorrectAnswer: {} };
19
- };
1
+ export const transformLookPictureFillBlankChooseAnswer=o=>{const r=o.answer;if(void 0!==r?.imageUrl||void 0!==r?.question||r?.options){return{apiContent:{imageUrl:r.imageUrl||"",question:r.question||"",options:r.options||[]},apiCorrectAnswer:{answer:r.correctAnswer??-1}}}return{apiContent:{},apiCorrectAnswer:{}}};
@@ -1,134 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useEffect, useMemo } from 'react';
4
- import { cn } from '../../../../shared/lib/utils';
5
- import { Input } from '../../../../components/ui/input';
6
- import { Check, X, Lightbulb, Pencil } from 'lucide-react';
7
- import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
8
- export function LookPictureFillWordHintClient({ questionData, userAnswer = {}, isReviewMode = false, autoFillCorrectAnswers = false, explanation, onSubmit, }) {
9
- // Build correct answers map
10
- const correctAnswersMap = useMemo(() => {
11
- return questionData.subQuestions.reduce((acc, sq) => {
12
- acc[sq.id] = sq.correctAnswer;
13
- return acc;
14
- }, {});
15
- }, [questionData.subQuestions]);
16
- const initialAnswers = autoFillCorrectAnswers ? correctAnswersMap : (userAnswer || {});
17
- const [answers, setAnswers] = useState(initialAnswers);
18
- useEffect(() => {
19
- if (autoFillCorrectAnswers) {
20
- setAnswers(correctAnswersMap);
21
- }
22
- else if (userAnswer) {
23
- setAnswers(userAnswer);
24
- }
25
- }, [userAnswer, autoFillCorrectAnswers, correctAnswersMap]);
26
- const handleAnswerChange = (subQuestionId, blankIndex, value) => {
27
- if (isReviewMode)
28
- return;
29
- const currentAnswer = answers[subQuestionId] || '';
30
- const currentParts = currentAnswer.split(',').map(a => a.trim());
31
- const subQuestion = questionData.subQuestions.find(sq => sq.id === subQuestionId);
32
- if (!subQuestion)
33
- return;
34
- const matches = subQuestion.word.match(/\{(\d+)\}/g);
35
- const blankCount = matches ? matches.length : 0;
36
- while (currentParts.length < blankCount) {
37
- currentParts.push('');
38
- }
39
- currentParts[blankIndex] = value.replace(/[^a-zA-Z]/g, '');
40
- const newAnswers = { ...answers, [subQuestionId]: currentParts.join(',') };
41
- setAnswers(newAnswers);
42
- onSubmit?.(newAnswers);
43
- };
44
- const hintLetters = questionData.hintLetters || [];
45
- const isSingleQuestion = questionData.subQuestions.length === 1;
46
- // For single question, use the card layout similar to LookPictureFillBlankChooseAnswer
47
- if (isSingleQuestion) {
48
- const subQuestion = questionData.subQuestions[0];
49
- const userAnswerValue = answers[subQuestion.id] || '';
50
- return (_jsx(SingleQuestionLayout, { subQuestion: subQuestion, userAnswerValue: userAnswerValue, isReviewMode: isReviewMode, hintLetters: hintLetters, explanation: explanation, onAnswerChange: (blankIdx, value) => handleAnswerChange(subQuestion.id, blankIdx, value) }));
51
- }
52
- // Multiple questions - grid layout
53
- return (_jsxs("div", { className: "space-y-4", children: [hintLetters.length > 0 && (_jsxs("div", { className: "flex items-center gap-3 rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-orange-50 px-4 py-3", children: [_jsx(Lightbulb, { className: "h-5 w-5 text-amber-500 flex-shrink-0" }), _jsx("span", { className: "text-sm font-medium text-amber-700", children: "G\u1EE3i \u00FD:" }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: hintLetters.map((letter, i) => (_jsx("span", { className: "inline-flex h-7 w-7 items-center justify-center rounded-md border border-amber-300 bg-white text-sm font-bold text-amber-700", children: letter.toUpperCase() }, i))) })] })), _jsx("div", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3", children: questionData.subQuestions.map((subQuestion, index) => (_jsx(MultiQuestionCard, { subQuestion: subQuestion, index: index, userAnswerValue: answers[subQuestion.id] || '', isReviewMode: isReviewMode, onAnswerChange: (blankIdx, value) => handleAnswerChange(subQuestion.id, blankIdx, value) }, subQuestion.id))) })] }));
54
- }
55
- // Parse word into segments
56
- function parseWord(word, answerParts, correctParts, isReviewMode) {
57
- const segments = [];
58
- let lastIdx = 0;
59
- const regex = /\{(\d+)\}/g;
60
- let match;
61
- while ((match = regex.exec(word)) !== null) {
62
- if (match.index > lastIdx) {
63
- segments.push({ type: 'text', value: word.substring(lastIdx, match.index) });
64
- }
65
- const blankIdx = parseInt(match[1], 10);
66
- const userVal = answerParts[blankIdx] || '';
67
- const correctVal = correctParts[blankIdx] || '';
68
- const isCorrect = userVal.toLowerCase() === correctVal.toLowerCase();
69
- segments.push({
70
- type: 'blank',
71
- value: userVal,
72
- index: blankIdx,
73
- isCorrect: isReviewMode ? isCorrect : undefined,
74
- correctValue: correctVal,
75
- });
76
- lastIdx = match.index + match[0].length;
77
- }
78
- if (lastIdx < word.length) {
79
- segments.push({ type: 'text', value: word.substring(lastIdx) });
80
- }
81
- return segments;
82
- }
83
- // Single question layout - similar to LookPictureFillBlankChooseAnswer
84
- function SingleQuestionLayout({ subQuestion, userAnswerValue, isReviewMode, hintLetters, explanation, onAnswerChange, }) {
85
- const { previewUrl, isLoading } = usePresignedFileUrl(subQuestion.imageUrl);
86
- const correctAnswer = subQuestion.correctAnswer.trim();
87
- const matches = subQuestion.word.match(/\{(\d+)\}/g);
88
- const blankCount = matches ? matches.length : 0;
89
- const userParts = userAnswerValue.split(',').map(a => a.trim());
90
- const correctParts = correctAnswer.split(',').map(a => a.trim());
91
- const isComplete = userParts.filter(p => p.length > 0).length === blankCount;
92
- const isAllCorrect = isReviewMode && isComplete && userAnswerValue.trim().toLowerCase() === correctAnswer.toLowerCase();
93
- const isWrong = isReviewMode && isComplete && !isAllCorrect;
94
- const hasAnswered = userParts.some(p => p.length > 0);
95
- const segments = parseWord(subQuestion.word, userParts, correctParts, isReviewMode);
96
- const imageSrc = previewUrl || (subQuestion.imageUrl?.startsWith('http') ? subQuestion.imageUrl : null);
97
- // Build complete word for preview
98
- const renderWordWithAnswer = (parts) => {
99
- let result = subQuestion.word;
100
- for (let i = 0; i < parts.length; i++) {
101
- result = result.replace(`{${i}}`, parts[i]?.toUpperCase() || '?');
102
- }
103
- return result;
104
- };
105
- return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm transition-all hover:shadow-md", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white 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-teal-500 to-cyan-600 text-white shadow-sm", children: _jsx(Pencil, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nh\u00ECn tranh, \u0111i\u1EC1n ch\u1EEF c\u00E1i c\u00F2n thi\u1EBFu" })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isAllCorrect ? 'bg-green-100 text-green-700' : isWrong ? 'bg-red-100 text-red-700' : 'bg-gray-100 text-gray-600'), children: [isAllCorrect && _jsx(Check, { className: "h-3.5 w-3.5" }), isWrong && _jsx(X, { className: "h-3.5 w-3.5" }), isAllCorrect ? 'Đúng' : isWrong ? 'Sai' : 'Chưa hoàn thành'] }))] }), _jsxs("div", { className: "p-5 space-y-5", children: [(imageSrc || subQuestion.imageUrl) && (_jsxs("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-gradient-to-br from-slate-50 to-gray-100", children: [_jsx("div", { className: "border-b border-gray-100 px-3 py-1.5", children: _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "h-2 w-2 rounded-full bg-red-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-yellow-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-green-400" }), _jsx("span", { className: "ml-2 text-xs font-medium text-gray-500", children: "H\u00ECnh \u1EA3nh" })] }) }), _jsx("div", { className: "flex items-center justify-center p-4", children: isLoading ? (_jsx("div", { className: "flex h-48 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-teal-500" }) })) : (_jsx("img", { src: imageSrc || subQuestion.imageUrl, alt: "Question", className: "max-h-56 w-auto object-contain rounded-md", onError: (e) => {
106
- e.target.src = '/images/placeholder-image.svg';
107
- } })) })] })), hintLetters.length > 0 && (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-amber-200 bg-gradient-to-r from-amber-50 to-orange-50 px-4 py-3", children: [_jsx(Lightbulb, { className: "h-5 w-5 text-amber-500 flex-shrink-0" }), _jsx("span", { className: "text-sm font-medium text-amber-700", children: "G\u1EE3i \u00FD ch\u1EEF c\u00E1i:" }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: hintLetters.map((letter, i) => (_jsx("span", { className: "inline-flex h-8 w-8 items-center justify-center rounded-lg border-2 border-amber-300 bg-white text-sm font-bold text-amber-700 shadow-sm", children: letter.toUpperCase() }, i))) })] })), _jsx("div", { className: "rounded-lg bg-gradient-to-r from-gray-50 to-slate-50 p-4 border border-gray-100", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-teal-100 text-teal-600", children: _jsx("span", { className: "text-xs font-bold", children: "?" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("span", { className: "text-xs font-medium text-gray-500 uppercase tracking-wider", children: "\u0110i\u1EC1n ch\u1EEF c\u00E1i c\u00F2n thi\u1EBFu" }), _jsx("div", { className: "mt-3 flex flex-wrap items-center gap-1", children: segments.map((seg, i) => {
108
- if (seg.type === 'text') {
109
- return (_jsx("span", { className: "text-2xl font-bold text-gray-800 font-mono tracking-wide", children: seg.value }, i));
110
- }
111
- // Blank input
112
- return (_jsxs("div", { className: "relative inline-flex flex-col items-center mx-0.5", children: [_jsx(Input, { type: "text", value: seg.value.toUpperCase(), onChange: (e) => onAnswerChange(seg.index, e.target.value), disabled: isReviewMode, maxLength: 2, placeholder: "?", className: cn('h-11 w-11 rounded-lg border-2 text-center font-mono font-bold text-xl uppercase transition-all', seg.isCorrect === true && 'border-green-400 bg-green-50 text-green-700', seg.isCorrect === false && 'border-red-400 bg-red-50 text-red-700', seg.isCorrect === undefined && 'border-teal-300 bg-teal-50 text-teal-700 focus:border-teal-500 focus:ring-2 focus:ring-teal-200') }), seg.isCorrect === false && (_jsx("span", { className: "absolute -bottom-5 text-xs font-bold text-green-600", children: seg.correctValue?.toUpperCase() }))] }, i));
113
- }) }), isWrong && _jsx("div", { className: "h-4" })] })] }) }), hasAnswered && !isReviewMode && (_jsxs("div", { className: "rounded-lg bg-gradient-to-r from-teal-50 to-cyan-50 p-4 border border-teal-100", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-teal-500 text-white", children: _jsx(Check, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-xs font-semibold text-teal-700 uppercase tracking-wider", children: "Preview t\u1EEB ho\u00E0n ch\u1EC9nh" })] }), _jsx("p", { className: "text-lg font-mono font-bold text-gray-800 tracking-wide", children: renderWordWithAnswer(userParts) })] })), isReviewMode && isWrong && (_jsxs("div", { className: "rounded-lg bg-gradient-to-r from-green-50 to-emerald-50 p-4 border border-green-200", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-green-500 text-white", children: _jsx(Check, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-xs font-semibold text-green-700 uppercase tracking-wider", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" })] }), _jsx("p", { className: "text-lg font-mono font-bold text-gray-800 tracking-wide", children: renderWordWithAnswer(correctParts) })] })), !isReviewMode && !hasAnswered && (_jsxs("div", { className: "rounded-lg border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-3 flex items-center gap-2", children: [_jsx("div", { className: "flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-white", children: _jsx("span", { className: "text-xs font-bold", children: "!" }) }), _jsx("span", { className: "text-sm text-amber-800", children: "Vui l\u00F2ng \u0111i\u1EC1n ch\u1EEF c\u00E1i v\u00E0o c\u00E1c \u00F4 tr\u1ED1ng" })] })), isReviewMode && explanation && (_jsx("div", { className: "rounded-lg bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-blue-500 text-white", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("span", { className: "text-xs font-semibold text-blue-700 uppercase tracking-wider", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-gray-700 leading-relaxed", children: explanation })] })] }) }))] })] }));
114
- }
115
- // Multiple questions - compact card for grid
116
- function MultiQuestionCard({ subQuestion, index, userAnswerValue, isReviewMode, onAnswerChange, }) {
117
- const { previewUrl } = usePresignedFileUrl(subQuestion.imageUrl);
118
- const correctAnswer = subQuestion.correctAnswer.trim();
119
- const matches = subQuestion.word.match(/\{(\d+)\}/g);
120
- const blankCount = matches ? matches.length : 0;
121
- const userParts = userAnswerValue.split(',').map(a => a.trim());
122
- const correctParts = correctAnswer.split(',').map(a => a.trim());
123
- const isComplete = userParts.filter(p => p.length > 0).length === blankCount;
124
- const isAllCorrect = isReviewMode && isComplete && userAnswerValue.trim().toLowerCase() === correctAnswer.toLowerCase();
125
- const isWrong = isReviewMode && isComplete && !isAllCorrect;
126
- const segments = parseWord(subQuestion.word, userParts, correctParts, isReviewMode);
127
- const imageSrc = previewUrl || (subQuestion.imageUrl?.startsWith('http') ? subQuestion.imageUrl : null);
128
- return (_jsxs("div", { className: cn('overflow-hidden rounded-xl border-2 bg-white transition-all', isAllCorrect && 'border-green-400', isWrong && 'border-red-400', !isReviewMode && 'border-gray-200 hover:shadow-md'), children: [_jsxs("div", { className: "relative h-36 bg-gradient-to-br from-slate-50 to-slate-100 flex items-center justify-center", children: [imageSrc ? (_jsx("img", { src: imageSrc, alt: "Question", className: "h-full w-full object-contain p-3" })) : (_jsx("svg", { className: "h-10 w-10 text-gray-300", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) })), _jsx("div", { className: "absolute left-2 top-2 flex h-6 w-6 items-center justify-center rounded-full bg-teal-500 text-xs font-bold text-white", children: index + 1 }), isReviewMode && (isAllCorrect || isWrong) && (_jsx("div", { className: cn('absolute right-2 top-2 rounded-full p-1', isAllCorrect && 'bg-green-500 text-white', isWrong && 'bg-red-500 text-white'), children: isAllCorrect ? _jsx(Check, { className: "h-3 w-3" }) : _jsx(X, { className: "h-3 w-3" }) }))] }), _jsxs("div", { className: "p-3", children: [_jsx("div", { className: "flex flex-wrap items-center justify-center gap-0.5", children: segments.map((seg, i) => {
129
- if (seg.type === 'text') {
130
- return _jsx("span", { className: "text-lg font-bold text-gray-800 font-mono", children: seg.value }, i);
131
- }
132
- return (_jsx(Input, { type: "text", value: seg.value.toUpperCase(), onChange: (e) => onAnswerChange(seg.index, e.target.value), disabled: isReviewMode, maxLength: 2, placeholder: "?", className: cn('h-8 w-8 rounded border-2 text-center font-mono font-bold text-lg uppercase outline-none transition-all', seg.isCorrect === true && 'border-green-400 bg-green-50 text-green-700', seg.isCorrect === false && 'border-red-400 bg-red-50 text-red-700', seg.isCorrect === undefined && 'border-gray-300 focus:border-teal-400') }, i));
133
- }) }), isWrong && (_jsxs("p", { className: "text-center text-xs text-green-600 font-medium mt-2", children: ["\u0110\u00E1p \u00E1n: ", subQuestion.word.replace(/\{(\d+)\}/g, (_, idx) => correctParts[parseInt(idx)]?.toUpperCase() || '')] }))] })] }));
134
- }
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useState as r,useEffect as a,useMemo as s}from"react";import{cn as n}from"../../../../shared/lib/utils";import{Input as l}from"../../../../components/ui/input";import{Check as i,X as o,Lightbulb as d,Pencil as c}from"lucide-react";import{usePresignedFileUrl as m}from"../../../../shared/lib/hooks";export function LookPictureFillWordHintClient({questionData:n,userAnswer:l={},isReviewMode:i=!1,autoFillCorrectAnswers:o=!1,explanation:c,onSubmit:m}){const h=s(()=>n.subQuestions.reduce((e,t)=>(e[t.id]=t.correctAnswer,e),{}),[n.subQuestions]),x=o?h:l||{},[b,p]=r(x);a(()=>{o?p(h):l&&p(l)},[l,o,h]);const f=(e,t,r)=>{if(i)return;const a=(b[e]||"").split(",").map(e=>e.trim()),s=n.subQuestions.find(t=>t.id===e);if(!s)return;const l=s.word.match(/\{(\d+)\}/g),o=l?l.length:0;for(;a.length<o;)a.push("");a[t]=r.replace(/[^a-zA-Z]/g,"");const d={...b,[e]:a.join(",")};p(d),m?.(d)},w=n.hintLetters||[];if(1===n.subQuestions.length){const t=n.subQuestions[0],r=b[t.id]||"";return e(g,{subQuestion:t,userAnswerValue:r,isReviewMode:i,hintLetters:w,explanation:c,onAnswerChange:(e,r)=>f(t.id,e,r)})}return t("div",{className:"space-y-4",children:[w.length>0&&t("div",{className:"flex items-center gap-3 rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-orange-50 px-4 py-3",children:[e(d,{className:"h-5 w-5 text-amber-500 flex-shrink-0"}),e("span",{className:"text-sm font-medium text-amber-700",children:"Gợi ý:"}),e("div",{className:"flex flex-wrap gap-1.5",children:w.map((t,r)=>e("span",{className:"inline-flex h-7 w-7 items-center justify-center rounded-md border border-amber-300 bg-white text-sm font-bold text-amber-700",children:t.toUpperCase()},r))})]}),e("div",{className:"grid gap-4 sm:grid-cols-2 lg:grid-cols-3",children:n.subQuestions.map((t,r)=>e(u,{subQuestion:t,index:r,userAnswerValue:b[t.id]||"",isReviewMode:i,onAnswerChange:(e,r)=>f(t.id,e,r)},t.id))})]})}function h(e,t,r,a){const s=[];let n=0;const l=/\{(\d+)\}/g;let i;for(;null!==(i=l.exec(e));){i.index>n&&s.push({type:"text",value:e.substring(n,i.index)});const l=parseInt(i[1],10),o=t[l]||"",d=r[l]||"",c=o.toLowerCase()===d.toLowerCase();s.push({type:"blank",value:o,index:l,isCorrect:a?c:void 0,correctValue:d}),n=i.index+i[0].length}return n<e.length&&s.push({type:"text",value:e.substring(n)}),s}function g({subQuestion:r,userAnswerValue:a,isReviewMode:s,hintLetters:g,explanation:u,onAnswerChange:x}){const{previewUrl:b,isLoading:p}=m(r.imageUrl),f=r.correctAnswer.trim(),w=r.word.match(/\{(\d+)\}/g),N=w?w.length:0,v=a.split(",").map(e=>e.trim()),y=f.split(",").map(e=>e.trim()),C=v.filter(e=>e.length>0).length===N,j=s&&C&&a.trim().toLowerCase()===f.toLowerCase(),k=s&&C&&!j,L=v.some(e=>e.length>0),U=h(r.word,v,y,s),A=b||(r.imageUrl?.startsWith("http")?r.imageUrl:null),Q=e=>{let t=r.word;for(let r=0;r<e.length;r++)t=t.replace(`{${r}}`,e[r]?.toUpperCase()||"?");return t};return t("div",{className:"overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm transition-all hover:shadow-md",children:[t("div",{className:"flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-5 py-3",children:[t("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-teal-500 to-cyan-600 text-white shadow-sm",children:e(c,{className:"h-4 w-4"})}),e("span",{className:"text-sm font-medium text-gray-700",children:"Nhìn tranh, điền chữ cái còn thiếu"})]}),s&&L&&t("div",{className:n("flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold",j?"bg-green-100 text-green-700":k?"bg-red-100 text-red-700":"bg-gray-100 text-gray-600"),children:[j&&e(i,{className:"h-3.5 w-3.5"}),k&&e(o,{className:"h-3.5 w-3.5"}),j?"Đúng":k?"Sai":"Chưa hoàn thành"]})]}),t("div",{className:"p-5 space-y-5",children:[(A||r.imageUrl)&&t("div",{className:"overflow-hidden rounded-lg border border-gray-200 bg-gradient-to-br from-slate-50 to-gray-100",children:[e("div",{className:"border-b border-gray-100 px-3 py-1.5",children:t("div",{className:"flex items-center gap-1.5",children:[e("div",{className:"h-2 w-2 rounded-full bg-red-400"}),e("div",{className:"h-2 w-2 rounded-full bg-yellow-400"}),e("div",{className:"h-2 w-2 rounded-full bg-green-400"}),e("span",{className:"ml-2 text-xs font-medium text-gray-500",children:"Hình ảnh"})]})}),e("div",{className:"flex items-center justify-center p-4",children:p?e("div",{className:"flex h-48 items-center justify-center",children:e("div",{className:"h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-teal-500"})}):e("img",{src:A||r.imageUrl,alt:"Question",className:"max-h-56 w-auto object-contain rounded-md",onError:e=>{e.target.src="/images/placeholder-image.svg"}})})]}),g.length>0&&t("div",{className:"flex items-center gap-3 rounded-lg border border-amber-200 bg-gradient-to-r from-amber-50 to-orange-50 px-4 py-3",children:[e(d,{className:"h-5 w-5 text-amber-500 flex-shrink-0"}),e("span",{className:"text-sm font-medium text-amber-700",children:"Gợi ý chữ cái:"}),e("div",{className:"flex flex-wrap gap-1.5",children:g.map((t,r)=>e("span",{className:"inline-flex h-8 w-8 items-center justify-center rounded-lg border-2 border-amber-300 bg-white text-sm font-bold text-amber-700 shadow-sm",children:t.toUpperCase()},r))})]}),e("div",{className:"rounded-lg bg-gradient-to-r from-gray-50 to-slate-50 p-4 border border-gray-100",children:t("div",{className:"flex items-start gap-3",children:[e("div",{className:"flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-teal-100 text-teal-600",children:e("span",{className:"text-xs font-bold",children:"?"})}),t("div",{className:"flex-1",children:[e("span",{className:"text-xs font-medium text-gray-500 uppercase tracking-wider",children:"Điền chữ cái còn thiếu"}),e("div",{className:"mt-3 flex flex-wrap items-center gap-1",children:U.map((r,a)=>"text"===r.type?e("span",{className:"text-2xl font-bold text-gray-800 font-mono tracking-wide",children:r.value},a):t("div",{className:"relative inline-flex flex-col items-center mx-0.5",children:[e(l,{type:"text",value:r.value.toUpperCase(),onChange:e=>x(r.index,e.target.value),disabled:s,maxLength:2,placeholder:"?",className:n("h-11 w-11 rounded-lg border-2 text-center font-mono font-bold text-xl uppercase transition-all",!0===r.isCorrect&&"border-green-400 bg-green-50 text-green-700",!1===r.isCorrect&&"border-red-400 bg-red-50 text-red-700",void 0===r.isCorrect&&"border-teal-300 bg-teal-50 text-teal-700 focus:border-teal-500 focus:ring-2 focus:ring-teal-200")}),!1===r.isCorrect&&e("span",{className:"absolute -bottom-5 text-xs font-bold text-green-600",children:r.correctValue?.toUpperCase()})]},a))}),k&&e("div",{className:"h-4"})]})]})}),L&&!s&&t("div",{className:"rounded-lg bg-gradient-to-r from-teal-50 to-cyan-50 p-4 border border-teal-100",children:[t("div",{className:"flex items-center gap-2 mb-2",children:[e("div",{className:"flex h-5 w-5 items-center justify-center rounded-full bg-teal-500 text-white",children:e(i,{className:"h-3 w-3"})}),e("span",{className:"text-xs font-semibold text-teal-700 uppercase tracking-wider",children:"Preview từ hoàn chỉnh"})]}),e("p",{className:"text-lg font-mono font-bold text-gray-800 tracking-wide",children:Q(v)})]}),s&&k&&t("div",{className:"rounded-lg bg-gradient-to-r from-green-50 to-emerald-50 p-4 border border-green-200",children:[t("div",{className:"flex items-center gap-2 mb-2",children:[e("div",{className:"flex h-5 w-5 items-center justify-center rounded-full bg-green-500 text-white",children:e(i,{className:"h-3 w-3"})}),e("span",{className:"text-xs font-semibold text-green-700 uppercase tracking-wider",children:"Đáp án đúng"})]}),e("p",{className:"text-lg font-mono font-bold text-gray-800 tracking-wide",children:Q(y)})]}),!s&&!L&&t("div",{className:"rounded-lg border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-3 flex items-center gap-2",children:[e("div",{className:"flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-white",children:e("span",{className:"text-xs font-bold",children:"!"})}),e("span",{className:"text-sm text-amber-800",children:"Vui lòng điền chữ cái vào các ô trống"})]}),s&&u&&e("div",{className:"rounded-lg bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200",children:t("div",{className:"flex items-start gap-3",children:[e("div",{className:"flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-blue-500 text-white",children:e(d,{className:"h-3.5 w-3.5"})}),t("div",{className:"flex-1",children:[e("span",{className:"text-xs font-semibold text-blue-700 uppercase tracking-wider",children:"Giải thích"}),e("p",{className:"mt-1 text-sm text-gray-700 leading-relaxed",children:u})]})]})})]})]})}function u({subQuestion:r,index:a,userAnswerValue:s,isReviewMode:d,onAnswerChange:c}){const{previewUrl:g}=m(r.imageUrl),u=r.correctAnswer.trim(),x=r.word.match(/\{(\d+)\}/g),b=x?x.length:0,p=s.split(",").map(e=>e.trim()),f=u.split(",").map(e=>e.trim()),w=p.filter(e=>e.length>0).length===b,N=d&&w&&s.trim().toLowerCase()===u.toLowerCase(),v=d&&w&&!N,y=h(r.word,p,f,d),C=g||(r.imageUrl?.startsWith("http")?r.imageUrl:null);return t("div",{className:n("overflow-hidden rounded-xl border-2 bg-white transition-all",N&&"border-green-400",v&&"border-red-400",!d&&"border-gray-200 hover:shadow-md"),children:[t("div",{className:"relative h-36 bg-gradient-to-br from-slate-50 to-slate-100 flex items-center justify-center",children:[C?e("img",{src:C,alt:"Question",className:"h-full w-full object-contain p-3"}):e("svg",{className:"h-10 w-10 text-gray-300",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:1.5,d:"M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"})}),e("div",{className:"absolute left-2 top-2 flex h-6 w-6 items-center justify-center rounded-full bg-teal-500 text-xs font-bold text-white",children:a+1}),d&&(N||v)&&e("div",{className:n("absolute right-2 top-2 rounded-full p-1",N&&"bg-green-500 text-white",v&&"bg-red-500 text-white"),children:e(N?i:o,{className:"h-3 w-3"})})]}),t("div",{className:"p-3",children:[e("div",{className:"flex flex-wrap items-center justify-center gap-0.5",children:y.map((t,r)=>"text"===t.type?e("span",{className:"text-lg font-bold text-gray-800 font-mono",children:t.value},r):e(l,{type:"text",value:t.value.toUpperCase(),onChange:e=>c(t.index,e.target.value),disabled:d,maxLength:2,placeholder:"?",className:n("h-8 w-8 rounded border-2 text-center font-mono font-bold text-lg uppercase outline-none transition-all",!0===t.isCorrect&&"border-green-400 bg-green-50 text-green-700",!1===t.isCorrect&&"border-red-400 bg-red-50 text-red-700",void 0===t.isCorrect&&"border-gray-300 focus:border-teal-400")},r))}),v&&t("p",{className:"text-center text-xs text-green-600 font-medium mt-2",children:["Đáp án: ",r.word.replace(/\{(\d+)\}/g,(e,t)=>f[parseInt(t)]?.toUpperCase()||"")]})]})]})}