@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,320 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useRef, useEffect } from 'react';
4
- import { Button } from '../../../../components/ui/button';
5
- import { Label } from '../../../../components/ui/label';
6
- import { Input } from '../../../../components/ui/input';
7
- import { Textarea } from '../../../../components/ui/textarea';
8
- import { PointsInput } from '../../../../components/ui/points-input';
9
- import { FileUpload } from '../../../../components/ui/file-upload';
10
- import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
11
- import { Pencil, Eye, Plus, Trash2, Lightbulb, Mail, ImageIcon, X } from 'lucide-react';
12
- import { WriteAShortLetterClient } from './WriteAShortLetterClient';
13
- import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
14
- import { usePresignedFileUrl } from '../../../../shared/lib/hooks/usePresignedFileUrl';
15
- import { z } from 'zod';
16
- // Component to render a single guide image with presigned URL resolution
17
- function GuideImageItem({ imageKey, index, localPreviewUrl, onRemove, }) {
18
- const { previewUrl: presignedUrl } = usePresignedFileUrl(imageKey);
19
- const displayUrl = localPreviewUrl || presignedUrl || '';
20
- return (_jsxs("div", { className: "group relative overflow-hidden rounded-lg border border-violet-200 bg-white shadow-sm transition-all hover:shadow-md", children: [_jsx("div", { className: "aspect-square w-full overflow-hidden bg-gray-50", children: displayUrl ? (_jsx("img", { src: displayUrl, alt: `Ảnh hướng dẫn ${index + 1}`, className: "h-full w-full object-cover transition-transform group-hover:scale-105", onError: (e) => {
21
- e.target.src = '/images/placeholder-image.svg';
22
- } })) : (_jsx("div", { className: "flex h-full w-full items-center justify-center", children: _jsx(ImageIcon, { className: "h-8 w-8 text-gray-300" }) })) }), _jsx("button", { type: "button", onClick: () => onRemove(index), className: "absolute right-1.5 top-1.5 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-white opacity-0 shadow-md transition-opacity group-hover:opacity-100 hover:bg-red-600", title: "X\u00F3a \u1EA3nh", children: _jsx(X, { className: "h-3.5 w-3.5" }) }), _jsx("div", { className: "absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/50 to-transparent p-1.5 opacity-0 transition-opacity group-hover:opacity-100", children: _jsxs("span", { className: "text-xs font-medium text-white", children: ["\u1EA2nh ", index + 1] }) })] }));
23
- }
24
- // Form schema for validation only
25
- const writeAShortLetterFormSchema = z.object({
26
- informations: z.array(z.string()),
27
- minWords: z.coerce.number().min(1, 'Số từ tối thiểu phải >= 1'),
28
- maxWords: z.coerce.number().optional(),
29
- instruction: z.string().optional(),
30
- points: z.coerce.number().min(1, 'Điểm phải >= 1'),
31
- });
32
- function WriteAShortLetterCreatorContent({ initialData, onChange, externalErrors, validationRef, }) {
33
- // State - use simple useState like ReadAndColorObjectsCreator
34
- const [informations, setInformations] = useState(initialData?.informations || ['']);
35
- const [minWords, setMinWords] = useState(initialData?.minWords ?? 20);
36
- const [maxWords, setMaxWords] = useState(initialData?.maxWords);
37
- const [instruction, setInstruction] = useState(initialData?.instructions?.mainInstruction || '');
38
- const [guideImages, setGuideImages] = useState(initialData?.guideImages || []);
39
- const [points, setPoints] = useState(initialData?.points || 10);
40
- // Store presigned URLs keyed by imageKey (NOT index) to avoid index races
41
- // when the user uploads several images quickly.
42
- const [guideImagePreviewUrls, setGuideImagePreviewUrls] = useState({});
43
- // Correlates an upload's `onChange(key)` with its later `onPresignedUrlChange(url)`
44
- const pendingUploadKeyRef = useRef(null);
45
- // UI State
46
- const [isClientMode, setIsClientMode] = useState(false);
47
- // Use react-hook-form for validation only (like ReadAndColorObjectsCreator)
48
- const form = useReactHookForm({
49
- defaultValues: {
50
- informations: informations,
51
- minWords: minWords,
52
- maxWords: maxWords,
53
- instruction: instruction,
54
- points: points,
55
- },
56
- zodSchema: writeAShortLetterFormSchema,
57
- mode: 'onBlur',
58
- });
59
- // Track previous initialData to detect changes
60
- const previousInitialDataRef = useRef(initialData);
61
- const isInitializingRef = useRef(true);
62
- const hasInitializedRef = useRef(false);
63
- // Stringified shape of the last payload we emitted (in initialData shape).
64
- // Used to recognize when incoming initialData is just the parent echoing our own
65
- // emission back — in that case we must NOT re-run the sync block, otherwise
66
- // fast typing gets silently dropped (the sync's 150ms guard blocks subsequent
67
- // onChange emissions).
68
- const lastEmittedInitialDataRef = useRef('');
69
- // Track a few recently emitted payloads because parent echoes can arrive slightly
70
- // out of order while typing into number inputs (e.g. local `300`, then parent
71
- // echoes older `30`). Those stale echoes must be ignored instead of overwriting
72
- // the latest local state.
73
- const recentEmittedInitialDataRef = useRef([]);
74
- // Expose validation function via ref
75
- useEffect(() => {
76
- if (validationRef) {
77
- validationRef.current = {
78
- triggerValidation: async () => {
79
- return validateForm();
80
- },
81
- };
82
- }
83
- }, [validationRef, informations, minWords, maxWords, instruction, guideImages, points]);
84
- // Sync state from initialData when it changes
85
- useEffect(() => {
86
- const incomingStr = JSON.stringify(initialData);
87
- // If parent is echoing back one of our very recent emissions, do NOT
88
- // re-enter the init block — otherwise we'd block subsequent user keystrokes
89
- // via isInitializingRef for 150ms and silently drop input
90
- // (e.g. "300" → older echoed "30").
91
- if (hasInitializedRef.current &&
92
- (incomingStr === lastEmittedInitialDataRef.current ||
93
- recentEmittedInitialDataRef.current.includes(incomingStr))) {
94
- previousInitialDataRef.current = initialData;
95
- return;
96
- }
97
- const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== incomingStr;
98
- if (initialDataChanged || !hasInitializedRef.current) {
99
- isInitializingRef.current = true;
100
- previousInitialDataRef.current = initialData;
101
- hasInitializedRef.current = true;
102
- // Always overwrite every field so previous question's state doesn't leak
103
- // (e.g. previous had maxWords=300, new question has no maxWords → must reset to undefined)
104
- setInformations(initialData?.informations?.length ? initialData.informations : ['']);
105
- setMinWords(initialData?.minWords ?? 20);
106
- setMaxWords(initialData?.maxWords);
107
- setInstruction(initialData?.instructions?.mainInstruction ?? '');
108
- setGuideImages(initialData?.guideImages ?? []);
109
- setPoints(initialData?.points ?? 10);
110
- // Clear stale preview URLs from the previous question's uploads
111
- setGuideImagePreviewUrls({});
112
- // Reset flag after state updates
113
- setTimeout(() => {
114
- isInitializingRef.current = false;
115
- }, 150);
116
- }
117
- }, [initialData]);
118
- // Store onChange callback in ref to avoid dependency issues
119
- const onChangeRef = useRef(onChange);
120
- useEffect(() => {
121
- onChangeRef.current = onChange;
122
- }, [onChange]);
123
- // Track previous values to prevent unnecessary onChange calls
124
- const previousPayloadRef = useRef('');
125
- // Trigger onChange when state changes (like ReadAndColorObjectsCreator)
126
- useEffect(() => {
127
- // Skip onChange during initialization to prevent infinite loop
128
- if (isInitializingRef.current || !hasInitializedRef.current) {
129
- // Still update previous payload ref to track current state
130
- const currentPayload = JSON.stringify({
131
- informations,
132
- minWords,
133
- maxWords,
134
- instruction,
135
- guideImages,
136
- points,
137
- });
138
- previousPayloadRef.current = currentPayload;
139
- return;
140
- }
141
- // Don't call onChange if we have no meaningful data yet
142
- if (!instruction.trim() && informations.every((s) => !s.trim())) {
143
- return;
144
- }
145
- // Create payload
146
- const payload = {
147
- informations,
148
- minWords,
149
- maxWords,
150
- instructions: {
151
- mainInstruction: instruction || '',
152
- },
153
- guideImages: guideImages.length > 0 ? guideImages : undefined,
154
- points,
155
- };
156
- // Serialize payload for comparison
157
- const payloadString = JSON.stringify(payload);
158
- // Only call onChange if payload actually changed
159
- if (previousPayloadRef.current !== payloadString) {
160
- previousPayloadRef.current = payloadString;
161
- // Record the shape parent is expected to echo back (via register.tsx
162
- // transformInitialData), so the sync effect can recognize the echo and
163
- // not re-enter its init guard. Field order & shape MUST match
164
- // `transformInitialData` in register.tsx exactly, including
165
- // `guideImages` being `[]` (never `undefined`).
166
- lastEmittedInitialDataRef.current = JSON.stringify({
167
- informations,
168
- minWords,
169
- maxWords,
170
- instructions: { mainInstruction: instruction || '' },
171
- guideImages,
172
- points,
173
- });
174
- recentEmittedInitialDataRef.current = [
175
- ...recentEmittedInitialDataRef.current,
176
- lastEmittedInitialDataRef.current,
177
- ].slice(-10);
178
- onChangeRef.current?.(payload);
179
- }
180
- }, [informations, minWords, maxWords, instruction, guideImages, points]);
181
- // Get validation errors
182
- const getErrors = () => {
183
- const errors = {};
184
- const informationsError = form.formState.errors.informations;
185
- if (informationsError && typeof informationsError.message === 'string') {
186
- errors.informations = informationsError.message;
187
- }
188
- const minWordsError = form.formState.errors.minWords;
189
- if (minWordsError && typeof minWordsError.message === 'string') {
190
- errors.minWords = minWordsError.message;
191
- }
192
- const maxWordsError = form.formState.errors.maxWords;
193
- if (maxWordsError && typeof maxWordsError.message === 'string') {
194
- errors.maxWords = maxWordsError.message;
195
- }
196
- const instructionError = form.formState.errors.instruction;
197
- if (instructionError && typeof instructionError.message === 'string') {
198
- errors.instruction = instructionError.message;
199
- }
200
- const pointsError = form.formState.errors.points;
201
- if (pointsError && typeof pointsError.message === 'string') {
202
- errors.points = pointsError.message;
203
- }
204
- return errors;
205
- };
206
- // Validate form data
207
- const validateForm = () => {
208
- // Update form values before validation
209
- form.setValue('informations', informations);
210
- form.setValue('minWords', minWords);
211
- form.setValue('maxWords', maxWords);
212
- form.setValue('instruction', instruction);
213
- form.setValue('points', points);
214
- // Trigger validation
215
- const result = writeAShortLetterFormSchema.safeParse({
216
- informations,
217
- minWords,
218
- maxWords,
219
- instruction,
220
- points,
221
- });
222
- if (!result.success) {
223
- // Set errors from Zod validation
224
- result.error.issues.forEach((issue) => {
225
- const path = issue.path.join('.');
226
- form.setError(path, { message: issue.message });
227
- });
228
- return false;
229
- }
230
- return true;
231
- };
232
- // Add new information
233
- const addInformation = () => {
234
- setInformations([...informations, '']);
235
- form.clearErrors('informations');
236
- };
237
- // Remove information
238
- const removeInformation = (index) => {
239
- if (informations.length > 1) {
240
- setInformations(informations.filter((_, i) => i !== index));
241
- }
242
- };
243
- // Update information at index
244
- const updateInformation = (index, value) => {
245
- const newInformations = [...informations];
246
- newInformations[index] = value;
247
- setInformations(newInformations);
248
- form.clearErrors('informations');
249
- };
250
- // Handle guide image upload
251
- const handleGuideImageUpload = (key) => {
252
- if (key) {
253
- pendingUploadKeyRef.current = key;
254
- setGuideImages((prev) => [...prev, key]);
255
- }
256
- };
257
- // Handle guide image presigned URL from upload
258
- // Correlate via pendingUploadKeyRef so rapid uploads don't collide on stale index
259
- const handleGuideImagePresignedUrl = (presignedUrl) => {
260
- const key = pendingUploadKeyRef.current;
261
- if (presignedUrl && key) {
262
- setGuideImagePreviewUrls((prev) => ({ ...prev, [key]: presignedUrl }));
263
- pendingUploadKeyRef.current = null;
264
- }
265
- };
266
- // Remove guide image
267
- const removeGuideImage = (index) => {
268
- const removedKey = guideImages[index];
269
- setGuideImages((prev) => prev.filter((_, i) => i !== index));
270
- if (removedKey) {
271
- setGuideImagePreviewUrls((prev) => {
272
- if (!(removedKey in prev))
273
- return prev;
274
- const { [removedKey]: _removed, ...rest } = prev;
275
- return rest;
276
- });
277
- }
278
- };
279
- const errors = getErrors();
280
- // If in client mode, render the Client component
281
- if (isClientMode) {
282
- const questionData = {
283
- informations,
284
- minWords,
285
- maxWords,
286
- instructions: {
287
- mainInstruction: instruction || '',
288
- },
289
- guideImages: guideImages.length > 0 ? guideImages : undefined,
290
- };
291
- return (_jsxs("div", { className: "space-y-6", children: [_jsx(Card, { children: _jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx(CardTitle, { children: "Write a Short Letter (Preview)" }), _jsx("p", { className: "mt-1 text-sm text-gray-500", children: "Ch\u1EBF \u0111\u1ED9 xem tr\u01B0\u1EDBc - Hi\u1EC3n th\u1ECB \u0111\u00E1p \u00E1n \u0111\u00FAng" })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(false), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), "Quay l\u1EA1i Edit Mode"] })] }) }) }), _jsx(WriteAShortLetterClient, { questionData: questionData, onSubmit: () => { }, isReviewMode: true })] }));
292
- }
293
- return (_jsxs("div", { className: "space-y-6", children: [externalErrors && externalErrors.length > 0 && (_jsx("div", { className: "rounded-lg border border-red-200 bg-red-50 p-4", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex-shrink-0", children: _jsx("svg", { className: "h-5 w-5 text-red-600", fill: "currentColor", viewBox: "0 0 20 20", children: _jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z", clipRule: "evenodd" }) }) }), _jsxs("div", { className: "flex-1", children: [_jsx("h3", { className: "text-sm font-medium text-red-800", children: "Vui l\u00F2ng s\u1EEDa c\u00E1c l\u1ED7i sau:" }), _jsx("ul", { className: "mt-2 list-disc list-inside space-y-1", children: externalErrors.map((error, index) => (_jsx("li", { className: "text-sm text-red-700", children: error }, index))) })] })] }) })), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50", children: [_jsx("div", { className: "h-1.5 bg-gradient-to-r from-pink-500 via-rose-500 to-orange-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-pink-500 to-rose-600 shadow-md", children: _jsx(Mail, { className: "h-5 w-5 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-xl font-bold text-gray-900", children: "Write a Short Letter" }), _jsx("p", { className: "text-sm text-gray-500", children: "T\u1EA1o b\u00E0i t\u1EADp vi\u1EBFt th\u01B0 v\u1EDBi c\u00E1c th\u00F4ng tin c\u1EA7n bao g\u1ED3m" })] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(true), disabled: !minWords || minWords < 1, className: "gap-2 border-rose-200 text-rose-600 hover:bg-rose-50 hover:text-rose-700", children: [_jsx(Eye, { className: "h-4 w-4" }), "Xem tr\u01B0\u1EDBc"] })] }) }), _jsxs(CardContent, { className: "space-y-6", children: [_jsx("div", { className: "rounded-xl border border-pink-200 bg-gradient-to-r from-pink-50 to-rose-50 p-5 shadow-sm", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-pink-100", children: _jsx(Lightbulb, { className: "h-4 w-4 text-pink-600" }) }), _jsxs("div", { className: "text-sm text-pink-900", children: [_jsx("p", { className: "font-semibold text-pink-800", children: "H\u01B0\u1EDBng d\u1EABn t\u1EA1o c\u00E2u h\u1ECFi" }), _jsxs("ul", { className: "mt-2 space-y-1.5 text-pink-700", children: [_jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-pink-400" }), "Nh\u1EADp h\u01B0\u1EDBng d\u1EABn vi\u1EBFt th\u01B0 cho h\u1ECDc sinh"] }), _jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-pink-400" }), "Li\u1EC7t k\u00EA c\u00E1c th\u00F4ng tin c\u1EA7n bao g\u1ED3m trong b\u00E0i vi\u1EBFt"] }), _jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-pink-400" }), "Thi\u1EBFt l\u1EADp gi\u1EDBi h\u1EA1n s\u1ED1 t\u1EEB t\u1ED1i thi\u1EC3u/t\u1ED1i \u0111a"] })] })] })] }) }), _jsxs("div", { className: "grid grid-cols-1 gap-6 md:grid-cols-2", children: [_jsx("div", { children: _jsx(PointsInput, { defaultValue: points, onChange: (e) => setPoints(Number(e.target.value) || 1), error: errors.points }) }), _jsxs("div", { className: "rounded-lg border border-gray-200 bg-gray-50/50 p-4", children: [_jsx(Label, { className: "mb-3 block text-sm font-medium text-gray-700", children: "Gi\u1EDBi h\u1EA1n s\u1ED1 t\u1EEB" }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "flex-1", children: [_jsxs(Label, { htmlFor: "minWords", className: "text-xs text-gray-500", children: ["T\u1ED1i thi\u1EC3u ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Input, { id: "minWords", type: "number", min: "1", step: "1", value: minWords ?? '', onChange: (e) => {
294
- const value = e.target.value;
295
- if (value === '') {
296
- setMinWords(1);
297
- }
298
- else {
299
- const numValue = parseInt(value, 10);
300
- setMinWords(numValue < 1 ? 1 : numValue);
301
- }
302
- form.clearErrors('minWords');
303
- }, placeholder: "20", className: `mt-1 ${errors.minWords ? 'border-red-500 focus:ring-red-500' : ''}` }), errors.minWords && (_jsx("p", { className: "mt-1 text-xs text-red-600", children: errors.minWords }))] }), _jsx("span", { className: "mt-6 text-gray-400", children: "\u2014" }), _jsxs("div", { className: "flex-1", children: [_jsx(Label, { htmlFor: "maxWords", className: "text-xs text-gray-500", children: "T\u1ED1i \u0111a" }), _jsx(Input, { id: "maxWords", type: "number", min: "1", step: "1", value: maxWords ?? '', onChange: (e) => {
304
- const value = e.target.value;
305
- if (value === '') {
306
- setMaxWords(undefined);
307
- }
308
- else {
309
- const numValue = parseInt(value, 10);
310
- setMaxWords(numValue < 1 ? 1 : numValue);
311
- }
312
- form.clearErrors('maxWords');
313
- }, placeholder: "\u221E", className: `mt-1 ${errors.maxWords ? 'border-red-500 focus:ring-red-500' : ''}` }), errors.maxWords && (_jsx("p", { className: "mt-1 text-xs text-red-600", children: errors.maxWords }))] })] })] })] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: "instruction", className: "text-sm font-medium", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx(Textarea, { id: "instruction", value: instruction, onChange: (e) => {
314
- setInstruction(e.target.value);
315
- form.clearErrors('instruction');
316
- }, placeholder: "V\u00ED d\u1EE5: You are having a birthday party. Write a short letter to your friend, Minh, to invite him. Tell him about the party.", className: `mt-1.5 resize-none ${errors.instruction ? 'border-red-500' : ''}`, rows: 3 }), errors.instruction && (_jsx("p", { className: "mt-1 text-sm text-red-600", children: errors.instruction }))] }), _jsxs("div", { className: "space-y-3 rounded-lg border border-blue-100 bg-blue-50/30 p-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-full bg-blue-100", children: _jsx("span", { className: "text-xs font-semibold text-blue-600", children: "i" }) }), _jsx(Label, { className: "text-sm font-semibold text-gray-800", children: "Th\u00F4ng tin c\u1EA7n bao g\u1ED3m" })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addInformation, className: "h-8 gap-1.5 border-blue-200 bg-white text-blue-600 hover:bg-blue-50 hover:text-blue-700", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm"] })] }), errors.informations && (_jsx("div", { className: "rounded-md border border-red-200 bg-red-50 p-3", children: _jsx("p", { className: "text-sm text-red-600", children: errors.informations }) })), _jsx("div", { className: "space-y-2", children: informations.map((info, index) => (_jsx("div", { className: "space-y-1", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 text-xs font-medium text-blue-600", children: index + 1 }), _jsx(Input, { value: info, onChange: (e) => updateInformation(index, e.target.value), placeholder: `Thông tin ${index + 1}...`, className: "flex-1 border-gray-200" }), informations.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeInformation(index), className: "h-8 w-8 flex-shrink-0 p-0 text-gray-400 hover:bg-red-50 hover:text-red-500", children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }) }, index))) }), _jsx("p", { className: "text-xs text-gray-500 italic", children: "\uD83D\uDCA1 Li\u1EC7t k\u00EA c\u00E1c th\u00F4ng tin h\u1ECDc sinh c\u1EA7n \u0111\u1EC1 c\u1EADp trong b\u00E0i vi\u1EBFt" })] }), _jsxs("div", { className: "space-y-3 rounded-lg border border-violet-100 bg-violet-50/30 p-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-full bg-violet-100", children: _jsx(ImageIcon, { className: "h-3.5 w-3.5 text-violet-600" }) }), _jsx(Label, { className: "text-sm font-semibold text-gray-800", children: "\u1EA2nh h\u01B0\u1EDBng d\u1EABn" }), _jsx("span", { className: "text-xs text-gray-400" })] }), guideImages.length > 0 && (_jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4", children: guideImages.map((imageKey, index) => (_jsx(GuideImageItem, { imageKey: imageKey, index: index, localPreviewUrl: guideImagePreviewUrls[imageKey], onRemove: removeGuideImage }, `${imageKey}-${index}`))) })), _jsx(FileUpload, { label: "Th\u00EAm \u1EA3nh h\u01B0\u1EDBng d\u1EABn", accept: "image/*", value: "", onChange: handleGuideImageUpload, onPresignedUrlChange: handleGuideImagePresignedUrl, autoUpload: true, prefix: "write-short-letter/guide", placeholder: "Ch\u1ECDn ho\u1EB7c k\u00E9o th\u1EA3 \u1EA3nh v\u00E0o \u0111\u00E2y", icon: _jsx(ImageIcon, { className: "h-4 w-4" }) }), _jsx("p", { className: "text-xs text-gray-500 italic", children: "\uD83D\uDCF7 Upload nhi\u1EC1u \u1EA3nh \u0111\u1EC3 h\u01B0\u1EDBng d\u1EABn h\u1ECDc sinh vi\u1EBFt b\u00E0i (v\u00ED d\u1EE5: m\u1EABu th\u01B0, h\u00ECnh \u1EA3nh minh h\u1ECDa)" })] })] })] })] }));
317
- }
318
- export function WriteAShortLetterCreator(props) {
319
- return _jsx(WriteAShortLetterCreatorContent, { ...props });
320
- }
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useState as t,useRef as s,useEffect as i}from"react";import{Button as n}from"../../../../components/ui/button";import{Label as a}from"../../../../components/ui/label";import{Input as o}from"../../../../components/ui/input";import{Textarea as l}from"../../../../components/ui/textarea";import{PointsInput as c}from"../../../../components/ui/points-input";import{FileUpload as d}from"../../../../components/ui/file-upload";import{Card as m,CardContent as h,CardHeader as u,CardTitle as g}from"../../../../components/ui/card";import{Pencil as p,Eye as f,Plus as x,Trash2 as v,Lightbulb as b,Mail as N,ImageIcon as y,X as w}from"lucide-react";import{WriteAShortLetterClient as W}from"./WriteAShortLetterClient";import{useReactHookForm as k}from"../../../../shared/lib/hooks/useReactHookForm";import{usePresignedFileUrl as C}from"../../../../shared/lib/hooks/usePresignedFileUrl";import{z as S}from"zod";function j({imageKey:t,index:s,localPreviewUrl:i,onRemove:n}){const{previewUrl:a}=C(t),o=i||a||"";return r("div",{className:"group relative overflow-hidden rounded-lg border border-violet-200 bg-white shadow-sm transition-all hover:shadow-md",children:[e("div",{className:"aspect-square w-full overflow-hidden bg-gray-50",children:o?e("img",{src:o,alt:`Ảnh hướng dẫn ${s+1}`,className:"h-full w-full object-cover transition-transform group-hover:scale-105",onError:e=>{e.target.src="/images/placeholder-image.svg"}}):e("div",{className:"flex h-full w-full items-center justify-center",children:e(y,{className:"h-8 w-8 text-gray-300"})})}),e("button",{type:"button",onClick:()=>n(s),className:"absolute right-1.5 top-1.5 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-white opacity-0 shadow-md transition-opacity group-hover:opacity-100 hover:bg-red-600",title:"Xóa ảnh",children:e(w,{className:"h-3.5 w-3.5"})}),e("div",{className:"absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/50 to-transparent p-1.5 opacity-0 transition-opacity group-hover:opacity-100",children:r("span",{className:"text-xs font-medium text-white",children:["Ảnh ",s+1]})})]})}const I=S.object({informations:S.array(S.string()),minWords:S.coerce.number().min(1,"Số từ tối thiểu phải >= 1"),maxWords:S.coerce.number().optional(),instruction:S.string().optional(),points:S.coerce.number().min(1,"Điểm phải >= 1")});function E({initialData:w,onChange:C,externalErrors:S,validationRef:E}){const[L,T]=t(w?.informations||[""]),[V,z]=t(w?.minWords??20),[R,U]=t(w?.maxWords),[P,F]=t(w?.instructions?.mainInstruction||""),[J,M]=t(w?.guideImages||[]),[O,H]=t(w?.points||10),[$,q]=t({}),A=s(null),[B,D]=t(!1),K=k({defaultValues:{informations:L,minWords:V,maxWords:R,instruction:P,points:O},zodSchema:I,mode:"onBlur"}),X=s(w),G=s(!0),Q=s(!1),Y=s(""),Z=s([]);i(()=>{E&&(E.current={triggerValidation:async()=>re()})},[E,L,V,R,P,J,O]),i(()=>{const e=JSON.stringify(w);if(Q.current&&(e===Y.current||Z.current.includes(e)))return void(X.current=w);!(JSON.stringify(X.current)!==e)&&Q.current||(G.current=!0,X.current=w,Q.current=!0,T(w?.informations?.length?w.informations:[""]),z(w?.minWords??20),U(w?.maxWords),F(w?.instructions?.mainInstruction??""),M(w?.guideImages??[]),H(w?.points??10),q({}),setTimeout(()=>{G.current=!1},150))},[w]);const _=s(C);i(()=>{_.current=C},[C]);const ee=s("");i(()=>{if(G.current||!Q.current){const e=JSON.stringify({informations:L,minWords:V,maxWords:R,instruction:P,guideImages:J,points:O});return void(ee.current=e)}if(!P.trim()&&L.every(e=>!e.trim()))return;const e={informations:L,minWords:V,maxWords:R,instructions:{mainInstruction:P||""},guideImages:J.length>0?J:void 0,points:O},r=JSON.stringify(e);ee.current!==r&&(ee.current=r,Y.current=JSON.stringify({informations:L,minWords:V,maxWords:R,instructions:{mainInstruction:P||""},guideImages:J,points:O}),Z.current=[...Z.current,Y.current].slice(-10),_.current?.(e))},[L,V,R,P,J,O]);const re=()=>{K.setValue("informations",L),K.setValue("minWords",V),K.setValue("maxWords",R),K.setValue("instruction",P),K.setValue("points",O);const e=I.safeParse({informations:L,minWords:V,maxWords:R,instruction:P,points:O});return!!e.success||(e.error.issues.forEach(e=>{const r=e.path.join(".");K.setError(r,{message:e.message})}),!1)},te=e=>{const r=J[e];M(r=>r.filter((r,t)=>t!==e)),r&&q(e=>{if(!(r in e))return e;const{[r]:t,...s}=e;return s})},se=(()=>{const e={},r=K.formState.errors.informations;r&&"string"==typeof r.message&&(e.informations=r.message);const t=K.formState.errors.minWords;t&&"string"==typeof t.message&&(e.minWords=t.message);const s=K.formState.errors.maxWords;s&&"string"==typeof s.message&&(e.maxWords=s.message);const i=K.formState.errors.instruction;i&&"string"==typeof i.message&&(e.instruction=i.message);const n=K.formState.errors.points;return n&&"string"==typeof n.message&&(e.points=n.message),e})();if(B){const t={informations:L,minWords:V,maxWords:R,instructions:{mainInstruction:P||""},guideImages:J.length>0?J:void 0};return r("div",{className:"space-y-6",children:[e(m,{children:e(u,{children:r("div",{className:"flex items-center justify-between",children:[r("div",{children:[e(g,{children:"Write a Short Letter (Preview)"}),e("p",{className:"mt-1 text-sm text-gray-500",children:"Chế độ xem trước - Hiển thị đáp án đúng"})]}),r(n,{variant:"outline",size:"sm",onClick:()=>D(!1),children:[e(p,{className:"mr-2 h-4 w-4"}),"Quay lại Edit Mode"]})]})})}),e(W,{questionData:t,onSubmit:()=>{},isReviewMode:!0})]})}return r("div",{className:"space-y-6",children:[S&&S.length>0&&e("div",{className:"rounded-lg border border-red-200 bg-red-50 p-4",children:r("div",{className:"flex items-start gap-3",children:[e("div",{className:"flex-shrink-0",children:e("svg",{className:"h-5 w-5 text-red-600",fill:"currentColor",viewBox:"0 0 20 20",children:e("path",{fillRule:"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z",clipRule:"evenodd"})})}),r("div",{className:"flex-1",children:[e("h3",{className:"text-sm font-medium text-red-800",children:"Vui lòng sửa các lỗi sau:"}),e("ul",{className:"mt-2 list-disc list-inside space-y-1",children:S.map((r,t)=>e("li",{className:"text-sm text-red-700",children:r},t))})]})]})}),r(m,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50",children:[e("div",{className:"h-1.5 bg-gradient-to-r from-pink-500 via-rose-500 to-orange-500"}),e(u,{className:"pb-4",children:r("div",{className:"flex items-center justify-between",children:[r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-pink-500 to-rose-600 shadow-md",children:e(N,{className:"h-5 w-5 text-white"})}),r("div",{children:[e(g,{className:"text-xl font-bold text-gray-900",children:"Write a Short Letter"}),e("p",{className:"text-sm text-gray-500",children:"Tạo bài tập viết thư với các thông tin cần bao gồm"})]})]}),r(n,{variant:"outline",size:"sm",onClick:()=>D(!0),disabled:!V||V<1,className:"gap-2 border-rose-200 text-rose-600 hover:bg-rose-50 hover:text-rose-700",children:[e(f,{className:"h-4 w-4"}),"Xem trước"]})]})}),r(h,{className:"space-y-6",children:[e("div",{className:"rounded-xl border border-pink-200 bg-gradient-to-r from-pink-50 to-rose-50 p-5 shadow-sm",children:r("div",{className:"flex items-start gap-3",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-pink-100",children:e(b,{className:"h-4 w-4 text-pink-600"})}),r("div",{className:"text-sm text-pink-900",children:[e("p",{className:"font-semibold text-pink-800",children:"Hướng dẫn tạo câu hỏi"}),r("ul",{className:"mt-2 space-y-1.5 text-pink-700",children:[r("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-pink-400"}),"Nhập hướng dẫn viết thư cho học sinh"]}),r("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-pink-400"}),"Liệt kê các thông tin cần bao gồm trong bài viết"]}),r("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-pink-400"}),"Thiết lập giới hạn số từ tối thiểu/tối đa"]})]})]})]})}),r("div",{className:"grid grid-cols-1 gap-6 md:grid-cols-2",children:[e("div",{children:e(c,{defaultValue:O,onChange:e=>H(Number(e.target.value)||1),error:se.points})}),r("div",{className:"rounded-lg border border-gray-200 bg-gray-50/50 p-4",children:[e(a,{className:"mb-3 block text-sm font-medium text-gray-700",children:"Giới hạn số từ"}),r("div",{className:"flex items-center gap-4",children:[r("div",{className:"flex-1",children:[r(a,{htmlFor:"minWords",className:"text-xs text-gray-500",children:["Tối thiểu ",e("span",{className:"text-red-500",children:"*"})]}),e(o,{id:"minWords",type:"number",min:"1",step:"1",value:V??"",onChange:e=>{const r=e.target.value;if(""===r)z(1);else{const e=parseInt(r,10);z(e<1?1:e)}K.clearErrors("minWords")},placeholder:"20",className:"mt-1 "+(se.minWords?"border-red-500 focus:ring-red-500":"")}),se.minWords&&e("p",{className:"mt-1 text-xs text-red-600",children:se.minWords})]}),e("span",{className:"mt-6 text-gray-400",children:"—"}),r("div",{className:"flex-1",children:[e(a,{htmlFor:"maxWords",className:"text-xs text-gray-500",children:"Tối đa"}),e(o,{id:"maxWords",type:"number",min:"1",step:"1",value:R??"",onChange:e=>{const r=e.target.value;if(""===r)U(void 0);else{const e=parseInt(r,10);U(e<1?1:e)}K.clearErrors("maxWords")},placeholder:"∞",className:"mt-1 "+(se.maxWords?"border-red-500 focus:ring-red-500":"")}),se.maxWords&&e("p",{className:"mt-1 text-xs text-red-600",children:se.maxWords})]})]})]})]}),r("div",{children:[e(a,{htmlFor:"instruction",className:"text-sm font-medium",children:"Hướng dẫn"}),e(l,{id:"instruction",value:P,onChange:e=>{F(e.target.value),K.clearErrors("instruction")},placeholder:"Ví dụ: You are having a birthday party. Write a short letter to your friend, Minh, to invite him. Tell him about the party.",className:"mt-1.5 resize-none "+(se.instruction?"border-red-500":""),rows:3}),se.instruction&&e("p",{className:"mt-1 text-sm text-red-600",children:se.instruction})]}),r("div",{className:"space-y-3 rounded-lg border border-blue-100 bg-blue-50/30 p-4",children:[r("div",{className:"flex items-center justify-between",children:[r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-full bg-blue-100",children:e("span",{className:"text-xs font-semibold text-blue-600",children:"i"})}),e(a,{className:"text-sm font-semibold text-gray-800",children:"Thông tin cần bao gồm"})]}),r(n,{type:"button",variant:"outline",size:"sm",onClick:()=>{T([...L,""]),K.clearErrors("informations")},className:"h-8 gap-1.5 border-blue-200 bg-white text-blue-600 hover:bg-blue-50 hover:text-blue-700",children:[e(x,{className:"h-3.5 w-3.5"}),"Thêm"]})]}),se.informations&&e("div",{className:"rounded-md border border-red-200 bg-red-50 p-3",children:e("p",{className:"text-sm text-red-600",children:se.informations})}),e("div",{className:"space-y-2",children:L.map((t,s)=>e("div",{className:"space-y-1",children:r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 text-xs font-medium text-blue-600",children:s+1}),e(o,{value:t,onChange:e=>((e,r)=>{const t=[...L];t[e]=r,T(t),K.clearErrors("informations")})(s,e.target.value),placeholder:`Thông tin ${s+1}...`,className:"flex-1 border-gray-200"}),L.length>1&&e(n,{type:"button",variant:"ghost",size:"sm",onClick:()=>(e=>{L.length>1&&T(L.filter((r,t)=>t!==e))})(s),className:"h-8 w-8 flex-shrink-0 p-0 text-gray-400 hover:bg-red-50 hover:text-red-500",children:e(v,{className:"h-4 w-4"})})]})},s))}),e("p",{className:"text-xs text-gray-500 italic",children:"💡 Liệt kê các thông tin học sinh cần đề cập trong bài viết"})]}),r("div",{className:"space-y-3 rounded-lg border border-violet-100 bg-violet-50/30 p-4",children:[r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-full bg-violet-100",children:e(y,{className:"h-3.5 w-3.5 text-violet-600"})}),e(a,{className:"text-sm font-semibold text-gray-800",children:"Ảnh hướng dẫn"}),e("span",{className:"text-xs text-gray-400"})]}),J.length>0&&e("div",{className:"grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4",children:J.map((r,t)=>e(j,{imageKey:r,index:t,localPreviewUrl:$[r],onRemove:te},`${r}-${t}`))}),e(d,{label:"Thêm ảnh hướng dẫn",accept:"image/*",value:"",onChange:e=>{e&&(A.current=e,M(r=>[...r,e]))},onPresignedUrlChange:e=>{const r=A.current;e&&r&&(q(t=>({...t,[r]:e})),A.current=null)},autoUpload:!0,prefix:"write-short-letter/guide",placeholder:"Chọn hoặc kéo thả ảnh vào đây",icon:e(y,{className:"h-4 w-4"})}),e("p",{className:"text-xs text-gray-500 italic",children:"📷 Upload nhiều ảnh để hướng dẫn học sinh viết bài (ví dụ: mẫu thư, hình ảnh minh họa)"})]})]})]})]})}export function WriteAShortLetterCreator(r){return e(E,{...r})}
@@ -1,2 +1 @@
1
- export * from './WriteAShortLetterCreator';
2
- export * from './WriteAShortLetterClient';
1
+ export*from"./WriteAShortLetterCreator";export*from"./WriteAShortLetterClient";
@@ -1,68 +1 @@
1
- import { WriteAShortLetterCreator } from './WriteAShortLetterCreator';
2
- export const writeAShortLetterRegistration = {
3
- component: WriteAShortLetterCreator,
4
- transformInitialData: (initialData) => {
5
- const content = initialData?.content;
6
- const answer = initialData?.answer;
7
- const apiPoints = initialData?.total_points
8
- ? parseFloat(initialData.total_points)
9
- : (initialData?.points || 10); // WRITE_A_SHORT_LETTER defaults to 10 points
10
- let creatorData = { points: apiPoints };
11
- // Priority 1: Data from store (answer has informations directly)
12
- if (answer?.informations && Array.isArray(answer.informations)) {
13
- creatorData = {
14
- informations: answer.informations,
15
- minWords: answer.minWords,
16
- maxWords: answer.maxWords,
17
- instructions: answer.instructions || { mainInstruction: '' },
18
- guideImages: answer.guideImages || [],
19
- points: apiPoints,
20
- };
21
- }
22
- // Priority 2: Data from API (content has informations)
23
- else if (content?.informations && Array.isArray(content.informations)) {
24
- creatorData = {
25
- informations: content.informations,
26
- minWords: content.minWords,
27
- maxWords: content.maxWords,
28
- instructions: content.instructions || { mainInstruction: '' },
29
- guideImages: content.guideImages || [],
30
- points: apiPoints,
31
- };
32
- }
33
- // Priority 3: Legacy format with contexts
34
- else if (content?.contexts || answer?.contexts) {
35
- const contexts = content?.contexts || answer?.contexts || [];
36
- const informations = contexts
37
- .filter((ctx) => ctx.type === 'text' && ctx.text)
38
- .map((ctx) => ctx.text);
39
- creatorData = {
40
- informations: informations.length > 0 ? informations : [],
41
- minWords: content?.minWords ?? answer?.minWords,
42
- maxWords: content?.maxWords ?? answer?.maxWords,
43
- instructions: content?.instructions || answer?.instructions || { mainInstruction: '' },
44
- guideImages: content?.guideImages || answer?.guideImages || [],
45
- points: apiPoints,
46
- };
47
- }
48
- return creatorData;
49
- },
50
- wrapOnSave: (data, ctx) => ({
51
- type: ctx.questionType,
52
- content: ctx.state.content,
53
- points: data?.points ?? ctx.state.points ?? 10,
54
- level: ctx.state.level,
55
- difficulty: ctx.state.difficulty,
56
- skill: ctx.state.skill,
57
- answer: data,
58
- }),
59
- wrapOnChange: (data, ctx) => ({
60
- type: ctx.questionType,
61
- content: ctx.state.content,
62
- points: data?.points ?? ctx.state.points ?? 10,
63
- level: ctx.state.level,
64
- difficulty: ctx.state.difficulty,
65
- skill: ctx.state.skill,
66
- answer: data,
67
- }),
68
- };
1
+ import{WriteAShortLetterCreator as t}from"./WriteAShortLetterCreator";export const writeAShortLetterRegistration={component:t,transformInitialData:t=>{const n=t?.content,s=t?.answer,i=t?.total_points?parseFloat(t.total_points):t?.points||10;let o={points:i};if(s?.informations&&Array.isArray(s.informations))o={informations:s.informations,minWords:s.minWords,maxWords:s.maxWords,instructions:s.instructions||{mainInstruction:""},guideImages:s.guideImages||[],points:i};else if(n?.informations&&Array.isArray(n.informations))o={informations:n.informations,minWords:n.minWords,maxWords:n.maxWords,instructions:n.instructions||{mainInstruction:""},guideImages:n.guideImages||[],points:i};else if(n?.contexts||s?.contexts){const t=(n?.contexts||s?.contexts||[]).filter(t=>"text"===t.type&&t.text).map(t=>t.text);o={informations:t.length>0?t:[],minWords:n?.minWords??s?.minWords,maxWords:n?.maxWords??s?.maxWords,instructions:n?.instructions||s?.instructions||{mainInstruction:""},guideImages:n?.guideImages||s?.guideImages||[],points:i}}return o},wrapOnSave:(t,n)=>({type:n.questionType,content:n.state.content,points:t?.points??n.state.points??10,level:n.state.level,difficulty:n.state.difficulty,skill:n.state.skill,answer:t}),wrapOnChange:(t,n)=>({type:n.questionType,content:n.state.content,points:t?.points??n.state.points??10,level:n.state.level,difficulty:n.state.difficulty,skill:n.state.skill,answer:t})};
@@ -1,25 +1 @@
1
- // WRITE_A_SHORT_LETTER - essay type without correct answer
2
- // Uses simple string[] for informations (different from WRITE_SHORT_PARAGRAPH which uses object[] contexts)
3
- export const transformWriteAShortLetter = (question) => {
4
- const answerData = question.answer;
5
- const informations = Array.isArray(answerData?.informations)
6
- ? answerData.informations.filter((info) => typeof info === 'string' && info.trim() !== '')
7
- : [];
8
- const mainInstruction = answerData?.instructions?.mainInstruction || '';
9
- const guideImages = Array.isArray(answerData?.guideImages)
10
- ? answerData.guideImages.filter((url) => typeof url === 'string' && url.trim() !== '')
11
- : [];
12
- const apiContent = {
13
- informations,
14
- minWords: answerData?.minWords || 20,
15
- maxWords: answerData?.maxWords ?? null,
16
- instructions: { mainInstruction },
17
- };
18
- if (guideImages.length > 0) {
19
- apiContent.guideImages = guideImages;
20
- }
21
- return {
22
- apiContent,
23
- apiCorrectAnswer: {},
24
- };
25
- };
1
+ export const transformWriteAShortLetter=r=>{const t=r.answer,i=Array.isArray(t?.informations)?t.informations.filter(r=>"string"==typeof r&&""!==r.trim()):[],n=t?.instructions?.mainInstruction||"",s=Array.isArray(t?.guideImages)?t.guideImages.filter(r=>"string"==typeof r&&""!==r.trim()):[],o={informations:i,minWords:t?.minWords||20,maxWords:t?.maxWords??null,instructions:{mainInstruction:n}};return s.length>0&&(o.guideImages=s),{apiContent:o,apiCorrectAnswer:{}}};
@@ -1,61 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useEffect } from 'react';
4
- import { Input } from '../../../../components/ui/input';
5
- import { cn } from '../../../../shared/lib/utils';
6
- import { Check, X, FileText, PenLine, AlertTriangle, Lightbulb } from 'lucide-react';
7
- export function WriteCorrectVerbFormClient({ questionData, onSubmit, isReviewMode = false, userAnswer = '', }) {
8
- // ============ STATE ============
9
- const [answer, setAnswer] = useState(userAnswer || '');
10
- // Sync userAnswer when it changes
11
- useEffect(() => {
12
- if (userAnswer !== undefined) {
13
- setAnswer(userAnswer);
14
- }
15
- }, [userAnswer]);
16
- // ============ HANDLERS ============
17
- const handleAnswerChange = (value) => {
18
- if (isReviewMode)
19
- return;
20
- setAnswer(value);
21
- onSubmit?.(value);
22
- };
23
- // Check if answer is correct
24
- const isAnswerCorrect = (userInput) => {
25
- const correctAnswer = questionData.correctAnswer.trim();
26
- const inputAnswer = userInput.trim();
27
- if (questionData.caseSensitive) {
28
- return inputAnswer === correctAnswer;
29
- }
30
- else {
31
- return inputAnswer.toLowerCase() === correctAnswer.toLowerCase();
32
- }
33
- };
34
- const isCorrect = isReviewMode && isAnswerCorrect(answer);
35
- const isWrong = isReviewMode && answer && !isCorrect;
36
- const hasAnswered = answer.trim().length > 0;
37
- // Render sentence with input field at {0} position
38
- const renderSentenceWithInput = () => {
39
- const parts = questionData.sentence.split(/(\{0\})/g);
40
- return (_jsx("div", { className: "flex flex-wrap items-center gap-1.5 text-lg leading-relaxed", children: parts.map((part, index) => {
41
- if (part === '{0}') {
42
- return (_jsxs("span", { className: "inline-flex items-center gap-2", children: [_jsxs("div", { className: "relative inline-flex items-center", children: [_jsx(Input, { value: questionData.correctAnswer, readOnly: true, disabled: true, className: cn('w-36 text-center font-semibold border-2 rounded-lg transition-all', 'border-green-400 bg-green-50 text-green-800 cursor-not-allowed') }), isReviewMode && (_jsx("div", { className: cn('absolute -right-7 top-1/2 -translate-y-1/2 flex h-5 w-5 items-center justify-center rounded-full', isCorrect ? 'bg-green-500' : isWrong ? 'bg-red-500' : 'bg-gray-300'), children: isCorrect ? (_jsx(Check, { className: "h-3 w-3 text-white" })) : isWrong ? (_jsx(X, { className: "h-3 w-3 text-white" })) : null }))] }), _jsxs("span", { className: "rounded-lg bg-amber-100 px-2 py-0.5 text-sm font-medium text-amber-700", children: ["(", questionData.verbHint, ")"] })] }, index));
43
- }
44
- else {
45
- return _jsx("span", { className: "text-gray-800", children: part }, index);
46
- }
47
- }) }));
48
- };
49
- // ============ RENDER ============
50
- return (_jsxs("div", { className: "space-y-5", children: [questionData.instruction && (_jsx("div", { className: "overflow-hidden rounded-xl border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 shadow-sm", children: _jsxs("div", { className: "flex items-center gap-3 px-5 py-4", children: [_jsx("div", { className: "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-blue-500 to-indigo-600 text-white shadow-sm", children: _jsx(FileText, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold text-blue-900", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx("p", { className: "text-sm text-blue-700", children: questionData.instruction })] })] }) })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: cn('flex items-center justify-between border-b px-5 py-3', isReviewMode && isCorrect && 'border-green-200 bg-gradient-to-r from-green-50 to-emerald-50', isReviewMode && isWrong && 'border-red-200 bg-gradient-to-r from-red-50 to-rose-50', (!isReviewMode || (!isCorrect && !isWrong)) && 'border-gray-100 bg-gradient-to-r from-gray-50 to-white'), children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: cn('flex h-8 w-8 items-center justify-center rounded-lg text-white shadow-sm', isReviewMode && isCorrect && 'bg-gradient-to-br from-green-500 to-emerald-600', isReviewMode && isWrong && 'bg-gradient-to-br from-red-500 to-rose-600', (!isReviewMode || (!isCorrect && !isWrong)) && 'bg-gradient-to-br from-violet-500 to-purple-600'), children: _jsx(PenLine, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Chia \u0111\u1ED9ng t\u1EEB \u0111\u00FAng" })] }), isReviewMode && hasAnswered && (_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-6 space-y-5", children: [_jsx("div", { className: "rounded-xl bg-gradient-to-br from-slate-50 to-gray-50 border border-gray-100 p-5", children: renderSentenceWithInput() }), isReviewMode && (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: cn('rounded-xl border-2 p-4', isCorrect
51
- ? 'border-green-200 bg-green-50'
52
- : isWrong
53
- ? 'border-red-200 bg-red-50'
54
- : 'border-gray-200 bg-gray-50'), children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-lg', isCorrect ? 'bg-green-200' : isWrong ? 'bg-red-200' : 'bg-gray-200'), children: isCorrect ? (_jsx(Check, { className: "h-3.5 w-3.5 text-green-700" })) : isWrong ? (_jsx(X, { className: "h-3.5 w-3.5 text-red-700" })) : (_jsx("span", { className: "text-xs text-gray-500", children: "?" })) }), _jsx("span", { className: "text-xs font-medium text-gray-600", children: "C\u00E2u tr\u1EA3 l\u1EDDi c\u1EE7a b\u1EA1n" })] }), _jsx("p", { className: cn('text-lg font-bold', isCorrect ? 'text-green-700' : isWrong ? 'text-red-700' : 'text-gray-400'), children: answer || '(Chưa trả lời)' })] }), _jsxs("div", { className: "rounded-xl border-2 border-green-200 bg-gradient-to-br from-green-50 to-emerald-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-lg bg-green-200", children: _jsx(Check, { className: "h-3.5 w-3.5 text-green-700" }) }), _jsx("span", { className: "text-xs font-medium text-gray-600", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" })] }), _jsx("p", { className: "text-lg font-bold text-green-700", children: questionData.correctAnswer })] })] }), _jsx("div", { className: cn('rounded-xl p-4', isCorrect
55
- ? 'bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200'
56
- : 'bg-gradient-to-r from-red-50 to-rose-50 border border-red-200'), children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: cn('flex h-10 w-10 items-center justify-center rounded-xl text-white shadow-sm', isCorrect
57
- ? 'bg-gradient-to-br from-green-500 to-emerald-600'
58
- : 'bg-gradient-to-br from-red-500 to-rose-600'), children: isCorrect ? _jsx(Check, { className: "h-5 w-5" }) : _jsx(X, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("p", { className: cn('text-sm font-semibold', isCorrect ? 'text-green-800' : 'text-red-800'), children: isCorrect ? 'Chính xác!' : 'Chưa đúng' }), _jsx("p", { className: cn('text-xs', isCorrect ? 'text-green-600' : 'text-red-600'), children: isCorrect
59
- ? 'Bạn đã chia động từ đúng.'
60
- : `Đáp án đúng là "${questionData.correctAnswer}".` })] })] }) }), questionData.explanation && (_jsx("div", { className: "rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-lg bg-blue-500 text-white", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("span", { className: "text-xs font-semibold text-blue-700 uppercase tracking-wider", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-gray-700 leading-relaxed", children: questionData.explanation })] })] }) }))] })), !isReviewMode && !hasAnswered && (_jsx("div", { className: "rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-amber-400 to-orange-500 text-white shadow-sm", children: _jsx("span", { className: "text-sm font-bold", children: "!" }) }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-amber-800", children: "Nh\u1EADp d\u1EA1ng \u0111\u00FAng c\u1EE7a \u0111\u1ED9ng t\u1EEB" }), _jsx("p", { className: "text-xs text-amber-600", children: "S\u1EED d\u1EE5ng g\u1EE3i \u00FD trong ngo\u1EB7c \u0111\u1EC3 chia \u0111\u1ED9ng t\u1EEB ph\u00F9 h\u1EE3p." })] })] }) }))] })] }), !isReviewMode && questionData.caseSensitive && (_jsx("div", { className: "overflow-hidden rounded-xl border border-yellow-200 bg-gradient-to-r from-yellow-50 to-amber-50 shadow-sm", children: _jsxs("div", { className: "flex items-center gap-3 px-5 py-4", children: [_jsx("div", { className: "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-yellow-400 to-amber-500 text-white shadow-sm", children: _jsx(AlertTriangle, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold text-yellow-900", children: "L\u01B0u \u00FD quan tr\u1ECDng" }), _jsx("p", { className: "text-sm text-yellow-700", children: "\u0110\u00E1p \u00E1n c\u00F3 ph\u00E2n bi\u1EC7t ch\u1EEF hoa/th\u01B0\u1EDDng" })] })] }) }))] }));
61
- }
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useState as t,useEffect as s}from"react";import{Input as n}from"../../../../components/ui/input";import{cn as a}from"../../../../shared/lib/utils";import{Check as d,X as l,FileText as i,PenLine as o,AlertTriangle as c,Lightbulb as m}from"lucide-react";export function WriteCorrectVerbFormClient({questionData:g,onSubmit:h,isReviewMode:x=!1,userAnswer:b=""}){const[f,p]=t(b||"");s(()=>{void 0!==b&&p(b)},[b]);const u=x&&(e=>{const r=g.correctAnswer.trim(),t=e.trim();return g.caseSensitive?t===r:t.toLowerCase()===r.toLowerCase()})(f),N=x&&f&&!u,w=f.trim().length>0;return r("div",{className:"space-y-5",children:[g.instruction&&e("div",{className:"overflow-hidden rounded-xl border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 shadow-sm",children:r("div",{className:"flex items-center gap-3 px-5 py-4",children:[e("div",{className:"flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-blue-500 to-indigo-600 text-white shadow-sm",children:e(i,{className:"h-5 w-5"})}),r("div",{children:[e("h3",{className:"text-sm font-semibold text-blue-900",children:"Hướng dẫn"}),e("p",{className:"text-sm text-blue-700",children:g.instruction})]})]})}),r("div",{className:"overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm",children:[r("div",{className:a("flex items-center justify-between border-b px-5 py-3",x&&u&&"border-green-200 bg-gradient-to-r from-green-50 to-emerald-50",x&&N&&"border-red-200 bg-gradient-to-r from-red-50 to-rose-50",(!x||!u&&!N)&&"border-gray-100 bg-gradient-to-r from-gray-50 to-white"),children:[r("div",{className:"flex items-center gap-3",children:[e("div",{className:a("flex h-8 w-8 items-center justify-center rounded-lg text-white shadow-sm",x&&u&&"bg-gradient-to-br from-green-500 to-emerald-600",x&&N&&"bg-gradient-to-br from-red-500 to-rose-600",(!x||!u&&!N)&&"bg-gradient-to-br from-violet-500 to-purple-600"),children:e(o,{className:"h-4 w-4"})}),e("span",{className:"text-sm font-medium text-gray-700",children:"Chia động từ đúng"})]}),x&&w&&r("div",{className:a("flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold",u?"bg-green-100 text-green-700":"bg-red-100 text-red-700"),children:[e(u?d:l,{className:"h-3.5 w-3.5"}),u?"Đúng":"Sai"]})]}),r("div",{className:"p-6 space-y-5",children:[e("div",{className:"rounded-xl bg-gradient-to-br from-slate-50 to-gray-50 border border-gray-100 p-5",children:(()=>{const t=g.sentence.split(/(\{0\})/g);return e("div",{className:"flex flex-wrap items-center gap-1.5 text-lg leading-relaxed",children:t.map((t,s)=>"{0}"===t?r("span",{className:"inline-flex items-center gap-2",children:[r("div",{className:"relative inline-flex items-center",children:[e(n,{value:g.correctAnswer,readOnly:!0,disabled:!0,className:a("w-36 text-center font-semibold border-2 rounded-lg transition-all","border-green-400 bg-green-50 text-green-800 cursor-not-allowed")}),x&&e("div",{className:a("absolute -right-7 top-1/2 -translate-y-1/2 flex h-5 w-5 items-center justify-center rounded-full",u?"bg-green-500":N?"bg-red-500":"bg-gray-300"),children:u?e(d,{className:"h-3 w-3 text-white"}):N?e(l,{className:"h-3 w-3 text-white"}):null})]}),r("span",{className:"rounded-lg bg-amber-100 px-2 py-0.5 text-sm font-medium text-amber-700",children:["(",g.verbHint,")"]})]},s):e("span",{className:"text-gray-800",children:t},s))})})()}),x&&r("div",{className:"space-y-3",children:[r("div",{className:"grid grid-cols-2 gap-4",children:[r("div",{className:a("rounded-xl border-2 p-4",u?"border-green-200 bg-green-50":N?"border-red-200 bg-red-50":"border-gray-200 bg-gray-50"),children:[r("div",{className:"flex items-center gap-2 mb-2",children:[e("div",{className:a("flex h-6 w-6 items-center justify-center rounded-lg",u?"bg-green-200":N?"bg-red-200":"bg-gray-200"),children:u?e(d,{className:"h-3.5 w-3.5 text-green-700"}):N?e(l,{className:"h-3.5 w-3.5 text-red-700"}):e("span",{className:"text-xs text-gray-500",children:"?"})}),e("span",{className:"text-xs font-medium text-gray-600",children:"Câu trả lời của bạn"})]}),e("p",{className:a("text-lg font-bold",u?"text-green-700":N?"text-red-700":"text-gray-400"),children:f||"(Chưa trả lời)"})]}),r("div",{className:"rounded-xl border-2 border-green-200 bg-gradient-to-br from-green-50 to-emerald-50 p-4",children:[r("div",{className:"flex items-center gap-2 mb-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-lg bg-green-200",children:e(d,{className:"h-3.5 w-3.5 text-green-700"})}),e("span",{className:"text-xs font-medium text-gray-600",children:"Đáp án đúng"})]}),e("p",{className:"text-lg font-bold text-green-700",children:g.correctAnswer})]})]}),e("div",{className:a("rounded-xl p-4",u?"bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200":"bg-gradient-to-r from-red-50 to-rose-50 border border-red-200"),children:r("div",{className:"flex items-center gap-3",children:[e("div",{className:a("flex h-10 w-10 items-center justify-center rounded-xl text-white shadow-sm",u?"bg-gradient-to-br from-green-500 to-emerald-600":"bg-gradient-to-br from-red-500 to-rose-600"),children:e(u?d:l,{className:"h-5 w-5"})}),r("div",{children:[e("p",{className:a("text-sm font-semibold",u?"text-green-800":"text-red-800"),children:u?"Chính xác!":"Chưa đúng"}),e("p",{className:a("text-xs",u?"text-green-600":"text-red-600"),children:u?"Bạn đã chia động từ đúng.":`Đáp án đúng là "${g.correctAnswer}".`})]})]})}),g.explanation&&e("div",{className:"rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200",children:r("div",{className:"flex items-start gap-3",children:[e("div",{className:"flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-lg bg-blue-500 text-white",children:e(m,{className:"h-3.5 w-3.5"})}),r("div",{className:"flex-1",children:[e("span",{className:"text-xs font-semibold text-blue-700 uppercase tracking-wider",children:"Giải thích"}),e("p",{className:"mt-1 text-sm text-gray-700 leading-relaxed",children:g.explanation})]})]})})]}),!x&&!w&&e("div",{className:"rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4",children:r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-amber-400 to-orange-500 text-white shadow-sm",children:e("span",{className:"text-sm font-bold",children:"!"})}),r("div",{children:[e("p",{className:"text-sm font-medium text-amber-800",children:"Nhập dạng đúng của động từ"}),e("p",{className:"text-xs text-amber-600",children:"Sử dụng gợi ý trong ngoặc để chia động từ phù hợp."})]})]})})]})]}),!x&&g.caseSensitive&&e("div",{className:"overflow-hidden rounded-xl border border-yellow-200 bg-gradient-to-r from-yellow-50 to-amber-50 shadow-sm",children:r("div",{className:"flex items-center gap-3 px-5 py-4",children:[e("div",{className:"flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-yellow-400 to-amber-500 text-white shadow-sm",children:e(c,{className:"h-5 w-5"})}),r("div",{children:[e("h3",{className:"text-sm font-semibold text-yellow-900",children:"Lưu ý quan trọng"}),e("p",{className:"text-sm text-yellow-700",children:"Đáp án có phân biệt chữ hoa/thường"})]})]})})]})}