@tinyweb_dev/oe-exam-sdk 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (485) hide show
  1. package/dist/api/exam-create.js +1 -179
  2. package/dist/api/exam-create.types.js +1 -1
  3. package/dist/api/exam-questions.js +1 -98
  4. package/dist/api/exam-questions.types.js +1 -1
  5. package/dist/api/exam-taking.js +1 -73
  6. package/dist/api/exam-taking.types.js +1 -1
  7. package/dist/api/index.js +1 -6
  8. package/dist/api/result-review.js +1 -50
  9. package/dist/api/result-review.types.js +1 -1
  10. package/dist/components/CueCardEditor.js +1 -58
  11. package/dist/components/common/ResolvedImage.js +1 -15
  12. package/dist/components/exams/CreateExamInfoForm.js +1 -35
  13. package/dist/components/exams/CreateExamPageContainer.js +1 -103
  14. package/dist/components/exams/ExamCreator.js +1 -89
  15. package/dist/components/exams/ExamPartTabs.js +1 -40
  16. package/dist/components/exams/ExamQuestionGrid.js +1 -18
  17. package/dist/components/exams/ExamQuestionsPageContainer.js +1 -90
  18. package/dist/components/exams/PartAudioUploader.js +1 -9
  19. package/dist/components/exams/TemplateSelector.js +1 -66
  20. package/dist/components/exams/api.js +1 -3
  21. package/dist/components/exams/create-exam.validation.js +1 -18
  22. package/dist/components/exams/exam-template.utils.js +1 -18
  23. package/dist/components/exams/index.js +1 -10
  24. package/dist/components/exams/take/ExamTakingPageContainer.js +1 -153
  25. package/dist/components/exams/take/GenericQuestionRenderer.js +1 -14
  26. package/dist/components/exams/take/exam-taking.utils.js +1 -88
  27. package/dist/components/exams/take/hooks/useSpeakingRecorder.js +1 -24
  28. package/dist/components/exams/take/index.js +1 -2
  29. package/dist/components/exams/take/types.js +1 -1
  30. package/dist/components/index.js +1 -42
  31. package/dist/components/questions/_shared/config/question-types.config.js +1 -333
  32. package/dist/components/questions/_shared/constants/color-palette.js +1 -21
  33. package/dist/components/questions/_shared/hooks/useAnswerGroupQuestionGroup.js +1 -105
  34. package/dist/components/questions/_shared/hooks/useBasicQuestionGroup.js +1 -222
  35. package/dist/components/questions/_shared/hooks/useCompareImagesGroup.js +1 -140
  36. package/dist/components/questions/_shared/hooks/useImageQuestionGroup.js +1 -123
  37. package/dist/components/questions/_shared/hooks/useInfoExchangeGroup.js +1 -50
  38. package/dist/components/questions/_shared/hooks/useQuestionFormState.js +1 -54
  39. package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +1 -123
  40. package/dist/components/questions/_shared/hooks/useQuestionListGroup.js +1 -121
  41. package/dist/components/questions/_shared/hooks/useQuestionOnChange.js +1 -70
  42. package/dist/components/questions/_shared/hooks/useSceneQuestionGroup.js +1 -194
  43. package/dist/components/questions/_shared/index.js +1 -7
  44. package/dist/components/questions/_shared/types/answer-the-question.type.js +1 -1
  45. package/dist/components/questions/_shared/types/arrange-letters-into-words.type.js +1 -1
  46. package/dist/components/questions/_shared/types/choose-correct-adjective.type.js +1 -3
  47. package/dist/components/questions/_shared/types/color-region.config.js +1 -1
  48. package/dist/components/questions/_shared/types/fill-in-blank.type.js +1 -1
  49. package/dist/components/questions/_shared/types/fill-missing-words-in-grid.type.js +1 -1
  50. package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +1 -1
  51. package/dist/components/questions/_shared/types/index.js +1 -12
  52. package/dist/components/questions/_shared/types/label-the-picture.type.js +1 -1
  53. package/dist/components/questions/_shared/types/listen-and-choose-from-answer-group.type.js +1 -1
  54. package/dist/components/questions/_shared/types/listen-and-choose-objects-in-scene.type.js +1 -1
  55. package/dist/components/questions/_shared/types/listen-and-drag-objects-into-scene.type.js +1 -1
  56. package/dist/components/questions/_shared/types/listen-and-speak-answer.type.js +1 -1
  57. package/dist/components/questions/_shared/types/listen-and-speak-compare-images.type.js +1 -1
  58. package/dist/components/questions/_shared/types/listen-and-speak-image-group.type.js +1 -1
  59. package/dist/components/questions/_shared/types/listen-and-speak-info-exchange.type.js +1 -1
  60. package/dist/components/questions/_shared/types/listen-and-speak-odd-one-out.type.js +1 -3
  61. package/dist/components/questions/_shared/types/listen-and-speak-question-list.type.js +1 -1
  62. package/dist/components/questions/_shared/types/listen-and-speak-with-story-images.type.js +1 -3
  63. package/dist/components/questions/_shared/types/listen-and-tick-answer.type.js +1 -1
  64. package/dist/components/questions/_shared/types/listening-drawline.type.js +1 -1
  65. package/dist/components/questions/_shared/types/look-and-choose-answer.type.js +1 -1
  66. package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +1 -1
  67. package/dist/components/questions/_shared/types/match-by-writing-answer.type.js +1 -1
  68. package/dist/components/questions/_shared/types/multiple-choice.type.js +1 -1
  69. package/dist/components/questions/_shared/types/question-group.type.js +1 -11
  70. package/dist/components/questions/_shared/types/read-and-color-objects.type.js +1 -1
  71. package/dist/components/questions/_shared/types/read-displayed-content.type.js +1 -1
  72. package/dist/components/questions/_shared/types/read-passage-and-answer-questions.type.js +1 -1
  73. package/dist/components/questions/_shared/types/speaking-describe-image.type.js +1 -1
  74. package/dist/components/questions/_shared/types/spontaneous-qa.type.js +1 -1
  75. package/dist/components/questions/_shared/types/word-fill-paragraph-with-options.type.js +1 -1
  76. package/dist/components/questions/_shared/types/word-fill-paragraph.type.js +1 -1
  77. package/dist/components/questions/_shared/types/word-fill-structured-form.type.js +1 -1
  78. package/dist/components/questions/_shared/types/word-ordering.type.js +1 -1
  79. package/dist/components/questions/_shared/types/write-a-short-letter.type.js +1 -1
  80. package/dist/components/questions/_shared/types/write-correct-verb-form.type.js +1 -3
  81. package/dist/components/questions/_shared/types/write-sentences.type.js +1 -1
  82. package/dist/components/questions/_shared/types/write-short-paragraph.type.js +1 -1
  83. package/dist/components/questions/_shared/utils/answer-builder.js +1 -79
  84. package/dist/components/questions/_shared/utils/createColorRegionRegistration.js +1 -39
  85. package/dist/components/questions/_shared/utils/id-generator.js +1 -3
  86. package/dist/components/questions/_shared/utils/question-validation.js +1 -21
  87. package/dist/components/questions/components/ColorNode.js +1 -85
  88. package/dist/components/questions/components/ColorRegionClient.js +1 -545
  89. package/dist/components/questions/components/ColorRegionCreator.js +1 -420
  90. package/dist/components/questions/components/ColorRegionNode.js +1 -59
  91. package/dist/components/questions/components/CreatorGuide.js +1 -103
  92. package/dist/components/questions/components/NameNode.js +1 -26
  93. package/dist/components/questions/components/QuestionBankOption.js +1 -46
  94. package/dist/components/questions/components/QuestionBasicInfoForm.js +1 -9
  95. package/dist/components/questions/components/QuestionCommonFields.js +1 -33
  96. package/dist/components/questions/components/QuestionSearchDropdown.js +1 -258
  97. package/dist/components/questions/components/QuestionTypeSelector.js +1 -32
  98. package/dist/components/questions/components/QuestionTypeSpecificForms.js +1 -6
  99. package/dist/components/questions/components/RegionNode.js +1 -94
  100. package/dist/components/questions/components/index.js +1 -14
  101. package/dist/components/questions/creator/QuestionCreator.js +1 -90
  102. package/dist/components/questions/creator/QuestionGroupCreator.js +1 -119
  103. package/dist/components/questions/creator/dedicated-component-router.js +1 -79
  104. package/dist/components/questions/creator/index.js +1 -3
  105. package/dist/components/questions/creator/question-type-registry.js +1 -80
  106. package/dist/components/questions/groups/ArticlesGroupCard.js +1 -121
  107. package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -50
  108. package/dist/components/questions/groups/DocumentGroupCard.js +1 -114
  109. package/dist/components/questions/groups/MatchAnswerGroupCard.js +1 -25
  110. package/dist/components/questions/index.js +1 -10
  111. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +1 -37
  112. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +1 -351
  113. package/dist/components/questions/types/answer-the-question/register.js +1 -67
  114. package/dist/components/questions/types/answer-the-question/transform.js +1 -36
  115. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +1 -141
  116. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +1 -118
  117. package/dist/components/questions/types/arrange-letters-into-words/index.js +1 -2
  118. package/dist/components/questions/types/arrange-letters-into-words/register.js +1 -4
  119. package/dist/components/questions/types/arrange-letters-into-words/transform.js +1 -24
  120. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -64
  121. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +1 -169
  122. package/dist/components/questions/types/choose-correct-adjective/index.js +1 -2
  123. package/dist/components/questions/types/choose-correct-adjective/register.js +1 -55
  124. package/dist/components/questions/types/choose-correct-adjective/transform.js +1 -16
  125. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +1 -131
  126. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +1 -581
  127. package/dist/components/questions/types/choose-the-correct-answer/index.js +1 -2
  128. package/dist/components/questions/types/choose-the-correct-answer/register.js +1 -101
  129. package/dist/components/questions/types/choose-the-correct-answer/transform.js +1 -43
  130. package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +1 -275
  131. package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +1 -448
  132. package/dist/components/questions/types/fill-in-blank/index.js +1 -2
  133. package/dist/components/questions/types/fill-in-blank/register.js +1 -138
  134. package/dist/components/questions/types/fill-in-blank/transform.js +1 -59
  135. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +1 -117
  136. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +1 -671
  137. package/dist/components/questions/types/fill-missing-words-in-grid/index.js +1 -2
  138. package/dist/components/questions/types/fill-missing-words-in-grid/register.js +1 -37
  139. package/dist/components/questions/types/fill-missing-words-in-grid/transform.js +1 -15
  140. package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +1 -262
  141. package/dist/components/questions/types/gn-speaking-interview/register.js +1 -121
  142. package/dist/components/questions/types/gn-speaking-interview/transform.js +1 -48
  143. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingClient.js +1 -16
  144. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingCreator.js +1 -27
  145. package/dist/components/questions/types/image-object-matching/index.js +1 -2
  146. package/dist/components/questions/types/image-object-matching/register.js +1 -3
  147. package/dist/components/questions/types/image-object-matching/transform.js +1 -2
  148. package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +1 -102
  149. package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +1 -344
  150. package/dist/components/questions/types/label-the-picture/index.js +1 -2
  151. package/dist/components/questions/types/label-the-picture/register.js +1 -54
  152. package/dist/components/questions/types/label-the-picture/transform.js +1 -28
  153. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +1 -78
  154. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +1 -232
  155. package/dist/components/questions/types/listen-and-choose-from-answer-group/index.js +1 -2
  156. package/dist/components/questions/types/listen-and-choose-from-answer-group/register.js +1 -82
  157. package/dist/components/questions/types/listen-and-choose-from-answer-group/transform.js +1 -27
  158. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +1 -70
  159. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +1 -386
  160. package/dist/components/questions/types/listen-and-choose-objects-in-scene/index.js +1 -2
  161. package/dist/components/questions/types/listen-and-choose-objects-in-scene/register.js +1 -100
  162. package/dist/components/questions/types/listen-and-choose-objects-in-scene/transform.js +1 -33
  163. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +1 -106
  164. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +1 -457
  165. package/dist/components/questions/types/listen-and-drag-objects-into-scene/index.js +1 -2
  166. package/dist/components/questions/types/listen-and-drag-objects-into-scene/register.js +1 -110
  167. package/dist/components/questions/types/listen-and-drag-objects-into-scene/transform.js +1 -35
  168. package/dist/components/questions/types/listen-and-speak-answer/ArrowIndicator.js +1 -8
  169. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -22
  170. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +1 -291
  171. package/dist/components/questions/types/listen-and-speak-answer/index.js +1 -2
  172. package/dist/components/questions/types/listen-and-speak-answer/register.js +1 -84
  173. package/dist/components/questions/types/listen-and-speak-answer/transform.js +1 -27
  174. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -32
  175. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +1 -295
  176. package/dist/components/questions/types/listen-and-speak-compare-images/register.js +1 -79
  177. package/dist/components/questions/types/listen-and-speak-compare-images/transform.js +1 -27
  178. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -34
  179. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +1 -285
  180. package/dist/components/questions/types/listen-and-speak-image-group/register.js +1 -81
  181. package/dist/components/questions/types/listen-and-speak-image-group/transform.js +1 -26
  182. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -38
  183. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +1 -385
  184. package/dist/components/questions/types/listen-and-speak-info-exchange/register.js +1 -87
  185. package/dist/components/questions/types/listen-and-speak-info-exchange/transform.js +1 -32
  186. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -26
  187. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +1 -247
  188. package/dist/components/questions/types/listen-and-speak-odd-one-out/register.js +1 -59
  189. package/dist/components/questions/types/listen-and-speak-odd-one-out/transform.js +1 -18
  190. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -26
  191. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +1 -256
  192. package/dist/components/questions/types/listen-and-speak-question-list/register.js +1 -81
  193. package/dist/components/questions/types/listen-and-speak-question-list/transform.js +1 -26
  194. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -26
  195. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +1 -230
  196. package/dist/components/questions/types/listen-and-speak-with-story-images/register.js +1 -56
  197. package/dist/components/questions/types/listen-and-speak-with-story-images/transform.js +1 -16
  198. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +1 -72
  199. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +1 -230
  200. package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +1 -1
  201. package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +1 -66
  202. package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +1 -42
  203. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +1 -96
  204. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +1 -238
  205. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +1 -2
  206. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +1 -76
  207. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +1 -19
  208. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +1 -134
  209. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +1 -617
  210. package/dist/components/questions/types/look-picture-fill-word-hint/index.js +1 -2
  211. package/dist/components/questions/types/look-picture-fill-word-hint/register.js +1 -82
  212. package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +1 -29
  213. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +1 -38
  214. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +1 -359
  215. package/dist/components/questions/types/match-by-writing-answer/index.js +1 -3
  216. package/dist/components/questions/types/match-by-writing-answer/register.js +1 -58
  217. package/dist/components/questions/types/match-by-writing-answer/transform.js +1 -61
  218. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsClient.js +1 -16
  219. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsCreator.js +1 -23
  220. package/dist/components/questions/types/read-and-color-objects/index.js +1 -2
  221. package/dist/components/questions/types/read-and-color-objects/register.js +1 -3
  222. package/dist/components/questions/types/read-and-color-objects/transform.js +1 -36
  223. package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +1 -125
  224. package/dist/components/questions/types/read-displayed-content/register.js +1 -64
  225. package/dist/components/questions/types/read-displayed-content/transform.js +1 -12
  226. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -64
  227. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +1 -511
  228. package/dist/components/questions/types/read-passage-and-answer-questions/index.js +1 -2
  229. package/dist/components/questions/types/read-passage-and-answer-questions/register.js +1 -58
  230. package/dist/components/questions/types/read-passage-and-answer-questions/transform.js +1 -28
  231. package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +1 -373
  232. package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +1 -41
  233. package/dist/components/questions/types/speaking-conversation/register.js +1 -110
  234. package/dist/components/questions/types/speaking-conversation/transform.js +1 -34
  235. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +1 -74
  236. package/dist/components/questions/types/speaking-cue-card/register.js +1 -52
  237. package/dist/components/questions/types/speaking-cue-card/transform.js +1 -12
  238. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -10
  239. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +1 -161
  240. package/dist/components/questions/types/speaking-describe-image/index.js +1 -2
  241. package/dist/components/questions/types/speaking-describe-image/register.js +1 -69
  242. package/dist/components/questions/types/speaking-describe-image/transform.js +1 -16
  243. package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +1 -234
  244. package/dist/components/questions/types/spontaneous-qa/register.js +1 -93
  245. package/dist/components/questions/types/spontaneous-qa/transform.js +1 -34
  246. package/dist/components/questions/types/true-false/TrueFalseClient.js +1 -75
  247. package/dist/components/questions/types/true-false/TrueFalseCreator.js +1 -244
  248. package/dist/components/questions/types/true-false/index.js +1 -2
  249. package/dist/components/questions/types/true-false/register.js +1 -77
  250. package/dist/components/questions/types/true-false/transform.js +1 -32
  251. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +1 -233
  252. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +1 -631
  253. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphWrapper.js +1 -28
  254. package/dist/components/questions/types/word-fill-paragraph/index.js +1 -2
  255. package/dist/components/questions/types/word-fill-paragraph/register.js +1 -136
  256. package/dist/components/questions/types/word-fill-paragraph/transform.js +1 -56
  257. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -138
  258. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +1 -261
  259. package/dist/components/questions/types/word-fill-structured-form/index.js +1 -2
  260. package/dist/components/questions/types/word-fill-structured-form/register.js +1 -67
  261. package/dist/components/questions/types/word-fill-structured-form/transform.js +1 -40
  262. package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -84
  263. package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +1 -192
  264. package/dist/components/questions/types/word-ordering/index.js +1 -2
  265. package/dist/components/questions/types/word-ordering/register.js +1 -46
  266. package/dist/components/questions/types/word-ordering/transform.js +1 -24
  267. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +1 -98
  268. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +1 -320
  269. package/dist/components/questions/types/write-a-short-letter/index.js +1 -2
  270. package/dist/components/questions/types/write-a-short-letter/register.js +1 -68
  271. package/dist/components/questions/types/write-a-short-letter/transform.js +1 -25
  272. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +1 -61
  273. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +1 -196
  274. package/dist/components/questions/types/write-correct-verb-form/index.js +1 -2
  275. package/dist/components/questions/types/write-correct-verb-form/register.js +1 -62
  276. package/dist/components/questions/types/write-correct-verb-form/transform.js +1 -17
  277. package/dist/components/questions/types/write-sentences/WriteSentencesClient.js +1 -22
  278. package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +1 -174
  279. package/dist/components/questions/types/write-sentences/index.js +1 -2
  280. package/dist/components/questions/types/write-sentences/register.js +1 -58
  281. package/dist/components/questions/types/write-sentences/transform.js +1 -28
  282. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -96
  283. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +1 -161
  284. package/dist/components/questions/types/write-short-paragraph/index.js +1 -2
  285. package/dist/components/questions/types/write-short-paragraph/register.js +1 -4
  286. package/dist/components/questions/types/write-short-paragraph/transform.js +1 -21
  287. package/dist/components/questions/viewer/QuestionGroupViewer.js +1 -36
  288. package/dist/components/questions/viewer/QuestionViewer.js +1 -446
  289. package/dist/components/questions/viewer/index.js +1 -2
  290. package/dist/components/results/ResultReviewPageContainer.js +1 -98
  291. package/dist/components/results/ReviewQuestionRenderer.js +1 -72
  292. package/dist/components/results/index.js +1 -2
  293. package/dist/components/results/result-review.utils.js +1 -6
  294. package/dist/components/results/review-data.js +1 -150
  295. package/dist/components/results/review-renderer.utils.js +1 -97
  296. package/dist/components/results/review-types.js +1 -1
  297. package/dist/components/themes/cambridge-yle/CambridgeYleAudioPlayer.js +1 -138
  298. package/dist/components/themes/cambridge-yle/CambridgeYleBottomNav.js +1 -12
  299. package/dist/components/themes/cambridge-yle/CambridgeYleExamLayout.js +1 -18
  300. package/dist/components/themes/cambridge-yle/CambridgeYleExampleText.js +1 -11
  301. package/dist/components/themes/cambridge-yle/CambridgeYleHeader.js +1 -11
  302. package/dist/components/themes/cambridge-yle/CambridgeYleInstructionBanner.js +1 -9
  303. package/dist/components/themes/cambridge-yle/CambridgeYleNavButton.js +1 -13
  304. package/dist/components/themes/cambridge-yle/CambridgeYlePartBanner.js +1 -8
  305. package/dist/components/themes/cambridge-yle/CambridgeYleProgressBar.js +1 -6
  306. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCard.js +1 -5
  307. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCardFullWidth.js +1 -9
  308. package/dist/components/themes/cambridge-yle/CambridgeYleSidebar.js +1 -41
  309. package/dist/components/themes/cambridge-yle/CambridgeYleTimer.js +1 -25
  310. package/dist/components/themes/cambridge-yle/index.js +1 -13
  311. package/dist/components/themes/english-certification/EcAnswerTheQuestion.js +1 -24
  312. package/dist/components/themes/english-certification/EcMatchByWritingAnswer.js +1 -88
  313. package/dist/components/themes/english-certification/EcWordFillParagraph.js +1 -55
  314. package/dist/components/themes/english-certification/EcWordFillStructuredForm.js +1 -156
  315. package/dist/components/themes/english-certification/EcWritingTask.js +1 -34
  316. package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.js +1 -114
  317. package/dist/components/themes/english-certification/EnglishCertificationBrand.js +1 -11
  318. package/dist/components/themes/english-certification/EnglishCertificationExamLayout.js +1 -21
  319. package/dist/components/themes/english-certification/EnglishCertificationFlagButton.js +1 -15
  320. package/dist/components/themes/english-certification/EnglishCertificationFooterBar.js +1 -19
  321. package/dist/components/themes/english-certification/EnglishCertificationGroupedQuestion.js +1 -148
  322. package/dist/components/themes/english-certification/EnglishCertificationHeader.js +1 -31
  323. package/dist/components/themes/english-certification/EnglishCertificationNavigationPanel.js +1 -71
  324. package/dist/components/themes/english-certification/EnglishCertificationOptionSelector.js +1 -33
  325. package/dist/components/themes/english-certification/EnglishCertificationPassagePanel.js +1 -16
  326. package/dist/components/themes/english-certification/EnglishCertificationQuestionCard.js +1 -22
  327. package/dist/components/themes/english-certification/EnglishCertificationQuestionItem.js +1 -37
  328. package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.js +1 -72
  329. package/dist/components/themes/english-certification/EnglishCertificationReadingSection.js +1 -90
  330. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestion.js +1 -18
  331. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestionList.js +1 -29
  332. package/dist/components/themes/english-certification/english-certification-theme.js +1 -58
  333. package/dist/components/themes/english-certification/grouped-question-utils.js +1 -218
  334. package/dist/components/themes/english-certification/index.js +1 -21
  335. package/dist/components/themes/index.js +1 -6
  336. package/dist/components/themes/summer-sky/SummerSkyAudioPlayer.js +1 -170
  337. package/dist/components/themes/summer-sky/SummerSkyLayout.js +1 -221
  338. package/dist/components/themes/summer-sky/SummerSkySubmitModal.js +1 -18
  339. package/dist/components/themes/summer-sky/SummerSkyWaitingRoom.js +1 -194
  340. package/dist/components/themes/types.js +1 -1
  341. package/dist/components/ui/alert-dialog.js +1 -26
  342. package/dist/components/ui/alert.js +1 -22
  343. package/dist/components/ui/avatar.js +1 -12
  344. package/dist/components/ui/badge.js +1 -38
  345. package/dist/components/ui/button.js +1 -35
  346. package/dist/components/ui/calendar.js +1 -76
  347. package/dist/components/ui/card.js +1 -16
  348. package/dist/components/ui/carousel.js +1 -91
  349. package/dist/components/ui/checkbox.js +1 -9
  350. package/dist/components/ui/collapsible.js +1 -13
  351. package/dist/components/ui/command.js +1 -34
  352. package/dist/components/ui/confirm-dialog.js +1 -49
  353. package/dist/components/ui/dialog.js +1 -23
  354. package/dist/components/ui/drawer.js +1 -35
  355. package/dist/components/ui/dropdown-menu.js +1 -33
  356. package/dist/components/ui/file-upload.js +1 -155
  357. package/dist/components/ui/form.js +1 -62
  358. package/dist/components/ui/hover-card.js +1 -10
  359. package/dist/components/ui/image-preview.js +1 -29
  360. package/dist/components/ui/input.js +1 -8
  361. package/dist/components/ui/label.js +1 -9
  362. package/dist/components/ui/language-switcher.js +1 -15
  363. package/dist/components/ui/multi-select.js +1 -90
  364. package/dist/components/ui/points-input.js +1 -33
  365. package/dist/components/ui/popover.js +1 -26
  366. package/dist/components/ui/progress.js +1 -16
  367. package/dist/components/ui/radio-group.js +1 -12
  368. package/dist/components/ui/scroll-area.js +1 -11
  369. package/dist/components/ui/select.js +1 -38
  370. package/dist/components/ui/separator.js +1 -8
  371. package/dist/components/ui/sheet.js +1 -40
  372. package/dist/components/ui/single-select.js +1 -76
  373. package/dist/components/ui/skeleton.js +1 -6
  374. package/dist/components/ui/switch.js +1 -8
  375. package/dist/components/ui/table.js +1 -20
  376. package/dist/components/ui/tabs.js +1 -17
  377. package/dist/components/ui/textarea.js +1 -8
  378. package/dist/components/ui/tooltip.js +1 -11
  379. package/dist/index.js +1 -19
  380. package/dist/mcp/config.js +1 -32
  381. package/dist/mcp/http-client.js +1 -48
  382. package/dist/mcp/index.js +1 -29
  383. package/dist/mcp/tools/_helpers.js +1 -25
  384. package/dist/mcp/tools/exams.js +1 -439
  385. package/dist/mcp/tools/index.js +1 -6
  386. package/dist/mcp/tools/results.js +1 -19
  387. package/dist/shared/constants/ApiConstant.js +1 -276
  388. package/dist/shared/constants/QueryKeyConstant.js +1 -112
  389. package/dist/shared/constants/exam-level.enum.js +1 -41
  390. package/dist/shared/constants/landings/placement-test.constants.js +1 -431
  391. package/dist/shared/constants/question-skills.js +1 -267
  392. package/dist/shared/constants/routes.js +1 -81
  393. package/dist/shared/index.js +1 -7
  394. package/dist/shared/lib/hooks/index.js +1 -10
  395. package/dist/shared/lib/hooks/useAlertDialog.js +1 -57
  396. package/dist/shared/lib/hooks/useAudioPlayCounter.js +1 -30
  397. package/dist/shared/lib/hooks/useAudioPreview.js +1 -81
  398. package/dist/shared/lib/hooks/useAudioUpload.js +1 -24
  399. package/dist/shared/lib/hooks/useConfirmDialog.js +1 -89
  400. package/dist/shared/lib/hooks/useDebouncedCallback.js +1 -32
  401. package/dist/shared/lib/hooks/useDelayedAutoPlay.js +1 -12
  402. package/dist/shared/lib/hooks/useFileUpload.js +1 -24
  403. package/dist/shared/lib/hooks/useImageUrl.js +1 -5
  404. package/dist/shared/lib/hooks/usePresignedFileUrl.js +1 -10
  405. package/dist/shared/lib/hooks/useReactHookForm.js +1 -31
  406. package/dist/shared/lib/hooks/useToast.js +1 -62
  407. package/dist/shared/lib/i18n/index.js +1 -54
  408. package/dist/shared/lib/i18n/messages/en/admin.js +1 -2104
  409. package/dist/shared/lib/i18n/messages/en/auth.js +1 -86
  410. package/dist/shared/lib/i18n/messages/en/common.js +1 -140
  411. package/dist/shared/lib/i18n/messages/en/file-upload.js +1 -18
  412. package/dist/shared/lib/i18n/messages/en/student.js +1 -133
  413. package/dist/shared/lib/i18n/messages/en/terms.js +1 -37
  414. package/dist/shared/lib/i18n/messages/en.js +1 -15
  415. package/dist/shared/lib/i18n/messages/vi/admin.js +1 -2104
  416. package/dist/shared/lib/i18n/messages/vi/auth.js +1 -86
  417. package/dist/shared/lib/i18n/messages/vi/common.js +1 -140
  418. package/dist/shared/lib/i18n/messages/vi/file-upload.js +1 -18
  419. package/dist/shared/lib/i18n/messages/vi/student.js +1 -133
  420. package/dist/shared/lib/i18n/messages/vi/terms.js +1 -37
  421. package/dist/shared/lib/i18n/messages/vi.js +1 -15
  422. package/dist/shared/lib/i18n/types.js +1 -1
  423. package/dist/shared/lib/stores/examQuestionStore.js +1 -212
  424. package/dist/shared/lib/stores/localeStore.js +1 -10
  425. package/dist/shared/lib/utils/public-page.js +1 -44
  426. package/dist/shared/lib/utils/question-transform-types.js +1 -1
  427. package/dist/shared/lib/utils/question-transform.js +1 -174
  428. package/dist/shared/lib/utils.js +1 -168
  429. package/dist/shared/lib/validation/choose-correct-answer.validation.js +1 -111
  430. package/dist/shared/lib/validation/label-the-picture.validation.js +1 -19
  431. package/dist/shared/lib/validation/listen-and-choose-from-answer-group.validation.js +1 -46
  432. package/dist/shared/lib/validation/listen-and-choose-objects-in-scene.validation.js +1 -21
  433. package/dist/shared/lib/validation/listen-and-drag-objects-into-scene.validation.js +1 -34
  434. package/dist/shared/lib/validation/listen-and-speak-answer.validation.js +1 -40
  435. package/dist/shared/lib/validation/listen-and-speak-compare-images.validation.js +1 -45
  436. package/dist/shared/lib/validation/listen-and-speak-image-group.validation.js +1 -19
  437. package/dist/shared/lib/validation/listen-and-speak-info-exchange.validation.js +1 -39
  438. package/dist/shared/lib/validation/listen-and-speak-odd-one-out.validation.js +1 -32
  439. package/dist/shared/lib/validation/listen-and-speak-question-list.validation.js +1 -19
  440. package/dist/shared/lib/validation/listen-and-speak-with-story-images.validation.js +1 -28
  441. package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +1 -12
  442. package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +1 -47
  443. package/dist/shared/lib/validation/read-and-color-objects.validation.js +1 -29
  444. package/dist/shared/lib/validation/read-displayed-content.validation.js +1 -28
  445. package/dist/shared/lib/validation/speaking-conversation.validation.js +1 -75
  446. package/dist/shared/lib/validation/speaking-describe-image.validation.js +1 -31
  447. package/dist/shared/lib/validation/spontaneous-qa.validation.js +1 -48
  448. package/dist/shared/lib/validation/word-fill-paragraph-default.validation.js +1 -38
  449. package/dist/shared/lib/validation/word-fill-paragraph-with-options.validation.js +1 -39
  450. package/dist/shared/lib/validation/word-fill-paragraph.validation.js +1 -43
  451. package/dist/shared/lib/validation/word-ordering.validation.js +1 -33
  452. package/dist/shared/types/branch.types.js +1 -6
  453. package/dist/shared/types/common.types.js +1 -50
  454. package/dist/shared/types/entities/exam-schedule.types.js +1 -5
  455. package/dist/shared/types/entities/exam.types.js +1 -1
  456. package/dist/shared/types/entities/question.types.js +1 -4
  457. package/dist/shared/types/entities/result.types.js +1 -4
  458. package/dist/shared/types/entities/user.types.js +1 -4
  459. package/dist/shared/types/exam-taking.types.js +1 -2
  460. package/dist/shared/types/history.types.js +1 -34
  461. package/dist/shared/types/index.js +1 -1
  462. package/dist/shared/types/questions/choose-correct-adjective.js +1 -4
  463. package/dist/shared/types/questions/choose-the-correct-answer.js +1 -9
  464. package/dist/shared/types/questions/fill-missing-words-in-grid.js +1 -11
  465. package/dist/shared/types/questions/index.js +1 -40
  466. package/dist/shared/types/questions/listen-and-speak-answer.js +1 -1
  467. package/dist/shared/types/questions/listen-and-speak-compare-images.js +1 -1
  468. package/dist/shared/types/questions/listen-and-speak-image-group.js +1 -1
  469. package/dist/shared/types/questions/listen-and-speak-info-exchange.js +1 -3
  470. package/dist/shared/types/questions/listen-and-speak-odd-one-out.js +1 -3
  471. package/dist/shared/types/questions/listen-and-speak-question-list.js +1 -1
  472. package/dist/shared/types/questions/listen-and-speak-with-story-images.js +1 -3
  473. package/dist/shared/types/questions/look-picture-choose-correct-answer.js +1 -5
  474. package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +1 -9
  475. package/dist/shared/types/questions/look-picture-fill-word-hint.js +1 -7
  476. package/dist/shared/types/questions/read-displayed-content.js +1 -1
  477. package/dist/shared/types/questions/speaking-conversation.js +1 -7
  478. package/dist/shared/types/questions/speaking-describe-image.js +1 -1
  479. package/dist/shared/types/questions/spontaneous-qa.js +1 -1
  480. package/dist/shared/types/questions/write-correct-verb-form.js +1 -12
  481. package/dist/shared/types/role.types.js +1 -1
  482. package/dist/shared/types/structure-management.js +1 -28
  483. package/dist/shared/types/student.types.js +1 -54
  484. package/dist/shared/types/teacher-dashboard-rooms.js +1 -2
  485. package/package.json +4 -2
