@tinyweb_dev/oe-exam-sdk 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (514) hide show
  1. package/dist/api/exam-create.js +1 -179
  2. package/dist/api/exam-create.types.js +1 -1
  3. package/dist/api/exam-questions.d.ts +1 -1
  4. package/dist/api/exam-questions.js +1 -98
  5. package/dist/api/exam-questions.types.d.ts +45 -1
  6. package/dist/api/exam-questions.types.js +1 -1
  7. package/dist/api/exam-taking.js +1 -73
  8. package/dist/api/exam-taking.types.js +1 -1
  9. package/dist/api/index.js +1 -6
  10. package/dist/api/result-review.js +1 -50
  11. package/dist/api/result-review.types.js +1 -1
  12. package/dist/components/CueCardEditor.js +1 -58
  13. package/dist/components/common/ResolvedImage.js +1 -15
  14. package/dist/components/exams/CompletionDialog.d.ts +8 -0
  15. package/dist/components/exams/CompletionDialog.js +1 -0
  16. package/dist/components/exams/CreateExamInfoForm.d.ts +4 -1
  17. package/dist/components/exams/CreateExamInfoForm.js +1 -35
  18. package/dist/components/exams/CreateExamPageContainer.d.ts +3 -0
  19. package/dist/components/exams/CreateExamPageContainer.js +1 -103
  20. package/dist/components/exams/ExamCreator.d.ts +9 -3
  21. package/dist/components/exams/ExamCreator.js +1 -89
  22. package/dist/components/exams/ExamPartTabs.d.ts +1 -1
  23. package/dist/components/exams/ExamPartTabs.js +1 -40
  24. package/dist/components/exams/ExamPreviewDialog.d.ts +20 -0
  25. package/dist/components/exams/ExamPreviewDialog.js +1 -0
  26. package/dist/components/exams/ExamQuestionGrid.d.ts +1 -1
  27. package/dist/components/exams/ExamQuestionGrid.js +1 -18
  28. package/dist/components/exams/ExamQuestionsPageContainer.d.ts +3 -0
  29. package/dist/components/exams/ExamQuestionsPageContainer.js +1 -90
  30. package/dist/components/exams/ExamViewDialog.d.ts +24 -0
  31. package/dist/components/exams/ExamViewDialog.js +1 -0
  32. package/dist/components/exams/PartAudioUploader.js +1 -9
  33. package/dist/components/exams/TemplateSelector.js +1 -66
  34. package/dist/components/exams/UnsavedChangesDialog.d.ts +8 -0
  35. package/dist/components/exams/UnsavedChangesDialog.js +1 -0
  36. package/dist/components/exams/api.d.ts +1 -1
  37. package/dist/components/exams/api.js +1 -3
  38. package/dist/components/exams/create-exam.texts.d.ts +58 -0
  39. package/dist/components/exams/create-exam.texts.js +1 -0
  40. package/dist/components/exams/create-exam.validation.js +1 -18
  41. package/dist/components/exams/exam-question-status.d.ts +9 -0
  42. package/dist/components/exams/exam-question-status.js +1 -0
  43. package/dist/components/exams/exam-template.utils.js +1 -18
  44. package/dist/components/exams/index.d.ts +11 -1
  45. package/dist/components/exams/index.js +1 -10
  46. package/dist/components/exams/take/ExamTakingPageContainer.js +1 -153
  47. package/dist/components/exams/take/GenericQuestionRenderer.js +1 -14
  48. package/dist/components/exams/take/exam-taking.utils.js +1 -88
  49. package/dist/components/exams/take/hooks/useSpeakingRecorder.js +1 -24
  50. package/dist/components/exams/take/index.js +1 -2
  51. package/dist/components/exams/take/types.js +1 -1
  52. package/dist/components/index.js +1 -42
  53. package/dist/components/questions/_shared/config/question-types.config.js +1 -333
  54. package/dist/components/questions/_shared/constants/color-palette.js +1 -21
  55. package/dist/components/questions/_shared/hooks/useAnswerGroupQuestionGroup.js +1 -105
  56. package/dist/components/questions/_shared/hooks/useBasicQuestionGroup.js +1 -222
  57. package/dist/components/questions/_shared/hooks/useCompareImagesGroup.js +1 -140
  58. package/dist/components/questions/_shared/hooks/useImageQuestionGroup.js +1 -123
  59. package/dist/components/questions/_shared/hooks/useInfoExchangeGroup.js +1 -50
  60. package/dist/components/questions/_shared/hooks/useQuestionFormState.js +1 -54
  61. package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +1 -123
  62. package/dist/components/questions/_shared/hooks/useQuestionListGroup.js +1 -121
  63. package/dist/components/questions/_shared/hooks/useQuestionOnChange.js +1 -70
  64. package/dist/components/questions/_shared/hooks/useSceneQuestionGroup.js +1 -194
  65. package/dist/components/questions/_shared/index.js +1 -7
  66. package/dist/components/questions/_shared/types/answer-the-question.type.js +1 -1
  67. package/dist/components/questions/_shared/types/arrange-letters-into-words.type.js +1 -1
  68. package/dist/components/questions/_shared/types/choose-correct-adjective.type.js +1 -3
  69. package/dist/components/questions/_shared/types/color-region.config.js +1 -1
  70. package/dist/components/questions/_shared/types/fill-in-blank.type.js +1 -1
  71. package/dist/components/questions/_shared/types/fill-missing-words-in-grid.type.js +1 -1
  72. package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +1 -1
  73. package/dist/components/questions/_shared/types/index.js +1 -12
  74. package/dist/components/questions/_shared/types/label-the-picture.type.js +1 -1
  75. package/dist/components/questions/_shared/types/listen-and-choose-from-answer-group.type.js +1 -1
  76. package/dist/components/questions/_shared/types/listen-and-choose-objects-in-scene.type.js +1 -1
  77. package/dist/components/questions/_shared/types/listen-and-drag-objects-into-scene.type.js +1 -1
  78. package/dist/components/questions/_shared/types/listen-and-speak-answer.type.js +1 -1
  79. package/dist/components/questions/_shared/types/listen-and-speak-compare-images.type.js +1 -1
  80. package/dist/components/questions/_shared/types/listen-and-speak-image-group.type.js +1 -1
  81. package/dist/components/questions/_shared/types/listen-and-speak-info-exchange.type.js +1 -1
  82. package/dist/components/questions/_shared/types/listen-and-speak-odd-one-out.type.js +1 -3
  83. package/dist/components/questions/_shared/types/listen-and-speak-question-list.type.js +1 -1
  84. package/dist/components/questions/_shared/types/listen-and-speak-with-story-images.type.js +1 -3
  85. package/dist/components/questions/_shared/types/listen-and-tick-answer.type.js +1 -1
  86. package/dist/components/questions/_shared/types/listening-drawline.type.js +1 -1
  87. package/dist/components/questions/_shared/types/look-and-choose-answer.type.js +1 -1
  88. package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +1 -1
  89. package/dist/components/questions/_shared/types/match-by-writing-answer.type.js +1 -1
  90. package/dist/components/questions/_shared/types/multiple-choice.type.js +1 -1
  91. package/dist/components/questions/_shared/types/question-group.type.js +1 -11
  92. package/dist/components/questions/_shared/types/read-and-color-objects.type.js +1 -1
  93. package/dist/components/questions/_shared/types/read-displayed-content.type.js +1 -1
  94. package/dist/components/questions/_shared/types/read-passage-and-answer-questions.type.js +1 -1
  95. package/dist/components/questions/_shared/types/speaking-describe-image.type.js +1 -1
  96. package/dist/components/questions/_shared/types/spontaneous-qa.type.js +1 -1
  97. package/dist/components/questions/_shared/types/word-fill-paragraph-with-options.type.js +1 -1
  98. package/dist/components/questions/_shared/types/word-fill-paragraph.type.js +1 -1
  99. package/dist/components/questions/_shared/types/word-fill-structured-form.type.js +1 -1
  100. package/dist/components/questions/_shared/types/word-ordering.type.js +1 -1
  101. package/dist/components/questions/_shared/types/write-a-short-letter.type.js +1 -1
  102. package/dist/components/questions/_shared/types/write-correct-verb-form.type.js +1 -3
  103. package/dist/components/questions/_shared/types/write-sentences.type.js +1 -1
  104. package/dist/components/questions/_shared/types/write-short-paragraph.type.js +1 -1
  105. package/dist/components/questions/_shared/utils/answer-builder.js +1 -79
  106. package/dist/components/questions/_shared/utils/createColorRegionRegistration.js +1 -39
  107. package/dist/components/questions/_shared/utils/id-generator.js +1 -3
  108. package/dist/components/questions/_shared/utils/question-validation.js +1 -21
  109. package/dist/components/questions/components/ColorNode.js +1 -85
  110. package/dist/components/questions/components/ColorRegionClient.js +1 -545
  111. package/dist/components/questions/components/ColorRegionCreator.js +1 -420
  112. package/dist/components/questions/components/ColorRegionNode.js +1 -59
  113. package/dist/components/questions/components/CreatorGuide.js +1 -103
  114. package/dist/components/questions/components/NameNode.js +1 -26
  115. package/dist/components/questions/components/QuestionBankOption.js +1 -46
  116. package/dist/components/questions/components/QuestionBasicInfoForm.js +1 -9
  117. package/dist/components/questions/components/QuestionCommonFields.js +1 -33
  118. package/dist/components/questions/components/QuestionSearchDropdown.js +1 -258
  119. package/dist/components/questions/components/QuestionTypeSelector.js +1 -32
  120. package/dist/components/questions/components/QuestionTypeSpecificForms.js +1 -6
  121. package/dist/components/questions/components/RegionNode.js +1 -94
  122. package/dist/components/questions/components/index.js +1 -14
  123. package/dist/components/questions/creator/QuestionCreator.js +1 -90
  124. package/dist/components/questions/creator/QuestionGroupCreator.js +1 -119
  125. package/dist/components/questions/creator/dedicated-component-router.js +1 -79
  126. package/dist/components/questions/creator/index.js +1 -3
  127. package/dist/components/questions/creator/question-type-registry.js +1 -80
  128. package/dist/components/questions/groups/ArticlesGroupCard.js +1 -121
  129. package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -50
  130. package/dist/components/questions/groups/DocumentGroupCard.js +1 -114
  131. package/dist/components/questions/groups/MatchAnswerGroupCard.js +1 -25
  132. package/dist/components/questions/index.js +1 -10
  133. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +1 -37
  134. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +1 -351
  135. package/dist/components/questions/types/answer-the-question/register.js +1 -67
  136. package/dist/components/questions/types/answer-the-question/transform.js +1 -36
  137. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +1 -141
  138. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +1 -118
  139. package/dist/components/questions/types/arrange-letters-into-words/index.js +1 -2
  140. package/dist/components/questions/types/arrange-letters-into-words/register.js +1 -4
  141. package/dist/components/questions/types/arrange-letters-into-words/transform.js +1 -24
  142. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -64
  143. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +1 -169
  144. package/dist/components/questions/types/choose-correct-adjective/index.js +1 -2
  145. package/dist/components/questions/types/choose-correct-adjective/register.js +1 -55
  146. package/dist/components/questions/types/choose-correct-adjective/transform.js +1 -16
  147. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +1 -131
  148. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +1 -581
  149. package/dist/components/questions/types/choose-the-correct-answer/index.js +1 -2
  150. package/dist/components/questions/types/choose-the-correct-answer/register.js +1 -101
  151. package/dist/components/questions/types/choose-the-correct-answer/transform.js +1 -43
  152. package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +1 -275
  153. package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +1 -448
  154. package/dist/components/questions/types/fill-in-blank/index.js +1 -2
  155. package/dist/components/questions/types/fill-in-blank/register.js +1 -138
  156. package/dist/components/questions/types/fill-in-blank/transform.js +1 -59
  157. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +1 -117
  158. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +1 -671
  159. package/dist/components/questions/types/fill-missing-words-in-grid/index.js +1 -2
  160. package/dist/components/questions/types/fill-missing-words-in-grid/register.js +1 -37
  161. package/dist/components/questions/types/fill-missing-words-in-grid/transform.js +1 -15
  162. package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +1 -262
  163. package/dist/components/questions/types/gn-speaking-interview/register.js +1 -121
  164. package/dist/components/questions/types/gn-speaking-interview/transform.js +1 -48
  165. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingClient.js +1 -16
  166. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingCreator.js +1 -27
  167. package/dist/components/questions/types/image-object-matching/index.js +1 -2
  168. package/dist/components/questions/types/image-object-matching/register.js +1 -3
  169. package/dist/components/questions/types/image-object-matching/transform.js +1 -2
  170. package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +1 -102
  171. package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +1 -344
  172. package/dist/components/questions/types/label-the-picture/index.js +1 -2
  173. package/dist/components/questions/types/label-the-picture/register.js +1 -54
  174. package/dist/components/questions/types/label-the-picture/transform.js +1 -28
  175. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +1 -78
  176. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +1 -232
  177. package/dist/components/questions/types/listen-and-choose-from-answer-group/index.js +1 -2
  178. package/dist/components/questions/types/listen-and-choose-from-answer-group/register.js +1 -82
  179. package/dist/components/questions/types/listen-and-choose-from-answer-group/transform.js +1 -27
  180. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +1 -70
  181. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +1 -386
  182. package/dist/components/questions/types/listen-and-choose-objects-in-scene/index.js +1 -2
  183. package/dist/components/questions/types/listen-and-choose-objects-in-scene/register.js +1 -100
  184. package/dist/components/questions/types/listen-and-choose-objects-in-scene/transform.js +1 -33
  185. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +1 -106
  186. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +1 -457
  187. package/dist/components/questions/types/listen-and-drag-objects-into-scene/index.js +1 -2
  188. package/dist/components/questions/types/listen-and-drag-objects-into-scene/register.js +1 -110
  189. package/dist/components/questions/types/listen-and-drag-objects-into-scene/transform.js +1 -35
  190. package/dist/components/questions/types/listen-and-speak-answer/ArrowIndicator.js +1 -8
  191. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -22
  192. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +1 -291
  193. package/dist/components/questions/types/listen-and-speak-answer/index.js +1 -2
  194. package/dist/components/questions/types/listen-and-speak-answer/register.js +1 -84
  195. package/dist/components/questions/types/listen-and-speak-answer/transform.js +1 -27
  196. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -32
  197. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +1 -295
  198. package/dist/components/questions/types/listen-and-speak-compare-images/register.js +1 -79
  199. package/dist/components/questions/types/listen-and-speak-compare-images/transform.js +1 -27
  200. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -34
  201. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +1 -285
  202. package/dist/components/questions/types/listen-and-speak-image-group/register.js +1 -81
  203. package/dist/components/questions/types/listen-and-speak-image-group/transform.js +1 -26
  204. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -38
  205. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +1 -385
  206. package/dist/components/questions/types/listen-and-speak-info-exchange/register.js +1 -87
  207. package/dist/components/questions/types/listen-and-speak-info-exchange/transform.js +1 -32
  208. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -26
  209. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +1 -247
  210. package/dist/components/questions/types/listen-and-speak-odd-one-out/register.js +1 -59
  211. package/dist/components/questions/types/listen-and-speak-odd-one-out/transform.js +1 -18
  212. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -26
  213. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +1 -256
  214. package/dist/components/questions/types/listen-and-speak-question-list/register.js +1 -81
  215. package/dist/components/questions/types/listen-and-speak-question-list/transform.js +1 -26
  216. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -26
  217. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +1 -230
  218. package/dist/components/questions/types/listen-and-speak-with-story-images/register.js +1 -56
  219. package/dist/components/questions/types/listen-and-speak-with-story-images/transform.js +1 -16
  220. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +1 -72
  221. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +1 -230
  222. package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +1 -1
  223. package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +1 -66
  224. package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +1 -42
  225. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +1 -96
  226. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +1 -238
  227. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +1 -2
  228. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +1 -76
  229. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +1 -19
  230. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +1 -134
  231. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +1 -617
  232. package/dist/components/questions/types/look-picture-fill-word-hint/index.js +1 -2
  233. package/dist/components/questions/types/look-picture-fill-word-hint/register.js +1 -82
  234. package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +1 -29
  235. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +1 -38
  236. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +1 -359
  237. package/dist/components/questions/types/match-by-writing-answer/index.js +1 -3
  238. package/dist/components/questions/types/match-by-writing-answer/register.js +1 -58
  239. package/dist/components/questions/types/match-by-writing-answer/transform.js +1 -61
  240. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsClient.js +1 -16
  241. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsCreator.js +1 -23
  242. package/dist/components/questions/types/read-and-color-objects/index.js +1 -2
  243. package/dist/components/questions/types/read-and-color-objects/register.js +1 -3
  244. package/dist/components/questions/types/read-and-color-objects/transform.js +1 -36
  245. package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +1 -125
  246. package/dist/components/questions/types/read-displayed-content/register.js +1 -64
  247. package/dist/components/questions/types/read-displayed-content/transform.js +1 -12
  248. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -64
  249. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +1 -511
  250. package/dist/components/questions/types/read-passage-and-answer-questions/index.js +1 -2
  251. package/dist/components/questions/types/read-passage-and-answer-questions/register.js +1 -58
  252. package/dist/components/questions/types/read-passage-and-answer-questions/transform.js +1 -28
  253. package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +1 -373
  254. package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +1 -41
  255. package/dist/components/questions/types/speaking-conversation/register.js +1 -110
  256. package/dist/components/questions/types/speaking-conversation/transform.js +1 -34
  257. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +1 -74
  258. package/dist/components/questions/types/speaking-cue-card/register.js +1 -52
  259. package/dist/components/questions/types/speaking-cue-card/transform.js +1 -12
  260. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -10
  261. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +1 -161
  262. package/dist/components/questions/types/speaking-describe-image/index.js +1 -2
  263. package/dist/components/questions/types/speaking-describe-image/register.js +1 -69
  264. package/dist/components/questions/types/speaking-describe-image/transform.js +1 -16
  265. package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +1 -234
  266. package/dist/components/questions/types/spontaneous-qa/register.js +1 -93
  267. package/dist/components/questions/types/spontaneous-qa/transform.js +1 -34
  268. package/dist/components/questions/types/true-false/TrueFalseClient.js +1 -75
  269. package/dist/components/questions/types/true-false/TrueFalseCreator.js +1 -244
  270. package/dist/components/questions/types/true-false/index.js +1 -2
  271. package/dist/components/questions/types/true-false/register.js +1 -77
  272. package/dist/components/questions/types/true-false/transform.js +1 -32
  273. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +1 -233
  274. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +1 -631
  275. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphWrapper.js +1 -28
  276. package/dist/components/questions/types/word-fill-paragraph/index.js +1 -2
  277. package/dist/components/questions/types/word-fill-paragraph/register.js +1 -136
  278. package/dist/components/questions/types/word-fill-paragraph/transform.js +1 -56
  279. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -138
  280. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +1 -261
  281. package/dist/components/questions/types/word-fill-structured-form/index.js +1 -2
  282. package/dist/components/questions/types/word-fill-structured-form/register.js +1 -67
  283. package/dist/components/questions/types/word-fill-structured-form/transform.js +1 -40
  284. package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -84
  285. package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +1 -192
  286. package/dist/components/questions/types/word-ordering/index.js +1 -2
  287. package/dist/components/questions/types/word-ordering/register.js +1 -46
  288. package/dist/components/questions/types/word-ordering/transform.js +1 -24
  289. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +1 -98
  290. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +1 -320
  291. package/dist/components/questions/types/write-a-short-letter/index.js +1 -2
  292. package/dist/components/questions/types/write-a-short-letter/register.js +1 -68
  293. package/dist/components/questions/types/write-a-short-letter/transform.js +1 -25
  294. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +1 -61
  295. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +1 -196
  296. package/dist/components/questions/types/write-correct-verb-form/index.js +1 -2
  297. package/dist/components/questions/types/write-correct-verb-form/register.js +1 -62
  298. package/dist/components/questions/types/write-correct-verb-form/transform.js +1 -17
  299. package/dist/components/questions/types/write-sentences/WriteSentencesClient.js +1 -22
  300. package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +1 -174
  301. package/dist/components/questions/types/write-sentences/index.js +1 -2
  302. package/dist/components/questions/types/write-sentences/register.js +1 -58
  303. package/dist/components/questions/types/write-sentences/transform.js +1 -28
  304. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -96
  305. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +1 -161
  306. package/dist/components/questions/types/write-short-paragraph/index.js +1 -2
  307. package/dist/components/questions/types/write-short-paragraph/register.js +1 -4
  308. package/dist/components/questions/types/write-short-paragraph/transform.js +1 -21
  309. package/dist/components/questions/viewer/QuestionGroupViewer.js +1 -36
  310. package/dist/components/questions/viewer/QuestionViewer.js +1 -446
  311. package/dist/components/questions/viewer/index.js +1 -2
  312. package/dist/components/results/ResultReviewPageContainer.js +1 -98
  313. package/dist/components/results/ReviewQuestionRenderer.js +1 -72
  314. package/dist/components/results/index.js +1 -2
  315. package/dist/components/results/result-review.utils.js +1 -6
  316. package/dist/components/results/review-data.js +1 -150
  317. package/dist/components/results/review-renderer.utils.js +1 -97
  318. package/dist/components/results/review-types.js +1 -1
  319. package/dist/components/themes/cambridge-yle/CambridgeYleAudioPlayer.js +1 -138
  320. package/dist/components/themes/cambridge-yle/CambridgeYleBottomNav.js +1 -12
  321. package/dist/components/themes/cambridge-yle/CambridgeYleExamLayout.js +1 -18
  322. package/dist/components/themes/cambridge-yle/CambridgeYleExampleText.js +1 -11
  323. package/dist/components/themes/cambridge-yle/CambridgeYleHeader.js +1 -11
  324. package/dist/components/themes/cambridge-yle/CambridgeYleInstructionBanner.js +1 -9
  325. package/dist/components/themes/cambridge-yle/CambridgeYleNavButton.js +1 -13
  326. package/dist/components/themes/cambridge-yle/CambridgeYlePartBanner.js +1 -8
  327. package/dist/components/themes/cambridge-yle/CambridgeYleProgressBar.js +1 -6
  328. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCard.js +1 -5
  329. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCardFullWidth.js +1 -9
  330. package/dist/components/themes/cambridge-yle/CambridgeYleSidebar.js +1 -41
  331. package/dist/components/themes/cambridge-yle/CambridgeYleTimer.js +1 -25
  332. package/dist/components/themes/cambridge-yle/index.js +1 -13
  333. package/dist/components/themes/english-certification/EcAnswerTheQuestion.js +1 -24
  334. package/dist/components/themes/english-certification/EcMatchByWritingAnswer.js +1 -88
  335. package/dist/components/themes/english-certification/EcWordFillParagraph.js +1 -55
  336. package/dist/components/themes/english-certification/EcWordFillStructuredForm.js +1 -156
  337. package/dist/components/themes/english-certification/EcWritingTask.js +1 -34
  338. package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.js +1 -114
  339. package/dist/components/themes/english-certification/EnglishCertificationBrand.js +1 -11
  340. package/dist/components/themes/english-certification/EnglishCertificationExamLayout.js +1 -21
  341. package/dist/components/themes/english-certification/EnglishCertificationFlagButton.js +1 -15
  342. package/dist/components/themes/english-certification/EnglishCertificationFooterBar.js +1 -19
  343. package/dist/components/themes/english-certification/EnglishCertificationGroupedQuestion.js +1 -148
  344. package/dist/components/themes/english-certification/EnglishCertificationHeader.js +1 -31
  345. package/dist/components/themes/english-certification/EnglishCertificationNavigationPanel.js +1 -71
  346. package/dist/components/themes/english-certification/EnglishCertificationOptionSelector.js +1 -33
  347. package/dist/components/themes/english-certification/EnglishCertificationPassagePanel.js +1 -16
  348. package/dist/components/themes/english-certification/EnglishCertificationQuestionCard.js +1 -22
  349. package/dist/components/themes/english-certification/EnglishCertificationQuestionItem.js +1 -37
  350. package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.js +1 -72
  351. package/dist/components/themes/english-certification/EnglishCertificationReadingSection.js +1 -90
  352. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestion.js +1 -18
  353. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestionList.js +1 -29
  354. package/dist/components/themes/english-certification/english-certification-theme.js +1 -58
  355. package/dist/components/themes/english-certification/grouped-question-utils.js +1 -218
  356. package/dist/components/themes/english-certification/index.js +1 -21
  357. package/dist/components/themes/index.js +1 -6
  358. package/dist/components/themes/summer-sky/SummerSkyAudioPlayer.js +1 -170
  359. package/dist/components/themes/summer-sky/SummerSkyLayout.js +1 -221
  360. package/dist/components/themes/summer-sky/SummerSkySubmitModal.js +1 -18
  361. package/dist/components/themes/summer-sky/SummerSkyWaitingRoom.js +1 -194
  362. package/dist/components/themes/types.js +1 -1
  363. package/dist/components/ui/alert-dialog.js +1 -26
  364. package/dist/components/ui/alert.js +1 -22
  365. package/dist/components/ui/avatar.js +1 -12
  366. package/dist/components/ui/badge.js +1 -38
  367. package/dist/components/ui/button.js +1 -35
  368. package/dist/components/ui/calendar.js +1 -76
  369. package/dist/components/ui/card.js +1 -16
  370. package/dist/components/ui/carousel.js +1 -91
  371. package/dist/components/ui/checkbox.js +1 -9
  372. package/dist/components/ui/collapsible.js +1 -13
  373. package/dist/components/ui/command.js +1 -34
  374. package/dist/components/ui/confirm-dialog.js +1 -49
  375. package/dist/components/ui/dialog.js +1 -23
  376. package/dist/components/ui/drawer.js +1 -35
  377. package/dist/components/ui/dropdown-menu.js +1 -33
  378. package/dist/components/ui/file-upload.js +1 -155
  379. package/dist/components/ui/form.js +1 -62
  380. package/dist/components/ui/hover-card.js +1 -10
  381. package/dist/components/ui/image-preview.js +1 -29
  382. package/dist/components/ui/input.js +1 -8
  383. package/dist/components/ui/label.js +1 -9
  384. package/dist/components/ui/language-switcher.js +1 -15
  385. package/dist/components/ui/multi-select.js +1 -90
  386. package/dist/components/ui/points-input.js +1 -33
  387. package/dist/components/ui/popover.js +1 -26
  388. package/dist/components/ui/progress.js +1 -16
  389. package/dist/components/ui/radio-group.js +1 -12
  390. package/dist/components/ui/scroll-area.js +1 -11
  391. package/dist/components/ui/select.js +1 -38
  392. package/dist/components/ui/separator.js +1 -8
  393. package/dist/components/ui/sheet.js +1 -40
  394. package/dist/components/ui/single-select.js +1 -76
  395. package/dist/components/ui/skeleton.js +1 -6
  396. package/dist/components/ui/switch.js +1 -8
  397. package/dist/components/ui/table.js +1 -20
  398. package/dist/components/ui/tabs.js +1 -17
  399. package/dist/components/ui/textarea.js +1 -8
  400. package/dist/components/ui/tooltip.js +1 -11
  401. package/dist/index.d.ts +3 -3
  402. package/dist/index.js +1 -19
  403. package/dist/mcp/config.js +1 -32
  404. package/dist/mcp/http-client.js +1 -48
  405. package/dist/mcp/index.js +1 -29
  406. package/dist/mcp/tools/_helpers.js +1 -25
  407. package/dist/mcp/tools/exams.js +1 -439
  408. package/dist/mcp/tools/index.js +1 -6
  409. package/dist/mcp/tools/results.js +1 -19
  410. package/dist/shared/constants/ApiConstant.js +1 -276
  411. package/dist/shared/constants/QueryKeyConstant.js +1 -112
  412. package/dist/shared/constants/exam-level.enum.js +1 -41
  413. package/dist/shared/constants/landings/placement-test.constants.js +1 -431
  414. package/dist/shared/constants/question-skills.js +1 -267
  415. package/dist/shared/constants/routes.js +1 -81
  416. package/dist/shared/index.d.ts +3 -0
  417. package/dist/shared/index.js +1 -7
  418. package/dist/shared/lib/hooks/index.js +1 -10
  419. package/dist/shared/lib/hooks/useAlertDialog.js +1 -57
  420. package/dist/shared/lib/hooks/useAudioPlayCounter.js +1 -30
  421. package/dist/shared/lib/hooks/useAudioPreview.js +1 -81
  422. package/dist/shared/lib/hooks/useAudioUpload.js +1 -24
  423. package/dist/shared/lib/hooks/useConfirmDialog.js +1 -89
  424. package/dist/shared/lib/hooks/useDebouncedCallback.js +1 -32
  425. package/dist/shared/lib/hooks/useDelayedAutoPlay.js +1 -12
  426. package/dist/shared/lib/hooks/useFileUpload.js +1 -24
  427. package/dist/shared/lib/hooks/useImageUrl.js +1 -5
  428. package/dist/shared/lib/hooks/usePresignedFileUrl.js +1 -10
  429. package/dist/shared/lib/hooks/useReactHookForm.js +1 -31
  430. package/dist/shared/lib/hooks/useToast.js +1 -62
  431. package/dist/shared/lib/i18n/index.js +1 -54
  432. package/dist/shared/lib/i18n/messages/en/admin.js +1 -2104
  433. package/dist/shared/lib/i18n/messages/en/auth.js +1 -86
  434. package/dist/shared/lib/i18n/messages/en/common.js +1 -140
  435. package/dist/shared/lib/i18n/messages/en/file-upload.js +1 -18
  436. package/dist/shared/lib/i18n/messages/en/student.js +1 -133
  437. package/dist/shared/lib/i18n/messages/en/terms.js +1 -37
  438. package/dist/shared/lib/i18n/messages/en.js +1 -15
  439. package/dist/shared/lib/i18n/messages/vi/admin.js +1 -2104
  440. package/dist/shared/lib/i18n/messages/vi/auth.js +1 -86
  441. package/dist/shared/lib/i18n/messages/vi/common.js +1 -140
  442. package/dist/shared/lib/i18n/messages/vi/file-upload.js +1 -18
  443. package/dist/shared/lib/i18n/messages/vi/student.js +1 -133
  444. package/dist/shared/lib/i18n/messages/vi/terms.js +1 -37
  445. package/dist/shared/lib/i18n/messages/vi.js +1 -15
  446. package/dist/shared/lib/i18n/types.js +1 -1
  447. package/dist/shared/lib/stores/examQuestionStore.js +1 -212
  448. package/dist/shared/lib/stores/localeStore.js +1 -10
  449. package/dist/shared/lib/utils/public-page.js +1 -44
  450. package/dist/shared/lib/utils/question-reverse-transform.d.ts +6 -0
  451. package/dist/shared/lib/utils/question-reverse-transform.js +1 -0
  452. package/dist/shared/lib/utils/question-transform-types.js +1 -1
  453. package/dist/shared/lib/utils/question-transform-validation.d.ts +6 -0
  454. package/dist/shared/lib/utils/question-transform-validation.js +1 -0
  455. package/dist/shared/lib/utils/question-transform.d.ts +6 -2
  456. package/dist/shared/lib/utils/question-transform.js +1 -174
  457. package/dist/shared/lib/utils.js +1 -168
  458. package/dist/shared/lib/validation/choose-correct-answer.validation.js +1 -111
  459. package/dist/shared/lib/validation/label-the-picture.validation.js +1 -19
  460. package/dist/shared/lib/validation/listen-and-choose-from-answer-group.validation.js +1 -46
  461. package/dist/shared/lib/validation/listen-and-choose-objects-in-scene.validation.js +1 -21
  462. package/dist/shared/lib/validation/listen-and-drag-objects-into-scene.validation.js +1 -34
  463. package/dist/shared/lib/validation/listen-and-speak-answer.validation.js +1 -40
  464. package/dist/shared/lib/validation/listen-and-speak-compare-images.validation.js +1 -45
  465. package/dist/shared/lib/validation/listen-and-speak-image-group.validation.js +1 -19
  466. package/dist/shared/lib/validation/listen-and-speak-info-exchange.validation.js +1 -39
  467. package/dist/shared/lib/validation/listen-and-speak-odd-one-out.validation.js +1 -32
  468. package/dist/shared/lib/validation/listen-and-speak-question-list.validation.js +1 -19
  469. package/dist/shared/lib/validation/listen-and-speak-with-story-images.validation.js +1 -28
  470. package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +1 -12
  471. package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +1 -47
  472. package/dist/shared/lib/validation/read-and-color-objects.validation.js +1 -29
  473. package/dist/shared/lib/validation/read-displayed-content.validation.js +1 -28
  474. package/dist/shared/lib/validation/speaking-conversation.validation.js +1 -75
  475. package/dist/shared/lib/validation/speaking-describe-image.validation.js +1 -31
  476. package/dist/shared/lib/validation/spontaneous-qa.validation.js +1 -48
  477. package/dist/shared/lib/validation/word-fill-paragraph-default.validation.js +1 -38
  478. package/dist/shared/lib/validation/word-fill-paragraph-with-options.validation.js +1 -39
  479. package/dist/shared/lib/validation/word-fill-paragraph.validation.js +1 -43
  480. package/dist/shared/lib/validation/word-ordering.validation.js +1 -33
  481. package/dist/shared/types/branch.types.js +1 -6
  482. package/dist/shared/types/common.types.js +1 -50
  483. package/dist/shared/types/entities/exam-schedule.types.js +1 -5
  484. package/dist/shared/types/entities/exam.types.js +1 -1
  485. package/dist/shared/types/entities/question.types.js +1 -4
  486. package/dist/shared/types/entities/result.types.js +1 -4
  487. package/dist/shared/types/entities/user.types.js +1 -4
  488. package/dist/shared/types/exam-taking.types.js +1 -2
  489. package/dist/shared/types/history.types.js +1 -34
  490. package/dist/shared/types/index.js +1 -1
  491. package/dist/shared/types/questions/choose-correct-adjective.js +1 -4
  492. package/dist/shared/types/questions/choose-the-correct-answer.js +1 -9
  493. package/dist/shared/types/questions/fill-missing-words-in-grid.js +1 -11
  494. package/dist/shared/types/questions/index.js +1 -40
  495. package/dist/shared/types/questions/listen-and-speak-answer.js +1 -1
  496. package/dist/shared/types/questions/listen-and-speak-compare-images.js +1 -1
  497. package/dist/shared/types/questions/listen-and-speak-image-group.js +1 -1
  498. package/dist/shared/types/questions/listen-and-speak-info-exchange.js +1 -3
  499. package/dist/shared/types/questions/listen-and-speak-odd-one-out.js +1 -3
  500. package/dist/shared/types/questions/listen-and-speak-question-list.js +1 -1
  501. package/dist/shared/types/questions/listen-and-speak-with-story-images.js +1 -3
  502. package/dist/shared/types/questions/look-picture-choose-correct-answer.js +1 -5
  503. package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +1 -9
  504. package/dist/shared/types/questions/look-picture-fill-word-hint.js +1 -7
  505. package/dist/shared/types/questions/read-displayed-content.js +1 -1
  506. package/dist/shared/types/questions/speaking-conversation.js +1 -7
  507. package/dist/shared/types/questions/speaking-describe-image.js +1 -1
  508. package/dist/shared/types/questions/spontaneous-qa.js +1 -1
  509. package/dist/shared/types/questions/write-correct-verb-form.js +1 -12
  510. package/dist/shared/types/role.types.js +1 -1
  511. package/dist/shared/types/structure-management.js +1 -28
  512. package/dist/shared/types/student.types.js +1 -54
  513. package/dist/shared/types/teacher-dashboard-rooms.js +1 -2
  514. package/package.json +12 -3
