@tinyweb_dev/oe-exam-sdk 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (485) hide show
  1. package/dist/api/exam-create.js +1 -179
  2. package/dist/api/exam-create.types.js +1 -1
  3. package/dist/api/exam-questions.js +1 -98
  4. package/dist/api/exam-questions.types.js +1 -1
  5. package/dist/api/exam-taking.js +1 -73
  6. package/dist/api/exam-taking.types.js +1 -1
  7. package/dist/api/index.js +1 -6
  8. package/dist/api/result-review.js +1 -50
  9. package/dist/api/result-review.types.js +1 -1
  10. package/dist/components/CueCardEditor.js +1 -58
  11. package/dist/components/common/ResolvedImage.js +1 -15
  12. package/dist/components/exams/CreateExamInfoForm.js +1 -35
  13. package/dist/components/exams/CreateExamPageContainer.js +1 -103
  14. package/dist/components/exams/ExamCreator.js +1 -89
  15. package/dist/components/exams/ExamPartTabs.js +1 -40
  16. package/dist/components/exams/ExamQuestionGrid.js +1 -18
  17. package/dist/components/exams/ExamQuestionsPageContainer.js +1 -90
  18. package/dist/components/exams/PartAudioUploader.js +1 -9
  19. package/dist/components/exams/TemplateSelector.js +1 -66
  20. package/dist/components/exams/api.js +1 -3
  21. package/dist/components/exams/create-exam.validation.js +1 -18
  22. package/dist/components/exams/exam-template.utils.js +1 -18
  23. package/dist/components/exams/index.js +1 -10
  24. package/dist/components/exams/take/ExamTakingPageContainer.js +1 -153
  25. package/dist/components/exams/take/GenericQuestionRenderer.js +1 -14
  26. package/dist/components/exams/take/exam-taking.utils.js +1 -88
  27. package/dist/components/exams/take/hooks/useSpeakingRecorder.js +1 -24
  28. package/dist/components/exams/take/index.js +1 -2
  29. package/dist/components/exams/take/types.js +1 -1
  30. package/dist/components/index.js +1 -42
  31. package/dist/components/questions/_shared/config/question-types.config.js +1 -333
  32. package/dist/components/questions/_shared/constants/color-palette.js +1 -21
  33. package/dist/components/questions/_shared/hooks/useAnswerGroupQuestionGroup.js +1 -105
  34. package/dist/components/questions/_shared/hooks/useBasicQuestionGroup.js +1 -222
  35. package/dist/components/questions/_shared/hooks/useCompareImagesGroup.js +1 -140
  36. package/dist/components/questions/_shared/hooks/useImageQuestionGroup.js +1 -123
  37. package/dist/components/questions/_shared/hooks/useInfoExchangeGroup.js +1 -50
  38. package/dist/components/questions/_shared/hooks/useQuestionFormState.js +1 -54
  39. package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +1 -123
  40. package/dist/components/questions/_shared/hooks/useQuestionListGroup.js +1 -121
  41. package/dist/components/questions/_shared/hooks/useQuestionOnChange.js +1 -70
  42. package/dist/components/questions/_shared/hooks/useSceneQuestionGroup.js +1 -194
  43. package/dist/components/questions/_shared/index.js +1 -7
  44. package/dist/components/questions/_shared/types/answer-the-question.type.js +1 -1
  45. package/dist/components/questions/_shared/types/arrange-letters-into-words.type.js +1 -1
  46. package/dist/components/questions/_shared/types/choose-correct-adjective.type.js +1 -3
  47. package/dist/components/questions/_shared/types/color-region.config.js +1 -1
  48. package/dist/components/questions/_shared/types/fill-in-blank.type.js +1 -1
  49. package/dist/components/questions/_shared/types/fill-missing-words-in-grid.type.js +1 -1
  50. package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +1 -1
  51. package/dist/components/questions/_shared/types/index.js +1 -12
  52. package/dist/components/questions/_shared/types/label-the-picture.type.js +1 -1
  53. package/dist/components/questions/_shared/types/listen-and-choose-from-answer-group.type.js +1 -1
  54. package/dist/components/questions/_shared/types/listen-and-choose-objects-in-scene.type.js +1 -1
  55. package/dist/components/questions/_shared/types/listen-and-drag-objects-into-scene.type.js +1 -1
  56. package/dist/components/questions/_shared/types/listen-and-speak-answer.type.js +1 -1
  57. package/dist/components/questions/_shared/types/listen-and-speak-compare-images.type.js +1 -1
  58. package/dist/components/questions/_shared/types/listen-and-speak-image-group.type.js +1 -1
  59. package/dist/components/questions/_shared/types/listen-and-speak-info-exchange.type.js +1 -1
  60. package/dist/components/questions/_shared/types/listen-and-speak-odd-one-out.type.js +1 -3
  61. package/dist/components/questions/_shared/types/listen-and-speak-question-list.type.js +1 -1
  62. package/dist/components/questions/_shared/types/listen-and-speak-with-story-images.type.js +1 -3
  63. package/dist/components/questions/_shared/types/listen-and-tick-answer.type.js +1 -1
  64. package/dist/components/questions/_shared/types/listening-drawline.type.js +1 -1
  65. package/dist/components/questions/_shared/types/look-and-choose-answer.type.js +1 -1
  66. package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +1 -1
  67. package/dist/components/questions/_shared/types/match-by-writing-answer.type.js +1 -1
  68. package/dist/components/questions/_shared/types/multiple-choice.type.js +1 -1
  69. package/dist/components/questions/_shared/types/question-group.type.js +1 -11
  70. package/dist/components/questions/_shared/types/read-and-color-objects.type.js +1 -1
  71. package/dist/components/questions/_shared/types/read-displayed-content.type.js +1 -1
  72. package/dist/components/questions/_shared/types/read-passage-and-answer-questions.type.js +1 -1
  73. package/dist/components/questions/_shared/types/speaking-describe-image.type.js +1 -1
  74. package/dist/components/questions/_shared/types/spontaneous-qa.type.js +1 -1
  75. package/dist/components/questions/_shared/types/word-fill-paragraph-with-options.type.js +1 -1
  76. package/dist/components/questions/_shared/types/word-fill-paragraph.type.js +1 -1
  77. package/dist/components/questions/_shared/types/word-fill-structured-form.type.js +1 -1
  78. package/dist/components/questions/_shared/types/word-ordering.type.js +1 -1
  79. package/dist/components/questions/_shared/types/write-a-short-letter.type.js +1 -1
  80. package/dist/components/questions/_shared/types/write-correct-verb-form.type.js +1 -3
  81. package/dist/components/questions/_shared/types/write-sentences.type.js +1 -1
  82. package/dist/components/questions/_shared/types/write-short-paragraph.type.js +1 -1
  83. package/dist/components/questions/_shared/utils/answer-builder.js +1 -79
  84. package/dist/components/questions/_shared/utils/createColorRegionRegistration.js +1 -39
  85. package/dist/components/questions/_shared/utils/id-generator.js +1 -3
  86. package/dist/components/questions/_shared/utils/question-validation.js +1 -21
  87. package/dist/components/questions/components/ColorNode.js +1 -85
  88. package/dist/components/questions/components/ColorRegionClient.js +1 -545
  89. package/dist/components/questions/components/ColorRegionCreator.js +1 -420
  90. package/dist/components/questions/components/ColorRegionNode.js +1 -59
  91. package/dist/components/questions/components/CreatorGuide.js +1 -103
  92. package/dist/components/questions/components/NameNode.js +1 -26
  93. package/dist/components/questions/components/QuestionBankOption.js +1 -46
  94. package/dist/components/questions/components/QuestionBasicInfoForm.js +1 -9
  95. package/dist/components/questions/components/QuestionCommonFields.js +1 -33
  96. package/dist/components/questions/components/QuestionSearchDropdown.js +1 -258
  97. package/dist/components/questions/components/QuestionTypeSelector.js +1 -32
  98. package/dist/components/questions/components/QuestionTypeSpecificForms.js +1 -6
  99. package/dist/components/questions/components/RegionNode.js +1 -94
  100. package/dist/components/questions/components/index.js +1 -14
  101. package/dist/components/questions/creator/QuestionCreator.js +1 -90
  102. package/dist/components/questions/creator/QuestionGroupCreator.js +1 -119
  103. package/dist/components/questions/creator/dedicated-component-router.js +1 -79
  104. package/dist/components/questions/creator/index.js +1 -3
  105. package/dist/components/questions/creator/question-type-registry.js +1 -80
  106. package/dist/components/questions/groups/ArticlesGroupCard.js +1 -121
  107. package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -50
  108. package/dist/components/questions/groups/DocumentGroupCard.js +1 -114
  109. package/dist/components/questions/groups/MatchAnswerGroupCard.js +1 -25
  110. package/dist/components/questions/index.js +1 -10
  111. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +1 -37
  112. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +1 -351
  113. package/dist/components/questions/types/answer-the-question/register.js +1 -67
  114. package/dist/components/questions/types/answer-the-question/transform.js +1 -36
  115. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +1 -141
  116. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +1 -118
  117. package/dist/components/questions/types/arrange-letters-into-words/index.js +1 -2
  118. package/dist/components/questions/types/arrange-letters-into-words/register.js +1 -4
  119. package/dist/components/questions/types/arrange-letters-into-words/transform.js +1 -24
  120. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -64
  121. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +1 -169
  122. package/dist/components/questions/types/choose-correct-adjective/index.js +1 -2
  123. package/dist/components/questions/types/choose-correct-adjective/register.js +1 -55
  124. package/dist/components/questions/types/choose-correct-adjective/transform.js +1 -16
  125. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +1 -131
  126. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +1 -581
  127. package/dist/components/questions/types/choose-the-correct-answer/index.js +1 -2
  128. package/dist/components/questions/types/choose-the-correct-answer/register.js +1 -101
  129. package/dist/components/questions/types/choose-the-correct-answer/transform.js +1 -43
  130. package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +1 -275
  131. package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +1 -448
  132. package/dist/components/questions/types/fill-in-blank/index.js +1 -2
  133. package/dist/components/questions/types/fill-in-blank/register.js +1 -138
  134. package/dist/components/questions/types/fill-in-blank/transform.js +1 -59
  135. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +1 -117
  136. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +1 -671
  137. package/dist/components/questions/types/fill-missing-words-in-grid/index.js +1 -2
  138. package/dist/components/questions/types/fill-missing-words-in-grid/register.js +1 -37
  139. package/dist/components/questions/types/fill-missing-words-in-grid/transform.js +1 -15
  140. package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +1 -262
  141. package/dist/components/questions/types/gn-speaking-interview/register.js +1 -121
  142. package/dist/components/questions/types/gn-speaking-interview/transform.js +1 -48
  143. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingClient.js +1 -16
  144. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingCreator.js +1 -27
  145. package/dist/components/questions/types/image-object-matching/index.js +1 -2
  146. package/dist/components/questions/types/image-object-matching/register.js +1 -3
  147. package/dist/components/questions/types/image-object-matching/transform.js +1 -2
  148. package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +1 -102
  149. package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +1 -344
  150. package/dist/components/questions/types/label-the-picture/index.js +1 -2
  151. package/dist/components/questions/types/label-the-picture/register.js +1 -54
  152. package/dist/components/questions/types/label-the-picture/transform.js +1 -28
  153. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +1 -78
  154. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +1 -232
  155. package/dist/components/questions/types/listen-and-choose-from-answer-group/index.js +1 -2
  156. package/dist/components/questions/types/listen-and-choose-from-answer-group/register.js +1 -82
  157. package/dist/components/questions/types/listen-and-choose-from-answer-group/transform.js +1 -27
  158. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +1 -70
  159. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +1 -386
  160. package/dist/components/questions/types/listen-and-choose-objects-in-scene/index.js +1 -2
  161. package/dist/components/questions/types/listen-and-choose-objects-in-scene/register.js +1 -100
  162. package/dist/components/questions/types/listen-and-choose-objects-in-scene/transform.js +1 -33
  163. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +1 -106
  164. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +1 -457
  165. package/dist/components/questions/types/listen-and-drag-objects-into-scene/index.js +1 -2
  166. package/dist/components/questions/types/listen-and-drag-objects-into-scene/register.js +1 -110
  167. package/dist/components/questions/types/listen-and-drag-objects-into-scene/transform.js +1 -35
  168. package/dist/components/questions/types/listen-and-speak-answer/ArrowIndicator.js +1 -8
  169. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -22
  170. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +1 -291
  171. package/dist/components/questions/types/listen-and-speak-answer/index.js +1 -2
  172. package/dist/components/questions/types/listen-and-speak-answer/register.js +1 -84
  173. package/dist/components/questions/types/listen-and-speak-answer/transform.js +1 -27
  174. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -32
  175. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +1 -295
  176. package/dist/components/questions/types/listen-and-speak-compare-images/register.js +1 -79
  177. package/dist/components/questions/types/listen-and-speak-compare-images/transform.js +1 -27
  178. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -34
  179. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +1 -285
  180. package/dist/components/questions/types/listen-and-speak-image-group/register.js +1 -81
  181. package/dist/components/questions/types/listen-and-speak-image-group/transform.js +1 -26
  182. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -38
  183. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +1 -385
  184. package/dist/components/questions/types/listen-and-speak-info-exchange/register.js +1 -87
  185. package/dist/components/questions/types/listen-and-speak-info-exchange/transform.js +1 -32
  186. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -26
  187. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +1 -247
  188. package/dist/components/questions/types/listen-and-speak-odd-one-out/register.js +1 -59
  189. package/dist/components/questions/types/listen-and-speak-odd-one-out/transform.js +1 -18
  190. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -26
  191. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +1 -256
  192. package/dist/components/questions/types/listen-and-speak-question-list/register.js +1 -81
  193. package/dist/components/questions/types/listen-and-speak-question-list/transform.js +1 -26
  194. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -26
  195. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +1 -230
  196. package/dist/components/questions/types/listen-and-speak-with-story-images/register.js +1 -56
  197. package/dist/components/questions/types/listen-and-speak-with-story-images/transform.js +1 -16
  198. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +1 -72
  199. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +1 -230
  200. package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +1 -1
  201. package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +1 -66
  202. package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +1 -42
  203. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +1 -96
  204. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +1 -238
  205. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +1 -2
  206. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +1 -76
  207. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +1 -19
  208. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +1 -134
  209. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +1 -617
  210. package/dist/components/questions/types/look-picture-fill-word-hint/index.js +1 -2
  211. package/dist/components/questions/types/look-picture-fill-word-hint/register.js +1 -82
  212. package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +1 -29
  213. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +1 -38
  214. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +1 -359
  215. package/dist/components/questions/types/match-by-writing-answer/index.js +1 -3
  216. package/dist/components/questions/types/match-by-writing-answer/register.js +1 -58
  217. package/dist/components/questions/types/match-by-writing-answer/transform.js +1 -61
  218. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsClient.js +1 -16
  219. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsCreator.js +1 -23
  220. package/dist/components/questions/types/read-and-color-objects/index.js +1 -2
  221. package/dist/components/questions/types/read-and-color-objects/register.js +1 -3
  222. package/dist/components/questions/types/read-and-color-objects/transform.js +1 -36
  223. package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +1 -125
  224. package/dist/components/questions/types/read-displayed-content/register.js +1 -64
  225. package/dist/components/questions/types/read-displayed-content/transform.js +1 -12
  226. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -64
  227. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +1 -511
  228. package/dist/components/questions/types/read-passage-and-answer-questions/index.js +1 -2
  229. package/dist/components/questions/types/read-passage-and-answer-questions/register.js +1 -58
  230. package/dist/components/questions/types/read-passage-and-answer-questions/transform.js +1 -28
  231. package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +1 -373
  232. package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +1 -41
  233. package/dist/components/questions/types/speaking-conversation/register.js +1 -110
  234. package/dist/components/questions/types/speaking-conversation/transform.js +1 -34
  235. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +1 -74
  236. package/dist/components/questions/types/speaking-cue-card/register.js +1 -52
  237. package/dist/components/questions/types/speaking-cue-card/transform.js +1 -12
  238. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -10
  239. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +1 -161
  240. package/dist/components/questions/types/speaking-describe-image/index.js +1 -2
  241. package/dist/components/questions/types/speaking-describe-image/register.js +1 -69
  242. package/dist/components/questions/types/speaking-describe-image/transform.js +1 -16
  243. package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +1 -234
  244. package/dist/components/questions/types/spontaneous-qa/register.js +1 -93
  245. package/dist/components/questions/types/spontaneous-qa/transform.js +1 -34
  246. package/dist/components/questions/types/true-false/TrueFalseClient.js +1 -75
  247. package/dist/components/questions/types/true-false/TrueFalseCreator.js +1 -244
  248. package/dist/components/questions/types/true-false/index.js +1 -2
  249. package/dist/components/questions/types/true-false/register.js +1 -77
  250. package/dist/components/questions/types/true-false/transform.js +1 -32
  251. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +1 -233
  252. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +1 -631
  253. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphWrapper.js +1 -28
  254. package/dist/components/questions/types/word-fill-paragraph/index.js +1 -2
  255. package/dist/components/questions/types/word-fill-paragraph/register.js +1 -136
  256. package/dist/components/questions/types/word-fill-paragraph/transform.js +1 -56
  257. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -138
  258. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +1 -261
  259. package/dist/components/questions/types/word-fill-structured-form/index.js +1 -2
  260. package/dist/components/questions/types/word-fill-structured-form/register.js +1 -67
  261. package/dist/components/questions/types/word-fill-structured-form/transform.js +1 -40
  262. package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -84
  263. package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +1 -192
  264. package/dist/components/questions/types/word-ordering/index.js +1 -2
  265. package/dist/components/questions/types/word-ordering/register.js +1 -46
  266. package/dist/components/questions/types/word-ordering/transform.js +1 -24
  267. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +1 -98
  268. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +1 -320
  269. package/dist/components/questions/types/write-a-short-letter/index.js +1 -2
  270. package/dist/components/questions/types/write-a-short-letter/register.js +1 -68
  271. package/dist/components/questions/types/write-a-short-letter/transform.js +1 -25
  272. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +1 -61
  273. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +1 -196
  274. package/dist/components/questions/types/write-correct-verb-form/index.js +1 -2
  275. package/dist/components/questions/types/write-correct-verb-form/register.js +1 -62
  276. package/dist/components/questions/types/write-correct-verb-form/transform.js +1 -17
  277. package/dist/components/questions/types/write-sentences/WriteSentencesClient.js +1 -22
  278. package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +1 -174
  279. package/dist/components/questions/types/write-sentences/index.js +1 -2
  280. package/dist/components/questions/types/write-sentences/register.js +1 -58
  281. package/dist/components/questions/types/write-sentences/transform.js +1 -28
  282. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -96
  283. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +1 -161
  284. package/dist/components/questions/types/write-short-paragraph/index.js +1 -2
  285. package/dist/components/questions/types/write-short-paragraph/register.js +1 -4
  286. package/dist/components/questions/types/write-short-paragraph/transform.js +1 -21
  287. package/dist/components/questions/viewer/QuestionGroupViewer.js +1 -36
  288. package/dist/components/questions/viewer/QuestionViewer.js +1 -446
  289. package/dist/components/questions/viewer/index.js +1 -2
  290. package/dist/components/results/ResultReviewPageContainer.js +1 -98
  291. package/dist/components/results/ReviewQuestionRenderer.js +1 -72
  292. package/dist/components/results/index.js +1 -2
  293. package/dist/components/results/result-review.utils.js +1 -6
  294. package/dist/components/results/review-data.js +1 -150
  295. package/dist/components/results/review-renderer.utils.js +1 -97
  296. package/dist/components/results/review-types.js +1 -1
  297. package/dist/components/themes/cambridge-yle/CambridgeYleAudioPlayer.js +1 -138
  298. package/dist/components/themes/cambridge-yle/CambridgeYleBottomNav.js +1 -12
  299. package/dist/components/themes/cambridge-yle/CambridgeYleExamLayout.js +1 -18
  300. package/dist/components/themes/cambridge-yle/CambridgeYleExampleText.js +1 -11
  301. package/dist/components/themes/cambridge-yle/CambridgeYleHeader.js +1 -11
  302. package/dist/components/themes/cambridge-yle/CambridgeYleInstructionBanner.js +1 -9
  303. package/dist/components/themes/cambridge-yle/CambridgeYleNavButton.js +1 -13
  304. package/dist/components/themes/cambridge-yle/CambridgeYlePartBanner.js +1 -8
  305. package/dist/components/themes/cambridge-yle/CambridgeYleProgressBar.js +1 -6
  306. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCard.js +1 -5
  307. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCardFullWidth.js +1 -9
  308. package/dist/components/themes/cambridge-yle/CambridgeYleSidebar.js +1 -41
  309. package/dist/components/themes/cambridge-yle/CambridgeYleTimer.js +1 -25
  310. package/dist/components/themes/cambridge-yle/index.js +1 -13
  311. package/dist/components/themes/english-certification/EcAnswerTheQuestion.js +1 -24
  312. package/dist/components/themes/english-certification/EcMatchByWritingAnswer.js +1 -88
  313. package/dist/components/themes/english-certification/EcWordFillParagraph.js +1 -55
  314. package/dist/components/themes/english-certification/EcWordFillStructuredForm.js +1 -156
  315. package/dist/components/themes/english-certification/EcWritingTask.js +1 -34
  316. package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.js +1 -114
  317. package/dist/components/themes/english-certification/EnglishCertificationBrand.js +1 -11
  318. package/dist/components/themes/english-certification/EnglishCertificationExamLayout.js +1 -21
  319. package/dist/components/themes/english-certification/EnglishCertificationFlagButton.js +1 -15
  320. package/dist/components/themes/english-certification/EnglishCertificationFooterBar.js +1 -19
  321. package/dist/components/themes/english-certification/EnglishCertificationGroupedQuestion.js +1 -148
  322. package/dist/components/themes/english-certification/EnglishCertificationHeader.js +1 -31
  323. package/dist/components/themes/english-certification/EnglishCertificationNavigationPanel.js +1 -71
  324. package/dist/components/themes/english-certification/EnglishCertificationOptionSelector.js +1 -33
  325. package/dist/components/themes/english-certification/EnglishCertificationPassagePanel.js +1 -16
  326. package/dist/components/themes/english-certification/EnglishCertificationQuestionCard.js +1 -22
  327. package/dist/components/themes/english-certification/EnglishCertificationQuestionItem.js +1 -37
  328. package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.js +1 -72
  329. package/dist/components/themes/english-certification/EnglishCertificationReadingSection.js +1 -90
  330. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestion.js +1 -18
  331. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestionList.js +1 -29
  332. package/dist/components/themes/english-certification/english-certification-theme.js +1 -58
  333. package/dist/components/themes/english-certification/grouped-question-utils.js +1 -218
  334. package/dist/components/themes/english-certification/index.js +1 -21
  335. package/dist/components/themes/index.js +1 -6
  336. package/dist/components/themes/summer-sky/SummerSkyAudioPlayer.js +1 -170
  337. package/dist/components/themes/summer-sky/SummerSkyLayout.js +1 -221
  338. package/dist/components/themes/summer-sky/SummerSkySubmitModal.js +1 -18
  339. package/dist/components/themes/summer-sky/SummerSkyWaitingRoom.js +1 -194
  340. package/dist/components/themes/types.js +1 -1
  341. package/dist/components/ui/alert-dialog.js +1 -26
  342. package/dist/components/ui/alert.js +1 -22
  343. package/dist/components/ui/avatar.js +1 -12
  344. package/dist/components/ui/badge.js +1 -38
  345. package/dist/components/ui/button.js +1 -35
  346. package/dist/components/ui/calendar.js +1 -76
  347. package/dist/components/ui/card.js +1 -16
  348. package/dist/components/ui/carousel.js +1 -91
  349. package/dist/components/ui/checkbox.js +1 -9
  350. package/dist/components/ui/collapsible.js +1 -13
  351. package/dist/components/ui/command.js +1 -34
  352. package/dist/components/ui/confirm-dialog.js +1 -49
  353. package/dist/components/ui/dialog.js +1 -23
  354. package/dist/components/ui/drawer.js +1 -35
  355. package/dist/components/ui/dropdown-menu.js +1 -33
  356. package/dist/components/ui/file-upload.js +1 -155
  357. package/dist/components/ui/form.js +1 -62
  358. package/dist/components/ui/hover-card.js +1 -10
  359. package/dist/components/ui/image-preview.js +1 -29
  360. package/dist/components/ui/input.js +1 -8
  361. package/dist/components/ui/label.js +1 -9
  362. package/dist/components/ui/language-switcher.js +1 -15
  363. package/dist/components/ui/multi-select.js +1 -90
  364. package/dist/components/ui/points-input.js +1 -33
  365. package/dist/components/ui/popover.js +1 -26
  366. package/dist/components/ui/progress.js +1 -16
  367. package/dist/components/ui/radio-group.js +1 -12
  368. package/dist/components/ui/scroll-area.js +1 -11
  369. package/dist/components/ui/select.js +1 -38
  370. package/dist/components/ui/separator.js +1 -8
  371. package/dist/components/ui/sheet.js +1 -40
  372. package/dist/components/ui/single-select.js +1 -76
  373. package/dist/components/ui/skeleton.js +1 -6
  374. package/dist/components/ui/switch.js +1 -8
  375. package/dist/components/ui/table.js +1 -20
  376. package/dist/components/ui/tabs.js +1 -17
  377. package/dist/components/ui/textarea.js +1 -8
  378. package/dist/components/ui/tooltip.js +1 -11
  379. package/dist/index.js +1 -19
  380. package/dist/mcp/config.js +1 -32
  381. package/dist/mcp/http-client.js +1 -48
  382. package/dist/mcp/index.js +1 -29
  383. package/dist/mcp/tools/_helpers.js +1 -25
  384. package/dist/mcp/tools/exams.js +1 -439
  385. package/dist/mcp/tools/index.js +1 -6
  386. package/dist/mcp/tools/results.js +1 -19
  387. package/dist/shared/constants/ApiConstant.js +1 -276
  388. package/dist/shared/constants/QueryKeyConstant.js +1 -112
  389. package/dist/shared/constants/exam-level.enum.js +1 -41
  390. package/dist/shared/constants/landings/placement-test.constants.js +1 -431
  391. package/dist/shared/constants/question-skills.js +1 -267
  392. package/dist/shared/constants/routes.js +1 -81
  393. package/dist/shared/index.js +1 -7
  394. package/dist/shared/lib/hooks/index.js +1 -10
  395. package/dist/shared/lib/hooks/useAlertDialog.js +1 -57
  396. package/dist/shared/lib/hooks/useAudioPlayCounter.js +1 -30
  397. package/dist/shared/lib/hooks/useAudioPreview.js +1 -81
  398. package/dist/shared/lib/hooks/useAudioUpload.js +1 -24
  399. package/dist/shared/lib/hooks/useConfirmDialog.js +1 -89
  400. package/dist/shared/lib/hooks/useDebouncedCallback.js +1 -32
  401. package/dist/shared/lib/hooks/useDelayedAutoPlay.js +1 -12
  402. package/dist/shared/lib/hooks/useFileUpload.js +1 -24
  403. package/dist/shared/lib/hooks/useImageUrl.js +1 -5
  404. package/dist/shared/lib/hooks/usePresignedFileUrl.js +1 -10
  405. package/dist/shared/lib/hooks/useReactHookForm.js +1 -31
  406. package/dist/shared/lib/hooks/useToast.js +1 -62
  407. package/dist/shared/lib/i18n/index.js +1 -54
  408. package/dist/shared/lib/i18n/messages/en/admin.js +1 -2104
  409. package/dist/shared/lib/i18n/messages/en/auth.js +1 -86
  410. package/dist/shared/lib/i18n/messages/en/common.js +1 -140
  411. package/dist/shared/lib/i18n/messages/en/file-upload.js +1 -18
  412. package/dist/shared/lib/i18n/messages/en/student.js +1 -133
  413. package/dist/shared/lib/i18n/messages/en/terms.js +1 -37
  414. package/dist/shared/lib/i18n/messages/en.js +1 -15
  415. package/dist/shared/lib/i18n/messages/vi/admin.js +1 -2104
  416. package/dist/shared/lib/i18n/messages/vi/auth.js +1 -86
  417. package/dist/shared/lib/i18n/messages/vi/common.js +1 -140
  418. package/dist/shared/lib/i18n/messages/vi/file-upload.js +1 -18
  419. package/dist/shared/lib/i18n/messages/vi/student.js +1 -133
  420. package/dist/shared/lib/i18n/messages/vi/terms.js +1 -37
  421. package/dist/shared/lib/i18n/messages/vi.js +1 -15
  422. package/dist/shared/lib/i18n/types.js +1 -1
  423. package/dist/shared/lib/stores/examQuestionStore.js +1 -212
  424. package/dist/shared/lib/stores/localeStore.js +1 -10
  425. package/dist/shared/lib/utils/public-page.js +1 -44
  426. package/dist/shared/lib/utils/question-transform-types.js +1 -1
  427. package/dist/shared/lib/utils/question-transform.js +1 -174
  428. package/dist/shared/lib/utils.js +1 -168
  429. package/dist/shared/lib/validation/choose-correct-answer.validation.js +1 -111
  430. package/dist/shared/lib/validation/label-the-picture.validation.js +1 -19
  431. package/dist/shared/lib/validation/listen-and-choose-from-answer-group.validation.js +1 -46
  432. package/dist/shared/lib/validation/listen-and-choose-objects-in-scene.validation.js +1 -21
  433. package/dist/shared/lib/validation/listen-and-drag-objects-into-scene.validation.js +1 -34
  434. package/dist/shared/lib/validation/listen-and-speak-answer.validation.js +1 -40
  435. package/dist/shared/lib/validation/listen-and-speak-compare-images.validation.js +1 -45
  436. package/dist/shared/lib/validation/listen-and-speak-image-group.validation.js +1 -19
  437. package/dist/shared/lib/validation/listen-and-speak-info-exchange.validation.js +1 -39
  438. package/dist/shared/lib/validation/listen-and-speak-odd-one-out.validation.js +1 -32
  439. package/dist/shared/lib/validation/listen-and-speak-question-list.validation.js +1 -19
  440. package/dist/shared/lib/validation/listen-and-speak-with-story-images.validation.js +1 -28
  441. package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +1 -12
  442. package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +1 -47
  443. package/dist/shared/lib/validation/read-and-color-objects.validation.js +1 -29
  444. package/dist/shared/lib/validation/read-displayed-content.validation.js +1 -28
  445. package/dist/shared/lib/validation/speaking-conversation.validation.js +1 -75
  446. package/dist/shared/lib/validation/speaking-describe-image.validation.js +1 -31
  447. package/dist/shared/lib/validation/spontaneous-qa.validation.js +1 -48
  448. package/dist/shared/lib/validation/word-fill-paragraph-default.validation.js +1 -38
  449. package/dist/shared/lib/validation/word-fill-paragraph-with-options.validation.js +1 -39
  450. package/dist/shared/lib/validation/word-fill-paragraph.validation.js +1 -43
  451. package/dist/shared/lib/validation/word-ordering.validation.js +1 -33
  452. package/dist/shared/types/branch.types.js +1 -6
  453. package/dist/shared/types/common.types.js +1 -50
  454. package/dist/shared/types/entities/exam-schedule.types.js +1 -5
  455. package/dist/shared/types/entities/exam.types.js +1 -1
  456. package/dist/shared/types/entities/question.types.js +1 -4
  457. package/dist/shared/types/entities/result.types.js +1 -4
  458. package/dist/shared/types/entities/user.types.js +1 -4
  459. package/dist/shared/types/exam-taking.types.js +1 -2
  460. package/dist/shared/types/history.types.js +1 -34
  461. package/dist/shared/types/index.js +1 -1
  462. package/dist/shared/types/questions/choose-correct-adjective.js +1 -4
  463. package/dist/shared/types/questions/choose-the-correct-answer.js +1 -9
  464. package/dist/shared/types/questions/fill-missing-words-in-grid.js +1 -11
  465. package/dist/shared/types/questions/index.js +1 -40
  466. package/dist/shared/types/questions/listen-and-speak-answer.js +1 -1
  467. package/dist/shared/types/questions/listen-and-speak-compare-images.js +1 -1
  468. package/dist/shared/types/questions/listen-and-speak-image-group.js +1 -1
  469. package/dist/shared/types/questions/listen-and-speak-info-exchange.js +1 -3
  470. package/dist/shared/types/questions/listen-and-speak-odd-one-out.js +1 -3
  471. package/dist/shared/types/questions/listen-and-speak-question-list.js +1 -1
  472. package/dist/shared/types/questions/listen-and-speak-with-story-images.js +1 -3
  473. package/dist/shared/types/questions/look-picture-choose-correct-answer.js +1 -5
  474. package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +1 -9
  475. package/dist/shared/types/questions/look-picture-fill-word-hint.js +1 -7
  476. package/dist/shared/types/questions/read-displayed-content.js +1 -1
  477. package/dist/shared/types/questions/speaking-conversation.js +1 -7
  478. package/dist/shared/types/questions/speaking-describe-image.js +1 -1
  479. package/dist/shared/types/questions/spontaneous-qa.js +1 -1
  480. package/dist/shared/types/questions/write-correct-verb-form.js +1 -12
  481. package/dist/shared/types/role.types.js +1 -1
  482. package/dist/shared/types/structure-management.js +1 -28
  483. package/dist/shared/types/student.types.js +1 -54
  484. package/dist/shared/types/teacher-dashboard-rooms.js +1 -2
  485. package/package.json +4 -2
@@ -1,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;