@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,617 +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, Lightbulb, ImageIcon, Type, ToggleLeft, BookOpen } from 'lucide-react';
11
- import { FileUpload } from '../../../../components/ui/file-upload';
12
- import { ImagePreview } from '../../../../components/ui/image-preview';
13
- import { LookPictureFillWordHintClient } from './LookPictureFillWordHintClient';
14
- import { Switch } from '../../../../components/ui/switch';
15
- import { useDebouncedCallback, usePresignedFileUrl } from '../../../../shared/lib/hooks';
16
- import { useExamQuestionStore } from '../../../../shared/lib/stores/examQuestionStore';
17
- // Helper: Calculate group number for a question index based on groups config
18
- function getGroupNumberForIndex(questionIndex, groups) {
19
- if (!groups || groups.length === 0) {
20
- return 1; // Default to group 1 if no groups config
21
- }
22
- let currentIndex = 0;
23
- for (const group of groups) {
24
- if (questionIndex < currentIndex + group.questionCount) {
25
- return group.number;
26
- }
27
- currentIndex += group.questionCount;
28
- }
29
- return groups[groups.length - 1]?.number || 1;
30
- }
31
- /**
32
- * LookPictureFillWordHintCreator - Single Question Mode
33
- *
34
- * This component handles ONE question at a time. Navigation between questions
35
- * is handled by the parent ExamCreator component.
36
- *
37
- * API Format (from questionGroup.payload.hintLetters):
38
- * - hintLetters: ["a", "e", "i", "o", "u"]
39
- */
40
- function LookPictureFillWordHintContent({ initialData, onSave, onSaveToQuestionBank, onCancel, onChange, onUnsavedChangesChange, externalErrors, groups, questionIndexInPart = 0, partId = '', validationRef, }) {
41
- // Access exam question store to find and update shared hintLetters for questions in same group
42
- const questionsState = useExamQuestionStore((state) => state.questions);
43
- const currentQuestionIndex = useExamQuestionStore((state) => state.currentQuestionIndex);
44
- const upsertQuestionData = useExamQuestionStore((state) => state.upsertQuestionData);
45
- const getQuestionData = useExamQuestionStore((state) => state.getQuestionData);
46
- // New: Use shared hintLetters by partId + groupNumber
47
- const setSharedHintLetters = useExamQuestionStore((state) => state.setSharedHintLetters);
48
- const getSharedHintLetters = useExamQuestionStore((state) => state.getSharedHintLetters);
49
- // Get current question data from store (if exists) - this has priority over initialData
50
- // because store contains the latest user input even if not saved yet
51
- const getStoredQuestionData = () => {
52
- const storedData = getQuestionData(currentQuestionIndex);
53
- if (storedData?.answer) {
54
- return storedData.answer;
55
- }
56
- return null;
57
- };
58
- // Find hintLetters from other questions in the same group (within same part)
59
- // This allows sharing hintLetters across questions with same groupNumber
60
- const findSharedHintLetters = (targetGroupNumber) => {
61
- // Get the part info to find questions in same part
62
- // Questions in same part share the same groups config
63
- const allQuestions = Object.values(questionsState);
64
- for (const entry of allQuestions) {
65
- if (!entry?.data?.answer)
66
- continue;
67
- const answerData = entry.data.answer;
68
- // Check if this question has hintLetters and same type
69
- if (entry.data.type === 'LOOK_PICTURE_FILL_WORD_HINT') {
70
- // Check hintLetters in answer data
71
- if (answerData?.hintLetters && Array.isArray(answerData.hintLetters) && answerData.hintLetters.length > 0) {
72
- // Check if same groupNumber
73
- const subQuestion = answerData?.subQuestions?.[0];
74
- if (subQuestion?.groupNumber === targetGroupNumber) {
75
- return answerData.hintLetters;
76
- }
77
- }
78
- }
79
- }
80
- return [];
81
- };
82
- // Create a default sub-question
83
- const createDefaultSubQuestion = () => {
84
- // Get current question's group number based on position in part
85
- const groupNumber = getGroupNumberForIndex(questionIndexInPart, groups);
86
- return {
87
- id: `sq-${Date.now()}-${Math.random()}`,
88
- imageUrl: '',
89
- word: '',
90
- correctAnswer: '',
91
- points: 1,
92
- groupNumber,
93
- };
94
- };
95
- // Initialize from initialData or create new
96
- // Supports both:
97
- // 1. New API format: content.imageUrl, content.word, answer.correctAnswer
98
- // 2. Legacy format: subQuestions array (for backward compatibility)
99
- const initializeData = () => {
100
- // Check for new API format first (content has imageUrl and word directly)
101
- // This is the format from question-reverse-transform.ts
102
- const content = initialData?.content;
103
- const answer = initialData?.answer;
104
- if (content?.imageUrl || content?.word || answer?.correctAnswer) {
105
- const pointsFromInitialData = initialData?.points || 1;
106
- return {
107
- id: `sq-${Date.now()}-imported`,
108
- imageUrl: content?.imageUrl || '',
109
- word: content?.word || '',
110
- correctAnswer: answer?.correctAnswer || '',
111
- points: pointsFromInitialData,
112
- groupNumber: getGroupNumberForIndex(questionIndexInPart, groups),
113
- };
114
- }
115
- // Legacy format: subQuestions array
116
- if (initialData?.subQuestions && initialData.subQuestions.length > 0) {
117
- console.log('⚠️ Using legacy format (subQuestions)');
118
- const subQuestion = initialData.subQuestions[0];
119
- const pointsFromInitialData = initialData?.points || subQuestion.points || 1;
120
- return {
121
- ...subQuestion,
122
- points: pointsFromInitialData,
123
- groupNumber: getGroupNumberForIndex(questionIndexInPart, groups),
124
- };
125
- }
126
- return createDefaultSubQuestion();
127
- };
128
- // State for current question
129
- const [currentQuestion, setCurrentQuestion] = useState(initializeData);
130
- // Get current group number for initialization
131
- const initialGroupNum = getGroupNumberForIndex(questionIndexInPart, groups);
132
- // hintLetters: Simple string input for hint letters (comma separated)
133
- // API format: hintLetters is an array like ["a", "e", "i", "o", "u"]
134
- const [hintLettersInput, setHintLettersInput] = useState(() => {
135
- console.log('🔍 [useState init] Initializing hintLettersInput', {
136
- partId,
137
- initialGroupNum,
138
- questionIndexInPart,
139
- });
140
- // Priority 1: Check shared hintLetters from store (by partId + groupNumber)
141
- // This is the most reliable source as it's updated when any question in the group is saved
142
- if (partId) {
143
- const storedSharedHintLetters = getSharedHintLetters(partId, initialGroupNum);
144
- console.log('🔍 [useState init] Store hintLetters:', {
145
- key: `${partId}_${initialGroupNum}`,
146
- value: storedSharedHintLetters,
147
- });
148
- if (storedSharedHintLetters && storedSharedHintLetters.length > 0) {
149
- console.log('✅ [useState init] Using store hintLetters:', storedSharedHintLetters);
150
- return storedSharedHintLetters.join(', ');
151
- }
152
- }
153
- // Priority 2: New format - hintLetters in answer from question-reverse-transform.ts
154
- const answerHintLetters = initialData?.answer?.hintLetters;
155
- if (answerHintLetters && Array.isArray(answerHintLetters) && answerHintLetters.length > 0) {
156
- console.log('✅ [useState init] Using answer.hintLetters:', answerHintLetters);
157
- return answerHintLetters.join(', ');
158
- }
159
- // Priority 3: Handle hintLetters array directly (from API transform via dedicated-component-router)
160
- if (initialData?.hintLetters && Array.isArray(initialData.hintLetters) && initialData.hintLetters.length > 0) {
161
- console.log('✅ [useState init] Using initialData.hintLetters:', initialData.hintLetters);
162
- return initialData.hintLetters.join(', ');
163
- }
164
- // Priority 4: Legacy - Handle hintLettersByGroup if present
165
- if (initialData?.hintLettersByGroup) {
166
- const letters = initialData.hintLettersByGroup[initialGroupNum];
167
- if (letters && Array.isArray(letters)) {
168
- console.log('✅ [useState init] Using hintLettersByGroup:', letters);
169
- return letters.join(', ');
170
- }
171
- }
172
- // Priority 5: Try to find shared hintLetters from other questions in same group (legacy fallback)
173
- const sharedHintLetters = findSharedHintLetters(initialGroupNum);
174
- if (sharedHintLetters.length > 0) {
175
- console.log('✅ [useState init] Using findSharedHintLetters:', sharedHintLetters);
176
- return sharedHintLetters.join(', ');
177
- }
178
- console.log('⚠️ [useState init] No hintLetters found, returning empty');
179
- return '';
180
- });
181
- const [caseSensitive, setCaseSensitive] = useState(initialData?.caseSensitive || false);
182
- const [explanation, setExplanation] = useState(initialData?.answer?.explanation || initialData?.explanation || '');
183
- const [isClientMode, setIsClientMode] = useState(false);
184
- const [addToQuestionBank, setAddToQuestionBank] = useState(false);
185
- const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
186
- // Inline validation errors
187
- const [validationErrors, setValidationErrors] = useState({});
188
- // Store the last saved state for comparison
189
- const savedStateRef = useRef('');
190
- // Track presigned URLs for images
191
- const [imagePreviewUrl, setImagePreviewUrl] = useState('');
192
- // Refs for change tracking
193
- const previousInitialDataRef = useRef(initialData);
194
- const previousQuestionIndexRef = useRef(questionIndexInPart); // Track question index instead of ID
195
- const isInitializingRef = useRef(false);
196
- const hasInitializedRef = useRef(false); // Track if component has been initialized
197
- const onChangeRef = useRef(onChange);
198
- const previousPayloadRef = useRef('');
199
- // Current group number
200
- const currentGroupNumber = currentQuestion.groupNumber || 1;
201
- // Ref for onUnsavedChangesChange callback
202
- const onUnsavedChangesChangeRef = useRef(onUnsavedChangesChange);
203
- useEffect(() => {
204
- onUnsavedChangesChangeRef.current = onUnsavedChangesChange;
205
- }, [onUnsavedChangesChange]);
206
- // Update onChange ref
207
- useEffect(() => {
208
- onChangeRef.current = onChange;
209
- }, [onChange]);
210
- // Helper function to create current state string for comparison
211
- const getCurrentStateString = () => {
212
- return JSON.stringify({
213
- imageUrl: currentQuestion.imageUrl,
214
- word: currentQuestion.word,
215
- correctAnswer: currentQuestion.correctAnswer,
216
- points: currentQuestion.points,
217
- hintLetters: getHintLettersArray(),
218
- caseSensitive,
219
- explanation,
220
- });
221
- };
222
- // Initialize saved state when initialData changes (navigation to new question)
223
- useEffect(() => {
224
- if (isInitializingRef.current)
225
- return;
226
- // Set initial saved state after a short delay to allow state to settle
227
- const timer = setTimeout(() => {
228
- savedStateRef.current = getCurrentStateString();
229
- setHasUnsavedChanges(false);
230
- }, 150);
231
- return () => clearTimeout(timer);
232
- }, [questionIndexInPart]); // Reset when navigating to different question
233
- // Detect unsaved changes by comparing current state with saved state
234
- useEffect(() => {
235
- if (isInitializingRef.current)
236
- return;
237
- if (!savedStateRef.current)
238
- return; // Skip if no saved state yet
239
- const currentState = getCurrentStateString();
240
- const hasChanges = currentState !== savedStateRef.current;
241
- if (hasChanges !== hasUnsavedChanges) {
242
- setHasUnsavedChanges(hasChanges);
243
- onUnsavedChangesChangeRef.current?.(hasChanges);
244
- }
245
- }, [currentQuestion, hintLettersInput, caseSensitive, explanation, hasUnsavedChanges]);
246
- // Debounced onChange callback
247
- const debouncedOnChange = useDebouncedCallback((data) => {
248
- onChangeRef.current?.(data);
249
- }, 300);
250
- // Sync from initialData when:
251
- // 1. First initialization with valid data (hasInitializedRef is false and initialData has valid content), OR
252
- // 2. User navigated to a different question (questionIndexInPart changed)
253
- // This prevents resetting state when parent updates initialData due to onChange callback
254
- useEffect(() => {
255
- const questionIndexChanged = questionIndexInPart !== previousQuestionIndexRef.current;
256
- // Check for valid data in both new format (content/answer) and legacy format (subQuestions)
257
- const content = initialData?.content;
258
- const answer = initialData?.answer;
259
- const hasNewFormatData = !!(content?.imageUrl || content?.word || answer?.correctAnswer);
260
- const hasLegacyFormatData = !!(initialData?.subQuestions && initialData.subQuestions.length > 0);
261
- const hasValidInitialData = hasNewFormatData || hasLegacyFormatData;
262
- // Only sync if:
263
- // 1. First initialization with valid data (and not already initialized), OR
264
- // 2. User navigated to a different question (questionIndexInPart changed)
265
- // IMPORTANT: Do NOT sync when parent updates initialData due to save/onChange callback
266
- // This prevents resetting user input after save
267
- const shouldSyncForFirstInit = !hasInitializedRef.current && hasValidInitialData;
268
- const shouldSyncForNavigation = questionIndexChanged;
269
- // Skip sync if we're already initialized and index didn't change
270
- // This prevents the bug where "Đáp án đúng" input is cleared after save
271
- if (!shouldSyncForFirstInit && !shouldSyncForNavigation) {
272
- return;
273
- }
274
- if (shouldSyncForFirstInit || shouldSyncForNavigation) {
275
- isInitializingRef.current = true;
276
- previousInitialDataRef.current = initialData;
277
- previousQuestionIndexRef.current = questionIndexInPart;
278
- // Mark as initialized - either with valid data or as empty new question
279
- // This prevents re-syncing when parent updates initialData after save
280
- hasInitializedRef.current = true;
281
- if (initialData) {
282
- // Get the group number for current question
283
- const groupNum = getGroupNumberForIndex(questionIndexInPart, groups);
284
- // Handle NEW API format: content.imageUrl, content.word, answer.correctAnswer
285
- if (hasNewFormatData) {
286
- const pointsFromInitialData = initialData?.points || 1;
287
- setCurrentQuestion({
288
- id: `sq-${Date.now()}-synced`,
289
- imageUrl: content?.imageUrl || '',
290
- word: content?.word || '',
291
- correctAnswer: answer?.correctAnswer || '',
292
- points: pointsFromInitialData,
293
- groupNumber: groupNum,
294
- });
295
- // Handle hintLetters from answer (new format)
296
- const answerHintLetters = answer?.hintLetters;
297
- if (answerHintLetters && Array.isArray(answerHintLetters) && answerHintLetters.length > 0) {
298
- setHintLettersInput(answerHintLetters.join(', '));
299
- }
300
- // Handle caseSensitive from answer
301
- if (answer?.caseSensitive !== undefined) {
302
- setCaseSensitive(answer.caseSensitive);
303
- }
304
- // Handle explanation from answer (new format)
305
- if (answer?.explanation !== undefined) {
306
- setExplanation(answer.explanation);
307
- }
308
- }
309
- // Handle LEGACY format: subQuestions array
310
- else if (hasLegacyFormatData) {
311
- console.log('⚠️ [sync effect] Using legacy format (subQuestions)');
312
- const pointsFromInitialData = initialData?.points || initialData.subQuestions[0].points || 1;
313
- setCurrentQuestion({
314
- ...initialData.subQuestions[0],
315
- points: pointsFromInitialData,
316
- groupNumber: groupNum,
317
- });
318
- }
319
- // Handle hintLetters from various sources (for both formats)
320
- // Priority 1: Check shared hintLetters from store (by partId + groupNumber)
321
- // This is the most reliable source as it's updated when any question in the group is saved
322
- let hintLettersSet = false;
323
- if (partId) {
324
- const storedSharedHintLetters = getSharedHintLetters(partId, groupNum);
325
- if (storedSharedHintLetters && storedSharedHintLetters.length > 0) {
326
- setHintLettersInput(storedSharedHintLetters.join(', '));
327
- hintLettersSet = true;
328
- }
329
- }
330
- // Priority 2: From initialData.hintLetters
331
- if (!hintLettersSet && initialData.hintLetters && Array.isArray(initialData.hintLetters) && initialData.hintLetters.length > 0) {
332
- setHintLettersInput(initialData.hintLetters.join(', '));
333
- hintLettersSet = true;
334
- }
335
- // Priority 3: Legacy - hintLettersByGroup
336
- if (!hintLettersSet && initialData.hintLettersByGroup) {
337
- const letters = initialData.hintLettersByGroup[groupNum];
338
- if (letters && Array.isArray(letters)) {
339
- setHintLettersInput(letters.join(', '));
340
- hintLettersSet = true;
341
- }
342
- }
343
- // Priority 4: Legacy fallback - find shared hintLetters from other questions
344
- if (!hintLettersSet && !hasNewFormatData) {
345
- const sharedHintLetters = findSharedHintLetters(groupNum);
346
- if (sharedHintLetters.length > 0) {
347
- setHintLettersInput(sharedHintLetters.join(', '));
348
- }
349
- }
350
- if (initialData.caseSensitive !== undefined) {
351
- setCaseSensitive(initialData.caseSensitive);
352
- }
353
- }
354
- setTimeout(() => {
355
- isInitializingRef.current = false;
356
- }, 100);
357
- }
358
- }, [initialData, groups, questionIndexInPart]);
359
- const { previewUrl: resolvedImagePreviewUrl } = usePresignedFileUrl(currentQuestion.imageUrl);
360
- useEffect(() => {
361
- setImagePreviewUrl(resolvedImagePreviewUrl || currentQuestion.imageUrl || '');
362
- }, [currentQuestion.imageUrl, resolvedImagePreviewUrl]);
363
- // Convert hintLettersInput string to array
364
- const getHintLettersArray = () => {
365
- return hintLettersInput
366
- .split(',')
367
- .map((l) => l.trim())
368
- .filter((l) => l.length > 0);
369
- };
370
- // Call onChange when data changes
371
- useEffect(() => {
372
- if (isInitializingRef.current)
373
- return;
374
- const hintLettersArray = getHintLettersArray();
375
- const payload = {
376
- subQuestions: [currentQuestion],
377
- hintLetters: hintLettersArray,
378
- caseSensitive,
379
- groups,
380
- };
381
- // Calculate total points
382
- payload.points = currentQuestion.points || 1;
383
- // Include explanation in payload
384
- payload.explanation = explanation;
385
- const payloadString = JSON.stringify(payload);
386
- if (previousPayloadRef.current !== payloadString) {
387
- previousPayloadRef.current = payloadString;
388
- // IMPORTANT: Save hintLetters to shared store whenever they change
389
- // This ensures hintLetters are available for other questions in the same group
390
- // even before user clicks "Save" button
391
- if (partId && hintLettersArray.length > 0) {
392
- console.log('📤 [onChange effect] Saving hintLetters to store:', {
393
- partId,
394
- groupNumber: currentGroupNumber,
395
- hintLettersArray,
396
- });
397
- setSharedHintLetters(partId, currentGroupNumber, hintLettersArray);
398
- }
399
- debouncedOnChange(payload);
400
- }
401
- }, [currentQuestion, hintLettersInput, caseSensitive, explanation, groups, debouncedOnChange, partId, currentGroupNumber, setSharedHintLetters]);
402
- // Update current question field
403
- const updateCurrentQuestion = (field, value) => {
404
- setCurrentQuestion((prev) => ({ ...prev, [field]: value }));
405
- };
406
- // Format hint letters input on blur
407
- const formatHintLettersInput = () => {
408
- const letters = getHintLettersArray();
409
- if (letters.length > 0) {
410
- setHintLettersInput(letters.join(', '));
411
- }
412
- };
413
- // Preview mode
414
- const handlePreview = () => {
415
- setIsClientMode(true);
416
- };
417
- // Sync hintLetters to all questions in the same group
418
- // This ensures when navigating back/next, all questions in same group show the latest hintLetters
419
- const syncHintLettersToSameGroup = (newHintLetters) => {
420
- const targetGroupNumber = currentGroupNumber;
421
- // Find all questions in same group and update their hintLetters
422
- Object.entries(questionsState).forEach(([indexStr, entry]) => {
423
- const questionIndex = Number(indexStr);
424
- // Skip current question (it will be saved by parent)
425
- if (questionIndex === currentQuestionIndex)
426
- return;
427
- // Check if this question is same type and same group
428
- if (!entry?.data)
429
- return;
430
- if (entry.data.type !== 'LOOK_PICTURE_FILL_WORD_HINT')
431
- return;
432
- const answerData = entry.data.answer;
433
- const subQuestion = answerData?.subQuestions?.[0];
434
- // Check if same group number
435
- if (subQuestion?.groupNumber !== targetGroupNumber)
436
- return;
437
- // Update this question's hintLetters in store
438
- const updatedAnswer = {
439
- ...answerData,
440
- hintLetters: newHintLetters,
441
- };
442
- upsertQuestionData(questionIndex, {
443
- ...entry.data,
444
- answer: updatedAnswer,
445
- });
446
- });
447
- };
448
- // Validate form data - returns errors object and sets inline errors
449
- const validateForm = () => {
450
- const hintLettersArray = getHintLettersArray();
451
- const errors = {};
452
- if (hintLettersArray.length === 0) {
453
- errors.hintLetters = 'Vui lòng nhập chữ cái gợi ý!';
454
- }
455
- if (!currentQuestion.imageUrl) {
456
- errors.imageUrl = 'Vui lòng upload hình ảnh!';
457
- }
458
- if (!currentQuestion.word) {
459
- errors.word = 'Vui lòng nhập từ có chỗ trống!';
460
- }
461
- if (!currentQuestion.correctAnswer) {
462
- errors.correctAnswer = 'Vui lòng nhập đáp án đúng!';
463
- }
464
- // Set validation errors for inline display
465
- setValidationErrors(errors);
466
- if (Object.keys(errors).length > 0) {
467
- return { isValid: false };
468
- }
469
- const saveData = {
470
- subQuestions: [currentQuestion],
471
- hintLetters: hintLettersArray,
472
- caseSensitive,
473
- groups,
474
- };
475
- saveData.points = currentQuestion.points || 1;
476
- saveData.explanation = explanation;
477
- return { isValid: true, saveData };
478
- };
479
- // Expose validation function to parent via ref
480
- useEffect(() => {
481
- if (validationRef) {
482
- validationRef.current = {
483
- triggerValidation: async () => {
484
- const { isValid } = validateForm();
485
- return isValid;
486
- },
487
- // Return current form data immediately (not debounced)
488
- getFormData: () => {
489
- const hintLettersArray = getHintLettersArray();
490
- return {
491
- subQuestions: [currentQuestion],
492
- hintLetters: hintLettersArray,
493
- caseSensitive,
494
- groups,
495
- points: currentQuestion.points || 1,
496
- explanation,
497
- };
498
- },
499
- };
500
- }
501
- }, [validationRef, currentQuestion, hintLettersInput, caseSensitive, explanation, groups]);
502
- // Save question only
503
- const handleSave = () => {
504
- const { isValid, saveData } = validateForm();
505
- if (!isValid || !saveData)
506
- return;
507
- const hintLettersArray = getHintLettersArray();
508
- console.log('💾 [handleSave] Saving hintLetters to store:', {
509
- partId,
510
- currentGroupNumber,
511
- hintLettersArray,
512
- storeKey: `${partId}_${currentGroupNumber}`,
513
- });
514
- // Save shared hintLetters to store by partId + groupNumber
515
- // This is the primary sync mechanism - simple and reliable
516
- if (partId) {
517
- setSharedHintLetters(partId, currentGroupNumber, hintLettersArray);
518
- console.log('✅ [handleSave] hintLetters saved to store');
519
- }
520
- else {
521
- console.log('⚠️ [handleSave] partId is empty, skipping store save');
522
- }
523
- // Legacy: Also sync to other questions in same group (for backward compatibility)
524
- syncHintLettersToSameGroup(hintLettersArray);
525
- // Update saved state to current state (mark as saved)
526
- savedStateRef.current = getCurrentStateString();
527
- setHasUnsavedChanges(false);
528
- onUnsavedChangesChangeRef.current?.(false);
529
- onSave?.(saveData);
530
- };
531
- // Save and add to question bank
532
- const handleSaveToQuestionBank = () => {
533
- const { isValid, saveData } = validateForm();
534
- if (!isValid || !saveData)
535
- return;
536
- const hintLettersArray = getHintLettersArray();
537
- // Save shared hintLetters to store by partId + groupNumber
538
- // This is the primary sync mechanism - simple and reliable
539
- if (partId) {
540
- setSharedHintLetters(partId, currentGroupNumber, hintLettersArray);
541
- }
542
- // Legacy: Also sync to other questions in same group (for backward compatibility)
543
- syncHintLettersToSameGroup(hintLettersArray);
544
- // Update saved state to current state (mark as saved)
545
- savedStateRef.current = getCurrentStateString();
546
- setHasUnsavedChanges(false);
547
- onUnsavedChangesChangeRef.current?.(false);
548
- onSaveToQuestionBank?.(saveData);
549
- };
550
- // Client mode preview
551
- if (isClientMode) {
552
- const questionData = {
553
- subQuestions: [{
554
- ...currentQuestion,
555
- imageUrl: imagePreviewUrl || currentQuestion.imageUrl,
556
- }],
557
- hintLetters: getHintLettersArray(),
558
- caseSensitive,
559
- groups,
560
- };
561
- 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: "Nh\u00ECn tranh \u0111i\u1EC1n t\u1EEB g\u1EE3i \u00FD (Preview)" }), _jsx("p", { className: "mt-1 text-sm text-gray-500", children: "Ch\u1EBF \u0111\u1ED9 xem tr\u01B0\u1EDBc" })] }), _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(LookPictureFillWordHintClient, { questionData: questionData, autoFillCorrectAnswers: true, isReviewMode: true, explanation: explanation, onSubmit: () => { } })] }));
562
- }
563
- // Get display URL for current question image
564
- const rawImageUrl = currentQuestion.imageUrl || '';
565
- const isHttpUrl = rawImageUrl.startsWith('http');
566
- const displayUrl = imagePreviewUrl || (isHttpUrl ? rawImageUrl : '');
567
- const isLoadingImage = rawImageUrl && !isHttpUrl && !imagePreviewUrl;
568
- // Preview word with answer
569
- const renderWordPreview = () => {
570
- if (!currentQuestion.word || !currentQuestion.correctAnswer)
571
- return null;
572
- const word = currentQuestion.word;
573
- const answerStr = currentQuestion.correctAnswer;
574
- const answers = answerStr
575
- .split(',')
576
- .map((a) => a.trim())
577
- .filter((a) => a.length > 0);
578
- const placeholderRegex = /\{(\d+)\}/g;
579
- const matches = word.match(placeholderRegex);
580
- const blankCount = matches ? matches.length : 0;
581
- let result = word;
582
- for (let i = 0; i < Math.min(answers.length, blankCount); i++) {
583
- result = result.replace(`{${i}}`, `[${answers[i]}]`);
584
- }
585
- return (_jsxs("div", { className: "mt-3 rounded-lg border-2 border-blue-200 bg-blue-50 p-2", children: [_jsx("p", { className: "text-blue-800", children: result }), answers.length !== blankCount && (_jsxs("p", { className: "mt-1 text-xs text-amber-600", children: ["\u26A0\uFE0F S\u1ED1 \u0111\u00E1p \u00E1n (", answers.length, ") kh\u00F4ng kh\u1EDBp v\u1EDBi s\u1ED1 ch\u1ED7 tr\u1ED1ng (", blankCount, ")"] }))] }));
586
- };
587
- return (_jsxs("div", { className: "space-y-6", children: [_jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-purple-100/50", children: [_jsx("div", { className: "h-1.5 bg-gradient-to-r from-purple-500 via-indigo-500 to-blue-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-purple-500 to-indigo-600 shadow-md", children: _jsx(ImageIcon, { className: "h-5 w-5 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-xl font-bold text-gray-900", children: "Nh\u00ECn tranh \u0111i\u1EC1n t\u1EEB g\u1EE3i \u00FD" }), _jsx("div", { className: "mt-1 inline-flex items-center gap-1.5 rounded-md bg-purple-100 px-2.5 py-1 text-sm font-medium text-purple-700 ring-1 ring-inset ring-purple-700/10", children: _jsxs("span", { children: ["\uD83D\uDCDA Nh\u00F3m ", currentGroupNumber] }) })] })] }), _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, { id: "points-header", value: currentQuestion.points || 1, onChange: (e) => {
588
- updateCurrentQuestion('points', Number(e.target.value) || 1);
589
- } }) }), _jsx("div", { className: "rounded-xl border border-purple-200 bg-gradient-to-r from-purple-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-purple-100", children: _jsx(Lightbulb, { className: "h-4 w-4 text-purple-600" }) }), _jsxs("div", { className: "text-sm text-purple-900", children: [_jsx("p", { className: "font-semibold text-purple-800", children: "H\u01B0\u1EDBng d\u1EABn t\u1EA1o c\u00E2u h\u1ECFi" }), _jsxs("ul", { className: "mt-2 space-y-1.5 text-purple-700", children: [_jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-purple-400" }), "Nh\u1EADp c\u00E1c ch\u1EEF c\u00E1i g\u1EE3i \u00FD (ph\u00E2n c\u00E1ch b\u1EB1ng d\u1EA5u ph\u1EA9y)"] }), _jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-purple-400" }), "S\u1EED d\u1EE5ng ", _jsx("code", { className: "mx-1 rounded bg-purple-100 px-1.5 py-0.5 font-mono text-purple-800", children: '{0}' }), ", ", _jsx("code", { className: "mx-1 rounded bg-purple-100 px-1.5 py-0.5 font-mono text-purple-800", children: '{1}' }), "... \u0111\u1EC3 \u0111\u00E1nh d\u1EA5u ch\u1ED7 tr\u1ED1ng"] }), _jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-purple-400" }), "V\u00ED d\u1EE5 t\u1EEB: ", _jsxs("code", { className: "ml-1 rounded bg-purple-100 px-1.5 py-0.5 font-mono text-purple-800 select-all cursor-pointer", title: "Click \u0111\u1EC3 copy", children: ["c", '{0}', '{1}'] }), " v\u1EDBi \u0111\u00E1p \u00E1n ", _jsx("code", { className: "rounded bg-purple-100 px-1.5 py-0.5 font-mono text-purple-800", children: "a, r" })] })] })] })] }) }), _jsxs("div", { className: `rounded-lg border-2 p-4 ${validationErrors.hintLetters ? 'border-red-300 bg-red-50' : 'border-amber-200 bg-amber-50'}`, children: [_jsxs(Label, { htmlFor: "hint-letters", className: validationErrors.hintLetters ? 'text-red-800' : 'text-amber-800', children: ["Ch\u1EEF c\u00E1i g\u1EE3i \u00FD ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Input, { id: "hint-letters", type: "text", value: hintLettersInput, onBlur: formatHintLettersInput, onChange: (e) => {
590
- setHintLettersInput(e.target.value);
591
- if (validationErrors.hintLetters) {
592
- setValidationErrors(prev => ({ ...prev, hintLetters: undefined }));
593
- }
594
- }, placeholder: "V\u00ED d\u1EE5: a, e, i, o, u", className: `mt-1 bg-white ${validationErrors.hintLetters ? 'border-red-500 focus:ring-red-500' : ''}` }), validationErrors.hintLetters ? (_jsx("p", { className: "mt-1 text-sm text-red-600", children: validationErrors.hintLetters })) : (_jsx("p", { className: "mt-1 text-xs text-amber-700", children: "Nh\u1EADp c\u00E1c ch\u1EEF c\u00E1i g\u1EE3i \u00FD, ph\u00E2n c\u00E1ch b\u1EB1ng d\u1EA5u ph\u1EA9y" }))] }), _jsxs("div", { className: "flex items-center justify-between rounded-xl border-2 border-gray-200 bg-white p-4 transition-all hover:border-gray-300", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gray-100", children: _jsx(ToggleLeft, { className: "h-4 w-4 text-gray-600" }) }), _jsxs("div", { className: "space-y-0.5", children: [_jsx(Label, { htmlFor: "case-sensitive", className: "font-semibold text-gray-800", children: "Ph\u00E2n bi\u1EC7t hoa/th\u01B0\u1EDDng" }), _jsx("p", { className: "text-sm text-gray-500", children: "B\u1EADt n\u1EBFu \u0111\u00E1p \u00E1n c\u1EA7n ph\u00E2n bi\u1EC7t ch\u1EEF hoa/th\u01B0\u1EDDng" })] })] }), _jsx(Switch, { id: "case-sensitive", checked: caseSensitive, onCheckedChange: setCaseSensitive })] }), externalErrors && externalErrors.length > 0 && (_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("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-red-100", children: _jsx("span", { className: "text-lg", children: "\u274C" }) }), _jsxs("div", { className: "text-sm", children: [_jsx("p", { className: "font-semibold text-red-800", children: "L\u1ED7i:" }), _jsx("ul", { className: "mt-1 list-inside list-disc text-red-700", children: externalErrors.map((error, index) => (_jsx("li", { children: error }, index))) })] })] }))] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-blue-400 to-indigo-400" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-blue-100", children: _jsx(Type, { className: "h-3.5 w-3.5 text-blue-600" }) }), _jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "N\u1ED9i dung c\u00E2u h\u1ECFi" }), _jsxs("span", { className: "inline-flex items-center gap-1 rounded-md bg-purple-100 px-2 py-0.5 text-xs font-medium text-purple-700 ring-1 ring-inset ring-purple-700/10", children: ["\uD83D\uDCDA Nh\u00F3m ", currentGroupNumber] })] }) }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-indigo-500" }), "H\u00ECnh \u1EA3nh ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FileUpload, { id: "image", label: "H\u00ECnh \u1EA3nh", accept: "image/*", value: currentQuestion.imageUrl || '', onChange: (url) => {
595
- updateCurrentQuestion('imageUrl', url);
596
- if (validationErrors.imageUrl) {
597
- setValidationErrors(prev => ({ ...prev, imageUrl: undefined }));
598
- }
599
- }, onPresignedUrlChange: (presignedUrl) => {
600
- setImagePreviewUrl(presignedUrl);
601
- }, maxSize: 5, placeholder: "Upload \u1EA3nh ho\u1EB7c paste URL", required: true, autoUpload: true, prefix: "questions" }), validationErrors.imageUrl && (_jsx("p", { className: "mt-1 text-sm text-red-600", children: validationErrors.imageUrl })), isLoadingImage && (_jsx(ImagePreview, { src: displayUrl, alt: "Preview for question", className: "mt-2 h-48 w-full max-w-md rounded-lg border border-gray-200" }))] }), _jsxs("div", { children: [_jsxs(Label, { htmlFor: "word", children: ["T\u1EEB c\u00F3 ch\u1ED7 tr\u1ED1ng ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Input, { id: "word", value: currentQuestion.word || '', onChange: (e) => {
602
- updateCurrentQuestion('word', e.target.value);
603
- if (validationErrors.word) {
604
- setValidationErrors(prev => ({ ...prev, word: undefined }));
605
- }
606
- }, placeholder: "V\u00ED d\u1EE5: c{0}r, B{0}{1}k, f{0}o{1}", className: `mt-1 ${validationErrors.word ? 'border-red-500 focus:ring-red-500' : ''}`, maxLength: 50 }), validationErrors.word ? (_jsx("p", { className: "mt-1 text-sm text-red-600", children: validationErrors.word })) : (_jsxs("div", { className: "mt-1 space-y-1", children: [_jsxs("p", { className: "text-xs text-gray-500", children: ["S\u1EED d\u1EE5ng ", '{0}', ", ", '{1}', ", ", '{2}', ",... \u0111\u1EC3 \u0111\u00E1nh d\u1EA5u ch\u1ED7 tr\u1ED1ng c\u1EA7n \u0111i\u1EC1n"] }), _jsxs("p", { className: "text-xs text-gray-400", children: ["V\u00ED d\u1EE5: ", _jsxs("code", { className: "bg-gray-100 px-1 rounded select-all cursor-pointer", title: "Click \u0111\u1EC3 copy", children: ["c", '{0}', '{1}'] })] })] }))] }), _jsxs("div", { children: [_jsxs(Label, { htmlFor: "answer", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Input, { id: "answer", value: currentQuestion.correctAnswer || '', onChange: (e) => {
607
- updateCurrentQuestion('correctAnswer', e.target.value);
608
- if (validationErrors.correctAnswer) {
609
- setValidationErrors(prev => ({ ...prev, correctAnswer: undefined }));
610
- }
611
- }, placeholder: "V\u00ED d\u1EE5: a ho\u1EB7c o,o (c\u00E1ch nhau b\u1EB1ng d\u1EA5u ph\u1EA9y)", className: `mt-1 ${validationErrors.correctAnswer ? 'border-red-500 focus:ring-red-500' : ''}` }), validationErrors.correctAnswer ? (_jsx("p", { className: "mt-1 text-sm text-red-600", children: validationErrors.correctAnswer })) : (_jsx("p", { className: "mt-1 text-xs text-gray-500", children: "Nh\u1EADp c\u00E1c \u0111\u00E1p \u00E1n c\u00E1ch nhau b\u1EB1ng d\u1EA5u ph\u1EA9y. S\u1ED1 \u0111\u00E1p \u00E1n ph\u1EA3i kh\u1EDBp v\u1EDBi s\u1ED1 placeholder" })), renderWordPreview()] })] })] }), _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" }) })] })] }));
612
- }
613
- export function LookPictureFillWordHintCreator(props) {
614
- return _jsx(LookPictureFillWordHintContent, { ...props });
615
- }
616
- // Legacy export for backward compatibility
617
- export const LookPictureFillWordHint = LookPictureFillWordHintCreator;
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useState as r,useRef as n,useEffect as i}from"react";import{Button as s}from"../../../../components/ui/button";import{Label as o}from"../../../../components/ui/label";import{Input as a}from"../../../../components/ui/input";import{Textarea as l}from"../../../../components/ui/textarea";import{Card as c,CardContent as d,CardHeader as h,CardTitle as m}from"../../../../components/ui/card";import{PointsInput as u}from"../../../../components/ui/points-input";import{Pencil as p,Eye as g,Lightbulb as x,ImageIcon as b,Type as f,ToggleLeft as N,BookOpen as w}from"lucide-react";import{FileUpload as v}from"../../../../components/ui/file-upload";import{ImagePreview as y}from"../../../../components/ui/image-preview";import{LookPictureFillWordHintClient as L}from"./LookPictureFillWordHintClient";import{Switch as U}from"../../../../components/ui/switch";import{useDebouncedCallback as A,usePresignedFileUrl as S}from"../../../../shared/lib/hooks";import{useExamQuestionStore as C}from"../../../../shared/lib/stores/examQuestionStore";function j(e,t){if(!t||0===t.length)return 1;let r=0;for(const n of t){if(e<r+n.questionCount)return n.number;r+=n.questionCount}return t[t.length-1]?.number||1}function k({initialData:k,onSave:Q,onSaveToQuestionBank:V,onCancel:I,onChange:q,onUnsavedChangesChange:D,externalErrors:F,groups:H,questionIndexInPart:P=0,partId:B="",validationRef:G}){const $=C(e=>e.questions),O=(C(e=>e.currentQuestionIndex),C(e=>e.upsertQuestionData),C(e=>e.getQuestionData),C(e=>e.setSharedHintLetters)),T=C(e=>e.getSharedHintLetters),W=e=>{const t=Object.values($);for(const r of t){if(!r?.data?.answer)continue;const t=r.data.answer;if("LOOK_PICTURE_FILL_WORD_HINT"===r.data.type&&t?.hintLetters&&Array.isArray(t.hintLetters)&&t.hintLetters.length>0){const r=t?.subQuestions?.[0];if(r?.groupNumber===e)return t.hintLetters}}return[]},[z,R]=r(()=>{const e=k?.content,t=k?.answer;if(e?.imageUrl||e?.word||t?.correctAnswer){const r=k?.points||1;return{id:`sq-${Date.now()}-imported`,imageUrl:e?.imageUrl||"",word:e?.word||"",correctAnswer:t?.correctAnswer||"",points:r,groupNumber:j(P,H)}}if(k?.subQuestions&&k.subQuestions.length>0){console.log("⚠️ Using legacy format (subQuestions)");const e=k.subQuestions[0],t=k?.points||e.points||1;return{...e,points:t,groupNumber:j(P,H)}}return(()=>{const e=j(P,H);return{id:`sq-${Date.now()}-${Math.random()}`,imageUrl:"",word:"",correctAnswer:"",points:1,groupNumber:e}})()}),_=j(P,H),[M,E]=r(()=>{if(console.log("🔍 [useState init] Initializing hintLettersInput",{partId:B,initialGroupNum:_,questionIndexInPart:P}),B){const e=T(B,_);if(console.log("🔍 [useState init] Store hintLetters:",{key:`${B}_${_}`,value:e}),e&&e.length>0)return console.log("✅ [useState init] Using store hintLetters:",e),e.join(", ")}const e=k?.answer?.hintLetters;if(e&&Array.isArray(e)&&e.length>0)return console.log("✅ [useState init] Using answer.hintLetters:",e),e.join(", ");if(k?.hintLetters&&Array.isArray(k.hintLetters)&&k.hintLetters.length>0)return console.log("✅ [useState init] Using initialData.hintLetters:",k.hintLetters),k.hintLetters.join(", ");if(k?.hintLettersByGroup){const e=k.hintLettersByGroup[_];if(e&&Array.isArray(e))return console.log("✅ [useState init] Using hintLettersByGroup:",e),e.join(", ")}const t=W(_);return t.length>0?(console.log("✅ [useState init] Using findSharedHintLetters:",t),t.join(", ")):(console.log("⚠️ [useState init] No hintLetters found, returning empty"),"")}),[J,K]=r(k?.caseSensitive||!1),[X,Y]=r(k?.answer?.explanation||k?.explanation||""),[Z,ee]=r(!1),[te,re]=r(!1),[ne,ie]=r(!1),[se,oe]=r({}),ae=n(""),[le,ce]=r(""),de=n(k),he=n(P),me=n(!1),ue=n(!1),pe=n(q),ge=n(""),xe=z.groupNumber||1,be=n(D);i(()=>{be.current=D},[D]),i(()=>{pe.current=q},[q]);const fe=()=>JSON.stringify({imageUrl:z.imageUrl,word:z.word,correctAnswer:z.correctAnswer,points:z.points,hintLetters:ve(),caseSensitive:J,explanation:X});i(()=>{if(me.current)return;const e=setTimeout(()=>{ae.current=fe(),ie(!1)},150);return()=>clearTimeout(e)},[P]),i(()=>{if(me.current)return;if(!ae.current)return;const e=fe()!==ae.current;e!==ne&&(ie(e),be.current?.(e))},[z,M,J,X,ne]);const Ne=A(e=>{pe.current?.(e)},300);i(()=>{const e=P!==he.current,t=k?.content,r=k?.answer,n=!!(t?.imageUrl||t?.word||r?.correctAnswer),i=!!(k?.subQuestions&&k.subQuestions.length>0),s=n||i,o=!ue.current&&s;if((o||e)&&(o||e)){if(me.current=!0,de.current=k,he.current=P,ue.current=!0,k){const e=j(P,H);if(n){const n=k?.points||1;R({id:`sq-${Date.now()}-synced`,imageUrl:t?.imageUrl||"",word:t?.word||"",correctAnswer:r?.correctAnswer||"",points:n,groupNumber:e});const i=r?.hintLetters;i&&Array.isArray(i)&&i.length>0&&E(i.join(", ")),void 0!==r?.caseSensitive&&K(r.caseSensitive),void 0!==r?.explanation&&Y(r.explanation)}else if(i){console.log("⚠️ [sync effect] Using legacy format (subQuestions)");const t=k?.points||k.subQuestions[0].points||1;R({...k.subQuestions[0],points:t,groupNumber:e})}let s=!1;if(B){const t=T(B,e);t&&t.length>0&&(E(t.join(", ")),s=!0)}if(!s&&k.hintLetters&&Array.isArray(k.hintLetters)&&k.hintLetters.length>0&&(E(k.hintLetters.join(", ")),s=!0),!s&&k.hintLettersByGroup){const t=k.hintLettersByGroup[e];t&&Array.isArray(t)&&(E(t.join(", ")),s=!0)}if(!s&&!n){const t=W(e);t.length>0&&E(t.join(", "))}void 0!==k.caseSensitive&&K(k.caseSensitive)}setTimeout(()=>{me.current=!1},100)}},[k,H,P]);const{previewUrl:we}=S(z.imageUrl);i(()=>{ce(we||z.imageUrl||"")},[z.imageUrl,we]);const ve=()=>M.split(",").map(e=>e.trim()).filter(e=>e.length>0);i(()=>{if(me.current)return;const e=ve(),t={subQuestions:[z],hintLetters:e,caseSensitive:J,groups:H};t.points=z.points||1,t.explanation=X;const r=JSON.stringify(t);ge.current!==r&&(ge.current=r,B&&e.length>0&&(console.log("📤 [onChange effect] Saving hintLetters to store:",{partId:B,groupNumber:xe,hintLettersArray:e}),O(B,xe,e)),Ne(t))},[z,M,J,X,H,Ne,B,xe,O]);const ye=(e,t)=>{R(r=>({...r,[e]:t}))},Le=()=>{const e=ve(),t={};if(0===e.length&&(t.hintLetters="Vui lòng nhập chữ cái gợi ý!"),z.imageUrl||(t.imageUrl="Vui lòng upload hình ảnh!"),z.word||(t.word="Vui lòng nhập từ có chỗ trống!"),z.correctAnswer||(t.correctAnswer="Vui lòng nhập đáp án đúng!"),oe(t),Object.keys(t).length>0)return{isValid:!1};const r={subQuestions:[z],hintLetters:e,caseSensitive:J,groups:H};return r.points=z.points||1,r.explanation=X,{isValid:!0,saveData:r}};i(()=>{G&&(G.current={triggerValidation:async()=>{const{isValid:e}=Le();return e},getFormData:()=>{const e=ve();return{subQuestions:[z],hintLetters:e,caseSensitive:J,groups:H,points:z.points||1,explanation:X}}})},[G,z,M,J,X,H]);if(Z){const r={subQuestions:[{...z,imageUrl:le||z.imageUrl}],hintLetters:ve(),caseSensitive:J,groups:H};return t("div",{className:"space-y-6",children:[e(c,{children:e(h,{children:t("div",{className:"flex items-center justify-between",children:[t("div",{children:[e(m,{children:"Nhìn tranh điền từ gợi ý (Preview)"}),e("p",{className:"mt-1 text-sm text-gray-500",children:"Chế độ xem trước"})]}),t(s,{variant:"outline",size:"sm",onClick:()=>ee(!1),children:[e(p,{className:"mr-2 h-4 w-4"}),"Quay lại Edit Mode"]})]})})}),e(L,{questionData:r,autoFillCorrectAnswers:!0,isReviewMode:!0,explanation:X,onSubmit:()=>{}})]})}const Ue=z.imageUrl||"",Ae=Ue.startsWith("http"),Se=le||(Ae?Ue:""),Ce=Ue&&!Ae&&!le;return t("div",{className:"space-y-6",children:[t(c,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-purple-100/50",children:[e("div",{className:"h-1.5 bg-gradient-to-r from-purple-500 via-indigo-500 to-blue-500"}),e(h,{className:"pb-4",children:t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-purple-500 to-indigo-600 shadow-md",children:e(b,{className:"h-5 w-5 text-white"})}),t("div",{children:[e(m,{className:"text-xl font-bold text-gray-900",children:"Nhìn tranh điền từ gợi ý"}),e("div",{className:"mt-1 inline-flex items-center gap-1.5 rounded-md bg-purple-100 px-2.5 py-1 text-sm font-medium text-purple-700 ring-1 ring-inset ring-purple-700/10",children:t("span",{children:["📚 Nhóm ",xe]})})]})]}),t(s,{variant:"outline",size:"sm",onClick:()=>{ee(!0)},className:"gap-2 border-indigo-200 text-indigo-600 hover:bg-indigo-50 hover:text-indigo-700",children:[e(g,{className:"h-4 w-4"}),"Xem trước"]})]})}),t(d,{className:"space-y-6",children:[e("div",{className:"rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-4",children:e(u,{id:"points-header",value:z.points||1,onChange:e=>{ye("points",Number(e.target.value)||1)}})}),e("div",{className:"rounded-xl border border-purple-200 bg-gradient-to-r from-purple-50 to-indigo-50 p-5 shadow-sm",children:t("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-purple-100",children:e(x,{className:"h-4 w-4 text-purple-600"})}),t("div",{className:"text-sm text-purple-900",children:[e("p",{className:"font-semibold text-purple-800",children:"Hướng dẫn tạo câu hỏi"}),t("ul",{className:"mt-2 space-y-1.5 text-purple-700",children:[t("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-purple-400"}),"Nhập các chữ cái gợi ý (phân cách bằng dấu phẩy)"]}),t("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-purple-400"}),"Sử dụng ",e("code",{className:"mx-1 rounded bg-purple-100 px-1.5 py-0.5 font-mono text-purple-800",children:"{0}"}),", ",e("code",{className:"mx-1 rounded bg-purple-100 px-1.5 py-0.5 font-mono text-purple-800",children:"{1}"}),"... để đánh dấu chỗ trống"]}),t("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-purple-400"}),"Ví dụ từ: ",t("code",{className:"ml-1 rounded bg-purple-100 px-1.5 py-0.5 font-mono text-purple-800 select-all cursor-pointer",title:"Click để copy",children:["c","{0}","{1}"]})," với đáp án ",e("code",{className:"rounded bg-purple-100 px-1.5 py-0.5 font-mono text-purple-800",children:"a, r"})]})]})]})]})}),t("div",{className:"rounded-lg border-2 p-4 "+(se.hintLetters?"border-red-300 bg-red-50":"border-amber-200 bg-amber-50"),children:[t(o,{htmlFor:"hint-letters",className:se.hintLetters?"text-red-800":"text-amber-800",children:["Chữ cái gợi ý ",e("span",{className:"text-red-500",children:"*"})]}),e(a,{id:"hint-letters",type:"text",value:M,onBlur:()=>{const e=ve();e.length>0&&E(e.join(", "))},onChange:e=>{E(e.target.value),se.hintLetters&&oe(e=>({...e,hintLetters:void 0}))},placeholder:"Ví dụ: a, e, i, o, u",className:"mt-1 bg-white "+(se.hintLetters?"border-red-500 focus:ring-red-500":"")}),se.hintLetters?e("p",{className:"mt-1 text-sm text-red-600",children:se.hintLetters}):e("p",{className:"mt-1 text-xs text-amber-700",children:"Nhập các chữ cái gợi ý, phân cách bằng dấu phẩy"})]}),t("div",{className:"flex items-center justify-between rounded-xl border-2 border-gray-200 bg-white p-4 transition-all hover:border-gray-300",children:[t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg bg-gray-100",children:e(N,{className:"h-4 w-4 text-gray-600"})}),t("div",{className:"space-y-0.5",children:[e(o,{htmlFor:"case-sensitive",className:"font-semibold text-gray-800",children:"Phân biệt hoa/thường"}),e("p",{className:"text-sm text-gray-500",children:"Bật nếu đáp án cần phân biệt chữ hoa/thường"})]})]}),e(U,{id:"case-sensitive",checked:J,onCheckedChange:K})]}),F&&F.length>0&&t("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("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-red-100",children:e("span",{className:"text-lg",children:"❌"})}),t("div",{className:"text-sm",children:[e("p",{className:"font-semibold text-red-800",children:"Lỗi:"}),e("ul",{className:"mt-1 list-inside list-disc text-red-700",children:F.map((t,r)=>e("li",{children:t},r))})]})]})]})]}),t(c,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-blue-400 to-indigo-400"}),e(h,{className:"pb-4",children:t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-blue-100",children:e(f,{className:"h-3.5 w-3.5 text-blue-600"})}),e(m,{className:"text-lg font-semibold text-gray-800",children:"Nội dung câu hỏi"}),t("span",{className:"inline-flex items-center gap-1 rounded-md bg-purple-100 px-2 py-0.5 text-xs font-medium text-purple-700 ring-1 ring-inset ring-purple-700/10",children:["📚 Nhóm ",xe]})]})}),t(d,{className:"space-y-4",children:[t("div",{className:"space-y-2",children:[t(o,{className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(b,{className:"h-4 w-4 text-indigo-500"}),"Hình ảnh ",e("span",{className:"text-red-500",children:"*"})]}),e(v,{id:"image",label:"Hình ảnh",accept:"image/*",value:z.imageUrl||"",onChange:e=>{ye("imageUrl",e),se.imageUrl&&oe(e=>({...e,imageUrl:void 0}))},onPresignedUrlChange:e=>{ce(e)},maxSize:5,placeholder:"Upload ảnh hoặc paste URL",required:!0,autoUpload:!0,prefix:"questions"}),se.imageUrl&&e("p",{className:"mt-1 text-sm text-red-600",children:se.imageUrl}),Ce&&e(y,{src:Se,alt:"Preview for question",className:"mt-2 h-48 w-full max-w-md rounded-lg border border-gray-200"})]}),t("div",{children:[t(o,{htmlFor:"word",children:["Từ có chỗ trống ",e("span",{className:"text-red-500",children:"*"})]}),e(a,{id:"word",value:z.word||"",onChange:e=>{ye("word",e.target.value),se.word&&oe(e=>({...e,word:void 0}))},placeholder:"Ví dụ: c{0}r, B{0}{1}k, f{0}o{1}",className:"mt-1 "+(se.word?"border-red-500 focus:ring-red-500":""),maxLength:50}),se.word?e("p",{className:"mt-1 text-sm text-red-600",children:se.word}):t("div",{className:"mt-1 space-y-1",children:[t("p",{className:"text-xs text-gray-500",children:["Sử dụng ","{0}",", ","{1}",", ","{2}",",... để đánh dấu chỗ trống cần điền"]}),t("p",{className:"text-xs text-gray-400",children:["Ví dụ: ",t("code",{className:"bg-gray-100 px-1 rounded select-all cursor-pointer",title:"Click để copy",children:["c","{0}","{1}"]})]})]})]}),t("div",{children:[t(o,{htmlFor:"answer",children:["Đáp án đúng ",e("span",{className:"text-red-500",children:"*"})]}),e(a,{id:"answer",value:z.correctAnswer||"",onChange:e=>{ye("correctAnswer",e.target.value),se.correctAnswer&&oe(e=>({...e,correctAnswer:void 0}))},placeholder:"Ví dụ: a hoặc o,o (cách nhau bằng dấu phẩy)",className:"mt-1 "+(se.correctAnswer?"border-red-500 focus:ring-red-500":"")}),se.correctAnswer?e("p",{className:"mt-1 text-sm text-red-600",children:se.correctAnswer}):e("p",{className:"mt-1 text-xs text-gray-500",children:"Nhập các đáp án cách nhau bằng dấu phẩy. Số đáp án phải khớp với số placeholder"}),(()=>{if(!z.word||!z.correctAnswer)return null;const r=z.word,n=z.correctAnswer.split(",").map(e=>e.trim()).filter(e=>e.length>0),i=r.match(/\{(\d+)\}/g),s=i?i.length:0;let o=r;for(let e=0;e<Math.min(n.length,s);e++)o=o.replace(`{${e}}`,`[${n[e]}]`);return t("div",{className:"mt-3 rounded-lg border-2 border-blue-200 bg-blue-50 p-2",children:[e("p",{className:"text-blue-800",children:o}),n.length!==s&&t("p",{className:"mt-1 text-xs text-amber-600",children:["⚠️ Số đáp án (",n.length,") không khớp với số chỗ trống (",s,")"]})]})})()]})]})]}),t(c,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-amber-400 to-orange-400"}),e(h,{className:"pb-4",children:t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm",children:e(w,{className:"h-4 w-4 text-white"})}),t("div",{children:[t("div",{className:"flex items-center gap-2",children:[e(m,{className:"text-lg font-semibold text-gray-800",children:"Giải thích"}),e("span",{className:"inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600",children:"Không bắt buộc"})]}),e("p",{className:"text-sm text-gray-500 mt-0.5",children:"Giải thích cho đáp án đúng — hiển thị sau khi học sinh hoàn thành câu hỏi"})]})]})}),e(d,{children:e(l,{id:"explanation",value:X,onChange:e=>Y(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 LookPictureFillWordHintCreator(t){return e(k,{...t})}export const LookPictureFillWordHint=LookPictureFillWordHintCreator;