@tinyweb_dev/oe-exam-sdk 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (514) hide show
  1. package/dist/api/exam-create.js +1 -179
  2. package/dist/api/exam-create.types.js +1 -1
  3. package/dist/api/exam-questions.d.ts +1 -1
  4. package/dist/api/exam-questions.js +1 -98
  5. package/dist/api/exam-questions.types.d.ts +45 -1
  6. package/dist/api/exam-questions.types.js +1 -1
  7. package/dist/api/exam-taking.js +1 -73
  8. package/dist/api/exam-taking.types.js +1 -1
  9. package/dist/api/index.js +1 -6
  10. package/dist/api/result-review.js +1 -50
  11. package/dist/api/result-review.types.js +1 -1
  12. package/dist/components/CueCardEditor.js +1 -58
  13. package/dist/components/common/ResolvedImage.js +1 -15
  14. package/dist/components/exams/CompletionDialog.d.ts +8 -0
  15. package/dist/components/exams/CompletionDialog.js +1 -0
  16. package/dist/components/exams/CreateExamInfoForm.d.ts +4 -1
  17. package/dist/components/exams/CreateExamInfoForm.js +1 -35
  18. package/dist/components/exams/CreateExamPageContainer.d.ts +3 -0
  19. package/dist/components/exams/CreateExamPageContainer.js +1 -103
  20. package/dist/components/exams/ExamCreator.d.ts +9 -3
  21. package/dist/components/exams/ExamCreator.js +1 -89
  22. package/dist/components/exams/ExamPartTabs.d.ts +1 -1
  23. package/dist/components/exams/ExamPartTabs.js +1 -40
  24. package/dist/components/exams/ExamPreviewDialog.d.ts +20 -0
  25. package/dist/components/exams/ExamPreviewDialog.js +1 -0
  26. package/dist/components/exams/ExamQuestionGrid.d.ts +1 -1
  27. package/dist/components/exams/ExamQuestionGrid.js +1 -18
  28. package/dist/components/exams/ExamQuestionsPageContainer.d.ts +3 -0
  29. package/dist/components/exams/ExamQuestionsPageContainer.js +1 -90
  30. package/dist/components/exams/ExamViewDialog.d.ts +24 -0
  31. package/dist/components/exams/ExamViewDialog.js +1 -0
  32. package/dist/components/exams/PartAudioUploader.js +1 -9
  33. package/dist/components/exams/TemplateSelector.js +1 -66
  34. package/dist/components/exams/UnsavedChangesDialog.d.ts +8 -0
  35. package/dist/components/exams/UnsavedChangesDialog.js +1 -0
  36. package/dist/components/exams/api.d.ts +1 -1
  37. package/dist/components/exams/api.js +1 -3
  38. package/dist/components/exams/create-exam.texts.d.ts +58 -0
  39. package/dist/components/exams/create-exam.texts.js +1 -0
  40. package/dist/components/exams/create-exam.validation.js +1 -18
  41. package/dist/components/exams/exam-question-status.d.ts +9 -0
  42. package/dist/components/exams/exam-question-status.js +1 -0
  43. package/dist/components/exams/exam-template.utils.js +1 -18
  44. package/dist/components/exams/index.d.ts +11 -1
  45. package/dist/components/exams/index.js +1 -10
  46. package/dist/components/exams/take/ExamTakingPageContainer.js +1 -153
  47. package/dist/components/exams/take/GenericQuestionRenderer.js +1 -14
  48. package/dist/components/exams/take/exam-taking.utils.js +1 -88
  49. package/dist/components/exams/take/hooks/useSpeakingRecorder.js +1 -24
  50. package/dist/components/exams/take/index.js +1 -2
  51. package/dist/components/exams/take/types.js +1 -1
  52. package/dist/components/index.js +1 -42
  53. package/dist/components/questions/_shared/config/question-types.config.js +1 -333
  54. package/dist/components/questions/_shared/constants/color-palette.js +1 -21
  55. package/dist/components/questions/_shared/hooks/useAnswerGroupQuestionGroup.js +1 -105
  56. package/dist/components/questions/_shared/hooks/useBasicQuestionGroup.js +1 -222
  57. package/dist/components/questions/_shared/hooks/useCompareImagesGroup.js +1 -140
  58. package/dist/components/questions/_shared/hooks/useImageQuestionGroup.js +1 -123
  59. package/dist/components/questions/_shared/hooks/useInfoExchangeGroup.js +1 -50
  60. package/dist/components/questions/_shared/hooks/useQuestionFormState.js +1 -54
  61. package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +1 -123
  62. package/dist/components/questions/_shared/hooks/useQuestionListGroup.js +1 -121
  63. package/dist/components/questions/_shared/hooks/useQuestionOnChange.js +1 -70
  64. package/dist/components/questions/_shared/hooks/useSceneQuestionGroup.js +1 -194
  65. package/dist/components/questions/_shared/index.js +1 -7
  66. package/dist/components/questions/_shared/types/answer-the-question.type.js +1 -1
  67. package/dist/components/questions/_shared/types/arrange-letters-into-words.type.js +1 -1
  68. package/dist/components/questions/_shared/types/choose-correct-adjective.type.js +1 -3
  69. package/dist/components/questions/_shared/types/color-region.config.js +1 -1
  70. package/dist/components/questions/_shared/types/fill-in-blank.type.js +1 -1
  71. package/dist/components/questions/_shared/types/fill-missing-words-in-grid.type.js +1 -1
  72. package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +1 -1
  73. package/dist/components/questions/_shared/types/index.js +1 -12
  74. package/dist/components/questions/_shared/types/label-the-picture.type.js +1 -1
  75. package/dist/components/questions/_shared/types/listen-and-choose-from-answer-group.type.js +1 -1
  76. package/dist/components/questions/_shared/types/listen-and-choose-objects-in-scene.type.js +1 -1
  77. package/dist/components/questions/_shared/types/listen-and-drag-objects-into-scene.type.js +1 -1
  78. package/dist/components/questions/_shared/types/listen-and-speak-answer.type.js +1 -1
  79. package/dist/components/questions/_shared/types/listen-and-speak-compare-images.type.js +1 -1
  80. package/dist/components/questions/_shared/types/listen-and-speak-image-group.type.js +1 -1
  81. package/dist/components/questions/_shared/types/listen-and-speak-info-exchange.type.js +1 -1
  82. package/dist/components/questions/_shared/types/listen-and-speak-odd-one-out.type.js +1 -3
  83. package/dist/components/questions/_shared/types/listen-and-speak-question-list.type.js +1 -1
  84. package/dist/components/questions/_shared/types/listen-and-speak-with-story-images.type.js +1 -3
  85. package/dist/components/questions/_shared/types/listen-and-tick-answer.type.js +1 -1
  86. package/dist/components/questions/_shared/types/listening-drawline.type.js +1 -1
  87. package/dist/components/questions/_shared/types/look-and-choose-answer.type.js +1 -1
  88. package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +1 -1
  89. package/dist/components/questions/_shared/types/match-by-writing-answer.type.js +1 -1
  90. package/dist/components/questions/_shared/types/multiple-choice.type.js +1 -1
  91. package/dist/components/questions/_shared/types/question-group.type.js +1 -11
  92. package/dist/components/questions/_shared/types/read-and-color-objects.type.js +1 -1
  93. package/dist/components/questions/_shared/types/read-displayed-content.type.js +1 -1
  94. package/dist/components/questions/_shared/types/read-passage-and-answer-questions.type.js +1 -1
  95. package/dist/components/questions/_shared/types/speaking-describe-image.type.js +1 -1
  96. package/dist/components/questions/_shared/types/spontaneous-qa.type.js +1 -1
  97. package/dist/components/questions/_shared/types/word-fill-paragraph-with-options.type.js +1 -1
  98. package/dist/components/questions/_shared/types/word-fill-paragraph.type.js +1 -1
  99. package/dist/components/questions/_shared/types/word-fill-structured-form.type.js +1 -1
  100. package/dist/components/questions/_shared/types/word-ordering.type.js +1 -1
  101. package/dist/components/questions/_shared/types/write-a-short-letter.type.js +1 -1
  102. package/dist/components/questions/_shared/types/write-correct-verb-form.type.js +1 -3
  103. package/dist/components/questions/_shared/types/write-sentences.type.js +1 -1
  104. package/dist/components/questions/_shared/types/write-short-paragraph.type.js +1 -1
  105. package/dist/components/questions/_shared/utils/answer-builder.js +1 -79
  106. package/dist/components/questions/_shared/utils/createColorRegionRegistration.js +1 -39
  107. package/dist/components/questions/_shared/utils/id-generator.js +1 -3
  108. package/dist/components/questions/_shared/utils/question-validation.js +1 -21
  109. package/dist/components/questions/components/ColorNode.js +1 -85
  110. package/dist/components/questions/components/ColorRegionClient.js +1 -545
  111. package/dist/components/questions/components/ColorRegionCreator.js +1 -420
  112. package/dist/components/questions/components/ColorRegionNode.js +1 -59
  113. package/dist/components/questions/components/CreatorGuide.js +1 -103
  114. package/dist/components/questions/components/NameNode.js +1 -26
  115. package/dist/components/questions/components/QuestionBankOption.js +1 -46
  116. package/dist/components/questions/components/QuestionBasicInfoForm.js +1 -9
  117. package/dist/components/questions/components/QuestionCommonFields.js +1 -33
  118. package/dist/components/questions/components/QuestionSearchDropdown.js +1 -258
  119. package/dist/components/questions/components/QuestionTypeSelector.js +1 -32
  120. package/dist/components/questions/components/QuestionTypeSpecificForms.js +1 -6
  121. package/dist/components/questions/components/RegionNode.js +1 -94
  122. package/dist/components/questions/components/index.js +1 -14
  123. package/dist/components/questions/creator/QuestionCreator.js +1 -90
  124. package/dist/components/questions/creator/QuestionGroupCreator.js +1 -119
  125. package/dist/components/questions/creator/dedicated-component-router.js +1 -79
  126. package/dist/components/questions/creator/index.js +1 -3
  127. package/dist/components/questions/creator/question-type-registry.js +1 -80
  128. package/dist/components/questions/groups/ArticlesGroupCard.js +1 -121
  129. package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -50
  130. package/dist/components/questions/groups/DocumentGroupCard.js +1 -114
  131. package/dist/components/questions/groups/MatchAnswerGroupCard.js +1 -25
  132. package/dist/components/questions/index.js +1 -10
  133. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +1 -37
  134. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +1 -351
  135. package/dist/components/questions/types/answer-the-question/register.js +1 -67
  136. package/dist/components/questions/types/answer-the-question/transform.js +1 -36
  137. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +1 -141
  138. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +1 -118
  139. package/dist/components/questions/types/arrange-letters-into-words/index.js +1 -2
  140. package/dist/components/questions/types/arrange-letters-into-words/register.js +1 -4
  141. package/dist/components/questions/types/arrange-letters-into-words/transform.js +1 -24
  142. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -64
  143. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +1 -169
  144. package/dist/components/questions/types/choose-correct-adjective/index.js +1 -2
  145. package/dist/components/questions/types/choose-correct-adjective/register.js +1 -55
  146. package/dist/components/questions/types/choose-correct-adjective/transform.js +1 -16
  147. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +1 -131
  148. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +1 -581
  149. package/dist/components/questions/types/choose-the-correct-answer/index.js +1 -2
  150. package/dist/components/questions/types/choose-the-correct-answer/register.js +1 -101
  151. package/dist/components/questions/types/choose-the-correct-answer/transform.js +1 -43
  152. package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +1 -275
  153. package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +1 -448
  154. package/dist/components/questions/types/fill-in-blank/index.js +1 -2
  155. package/dist/components/questions/types/fill-in-blank/register.js +1 -138
  156. package/dist/components/questions/types/fill-in-blank/transform.js +1 -59
  157. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +1 -117
  158. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +1 -671
  159. package/dist/components/questions/types/fill-missing-words-in-grid/index.js +1 -2
  160. package/dist/components/questions/types/fill-missing-words-in-grid/register.js +1 -37
  161. package/dist/components/questions/types/fill-missing-words-in-grid/transform.js +1 -15
  162. package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +1 -262
  163. package/dist/components/questions/types/gn-speaking-interview/register.js +1 -121
  164. package/dist/components/questions/types/gn-speaking-interview/transform.js +1 -48
  165. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingClient.js +1 -16
  166. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingCreator.js +1 -27
  167. package/dist/components/questions/types/image-object-matching/index.js +1 -2
  168. package/dist/components/questions/types/image-object-matching/register.js +1 -3
  169. package/dist/components/questions/types/image-object-matching/transform.js +1 -2
  170. package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +1 -102
  171. package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +1 -344
  172. package/dist/components/questions/types/label-the-picture/index.js +1 -2
  173. package/dist/components/questions/types/label-the-picture/register.js +1 -54
  174. package/dist/components/questions/types/label-the-picture/transform.js +1 -28
  175. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +1 -78
  176. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +1 -232
  177. package/dist/components/questions/types/listen-and-choose-from-answer-group/index.js +1 -2
  178. package/dist/components/questions/types/listen-and-choose-from-answer-group/register.js +1 -82
  179. package/dist/components/questions/types/listen-and-choose-from-answer-group/transform.js +1 -27
  180. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +1 -70
  181. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +1 -386
  182. package/dist/components/questions/types/listen-and-choose-objects-in-scene/index.js +1 -2
  183. package/dist/components/questions/types/listen-and-choose-objects-in-scene/register.js +1 -100
  184. package/dist/components/questions/types/listen-and-choose-objects-in-scene/transform.js +1 -33
  185. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +1 -106
  186. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +1 -457
  187. package/dist/components/questions/types/listen-and-drag-objects-into-scene/index.js +1 -2
  188. package/dist/components/questions/types/listen-and-drag-objects-into-scene/register.js +1 -110
  189. package/dist/components/questions/types/listen-and-drag-objects-into-scene/transform.js +1 -35
  190. package/dist/components/questions/types/listen-and-speak-answer/ArrowIndicator.js +1 -8
  191. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -22
  192. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +1 -291
  193. package/dist/components/questions/types/listen-and-speak-answer/index.js +1 -2
  194. package/dist/components/questions/types/listen-and-speak-answer/register.js +1 -84
  195. package/dist/components/questions/types/listen-and-speak-answer/transform.js +1 -27
  196. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -32
  197. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +1 -295
  198. package/dist/components/questions/types/listen-and-speak-compare-images/register.js +1 -79
  199. package/dist/components/questions/types/listen-and-speak-compare-images/transform.js +1 -27
  200. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -34
  201. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +1 -285
  202. package/dist/components/questions/types/listen-and-speak-image-group/register.js +1 -81
  203. package/dist/components/questions/types/listen-and-speak-image-group/transform.js +1 -26
  204. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -38
  205. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +1 -385
  206. package/dist/components/questions/types/listen-and-speak-info-exchange/register.js +1 -87
  207. package/dist/components/questions/types/listen-and-speak-info-exchange/transform.js +1 -32
  208. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -26
  209. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +1 -247
  210. package/dist/components/questions/types/listen-and-speak-odd-one-out/register.js +1 -59
  211. package/dist/components/questions/types/listen-and-speak-odd-one-out/transform.js +1 -18
  212. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -26
  213. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +1 -256
  214. package/dist/components/questions/types/listen-and-speak-question-list/register.js +1 -81
  215. package/dist/components/questions/types/listen-and-speak-question-list/transform.js +1 -26
  216. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -26
  217. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +1 -230
  218. package/dist/components/questions/types/listen-and-speak-with-story-images/register.js +1 -56
  219. package/dist/components/questions/types/listen-and-speak-with-story-images/transform.js +1 -16
  220. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +1 -72
  221. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +1 -230
  222. package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +1 -1
  223. package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +1 -66
  224. package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +1 -42
  225. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +1 -96
  226. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +1 -238
  227. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +1 -2
  228. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +1 -76
  229. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +1 -19
  230. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +1 -134
  231. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +1 -617
  232. package/dist/components/questions/types/look-picture-fill-word-hint/index.js +1 -2
  233. package/dist/components/questions/types/look-picture-fill-word-hint/register.js +1 -82
  234. package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +1 -29
  235. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +1 -38
  236. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +1 -359
  237. package/dist/components/questions/types/match-by-writing-answer/index.js +1 -3
  238. package/dist/components/questions/types/match-by-writing-answer/register.js +1 -58
  239. package/dist/components/questions/types/match-by-writing-answer/transform.js +1 -61
  240. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsClient.js +1 -16
  241. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsCreator.js +1 -23
  242. package/dist/components/questions/types/read-and-color-objects/index.js +1 -2
  243. package/dist/components/questions/types/read-and-color-objects/register.js +1 -3
  244. package/dist/components/questions/types/read-and-color-objects/transform.js +1 -36
  245. package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +1 -125
  246. package/dist/components/questions/types/read-displayed-content/register.js +1 -64
  247. package/dist/components/questions/types/read-displayed-content/transform.js +1 -12
  248. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -64
  249. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +1 -511
  250. package/dist/components/questions/types/read-passage-and-answer-questions/index.js +1 -2
  251. package/dist/components/questions/types/read-passage-and-answer-questions/register.js +1 -58
  252. package/dist/components/questions/types/read-passage-and-answer-questions/transform.js +1 -28
  253. package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +1 -373
  254. package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +1 -41
  255. package/dist/components/questions/types/speaking-conversation/register.js +1 -110
  256. package/dist/components/questions/types/speaking-conversation/transform.js +1 -34
  257. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +1 -74
  258. package/dist/components/questions/types/speaking-cue-card/register.js +1 -52
  259. package/dist/components/questions/types/speaking-cue-card/transform.js +1 -12
  260. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -10
  261. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +1 -161
  262. package/dist/components/questions/types/speaking-describe-image/index.js +1 -2
  263. package/dist/components/questions/types/speaking-describe-image/register.js +1 -69
  264. package/dist/components/questions/types/speaking-describe-image/transform.js +1 -16
  265. package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +1 -234
  266. package/dist/components/questions/types/spontaneous-qa/register.js +1 -93
  267. package/dist/components/questions/types/spontaneous-qa/transform.js +1 -34
  268. package/dist/components/questions/types/true-false/TrueFalseClient.js +1 -75
  269. package/dist/components/questions/types/true-false/TrueFalseCreator.js +1 -244
  270. package/dist/components/questions/types/true-false/index.js +1 -2
  271. package/dist/components/questions/types/true-false/register.js +1 -77
  272. package/dist/components/questions/types/true-false/transform.js +1 -32
  273. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +1 -233
  274. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +1 -631
  275. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphWrapper.js +1 -28
  276. package/dist/components/questions/types/word-fill-paragraph/index.js +1 -2
  277. package/dist/components/questions/types/word-fill-paragraph/register.js +1 -136
  278. package/dist/components/questions/types/word-fill-paragraph/transform.js +1 -56
  279. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -138
  280. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +1 -261
  281. package/dist/components/questions/types/word-fill-structured-form/index.js +1 -2
  282. package/dist/components/questions/types/word-fill-structured-form/register.js +1 -67
  283. package/dist/components/questions/types/word-fill-structured-form/transform.js +1 -40
  284. package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -84
  285. package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +1 -192
  286. package/dist/components/questions/types/word-ordering/index.js +1 -2
  287. package/dist/components/questions/types/word-ordering/register.js +1 -46
  288. package/dist/components/questions/types/word-ordering/transform.js +1 -24
  289. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +1 -98
  290. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +1 -320
  291. package/dist/components/questions/types/write-a-short-letter/index.js +1 -2
  292. package/dist/components/questions/types/write-a-short-letter/register.js +1 -68
  293. package/dist/components/questions/types/write-a-short-letter/transform.js +1 -25
  294. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +1 -61
  295. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +1 -196
  296. package/dist/components/questions/types/write-correct-verb-form/index.js +1 -2
  297. package/dist/components/questions/types/write-correct-verb-form/register.js +1 -62
  298. package/dist/components/questions/types/write-correct-verb-form/transform.js +1 -17
  299. package/dist/components/questions/types/write-sentences/WriteSentencesClient.js +1 -22
  300. package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +1 -174
  301. package/dist/components/questions/types/write-sentences/index.js +1 -2
  302. package/dist/components/questions/types/write-sentences/register.js +1 -58
  303. package/dist/components/questions/types/write-sentences/transform.js +1 -28
  304. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -96
  305. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +1 -161
  306. package/dist/components/questions/types/write-short-paragraph/index.js +1 -2
  307. package/dist/components/questions/types/write-short-paragraph/register.js +1 -4
  308. package/dist/components/questions/types/write-short-paragraph/transform.js +1 -21
  309. package/dist/components/questions/viewer/QuestionGroupViewer.js +1 -36
  310. package/dist/components/questions/viewer/QuestionViewer.js +1 -446
  311. package/dist/components/questions/viewer/index.js +1 -2
  312. package/dist/components/results/ResultReviewPageContainer.js +1 -98
  313. package/dist/components/results/ReviewQuestionRenderer.js +1 -72
  314. package/dist/components/results/index.js +1 -2
  315. package/dist/components/results/result-review.utils.js +1 -6
  316. package/dist/components/results/review-data.js +1 -150
  317. package/dist/components/results/review-renderer.utils.js +1 -97
  318. package/dist/components/results/review-types.js +1 -1
  319. package/dist/components/themes/cambridge-yle/CambridgeYleAudioPlayer.js +1 -138
  320. package/dist/components/themes/cambridge-yle/CambridgeYleBottomNav.js +1 -12
  321. package/dist/components/themes/cambridge-yle/CambridgeYleExamLayout.js +1 -18
  322. package/dist/components/themes/cambridge-yle/CambridgeYleExampleText.js +1 -11
  323. package/dist/components/themes/cambridge-yle/CambridgeYleHeader.js +1 -11
  324. package/dist/components/themes/cambridge-yle/CambridgeYleInstructionBanner.js +1 -9
  325. package/dist/components/themes/cambridge-yle/CambridgeYleNavButton.js +1 -13
  326. package/dist/components/themes/cambridge-yle/CambridgeYlePartBanner.js +1 -8
  327. package/dist/components/themes/cambridge-yle/CambridgeYleProgressBar.js +1 -6
  328. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCard.js +1 -5
  329. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCardFullWidth.js +1 -9
  330. package/dist/components/themes/cambridge-yle/CambridgeYleSidebar.js +1 -41
  331. package/dist/components/themes/cambridge-yle/CambridgeYleTimer.js +1 -25
  332. package/dist/components/themes/cambridge-yle/index.js +1 -13
  333. package/dist/components/themes/english-certification/EcAnswerTheQuestion.js +1 -24
  334. package/dist/components/themes/english-certification/EcMatchByWritingAnswer.js +1 -88
  335. package/dist/components/themes/english-certification/EcWordFillParagraph.js +1 -55
  336. package/dist/components/themes/english-certification/EcWordFillStructuredForm.js +1 -156
  337. package/dist/components/themes/english-certification/EcWritingTask.js +1 -34
  338. package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.js +1 -114
  339. package/dist/components/themes/english-certification/EnglishCertificationBrand.js +1 -11
  340. package/dist/components/themes/english-certification/EnglishCertificationExamLayout.js +1 -21
  341. package/dist/components/themes/english-certification/EnglishCertificationFlagButton.js +1 -15
  342. package/dist/components/themes/english-certification/EnglishCertificationFooterBar.js +1 -19
  343. package/dist/components/themes/english-certification/EnglishCertificationGroupedQuestion.js +1 -148
  344. package/dist/components/themes/english-certification/EnglishCertificationHeader.js +1 -31
  345. package/dist/components/themes/english-certification/EnglishCertificationNavigationPanel.js +1 -71
  346. package/dist/components/themes/english-certification/EnglishCertificationOptionSelector.js +1 -33
  347. package/dist/components/themes/english-certification/EnglishCertificationPassagePanel.js +1 -16
  348. package/dist/components/themes/english-certification/EnglishCertificationQuestionCard.js +1 -22
  349. package/dist/components/themes/english-certification/EnglishCertificationQuestionItem.js +1 -37
  350. package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.js +1 -72
  351. package/dist/components/themes/english-certification/EnglishCertificationReadingSection.js +1 -90
  352. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestion.js +1 -18
  353. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestionList.js +1 -29
  354. package/dist/components/themes/english-certification/english-certification-theme.js +1 -58
  355. package/dist/components/themes/english-certification/grouped-question-utils.js +1 -218
  356. package/dist/components/themes/english-certification/index.js +1 -21
  357. package/dist/components/themes/index.js +1 -6
  358. package/dist/components/themes/summer-sky/SummerSkyAudioPlayer.js +1 -170
  359. package/dist/components/themes/summer-sky/SummerSkyLayout.js +1 -221
  360. package/dist/components/themes/summer-sky/SummerSkySubmitModal.js +1 -18
  361. package/dist/components/themes/summer-sky/SummerSkyWaitingRoom.js +1 -194
  362. package/dist/components/themes/types.js +1 -1
  363. package/dist/components/ui/alert-dialog.js +1 -26
  364. package/dist/components/ui/alert.js +1 -22
  365. package/dist/components/ui/avatar.js +1 -12
  366. package/dist/components/ui/badge.js +1 -38
  367. package/dist/components/ui/button.js +1 -35
  368. package/dist/components/ui/calendar.js +1 -76
  369. package/dist/components/ui/card.js +1 -16
  370. package/dist/components/ui/carousel.js +1 -91
  371. package/dist/components/ui/checkbox.js +1 -9
  372. package/dist/components/ui/collapsible.js +1 -13
  373. package/dist/components/ui/command.js +1 -34
  374. package/dist/components/ui/confirm-dialog.js +1 -49
  375. package/dist/components/ui/dialog.js +1 -23
  376. package/dist/components/ui/drawer.js +1 -35
  377. package/dist/components/ui/dropdown-menu.js +1 -33
  378. package/dist/components/ui/file-upload.js +1 -155
  379. package/dist/components/ui/form.js +1 -62
  380. package/dist/components/ui/hover-card.js +1 -10
  381. package/dist/components/ui/image-preview.js +1 -29
  382. package/dist/components/ui/input.js +1 -8
  383. package/dist/components/ui/label.js +1 -9
  384. package/dist/components/ui/language-switcher.js +1 -15
  385. package/dist/components/ui/multi-select.js +1 -90
  386. package/dist/components/ui/points-input.js +1 -33
  387. package/dist/components/ui/popover.js +1 -26
  388. package/dist/components/ui/progress.js +1 -16
  389. package/dist/components/ui/radio-group.js +1 -12
  390. package/dist/components/ui/scroll-area.js +1 -11
  391. package/dist/components/ui/select.js +1 -38
  392. package/dist/components/ui/separator.js +1 -8
  393. package/dist/components/ui/sheet.js +1 -40
  394. package/dist/components/ui/single-select.js +1 -76
  395. package/dist/components/ui/skeleton.js +1 -6
  396. package/dist/components/ui/switch.js +1 -8
  397. package/dist/components/ui/table.js +1 -20
  398. package/dist/components/ui/tabs.js +1 -17
  399. package/dist/components/ui/textarea.js +1 -8
  400. package/dist/components/ui/tooltip.js +1 -11
  401. package/dist/index.d.ts +3 -3
  402. package/dist/index.js +1 -19
  403. package/dist/mcp/config.js +1 -32
  404. package/dist/mcp/http-client.js +1 -48
  405. package/dist/mcp/index.js +1 -29
  406. package/dist/mcp/tools/_helpers.js +1 -25
  407. package/dist/mcp/tools/exams.js +1 -439
  408. package/dist/mcp/tools/index.js +1 -6
  409. package/dist/mcp/tools/results.js +1 -19
  410. package/dist/shared/constants/ApiConstant.js +1 -276
  411. package/dist/shared/constants/QueryKeyConstant.js +1 -112
  412. package/dist/shared/constants/exam-level.enum.js +1 -41
  413. package/dist/shared/constants/landings/placement-test.constants.js +1 -431
  414. package/dist/shared/constants/question-skills.js +1 -267
  415. package/dist/shared/constants/routes.js +1 -81
  416. package/dist/shared/index.d.ts +3 -0
  417. package/dist/shared/index.js +1 -7
  418. package/dist/shared/lib/hooks/index.js +1 -10
  419. package/dist/shared/lib/hooks/useAlertDialog.js +1 -57
  420. package/dist/shared/lib/hooks/useAudioPlayCounter.js +1 -30
  421. package/dist/shared/lib/hooks/useAudioPreview.js +1 -81
  422. package/dist/shared/lib/hooks/useAudioUpload.js +1 -24
  423. package/dist/shared/lib/hooks/useConfirmDialog.js +1 -89
  424. package/dist/shared/lib/hooks/useDebouncedCallback.js +1 -32
  425. package/dist/shared/lib/hooks/useDelayedAutoPlay.js +1 -12
  426. package/dist/shared/lib/hooks/useFileUpload.js +1 -24
  427. package/dist/shared/lib/hooks/useImageUrl.js +1 -5
  428. package/dist/shared/lib/hooks/usePresignedFileUrl.js +1 -10
  429. package/dist/shared/lib/hooks/useReactHookForm.js +1 -31
  430. package/dist/shared/lib/hooks/useToast.js +1 -62
  431. package/dist/shared/lib/i18n/index.js +1 -54
  432. package/dist/shared/lib/i18n/messages/en/admin.js +1 -2104
  433. package/dist/shared/lib/i18n/messages/en/auth.js +1 -86
  434. package/dist/shared/lib/i18n/messages/en/common.js +1 -140
  435. package/dist/shared/lib/i18n/messages/en/file-upload.js +1 -18
  436. package/dist/shared/lib/i18n/messages/en/student.js +1 -133
  437. package/dist/shared/lib/i18n/messages/en/terms.js +1 -37
  438. package/dist/shared/lib/i18n/messages/en.js +1 -15
  439. package/dist/shared/lib/i18n/messages/vi/admin.js +1 -2104
  440. package/dist/shared/lib/i18n/messages/vi/auth.js +1 -86
  441. package/dist/shared/lib/i18n/messages/vi/common.js +1 -140
  442. package/dist/shared/lib/i18n/messages/vi/file-upload.js +1 -18
  443. package/dist/shared/lib/i18n/messages/vi/student.js +1 -133
  444. package/dist/shared/lib/i18n/messages/vi/terms.js +1 -37
  445. package/dist/shared/lib/i18n/messages/vi.js +1 -15
  446. package/dist/shared/lib/i18n/types.js +1 -1
  447. package/dist/shared/lib/stores/examQuestionStore.js +1 -212
  448. package/dist/shared/lib/stores/localeStore.js +1 -10
  449. package/dist/shared/lib/utils/public-page.js +1 -44
  450. package/dist/shared/lib/utils/question-reverse-transform.d.ts +6 -0
  451. package/dist/shared/lib/utils/question-reverse-transform.js +1 -0
  452. package/dist/shared/lib/utils/question-transform-types.js +1 -1
  453. package/dist/shared/lib/utils/question-transform-validation.d.ts +6 -0
  454. package/dist/shared/lib/utils/question-transform-validation.js +1 -0
  455. package/dist/shared/lib/utils/question-transform.d.ts +6 -2
  456. package/dist/shared/lib/utils/question-transform.js +1 -174
  457. package/dist/shared/lib/utils.js +1 -168
  458. package/dist/shared/lib/validation/choose-correct-answer.validation.js +1 -111
  459. package/dist/shared/lib/validation/label-the-picture.validation.js +1 -19
  460. package/dist/shared/lib/validation/listen-and-choose-from-answer-group.validation.js +1 -46
  461. package/dist/shared/lib/validation/listen-and-choose-objects-in-scene.validation.js +1 -21
  462. package/dist/shared/lib/validation/listen-and-drag-objects-into-scene.validation.js +1 -34
  463. package/dist/shared/lib/validation/listen-and-speak-answer.validation.js +1 -40
  464. package/dist/shared/lib/validation/listen-and-speak-compare-images.validation.js +1 -45
  465. package/dist/shared/lib/validation/listen-and-speak-image-group.validation.js +1 -19
  466. package/dist/shared/lib/validation/listen-and-speak-info-exchange.validation.js +1 -39
  467. package/dist/shared/lib/validation/listen-and-speak-odd-one-out.validation.js +1 -32
  468. package/dist/shared/lib/validation/listen-and-speak-question-list.validation.js +1 -19
  469. package/dist/shared/lib/validation/listen-and-speak-with-story-images.validation.js +1 -28
  470. package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +1 -12
  471. package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +1 -47
  472. package/dist/shared/lib/validation/read-and-color-objects.validation.js +1 -29
  473. package/dist/shared/lib/validation/read-displayed-content.validation.js +1 -28
  474. package/dist/shared/lib/validation/speaking-conversation.validation.js +1 -75
  475. package/dist/shared/lib/validation/speaking-describe-image.validation.js +1 -31
  476. package/dist/shared/lib/validation/spontaneous-qa.validation.js +1 -48
  477. package/dist/shared/lib/validation/word-fill-paragraph-default.validation.js +1 -38
  478. package/dist/shared/lib/validation/word-fill-paragraph-with-options.validation.js +1 -39
  479. package/dist/shared/lib/validation/word-fill-paragraph.validation.js +1 -43
  480. package/dist/shared/lib/validation/word-ordering.validation.js +1 -33
  481. package/dist/shared/types/branch.types.js +1 -6
  482. package/dist/shared/types/common.types.js +1 -50
  483. package/dist/shared/types/entities/exam-schedule.types.js +1 -5
  484. package/dist/shared/types/entities/exam.types.js +1 -1
  485. package/dist/shared/types/entities/question.types.js +1 -4
  486. package/dist/shared/types/entities/result.types.js +1 -4
  487. package/dist/shared/types/entities/user.types.js +1 -4
  488. package/dist/shared/types/exam-taking.types.js +1 -2
  489. package/dist/shared/types/history.types.js +1 -34
  490. package/dist/shared/types/index.js +1 -1
  491. package/dist/shared/types/questions/choose-correct-adjective.js +1 -4
  492. package/dist/shared/types/questions/choose-the-correct-answer.js +1 -9
  493. package/dist/shared/types/questions/fill-missing-words-in-grid.js +1 -11
  494. package/dist/shared/types/questions/index.js +1 -40
  495. package/dist/shared/types/questions/listen-and-speak-answer.js +1 -1
  496. package/dist/shared/types/questions/listen-and-speak-compare-images.js +1 -1
  497. package/dist/shared/types/questions/listen-and-speak-image-group.js +1 -1
  498. package/dist/shared/types/questions/listen-and-speak-info-exchange.js +1 -3
  499. package/dist/shared/types/questions/listen-and-speak-odd-one-out.js +1 -3
  500. package/dist/shared/types/questions/listen-and-speak-question-list.js +1 -1
  501. package/dist/shared/types/questions/listen-and-speak-with-story-images.js +1 -3
  502. package/dist/shared/types/questions/look-picture-choose-correct-answer.js +1 -5
  503. package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +1 -9
  504. package/dist/shared/types/questions/look-picture-fill-word-hint.js +1 -7
  505. package/dist/shared/types/questions/read-displayed-content.js +1 -1
  506. package/dist/shared/types/questions/speaking-conversation.js +1 -7
  507. package/dist/shared/types/questions/speaking-describe-image.js +1 -1
  508. package/dist/shared/types/questions/spontaneous-qa.js +1 -1
  509. package/dist/shared/types/questions/write-correct-verb-form.js +1 -12
  510. package/dist/shared/types/role.types.js +1 -1
  511. package/dist/shared/types/structure-management.js +1 -28
  512. package/dist/shared/types/student.types.js +1 -54
  513. package/dist/shared/types/teacher-dashboard-rooms.js +1 -2
  514. package/package.json +12 -3