@@ -1,671 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useRef, useEffect, useCallback } from 'react';
4
- import { Button } from '../../../../components/ui/button';
5
- import { Label } from '../../../../components/ui/label';
6
- import { Input } from '../../../../components/ui/input';
7
- import { Textarea } from '../../../../components/ui/textarea';
8
- import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
9
- import { PointsInput } from '../../../../components/ui/points-input';
10
- import { Pencil, Eye, Trash2, Grid3X3, Lightbulb, CheckCircle2, BookOpen } from 'lucide-react';
11
- import { toast } from 'sonner';
12
- import { t } from '../../../../shared/lib/i18n';
13
- import { FillMissingWordsInGridClient } from './FillMissingWordsInGridClient';
14
- function FillMissingWordsInGridCreatorContent({ initialData, onSave, onCancel, // eslint-disable-line @typescript-eslint/no-unused-vars
15
- onChange, onUnsavedChangesChange, externalErrors = [], }) {
16
- // Grid dimensions - FIXED: 4 rows, 7 columns
17
- const [rows, setRows] = useState(4); // Fixed to 4
18
- const [columns, setColumns] = useState(7); // Fixed to 7
19
- // Points/Score
20
- const [points, setPoints] = useState(initialData?.points || 1);
21
- // Explanation (optional)
22
- const [explanation, setExplanation] = useState(initialData?.explanation || '');
23
- // Grid data: 2D array, null = empty cell (missing word)
24
- const [grid, setGrid] = useState(() => {
25
- if (initialData?.content?.grid) {
26
- return initialData.content.grid;
27
- }
28
- // Initialize empty grid
29
- return Array(rows)
30
- .fill(null)
31
- .map(() => Array(columns).fill(null));
32
- });
33
- // Missing words list - NEW: Support both API format (answers) and internal format (missingLetters)
34
- const [missingLetters, setMissingLetters] = useState(() => {
35
- if (initialData?.answer?.missingLetters) {
36
- // Internal format
37
- console.log('📝 Using missingLetters format');
38
- return initialData.answer.missingLetters.map((item, index) => {
39
- const [row1Based, column1Based] = item.position.split('-').map(Number);
40
- const row = (row1Based || 1) - 1;
41
- const column = (column1Based || 1) - 1;
42
- return {
43
- id: `word-${index}-${Date.now()}`,
44
- row,
45
- column,
46
- correctAnswer: item.correctAnswer,
47
- };
48
- });
49
- }
50
- else if (initialData?.answer?.answers && typeof initialData.answer.answers === 'object') {
51
- // API format
52
- const answersObj = initialData.answer.answers;
53
- const result = Object.entries(answersObj).map(([position, correctAnswer], index) => {
54
- const [row1Based, column1Based] = position.split('-').map(Number);
55
- const row = (row1Based || 1) - 1;
56
- const column = (column1Based || 1) - 1;
57
- return {
58
- id: `word-${index}-${Date.now()}-${Math.random()}`,
59
- row,
60
- column,
61
- correctAnswer: correctAnswer,
62
- };
63
- });
64
- return result;
65
- }
66
- return [];
67
- });
68
- // Disabled cells list - NEW: cells that are disabled (not fillable, not missing)
69
- const [disabledCells, setDisabledCells] = useState(() => {
70
- if (initialData?.content?.disabledCells) {
71
- return initialData.content.disabledCells;
72
- }
73
- return [];
74
- });
75
- // Track previous initialData to detect changes
76
- const previousInitialDataRef = useRef(initialData);
77
- const isInitializingRef = useRef(true); // Start as true - prevent false "unsaved changes" on initial mount
78
- const onChangeRef = useRef(onChange);
79
- const previousPayloadRef = useRef('');
80
- const hasInitializedOnceRef = useRef(false); // Start as false - track if component has fully initialized
81
- const hasSetInitialUnsavedStateRef = useRef(false); // Track if we've set initial unsaved state to false
82
- // CRITICAL FIX: Store the ORIGINAL saved data from API (not updated when store updates)
83
- // This is used to compare against to determine if there are unsaved changes
84
- const originalSavedDataRef = useRef(undefined);
85
- const hasOriginalDataRef = useRef(false); // Track if we have original data from API
86
- // Update onChange ref when it changes
87
- useEffect(() => {
88
- onChangeRef.current = onChange;
89
- }, [onChange]);
90
- // CRITICAL FIX: Initialize originalSavedDataRef ONLY ONCE when we first receive valid initialData from API
91
- // This ref should NOT be updated when the store updates (which would make initialData prop change)
92
- useEffect(() => {
93
- // Only set original data once, and only if initialData has valid content (from API)
94
- if (!hasOriginalDataRef.current && initialData?.content?.grid) {
95
- // Deep clone to prevent reference issues
96
- originalSavedDataRef.current = JSON.parse(JSON.stringify(initialData));
97
- hasOriginalDataRef.current = true;
98
- console.log('📌 [FillMissingWordsInGridCreator] Saved original data from API:', originalSavedDataRef.current);
99
- }
100
- }, [initialData]);
101
- // Track unsaved changes and notify parent
102
- // FIXED: Compare against originalSavedDataRef (stable) instead of initialData (which updates with store)
103
- useEffect(() => {
104
- // Don't notify during initialization
105
- if (isInitializingRef.current) {
106
- return;
107
- }
108
- // Use original saved data (from API) for comparison, not live initialData
109
- const savedData = originalSavedDataRef.current;
110
- // If we have original saved data, compare against it
111
- if (hasOriginalDataRef.current && savedData?.content?.grid) {
112
- // Existing question - check for changes from ORIGINAL saved state
113
- const hasGridChanges = JSON.stringify(grid) !== JSON.stringify(savedData.content.grid);
114
- // Normalize disabledCells arrays (sort before comparison)
115
- const normalizedDisabledCells = [...(disabledCells || [])].sort();
116
- const normalizedSavedDisabledCells = [...(savedData.content.disabledCells || [])].sort();
117
- const hasDisabledCellsChanges = JSON.stringify(normalizedDisabledCells) !== JSON.stringify(normalizedSavedDisabledCells);
118
- // Normalize points (convert to number for comparison)
119
- const normalizedPoints = typeof points === 'string' ? Number(points) : points;
120
- const normalizedSavedPoints = typeof savedData?.points === 'string'
121
- ? Number(savedData.points)
122
- : (savedData?.points || 1);
123
- const hasPointsChanges = normalizedPoints !== normalizedSavedPoints;
124
- // Normalize answers objects (sort keys before comparison)
125
- const savedAnswers = savedData?.answer?.answers || {};
126
- const currentAnswers = missingLetters.reduce((acc, item) => {
127
- acc[`${item.row + 1}-${item.column + 1}`] = item.correctAnswer;
128
- return acc;
129
- }, {});
130
- const savedAnswersSorted = Object.keys(savedAnswers).sort().reduce((acc, key) => {
131
- acc[key] = savedAnswers[key];
132
- return acc;
133
- }, {});
134
- const currentAnswersSorted = Object.keys(currentAnswers).sort().reduce((acc, key) => {
135
- acc[key] = currentAnswers[key];
136
- return acc;
137
- }, {});
138
- const hasMissingLettersChanges = JSON.stringify(currentAnswersSorted) !== JSON.stringify(savedAnswersSorted);
139
- const hasAnyChanges = hasGridChanges || hasDisabledCellsChanges || hasPointsChanges || hasMissingLettersChanges;
140
- onUnsavedChangesChange?.(hasAnyChanges);
141
- }
142
- else {
143
- // New question (no original data) - check if user has entered any data
144
- const hasGridContent = grid.some(row => row.some(cell => cell !== null && cell !== ''));
145
- const hasMissingLettersContent = missingLetters.length > 0;
146
- const hasDisabledCellsContent = disabledCells.length > 0;
147
- const hasAnyData = hasGridContent || hasMissingLettersContent || hasDisabledCellsContent;
148
- onUnsavedChangesChange?.(hasAnyData);
149
- }
150
- }, [grid, missingLetters, disabledCells, points, onUnsavedChangesChange]);
151
- // Sync state from initialData when it changes
152
- useEffect(() => {
153
- const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
154
- if (initialDataChanged) {
155
- isInitializingRef.current = true;
156
- previousInitialDataRef.current = initialData;
157
- if (initialData) {
158
- // FIXED: Do not update rows/columns from initialData - keep fixed at 4x7
159
- // if (initialData.content?.rows !== undefined) setRows(initialData.content.rows);
160
- // if (initialData.content?.columns !== undefined) setColumns(initialData.content.columns);
161
- if (initialData.content?.grid !== undefined)
162
- setGrid(initialData.content.grid);
163
- // NEW: Support both formats
164
- if (initialData.answer?.missingLetters) {
165
- setMissingLetters((prev) => {
166
- const positionToId = new Map(prev.map((item) => [`${item.row}-${item.column}`, item.id]));
167
- return initialData.answer.missingLetters.map((item, index) => {
168
- const [row1Based, column1Based] = item.position.split('-').map(Number);
169
- const row = (row1Based || 1) - 1;
170
- const column = (column1Based || 1) - 1;
171
- const positionKey = `${row}-${column}`;
172
- const existingId = positionToId.get(positionKey);
173
- const id = existingId || `word-${index}-${Date.now()}-${Math.random()}`;
174
- return {
175
- id,
176
- row,
177
- column,
178
- correctAnswer: item.correctAnswer,
179
- };
180
- });
181
- });
182
- }
183
- else if (initialData.answer?.answers) {
184
- setMissingLetters((prev) => {
185
- const positionToId = new Map(prev.map((item) => [`${item.row}-${item.column}`, item.id]));
186
- const result = Object.entries(initialData.answer.answers).map(([position, correctAnswer], index) => {
187
- const [row1Based, column1Based] = position.split('-').map(Number);
188
- const row = (row1Based || 1) - 1;
189
- const column = (column1Based || 1) - 1;
190
- const positionKey = `${row}-${column}`;
191
- const existingId = positionToId.get(positionKey);
192
- const id = existingId || `word-${index}-${Date.now()}-${Math.random()}`;
193
- return {
194
- id,
195
- row,
196
- column,
197
- correctAnswer: correctAnswer,
198
- };
199
- });
200
- return result;
201
- });
202
- }
203
- // NEW: Sync disabled cells (ensure it's an array)
204
- if (initialData.content?.disabledCells !== undefined) {
205
- const disabledCellsArray = Array.isArray(initialData.content.disabledCells)
206
- ? initialData.content.disabledCells
207
- : [];
208
- setDisabledCells(disabledCellsArray);
209
- }
210
- // Sync points (convert to number if it's a string)
211
- if (initialData.points !== undefined) {
212
- const pointsValue = initialData.points;
213
- setPoints(typeof pointsValue === 'string' ? Number(pointsValue) : pointsValue);
214
- }
215
- // Sync explanation
216
- if (initialData.explanation !== undefined) {
217
- setExplanation(initialData.explanation);
218
- }
219
- // FIXED: Keep rows and columns fixed
220
- // setRows(4);
221
- // setColumns(7);
222
- // setGrid(Array(4).fill(null).map(() => Array(7).fill(null)));
223
- // setMissingLetters([]);
224
- }
225
- // SIMPLIFIED: After syncing state from initialData, mark initialization complete
226
- // FIXED: Only set unsaved changes to false on FIRST initialization, not on every initialData change
227
- setTimeout(() => {
228
- isInitializingRef.current = false;
229
- // Only on first initialization, set the initial status
230
- if (!hasInitializedOnceRef.current) {
231
- hasInitializedOnceRef.current = true;
232
- hasSetInitialUnsavedStateRef.current = true;
233
- // If we have initialData from API, question is already saved - no unsaved changes
234
- if (initialData?.content?.grid) {
235
- onUnsavedChangesChange?.(false);
236
- }
237
- }
238
- // Note: After first initialization, we don't reset unsaved changes state
239
- // because the useEffect that tracks changes will handle it correctly
240
- }, 100);
241
- }
242
- else if (!hasInitializedOnceRef.current) {
243
- // If no initialData and haven't initialized yet, mark as initialized after short delay
244
- setTimeout(() => {
245
- isInitializingRef.current = false;
246
- hasInitializedOnceRef.current = true;
247
- hasSetInitialUnsavedStateRef.current = true;
248
- }, 100);
249
- }
250
- }, [initialData, rows, columns, onUnsavedChangesChange]);
251
- // Update grid when dimensions change (always update, not just when not initializing)
252
- useEffect(() => {
253
- setGrid((prev) => {
254
- // Check if dimensions actually changed
255
- const currentRows = prev.length;
256
- const currentCols = prev[0]?.length || 0;
257
- // If dimensions are the same, don't recreate grid
258
- if (currentRows === rows && currentCols === columns) {
259
- return prev;
260
- }
261
- const newGrid = [];
262
- for (let i = 0; i < rows; i++) {
263
- const newRow = [];
264
- for (let j = 0; j < columns; j++) {
265
- // Preserve existing values if within bounds
266
- if (i < prev.length && j < prev[i]?.length) {
267
- newRow.push(prev[i][j]);
268
- }
269
- else {
270
- newRow.push(null);
271
- }
272
- }
273
- newGrid.push(newRow);
274
- }
275
- return newGrid;
276
- });
277
- // Remove missing letters that are out of bounds (only when not initializing)
278
- if (!isInitializingRef.current) {
279
- setMissingLetters((prev) => {
280
- const filtered = prev.filter((letter) => letter.row < rows && letter.column < columns);
281
- const removedCount = prev.length - filtered.length;
282
- if (removedCount > 0) {
283
- // Show warning as text instead of toast
284
- setGeneralErrors((prev) => [
285
- ...prev.filter((err) => !err.includes('ô còn thiếu ngoài phạm vi grid')),
286
- `Đã xóa ${removedCount} ô còn thiếu ngoài phạm vi grid mới (${rows}x${columns})`,
287
- ]);
288
- }
289
- return filtered;
290
- });
291
- }
292
- }, [rows, columns]);
293
- // Convert to payload format and call onChange
294
- useEffect(() => {
295
- // Don't call onChange until we've initialized at least once
296
- if (isInitializingRef.current) {
297
- return;
298
- }
299
- // NEW: Output API format directly - no transformation needed
300
- const payload = {
301
- content: {
302
- grid,
303
- rows,
304
- columns,
305
- disabledCells: [...disabledCells].sort(), // Sort for consistent comparison
306
- },
307
- answer: {
308
- // Convert missingLetters array to answers object for API format
309
- answers: missingLetters.reduce((acc, item) => {
310
- acc[`${item.row + 1}-${item.column + 1}`] = item.correctAnswer;
311
- return acc;
312
- }, {}),
313
- caseSensitive: false,
314
- },
315
- points: typeof points === 'string' ? Number(points) : points, // Ensure points is a number
316
- explanation: explanation || '', // Include explanation in payload
317
- };
318
- const payloadString = JSON.stringify(payload);
319
- // Only call onChange if payload actually changed
320
- if (previousPayloadRef.current !== payloadString) {
321
- previousPayloadRef.current = payloadString;
322
- onChangeRef.current?.(payload);
323
- }
324
- }, [grid, rows, columns, missingLetters, disabledCells, points, explanation]);
325
- // Parse externalErrors and set validation errors for missing letters and general errors
326
- useEffect(() => {
327
- if (externalErrors && externalErrors.length > 0) {
328
- const newFieldErrors = {};
329
- const newGeneralErrors = [];
330
- externalErrors.forEach((error) => {
331
- // Check if error is about missing letter answer
332
- // Format: "Ô còn thiếu #X ở vị trí (row, col) phải có đáp án đúng"
333
- const match = error.match(/Ô còn thiếu #(\d+) ở vị trí \((\d+), (\d+)\) phải có đáp án đúng/);
334
- if (match) {
335
- const [, , rowStr, colStr] = match;
336
- const row = parseInt(rowStr) - 1; // Convert 1-based to 0-based
337
- const col = parseInt(colStr) - 1; // Convert 1-based to 0-based
338
- // Find the missing letter by position
339
- const letter = missingLetters.find((item) => item.row === row && item.column === col);
340
- if (letter) {
341
- newFieldErrors[letter.id] = 'Vui lòng nhập đáp án đúng';
342
- }
343
- }
344
- else {
345
- // Other errors go to general errors
346
- newGeneralErrors.push(error);
347
- }
348
- });
349
- // Merge with existing validation errors
350
- setValidationErrors((prev) => ({
351
- ...prev,
352
- ...newFieldErrors,
353
- }));
354
- // Set general errors from external (only if changed)
355
- if (newGeneralErrors.length > 0) {
356
- setGeneralErrors((prev) => {
357
- const isSame = prev.length === newGeneralErrors.length &&
358
- prev.every((err, i) => err === newGeneralErrors[i]);
359
- return isSame ? prev : newGeneralErrors;
360
- });
361
- }
362
- }
363
- else {
364
- // Clear general errors when externalErrors is empty (only if not already empty)
365
- setGeneralErrors((prev) => prev.length === 0 ? prev : []);
366
- }
367
- }, [externalErrors, missingLetters]);
368
- // UI State
369
- const [isClientMode, setIsClientMode] = useState(false);
370
- const [validationErrors, setValidationErrors] = useState({});
371
- const [dimensionErrors, setDimensionErrors] = useState({});
372
- const [generalErrors, setGeneralErrors] = useState([]);
373
- // Update grid cell
374
- const updateGridCell = useCallback((row, col, value) => {
375
- setGrid((prev) => {
376
- const newGrid = prev.map((r, i) => r.map((cell, j) => (i === row && j === col ? (value || null) : cell)));
377
- return newGrid;
378
- });
379
- // Clear general errors related to this cell when user fills it
380
- if (value && value.trim() !== '') {
381
- setGeneralErrors((prev) => prev.filter((err) => !err.includes(`vị trí (${row + 1}, ${col + 1})`)));
382
- }
383
- // Immediately notify parent that there are unsaved changes when user types
384
- onUnsavedChangesChange?.(true);
385
- }, [onUnsavedChangesChange]);
386
- // Toggle missing word (mark/unmark cell as missing)
387
- const toggleMissingLetter = useCallback((row, col) => {
388
- const positionKey = `${row}-${col}`;
389
- const existing = missingLetters.find((item) => `${item.row}-${item.column}` === positionKey);
390
- if (existing) {
391
- // Remove from missing letters
392
- setMissingLetters((prev) => prev.filter((item) => item.id !== existing.id));
393
- // Clear validation error when removing
394
- setValidationErrors((prev) => {
395
- const newErrors = { ...prev };
396
- delete newErrors[existing.id];
397
- return newErrors;
398
- });
399
- }
400
- else {
401
- // Add to missing letters and clear the cell value
402
- const cellValue = grid[row]?.[col];
403
- const newLetter = {
404
- id: `word-${Date.now()}-${Math.random()}`,
405
- row,
406
- column: col,
407
- correctAnswer: cellValue || '',
408
- };
409
- setMissingLetters((prev) => [...prev, newLetter]);
410
- // Clear the cell value in grid
411
- updateGridCell(row, col, '');
412
- // Clear general errors related to this cell when marked as missing
413
- setGeneralErrors((prev) => prev.filter((err) => !err.includes(`vị trí (${row + 1}, ${col + 1})`)));
414
- }
415
- // Immediately notify parent that there are unsaved changes
416
- onUnsavedChangesChange?.(true);
417
- }, [grid, missingLetters, updateGridCell, onUnsavedChangesChange]);
418
- // Toggle disabled cell (mark/unmark cell as disabled)
419
- const toggleDisabledCell = useCallback((row, col) => {
420
- const position = `${row + 1}-${col + 1}`;
421
- setDisabledCells(prev => {
422
- if (prev.includes(position)) {
423
- // Remove from disabled cells
424
- return prev.filter(p => p !== position);
425
- }
426
- else {
427
- // Add to disabled cells and clear the cell value
428
- updateGridCell(row, col, '');
429
- // Remove from missing letters if present
430
- setMissingLetters(prevMissing => prevMissing.filter(item => !(item.row === row && item.column === col)));
431
- return [...prev, position];
432
- }
433
- });
434
- // Clear general errors related to this cell
435
- setGeneralErrors((prev) => prev.filter((err) => !err.includes(`vị trí (${row + 1}, ${col + 1})`)));
436
- // Immediately notify parent that there are unsaved changes
437
- onUnsavedChangesChange?.(true);
438
- }, [updateGridCell, onUnsavedChangesChange]);
439
- // Update missing letter answer
440
- const updateMissingLetter = useCallback((id, field, value) => {
441
- setMissingLetters((prev) => prev.map((letter) => (letter.id === id ? { ...letter, [field]: value } : letter)));
442
- // Clear validation error when user starts typing
443
- if (field === 'correctAnswer') {
444
- setValidationErrors((prev) => {
445
- const newErrors = { ...prev };
446
- if (typeof value === 'string' && value.trim() !== '') {
447
- delete newErrors[id];
448
- }
449
- return newErrors;
450
- });
451
- }
452
- // Immediately notify parent that there are unsaved changes
453
- onUnsavedChangesChange?.(true);
454
- }, [onUnsavedChangesChange]);
455
- // Remove missing letter
456
- const removeMissingLetter = (id) => {
457
- setMissingLetters((prev) => prev.filter((letter) => letter.id !== id));
458
- // Clear validation error when removing
459
- setValidationErrors((prev) => {
460
- const newErrors = { ...prev };
461
- delete newErrors[id];
462
- return newErrors;
463
- });
464
- };
465
- // Validate grid and missing letters
466
- const validateGrid = useCallback(() => {
467
- const errors = [];
468
- const fieldErrors = {};
469
- // Validate dimensions
470
- if (!rows || rows < 1 || rows > 20) {
471
- errors.push('Số hàng phải từ 1 đến 20');
472
- }
473
- if (!columns || columns < 1 || columns > 20) {
474
- errors.push('Số cột phải từ 1 đến 20');
475
- }
476
- // Check if all cells have value OR are marked as missing OR are disabled
477
- for (let row = 0; row < rows; row++) {
478
- for (let col = 0; col < columns; col++) {
479
- const cellValue = grid[row]?.[col];
480
- const isMissing = missingLetters.some((item) => item.row === row && item.column === col);
481
- const isDisabled = disabledCells.includes(`${row + 1}-${col + 1}`); // NEW
482
- // Cell must have value OR be marked as missing OR be disabled
483
- if (!cellValue && !isMissing && !isDisabled) {
484
- errors.push(`Ô ở vị trí (${row + 1}, ${col + 1}) phải có giá trị, được đánh dấu là còn thiếu, hoặc bị vô hiệu hóa`);
485
- }
486
- }
487
- }
488
- // Check if all marked missing letters have correctAnswer
489
- missingLetters.forEach((letter) => {
490
- if (!letter.correctAnswer || letter.correctAnswer.trim() === '') {
491
- fieldErrors[letter.id] = 'Vui lòng nhập đáp án đúng';
492
- }
493
- else if (letter.correctAnswer.length > 100) {
494
- fieldErrors[letter.id] = 'Đáp án không được vượt quá 100 ký tự';
495
- }
496
- });
497
- // Update validation errors state
498
- setValidationErrors(fieldErrors);
499
- setGeneralErrors(errors);
500
- return {
501
- isValid: errors.length === 0 && Object.keys(fieldErrors).length === 0,
502
- errors,
503
- };
504
- }, [grid, rows, columns, missingLetters]);
505
- // Check if a cell is invalid (empty and not marked as missing and not disabled)
506
- const isCellInvalid = useCallback((row, col) => {
507
- const cellValue = grid[row]?.[col];
508
- const isMissing = missingLetters.some((item) => item.row === row && item.column === col);
509
- const isDisabled = disabledCells.includes(`${row + 1}-${col + 1}`); // NEW
510
- // Cell is invalid if it has no value AND is not marked as missing AND is not disabled
511
- return !cellValue && !isMissing && !isDisabled;
512
- }, [grid, missingLetters, disabledCells] // NEW: Add disabledCells dependency
513
- );
514
- // Check if we should show validation highlights (when there are errors)
515
- const shouldShowValidation = useCallback(() => {
516
- return generalErrors.length > 0 || (externalErrors && externalErrors.length > 0);
517
- }, [generalErrors, externalErrors]);
518
- // Validate before switching to client mode
519
- const handlePreview = () => {
520
- const validation = validateGrid();
521
- if (!validation.isValid) {
522
- // Errors are already set in state by validateGrid
523
- return;
524
- }
525
- setIsClientMode(true);
526
- };
527
- // Handle save with validation
528
- const handleSave = () => {
529
- const validation = validateGrid();
530
- if (!validation.isValid) {
531
- toast.error('Vui lòng điền đầy đủ thông tin!');
532
- return;
533
- }
534
- // Additional validation
535
- if (!rows || rows < 1 || rows > 20) {
536
- toast.error('Số hàng phải từ 1 đến 20!');
537
- return;
538
- }
539
- if (!columns || columns < 1 || columns > 20) {
540
- toast.error('Số cột phải từ 1 đến 20!');
541
- return;
542
- }
543
- if (missingLetters.length === 0) {
544
- toast.error('Vui lòng đánh dấu ít nhất 1 ô còn thiếu!');
545
- return;
546
- }
547
- // Check all missing letters have answers
548
- for (let i = 0; i < missingLetters.length; i++) {
549
- const letter = missingLetters[i];
550
- if (!letter.correctAnswer || letter.correctAnswer.trim() === '') {
551
- toast.error(t('common.questionType.missingAnswer', { index: String(i + 1), row: String(letter.row + 1), col: String(letter.column + 1) }));
552
- return;
553
- }
554
- }
555
- // All validation passed - NEW: Output API format with answers object
556
- onSave?.({
557
- content: {
558
- grid,
559
- rows,
560
- columns,
561
- disabledCells, // NEW: Include disabled cells in save
562
- },
563
- answer: {
564
- answers: missingLetters.reduce((acc, item) => {
565
- acc[`${item.row + 1}-${item.column + 1}`] = item.correctAnswer;
566
- return acc;
567
- }, {}),
568
- caseSensitive: false,
569
- },
570
- points, // Include points in save
571
- explanation: explanation || '', // Include explanation in save
572
- });
573
- };
574
- // If in client mode, show preview using Client component
575
- if (isClientMode) {
576
- // Prepare data for Client component
577
- const questionData = {
578
- content: {
579
- grid,
580
- rows,
581
- columns,
582
- disabledCells,
583
- },
584
- answer: {
585
- answers: missingLetters.reduce((acc, item) => {
586
- acc[`${item.row + 1}-${item.column + 1}`] = item.correctAnswer;
587
- return acc;
588
- }, {}),
589
- caseSensitive: false,
590
- },
591
- };
592
- // Auto-fill correct answers for preview
593
- const correctAnswers = missingLetters.reduce((acc, item) => {
594
- acc[`${item.row + 1}-${item.column + 1}`] = item.correctAnswer;
595
- return acc;
596
- }, {});
597
- return (_jsx("div", { className: "space-y-6", children: _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx(CardTitle, { children: "Fill Missing Words In Grid (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(CardContent, { children: _jsx(FillMissingWordsInGridClient, { questionData: questionData, isReviewMode: true, userAnswer: correctAnswers, explanation: explanation }) })] }) }));
598
- }
599
- return (_jsxs("div", { className: "space-y-6", children: [_jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50", children: [_jsx("div", { className: "h-1.5 bg-gradient-to-r from-cyan-500 via-blue-500 to-indigo-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-cyan-500 to-blue-600 shadow-md", children: _jsx(Grid3X3, { className: "h-5 w-5 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-xl font-bold text-gray-900", children: "\u0110i\u1EC1n t\u1EEB c\u00F2n thi\u1EBFu v\u00E0o grid" }), _jsx("p", { className: "text-sm text-gray-500", children: "T\u1EA1o grid v\u00E0 nh\u1EADp c\u00E1c t\u1EEB, \u0111\u00E1nh d\u1EA5u c\u00E1c \u00F4 c\u00F2n thi\u1EBFu" })] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: handlePreview, className: "gap-2 border-blue-200 text-blue-600 hover:bg-blue-50 hover:text-blue-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-cyan-200 bg-gradient-to-r from-cyan-50 to-blue-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-cyan-100", children: _jsx(Lightbulb, { className: "h-4 w-4 text-cyan-600" }) }), _jsxs("div", { className: "text-sm text-cyan-900", children: [_jsx("p", { className: "font-semibold text-cyan-800", children: "H\u01B0\u1EDBng d\u1EABn t\u1EA1o c\u00E2u h\u1ECFi" }), _jsxs("ul", { className: "mt-2 space-y-1.5 text-cyan-700", children: [_jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-cyan-400" }), "Nh\u1EADp t\u1EEB v\u00E0o c\u00E1c \u00F4 trong grid (4 h\u00E0ng \u00D7 7 c\u1ED9t)"] }), _jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-cyan-400" }), _jsxs("span", { children: [_jsx("strong", { children: "Double-click:" }), " \u0110\u00E1nh d\u1EA5u \u00F4 c\u00F2n thi\u1EBFu (m\u00E0u xanh d\u01B0\u01A1ng)"] })] }), _jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-cyan-400" }), _jsxs("span", { children: [_jsx("strong", { children: "Triple-click:" }), " V\u00F4 hi\u1EC7u h\u00F3a \u00F4 (m\u00E0u \u0111\u1ECF - kh\u00F4ng c\u1EA7n \u0111i\u1EC1n)"] })] }), _jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-cyan-400" }), "Nh\u1EADp \u0111\u00E1p \u00E1n \u0111\u00FAng cho m\u1ED7i \u00F4 c\u00F2n thi\u1EBFu"] })] })] })] }) }), _jsx("div", { className: "rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-4", children: _jsx(PointsInput, { defaultValue: points, onChange: (e) => setPoints(Number(e.target.value) || 1) }, `points-input-${points}`) }), _jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "flex items-center justify-between", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-indigo-100", children: _jsx(Grid3X3, { className: "h-3.5 w-3.5 text-indigo-600" }) }), _jsx(Label, { className: "text-base font-semibold text-gray-800", children: "Grid - Nh\u1EADp t\u1EEB v\u00E0o c\u00E1c \u00F4" })] }) }), _jsx("div", { className: "overflow-x-auto rounded-xl border-2 border-indigo-200 bg-gradient-to-br from-indigo-50/50 to-blue-50/50 p-5 shadow-sm", children: _jsxs("div", { className: "inline-block min-w-full", children: [_jsxs("div", { className: "grid gap-1 mb-1", style: { gridTemplateColumns: `auto repeat(${columns}, minmax(0, 1fr))` }, children: [_jsx("div", { className: "h-8" }), Array.from({ length: columns }, (_, i) => (_jsx("div", { className: "flex h-8 items-center justify-center text-xs font-semibold text-gray-600", children: i + 1 }, `col-header-${i}`)))] }), _jsx("div", { className: "grid gap-1", children: grid.map((row, rowIndex) => (_jsxs("div", { className: "grid gap-1", style: { gridTemplateColumns: `auto repeat(${columns}, minmax(0, 1fr))` }, children: [_jsx("div", { className: "flex h-12 items-center justify-center text-xs font-semibold text-gray-600 border-2 border-transparent", children: rowIndex + 1 }), row.map((cell, colIndex) => {
600
- const isMissing = missingLetters.some((item) => item.row === rowIndex && item.column === colIndex);
601
- const isDisabled = disabledCells.includes(`${rowIndex + 1}-${colIndex + 1}`); // NEW
602
- const isInvalid = shouldShowValidation() && isCellInvalid(rowIndex, colIndex);
603
- return (_jsxs("div", { className: `relative flex h-12 items-center justify-center border-2 rounded transition-colors ${isDisabled
604
- ? 'border-red-400 bg-red-100 hover:bg-red-150 cursor-pointer' // NEW: Red styling for disabled
605
- : isMissing
606
- ? 'border-blue-500 bg-blue-50 hover:bg-blue-100 cursor-pointer'
607
- : isInvalid
608
- ? 'border-red-500 bg-red-50 hover:bg-red-100'
609
- : cell
610
- ? 'border-gray-400 bg-white hover:bg-gray-50'
611
- : 'border-gray-300 bg-gray-100 hover:bg-gray-200'}`, onClick: (e) => {
612
- // Triple-click to toggle disabled
613
- if (e.detail === 3) {
614
- e.preventDefault();
615
- e.stopPropagation();
616
- toggleDisabledCell(rowIndex, colIndex);
617
- }
618
- }, onDoubleClick: (e) => {
619
- e.preventDefault();
620
- e.stopPropagation();
621
- if (!isDisabled) {
622
- toggleMissingLetter(rowIndex, colIndex);
623
- }
624
- }, children: [_jsx(Input, { type: "text", maxLength: 10, value: cell || '', onChange: (e) => {
625
- if (!isMissing && !isDisabled) { // NEW: Also check disabled
626
- // Trim whitespace and limit length
627
- const value = e.target.value.trim().slice(0, 10);
628
- updateGridCell(rowIndex, colIndex, value);
629
- }
630
- }, onDoubleClick: (e) => {
631
- e.preventDefault();
632
- e.stopPropagation();
633
- if (!isDisabled) { // NEW: Only toggle missing if not disabled
634
- toggleMissingLetter(rowIndex, colIndex);
635
- }
636
- }, disabled: isMissing || isDisabled, readOnly: isMissing || isDisabled, className: `h-full border-0 bg-transparent text-center text-lg font-semibold focus:ring-2 focus:ring-blue-500 ${isMissing || isDisabled ? 'cursor-not-allowed pointer-events-none' : ''} ${isDisabled ? 'text-gray-500' : isMissing ? 'text-blue-600' : ''}`, placeholder: "" }), isMissing && (_jsx("div", { className: "absolute -top-1 -right-1 flex h-5 w-5 items-center justify-center rounded-full bg-blue-500 text-xs font-bold text-white", children: "?" })), isDisabled && (_jsx("div", { className: "absolute -top-1 -right-1 flex h-5 w-5 items-center justify-center rounded-full bg-red-500 text-xs font-bold text-white", children: "\uD83D\uDD12" }))] }, `${rowIndex}-${colIndex}`));
637
- })] }, `row-${rowIndex}`))) })] }) })] }), missingLetters.length > 0 && (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-blue-100", children: _jsx(CheckCircle2, { className: "h-3.5 w-3.5 text-blue-600" }) }), _jsxs(Label, { className: "text-base font-semibold text-gray-800", children: ["Danh s\u00E1ch \u00F4 c\u00F2n thi\u1EBFu", _jsx("span", { className: "ml-2 rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-700", children: missingLetters.length })] })] }), _jsx("div", { className: "space-y-3", children: [...missingLetters]
638
- .sort((a, b) => {
639
- if (a.row !== b.row)
640
- return a.row - b.row;
641
- return a.column - b.column;
642
- })
643
- .map((letter, index) => {
644
- const cellValue = grid[letter.row]?.[letter.column];
645
- return (_jsx("div", { className: "rounded-xl border-2 border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-4 shadow-sm transition-all hover:border-blue-300 hover:shadow-md", children: _jsxs("div", { className: "flex items-start gap-4", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-blue-500 text-sm font-bold text-white shadow-sm", children: index + 1 }), _jsxs("div", { className: "flex-1 space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("span", { className: "text-sm font-semibold text-gray-700", children: ["V\u1ECB tr\u00ED (", letter.row + 1, ", ", letter.column + 1, ")"] }), cellValue && (_jsxs("span", { className: "rounded-full bg-gray-100 px-2 py-0.5 text-xs text-gray-500", children: ["Hi\u1EC7n t\u1EA1i: \"", cellValue, "\""] }))] }), _jsxs("div", { children: [_jsxs(Label, { htmlFor: `answer-${letter.id}`, className: "text-sm font-medium text-gray-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Input, { id: `answer-${letter.id}`, type: "text", maxLength: 100, value: letter.correctAnswer, onChange: (e) => updateMissingLetter(letter.id, 'correctAnswer', e.target.value.trim()), onBlur: () => {
646
- if (!letter.correctAnswer || letter.correctAnswer.trim() === '') {
647
- setValidationErrors((prev) => ({
648
- ...prev,
649
- [letter.id]: 'Vui lòng nhập đáp án đúng',
650
- }));
651
- }
652
- else if (letter.correctAnswer.length > 100) {
653
- setValidationErrors((prev) => ({
654
- ...prev,
655
- [letter.id]: 'Đáp án không được vượt quá 100 ký tự',
656
- }));
657
- }
658
- }, placeholder: "Nh\u1EADp \u0111\u00E1p \u00E1n \u0111\u00FAng...", className: `mt-1.5 border-gray-200 bg-white transition-all focus:border-blue-300 focus:ring-blue-200 ${validationErrors[letter.id]
659
- ? 'border-red-400 focus:border-red-500 focus:ring-red-200'
660
- : ''}` }), validationErrors[letter.id] && (_jsx("p", { className: "mt-1.5 text-sm text-red-600", children: validationErrors[letter.id] }))] })] }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeMissingLetter(letter.id), className: "h-8 w-8 text-red-400 hover:bg-red-50 hover:text-red-600", children: _jsx(Trash2, { className: "h-4 w-4" }) })] }) }, letter.id));
661
- }) })] }))] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-amber-400 to-orange-400" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm", children: _jsx(BookOpen, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch" }), _jsx("span", { className: "inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600", children: "Kh\u00F4ng b\u1EAFt bu\u1ED9c" })] }), _jsx("p", { className: "text-sm text-gray-500 mt-0.5", children: "Gi\u1EA3i th\u00EDch cho \u0111\u00E1p \u00E1n \u0111\u00FAng \u2014 hi\u1EC3n th\u1ECB sau khi h\u1ECDc sinh ho\u00E0n th\u00E0nh c\u00E2u h\u1ECFi" })] })] }) }), _jsx(CardContent, { children: _jsx(Textarea, { id: "explanation", value: explanation, onChange: (e) => {
662
- setExplanation(e.target.value);
663
- onUnsavedChangesChange?.(true);
664
- }, placeholder: "VD: C\u00E2u tr\u1EA3 l\u1EDDi \u0111\u00FAng l\u00E0... v\u00EC...", rows: 3, className: "min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none" }) })] })] }));
665
- }
666
- // Export with both new and legacy names for backward compatibility
667
- export function FillMissingWordsInGridCreator(props) {
668
- return _jsx(FillMissingWordsInGridCreatorContent, { ...props });
669
- }
670
- // Legacy export for backward compatibility
671
- export const WriteMissingLettersCreator = FillMissingWordsInGridCreator;
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useState as t,useRef as n,useEffect as s,useCallback as i}from"react";import{Button as l}from"../../../../components/ui/button";import{Label as o}from"../../../../components/ui/label";import{Input as c}from"../../../../components/ui/input";import{Textarea as a}from"../../../../components/ui/textarea";import{Card as d,CardContent as m,CardHeader as u,CardTitle as h}from"../../../../components/ui/card";import{PointsInput as g}from"../../../../components/ui/points-input";import{Pencil as p,Eye as f,Trash2 as b,Grid3X3 as w,Lightbulb as x,CheckCircle2 as N,BookOpen as v}from"lucide-react";import{toast as y}from"sonner";import{t as $}from"../../../../shared/lib/i18n";import{FillMissingWordsInGridClient as C}from"./FillMissingWordsInGridClient";function A({initialData:y,onSave:$,onCancel:A,onChange:j,onUnsavedChangesChange:k,externalErrors:O=[]}){const[S,D]=t(4),[M,J]=t(7),[V,G]=t(y?.points||1),[I,L]=t(y?.explanation||""),[F,T]=t(()=>y?.content?.grid?y.content.grid:Array(S).fill(null).map(()=>Array(M).fill(null))),[W,P]=t(()=>{if(y?.answer?.missingLetters)return console.log("📝 Using missingLetters format"),y.answer.missingLetters.map((e,r)=>{const[t,n]=e.position.split("-").map(Number),s=(t||1)-1,i=(n||1)-1;return{id:`word-${r}-${Date.now()}`,row:s,column:i,correctAnswer:e.correctAnswer}});if(y?.answer?.answers&&"object"==typeof y.answer.answers){const e=y.answer.answers;return Object.entries(e).map(([e,r],t)=>{const[n,s]=e.split("-").map(Number),i=(n||1)-1,l=(s||1)-1;return{id:`word-${t}-${Date.now()}-${Math.random()}`,row:i,column:l,correctAnswer:r}})}return[]}),[z,E]=t(()=>y?.content?.disabledCells?y.content.disabledCells:[]),q=n(y),H=n(!0),U=n(j),B=n(""),K=n(!1),Q=n(!1),R=n(void 0),X=n(!1);s(()=>{U.current=j},[j]),s(()=>{!X.current&&y?.content?.grid&&(R.current=JSON.parse(JSON.stringify(y)),X.current=!0,console.log("📌 [FillMissingWordsInGridCreator] Saved original data from API:",R.current))},[y]),s(()=>{if(H.current)return;const e=R.current;if(X.current&&e?.content?.grid){const r=JSON.stringify(F)!==JSON.stringify(e.content.grid),t=[...z||[]].sort(),n=[...e.content.disabledCells||[]].sort(),s=JSON.stringify(t)!==JSON.stringify(n),i=("string"==typeof V?Number(V):V)!==("string"==typeof e?.points?Number(e.points):e?.points||1),l=e?.answer?.answers||{},o=W.reduce((e,r)=>(e[`${r.row+1}-${r.column+1}`]=r.correctAnswer,e),{}),c=Object.keys(l).sort().reduce((e,r)=>(e[r]=l[r],e),{}),a=Object.keys(o).sort().reduce((e,r)=>(e[r]=o[r],e),{}),d=JSON.stringify(a)!==JSON.stringify(c),m=r||s||i||d;k?.(m)}else{const e=F.some(e=>e.some(e=>null!==e&&""!==e)),r=W.length>0,t=z.length>0,n=e||r||t;k?.(n)}},[F,W,z,V,k]),s(()=>{if(JSON.stringify(q.current)!==JSON.stringify(y)){if(H.current=!0,q.current=y,y){if(void 0!==y.content?.grid&&T(y.content.grid),y.answer?.missingLetters?P(e=>{const r=new Map(e.map(e=>[`${e.row}-${e.column}`,e.id]));return y.answer.missingLetters.map((e,t)=>{const[n,s]=e.position.split("-").map(Number),i=(n||1)-1,l=(s||1)-1,o=`${i}-${l}`;return{id:r.get(o)||`word-${t}-${Date.now()}-${Math.random()}`,row:i,column:l,correctAnswer:e.correctAnswer}})}):y.answer?.answers&&P(e=>{const r=new Map(e.map(e=>[`${e.row}-${e.column}`,e.id]));return Object.entries(y.answer.answers).map(([e,t],n)=>{const[s,i]=e.split("-").map(Number),l=(s||1)-1,o=(i||1)-1,c=`${l}-${o}`;return{id:r.get(c)||`word-${n}-${Date.now()}-${Math.random()}`,row:l,column:o,correctAnswer:t}})}),void 0!==y.content?.disabledCells){const e=Array.isArray(y.content.disabledCells)?y.content.disabledCells:[];E(e)}if(void 0!==y.points){const e=y.points;G("string"==typeof e?Number(e):e)}void 0!==y.explanation&&L(y.explanation)}setTimeout(()=>{H.current=!1,K.current||(K.current=!0,Q.current=!0,y?.content?.grid&&k?.(!1))},100)}else K.current||setTimeout(()=>{H.current=!1,K.current=!0,Q.current=!0},100)},[y,S,M,k]),s(()=>{T(e=>{const r=e.length,t=e[0]?.length||0;if(r===S&&t===M)return e;const n=[];for(let r=0;r<S;r++){const t=[];for(let n=0;n<M;n++)r<e.length&&n<e[r]?.length?t.push(e[r][n]):t.push(null);n.push(t)}return n}),H.current||P(e=>{const r=e.filter(e=>e.row<S&&e.column<M),t=e.length-r.length;return t>0&&se(e=>[...e.filter(e=>!e.includes("ô còn thiếu ngoài phạm vi grid")),`Đã xóa ${t} ô còn thiếu ngoài phạm vi grid mới (${S}x${M})`]),r})},[S,M]),s(()=>{if(H.current)return;const e={content:{grid:F,rows:S,columns:M,disabledCells:[...z].sort()},answer:{answers:W.reduce((e,r)=>(e[`${r.row+1}-${r.column+1}`]=r.correctAnswer,e),{}),caseSensitive:!1},points:"string"==typeof V?Number(V):V,explanation:I||""},r=JSON.stringify(e);B.current!==r&&(B.current=r,U.current?.(e))},[F,S,M,W,z,V,I]),s(()=>{if(O&&O.length>0){const e={},r=[];O.forEach(t=>{const n=t.match(/Ô còn thiếu #(\d+) ở vị trí \((\d+), (\d+)\) phải có đáp án đúng/);if(n){const[,,r,t]=n,s=parseInt(r)-1,i=parseInt(t)-1,l=W.find(e=>e.row===s&&e.column===i);l&&(e[l.id]="Vui lòng nhập đáp án đúng")}else r.push(t)}),ee(r=>({...r,...e})),r.length>0&&se(e=>e.length===r.length&&e.every((e,t)=>e===r[t])?e:r)}else se(e=>0===e.length?e:[])},[O,W]);const[Y,Z]=t(!1),[_,ee]=t({}),[re,te]=t({}),[ne,se]=t([]),ie=i((e,r,t)=>{T(n=>n.map((n,s)=>n.map((n,i)=>s===e&&i===r?t||null:n))),t&&""!==t.trim()&&se(t=>t.filter(t=>!t.includes(`vị trí (${e+1}, ${r+1})`))),k?.(!0)},[k]),le=i((e,r)=>{const t=`${e}-${r}`,n=W.find(e=>`${e.row}-${e.column}`===t);if(n)P(e=>e.filter(e=>e.id!==n.id)),ee(e=>{const r={...e};return delete r[n.id],r});else{const t=F[e]?.[r],n={id:`word-${Date.now()}-${Math.random()}`,row:e,column:r,correctAnswer:t||""};P(e=>[...e,n]),ie(e,r,""),se(t=>t.filter(t=>!t.includes(`vị trí (${e+1}, ${r+1})`)))}k?.(!0)},[F,W,ie,k]),oe=i((e,r)=>{const t=`${e+1}-${r+1}`;E(n=>n.includes(t)?n.filter(e=>e!==t):(ie(e,r,""),P(t=>t.filter(t=>!(t.row===e&&t.column===r))),[...n,t])),se(t=>t.filter(t=>!t.includes(`vị trí (${e+1}, ${r+1})`))),k?.(!0)},[ie,k]),ce=i((e,r,t)=>{P(n=>n.map(n=>n.id===e?{...n,[r]:t}:n)),"correctAnswer"===r&&ee(r=>{const n={...r};return"string"==typeof t&&""!==t.trim()&&delete n[e],n}),k?.(!0)},[k]),ae=i(()=>{const e=[],r={};(!S||S<1||S>20)&&e.push("Số hàng phải từ 1 đến 20"),(!M||M<1||M>20)&&e.push("Số cột phải từ 1 đến 20");for(let r=0;r<S;r++)for(let t=0;t<M;t++){const n=F[r]?.[t],s=W.some(e=>e.row===r&&e.column===t),i=z.includes(`${r+1}-${t+1}`);n||s||i||e.push(`Ô ở vị trí (${r+1}, ${t+1}) phải có giá trị, được đánh dấu là còn thiếu, hoặc bị vô hiệu hóa`)}return W.forEach(e=>{e.correctAnswer&&""!==e.correctAnswer.trim()?e.correctAnswer.length>100&&(r[e.id]="Đáp án không được vượt quá 100 ký tự"):r[e.id]="Vui lòng nhập đáp án đúng"}),ee(r),se(e),{isValid:0===e.length&&0===Object.keys(r).length,errors:e}},[F,S,M,W]),de=i((e,r)=>{const t=F[e]?.[r],n=W.some(t=>t.row===e&&t.column===r),s=z.includes(`${e+1}-${r+1}`);return!t&&!n&&!s},[F,W,z]),me=i(()=>ne.length>0||O&&O.length>0,[ne,O]);if(Y){const t={content:{grid:F,rows:S,columns:M,disabledCells:z},answer:{answers:W.reduce((e,r)=>(e[`${r.row+1}-${r.column+1}`]=r.correctAnswer,e),{}),caseSensitive:!1}},n=W.reduce((e,r)=>(e[`${r.row+1}-${r.column+1}`]=r.correctAnswer,e),{});return e("div",{className:"space-y-6",children:r(d,{children:[e(u,{children:r("div",{className:"flex items-center justify-between",children:[r("div",{children:[e(h,{children:"Fill Missing Words In Grid (Preview)"}),e("p",{className:"mt-1 text-sm text-gray-500",children:"Chế độ xem trước - Hiển thị đáp án đúng"})]}),r(l,{variant:"outline",size:"sm",onClick:()=>Z(!1),children:[e(p,{className:"mr-2 h-4 w-4"}),"Quay lại Edit Mode"]})]})}),e(m,{children:e(C,{questionData:t,isReviewMode:!0,userAnswer:n,explanation:I})})]})})}return r("div",{className:"space-y-6",children:[r(d,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50",children:[e("div",{className:"h-1.5 bg-gradient-to-r from-cyan-500 via-blue-500 to-indigo-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-cyan-500 to-blue-600 shadow-md",children:e(w,{className:"h-5 w-5 text-white"})}),r("div",{children:[e(h,{className:"text-xl font-bold text-gray-900",children:"Điền từ còn thiếu vào grid"}),e("p",{className:"text-sm text-gray-500",children:"Tạo grid và nhập các từ, đánh dấu các ô còn thiếu"})]})]}),r(l,{variant:"outline",size:"sm",onClick:()=>{ae().isValid&&Z(!0)},className:"gap-2 border-blue-200 text-blue-600 hover:bg-blue-50 hover:text-blue-700",children:[e(f,{className:"h-4 w-4"}),"Xem trước"]})]})}),r(m,{className:"space-y-6",children:[e("div",{className:"rounded-xl border border-cyan-200 bg-gradient-to-r from-cyan-50 to-blue-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-cyan-100",children:e(x,{className:"h-4 w-4 text-cyan-600"})}),r("div",{className:"text-sm text-cyan-900",children:[e("p",{className:"font-semibold text-cyan-800",children:"Hướng dẫn tạo câu hỏi"}),r("ul",{className:"mt-2 space-y-1.5 text-cyan-700",children:[r("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-cyan-400"}),"Nhập từ vào các ô trong grid (4 hàng × 7 cột)"]}),r("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-cyan-400"}),r("span",{children:[e("strong",{children:"Double-click:"})," Đánh dấu ô còn thiếu (màu xanh dương)"]})]}),r("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-cyan-400"}),r("span",{children:[e("strong",{children:"Triple-click:"})," Vô hiệu hóa ô (màu đỏ - không cần điền)"]})]}),r("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-cyan-400"}),"Nhập đáp án đúng cho mỗi ô còn thiếu"]})]})]})]})}),e("div",{className:"rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-4",children:e(g,{defaultValue:V,onChange:e=>G(Number(e.target.value)||1)},`points-input-${V}`)}),r("div",{className:"space-y-4",children:[e("div",{className:"flex items-center justify-between",children:r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-indigo-100",children:e(w,{className:"h-3.5 w-3.5 text-indigo-600"})}),e(o,{className:"text-base font-semibold text-gray-800",children:"Grid - Nhập từ vào các ô"})]})}),e("div",{className:"overflow-x-auto rounded-xl border-2 border-indigo-200 bg-gradient-to-br from-indigo-50/50 to-blue-50/50 p-5 shadow-sm",children:r("div",{className:"inline-block min-w-full",children:[r("div",{className:"grid gap-1 mb-1",style:{gridTemplateColumns:`auto repeat(${M}, minmax(0, 1fr))`},children:[e("div",{className:"h-8"}),Array.from({length:M},(r,t)=>e("div",{className:"flex h-8 items-center justify-center text-xs font-semibold text-gray-600",children:t+1},`col-header-${t}`))]}),e("div",{className:"grid gap-1",children:F.map((t,n)=>r("div",{className:"grid gap-1",style:{gridTemplateColumns:`auto repeat(${M}, minmax(0, 1fr))`},children:[e("div",{className:"flex h-12 items-center justify-center text-xs font-semibold text-gray-600 border-2 border-transparent",children:n+1}),t.map((t,s)=>{const i=W.some(e=>e.row===n&&e.column===s),l=z.includes(`${n+1}-${s+1}`),o=me()&&de(n,s);return r("div",{className:"relative flex h-12 items-center justify-center border-2 rounded transition-colors "+(l?"border-red-400 bg-red-100 hover:bg-red-150 cursor-pointer":i?"border-blue-500 bg-blue-50 hover:bg-blue-100 cursor-pointer":o?"border-red-500 bg-red-50 hover:bg-red-100":t?"border-gray-400 bg-white hover:bg-gray-50":"border-gray-300 bg-gray-100 hover:bg-gray-200"),onClick:e=>{3===e.detail&&(e.preventDefault(),e.stopPropagation(),oe(n,s))},onDoubleClick:e=>{e.preventDefault(),e.stopPropagation(),l||le(n,s)},children:[e(c,{type:"text",maxLength:10,value:t||"",onChange:e=>{if(!i&&!l){const r=e.target.value.trim().slice(0,10);ie(n,s,r)}},onDoubleClick:e=>{e.preventDefault(),e.stopPropagation(),l||le(n,s)},disabled:i||l,readOnly:i||l,className:`h-full border-0 bg-transparent text-center text-lg font-semibold focus:ring-2 focus:ring-blue-500 ${i||l?"cursor-not-allowed pointer-events-none":""} ${l?"text-gray-500":i?"text-blue-600":""}`,placeholder:""}),i&&e("div",{className:"absolute -top-1 -right-1 flex h-5 w-5 items-center justify-center rounded-full bg-blue-500 text-xs font-bold text-white",children:"?"}),l&&e("div",{className:"absolute -top-1 -right-1 flex h-5 w-5 items-center justify-center rounded-full bg-red-500 text-xs font-bold text-white",children:"🔒"})]},`${n}-${s}`)})]},`row-${n}`))})]})})]}),W.length>0&&r("div",{className:"space-y-4",children:[r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-blue-100",children:e(N,{className:"h-3.5 w-3.5 text-blue-600"})}),r(o,{className:"text-base font-semibold text-gray-800",children:["Danh sách ô còn thiếu",e("span",{className:"ml-2 rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-700",children:W.length})]})]}),e("div",{className:"space-y-3",children:[...W].sort((e,r)=>e.row!==r.row?e.row-r.row:e.column-r.column).map((t,n)=>{const s=F[t.row]?.[t.column];return e("div",{className:"rounded-xl border-2 border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-4 shadow-sm transition-all hover:border-blue-300 hover:shadow-md",children:r("div",{className:"flex items-start gap-4",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-blue-500 text-sm font-bold text-white shadow-sm",children:n+1}),r("div",{className:"flex-1 space-y-3",children:[r("div",{className:"flex items-center gap-2",children:[r("span",{className:"text-sm font-semibold text-gray-700",children:["Vị trí (",t.row+1,", ",t.column+1,")"]}),s&&r("span",{className:"rounded-full bg-gray-100 px-2 py-0.5 text-xs text-gray-500",children:['Hiện tại: "',s,'"']})]}),r("div",{children:[r(o,{htmlFor:`answer-${t.id}`,className:"text-sm font-medium text-gray-700",children:["Đáp án đúng ",e("span",{className:"text-red-500",children:"*"})]}),e(c,{id:`answer-${t.id}`,type:"text",maxLength:100,value:t.correctAnswer,onChange:e=>ce(t.id,"correctAnswer",e.target.value.trim()),onBlur:()=>{t.correctAnswer&&""!==t.correctAnswer.trim()?t.correctAnswer.length>100&&ee(e=>({...e,[t.id]:"Đáp án không được vượt quá 100 ký tự"})):ee(e=>({...e,[t.id]:"Vui lòng nhập đáp án đúng"}))},placeholder:"Nhập đáp án đúng...",className:"mt-1.5 border-gray-200 bg-white transition-all focus:border-blue-300 focus:ring-blue-200 "+(_[t.id]?"border-red-400 focus:border-red-500 focus:ring-red-200":"")}),_[t.id]&&e("p",{className:"mt-1.5 text-sm text-red-600",children:_[t.id]})]})]}),e(l,{type:"button",variant:"ghost",size:"icon",onClick:()=>{return e=t.id,P(r=>r.filter(r=>r.id!==e)),void ee(r=>{const t={...r};return delete t[e],t});var e},className:"h-8 w-8 text-red-400 hover:bg-red-50 hover:text-red-600",children:e(b,{className:"h-4 w-4"})})]})},t.id)})})]})]})]}),r(d,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-amber-400 to-orange-400"}),e(u,{className:"pb-4",children:r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm",children:e(v,{className:"h-4 w-4 text-white"})}),r("div",{children:[r("div",{className:"flex items-center gap-2",children:[e(h,{className:"text-lg font-semibold text-gray-800",children:"Giải thích"}),e("span",{className:"inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600",children:"Không bắt buộc"})]}),e("p",{className:"text-sm text-gray-500 mt-0.5",children:"Giải thích cho đáp án đúng — hiển thị sau khi học sinh hoàn thành câu hỏi"})]})]})}),e(m,{children:e(a,{id:"explanation",value:I,onChange:e=>{L(e.target.value),k?.(!0)},placeholder:"VD: Câu trả lời đúng là... vì...",rows:3,className:"min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none"})})]})]})}export function FillMissingWordsInGridCreator(r){return e(A,{...r})}export const WriteMissingLettersCreator=FillMissingWordsInGridCreator;