@@ -1,261 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useEffect, useRef, useMemo, useCallback } from 'react';
4
- import { useEditor, EditorContent } from '@tiptap/react';
5
- import StarterKit from '@tiptap/starter-kit';
6
- import TextAlign from '@tiptap/extension-text-align';
7
- import { Table, TableRow, TableCell, TableHeader } from '@tiptap/extension-table';
8
- import { List, ListOrdered, Bold, Italic, AlignLeft, AlignCenter, AlignRight, TableIcon, Plus, Minus, Trash2, BookOpen, Eye, Pencil, CheckCircle2, PenLine, Info, MessageSquare, ImageIcon, } from 'lucide-react';
9
- import { Button } from '../../../../components/ui/button';
10
- import { Label } from '../../../../components/ui/label';
11
- import { Input } from '../../../../components/ui/input';
12
- import { Textarea } from '../../../../components/ui/textarea';
13
- import { Switch } from '../../../../components/ui/switch';
14
- import { PointsInput } from '../../../../components/ui/points-input';
15
- import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
16
- import { FileUpload } from '../../../../components/ui/file-upload';
17
- import { ImagePreview } from '../../../../components/ui/image-preview';
18
- import { WordFillStructuredFormClient } from './WordFillStructuredFormClient';
19
- import { useDebouncedCallback, usePresignedFileUrl } from '../../../../shared/lib/hooks';
20
- import { BasicQuestionGroupCard } from '../../groups/BasicQuestionGroupCard';
21
- import { DocumentGroupCard } from '../../groups/DocumentGroupCard';
22
- import { useBasicQuestionGroup, getGroupNumberForIndex } from '../../_shared/hooks/useBasicQuestionGroup';
23
- // ─────────────────────────────────────────────────────────────────────────────
24
- // TipTap Toolbar
25
- // ─────────────────────────────────────────────────────────────────────────────
26
- const ToolbarBtn = ({ onClick, active, disabled, title, children, className = '', }) => (_jsx("button", { type: "button", onClick: onClick, disabled: disabled, title: title, className: `inline-flex h-7 min-w-7 items-center justify-center rounded px-1.5 text-sm transition-colors disabled:opacity-30
27
- ${active
28
- ? 'bg-teal-100 text-teal-700'
29
- : 'text-gray-500 hover:bg-gray-100 hover:text-gray-800'}
30
- ${className}`, children: children }));
31
- const ToolbarSep = () => _jsx("div", { className: "mx-1 h-4 w-px bg-gray-200" });
32
- const EditorToolbar = ({ editor }) => {
33
- if (!editor)
34
- return null;
35
- return (_jsxs("div", { className: "flex flex-wrap items-center gap-0.5 border-b border-gray-100 bg-gray-50/70 px-2 py-1.5 rounded-t-lg", children: [_jsx(ToolbarBtn, { onClick: () => editor.chain().focus().toggleBold().run(), active: editor.isActive('bold'), title: "\u0110\u1EADm", children: _jsx(Bold, { className: "h-3.5 w-3.5" }) }), _jsx(ToolbarBtn, { onClick: () => editor.chain().focus().toggleItalic().run(), active: editor.isActive('italic'), title: "Nghi\u00EAng", children: _jsx(Italic, { className: "h-3.5 w-3.5" }) }), _jsx(ToolbarSep, {}), _jsx(ToolbarBtn, { onClick: () => editor.chain().focus().toggleHeading({ level: 2 }).run(), active: editor.isActive('heading', { level: 2 }), className: "px-2 text-xs font-bold", children: "H2" }), _jsx(ToolbarBtn, { onClick: () => editor.chain().focus().toggleHeading({ level: 3 }).run(), active: editor.isActive('heading', { level: 3 }), className: "px-2 text-xs font-bold", children: "H3" }), _jsx(ToolbarBtn, { onClick: () => editor.chain().focus().toggleHeading({ level: 4 }).run(), active: editor.isActive('heading', { level: 4 }), className: "px-2 text-xs font-bold", children: "H4" }), _jsx(ToolbarSep, {}), _jsx(ToolbarBtn, { onClick: () => editor.chain().focus().toggleBulletList().run(), active: editor.isActive('bulletList'), title: "Danh s\u00E1ch", children: _jsx(List, { className: "h-3.5 w-3.5" }) }), _jsx(ToolbarBtn, { onClick: () => editor.chain().focus().toggleOrderedList().run(), active: editor.isActive('orderedList'), title: "Danh s\u00E1ch c\u00F3 s\u1ED1", children: _jsx(ListOrdered, { className: "h-3.5 w-3.5" }) }), _jsx(ToolbarSep, {}), _jsx(ToolbarBtn, { onClick: () => editor.chain().focus().setTextAlign('left').run(), active: editor.isActive({ textAlign: 'left' }), title: "Tr\u00E1i", children: _jsx(AlignLeft, { className: "h-3.5 w-3.5" }) }), _jsx(ToolbarBtn, { onClick: () => editor.chain().focus().setTextAlign('center').run(), active: editor.isActive({ textAlign: 'center' }), title: "Gi\u1EEFa", children: _jsx(AlignCenter, { className: "h-3.5 w-3.5" }) }), _jsx(ToolbarBtn, { onClick: () => editor.chain().focus().setTextAlign('right').run(), active: editor.isActive({ textAlign: 'right' }), title: "Ph\u1EA3i", children: _jsx(AlignRight, { className: "h-3.5 w-3.5" }) }), _jsx(ToolbarSep, {}), _jsxs(ToolbarBtn, { onClick: () => editor.chain().focus().insertTable({ rows: 5, cols: 2, withHeaderRow: false }).run(), title: "Ch\u00E8n b\u1EA3ng 2 c\u1ED9t \u00D7 5 h\u00E0ng", className: "gap-1 px-2 text-xs", children: [_jsx(TableIcon, { className: "h-3.5 w-3.5" }), _jsx("span", { className: "hidden sm:inline", children: "B\u1EA3ng 2 c\u1ED9t" })] }), _jsxs(ToolbarBtn, { onClick: () => editor.chain().focus().addRowAfter().run(), disabled: !editor.can().addRowAfter(), title: "Th\u00EAm h\u00E0ng", className: "gap-1 px-2 text-xs", children: [_jsx(Plus, { className: "h-3 w-3" }), _jsx("span", { className: "hidden sm:inline", children: "H\u00E0ng" })] }), _jsxs(ToolbarBtn, { onClick: () => editor.chain().focus().deleteRow().run(), disabled: !editor.can().deleteRow(), title: "X\u00F3a h\u00E0ng", className: "gap-1 px-2 text-xs", children: [_jsx(Minus, { className: "h-3 w-3" }), _jsx("span", { className: "hidden sm:inline", children: "H\u00E0ng" })] }), _jsxs(ToolbarBtn, { onClick: () => editor.chain().focus().deleteTable().run(), disabled: !editor.can().deleteTable(), title: "X\u00F3a b\u1EA3ng", className: "gap-1 px-2 text-xs text-red-400 hover:text-red-600", children: [_jsx(Trash2, { className: "h-3 w-3" }), _jsx("span", { className: "hidden sm:inline", children: "B\u1EA3ng" })] })] }));
36
- };
37
- // ─────────────────────────────────────────────────────────────────────────────
38
- // Main Creator
39
- // ─────────────────────────────────────────────────────────────────────────────
40
- export function WordFillStructuredFormCreator({ initialData, onSave, onCancel, onChange, onUnsavedChangesChange: _onUnsavedChangesChange, validationRef, externalErrors, groupConfig, groupType, groups, questionIndexInPart = 0, partId = '', }) {
41
- const [isPreviewMode, setIsPreviewMode] = useState(false);
42
- const [points, setPoints] = useState(initialData?.points ?? 1);
43
- const [caseSensitive, setCaseSensitive] = useState(initialData?.caseSensitive ?? false);
44
- const [explanation, setExplanation] = useState(initialData?.explanation || '');
45
- const [imagePreviewUrl, setImagePreviewUrl] = useState('');
46
- // ── GROUP ──────────────────────────────────────────────────────────────────
47
- const { isGrouped, isFirstInGroup, currentGroupNumber: hookGroupNumber, groupTitle, groupSubtitle, groupContent, imageUrl: groupImageUrl, setGroupTitle, setGroupSubtitle, setGroupContent, setImageUrl: setGroupImageUrl, } = useBasicQuestionGroup({
48
- partId,
49
- groups,
50
- questionIndexInPart,
51
- initialData: {
52
- title: initialData?.groupTitle || '',
53
- subtitle: initialData?.groupSubtitle || '',
54
- content: initialData?.groupContent || '',
55
- imageUrl: initialData?.groupImageUrl || '',
56
- },
57
- });
58
- const effectiveGroupType = (groupType || 'BASIC').toUpperCase();
59
- const hasGroupConfig = groupConfig && groupConfig.length > 0;
60
- const showContent = !hasGroupConfig || groupConfig.includes('content');
61
- const currentGroupNumber = hookGroupNumber || getGroupNumberForIndex(questionIndexInPart, groups);
62
- const [localTitle, setLocalTitle] = useState(initialData?.title || '');
63
- const [localImageUrl, setLocalImageUrl] = useState(initialData?.imageUrl || '');
64
- const effectiveTitle = localTitle;
65
- const effectiveImageUrl = localImageUrl;
66
- const setEffectiveTitle = setLocalTitle;
67
- const setEffectiveImageUrl = setLocalImageUrl;
68
- // Resolve presigned URL for image preview
69
- const { previewUrl: generatedPreviewUrl } = usePresignedFileUrl(effectiveImageUrl);
70
- const displayImageUrl = imagePreviewUrl || generatedPreviewUrl || '';
71
- // ── TipTap ─────────────────────────────────────────────────────────────────
72
- const initialContent = initialData?.content || '<p></p>';
73
- const [content, setContent] = useState(initialContent);
74
- const [answers, setAnswers] = useState(initialData?.answers || {});
75
- const previousPayloadRef = useRef('');
76
- const previousInitialDataRef = useRef(initialData);
77
- const isInitializingRef = useRef(false);
78
- const contentRef = useRef(content);
79
- const onChangeRef = useRef(onChange);
80
- useEffect(() => {
81
- onChangeRef.current = onChange;
82
- }, [onChange]);
83
- // Stable TipTap options: @tiptap/react calls `editor.setOptions` whenever the
84
- // passed options object differs from the editor instance. Inline `extensions`
85
- // / `editorProps` are new references every render → spurious `setOptions` →
86
- // document update → `onUpdate` → payload change → question flips to "Chưa lưu"
87
- // when an unrelated parent re-render happens (e.g. part audio upload).
88
- const tiptapExtensions = useMemo(() => [
89
- StarterKit.configure({ heading: { levels: [2, 3, 4] } }),
90
- TextAlign.configure({ types: ['heading', 'paragraph'] }),
91
- Table.configure({ resizable: true }),
92
- TableRow,
93
- TableCell,
94
- TableHeader,
95
- ], []);
96
- const tiptapEditorProps = useMemo(() => ({
97
- attributes: {
98
- class: 'tiptap-editor prose prose-sm max-w-none p-4 focus:outline-none min-h-[400px]',
99
- },
100
- }), []);
101
- const handleEditorUpdate = useCallback(({ editor: ed }) => {
102
- const html = ed.getHTML();
103
- if (html !== contentRef.current) {
104
- contentRef.current = html;
105
- setContent(html);
106
- }
107
- }, []);
108
- const editor = useEditor({
109
- immediatelyRender: false,
110
- extensions: tiptapExtensions,
111
- content,
112
- editorProps: tiptapEditorProps,
113
- onUpdate: handleEditorUpdate,
114
- });
115
- useEffect(() => {
116
- contentRef.current = content;
117
- }, [content]);
118
- // Sync when initialData changes
119
- useEffect(() => {
120
- const changed = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
121
- if (!changed)
122
- return;
123
- previousInitialDataRef.current = initialData;
124
- if (!initialData?.content || initialData.content === '<p></p>')
125
- return;
126
- isInitializingRef.current = true;
127
- setLocalTitle(initialData?.title || '');
128
- setLocalImageUrl(initialData?.imageUrl || '');
129
- setPoints(initialData?.points ?? 1);
130
- setCaseSensitive(initialData?.caseSensitive ?? false);
131
- setExplanation(initialData?.explanation || '');
132
- setAnswers(initialData?.answers || {});
133
- const newContent = initialData.content;
134
- if (newContent !== contentRef.current) {
135
- contentRef.current = newContent;
136
- setContent(newContent);
137
- if (editor && !editor.isDestroyed)
138
- editor.commands.setContent(newContent);
139
- }
140
- setTimeout(() => { isInitializingRef.current = false; }, 150);
141
- }, [initialData, editor]);
142
- const debouncedOnChange = useDebouncedCallback((data) => {
143
- onChangeRef.current?.(data);
144
- }, 300);
145
- const answersSerialized = JSON.stringify(answers);
146
- // Derive expected blank keys from content
147
- const expectedBlankKeys = useMemo(() => {
148
- const keys = new Set();
149
- const regex = /\{([\w\d]+)\}/g;
150
- let match;
151
- while ((match = regex.exec(content)) !== null)
152
- keys.add(match[1]);
153
- return Array.from(keys).sort((a, b) => {
154
- const numA = parseInt(a), numB = parseInt(b);
155
- if (!isNaN(numA) && !isNaN(numB))
156
- return numA - numB;
157
- return a.localeCompare(b);
158
- });
159
- }, [content]);
160
- // Keep answers map in sync with blank keys
161
- useEffect(() => {
162
- setAnswers(prev => {
163
- let changed = false;
164
- const next = { ...prev };
165
- Object.keys(next).forEach(k => { if (!expectedBlankKeys.includes(k)) {
166
- delete next[k];
167
- changed = true;
168
- } });
169
- expectedBlankKeys.forEach(k => { if (!(k in next)) {
170
- next[k] = '';
171
- changed = true;
172
- } });
173
- return changed ? next : prev;
174
- });
175
- }, [expectedBlankKeys]);
176
- // Emit changes (unsaved status is driven only by `onChange` → ExamCreator, like ChooseTheCorrectAnswerCreator)
177
- useEffect(() => {
178
- const payload = {
179
- title: effectiveTitle,
180
- imageUrl: effectiveImageUrl,
181
- content,
182
- answers,
183
- points,
184
- caseSensitive,
185
- explanation,
186
- groupTitle,
187
- groupSubtitle,
188
- groupContent,
189
- groupImageUrl,
190
- groupNumber: currentGroupNumber,
191
- };
192
- if (isInitializingRef.current) {
193
- previousPayloadRef.current = JSON.stringify(payload);
194
- return;
195
- }
196
- const str = JSON.stringify(payload);
197
- if (str !== previousPayloadRef.current) {
198
- previousPayloadRef.current = str;
199
- debouncedOnChange(payload);
200
- }
201
- }, [
202
- effectiveTitle,
203
- effectiveImageUrl,
204
- content,
205
- answersSerialized,
206
- points,
207
- caseSensitive,
208
- explanation,
209
- groupTitle,
210
- groupSubtitle,
211
- groupContent,
212
- groupImageUrl,
213
- currentGroupNumber,
214
- ]);
215
- // Expose validation
216
- useEffect(() => {
217
- if (!validationRef)
218
- return;
219
- validationRef.current = {
220
- triggerValidation: async () => {
221
- if (!content || content === '<p></p>')
222
- return false;
223
- if (expectedBlankKeys.length === 0)
224
- return false;
225
- for (const key of expectedBlankKeys) {
226
- if (!answers[key] || answers[key].trim() === '')
227
- return false;
228
- }
229
- return true;
230
- },
231
- getFormData: () => ({
232
- title: effectiveTitle,
233
- imageUrl: effectiveImageUrl,
234
- content,
235
- answers,
236
- points,
237
- caseSensitive,
238
- explanation,
239
- groupTitle,
240
- groupSubtitle,
241
- groupContent,
242
- groupImageUrl,
243
- groupNumber: currentGroupNumber,
244
- }),
245
- };
246
- }, [validationRef, effectiveTitle, effectiveImageUrl, content, answers, points, caseSensitive, explanation, expectedBlankKeys, groupTitle, groupSubtitle, groupContent, groupImageUrl, currentGroupNumber]);
247
- // ── Preview mode ───────────────────────────────────────────────────────────
248
- if (isPreviewMode) {
249
- return (_jsxs("div", { className: "space-y-4", children: [_jsx(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-teal-100/50", children: _jsx(CardHeader, { className: "px-4 py-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-teal-500 to-cyan-600 shadow-sm", children: _jsx(Eye, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-base font-bold text-gray-900", children: "\u0110i\u1EC1n t\u1EEB \u2013 Xem tr\u01B0\u1EDBc" }), _jsx("p", { className: "text-xs text-gray-500", children: "Ch\u1EBF \u0111\u1ED9 xem tr\u01B0\u1EDBc v\u1EDBi \u0111\u00E1p \u00E1n \u0111\u00FAng \u0111\u00E3 \u0111\u01B0\u1EE3c \u0111i\u1EC1n s\u1EB5n" })] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsPreviewMode(false), className: "gap-2 border-teal-200 text-teal-700 hover:bg-teal-50", children: [_jsx(Pencil, { className: "h-4 w-4" }), "Quay l\u1EA1i Edit"] })] }) }) }), _jsx(WordFillStructuredFormClient, { questionData: { title: effectiveTitle, imageUrl: effectiveImageUrl, content, answers, points, caseSensitive }, isReviewMode: true, userAnswer: answers })] }));
250
- }
251
- // ── Edit mode ──────────────────────────────────────────────────────────────
252
- return (_jsxs("div", { className: "space-y-4", children: [isGrouped && effectiveGroupType === 'BASIC' && (_jsx(BasicQuestionGroupCard, { groupNumber: currentGroupNumber, title: groupTitle, subtitle: groupSubtitle, content: groupContent, imageUrl: groupImageUrl, onTitleChange: setGroupTitle, onSubtitleChange: setGroupSubtitle, onContentChange: setGroupContent, onImageChange: setGroupImageUrl, groupConfig: groupType ? groupConfig : undefined, isReadOnly: !isFirstInGroup, uploadIdPrefix: `word-fill-structured-${partId ?? 'group'}` })), isGrouped && effectiveGroupType === 'DOCUMENT' && (_jsx(DocumentGroupCard, { groupNumber: currentGroupNumber, title: groupTitle, subtitle: groupSubtitle, content: groupContent, imageUrl: groupImageUrl, onTitleChange: setGroupTitle, onSubtitleChange: setGroupSubtitle, onContentChange: setGroupContent, onImageChange: setGroupImageUrl, groupConfig: groupType ? groupConfig : undefined, isReadOnly: !isFirstInGroup, uploadIdPrefix: `word-fill-structured-${partId ?? 'document'}` })), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-teal-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-teal-500 via-cyan-500 to-emerald-500" }), _jsx(CardHeader, { className: "px-4 py-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gray-100", children: _jsx(PenLine, { className: "h-4 w-4 text-teal-600" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-base font-bold text-gray-900", children: "\u0110i\u1EC1n t\u1EEB v\u00E0o bi\u1EC3u m\u1EABu c\u00F3 c\u1EA5u tr\u00FAc" }), _jsxs("p", { className: "text-xs text-gray-500", children: ["Nh\u1EADp n\u1ED9i dung, \u0111\u1EB7t ch\u1ED7 tr\u1ED1ng b\u1EB1ng ", `{0}`, " ", `{1}`, "\u2026 r\u1ED3i \u0111i\u1EC1n \u0111\u00E1p \u00E1n b\u00EAn d\u01B0\u1EDBi"] })] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsPreviewMode(true), disabled: expectedBlankKeys.length === 0 || expectedBlankKeys.some(k => !answers[k]?.trim()), className: "gap-2 border-teal-200 text-teal-600 hover:bg-teal-50 hover:text-teal-700", children: [_jsx(Eye, { className: "h-4 w-4" }), "Xem tr\u01B0\u1EDBc"] })] }) }), _jsxs(CardContent, { className: "space-y-4 px-4 pb-4", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsx(PointsInput, { value: points, onChange: (e) => setPoints(Number(e.target.value) || 1) }), _jsxs("div", { role: "button", tabIndex: 0, onClick: () => setCaseSensitive(!caseSensitive), onKeyDown: (e) => { if (e.key === ' ' || e.key === 'Enter')
253
- setCaseSensitive(!caseSensitive); }, style: { paddingBlock: 'calc(var(--spacing) * 1)' }, className: `inline-flex cursor-pointer items-center gap-2 rounded-md border px-3.5 text-sm font-medium transition-colors select-none ${caseSensitive
254
- ? 'border-teal-300 bg-teal-50 text-teal-700'
255
- : 'border-gray-200 bg-white text-gray-400 hover:text-gray-600'}`, children: [_jsx(Switch, { checked: caseSensitive, onCheckedChange: setCaseSensitive, className: caseSensitive ? 'data-[state=checked]:bg-teal-500 pointer-events-none' : 'pointer-events-none', tabIndex: -1 }), "Ph\u00E2n bi\u1EC7t Hoa / th\u01B0\u1EDDng"] })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { htmlFor: "wfsf-title", className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(MessageSquare, { className: "h-4 w-4 text-teal-500" }), "Ti\u00EAu \u0111\u1EC1", _jsx("span", { className: "text-sm font-normal text-gray-500", children: "(tu\u1EF3 ch\u1ECDn)" })] }), _jsx(Input, { id: "wfsf-title", value: effectiveTitle, onChange: (e) => setEffectiveTitle(e.target.value), placeholder: "Nh\u1EADp ti\u00EAu \u0111\u1EC1 c\u00E2u h\u1ECFi...", className: "border-gray-200 bg-white transition-all focus:border-teal-300 focus:ring-teal-100" })] }), _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-teal-500" }), "H\u00ECnh \u1EA3nh", _jsx("span", { className: "text-sm font-normal text-gray-500", children: "(tu\u1EF3 ch\u1ECDn)" })] }), _jsx(FileUpload, { id: "wfsf-image", label: "", accept: "image/*", value: effectiveImageUrl || '', onChange: (url) => setEffectiveImageUrl(url), onPresignedUrlChange: setImagePreviewUrl, maxSize: 5, placeholder: "Upload \u1EA3nh ho\u1EB7c paste URL", autoUpload: true, prefix: "questions" }), displayImageUrl && (_jsx(ImagePreview, { src: displayImageUrl, alt: "Preview", className: "mt-2 h-48 w-full max-w-md rounded-lg border border-gray-200" }))] }), 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 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, i) => _jsx("li", { children: error }, i)) })] })] }))] })] }), showContent && (_jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-teal-100/50", children: [_jsx(CardHeader, { className: "px-4 py-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-teal-100", children: _jsx(ListOrdered, { className: "h-3.5 w-3.5 text-teal-600" }) }), _jsx(CardTitle, { className: "text-base font-semibold text-gray-800", children: "N\u1ED9i dung c\u1EA5u tr\u00FAc" })] }), expectedBlankKeys.length > 0 && (_jsxs("span", { className: "inline-flex items-center rounded-full border border-teal-200 bg-teal-50 px-2.5 py-0.5 text-xs font-semibold text-teal-700", children: [expectedBlankKeys.length, " ch\u1ED7 tr\u1ED1ng"] }))] }) }), _jsxs(CardContent, { className: "space-y-3 px-4 pb-4", children: [_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 transition-all focus-within:border-teal-300 focus-within:ring-2 focus-within:ring-teal-100", children: [_jsx(EditorToolbar, { editor: editor }), _jsx(EditorContent, { editor: editor })] }), _jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-teal-100 bg-teal-50/60 px-3 py-2.5", children: [_jsx(Info, { className: "mt-0.5 h-4 w-4 shrink-0 text-teal-500" }), _jsxs("p", { className: "text-xs text-teal-700", children: ["Nh\u1EADp ", _jsx("strong", { children: `{0}` }), ", ", _jsx("strong", { children: `{1}` }), ", ", _jsx("strong", { children: `{2}` }), "\u2026 v\u00E0o v\u0103n b\u1EA3n \u0111\u1EC3 t\u1EA1o \u00F4 tr\u1ED1ng. \u0110\u00E1p \u00E1n s\u1EBD t\u1EF1 \u0111\u1ED9ng xu\u1EA5t hi\u1EC7n b\u00EAn d\u01B0\u1EDBi \u0111\u1EC3 b\u1EA1n nh\u1EADp."] })] })] })] })), expectedBlankKeys.length > 0 && (_jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-teal-100/50", children: [_jsx(CardHeader, { className: "px-4 py-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-teal-100", children: _jsx(CheckCircle2, { className: "h-3.5 w-3.5 text-teal-600" }) }), _jsxs(CardTitle, { className: "text-base font-semibold text-gray-800", children: ["\u0110\u00E1p \u00E1n ", _jsxs("span", { className: "text-xs font-normal text-gray-500", children: ["(", expectedBlankKeys.length, " ch\u1ED7 tr\u1ED1ng)"] })] })] }) }), _jsxs(CardContent, { className: "px-4 pb-4", children: [_jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4", children: expectedBlankKeys.map((key) => {
256
- const isEmpty = !answers[key] || answers[key].trim() === '';
257
- return (_jsxs("div", { className: `rounded-xl border-2 p-3 transition-all duration-200 ${isEmpty
258
- ? 'border-gray-200 bg-white'
259
- : 'border-teal-200 bg-gradient-to-br from-teal-50 to-cyan-50 shadow-sm shadow-teal-100'}`, children: [_jsxs(Label, { className: "mb-1.5 flex items-center justify-between text-sm font-semibold text-gray-700", children: [_jsxs("span", { children: ["Ch\u1ED7 tr\u1ED1ng ", _jsx("code", { className: "rounded bg-gray-100 px-1 text-teal-700", children: `{${key}}` })] }), isEmpty && (_jsx("span", { className: "text-[10px] font-normal italic text-red-500", children: "*B\u1EAFt bu\u1ED9c" }))] }), _jsx(Input, { value: answers[key] || '', onChange: e => setAnswers(prev => ({ ...prev, [key]: e.target.value })), placeholder: "\u0110\u00E1p \u00E1n \u0111\u00FAng...", className: `border-gray-200 bg-white transition-all focus:border-teal-300 focus:ring-teal-100 ${!isEmpty ? 'border-teal-200' : ''}` }), !isEmpty && (_jsxs("span", { className: "mt-1.5 inline-flex items-center gap-1 rounded-full bg-teal-100 px-2 py-0.5 text-[10px] font-semibold text-teal-700", children: [_jsx(CheckCircle2, { className: "h-3 w-3" }), "\u0110\u00E3 nh\u1EADp"] }))] }, key));
260
- }) }), expectedBlankKeys.some(k => !answers[k]?.trim()) && (_jsxs("div", { className: "mt-4 flex items-start gap-3 rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4", children: [_jsx("div", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-amber-100", children: _jsx("span", { className: "text-lg", children: "\u26A0\uFE0F" }) }), _jsxs("div", { className: "text-sm text-amber-800", children: [_jsx("p", { className: "font-semibold", children: "C\u00F2n ch\u1ED7 tr\u1ED1ng ch\u01B0a c\u00F3 \u0111\u00E1p \u00E1n" }), _jsxs("p", { className: "mt-0.5 text-amber-700", children: ["Vui l\u00F2ng \u0111i\u1EC1n \u0111\u00E1p \u00E1n \u0111\u00FAng cho t\u1EA5t c\u1EA3 c\u00E1c \u00F4 \u0111\u01B0\u1EE3c \u0111\u00E1nh d\u1EA5u ", _jsx("span", { className: "text-red-500 font-medium", children: "*B\u1EAFt bu\u1ED9c" }), "."] })] })] }))] })] })), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50", children: [_jsx(CardHeader, { className: "px-4 py-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm", children: _jsx(BookOpen, { className: "h-3.5 w-3.5 text-white" }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-base 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 py-0.5 text-xs font-medium text-amber-600", children: "Kh\u00F4ng b\u1EAFt bu\u1ED9c" })] }), _jsx("p", { className: "mt-0.5 text-xs text-gray-500", 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, { className: "px-4 pb-4", children: _jsx(Textarea, { 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] resize-none border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200" }) })] })] }));
261
- }
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useState as n,useEffect as r,useRef as a,useMemo as i,useCallback as l}from"react";import{useEditor as s,EditorContent as c}from"@tiptap/react";import o from"@tiptap/starter-kit";import d from"@tiptap/extension-text-align";import{Table as m,TableRow as h,TableCell as g,TableHeader as u}from"@tiptap/extension-table";import{List as p,ListOrdered as x,Bold as b,Italic as f,AlignLeft as N,AlignCenter as v,AlignRight as w,TableIcon as y,Plus as C,Minus as k,Trash2 as S,BookOpen as A,Eye as I,Pencil as U,CheckCircle2 as T,PenLine as j,Info as G,MessageSquare as H,ImageIcon as B}from"lucide-react";import{Button as D}from"../../../../components/ui/button";import{Label as R}from"../../../../components/ui/label";import{Input as O}from"../../../../components/ui/input";import{Textarea as P}from"../../../../components/ui/textarea";import{Switch as F}from"../../../../components/ui/switch";import{PointsInput as L}from"../../../../components/ui/points-input";import{Card as E,CardContent as z,CardHeader as J,CardTitle as $}from"../../../../components/ui/card";import{FileUpload as q}from"../../../../components/ui/file-upload";import{ImagePreview as X}from"../../../../components/ui/image-preview";import{WordFillStructuredFormClient as M}from"./WordFillStructuredFormClient";import{useDebouncedCallback as Q,usePresignedFileUrl as V}from"../../../../shared/lib/hooks";import{BasicQuestionGroupCard as K}from"../../groups/BasicQuestionGroupCard";import{DocumentGroupCard as W}from"../../groups/DocumentGroupCard";import{useBasicQuestionGroup as _,getGroupNumberForIndex as Y}from"../../_shared/hooks/useBasicQuestionGroup";const Z=({onClick:t,active:n,disabled:r,title:a,children:i,className:l=""})=>e("button",{type:"button",onClick:t,disabled:r,title:a,className:`inline-flex h-7 min-w-7 items-center justify-center rounded px-1.5 text-sm transition-colors disabled:opacity-30\n ${n?"bg-teal-100 text-teal-700":"text-gray-500 hover:bg-gray-100 hover:text-gray-800"}\n ${l}`,children:i}),ee=()=>e("div",{className:"mx-1 h-4 w-px bg-gray-200"}),te=({editor:n})=>n?t("div",{className:"flex flex-wrap items-center gap-0.5 border-b border-gray-100 bg-gray-50/70 px-2 py-1.5 rounded-t-lg",children:[e(Z,{onClick:()=>n.chain().focus().toggleBold().run(),active:n.isActive("bold"),title:"Đậm",children:e(b,{className:"h-3.5 w-3.5"})}),e(Z,{onClick:()=>n.chain().focus().toggleItalic().run(),active:n.isActive("italic"),title:"Nghiêng",children:e(f,{className:"h-3.5 w-3.5"})}),e(ee,{}),e(Z,{onClick:()=>n.chain().focus().toggleHeading({level:2}).run(),active:n.isActive("heading",{level:2}),className:"px-2 text-xs font-bold",children:"H2"}),e(Z,{onClick:()=>n.chain().focus().toggleHeading({level:3}).run(),active:n.isActive("heading",{level:3}),className:"px-2 text-xs font-bold",children:"H3"}),e(Z,{onClick:()=>n.chain().focus().toggleHeading({level:4}).run(),active:n.isActive("heading",{level:4}),className:"px-2 text-xs font-bold",children:"H4"}),e(ee,{}),e(Z,{onClick:()=>n.chain().focus().toggleBulletList().run(),active:n.isActive("bulletList"),title:"Danh sách",children:e(p,{className:"h-3.5 w-3.5"})}),e(Z,{onClick:()=>n.chain().focus().toggleOrderedList().run(),active:n.isActive("orderedList"),title:"Danh sách có số",children:e(x,{className:"h-3.5 w-3.5"})}),e(ee,{}),e(Z,{onClick:()=>n.chain().focus().setTextAlign("left").run(),active:n.isActive({textAlign:"left"}),title:"Trái",children:e(N,{className:"h-3.5 w-3.5"})}),e(Z,{onClick:()=>n.chain().focus().setTextAlign("center").run(),active:n.isActive({textAlign:"center"}),title:"Giữa",children:e(v,{className:"h-3.5 w-3.5"})}),e(Z,{onClick:()=>n.chain().focus().setTextAlign("right").run(),active:n.isActive({textAlign:"right"}),title:"Phải",children:e(w,{className:"h-3.5 w-3.5"})}),e(ee,{}),t(Z,{onClick:()=>n.chain().focus().insertTable({rows:5,cols:2,withHeaderRow:!1}).run(),title:"Chèn bảng 2 cột × 5 hàng",className:"gap-1 px-2 text-xs",children:[e(y,{className:"h-3.5 w-3.5"}),e("span",{className:"hidden sm:inline",children:"Bảng 2 cột"})]}),t(Z,{onClick:()=>n.chain().focus().addRowAfter().run(),disabled:!n.can().addRowAfter(),title:"Thêm hàng",className:"gap-1 px-2 text-xs",children:[e(C,{className:"h-3 w-3"}),e("span",{className:"hidden sm:inline",children:"Hàng"})]}),t(Z,{onClick:()=>n.chain().focus().deleteRow().run(),disabled:!n.can().deleteRow(),title:"Xóa hàng",className:"gap-1 px-2 text-xs",children:[e(k,{className:"h-3 w-3"}),e("span",{className:"hidden sm:inline",children:"Hàng"})]}),t(Z,{onClick:()=>n.chain().focus().deleteTable().run(),disabled:!n.can().deleteTable(),title:"Xóa bảng",className:"gap-1 px-2 text-xs text-red-400 hover:text-red-600",children:[e(S,{className:"h-3 w-3"}),e("span",{className:"hidden sm:inline",children:"Bảng"})]})]}):null;export function WordFillStructuredFormCreator({initialData:p,onSave:b,onCancel:f,onChange:N,onUnsavedChangesChange:v,validationRef:w,externalErrors:y,groupConfig:C,groupType:k,groups:S,questionIndexInPart:Z=0,partId:ee=""}){const[ne,re]=n(!1),[ae,ie]=n(p?.points??1),[le,se]=n(p?.caseSensitive??!1),[ce,oe]=n(p?.explanation||""),[de,me]=n(""),{isGrouped:he,isFirstInGroup:ge,currentGroupNumber:ue,groupTitle:pe,groupSubtitle:xe,groupContent:be,imageUrl:fe,setGroupTitle:Ne,setGroupSubtitle:ve,setGroupContent:we,setImageUrl:ye}=_({partId:ee,groups:S,questionIndexInPart:Z,initialData:{title:p?.groupTitle||"",subtitle:p?.groupSubtitle||"",content:p?.groupContent||"",imageUrl:p?.groupImageUrl||""}}),Ce=(k||"BASIC").toUpperCase(),ke=!(C&&C.length>0)||C.includes("content"),Se=ue||Y(Z,S),[Ae,Ie]=n(p?.title||""),[Ue,Te]=n(p?.imageUrl||""),je=Ae,Ge=Ue,He=Ie,Be=Te,{previewUrl:De}=V(Ge),Re=de||De||"",Oe=p?.content||"<p></p>",[Pe,Fe]=n(Oe),[Le,Ee]=n(p?.answers||{}),ze=a(""),Je=a(p),$e=a(!1),qe=a(Pe),Xe=a(N);r(()=>{Xe.current=N},[N]);const Me=i(()=>[o.configure({heading:{levels:[2,3,4]}}),d.configure({types:["heading","paragraph"]}),m.configure({resizable:!0}),h,g,u],[]),Qe=i(()=>({attributes:{class:"tiptap-editor prose prose-sm max-w-none p-4 focus:outline-none min-h-[400px]"}}),[]),Ve=l(({editor:e})=>{const t=e.getHTML();t!==qe.current&&(qe.current=t,Fe(t))},[]),Ke=s({immediatelyRender:!1,extensions:Me,content:Pe,editorProps:Qe,onUpdate:Ve});r(()=>{qe.current=Pe},[Pe]),r(()=>{if(!(JSON.stringify(Je.current)!==JSON.stringify(p)))return;if(Je.current=p,!p?.content||"<p></p>"===p.content)return;$e.current=!0,Ie(p?.title||""),Te(p?.imageUrl||""),ie(p?.points??1),se(p?.caseSensitive??!1),oe(p?.explanation||""),Ee(p?.answers||{});const e=p.content;e!==qe.current&&(qe.current=e,Fe(e),Ke&&!Ke.isDestroyed&&Ke.commands.setContent(e)),setTimeout(()=>{$e.current=!1},150)},[p,Ke]);const We=Q(e=>{Xe.current?.(e)},300),_e=JSON.stringify(Le),Ye=i(()=>{const e=new Set,t=/\{([\w\d]+)\}/g;let n;for(;null!==(n=t.exec(Pe));)e.add(n[1]);return Array.from(e).sort((e,t)=>{const n=parseInt(e),r=parseInt(t);return isNaN(n)||isNaN(r)?e.localeCompare(t):n-r})},[Pe]);return r(()=>{Ee(e=>{let t=!1;const n={...e};return Object.keys(n).forEach(e=>{Ye.includes(e)||(delete n[e],t=!0)}),Ye.forEach(e=>{e in n||(n[e]="",t=!0)}),t?n:e})},[Ye]),r(()=>{const e={title:je,imageUrl:Ge,content:Pe,answers:Le,points:ae,caseSensitive:le,explanation:ce,groupTitle:pe,groupSubtitle:xe,groupContent:be,groupImageUrl:fe,groupNumber:Se};if($e.current)return void(ze.current=JSON.stringify(e));const t=JSON.stringify(e);t!==ze.current&&(ze.current=t,We(e))},[je,Ge,Pe,_e,ae,le,ce,pe,xe,be,fe,Se]),r(()=>{w&&(w.current={triggerValidation:async()=>{if(!Pe||"<p></p>"===Pe)return!1;if(0===Ye.length)return!1;for(const e of Ye)if(!Le[e]||""===Le[e].trim())return!1;return!0},getFormData:()=>({title:je,imageUrl:Ge,content:Pe,answers:Le,points:ae,caseSensitive:le,explanation:ce,groupTitle:pe,groupSubtitle:xe,groupContent:be,groupImageUrl:fe,groupNumber:Se})})},[w,je,Ge,Pe,Le,ae,le,ce,Ye,pe,xe,be,fe,Se]),t("div",ne?{className:"space-y-4",children:[e(E,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-teal-100/50",children:e(J,{className:"px-4 py-3",children:t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-teal-500 to-cyan-600 shadow-sm",children:e(I,{className:"h-4 w-4 text-white"})}),t("div",{children:[e($,{className:"text-base font-bold text-gray-900",children:"Điền từ – Xem trước"}),e("p",{className:"text-xs text-gray-500",children:"Chế độ xem trước với đáp án đúng đã được điền sẵn"})]})]}),t(D,{variant:"outline",size:"sm",onClick:()=>re(!1),className:"gap-2 border-teal-200 text-teal-700 hover:bg-teal-50",children:[e(U,{className:"h-4 w-4"}),"Quay lại Edit"]})]})})}),e(M,{questionData:{title:je,imageUrl:Ge,content:Pe,answers:Le,points:ae,caseSensitive:le},isReviewMode:!0,userAnswer:Le})]}:{className:"space-y-4",children:[he&&"BASIC"===Ce&&e(K,{groupNumber:Se,title:pe,subtitle:xe,content:be,imageUrl:fe,onTitleChange:Ne,onSubtitleChange:ve,onContentChange:we,onImageChange:ye,groupConfig:k?C:void 0,isReadOnly:!ge,uploadIdPrefix:`word-fill-structured-${ee??"group"}`}),he&&"DOCUMENT"===Ce&&e(W,{groupNumber:Se,title:pe,subtitle:xe,content:be,imageUrl:fe,onTitleChange:Ne,onSubtitleChange:ve,onContentChange:we,onImageChange:ye,groupConfig:k?C:void 0,isReadOnly:!ge,uploadIdPrefix:`word-fill-structured-${ee??"document"}`}),t(E,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-teal-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-teal-500 via-cyan-500 to-emerald-500"}),e(J,{className:"px-4 py-3",children:t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg bg-gray-100",children:e(j,{className:"h-4 w-4 text-teal-600"})}),t("div",{children:[e($,{className:"text-base font-bold text-gray-900",children:"Điền từ vào biểu mẫu có cấu trúc"}),t("p",{className:"text-xs text-gray-500",children:["Nhập nội dung, đặt chỗ trống bằng ","{0}"," ","{1}","… rồi điền đáp án bên dưới"]})]})]}),t(D,{variant:"outline",size:"sm",onClick:()=>re(!0),disabled:0===Ye.length||Ye.some(e=>!Le[e]?.trim()),className:"gap-2 border-teal-200 text-teal-600 hover:bg-teal-50 hover:text-teal-700",children:[e(I,{className:"h-4 w-4"}),"Xem trước"]})]})}),t(z,{className:"space-y-4 px-4 pb-4",children:[t("div",{className:"flex flex-wrap items-center gap-3",children:[e(L,{value:ae,onChange:e=>ie(Number(e.target.value)||1)}),t("div",{role:"button",tabIndex:0,onClick:()=>se(!le),onKeyDown:e=>{" "!==e.key&&"Enter"!==e.key||se(!le)},style:{paddingBlock:"calc(var(--spacing) * 1)"},className:"inline-flex cursor-pointer items-center gap-2 rounded-md border px-3.5 text-sm font-medium transition-colors select-none "+(le?"border-teal-300 bg-teal-50 text-teal-700":"border-gray-200 bg-white text-gray-400 hover:text-gray-600"),children:[e(F,{checked:le,onCheckedChange:se,className:le?"data-[state=checked]:bg-teal-500 pointer-events-none":"pointer-events-none",tabIndex:-1}),"Phân biệt Hoa / thường"]})]}),t("div",{className:"space-y-2",children:[t(R,{htmlFor:"wfsf-title",className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(H,{className:"h-4 w-4 text-teal-500"}),"Tiêu đề",e("span",{className:"text-sm font-normal text-gray-500",children:"(tuỳ chọn)"})]}),e(O,{id:"wfsf-title",value:je,onChange:e=>He(e.target.value),placeholder:"Nhập tiêu đề câu hỏi...",className:"border-gray-200 bg-white transition-all focus:border-teal-300 focus:ring-teal-100"})]}),t("div",{className:"space-y-2",children:[t(R,{className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(B,{className:"h-4 w-4 text-teal-500"}),"Hình ảnh",e("span",{className:"text-sm font-normal text-gray-500",children:"(tuỳ chọn)"})]}),e(q,{id:"wfsf-image",label:"",accept:"image/*",value:Ge||"",onChange:e=>Be(e),onPresignedUrlChange:me,maxSize:5,placeholder:"Upload ảnh hoặc paste URL",autoUpload:!0,prefix:"questions"}),Re&&e(X,{src:Re,alt:"Preview",className:"mt-2 h-48 w-full max-w-md rounded-lg border border-gray-200"})]}),y&&y.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 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:y.map((t,n)=>e("li",{children:t},n))})]})]})]})]}),ke&&t(E,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-teal-100/50",children:[e(J,{className:"px-4 py-3",children:t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-teal-100",children:e(x,{className:"h-3.5 w-3.5 text-teal-600"})}),e($,{className:"text-base font-semibold text-gray-800",children:"Nội dung cấu trúc"})]}),Ye.length>0&&t("span",{className:"inline-flex items-center rounded-full border border-teal-200 bg-teal-50 px-2.5 py-0.5 text-xs font-semibold text-teal-700",children:[Ye.length," chỗ trống"]})]})}),t(z,{className:"space-y-3 px-4 pb-4",children:[t("div",{className:"overflow-hidden rounded-xl border border-gray-200 transition-all focus-within:border-teal-300 focus-within:ring-2 focus-within:ring-teal-100",children:[e(te,{editor:Ke}),e(c,{editor:Ke})]}),t("div",{className:"flex items-start gap-2 rounded-lg border border-teal-100 bg-teal-50/60 px-3 py-2.5",children:[e(G,{className:"mt-0.5 h-4 w-4 shrink-0 text-teal-500"}),t("p",{className:"text-xs text-teal-700",children:["Nhập ",e("strong",{children:"{0}"}),", ",e("strong",{children:"{1}"}),", ",e("strong",{children:"{2}"}),"… vào văn bản để tạo ô trống. Đáp án sẽ tự động xuất hiện bên dưới để bạn nhập."]})]})]})]}),Ye.length>0&&t(E,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-teal-100/50",children:[e(J,{className:"px-4 py-3",children:t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-teal-100",children:e(T,{className:"h-3.5 w-3.5 text-teal-600"})}),t($,{className:"text-base font-semibold text-gray-800",children:["Đáp án ",t("span",{className:"text-xs font-normal text-gray-500",children:["(",Ye.length," chỗ trống)"]})]})]})}),t(z,{className:"px-4 pb-4",children:[e("div",{className:"grid grid-cols-1 gap-3 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4",children:Ye.map(n=>{const r=!Le[n]||""===Le[n].trim();return t("div",{className:"rounded-xl border-2 p-3 transition-all duration-200 "+(r?"border-gray-200 bg-white":"border-teal-200 bg-gradient-to-br from-teal-50 to-cyan-50 shadow-sm shadow-teal-100"),children:[t(R,{className:"mb-1.5 flex items-center justify-between text-sm font-semibold text-gray-700",children:[t("span",{children:["Chỗ trống ",e("code",{className:"rounded bg-gray-100 px-1 text-teal-700",children:`{${n}}`})]}),r&&e("span",{className:"text-[10px] font-normal italic text-red-500",children:"*Bắt buộc"})]}),e(O,{value:Le[n]||"",onChange:e=>Ee(t=>({...t,[n]:e.target.value})),placeholder:"Đáp án đúng...",className:"border-gray-200 bg-white transition-all focus:border-teal-300 focus:ring-teal-100 "+(r?"":"border-teal-200")}),!r&&t("span",{className:"mt-1.5 inline-flex items-center gap-1 rounded-full bg-teal-100 px-2 py-0.5 text-[10px] font-semibold text-teal-700",children:[e(T,{className:"h-3 w-3"}),"Đã nhập"]})]},n)})}),Ye.some(e=>!Le[e]?.trim())&&t("div",{className:"mt-4 flex items-start gap-3 rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4",children:[e("div",{className:"flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-amber-100",children:e("span",{className:"text-lg",children:"⚠️"})}),t("div",{className:"text-sm text-amber-800",children:[e("p",{className:"font-semibold",children:"Còn chỗ trống chưa có đáp án"}),t("p",{className:"mt-0.5 text-amber-700",children:["Vui lòng điền đáp án đúng cho tất cả các ô được đánh dấu ",e("span",{className:"text-red-500 font-medium",children:"*Bắt buộc"}),"."]})]})]})]})]}),t(E,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50",children:[e(J,{className:"px-4 py-3",children:t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm",children:e(A,{className:"h-3.5 w-3.5 text-white"})}),t("div",{children:[t("div",{className:"flex items-center gap-2",children:[e($,{className:"text-base 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 py-0.5 text-xs font-medium text-amber-600",children:"Không bắt buộc"})]}),e("p",{className:"mt-0.5 text-xs text-gray-500",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(z,{className:"px-4 pb-4",children:e(P,{value:ce,onChange:e=>oe(e.target.value),placeholder:"VD: Câu trả lời đúng là... vì...",rows:3,className:"min-h-[80px] resize-none border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200"})})]})]})}
@@ -1,2 +1 @@
1
- export { WordFillStructuredFormCreator } from './WordFillStructuredFormCreator';
2
- export { WordFillStructuredFormClient } from './WordFillStructuredFormClient';
1
+ export{WordFillStructuredFormCreator}from"./WordFillStructuredFormCreator";export{WordFillStructuredFormClient}from"./WordFillStructuredFormClient";
@@ -1,67 +1 @@
1
- import { WordFillStructuredFormCreator } from './WordFillStructuredFormCreator';
2
- export const wordFillStructuredFormRegistration = {
3
- component: WordFillStructuredFormCreator,
4
- transformInitialData: (initialData) => {
5
- const apiContent = initialData?.content || {};
6
- const answer = initialData?.answer || {};
7
- const correctAnswer = initialData?.correctAnswer || answer?.correctAnswer || {};
8
- const points = initialData?.total_points
9
- ? parseFloat(initialData.total_points)
10
- : (answer?.points || initialData?.points || 1);
11
- const explanation = initialData?.explanation || answer?.explanation || '';
12
- // Resolve content: prefer single `content` string, fallback to merging old `blocks[]`
13
- let content = apiContent?.content || answer?.content || '';
14
- const blocks = apiContent?.blocks || answer?.blocks;
15
- if (!content && blocks?.length) {
16
- content = blocks.map((b) => {
17
- const heading = b.heading ? `<h3>${b.heading}</h3>` : '';
18
- return heading + (b.content || '');
19
- }).join('');
20
- }
21
- // Group data from questionGroup payload
22
- const groupPayload = initialData?.questionGroup?.payload;
23
- return {
24
- title: apiContent?.title || answer?.title || '',
25
- imageUrl: apiContent?.imageUrl || answer?.imageUrl || '',
26
- content,
27
- answers: correctAnswer?.answers || answer?.answers || {},
28
- caseSensitive: correctAnswer?.caseSensitive ?? answer?.caseSensitive ?? false,
29
- explanation,
30
- points,
31
- // Group shared fields
32
- groupTitle: groupPayload?.title || answer?.groupTitle || '',
33
- groupSubtitle: groupPayload?.subtitle || answer?.groupSubtitle || '',
34
- groupContent: groupPayload?.content || answer?.groupContent || '',
35
- groupImageUrl: groupPayload?.imageUrl || answer?.groupImageUrl || '',
36
- };
37
- },
38
- getExtraProps: (ctx) => ({
39
- onUnsavedChangesChange: ctx.onUnsavedChangesChange,
40
- validationRef: ctx.validationRef,
41
- groupConfig: ctx.groupConfig,
42
- groupType: ctx.groupType,
43
- groups: ctx.groups,
44
- questionIndexInPart: ctx.questionIndexInPart,
45
- partId: ctx.partId,
46
- }),
47
- wrapOnSave: (data, ctx) => ({
48
- type: ctx.questionType,
49
- content: ctx.state.content,
50
- points: data?.points ?? ctx.state.points ?? 1,
51
- level: ctx.state.level,
52
- difficulty: ctx.state.difficulty,
53
- skill: ctx.state.skill,
54
- answer: data,
55
- explanation: data?.explanation || '',
56
- }),
57
- wrapOnChange: (data, ctx) => ({
58
- type: ctx.questionType,
59
- content: ctx.state.content,
60
- points: data?.points ?? ctx.state.points ?? 1,
61
- level: ctx.state.level,
62
- difficulty: ctx.state.difficulty,
63
- skill: ctx.state.skill,
64
- answer: data,
65
- explanation: data?.explanation || '',
66
- }),
67
- };
1
+ import{WordFillStructuredFormCreator as t}from"./WordFillStructuredFormCreator";export const wordFillStructuredFormRegistration={component:t,transformInitialData:t=>{const e=t?.content||{},n=t?.answer||{},o=t?.correctAnswer||n?.correctAnswer||{},a=t?.total_points?parseFloat(t.total_points):n?.points||t?.points||1,i=t?.explanation||n?.explanation||"";let s=e?.content||n?.content||"";const r=e?.blocks||n?.blocks;!s&&r?.length&&(s=r.map(t=>(t.heading?`<h3>${t.heading}</h3>`:"")+(t.content||"")).join(""));const l=t?.questionGroup?.payload;return{title:e?.title||n?.title||"",imageUrl:e?.imageUrl||n?.imageUrl||"",content:s,answers:o?.answers||n?.answers||{},caseSensitive:o?.caseSensitive??n?.caseSensitive??!1,explanation:i,points:a,groupTitle:l?.title||n?.groupTitle||"",groupSubtitle:l?.subtitle||n?.groupSubtitle||"",groupContent:l?.content||n?.groupContent||"",groupImageUrl:l?.imageUrl||n?.groupImageUrl||""}},getExtraProps:t=>({onUnsavedChangesChange:t.onUnsavedChangesChange,validationRef:t.validationRef,groupConfig:t.groupConfig,groupType:t.groupType,groups:t.groups,questionIndexInPart:t.questionIndexInPart,partId:t.partId}),wrapOnSave:(t,e)=>({type:e.questionType,content:e.state.content,points:t?.points??e.state.points??1,level:e.state.level,difficulty:e.state.difficulty,skill:e.state.skill,answer:t,explanation:t?.explanation||""}),wrapOnChange:(t,e)=>({type:e.questionType,content:e.state.content,points:t?.points??e.state.points??1,level:e.state.level,difficulty:e.state.difficulty,skill:e.state.skill,answer:t,explanation:t?.explanation||""})};
@@ -1,40 +1 @@
1
- /**
2
- * Transform WORD_FILL_STRUCTURED_FORM creator data → API format
3
- *
4
- * Creator (question.answer): { title, imageUrl, content, answers, caseSensitive, points, explanation }
5
- * API:
6
- * content: { title, imageUrl, content }
7
- * correctAnswer: { answers, caseSensitive }
8
- */
9
- export const transformWordFillStructuredForm = (question) => {
10
- const answerData = question.answer;
11
- if (!answerData?.content || !answerData?.answers) {
12
- return { apiContent: {}, apiCorrectAnswer: {} };
13
- }
14
- // Build basicGroupData when any group field is present
15
- const hasGroupData = !!(answerData.groupTitle || answerData.groupSubtitle || answerData.groupContent || answerData.groupImageUrl);
16
- // Per-question title/imageUrl are independent from group title/imageUrl — always include them
17
- const apiContent = {
18
- title: answerData.title || '',
19
- imageUrl: answerData.imageUrl || '',
20
- content: answerData.content,
21
- };
22
- const apiCorrectAnswer = {
23
- answers: answerData.answers,
24
- caseSensitive: answerData.caseSensitive ?? false,
25
- };
26
- const basicGroupData = hasGroupData
27
- ? {
28
- title: answerData.groupTitle || '',
29
- subtitle: answerData.groupSubtitle || '',
30
- content: answerData.groupContent || '',
31
- imageUrl: answerData.groupImageUrl || '',
32
- }
33
- : undefined;
34
- return {
35
- apiContent,
36
- apiCorrectAnswer,
37
- basicGroupData,
38
- groupNumber: answerData.groupNumber || 1,
39
- };
40
- };
1
+ export const transformWordFillStructuredForm=t=>{const e=t.answer;if(!e?.content||!e?.answers)return{apiContent:{},apiCorrectAnswer:{}};const r=!!(e.groupTitle||e.groupSubtitle||e.groupContent||e.groupImageUrl);return{apiContent:{title:e.title||"",imageUrl:e.imageUrl||"",content:e.content},apiCorrectAnswer:{answers:e.answers,caseSensitive:e.caseSensitive??!1},basicGroupData:r?{title:e.groupTitle||"",subtitle:e.groupSubtitle||"",content:e.groupContent||"",imageUrl:e.groupImageUrl||""}:void 0,groupNumber:e.groupNumber||1}};
@@ -1,84 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useEffect, useCallback } from 'react';
4
- import { Check, X, ArrowUpDown, Lightbulb } from 'lucide-react';
5
- import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
6
- import { cn } from '../../../../shared/lib/utils';
7
- export function WordOrderingClient({ questionData, onSubmit, isReviewMode = false, userAnswer, autoFillCorrectAnswers = false, explanation, }) {
8
- const { words = [], answer = [], isExample, imageUrl } = questionData;
9
- // State: the user's current arrangement
10
- const [selectedWords, setSelectedWords] = useState(userAnswer || (autoFillCorrectAnswers ? answer : []));
11
- // Words remaining in the pool
12
- const [availableWords, setAvailableWords] = useState(() => {
13
- const initial = userAnswer || (autoFillCorrectAnswers ? answer : []);
14
- if (initial.length > 0) {
15
- // Remove used words from available pool
16
- const remaining = [...words];
17
- initial.forEach((w) => {
18
- const idx = remaining.indexOf(w);
19
- if (idx >= 0)
20
- remaining.splice(idx, 1);
21
- });
22
- return remaining;
23
- }
24
- return [...words];
25
- });
26
- const { previewUrl: imagePreviewUrl, isLoading } = usePresignedFileUrl(imageUrl || '');
27
- // Sync with userAnswer changes
28
- useEffect(() => {
29
- if (userAnswer) {
30
- setSelectedWords(userAnswer);
31
- const remaining = [...words];
32
- userAnswer.forEach((w) => {
33
- const idx = remaining.indexOf(w);
34
- if (idx >= 0)
35
- remaining.splice(idx, 1);
36
- });
37
- setAvailableWords(remaining);
38
- }
39
- }, [userAnswer]);
40
- // Reset when words change (question navigation)
41
- useEffect(() => {
42
- if (!userAnswer && !autoFillCorrectAnswers) {
43
- setSelectedWords([]);
44
- setAvailableWords([...words]);
45
- }
46
- }, [JSON.stringify(words)]);
47
- const handleSelectWord = useCallback((word, poolIndex) => {
48
- if (isReviewMode)
49
- return;
50
- const newSelected = [...selectedWords, word];
51
- const newAvailable = [...availableWords];
52
- newAvailable.splice(poolIndex, 1);
53
- setSelectedWords(newSelected);
54
- setAvailableWords(newAvailable);
55
- onSubmit?.(newSelected);
56
- }, [selectedWords, availableWords, isReviewMode, onSubmit]);
57
- const handleRemoveWord = useCallback((index) => {
58
- if (isReviewMode)
59
- return;
60
- const removed = selectedWords[index];
61
- const newSelected = selectedWords.filter((_, i) => i !== index);
62
- setSelectedWords(newSelected);
63
- setAvailableWords([...availableWords, removed]);
64
- onSubmit?.(newSelected);
65
- }, [selectedWords, availableWords, isReviewMode, onSubmit]);
66
- const isCorrect = isReviewMode && selectedWords.length === answer.length &&
67
- selectedWords.every((w, i) => w === answer[i]);
68
- const isWrong = isReviewMode && selectedWords.length > 0 && !isCorrect;
69
- const isComplete = selectedWords.length === words.length;
70
- const displayExplanation = explanation || questionData.explanation;
71
- return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-5 py-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-600 text-white shadow-sm", children: _jsx(ArrowUpDown, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "S\u1EAFp x\u1EBFp t\u1EEB" }), isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2.5 py-0.5 text-xs font-semibold text-amber-700", children: [_jsx(Lightbulb, { className: "h-3 w-3" }), "V\u00ED d\u1EE5"] }))] }), isReviewMode && isComplete && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isCorrect ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'), children: [isCorrect ? _jsx(Check, { className: "h-3.5 w-3.5" }) : _jsx(X, { className: "h-3.5 w-3.5" }), isCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "p-5 space-y-5", children: [(imagePreviewUrl || imageUrl) && (_jsx("div", { className: "flex justify-center", children: _jsx("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-gray-50", children: isLoading ? (_jsx("div", { className: "flex h-48 w-48 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-amber-500" }) })) : (_jsx("img", { src: imagePreviewUrl || imageUrl, alt: "H\u00ECnh \u1EA3nh c\u00E2u h\u1ECFi", className: "max-h-56 w-auto object-contain p-2" })) }) })), _jsxs("div", { className: "space-y-2", children: [_jsx("span", { className: "text-xs font-medium text-gray-500 uppercase tracking-wider", children: "\u0110\u00E1p \u00E1n c\u1EE7a b\u1EA1n" }), _jsx("div", { className: "flex flex-wrap gap-2 min-h-[48px] rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 p-3", children: selectedWords.length === 0 ? (_jsx("span", { className: "text-sm text-gray-400 italic", children: "Nh\u1EA5n v\u00E0o c\u00E1c t\u1EEB b\u00EAn d\u01B0\u1EDBi \u0111\u1EC3 s\u1EAFp x\u1EBFp..." })) : (selectedWords.map((word, index) => {
72
- const isWordCorrect = isReviewMode && answer[index] === word;
73
- const isWordWrong = isReviewMode && answer[index] !== word;
74
- return (_jsxs("button", { type: "button", onClick: () => handleRemoveWord(index), disabled: isReviewMode, className: cn('inline-flex items-center gap-1 rounded-lg px-3 py-2 text-sm font-semibold transition-all shadow-sm', isReviewMode
75
- ? isWordCorrect
76
- ? 'bg-green-100 text-green-800 border border-green-300'
77
- : 'bg-red-100 text-red-800 border border-red-300'
78
- : 'bg-amber-100 text-amber-800 border border-amber-300 hover:bg-amber-200 cursor-pointer'), children: [word, isReviewMode && (isWordCorrect
79
- ? _jsx(Check, { className: "h-3.5 w-3.5 text-green-600" })
80
- : _jsx(X, { className: "h-3.5 w-3.5 text-red-600" }))] }, `selected-${index}`));
81
- })) })] }), availableWords.length > 0 && (_jsxs("div", { className: "space-y-2", children: [_jsx("span", { className: "text-xs font-medium text-gray-500 uppercase tracking-wider", children: "T\u1EEB c\u00F3 s\u1EB5n" }), _jsx("div", { className: "flex flex-wrap gap-2", children: availableWords.map((word, index) => (_jsx("button", { type: "button", onClick: () => handleSelectWord(word, index), disabled: isReviewMode, className: cn('rounded-lg border-2 px-3 py-2 text-sm font-semibold transition-all shadow-sm', isReviewMode
82
- ? 'border-gray-200 bg-gray-100 text-gray-500 cursor-default'
83
- : 'border-blue-200 bg-blue-50 text-blue-700 hover:bg-blue-100 hover:border-blue-300 cursor-pointer hover:shadow-md'), children: word }, `available-${index}`))) })] })), isReviewMode && isWrong && (_jsxs("div", { className: "rounded-lg border border-green-200 bg-green-50 p-3", children: [_jsx("span", { className: "text-xs font-medium text-green-600 uppercase tracking-wider", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" }), _jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: answer.map((word, index) => (_jsx("span", { className: "rounded-lg bg-green-100 px-3 py-1.5 text-sm font-semibold text-green-800 border border-green-300", children: word }, `correct-${index}`))) })] })), isReviewMode && displayExplanation && (_jsxs("div", { className: "rounded-lg border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-3", children: [_jsx("span", { className: "text-xs font-medium text-blue-600 uppercase tracking-wider", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-blue-800", children: displayExplanation })] }))] })] }) }));
84
- }
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useState as t,useEffect as a,useCallback as s}from"react";import{Check as n,X as d,ArrowUpDown as i,Lightbulb as l}from"lucide-react";import{usePresignedFileUrl as o}from"../../../../shared/lib/hooks";import{cn as c}from"../../../../shared/lib/utils";export function WordOrderingClient({questionData:m,onSubmit:b,isReviewMode:g=!1,userAnswer:p,autoFillCorrectAnswers:h=!1,explanation:x}){const{words:u=[],answer:f=[],isExample:N,imageUrl:w}=m,[y,v]=t(p||(h?f:[])),[j,k]=t(()=>{const e=p||(h?f:[]);if(e.length>0){const r=[...u];return e.forEach(e=>{const t=r.indexOf(e);t>=0&&r.splice(t,1)}),r}return[...u]}),{previewUrl:C,isLoading:O}=o(w||"");a(()=>{if(p){v(p);const e=[...u];p.forEach(r=>{const t=e.indexOf(r);t>=0&&e.splice(t,1)}),k(e)}},[p]),a(()=>{p||h||(v([]),k([...u]))},[JSON.stringify(u)]);const S=s((e,r)=>{if(g)return;const t=[...y,e],a=[...j];a.splice(r,1),v(t),k(a),b?.(t)},[y,j,g,b]),E=s(e=>{if(g)return;const r=y[e],t=y.filter((r,t)=>t!==e);v(t),k([...j,r]),b?.(t)},[y,j,g,b]),$=g&&y.length===f.length&&y.every((e,r)=>e===f[r]),A=g&&y.length>0&&!$,U=y.length===u.length,q=x||m.explanation;return e("div",{className:"space-y-4",children:r("div",{className:"overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm",children:[r("div",{className:"flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-5 py-3",children:[r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-600 text-white shadow-sm",children:e(i,{className:"h-4 w-4"})}),e("span",{className:"text-sm font-medium text-gray-700",children:"Sắp xếp từ"}),N&&r("span",{className:"inline-flex items-center gap-1 rounded-full bg-amber-100 px-2.5 py-0.5 text-xs font-semibold text-amber-700",children:[e(l,{className:"h-3 w-3"}),"Ví dụ"]})]}),g&&U&&r("div",{className:c("flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold",$?"bg-green-100 text-green-700":"bg-red-100 text-red-700"),children:[e($?n:d,{className:"h-3.5 w-3.5"}),$?"Đúng":"Sai"]})]}),r("div",{className:"p-5 space-y-5",children:[(C||w)&&e("div",{className:"flex justify-center",children:e("div",{className:"overflow-hidden rounded-lg border border-gray-200 bg-gray-50",children:O?e("div",{className:"flex h-48 w-48 items-center justify-center",children:e("div",{className:"h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-amber-500"})}):e("img",{src:C||w,alt:"Hình ảnh câu hỏi",className:"max-h-56 w-auto object-contain p-2"})})}),r("div",{className:"space-y-2",children:[e("span",{className:"text-xs font-medium text-gray-500 uppercase tracking-wider",children:"Đáp án của bạn"}),e("div",{className:"flex flex-wrap gap-2 min-h-[48px] rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 p-3",children:0===y.length?e("span",{className:"text-sm text-gray-400 italic",children:"Nhấn vào các từ bên dưới để sắp xếp..."}):y.map((t,a)=>{const s=g&&f[a]===t;g&&f[a];return r("button",{type:"button",onClick:()=>E(a),disabled:g,className:c("inline-flex items-center gap-1 rounded-lg px-3 py-2 text-sm font-semibold transition-all shadow-sm",g?s?"bg-green-100 text-green-800 border border-green-300":"bg-red-100 text-red-800 border border-red-300":"bg-amber-100 text-amber-800 border border-amber-300 hover:bg-amber-200 cursor-pointer"),children:[t,g&&(s?e(n,{className:"h-3.5 w-3.5 text-green-600"}):e(d,{className:"h-3.5 w-3.5 text-red-600"}))]},`selected-${a}`)})})]}),j.length>0&&r("div",{className:"space-y-2",children:[e("span",{className:"text-xs font-medium text-gray-500 uppercase tracking-wider",children:"Từ có sẵn"}),e("div",{className:"flex flex-wrap gap-2",children:j.map((r,t)=>e("button",{type:"button",onClick:()=>S(r,t),disabled:g,className:c("rounded-lg border-2 px-3 py-2 text-sm font-semibold transition-all shadow-sm",g?"border-gray-200 bg-gray-100 text-gray-500 cursor-default":"border-blue-200 bg-blue-50 text-blue-700 hover:bg-blue-100 hover:border-blue-300 cursor-pointer hover:shadow-md"),children:r},`available-${t}`))})]}),g&&A&&r("div",{className:"rounded-lg border border-green-200 bg-green-50 p-3",children:[e("span",{className:"text-xs font-medium text-green-600 uppercase tracking-wider",children:"Đáp án đúng"}),e("div",{className:"mt-2 flex flex-wrap gap-2",children:f.map((r,t)=>e("span",{className:"rounded-lg bg-green-100 px-3 py-1.5 text-sm font-semibold text-green-800 border border-green-300",children:r},`correct-${t}`))})]}),g&&q&&r("div",{className:"rounded-lg border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-3",children:[e("span",{className:"text-xs font-medium text-blue-600 uppercase tracking-wider",children:"Giải thích"}),e("p",{className:"mt-1 text-sm text-blue-800",children:q})]})]})]})})}