@@ -1,3 +1 @@
1
- import { ImageObjectMatchingCreator } from './ImageObjectMatchingCreator';
2
- import { createColorRegionRegistration } from '../../_shared/utils/createColorRegionRegistration';
3
- export const imageObjectMatchingRegistration = createColorRegionRegistration(ImageObjectMatchingCreator);
1
+ import{ImageObjectMatchingCreator as t}from"./ImageObjectMatchingCreator";import{createColorRegionRegistration as e}from"../../_shared/utils/createColorRegionRegistration";export const imageObjectMatchingRegistration=e(t);
@@ -1,2 +1 @@
1
- // IMAGE_OBJECT_MATCHING reuses the same transform as READ_AND_COLOR_OBJECTS
2
- export { transformReadAndColorObjects as transformImageObjectMatching } from '../read-and-color-objects/transform';
1
+ export{transformReadAndColorObjects as transformImageObjectMatching}from"../read-and-color-objects/transform";
@@ -1,102 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useEffect, useRef } from 'react';
4
- import { cn } from '../../../../shared/lib/utils';
5
- import { Input } from '../../../../components/ui/input';
6
- import { Lightbulb } from 'lucide-react';
7
- import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
8
- export function LabelThePictureClient({ questionData, userAnswer = {}, isReviewMode = false, explanation, onSubmit, }) {
9
- const { imageUrl, wordBank, labels } = questionData;
10
- // Initialize answers with pre-filled values
11
- const initialAnswers = labels.reduce((acc, label) => {
12
- if (label.isPreFilled && label.correctAnswer) {
13
- acc[label.id] = label.correctAnswer;
14
- }
15
- return acc;
16
- }, {});
17
- const [answers, setAnswers] = useState(userAnswer || initialAnswers);
18
- // Use refs to track previous values and prevent infinite loops
19
- const previousUserAnswerRef = useRef('');
20
- const previousLabelsRef = useRef('');
21
- useEffect(() => {
22
- // Serialize current values for comparison
23
- const currentUserAnswerStr = JSON.stringify(userAnswer || {});
24
- const currentLabelsStr = JSON.stringify(labels);
25
- // Only update if values actually changed
26
- if (previousUserAnswerRef.current === currentUserAnswerStr &&
27
- previousLabelsRef.current === currentLabelsStr) {
28
- return;
29
- }
30
- // Update refs
31
- previousUserAnswerRef.current = currentUserAnswerStr;
32
- previousLabelsRef.current = currentLabelsStr;
33
- // Re-calculate pre-filled answers
34
- const preFilledAnswers = labels.reduce((acc, label) => {
35
- if (label.isPreFilled && label.correctAnswer) {
36
- acc[label.id] = label.correctAnswer;
37
- }
38
- return acc;
39
- }, {});
40
- if (userAnswer && Object.keys(userAnswer).length > 0) {
41
- // Merge user answers with pre-filled values
42
- const mergedAnswers = { ...preFilledAnswers, ...userAnswer };
43
- setAnswers(mergedAnswers);
44
- }
45
- else {
46
- // Initialize with pre-filled values if no user answer
47
- setAnswers(preFilledAnswers);
48
- }
49
- }, [userAnswer, labels]);
50
- const handleAnswerChange = (labelId, value) => {
51
- if (isReviewMode)
52
- return;
53
- const newAnswers = {
54
- ...answers,
55
- [labelId]: value,
56
- };
57
- setAnswers(newAnswers);
58
- onSubmit?.(newAnswers);
59
- };
60
- const { previewUrl, isLoading: isLoadingUrl, error: urlError } = usePresignedFileUrl(imageUrl);
61
- // Debug logging
62
- console.log('🖼️ LabelThePictureClient image debug:', {
63
- inputImageUrl: imageUrl,
64
- previewUrl,
65
- isLoadingUrl,
66
- urlError,
67
- finalSrc: previewUrl || imageUrl,
68
- });
69
- // Sort labels by number
70
- const sortedLabels = [...labels].sort((a, b) => a.number - b.number);
71
- return (_jsxs("div", { className: "space-y-5", children: [_jsx("div", { className: "flex items-center justify-between", children: _jsxs("div", { children: [_jsx("h2", { className: "text-lg font-semibold text-gray-900", children: "Label the Picture" }), _jsx("p", { className: "text-sm text-gray-500", children: "Nh\u00ECn tranh v\u00E0 \u0111i\u1EC1n t\u1EEB v\u00E0o c\u00E1c v\u1ECB tr\u00ED t\u01B0\u01A1ng \u1EE9ng" })] }) }), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-lg", children: [_jsx("div", { className: "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-2", children: [_jsx("div", { className: "h-2.5 w-2.5 rounded-full bg-red-400" }), _jsx("div", { className: "h-2.5 w-2.5 rounded-full bg-yellow-400" }), _jsx("div", { className: "h-2.5 w-2.5 rounded-full bg-green-400" }), _jsx("span", { className: "ml-3 text-sm font-medium text-gray-500", children: "H\u00ECnh \u1EA3nh" })] }) }), _jsx("div", { className: "flex justify-center bg-gradient-to-br from-slate-50 to-gray-100 p-4", children: _jsxs("div", { className: "relative max-h-[400px] w-full max-w-2xl overflow-hidden rounded-lg", children: [isLoadingUrl && (_jsx("div", { className: "flex h-64 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500" }) })), !isLoadingUrl && (previewUrl || imageUrl) ? (
72
- /* eslint-disable-next-line @next/next/no-img-element */
73
- _jsx("img", { src: previewUrl || imageUrl, alt: "Label the picture", className: "h-full w-full object-contain", onError: (e) => {
74
- console.error('Failed to load image:', imageUrl, 'previewUrl:', previewUrl);
75
- // Replace with default placeholder
76
- const target = e.target;
77
- target.onerror = null; // Prevent infinite loop
78
- target.src = '/images/placeholder-image.svg';
79
- } })) : (_jsxs("div", { className: "flex h-64 flex-col items-center justify-center gap-3 bg-gray-50 rounded-lg border-2 border-dashed border-gray-200", children: [_jsx("svg", { className: "h-16 w-16 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("p", { className: "text-gray-400 font-medium", children: "Ch\u01B0a c\u00F3 h\u00ECnh \u1EA3nh" })] }))] }) })] }), wordBank.length > 0 && (_jsxs("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-gray-100 bg-gradient-to-r from-amber-50 to-orange-50 px-4 py-2.5", children: [_jsx("svg", { className: "h-5 w-5 text-amber-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" }) }), _jsx("h3", { className: "text-sm font-semibold text-gray-800", children: "Word Bank" })] }), _jsx("div", { className: "p-4", children: _jsx("div", { className: "flex flex-wrap gap-2", children: wordBank.map((word, index) => {
80
- const isUsedAsPreFilled = labels.some((label) => label.isPreFilled && label.correctAnswer === word);
81
- const isUsedByUser = Object.values(answers).includes(word);
82
- return (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-lg border-2 px-3 py-1.5 text-sm font-semibold transition-all', isUsedAsPreFilled
83
- ? 'border-blue-300 bg-blue-50 text-blue-700'
84
- : isUsedByUser
85
- ? 'border-green-300 bg-green-50 text-green-700'
86
- : 'border-gray-200 bg-white text-gray-700 hover:border-gray-300 hover:shadow-sm'), children: [word, isUsedAsPreFilled && (_jsx("span", { className: "rounded bg-blue-200 px-1 py-0.5 text-xs", children: "s\u1EB5n" })), isUsedByUser && !isUsedAsPreFilled && (_jsx("span", { className: "rounded bg-green-200 px-1 py-0.5 text-xs", children: "\u2713" }))] }, index));
87
- }) }) })] })), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("svg", { className: "h-5 w-5 text-blue-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" }) }), _jsx("h3", { className: "text-sm font-semibold text-gray-800", children: "\u0110i\u1EC1n t\u1EEB v\u00E0o c\u00E1c v\u1ECB tr\u00ED" })] }), _jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-3", children: sortedLabels.map((label) => {
88
- const userAnswerValue = answers[label.id] || '';
89
- const correctAnswer = label.correctAnswer.trim().toLowerCase();
90
- const userAnswerLower = userAnswerValue.trim().toLowerCase();
91
- const isCorrect = isReviewMode && userAnswerLower === correctAnswer && userAnswerValue.trim() !== '';
92
- const isWrong = isReviewMode && userAnswerValue.trim() !== '' && userAnswerLower !== correctAnswer;
93
- const isPreFilled = label.isPreFilled || false;
94
- return (_jsxs("div", { className: cn('group overflow-hidden rounded-xl border bg-white shadow-sm transition-all hover:shadow-md', isPreFilled && 'border-blue-300 bg-blue-50/30', isReviewMode && isCorrect && !isPreFilled && 'border-green-400 bg-green-50/50', isReviewMode && isWrong && !isPreFilled && 'border-red-400 bg-red-50/50', !isReviewMode && !isPreFilled && 'border-gray-200'), children: [_jsxs("div", { className: cn('flex items-center justify-between px-4 py-2', isPreFilled ? 'bg-blue-100/50' : 'bg-gray-50'), children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: cn('flex h-7 w-7 items-center justify-center rounded-lg text-xs font-bold text-white shadow-sm', isPreFilled
95
- ? 'bg-blue-500'
96
- : isCorrect
97
- ? 'bg-green-500'
98
- : isWrong
99
- ? 'bg-red-500'
100
- : 'bg-gradient-to-br from-blue-500 to-indigo-600'), children: label.number }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["V\u1ECB tr\u00ED ", label.number] })] }), isPreFilled && (_jsx("span", { className: "rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-600", children: "\u0110i\u1EC1n s\u1EB5n" })), isReviewMode && isCorrect && !isPreFilled && (_jsx("span", { className: "rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-600", children: "\u2713 \u0110\u00FAng" })), isReviewMode && isWrong && !isPreFilled && (_jsx("span", { className: "rounded-full bg-red-100 px-2 py-0.5 text-xs font-medium text-red-600", children: "\u2717 Sai" }))] }), _jsxs("div", { className: "p-4", children: [_jsx(Input, { value: userAnswerValue, onChange: (e) => handleAnswerChange(label.id, e.target.value), readOnly: isReviewMode || isPreFilled, placeholder: isPreFilled ? '' : 'Nhập từ...', className: cn('w-full text-center font-semibold', isPreFilled && 'bg-blue-50 border-blue-200 cursor-not-allowed', isCorrect && !isPreFilled && 'bg-green-50 border-green-300', isWrong && !isPreFilled && 'bg-red-50 border-red-300') }), isReviewMode && isWrong && !isPreFilled && (_jsxs("div", { className: "mt-2 rounded-lg bg-green-50 p-2 flex items-center justify-center gap-1.5 border border-green-200", children: [_jsx("span", { className: "text-xs text-green-600", children: "\u0110\u00E1p \u00E1n:" }), _jsx("span", { className: "text-sm font-bold text-green-700", children: label.correctAnswer })] }))] })] }, label.id));
101
- }) })] }), isReviewMode && explanation && (_jsx("div", { className: "rounded-xl 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 })] })] }) }))] }));
102
- }
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useState as t,useEffect as l,useRef as s}from"react";import{cn as n}from"../../../../shared/lib/utils";import{Input as a}from"../../../../components/ui/input";import{Lightbulb as d}from"lucide-react";import{usePresignedFileUrl as i}from"../../../../shared/lib/hooks";export function LabelThePictureClient({questionData:o,userAnswer:c={},isReviewMode:m=!1,explanation:g,onSubmit:h}){const{imageUrl:b,wordBank:u,labels:x}=o,p=x.reduce((e,r)=>(r.isPreFilled&&r.correctAnswer&&(e[r.id]=r.correctAnswer),e),{}),[f,v]=t(c||p),N=s(""),w=s("");l(()=>{const e=JSON.stringify(c||{}),r=JSON.stringify(x);if(N.current===e&&w.current===r)return;N.current=e,w.current=r;const t=x.reduce((e,r)=>(r.isPreFilled&&r.correctAnswer&&(e[r.id]=r.correctAnswer),e),{});if(c&&Object.keys(c).length>0){const e={...t,...c};v(e)}else v(t)},[c,x]);const{previewUrl:y,isLoading:k,error:j}=i(b);console.log("🖼️ LabelThePictureClient image debug:",{inputImageUrl:b,previewUrl:y,isLoadingUrl:k,urlError:j,finalSrc:y||b});const L=[...x].sort((e,r)=>e.number-r.number);return r("div",{className:"space-y-5",children:[e("div",{className:"flex items-center justify-between",children:r("div",{children:[e("h2",{className:"text-lg font-semibold text-gray-900",children:"Label the Picture"}),e("p",{className:"text-sm text-gray-500",children:"Nhìn tranh và điền từ vào các vị trí tương ứng"})]})}),r("div",{className:"overflow-hidden rounded-xl border border-gray-200 bg-white shadow-lg",children:[e("div",{className:"border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-5 py-3",children:r("div",{className:"flex items-center gap-2",children:[e("div",{className:"h-2.5 w-2.5 rounded-full bg-red-400"}),e("div",{className:"h-2.5 w-2.5 rounded-full bg-yellow-400"}),e("div",{className:"h-2.5 w-2.5 rounded-full bg-green-400"}),e("span",{className:"ml-3 text-sm font-medium text-gray-500",children:"Hình ảnh"})]})}),e("div",{className:"flex justify-center bg-gradient-to-br from-slate-50 to-gray-100 p-4",children:r("div",{className:"relative max-h-[400px] w-full max-w-2xl overflow-hidden rounded-lg",children:[k&&e("div",{className:"flex h-64 items-center justify-center",children:e("div",{className:"h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500"})}),k||!y&&!b?r("div",{className:"flex h-64 flex-col items-center justify-center gap-3 bg-gray-50 rounded-lg border-2 border-dashed border-gray-200",children:[e("svg",{className:"h-16 w-16 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("p",{className:"text-gray-400 font-medium",children:"Chưa có hình ảnh"})]}):e("img",{src:y||b,alt:"Label the picture",className:"h-full w-full object-contain",onError:e=>{console.error("Failed to load image:",b,"previewUrl:",y);const r=e.target;r.onerror=null,r.src="/images/placeholder-image.svg"}})]})})]}),u.length>0&&r("div",{className:"overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm",children:[r("div",{className:"flex items-center gap-2 border-b border-gray-100 bg-gradient-to-r from-amber-50 to-orange-50 px-4 py-2.5",children:[e("svg",{className:"h-5 w-5 text-amber-600",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"})}),e("h3",{className:"text-sm font-semibold text-gray-800",children:"Word Bank"})]}),e("div",{className:"p-4",children:e("div",{className:"flex flex-wrap gap-2",children:u.map((t,l)=>{const s=x.some(e=>e.isPreFilled&&e.correctAnswer===t),a=Object.values(f).includes(t);return r("div",{className:n("flex items-center gap-1.5 rounded-lg border-2 px-3 py-1.5 text-sm font-semibold transition-all",s?"border-blue-300 bg-blue-50 text-blue-700":a?"border-green-300 bg-green-50 text-green-700":"border-gray-200 bg-white text-gray-700 hover:border-gray-300 hover:shadow-sm"),children:[t,s&&e("span",{className:"rounded bg-blue-200 px-1 py-0.5 text-xs",children:"sẵn"}),a&&!s&&e("span",{className:"rounded bg-green-200 px-1 py-0.5 text-xs",children:"✓"})]},l)})})})]}),r("div",{className:"space-y-4",children:[r("div",{className:"flex items-center gap-2",children:[e("svg",{className:"h-5 w-5 text-blue-600",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"})}),e("h3",{className:"text-sm font-semibold text-gray-800",children:"Điền từ vào các vị trí"})]}),e("div",{className:"grid gap-3 sm:grid-cols-2 lg:grid-cols-3",children:L.map(t=>{const l=f[t.id]||"",s=t.correctAnswer.trim().toLowerCase(),d=l.trim().toLowerCase(),i=m&&d===s&&""!==l.trim(),o=m&&""!==l.trim()&&d!==s,c=t.isPreFilled||!1;return r("div",{className:n("group overflow-hidden rounded-xl border bg-white shadow-sm transition-all hover:shadow-md",c&&"border-blue-300 bg-blue-50/30",m&&i&&!c&&"border-green-400 bg-green-50/50",m&&o&&!c&&"border-red-400 bg-red-50/50",!m&&!c&&"border-gray-200"),children:[r("div",{className:n("flex items-center justify-between px-4 py-2",c?"bg-blue-100/50":"bg-gray-50"),children:[r("div",{className:"flex items-center gap-2",children:[e("div",{className:n("flex h-7 w-7 items-center justify-center rounded-lg text-xs font-bold text-white shadow-sm",c?"bg-blue-500":i?"bg-green-500":o?"bg-red-500":"bg-gradient-to-br from-blue-500 to-indigo-600"),children:t.number}),r("span",{className:"text-sm font-medium text-gray-700",children:["Vị trí ",t.number]})]}),c&&e("span",{className:"rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-600",children:"Điền sẵn"}),m&&i&&!c&&e("span",{className:"rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-600",children:"✓ Đúng"}),m&&o&&!c&&e("span",{className:"rounded-full bg-red-100 px-2 py-0.5 text-xs font-medium text-red-600",children:"✗ Sai"})]}),r("div",{className:"p-4",children:[e(a,{value:l,onChange:e=>((e,r)=>{if(m)return;const t={...f,[e]:r};v(t),h?.(t)})(t.id,e.target.value),readOnly:m||c,placeholder:c?"":"Nhập từ...",className:n("w-full text-center font-semibold",c&&"bg-blue-50 border-blue-200 cursor-not-allowed",i&&!c&&"bg-green-50 border-green-300",o&&!c&&"bg-red-50 border-red-300")}),m&&o&&!c&&r("div",{className:"mt-2 rounded-lg bg-green-50 p-2 flex items-center justify-center gap-1.5 border border-green-200",children:[e("span",{className:"text-xs text-green-600",children:"Đáp án:"}),e("span",{className:"text-sm font-bold text-green-700",children:t.correctAnswer})]})]})]},t.id)})})]}),m&&g&&e("div",{className:"rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200",children:r("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"})}),r("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:g})]})]})})]})}
@@ -1,344 +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 { Pencil, Eye, Plus, X, Image, Lightbulb, Tags, BookOpen } from 'lucide-react';
11
- import { FileUpload } from '../../../../components/ui/file-upload';
12
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../../../../components/ui/select';
13
- import { LabelThePictureClient } from './LabelThePictureClient';
14
- import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
15
- import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
16
- import { labelThePictureFormSchema, } from '../../../../shared/lib/validation/label-the-picture.validation';
17
- function LabelThePictureCreatorContent({ initialData, onSave, onCancel, onChange, onUnsavedChangesChange, validationRef, }) {
18
- // ============ HELPER FUNCTIONS FOR NEW API FORMAT ============
19
- // Support NEW API format (content.imageUrl, content.wordBank, content.labels) and legacy formats
20
- const getInitialImageUrl = () => {
21
- // NEW API format: content.imageUrl
22
- if (initialData?.content?.imageUrl)
23
- return initialData.content.imageUrl;
24
- // Legacy format: imageUrl at root level
25
- return initialData?.imageUrl || '';
26
- };
27
- const getInitialWordBank = () => {
28
- // NEW API format: content.wordBank
29
- if (initialData?.content?.wordBank && Array.isArray(initialData.content.wordBank)) {
30
- return initialData.content.wordBank;
31
- }
32
- // Legacy format: wordBank at root level
33
- return initialData?.wordBank || [];
34
- };
35
- const getInitialLabels = () => {
36
- // NEW API format: content.labels with correctAnswer in each label
37
- if (initialData?.content?.labels && Array.isArray(initialData.content.labels)) {
38
- return initialData.content.labels;
39
- }
40
- // Legacy format: labels at root level
41
- return initialData?.labels || [];
42
- };
43
- const getInitialPoints = () => {
44
- // Points at root level
45
- return initialData?.points || 1;
46
- };
47
- const getInitialExplanation = () => {
48
- return initialData?.explanation || '';
49
- };
50
- // State
51
- const [imageUrl, setImageUrl] = useState(getInitialImageUrl());
52
- const [wordBank, setWordBank] = useState(getInitialWordBank());
53
- const [labels, setLabels] = useState(getInitialLabels());
54
- const [points, setPoints] = useState(getInitialPoints());
55
- const [explanation, setExplanation] = useState(getInitialExplanation());
56
- // Use react-hook-form with Zod validation
57
- const form = useReactHookForm({
58
- defaultValues: {
59
- imageUrl: imageUrl,
60
- wordBank: wordBank,
61
- labels: labels,
62
- },
63
- zodSchema: labelThePictureFormSchema,
64
- mode: 'onBlur',
65
- });
66
- // Track previous initialData to detect changes
67
- const previousInitialDataRef = useRef(initialData);
68
- const isInitializingRef = useRef(true); // Start as true to prevent onChange on first render
69
- const onChangeRef = useRef(onChange);
70
- const hasInitializedRef = useRef(false); // Track if we've initialized from initialData
71
- // NOTE: Status tracking is now handled entirely by ExamCreator via localStorage (exam_question_status_{id})
72
- // The onChange callback triggers handleQuestionDraftChange in ExamCreator which updates localStorage
73
- // Update onChange ref when it changes
74
- useEffect(() => {
75
- onChangeRef.current = onChange;
76
- }, [onChange]);
77
- // Expose validation function via ref
78
- useEffect(() => {
79
- if (validationRef) {
80
- validationRef.current = {
81
- triggerValidation: async () => {
82
- return validateForm();
83
- },
84
- // Return current form data immediately (not debounced)
85
- getFormData: () => ({
86
- imageUrl,
87
- wordBank,
88
- labels,
89
- points,
90
- explanation,
91
- }),
92
- };
93
- }
94
- }, [validationRef, imageUrl, wordBank, labels, points, explanation]);
95
- // Store presigned URLs for images
96
- const [imagePreviewUrl, setImagePreviewUrl] = useState('');
97
- // Use custom hooks for presigned URLs
98
- const { previewUrl: presignedPreviewUrl } = usePresignedFileUrl(imageUrl);
99
- // Update image preview URL - only set when we have a valid presigned URL
100
- // Don't fallback to raw imageUrl as it's a storage path that won't load
101
- useEffect(() => {
102
- if (presignedPreviewUrl) {
103
- setImagePreviewUrl(presignedPreviewUrl);
104
- }
105
- // Don't fallback to imageUrl directly - it's a storage path, not a valid URL
106
- }, [presignedPreviewUrl]);
107
- // Sync state from initialData when it changes
108
- // IMPORTANT: Only sync when initialData comes from API (has ID) or when explicitly loading
109
- // Don't sync when parent just echoes back our own onChange data
110
- useEffect(() => {
111
- // Skip if initialData is undefined/null
112
- if (!initialData) {
113
- // Only initialize once to empty state
114
- if (!hasInitializedRef.current) {
115
- hasInitializedRef.current = true;
116
- isInitializingRef.current = false;
117
- }
118
- return;
119
- }
120
- const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
121
- console.log('🏷️ LabelThePictureCreator useEffect:', {
122
- initialDataChanged,
123
- hasInitialized: hasInitializedRef.current,
124
- initialData,
125
- previousData: previousInitialDataRef.current,
126
- });
127
- // Only sync from initialData if:
128
- // 1. First time initialization (hasInitializedRef.current is false)
129
- // 2. OR initialData has actually changed AND we're not the source of the change
130
- // (detecting by checking if previousInitialDataRef is undefined or significantly different)
131
- const isFirstInit = !hasInitializedRef.current;
132
- const isApiDataLoad = initialDataChanged && previousInitialDataRef.current === undefined;
133
- if (isFirstInit || isApiDataLoad) {
134
- isInitializingRef.current = true;
135
- previousInitialDataRef.current = initialData;
136
- hasInitializedRef.current = true;
137
- console.log('🏷️ LabelThePictureCreator - Setting state from initialData:', {
138
- imageUrl: getInitialImageUrl(),
139
- wordBank: getInitialWordBank(),
140
- labels: getInitialLabels(),
141
- });
142
- setImageUrl(getInitialImageUrl());
143
- setWordBank(getInitialWordBank());
144
- setLabels(getInitialLabels());
145
- setPoints(getInitialPoints());
146
- setExplanation(getInitialExplanation());
147
- // Wait for state updates to complete before allowing onChange
148
- setTimeout(() => {
149
- isInitializingRef.current = false;
150
- }, 150);
151
- }
152
- else {
153
- // Just update the ref to track current value, but don't reset state
154
- previousInitialDataRef.current = initialData;
155
- }
156
- }, [initialData]);
157
- // Convert to payload format and call onChange
158
- // IMPORTANT: Only call onChange after initialization is complete AND state has values
159
- useEffect(() => {
160
- // Don't call onChange during initialization
161
- if (isInitializingRef.current || !hasInitializedRef.current) {
162
- return;
163
- }
164
- // Don't call onChange if we have no meaningful data yet
165
- // This prevents overwriting API data with empty values
166
- if (!imageUrl && wordBank.length === 0 && labels.length === 0) {
167
- return;
168
- }
169
- const payload = {
170
- imageUrl,
171
- wordBank,
172
- labels,
173
- };
174
- payload.points = points;
175
- payload.explanation = explanation;
176
- requestAnimationFrame(() => {
177
- onChangeRef.current?.(payload);
178
- });
179
- }, [imageUrl, wordBank, labels, points, explanation]);
180
- // UI State
181
- const [isClientMode, setIsClientMode] = useState(false);
182
- // Add word to word bank
183
- const addWordToBank = () => {
184
- setWordBank([...wordBank, '']);
185
- form.clearErrors();
186
- };
187
- // Update word in word bank
188
- const updateWordInBank = (index, value) => {
189
- const newWordBank = [...wordBank];
190
- newWordBank[index] = value;
191
- setWordBank(newWordBank);
192
- form.clearErrors();
193
- };
194
- // Remove word from word bank
195
- const removeWordFromBank = (index) => {
196
- setWordBank(wordBank.filter((_, i) => i !== index));
197
- form.clearErrors();
198
- };
199
- // Add label position
200
- const addLabel = () => {
201
- const maxNumber = labels.length > 0 ? Math.max(...labels.map((l) => l.number)) : 0;
202
- const newLabel = {
203
- id: `label-${Date.now()}-${Math.random()}`,
204
- number: maxNumber + 1,
205
- correctAnswer: '',
206
- };
207
- setLabels([...labels, newLabel]);
208
- form.clearErrors();
209
- };
210
- // Update label
211
- const updateLabel = (id, field, value) => {
212
- setLabels(labels.map((label) => (label.id === id ? { ...label, [field]: value } : label)));
213
- form.clearErrors();
214
- };
215
- // Remove label
216
- const removeLabel = (id) => {
217
- setLabels(labels.filter((label) => label.id !== id));
218
- // Re-number remaining labels
219
- const remainingLabels = labels.filter((label) => label.id !== id);
220
- const sortedLabels = remainingLabels.sort((a, b) => a.number - b.number);
221
- setLabels(sortedLabels.map((label, index) => ({
222
- ...label,
223
- number: index + 1,
224
- })));
225
- form.clearErrors();
226
- };
227
- // Get validation errors
228
- const getErrors = () => {
229
- const errors = {};
230
- // Image URL error
231
- const imageError = form.formState.errors.imageUrl;
232
- if (imageError) {
233
- errors.imageUrl = imageError.message;
234
- }
235
- // Word bank error
236
- const wordBankError = form.formState.errors.wordBank;
237
- if (wordBankError && typeof wordBankError.message === 'string') {
238
- errors.wordBank = wordBankError.message;
239
- }
240
- // Word bank items errors
241
- errors.wordBankItems = {};
242
- wordBank.forEach((_, index) => {
243
- const itemError = form.formState.errors.wordBank?.[index];
244
- if (itemError) {
245
- errors.wordBankItems[index] = itemError.message;
246
- }
247
- });
248
- // Labels error
249
- const labelsError = form.formState.errors.labels;
250
- if (labelsError && typeof labelsError.message === 'string') {
251
- errors.labels = labelsError.message;
252
- }
253
- // Label items errors
254
- errors.labelItems = {};
255
- labels.forEach((label, index) => {
256
- const itemError = form.formState.errors.labels?.[index]?.correctAnswer;
257
- if (itemError) {
258
- errors.labelItems[label.id] = itemError.message;
259
- }
260
- });
261
- return errors;
262
- };
263
- // Validate form data
264
- const validateForm = () => {
265
- // Update form values before validation
266
- form.setValue('imageUrl', imageUrl);
267
- form.setValue('wordBank', wordBank);
268
- form.setValue('labels', labels);
269
- // Trigger validation
270
- const result = labelThePictureFormSchema.safeParse({ imageUrl, wordBank, labels });
271
- if (!result.success) {
272
- // Set errors from Zod validation
273
- result.error.issues.forEach((issue) => {
274
- const path = issue.path.join('.');
275
- form.setError(path, { message: issue.message });
276
- });
277
- return false;
278
- }
279
- return true;
280
- };
281
- // Validate before switching to client mode
282
- const handlePreview = () => {
283
- if (!validateForm()) {
284
- return;
285
- }
286
- setIsClientMode(true);
287
- };
288
- // Handle save with validation
289
- const handleSave = () => {
290
- if (!validateForm()) {
291
- return;
292
- }
293
- // NOTE: Save status is now managed by ExamCreator via localStorage
294
- const saveData = {
295
- imageUrl,
296
- wordBank,
297
- labels,
298
- };
299
- saveData.points = points;
300
- onSave?.(saveData);
301
- };
302
- const errors = getErrors();
303
- // If in client mode, show preview
304
- if (isClientMode) {
305
- // Pass raw imageUrl (relative path) - Client will fetch presigned URL itself via usePresignedFileUrl hook
306
- const questionData = {
307
- imageUrl: imageUrl, // Use raw path, not presignedPreviewUrl
308
- wordBank,
309
- labels,
310
- };
311
- // Auto-fill all positions with correct answers for preview
312
- const correctAnswers = labels.reduce((acc, label) => {
313
- acc[label.id] = label.correctAnswer;
314
- return acc;
315
- }, {});
316
- return (_jsxs("div", { className: "space-y-6", children: [_jsx(Card, { children: _jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx(CardTitle, { children: "Label the Picture (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(LabelThePictureClient, { questionData: questionData, userAnswer: correctAnswers, isReviewMode: true, explanation: explanation })] }));
317
- }
318
- 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: [_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(Image, { className: "h-5 w-5 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-xl font-bold text-gray-900", children: "G\u1EAFn nh\u00E3n h\u00ECnh \u1EA3nh" }), _jsx("p", { className: "text-sm text-gray-500", children: "T\u1EA1o c\u00E2u h\u1ECFi v\u1EDBi h\u00ECnh \u1EA3nh, word bank v\u00E0 c\u00E1c v\u1ECB tr\u00ED c\u1EA7n \u0111i\u1EC1n" })] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: handlePreview, 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, { defaultValue: points, onChange: (e) => setPoints(Number(e.target.value) || 1) }) }), _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" }), "Upload h\u00ECnh \u1EA3nh c\u1EA7n g\u1EAFn nh\u00E3n"] }), _jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-blue-400" }), "Th\u00EAm c\u00E1c t\u1EEB v\u00E0o Word Bank (danh s\u00E1ch t\u1EEB g\u1EE3i \u00FD)"] }), _jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-blue-400" }), "T\u1EA1o c\u00E1c v\u1ECB tr\u00ED c\u1EA7n \u0111i\u1EC1n v\u00E0 ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng t\u1EEB Word Bank"] })] })] })] }) }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(Image, { className: "h-4 w-4 text-indigo-500" }), "H\u00ECnh \u1EA3nh ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FileUpload, { id: "label-picture-image", label: "H\u00ECnh \u1EA3nh", accept: "image/*", value: imageUrl, onChange: (url) => {
319
- setImageUrl(url);
320
- form.clearErrors();
321
- }, onPresignedUrlChange: (presignedUrl) => {
322
- setImagePreviewUrl(presignedUrl);
323
- }, maxSize: 5, placeholder: "Upload \u1EA3nh ho\u1EB7c paste URL", required: true, autoUpload: true, prefix: "questions" }), errors.imageUrl && (_jsx("p", { className: "mt-1 text-sm text-red-600", children: errors.imageUrl })), imagePreviewUrl && (_jsx("div", { className: "relative mt-2 h-64 w-full max-w-2xl overflow-hidden rounded border", children: _jsx("img", { src: imagePreviewUrl, alt: "Preview for question", className: "h-full w-full object-contain", onError: () => {
324
- console.error('Failed to load image:', imageUrl);
325
- } }) }))] })] })] }), _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(Tags, { className: "h-3.5 w-3.5 text-indigo-600" }) }), _jsxs(CardTitle, { className: "text-lg font-semibold text-gray-800", children: ["Word Bank ", _jsxs("span", { className: "text-sm font-normal text-gray-500", children: ["(", wordBank.filter(w => w.trim()).length, " t\u1EEB)"] })] })] }), _jsxs(Button, { type: "button", size: "sm", onClick: addWordToBank, className: "gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700", variant: "outline", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm t\u1EEB"] })] }) }), _jsxs(CardContent, { className: "space-y-4", children: [errors.wordBank && (_jsxs("div", { className: "flex items-start gap-3 rounded-xl border border-red-200 bg-gradient-to-r from-red-50 to-rose-50 p-4", children: [_jsx("span", { className: "text-lg", children: "\u274C" }), _jsx("p", { className: "text-sm text-red-600", children: errors.wordBank })] })), _jsx("div", { className: "grid gap-3 sm:grid-cols-2", children: wordBank.map((word, index) => (_jsxs("div", { className: `group relative rounded-xl border-2 p-3 transition-all duration-200 ${word.trim()
326
- ? 'border-green-200 bg-gradient-to-r from-green-50 to-emerald-50'
327
- : 'border-gray-200 bg-white hover:border-gray-300'}`, children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: `flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg font-bold transition-all ${word.trim()
328
- ? 'bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-md'
329
- : 'bg-gray-100 text-gray-600'}`, children: index + 1 }), _jsx(Input, { value: word, onChange: (e) => updateWordInBank(index, e.target.value), placeholder: `Từ ${index + 1}`, className: `flex-1 border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 ${errors.wordBankItems?.[index] ? 'border-red-400' : ''}` }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeWordFromBank(index), className: "text-gray-400 hover:bg-red-50 hover:text-red-500", children: _jsx(X, { className: "h-4 w-4" }) })] }), errors.wordBankItems?.[index] && (_jsx("p", { className: "mt-2 ml-11 text-sm text-red-600", children: errors.wordBankItems[index] }))] }, index))) }), wordBank.length === 0 && (_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("span", { className: "text-lg", children: "\u26A0\uFE0F" }), _jsx("p", { className: "text-sm text-amber-800", children: "Ch\u01B0a c\u00F3 t\u1EEB n\u00E0o. Nh\u1EA5n \"Th\u00EAm t\u1EEB\" \u0111\u1EC3 b\u1EAFt \u0111\u1EA7u." })] }))] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-purple-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-purple-400 to-pink-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-purple-100", children: _jsx(Tags, { className: "h-3.5 w-3.5 text-purple-600" }) }), _jsxs(CardTitle, { className: "text-lg font-semibold text-gray-800", children: ["V\u1ECB tr\u00ED c\u1EA7n \u0111i\u1EC1n ", _jsxs("span", { className: "text-sm font-normal text-gray-500", children: ["(", labels.length, " v\u1ECB tr\u00ED)"] })] })] }), _jsxs(Button, { type: "button", size: "sm", onClick: addLabel, className: "gap-2 border-purple-200 text-purple-600 hover:bg-purple-50 hover:text-purple-700", variant: "outline", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm v\u1ECB tr\u00ED"] })] }) }), _jsxs(CardContent, { className: "space-y-4", children: [errors.labels && (_jsxs("div", { className: "flex items-start gap-3 rounded-xl border border-red-200 bg-gradient-to-r from-red-50 to-rose-50 p-4", children: [_jsx("span", { className: "text-lg", children: "\u274C" }), _jsx("p", { className: "text-sm text-red-600", children: errors.labels })] })), _jsx("div", { className: "space-y-4", children: labels
330
- .sort((a, b) => a.number - b.number)
331
- .map((label) => {
332
- const hasAnswer = label.correctAnswer && label.correctAnswer.trim();
333
- return (_jsx("div", { className: `group relative rounded-xl border-2 p-4 transition-all duration-200 ${hasAnswer
334
- ? 'border-green-300 bg-gradient-to-r from-green-50 to-emerald-50 shadow-md shadow-green-100'
335
- : '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 items-center justify-center rounded-xl font-bold transition-all ${hasAnswer
336
- ? 'bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-md'
337
- : 'bg-purple-100 text-purple-700'}`, children: label.number }), _jsxs("div", { className: "flex-1 space-y-3", children: [_jsxs("div", { children: [_jsxs(Label, { className: "text-sm font-semibold text-gray-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx("div", { className: "mt-1.5", children: _jsxs(Select, { value: label.correctAnswer || '', onValueChange: (value) => updateLabel(label.id, 'correctAnswer', value), children: [_jsx(SelectTrigger, { className: `w-full border-gray-200 transition-all focus:border-indigo-300 focus:ring-indigo-200 ${errors.labelItems?.[label.id] ? 'border-red-400' : ''}`, children: _jsx(SelectValue, { placeholder: "Ch\u1ECDn t\u1EEB trong word bank" }) }), _jsx(SelectContent, { children: wordBank
338
- .filter((word) => word.trim() !== '')
339
- .map((word, index) => (_jsx(SelectItem, { value: word, children: word }, index))) })] }) }), errors.labelItems?.[label.id] && (_jsx("p", { className: "mt-1 text-sm text-red-600", children: errors.labelItems[label.id] }))] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("input", { type: "checkbox", id: `prefilled-${label.id}`, checked: label.isPreFilled || false, onChange: (e) => updateLabel(label.id, 'isPreFilled', e.target.checked), className: "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500" }), _jsx(Label, { htmlFor: `prefilled-${label.id}`, className: "text-sm font-normal text-gray-600 cursor-pointer", children: "\u0110\u00E1nh d\u1EA5u l\u00E0 t\u1EEB g\u1EE3i \u00FD (\u0111\u00E3 \u0111i\u1EC1n s\u1EB5n)" })] })] }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeLabel(label.id), className: "text-gray-400 hover:bg-red-50 hover:text-red-500", children: _jsx(X, { className: "h-4 w-4" }) })] }) }, label.id));
340
- }) }), labels.length === 0 && (_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("span", { className: "text-lg", children: "\u26A0\uFE0F" }), _jsx("p", { className: "text-sm text-amber-800", children: "Ch\u01B0a c\u00F3 v\u1ECB tr\u00ED n\u00E0o. Nh\u1EA5n \"Th\u00EAm v\u1ECB tr\u00ED\" \u0111\u1EC3 b\u1EAFt \u0111\u1EA7u." })] }))] })] }), _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, { id: "explanation", value: explanation, onChange: (e) => setExplanation(e.target.value), 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" }) })] })] }));
341
- }
342
- export function LabelThePictureCreator(props) {
343
- return _jsx(LabelThePictureCreatorContent, { ...props });
344
- }
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useState as t,useRef as a,useEffect as n}from"react";import{Button as l}from"../../../../components/ui/button";import{Label as s}from"../../../../components/ui/label";import{Input as i}from"../../../../components/ui/input";import{Textarea as o}from"../../../../components/ui/textarea";import{Card as d,CardContent as c,CardHeader as m,CardTitle as h}from"../../../../components/ui/card";import{PointsInput as g}from"../../../../components/ui/points-input";import{Pencil as u,Eye as b,Plus as p,X as f,Image as x,Lightbulb as N,Tags as v,BookOpen as w}from"lucide-react";import{FileUpload as y}from"../../../../components/ui/file-upload";import{Select as k,SelectContent as C,SelectItem as B,SelectTrigger as U,SelectValue as j}from"../../../../components/ui/select";import{LabelThePictureClient as E}from"./LabelThePictureClient";import{usePresignedFileUrl as A}from"../../../../shared/lib/hooks";import{useReactHookForm as T}from"../../../../shared/lib/hooks/useReactHookForm";import{labelThePictureFormSchema as I}from"../../../../shared/lib/validation/label-the-picture.validation";function P({initialData:P,onSave:S,onCancel:z,onChange:D,onUnsavedChangesChange:V,validationRef:F}){const L=()=>P?.content?.imageUrl?P.content.imageUrl:P?.imageUrl||"",q=()=>P?.content?.wordBank&&Array.isArray(P.content.wordBank)?P.content.wordBank:P?.wordBank||[],H=()=>P?.content?.labels&&Array.isArray(P.content.labels)?P.content.labels:P?.labels||[],$=()=>P?.points||1,M=()=>P?.explanation||"",[R,G]=t(L()),[W,J]=t(q()),[O,K]=t(H()),[Q,X]=t($()),[Y,Z]=t(M()),_=T({defaultValues:{imageUrl:R,wordBank:W,labels:O},zodSchema:I,mode:"onBlur"}),ee=a(P),re=a(!0),te=a(D),ae=a(!1);n(()=>{te.current=D},[D]),n(()=>{F&&(F.current={triggerValidation:async()=>ce(),getFormData:()=>({imageUrl:R,wordBank:W,labels:O,points:Q,explanation:Y})})},[F,R,W,O,Q,Y]);const[ne,le]=t(""),{previewUrl:se}=A(R);n(()=>{se&&le(se)},[se]),n(()=>{if(!P)return void(ae.current||(ae.current=!0,re.current=!1));const e=JSON.stringify(ee.current)!==JSON.stringify(P);console.log("🏷️ LabelThePictureCreator useEffect:",{initialDataChanged:e,hasInitialized:ae.current,initialData:P,previousData:ee.current});const r=!ae.current,t=e&&void 0===ee.current;r||t?(re.current=!0,ee.current=P,ae.current=!0,console.log("🏷️ LabelThePictureCreator - Setting state from initialData:",{imageUrl:L(),wordBank:q(),labels:H()}),G(L()),J(q()),K(H()),X($()),Z(M()),setTimeout(()=>{re.current=!1},150)):ee.current=P},[P]),n(()=>{if(re.current||!ae.current)return;if(!R&&0===W.length&&0===O.length)return;const e={imageUrl:R,wordBank:W,labels:O};e.points=Q,e.explanation=Y,requestAnimationFrame(()=>{te.current?.(e)})},[R,W,O,Q,Y]);const[ie,oe]=t(!1),de=(e,r,t)=>{K(O.map(a=>a.id===e?{...a,[r]:t}:a)),_.clearErrors()},ce=()=>{_.setValue("imageUrl",R),_.setValue("wordBank",W),_.setValue("labels",O);const e=I.safeParse({imageUrl:R,wordBank:W,labels:O});return!!e.success||(e.error.issues.forEach(e=>{const r=e.path.join(".");_.setError(r,{message:e.message})}),!1)},me=(()=>{const e={},r=_.formState.errors.imageUrl;r&&(e.imageUrl=r.message);const t=_.formState.errors.wordBank;t&&"string"==typeof t.message&&(e.wordBank=t.message),e.wordBankItems={},W.forEach((r,t)=>{const a=_.formState.errors.wordBank?.[t];a&&(e.wordBankItems[t]=a.message)});const a=_.formState.errors.labels;return a&&"string"==typeof a.message&&(e.labels=a.message),e.labelItems={},O.forEach((r,t)=>{const a=_.formState.errors.labels?.[t]?.correctAnswer;a&&(e.labelItems[r.id]=a.message)}),e})();if(ie){const t={imageUrl:R,wordBank:W,labels:O},a=O.reduce((e,r)=>(e[r.id]=r.correctAnswer,e),{});return r("div",{className:"space-y-6",children:[e(d,{children:e(m,{children:r("div",{className:"flex items-center justify-between",children:[r("div",{children:[e(h,{children:"Label the Picture (Preview)"}),e("p",{className:"mt-1 text-sm text-gray-500",children:"Chế độ xem trước - Hiển thị đáp án đúng"})]}),r(l,{variant:"outline",size:"sm",onClick:()=>oe(!1),children:[e(u,{className:"mr-2 h-4 w-4"}),"Quay lại Edit Mode"]})]})})}),e(E,{questionData:t,userAnswer:a,isReviewMode:!0,explanation:Y})]})}return r("div",{className:"space-y-6",children:[r(d,{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:[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(x,{className:"h-5 w-5 text-white"})}),r("div",{children:[e(h,{className:"text-xl font-bold text-gray-900",children:"Gắn nhãn hình ảnh"}),e("p",{className:"text-sm text-gray-500",children:"Tạo câu hỏi với hình ảnh, word bank và các vị trí cần điền"})]})]}),r(l,{variant:"outline",size:"sm",onClick:()=>{ce()&&oe(!0)},className:"gap-2 border-indigo-200 text-indigo-600 hover:bg-indigo-50 hover:text-indigo-700",children:[e(b,{className:"h-4 w-4"}),"Xem trước"]})]})}),r(c,{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,{defaultValue:Q,onChange:e=>X(Number(e.target.value)||1)})}),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(N,{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"}),"Upload hình ảnh cần gắn nhãn"]}),r("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-blue-400"}),"Thêm các từ vào Word Bank (danh sách từ gợi ý)"]}),r("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-blue-400"}),"Tạo các vị trí cần điền và chọn đáp án đúng từ Word Bank"]})]})]})]})}),r("div",{className:"space-y-2",children:[r(s,{className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(x,{className:"h-4 w-4 text-indigo-500"}),"Hình ảnh ",e("span",{className:"text-red-500",children:"*"})]}),e(y,{id:"label-picture-image",label:"Hình ảnh",accept:"image/*",value:R,onChange:e=>{G(e),_.clearErrors()},onPresignedUrlChange:e=>{le(e)},maxSize:5,placeholder:"Upload ảnh hoặc paste URL",required:!0,autoUpload:!0,prefix:"questions"}),me.imageUrl&&e("p",{className:"mt-1 text-sm text-red-600",children:me.imageUrl}),ne&&e("div",{className:"relative mt-2 h-64 w-full max-w-2xl overflow-hidden rounded border",children:e("img",{src:ne,alt:"Preview for question",className:"h-full w-full object-contain",onError:()=>{console.error("Failed to load image:",R)}})})]})]})]}),r(d,{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:["Word Bank ",r("span",{className:"text-sm font-normal text-gray-500",children:["(",W.filter(e=>e.trim()).length," từ)"]})]})]}),r(l,{type:"button",size:"sm",onClick:()=>{J([...W,""]),_.clearErrors()},className:"gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700",variant:"outline",children:[e(p,{className:"h-4 w-4"}),"Thêm từ"]})]})}),r(c,{className:"space-y-4",children:[me.wordBank&&r("div",{className:"flex items-start gap-3 rounded-xl border border-red-200 bg-gradient-to-r from-red-50 to-rose-50 p-4",children:[e("span",{className:"text-lg",children:"❌"}),e("p",{className:"text-sm text-red-600",children:me.wordBank})]}),e("div",{className:"grid gap-3 sm:grid-cols-2",children:W.map((t,a)=>r("div",{className:"group relative rounded-xl border-2 p-3 transition-all duration-200 "+(t.trim()?"border-green-200 bg-gradient-to-r from-green-50 to-emerald-50":"border-gray-200 bg-white hover:border-gray-300"),children:[r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg font-bold transition-all "+(t.trim()?"bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-md":"bg-gray-100 text-gray-600"),children:a+1}),e(i,{value:t,onChange:e=>((e,r)=>{const t=[...W];t[e]=r,J(t),_.clearErrors()})(a,e.target.value),placeholder:`Từ ${a+1}`,className:"flex-1 border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 "+(me.wordBankItems?.[a]?"border-red-400":"")}),e(l,{type:"button",variant:"ghost",size:"icon",onClick:()=>(e=>{J(W.filter((r,t)=>t!==e)),_.clearErrors()})(a),className:"text-gray-400 hover:bg-red-50 hover:text-red-500",children:e(f,{className:"h-4 w-4"})})]}),me.wordBankItems?.[a]&&e("p",{className:"mt-2 ml-11 text-sm text-red-600",children:me.wordBankItems[a]})]},a))}),0===W.length&&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("span",{className:"text-lg",children:"⚠️"}),e("p",{className:"text-sm text-amber-800",children:'Chưa có từ nào. Nhấn "Thêm từ" để bắt đầu.'})]})]})]}),r(d,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-purple-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-purple-400 to-pink-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-purple-100",children:e(v,{className:"h-3.5 w-3.5 text-purple-600"})}),r(h,{className:"text-lg font-semibold text-gray-800",children:["Vị trí cần điền ",r("span",{className:"text-sm font-normal text-gray-500",children:["(",O.length," vị trí)"]})]})]}),r(l,{type:"button",size:"sm",onClick:()=>{const e=O.length>0?Math.max(...O.map(e=>e.number)):0,r={id:`label-${Date.now()}-${Math.random()}`,number:e+1,correctAnswer:""};K([...O,r]),_.clearErrors()},className:"gap-2 border-purple-200 text-purple-600 hover:bg-purple-50 hover:text-purple-700",variant:"outline",children:[e(p,{className:"h-4 w-4"}),"Thêm vị trí"]})]})}),r(c,{className:"space-y-4",children:[me.labels&&r("div",{className:"flex items-start gap-3 rounded-xl border border-red-200 bg-gradient-to-r from-red-50 to-rose-50 p-4",children:[e("span",{className:"text-lg",children:"❌"}),e("p",{className:"text-sm text-red-600",children:me.labels})]}),e("div",{className:"space-y-4",children:O.sort((e,r)=>e.number-r.number).map(t=>{const a=t.correctAnswer&&t.correctAnswer.trim();return e("div",{className:"group relative rounded-xl border-2 p-4 transition-all duration-200 "+(a?"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 items-center justify-center rounded-xl font-bold transition-all "+(a?"bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-md":"bg-purple-100 text-purple-700"),children:t.number}),r("div",{className:"flex-1 space-y-3",children:[r("div",{children:[r(s,{className:"text-sm font-semibold text-gray-700",children:["Đáp án đúng ",e("span",{className:"text-red-500",children:"*"})]}),e("div",{className:"mt-1.5",children:r(k,{value:t.correctAnswer||"",onValueChange:e=>de(t.id,"correctAnswer",e),children:[e(U,{className:"w-full border-gray-200 transition-all focus:border-indigo-300 focus:ring-indigo-200 "+(me.labelItems?.[t.id]?"border-red-400":""),children:e(j,{placeholder:"Chọn từ trong word bank"})}),e(C,{children:W.filter(e=>""!==e.trim()).map((r,t)=>e(B,{value:r,children:r},t))})]})}),me.labelItems?.[t.id]&&e("p",{className:"mt-1 text-sm text-red-600",children:me.labelItems[t.id]})]}),r("div",{className:"flex items-center gap-2",children:[e("input",{type:"checkbox",id:`prefilled-${t.id}`,checked:t.isPreFilled||!1,onChange:e=>de(t.id,"isPreFilled",e.target.checked),className:"h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"}),e(s,{htmlFor:`prefilled-${t.id}`,className:"text-sm font-normal text-gray-600 cursor-pointer",children:"Đánh dấu là từ gợi ý (đã điền sẵn)"})]})]}),e(l,{type:"button",variant:"ghost",size:"icon",onClick:()=>(e=>{K(O.filter(r=>r.id!==e));const r=O.filter(r=>r.id!==e).sort((e,r)=>e.number-r.number);K(r.map((e,r)=>({...e,number:r+1}))),_.clearErrors()})(t.id),className:"text-gray-400 hover:bg-red-50 hover:text-red-500",children:e(f,{className:"h-4 w-4"})})]})},t.id)})}),0===O.length&&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("span",{className:"text-lg",children:"⚠️"}),e("p",{className:"text-sm text-amber-800",children:'Chưa có vị trí nào. Nhấn "Thêm vị trí" để bắt đầu.'})]})]})]}),r(d,{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(w,{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(c,{children:e(o,{id:"explanation",value:Y,onChange:e=>Z(e.target.value),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 LabelThePictureCreator(r){return e(P,{...r})}
@@ -1,2 +1 @@
1
- export * from './LabelThePictureCreator';
2
- export * from './LabelThePictureClient';
1
+ export*from"./LabelThePictureCreator";export*from"./LabelThePictureClient";
@@ -1,54 +1 @@
1
- import { LabelThePictureCreator } from './LabelThePictureCreator';
2
- export const labelThePictureRegistration = {
3
- component: LabelThePictureCreator,
4
- transformInitialData: (initialData) => {
5
- const content = initialData?.content;
6
- const answer = initialData?.answer;
7
- let creatorData = answer;
8
- if (content && typeof content === 'object' && content.labels && Array.isArray(content.labels)) {
9
- const mergedLabels = content.labels.map((label) => ({
10
- ...label,
11
- correctAnswer: answer?.answers?.[label.id] || label.correctAnswer || '',
12
- }));
13
- const apiPoints = initialData?.total_points
14
- ? parseFloat(initialData.total_points)
15
- : (initialData?.points || 1);
16
- creatorData = {
17
- ...content,
18
- labels: mergedLabels,
19
- points: apiPoints,
20
- };
21
- }
22
- else {
23
- const apiPoints = initialData?.total_points
24
- ? parseFloat(initialData.total_points)
25
- : (initialData?.points || answer?.points || 1);
26
- if (creatorData) {
27
- creatorData = { ...creatorData, points: apiPoints };
28
- }
29
- }
30
- return creatorData;
31
- },
32
- getExtraProps: (ctx) => ({
33
- onUnsavedChangesChange: ctx.onUnsavedChangesChange,
34
- validationRef: ctx.validationRef,
35
- }),
36
- wrapOnSave: (data, ctx) => ({
37
- type: ctx.questionType,
38
- content: ctx.state.content,
39
- points: data?.points ?? ctx.state.points ?? 1,
40
- level: ctx.state.level,
41
- difficulty: ctx.state.difficulty,
42
- skill: ctx.state.skill,
43
- answer: data,
44
- }),
45
- wrapOnChange: (data, ctx) => ({
46
- type: ctx.questionType,
47
- content: ctx.state.content,
48
- points: data?.points ?? ctx.state.points ?? 1,
49
- level: ctx.state.level,
50
- difficulty: ctx.state.difficulty,
51
- skill: ctx.state.skill,
52
- answer: data,
53
- }),
54
- };
1
+ import{LabelThePictureCreator as t}from"./LabelThePictureCreator";export const labelThePictureRegistration={component:t,transformInitialData:t=>{const e=t?.content,s=t?.answer;let n=s;if(e&&"object"==typeof e&&e.labels&&Array.isArray(e.labels)){const a=e.labels.map(t=>({...t,correctAnswer:s?.answers?.[t.id]||t.correctAnswer||""})),o=t?.total_points?parseFloat(t.total_points):t?.points||1;n={...e,labels:a,points:o}}else{const e=t?.total_points?parseFloat(t.total_points):t?.points||s?.points||1;n&&(n={...n,points:e})}return n},getExtraProps:t=>({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})};