@tinyweb_dev/oe-exam-sdk 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (514) hide show
  1. package/dist/api/exam-create.js +1 -179
  2. package/dist/api/exam-create.types.js +1 -1
  3. package/dist/api/exam-questions.d.ts +1 -1
  4. package/dist/api/exam-questions.js +1 -98
  5. package/dist/api/exam-questions.types.d.ts +45 -1
  6. package/dist/api/exam-questions.types.js +1 -1
  7. package/dist/api/exam-taking.js +1 -73
  8. package/dist/api/exam-taking.types.js +1 -1
  9. package/dist/api/index.js +1 -6
  10. package/dist/api/result-review.js +1 -50
  11. package/dist/api/result-review.types.js +1 -1
  12. package/dist/components/CueCardEditor.js +1 -58
  13. package/dist/components/common/ResolvedImage.js +1 -15
  14. package/dist/components/exams/CompletionDialog.d.ts +8 -0
  15. package/dist/components/exams/CompletionDialog.js +1 -0
  16. package/dist/components/exams/CreateExamInfoForm.d.ts +4 -1
  17. package/dist/components/exams/CreateExamInfoForm.js +1 -35
  18. package/dist/components/exams/CreateExamPageContainer.d.ts +3 -0
  19. package/dist/components/exams/CreateExamPageContainer.js +1 -103
  20. package/dist/components/exams/ExamCreator.d.ts +9 -3
  21. package/dist/components/exams/ExamCreator.js +1 -89
  22. package/dist/components/exams/ExamPartTabs.d.ts +1 -1
  23. package/dist/components/exams/ExamPartTabs.js +1 -40
  24. package/dist/components/exams/ExamPreviewDialog.d.ts +20 -0
  25. package/dist/components/exams/ExamPreviewDialog.js +1 -0
  26. package/dist/components/exams/ExamQuestionGrid.d.ts +1 -1
  27. package/dist/components/exams/ExamQuestionGrid.js +1 -18
  28. package/dist/components/exams/ExamQuestionsPageContainer.d.ts +3 -0
  29. package/dist/components/exams/ExamQuestionsPageContainer.js +1 -90
  30. package/dist/components/exams/ExamViewDialog.d.ts +24 -0
  31. package/dist/components/exams/ExamViewDialog.js +1 -0
  32. package/dist/components/exams/PartAudioUploader.js +1 -9
  33. package/dist/components/exams/TemplateSelector.js +1 -66
  34. package/dist/components/exams/UnsavedChangesDialog.d.ts +8 -0
  35. package/dist/components/exams/UnsavedChangesDialog.js +1 -0
  36. package/dist/components/exams/api.d.ts +1 -1
  37. package/dist/components/exams/api.js +1 -3
  38. package/dist/components/exams/create-exam.texts.d.ts +58 -0
  39. package/dist/components/exams/create-exam.texts.js +1 -0
  40. package/dist/components/exams/create-exam.validation.js +1 -18
  41. package/dist/components/exams/exam-question-status.d.ts +9 -0
  42. package/dist/components/exams/exam-question-status.js +1 -0
  43. package/dist/components/exams/exam-template.utils.js +1 -18
  44. package/dist/components/exams/index.d.ts +11 -1
  45. package/dist/components/exams/index.js +1 -10
  46. package/dist/components/exams/take/ExamTakingPageContainer.js +1 -153
  47. package/dist/components/exams/take/GenericQuestionRenderer.js +1 -14
  48. package/dist/components/exams/take/exam-taking.utils.js +1 -88
  49. package/dist/components/exams/take/hooks/useSpeakingRecorder.js +1 -24
  50. package/dist/components/exams/take/index.js +1 -2
  51. package/dist/components/exams/take/types.js +1 -1
  52. package/dist/components/index.js +1 -42
  53. package/dist/components/questions/_shared/config/question-types.config.js +1 -333
  54. package/dist/components/questions/_shared/constants/color-palette.js +1 -21
  55. package/dist/components/questions/_shared/hooks/useAnswerGroupQuestionGroup.js +1 -105
  56. package/dist/components/questions/_shared/hooks/useBasicQuestionGroup.js +1 -222
  57. package/dist/components/questions/_shared/hooks/useCompareImagesGroup.js +1 -140
  58. package/dist/components/questions/_shared/hooks/useImageQuestionGroup.js +1 -123
  59. package/dist/components/questions/_shared/hooks/useInfoExchangeGroup.js +1 -50
  60. package/dist/components/questions/_shared/hooks/useQuestionFormState.js +1 -54
  61. package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +1 -123
  62. package/dist/components/questions/_shared/hooks/useQuestionListGroup.js +1 -121
  63. package/dist/components/questions/_shared/hooks/useQuestionOnChange.js +1 -70
  64. package/dist/components/questions/_shared/hooks/useSceneQuestionGroup.js +1 -194
  65. package/dist/components/questions/_shared/index.js +1 -7
  66. package/dist/components/questions/_shared/types/answer-the-question.type.js +1 -1
  67. package/dist/components/questions/_shared/types/arrange-letters-into-words.type.js +1 -1
  68. package/dist/components/questions/_shared/types/choose-correct-adjective.type.js +1 -3
  69. package/dist/components/questions/_shared/types/color-region.config.js +1 -1
  70. package/dist/components/questions/_shared/types/fill-in-blank.type.js +1 -1
  71. package/dist/components/questions/_shared/types/fill-missing-words-in-grid.type.js +1 -1
  72. package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +1 -1
  73. package/dist/components/questions/_shared/types/index.js +1 -12
  74. package/dist/components/questions/_shared/types/label-the-picture.type.js +1 -1
  75. package/dist/components/questions/_shared/types/listen-and-choose-from-answer-group.type.js +1 -1
  76. package/dist/components/questions/_shared/types/listen-and-choose-objects-in-scene.type.js +1 -1
  77. package/dist/components/questions/_shared/types/listen-and-drag-objects-into-scene.type.js +1 -1
  78. package/dist/components/questions/_shared/types/listen-and-speak-answer.type.js +1 -1
  79. package/dist/components/questions/_shared/types/listen-and-speak-compare-images.type.js +1 -1
  80. package/dist/components/questions/_shared/types/listen-and-speak-image-group.type.js +1 -1
  81. package/dist/components/questions/_shared/types/listen-and-speak-info-exchange.type.js +1 -1
  82. package/dist/components/questions/_shared/types/listen-and-speak-odd-one-out.type.js +1 -3
  83. package/dist/components/questions/_shared/types/listen-and-speak-question-list.type.js +1 -1
  84. package/dist/components/questions/_shared/types/listen-and-speak-with-story-images.type.js +1 -3
  85. package/dist/components/questions/_shared/types/listen-and-tick-answer.type.js +1 -1
  86. package/dist/components/questions/_shared/types/listening-drawline.type.js +1 -1
  87. package/dist/components/questions/_shared/types/look-and-choose-answer.type.js +1 -1
  88. package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +1 -1
  89. package/dist/components/questions/_shared/types/match-by-writing-answer.type.js +1 -1
  90. package/dist/components/questions/_shared/types/multiple-choice.type.js +1 -1
  91. package/dist/components/questions/_shared/types/question-group.type.js +1 -11
  92. package/dist/components/questions/_shared/types/read-and-color-objects.type.js +1 -1
  93. package/dist/components/questions/_shared/types/read-displayed-content.type.js +1 -1
  94. package/dist/components/questions/_shared/types/read-passage-and-answer-questions.type.js +1 -1
  95. package/dist/components/questions/_shared/types/speaking-describe-image.type.js +1 -1
  96. package/dist/components/questions/_shared/types/spontaneous-qa.type.js +1 -1
  97. package/dist/components/questions/_shared/types/word-fill-paragraph-with-options.type.js +1 -1
  98. package/dist/components/questions/_shared/types/word-fill-paragraph.type.js +1 -1
  99. package/dist/components/questions/_shared/types/word-fill-structured-form.type.js +1 -1
  100. package/dist/components/questions/_shared/types/word-ordering.type.js +1 -1
  101. package/dist/components/questions/_shared/types/write-a-short-letter.type.js +1 -1
  102. package/dist/components/questions/_shared/types/write-correct-verb-form.type.js +1 -3
  103. package/dist/components/questions/_shared/types/write-sentences.type.js +1 -1
  104. package/dist/components/questions/_shared/types/write-short-paragraph.type.js +1 -1
  105. package/dist/components/questions/_shared/utils/answer-builder.js +1 -79
  106. package/dist/components/questions/_shared/utils/createColorRegionRegistration.js +1 -39
  107. package/dist/components/questions/_shared/utils/id-generator.js +1 -3
  108. package/dist/components/questions/_shared/utils/question-validation.js +1 -21
  109. package/dist/components/questions/components/ColorNode.js +1 -85
  110. package/dist/components/questions/components/ColorRegionClient.js +1 -545
  111. package/dist/components/questions/components/ColorRegionCreator.js +1 -420
  112. package/dist/components/questions/components/ColorRegionNode.js +1 -59
  113. package/dist/components/questions/components/CreatorGuide.js +1 -103
  114. package/dist/components/questions/components/NameNode.js +1 -26
  115. package/dist/components/questions/components/QuestionBankOption.js +1 -46
  116. package/dist/components/questions/components/QuestionBasicInfoForm.js +1 -9
  117. package/dist/components/questions/components/QuestionCommonFields.js +1 -33
  118. package/dist/components/questions/components/QuestionSearchDropdown.js +1 -258
  119. package/dist/components/questions/components/QuestionTypeSelector.js +1 -32
  120. package/dist/components/questions/components/QuestionTypeSpecificForms.js +1 -6
  121. package/dist/components/questions/components/RegionNode.js +1 -94
  122. package/dist/components/questions/components/index.js +1 -14
  123. package/dist/components/questions/creator/QuestionCreator.js +1 -90
  124. package/dist/components/questions/creator/QuestionGroupCreator.js +1 -119
  125. package/dist/components/questions/creator/dedicated-component-router.js +1 -79
  126. package/dist/components/questions/creator/index.js +1 -3
  127. package/dist/components/questions/creator/question-type-registry.js +1 -80
  128. package/dist/components/questions/groups/ArticlesGroupCard.js +1 -121
  129. package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -50
  130. package/dist/components/questions/groups/DocumentGroupCard.js +1 -114
  131. package/dist/components/questions/groups/MatchAnswerGroupCard.js +1 -25
  132. package/dist/components/questions/index.js +1 -10
  133. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +1 -37
  134. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +1 -351
  135. package/dist/components/questions/types/answer-the-question/register.js +1 -67
  136. package/dist/components/questions/types/answer-the-question/transform.js +1 -36
  137. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +1 -141
  138. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +1 -118
  139. package/dist/components/questions/types/arrange-letters-into-words/index.js +1 -2
  140. package/dist/components/questions/types/arrange-letters-into-words/register.js +1 -4
  141. package/dist/components/questions/types/arrange-letters-into-words/transform.js +1 -24
  142. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -64
  143. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +1 -169
  144. package/dist/components/questions/types/choose-correct-adjective/index.js +1 -2
  145. package/dist/components/questions/types/choose-correct-adjective/register.js +1 -55
  146. package/dist/components/questions/types/choose-correct-adjective/transform.js +1 -16
  147. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +1 -131
  148. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +1 -581
  149. package/dist/components/questions/types/choose-the-correct-answer/index.js +1 -2
  150. package/dist/components/questions/types/choose-the-correct-answer/register.js +1 -101
  151. package/dist/components/questions/types/choose-the-correct-answer/transform.js +1 -43
  152. package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +1 -275
  153. package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +1 -448
  154. package/dist/components/questions/types/fill-in-blank/index.js +1 -2
  155. package/dist/components/questions/types/fill-in-blank/register.js +1 -138
  156. package/dist/components/questions/types/fill-in-blank/transform.js +1 -59
  157. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +1 -117
  158. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +1 -671
  159. package/dist/components/questions/types/fill-missing-words-in-grid/index.js +1 -2
  160. package/dist/components/questions/types/fill-missing-words-in-grid/register.js +1 -37
  161. package/dist/components/questions/types/fill-missing-words-in-grid/transform.js +1 -15
  162. package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +1 -262
  163. package/dist/components/questions/types/gn-speaking-interview/register.js +1 -121
  164. package/dist/components/questions/types/gn-speaking-interview/transform.js +1 -48
  165. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingClient.js +1 -16
  166. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingCreator.js +1 -27
  167. package/dist/components/questions/types/image-object-matching/index.js +1 -2
  168. package/dist/components/questions/types/image-object-matching/register.js +1 -3
  169. package/dist/components/questions/types/image-object-matching/transform.js +1 -2
  170. package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +1 -102
  171. package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +1 -344
  172. package/dist/components/questions/types/label-the-picture/index.js +1 -2
  173. package/dist/components/questions/types/label-the-picture/register.js +1 -54
  174. package/dist/components/questions/types/label-the-picture/transform.js +1 -28
  175. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +1 -78
  176. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +1 -232
  177. package/dist/components/questions/types/listen-and-choose-from-answer-group/index.js +1 -2
  178. package/dist/components/questions/types/listen-and-choose-from-answer-group/register.js +1 -82
  179. package/dist/components/questions/types/listen-and-choose-from-answer-group/transform.js +1 -27
  180. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +1 -70
  181. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +1 -386
  182. package/dist/components/questions/types/listen-and-choose-objects-in-scene/index.js +1 -2
  183. package/dist/components/questions/types/listen-and-choose-objects-in-scene/register.js +1 -100
  184. package/dist/components/questions/types/listen-and-choose-objects-in-scene/transform.js +1 -33
  185. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +1 -106
  186. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +1 -457
  187. package/dist/components/questions/types/listen-and-drag-objects-into-scene/index.js +1 -2
  188. package/dist/components/questions/types/listen-and-drag-objects-into-scene/register.js +1 -110
  189. package/dist/components/questions/types/listen-and-drag-objects-into-scene/transform.js +1 -35
  190. package/dist/components/questions/types/listen-and-speak-answer/ArrowIndicator.js +1 -8
  191. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -22
  192. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +1 -291
  193. package/dist/components/questions/types/listen-and-speak-answer/index.js +1 -2
  194. package/dist/components/questions/types/listen-and-speak-answer/register.js +1 -84
  195. package/dist/components/questions/types/listen-and-speak-answer/transform.js +1 -27
  196. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -32
  197. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +1 -295
  198. package/dist/components/questions/types/listen-and-speak-compare-images/register.js +1 -79
  199. package/dist/components/questions/types/listen-and-speak-compare-images/transform.js +1 -27
  200. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -34
  201. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +1 -285
  202. package/dist/components/questions/types/listen-and-speak-image-group/register.js +1 -81
  203. package/dist/components/questions/types/listen-and-speak-image-group/transform.js +1 -26
  204. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -38
  205. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +1 -385
  206. package/dist/components/questions/types/listen-and-speak-info-exchange/register.js +1 -87
  207. package/dist/components/questions/types/listen-and-speak-info-exchange/transform.js +1 -32
  208. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -26
  209. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +1 -247
  210. package/dist/components/questions/types/listen-and-speak-odd-one-out/register.js +1 -59
  211. package/dist/components/questions/types/listen-and-speak-odd-one-out/transform.js +1 -18
  212. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -26
  213. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +1 -256
  214. package/dist/components/questions/types/listen-and-speak-question-list/register.js +1 -81
  215. package/dist/components/questions/types/listen-and-speak-question-list/transform.js +1 -26
  216. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -26
  217. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +1 -230
  218. package/dist/components/questions/types/listen-and-speak-with-story-images/register.js +1 -56
  219. package/dist/components/questions/types/listen-and-speak-with-story-images/transform.js +1 -16
  220. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +1 -72
  221. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +1 -230
  222. package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +1 -1
  223. package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +1 -66
  224. package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +1 -42
  225. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +1 -96
  226. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +1 -238
  227. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +1 -2
  228. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +1 -76
  229. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +1 -19
  230. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +1 -134
  231. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +1 -617
  232. package/dist/components/questions/types/look-picture-fill-word-hint/index.js +1 -2
  233. package/dist/components/questions/types/look-picture-fill-word-hint/register.js +1 -82
  234. package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +1 -29
  235. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +1 -38
  236. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +1 -359
  237. package/dist/components/questions/types/match-by-writing-answer/index.js +1 -3
  238. package/dist/components/questions/types/match-by-writing-answer/register.js +1 -58
  239. package/dist/components/questions/types/match-by-writing-answer/transform.js +1 -61
  240. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsClient.js +1 -16
  241. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsCreator.js +1 -23
  242. package/dist/components/questions/types/read-and-color-objects/index.js +1 -2
  243. package/dist/components/questions/types/read-and-color-objects/register.js +1 -3
  244. package/dist/components/questions/types/read-and-color-objects/transform.js +1 -36
  245. package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +1 -125
  246. package/dist/components/questions/types/read-displayed-content/register.js +1 -64
  247. package/dist/components/questions/types/read-displayed-content/transform.js +1 -12
  248. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -64
  249. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +1 -511
  250. package/dist/components/questions/types/read-passage-and-answer-questions/index.js +1 -2
  251. package/dist/components/questions/types/read-passage-and-answer-questions/register.js +1 -58
  252. package/dist/components/questions/types/read-passage-and-answer-questions/transform.js +1 -28
  253. package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +1 -373
  254. package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +1 -41
  255. package/dist/components/questions/types/speaking-conversation/register.js +1 -110
  256. package/dist/components/questions/types/speaking-conversation/transform.js +1 -34
  257. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +1 -74
  258. package/dist/components/questions/types/speaking-cue-card/register.js +1 -52
  259. package/dist/components/questions/types/speaking-cue-card/transform.js +1 -12
  260. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -10
  261. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +1 -161
  262. package/dist/components/questions/types/speaking-describe-image/index.js +1 -2
  263. package/dist/components/questions/types/speaking-describe-image/register.js +1 -69
  264. package/dist/components/questions/types/speaking-describe-image/transform.js +1 -16
  265. package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +1 -234
  266. package/dist/components/questions/types/spontaneous-qa/register.js +1 -93
  267. package/dist/components/questions/types/spontaneous-qa/transform.js +1 -34
  268. package/dist/components/questions/types/true-false/TrueFalseClient.js +1 -75
  269. package/dist/components/questions/types/true-false/TrueFalseCreator.js +1 -244
  270. package/dist/components/questions/types/true-false/index.js +1 -2
  271. package/dist/components/questions/types/true-false/register.js +1 -77
  272. package/dist/components/questions/types/true-false/transform.js +1 -32
  273. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +1 -233
  274. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +1 -631
  275. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphWrapper.js +1 -28
  276. package/dist/components/questions/types/word-fill-paragraph/index.js +1 -2
  277. package/dist/components/questions/types/word-fill-paragraph/register.js +1 -136
  278. package/dist/components/questions/types/word-fill-paragraph/transform.js +1 -56
  279. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -138
  280. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +1 -261
  281. package/dist/components/questions/types/word-fill-structured-form/index.js +1 -2
  282. package/dist/components/questions/types/word-fill-structured-form/register.js +1 -67
  283. package/dist/components/questions/types/word-fill-structured-form/transform.js +1 -40
  284. package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -84
  285. package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +1 -192
  286. package/dist/components/questions/types/word-ordering/index.js +1 -2
  287. package/dist/components/questions/types/word-ordering/register.js +1 -46
  288. package/dist/components/questions/types/word-ordering/transform.js +1 -24
  289. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +1 -98
  290. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +1 -320
  291. package/dist/components/questions/types/write-a-short-letter/index.js +1 -2
  292. package/dist/components/questions/types/write-a-short-letter/register.js +1 -68
  293. package/dist/components/questions/types/write-a-short-letter/transform.js +1 -25
  294. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +1 -61
  295. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +1 -196
  296. package/dist/components/questions/types/write-correct-verb-form/index.js +1 -2
  297. package/dist/components/questions/types/write-correct-verb-form/register.js +1 -62
  298. package/dist/components/questions/types/write-correct-verb-form/transform.js +1 -17
  299. package/dist/components/questions/types/write-sentences/WriteSentencesClient.js +1 -22
  300. package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +1 -174
  301. package/dist/components/questions/types/write-sentences/index.js +1 -2
  302. package/dist/components/questions/types/write-sentences/register.js +1 -58
  303. package/dist/components/questions/types/write-sentences/transform.js +1 -28
  304. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -96
  305. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +1 -161
  306. package/dist/components/questions/types/write-short-paragraph/index.js +1 -2
  307. package/dist/components/questions/types/write-short-paragraph/register.js +1 -4
  308. package/dist/components/questions/types/write-short-paragraph/transform.js +1 -21
  309. package/dist/components/questions/viewer/QuestionGroupViewer.js +1 -36
  310. package/dist/components/questions/viewer/QuestionViewer.js +1 -446
  311. package/dist/components/questions/viewer/index.js +1 -2
  312. package/dist/components/results/ResultReviewPageContainer.js +1 -98
  313. package/dist/components/results/ReviewQuestionRenderer.js +1 -72
  314. package/dist/components/results/index.js +1 -2
  315. package/dist/components/results/result-review.utils.js +1 -6
  316. package/dist/components/results/review-data.js +1 -150
  317. package/dist/components/results/review-renderer.utils.js +1 -97
  318. package/dist/components/results/review-types.js +1 -1
  319. package/dist/components/themes/cambridge-yle/CambridgeYleAudioPlayer.js +1 -138
  320. package/dist/components/themes/cambridge-yle/CambridgeYleBottomNav.js +1 -12
  321. package/dist/components/themes/cambridge-yle/CambridgeYleExamLayout.js +1 -18
  322. package/dist/components/themes/cambridge-yle/CambridgeYleExampleText.js +1 -11
  323. package/dist/components/themes/cambridge-yle/CambridgeYleHeader.js +1 -11
  324. package/dist/components/themes/cambridge-yle/CambridgeYleInstructionBanner.js +1 -9
  325. package/dist/components/themes/cambridge-yle/CambridgeYleNavButton.js +1 -13
  326. package/dist/components/themes/cambridge-yle/CambridgeYlePartBanner.js +1 -8
  327. package/dist/components/themes/cambridge-yle/CambridgeYleProgressBar.js +1 -6
  328. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCard.js +1 -5
  329. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCardFullWidth.js +1 -9
  330. package/dist/components/themes/cambridge-yle/CambridgeYleSidebar.js +1 -41
  331. package/dist/components/themes/cambridge-yle/CambridgeYleTimer.js +1 -25
  332. package/dist/components/themes/cambridge-yle/index.js +1 -13
  333. package/dist/components/themes/english-certification/EcAnswerTheQuestion.js +1 -24
  334. package/dist/components/themes/english-certification/EcMatchByWritingAnswer.js +1 -88
  335. package/dist/components/themes/english-certification/EcWordFillParagraph.js +1 -55
  336. package/dist/components/themes/english-certification/EcWordFillStructuredForm.js +1 -156
  337. package/dist/components/themes/english-certification/EcWritingTask.js +1 -34
  338. package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.js +1 -114
  339. package/dist/components/themes/english-certification/EnglishCertificationBrand.js +1 -11
  340. package/dist/components/themes/english-certification/EnglishCertificationExamLayout.js +1 -21
  341. package/dist/components/themes/english-certification/EnglishCertificationFlagButton.js +1 -15
  342. package/dist/components/themes/english-certification/EnglishCertificationFooterBar.js +1 -19
  343. package/dist/components/themes/english-certification/EnglishCertificationGroupedQuestion.js +1 -148
  344. package/dist/components/themes/english-certification/EnglishCertificationHeader.js +1 -31
  345. package/dist/components/themes/english-certification/EnglishCertificationNavigationPanel.js +1 -71
  346. package/dist/components/themes/english-certification/EnglishCertificationOptionSelector.js +1 -33
  347. package/dist/components/themes/english-certification/EnglishCertificationPassagePanel.js +1 -16
  348. package/dist/components/themes/english-certification/EnglishCertificationQuestionCard.js +1 -22
  349. package/dist/components/themes/english-certification/EnglishCertificationQuestionItem.js +1 -37
  350. package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.js +1 -72
  351. package/dist/components/themes/english-certification/EnglishCertificationReadingSection.js +1 -90
  352. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestion.js +1 -18
  353. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestionList.js +1 -29
  354. package/dist/components/themes/english-certification/english-certification-theme.js +1 -58
  355. package/dist/components/themes/english-certification/grouped-question-utils.js +1 -218
  356. package/dist/components/themes/english-certification/index.js +1 -21
  357. package/dist/components/themes/index.js +1 -6
  358. package/dist/components/themes/summer-sky/SummerSkyAudioPlayer.js +1 -170
  359. package/dist/components/themes/summer-sky/SummerSkyLayout.js +1 -221
  360. package/dist/components/themes/summer-sky/SummerSkySubmitModal.js +1 -18
  361. package/dist/components/themes/summer-sky/SummerSkyWaitingRoom.js +1 -194
  362. package/dist/components/themes/types.js +1 -1
  363. package/dist/components/ui/alert-dialog.js +1 -26
  364. package/dist/components/ui/alert.js +1 -22
  365. package/dist/components/ui/avatar.js +1 -12
  366. package/dist/components/ui/badge.js +1 -38
  367. package/dist/components/ui/button.js +1 -35
  368. package/dist/components/ui/calendar.js +1 -76
  369. package/dist/components/ui/card.js +1 -16
  370. package/dist/components/ui/carousel.js +1 -91
  371. package/dist/components/ui/checkbox.js +1 -9
  372. package/dist/components/ui/collapsible.js +1 -13
  373. package/dist/components/ui/command.js +1 -34
  374. package/dist/components/ui/confirm-dialog.js +1 -49
  375. package/dist/components/ui/dialog.js +1 -23
  376. package/dist/components/ui/drawer.js +1 -35
  377. package/dist/components/ui/dropdown-menu.js +1 -33
  378. package/dist/components/ui/file-upload.js +1 -155
  379. package/dist/components/ui/form.js +1 -62
  380. package/dist/components/ui/hover-card.js +1 -10
  381. package/dist/components/ui/image-preview.js +1 -29
  382. package/dist/components/ui/input.js +1 -8
  383. package/dist/components/ui/label.js +1 -9
  384. package/dist/components/ui/language-switcher.js +1 -15
  385. package/dist/components/ui/multi-select.js +1 -90
  386. package/dist/components/ui/points-input.js +1 -33
  387. package/dist/components/ui/popover.js +1 -26
  388. package/dist/components/ui/progress.js +1 -16
  389. package/dist/components/ui/radio-group.js +1 -12
  390. package/dist/components/ui/scroll-area.js +1 -11
  391. package/dist/components/ui/select.js +1 -38
  392. package/dist/components/ui/separator.js +1 -8
  393. package/dist/components/ui/sheet.js +1 -40
  394. package/dist/components/ui/single-select.js +1 -76
  395. package/dist/components/ui/skeleton.js +1 -6
  396. package/dist/components/ui/switch.js +1 -8
  397. package/dist/components/ui/table.js +1 -20
  398. package/dist/components/ui/tabs.js +1 -17
  399. package/dist/components/ui/textarea.js +1 -8
  400. package/dist/components/ui/tooltip.js +1 -11
  401. package/dist/index.d.ts +3 -3
  402. package/dist/index.js +1 -19
  403. package/dist/mcp/config.js +1 -32
  404. package/dist/mcp/http-client.js +1 -48
  405. package/dist/mcp/index.js +1 -29
  406. package/dist/mcp/tools/_helpers.js +1 -25
  407. package/dist/mcp/tools/exams.js +1 -439
  408. package/dist/mcp/tools/index.js +1 -6
  409. package/dist/mcp/tools/results.js +1 -19
  410. package/dist/shared/constants/ApiConstant.js +1 -276
  411. package/dist/shared/constants/QueryKeyConstant.js +1 -112
  412. package/dist/shared/constants/exam-level.enum.js +1 -41
  413. package/dist/shared/constants/landings/placement-test.constants.js +1 -431
  414. package/dist/shared/constants/question-skills.js +1 -267
  415. package/dist/shared/constants/routes.js +1 -81
  416. package/dist/shared/index.d.ts +3 -0
  417. package/dist/shared/index.js +1 -7
  418. package/dist/shared/lib/hooks/index.js +1 -10
  419. package/dist/shared/lib/hooks/useAlertDialog.js +1 -57
  420. package/dist/shared/lib/hooks/useAudioPlayCounter.js +1 -30
  421. package/dist/shared/lib/hooks/useAudioPreview.js +1 -81
  422. package/dist/shared/lib/hooks/useAudioUpload.js +1 -24
  423. package/dist/shared/lib/hooks/useConfirmDialog.js +1 -89
  424. package/dist/shared/lib/hooks/useDebouncedCallback.js +1 -32
  425. package/dist/shared/lib/hooks/useDelayedAutoPlay.js +1 -12
  426. package/dist/shared/lib/hooks/useFileUpload.js +1 -24
  427. package/dist/shared/lib/hooks/useImageUrl.js +1 -5
  428. package/dist/shared/lib/hooks/usePresignedFileUrl.js +1 -10
  429. package/dist/shared/lib/hooks/useReactHookForm.js +1 -31
  430. package/dist/shared/lib/hooks/useToast.js +1 -62
  431. package/dist/shared/lib/i18n/index.js +1 -54
  432. package/dist/shared/lib/i18n/messages/en/admin.js +1 -2104
  433. package/dist/shared/lib/i18n/messages/en/auth.js +1 -86
  434. package/dist/shared/lib/i18n/messages/en/common.js +1 -140
  435. package/dist/shared/lib/i18n/messages/en/file-upload.js +1 -18
  436. package/dist/shared/lib/i18n/messages/en/student.js +1 -133
  437. package/dist/shared/lib/i18n/messages/en/terms.js +1 -37
  438. package/dist/shared/lib/i18n/messages/en.js +1 -15
  439. package/dist/shared/lib/i18n/messages/vi/admin.js +1 -2104
  440. package/dist/shared/lib/i18n/messages/vi/auth.js +1 -86
  441. package/dist/shared/lib/i18n/messages/vi/common.js +1 -140
  442. package/dist/shared/lib/i18n/messages/vi/file-upload.js +1 -18
  443. package/dist/shared/lib/i18n/messages/vi/student.js +1 -133
  444. package/dist/shared/lib/i18n/messages/vi/terms.js +1 -37
  445. package/dist/shared/lib/i18n/messages/vi.js +1 -15
  446. package/dist/shared/lib/i18n/types.js +1 -1
  447. package/dist/shared/lib/stores/examQuestionStore.js +1 -212
  448. package/dist/shared/lib/stores/localeStore.js +1 -10
  449. package/dist/shared/lib/utils/public-page.js +1 -44
  450. package/dist/shared/lib/utils/question-reverse-transform.d.ts +6 -0
  451. package/dist/shared/lib/utils/question-reverse-transform.js +1 -0
  452. package/dist/shared/lib/utils/question-transform-types.js +1 -1
  453. package/dist/shared/lib/utils/question-transform-validation.d.ts +6 -0
  454. package/dist/shared/lib/utils/question-transform-validation.js +1 -0
  455. package/dist/shared/lib/utils/question-transform.d.ts +6 -2
  456. package/dist/shared/lib/utils/question-transform.js +1 -174
  457. package/dist/shared/lib/utils.js +1 -168
  458. package/dist/shared/lib/validation/choose-correct-answer.validation.js +1 -111
  459. package/dist/shared/lib/validation/label-the-picture.validation.js +1 -19
  460. package/dist/shared/lib/validation/listen-and-choose-from-answer-group.validation.js +1 -46
  461. package/dist/shared/lib/validation/listen-and-choose-objects-in-scene.validation.js +1 -21
  462. package/dist/shared/lib/validation/listen-and-drag-objects-into-scene.validation.js +1 -34
  463. package/dist/shared/lib/validation/listen-and-speak-answer.validation.js +1 -40
  464. package/dist/shared/lib/validation/listen-and-speak-compare-images.validation.js +1 -45
  465. package/dist/shared/lib/validation/listen-and-speak-image-group.validation.js +1 -19
  466. package/dist/shared/lib/validation/listen-and-speak-info-exchange.validation.js +1 -39
  467. package/dist/shared/lib/validation/listen-and-speak-odd-one-out.validation.js +1 -32
  468. package/dist/shared/lib/validation/listen-and-speak-question-list.validation.js +1 -19
  469. package/dist/shared/lib/validation/listen-and-speak-with-story-images.validation.js +1 -28
  470. package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +1 -12
  471. package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +1 -47
  472. package/dist/shared/lib/validation/read-and-color-objects.validation.js +1 -29
  473. package/dist/shared/lib/validation/read-displayed-content.validation.js +1 -28
  474. package/dist/shared/lib/validation/speaking-conversation.validation.js +1 -75
  475. package/dist/shared/lib/validation/speaking-describe-image.validation.js +1 -31
  476. package/dist/shared/lib/validation/spontaneous-qa.validation.js +1 -48
  477. package/dist/shared/lib/validation/word-fill-paragraph-default.validation.js +1 -38
  478. package/dist/shared/lib/validation/word-fill-paragraph-with-options.validation.js +1 -39
  479. package/dist/shared/lib/validation/word-fill-paragraph.validation.js +1 -43
  480. package/dist/shared/lib/validation/word-ordering.validation.js +1 -33
  481. package/dist/shared/types/branch.types.js +1 -6
  482. package/dist/shared/types/common.types.js +1 -50
  483. package/dist/shared/types/entities/exam-schedule.types.js +1 -5
  484. package/dist/shared/types/entities/exam.types.js +1 -1
  485. package/dist/shared/types/entities/question.types.js +1 -4
  486. package/dist/shared/types/entities/result.types.js +1 -4
  487. package/dist/shared/types/entities/user.types.js +1 -4
  488. package/dist/shared/types/exam-taking.types.js +1 -2
  489. package/dist/shared/types/history.types.js +1 -34
  490. package/dist/shared/types/index.js +1 -1
  491. package/dist/shared/types/questions/choose-correct-adjective.js +1 -4
  492. package/dist/shared/types/questions/choose-the-correct-answer.js +1 -9
  493. package/dist/shared/types/questions/fill-missing-words-in-grid.js +1 -11
  494. package/dist/shared/types/questions/index.js +1 -40
  495. package/dist/shared/types/questions/listen-and-speak-answer.js +1 -1
  496. package/dist/shared/types/questions/listen-and-speak-compare-images.js +1 -1
  497. package/dist/shared/types/questions/listen-and-speak-image-group.js +1 -1
  498. package/dist/shared/types/questions/listen-and-speak-info-exchange.js +1 -3
  499. package/dist/shared/types/questions/listen-and-speak-odd-one-out.js +1 -3
  500. package/dist/shared/types/questions/listen-and-speak-question-list.js +1 -1
  501. package/dist/shared/types/questions/listen-and-speak-with-story-images.js +1 -3
  502. package/dist/shared/types/questions/look-picture-choose-correct-answer.js +1 -5
  503. package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +1 -9
  504. package/dist/shared/types/questions/look-picture-fill-word-hint.js +1 -7
  505. package/dist/shared/types/questions/read-displayed-content.js +1 -1
  506. package/dist/shared/types/questions/speaking-conversation.js +1 -7
  507. package/dist/shared/types/questions/speaking-describe-image.js +1 -1
  508. package/dist/shared/types/questions/spontaneous-qa.js +1 -1
  509. package/dist/shared/types/questions/write-correct-verb-form.js +1 -12
  510. package/dist/shared/types/role.types.js +1 -1
  511. package/dist/shared/types/structure-management.js +1 -28
  512. package/dist/shared/types/student.types.js +1 -54
  513. package/dist/shared/types/teacher-dashboard-rooms.js +1 -2
  514. package/package.json +12 -3
@@ -1,179 +1 @@
1
- import { API_BASE_URL, API_ENDPOINTS } from '../shared/constants/ApiConstant';
2
- const TEMPLATES_PAGE_SIZE = 9;
3
- const EXAM_LEVELS = ['PRE_A1', 'A1', 'A2', 'B1', 'B2', 'C1', 'C2'].map((id) => ({ id, name: id === 'PRE_A1' ? 'Pre-A1' : id }));
4
- export function createExamCreateApi(options = {}) {
5
- const request = createRequest(options);
6
- return {
7
- getExamLevels: async () => EXAM_LEVELS,
8
- getExamTemplates: async ({ page, search }) => {
9
- const currentPage = page || 1;
10
- const query = new URLSearchParams({ page: String(currentPage), size: String(TEMPLATES_PAGE_SIZE) });
11
- if (search)
12
- query.set('filter', `name:like:${search}`);
13
- const result = await request(`${API_ENDPOINTS.SHARED.EXAM_TEMPLATES}?${query}`);
14
- const items = Array.isArray(result.data) ? result.data : result.data.data;
15
- const meta = result.meta ?? {};
16
- const totalItems = meta.totalItems ?? items.length;
17
- return {
18
- items,
19
- page: meta.page ?? currentPage,
20
- totalPages: meta.totalPages ?? Math.max(1, Math.ceil(totalItems / TEMPLATES_PAGE_SIZE)),
21
- totalItems,
22
- };
23
- },
24
- getExamTemplateDetail: (id) => request(API_ENDPOINTS.SHARED.EXAM_TEMPLATE_DETAIL(id)),
25
- createExam: (payload) => request(API_ENDPOINTS.MANAGE.EXAMS, {
26
- method: 'POST',
27
- body: JSON.stringify(cleanPayload(payload)),
28
- }),
29
- updateExamTemplate: async ({ examId, template }) => {
30
- await request(API_ENDPOINTS.MANAGE.EXAM_TEMPLATE(examId), {
31
- method: 'PATCH',
32
- body: JSON.stringify({ template }),
33
- });
34
- },
35
- };
36
- }
37
- export function createMockCreateExamApi(seed) {
38
- let nextId = 1;
39
- return {
40
- getExamLevels: async () => seed.levels ?? EXAM_LEVELS,
41
- getExamTemplates: async ({ page, search }) => {
42
- const filtered = seed.templates
43
- .filter((template) => !search || template.name.toLowerCase().includes(search.toLowerCase()))
44
- .map(examTemplateToSummary);
45
- const currentPage = page || 1;
46
- const totalItems = filtered.length;
47
- const totalPages = Math.max(1, Math.ceil(totalItems / TEMPLATES_PAGE_SIZE));
48
- const start = (currentPage - 1) * TEMPLATES_PAGE_SIZE;
49
- return {
50
- items: filtered.slice(start, start + TEMPLATES_PAGE_SIZE),
51
- page: currentPage,
52
- totalPages,
53
- totalItems,
54
- };
55
- },
56
- getExamTemplateDetail: async (id) => {
57
- const template = seed.templates.find((t) => t.id === id);
58
- if (!template)
59
- throw new Error(`Mock exam template not found: ${id}`);
60
- return { data: examTemplateToDetail(template) };
61
- },
62
- createExam: async (payload) => ({ data: { id: `mock-exam-${nextId++}`, code: payload.code, name: payload.name, examLevel: payload.examLevel, difficulty: payload.difficulty, totalScore: payload.totalScore, durationMinutes: payload.durationMinutes, description: payload.description, examTemplateId: payload.examTemplateId } }),
63
- updateExamTemplate: async () => undefined,
64
- };
65
- }
66
- /** Derive a list-row summary from a full mock `ExamTemplate`. */
67
- function examTemplateToSummary(template) {
68
- return {
69
- id: template.id,
70
- title: template.name,
71
- subTitle: template.description,
72
- theme: template.theme,
73
- type: template.type,
74
- totalQuestions: template.totalQuestions,
75
- totalParts: template.parts.length,
76
- examLevel: template.level ?? '',
77
- };
78
- }
79
- /** Derive a detail DTO from a full mock `ExamTemplate` (carries parts through). */
80
- function examTemplateToDetail(template) {
81
- return {
82
- id: template.id,
83
- title: template.name,
84
- subTitle: template.description,
85
- description: template.description,
86
- theme: template.theme,
87
- type: template.type,
88
- totalQuestions: template.totalQuestions,
89
- totalParts: template.parts.length,
90
- examLevel: template.level ?? '',
91
- parts: template.parts.map((part) => ({ ...part, title: part.name })),
92
- };
93
- }
94
- export function buildExamTemplatePayload(template) {
95
- return {
96
- id: template.id,
97
- title: template.name,
98
- subTitle: '',
99
- theme: template.theme || '',
100
- totalQuestions: template.totalQuestions,
101
- totalParts: template.parts.length,
102
- description: template.description || '',
103
- examLevel: template.level || '',
104
- type: template.type,
105
- parts: template.parts.map((part) => ({ ...part, name: part.name || '', category: part.category || '', instructions: part.instructions || '', skill: part.skill || '', questionCount: part.questionCount, isGroup: part.isGroup ?? false })),
106
- };
107
- }
108
- /**
109
- * Map the template detail DTO (`title`/`subTitle`/`examLevel`/`parts[].title`) returned by
110
- * `getExamTemplateDetail` into the full `ExamTemplate` entity the create flow expects
111
- * (`name`/`description`/`level`/`parts[].name`). Ported from the original oe-exam-fe
112
- * `TemplateSelector.handleConfirm`. Mapping is lenient (`|| ''`, optional fields) so a missing
113
- * field never throws.
114
- */
115
- export function mapTemplateDetailToExamTemplate(detail) {
116
- return {
117
- id: detail.id,
118
- name: detail.title,
119
- description: detail.description || detail.subTitle || '',
120
- theme: detail.theme,
121
- totalQuestions: detail.totalQuestions,
122
- totalTime: 0,
123
- level: detail.examLevel,
124
- type: detail.type,
125
- parts: (detail.parts ?? []).map((part) => ({
126
- id: part.id,
127
- name: part.name || part.title || '',
128
- description: part.description || '',
129
- questionCount: part.questionCount,
130
- questionType: part.questionType,
131
- skill: part.skill,
132
- startIndex: part.startIndex,
133
- endIndex: part.endIndex,
134
- points: part.points,
135
- audioUrl: part.audioUrl,
136
- optionCount: part.optionCount,
137
- optionMode: part.optionMode,
138
- defaultValue: part.defaultValue,
139
- defaultQuestion: part.defaultQuestion,
140
- viewMode: part.viewMode,
141
- instructions: part.instructions,
142
- group: part.group,
143
- groups: part.groups,
144
- groupConfig: part.groupConfig,
145
- groupType: part.groupType,
146
- articleCount: part.articleCount,
147
- questionConfig: part.questionConfig,
148
- isGroup: part.isGroup,
149
- suggestedAnswers: part.suggestedAnswers,
150
- hasSuggestedAnswers: part.hasSuggestedAnswers,
151
- category: part.category,
152
- sections: part.sections,
153
- })),
154
- };
155
- }
156
- function createRequest({ baseUrl = API_BASE_URL ?? '', fetcher = fetch, headers, apiKey }) {
157
- return async function request(path, init) {
158
- const authHeaders = apiKey ? { 'X-API-Key': apiKey } : undefined;
159
- const response = await fetcher(`${baseUrl}${path}`, {
160
- // When authenticating via API key, do NOT send browser cookies.
161
- ...(apiKey ? {} : { credentials: 'include' }),
162
- ...init,
163
- headers: { 'Content-Type': 'application/json', ...authHeaders, ...headers, ...init?.headers },
164
- });
165
- if (!response.ok) {
166
- throw new Error(await readErrorMessage(response));
167
- }
168
- return response.json();
169
- };
170
- }
171
- async function readErrorMessage(response) {
172
- const body = await response.json().catch(() => undefined);
173
- if (typeof body === 'object' && body !== null && 'message' in body && typeof body.message === 'string')
174
- return body.message;
175
- return response.statusText;
176
- }
177
- function cleanPayload(payload) {
178
- return Object.fromEntries(Object.entries(payload).filter(([, value]) => value !== undefined && value !== ''));
179
- }
1
+ import{API_BASE_URL as e,API_ENDPOINTS as t}from"../shared/constants/ApiConstant";const a=["PRE_A1","A1","A2","B1","B2","C1","C2"].map(e=>({id:e,name:"PRE_A1"===e?"Pre-A1":e}));export function createExamCreateApi(n={}){const s=function({baseUrl:t=e??"",fetcher:a=fetch,headers:n,apiKey:s}){return async function(e,i){const o=s?{"X-API-Key":s}:void 0,r=await a(`${t}${e}`,{...s?{}:{credentials:"include"},...i,headers:{"Content-Type":"application/json",...o,...n,...i?.headers}});if(!r.ok)throw new Error(await async function(e){const t=await e.json().catch(()=>{});return"object"==typeof t&&null!==t&&"message"in t&&"string"==typeof t.message?t.message:e.statusText}(r));return r.json()}}(n);return{getExamLevels:async()=>a,getExamTemplates:async({page:e,search:a})=>{const n=e||1,i=new URLSearchParams({page:String(n),size:String(9)});a&&i.set("filter",`name:like:${a}`);const o=await s(`${t.SHARED.EXAM_TEMPLATES}?${i}`),r=Array.isArray(o.data)?o.data:o.data.data,l=o.meta??{},p=l.totalItems??r.length;return{items:r,page:l.page??n,totalPages:l.totalPages??Math.max(1,Math.ceil(p/9)),totalItems:p}},getExamTemplateDetail:e=>s(t.SHARED.EXAM_TEMPLATE_DETAIL(e)),createExam:e=>s(t.MANAGE.EXAMS,{method:"POST",body:JSON.stringify(i(e))}),updateExamTemplate:async({examId:e,template:a})=>{await s(t.MANAGE.EXAM_TEMPLATE(e),{method:"PATCH",body:JSON.stringify({template:a})})}}}export function createMockCreateExamApi(e){let t=1;return{getExamLevels:async()=>e.levels??a,getExamTemplates:async({page:t,search:a})=>{const s=e.templates.filter(e=>!a||e.name.toLowerCase().includes(a.toLowerCase())).map(n),i=t||1,o=s.length,r=Math.max(1,Math.ceil(o/9)),l=9*(i-1);return{items:s.slice(l,l+9),page:i,totalPages:r,totalItems:o}},getExamTemplateDetail:async t=>{const a=e.templates.find(e=>e.id===t);if(!a)throw new Error(`Mock exam template not found: ${t}`);return{data:s(a)}},createExam:async e=>({data:{id:"mock-exam-"+t++,code:e.code,name:e.name,examLevel:e.examLevel,difficulty:e.difficulty,totalScore:e.totalScore,durationMinutes:e.durationMinutes,description:e.description,examTemplateId:e.examTemplateId}}),updateExamTemplate:async()=>{}}}function n(e){return{id:e.id,title:e.name,subTitle:e.description,theme:e.theme,type:e.type,totalQuestions:e.totalQuestions,totalParts:e.parts.length,examLevel:e.level??""}}function s(e){return{id:e.id,title:e.name,subTitle:e.description,description:e.description,theme:e.theme,type:e.type,totalQuestions:e.totalQuestions,totalParts:e.parts.length,examLevel:e.level??"",parts:e.parts.map(e=>({...e,title:e.name}))}}export function buildExamTemplatePayload(e){return{id:e.id,title:e.name,subTitle:"",theme:e.theme||"",totalQuestions:e.totalQuestions,totalParts:e.parts.length,description:e.description||"",examLevel:e.level||"",type:e.type,parts:e.parts.map(e=>({...e,name:e.name||"",category:e.category||"",instructions:e.instructions||"",skill:e.skill||"",questionCount:e.questionCount,isGroup:e.isGroup??!1}))}}export function mapTemplateDetailToExamTemplate(e){return{id:e.id,name:e.title,description:e.description||e.subTitle||"",theme:e.theme,totalQuestions:e.totalQuestions,totalTime:0,level:e.examLevel,type:e.type,parts:(e.parts??[]).map(e=>({id:e.id,name:e.name||e.title||"",description:e.description||"",questionCount:e.questionCount,questionType:e.questionType,skill:e.skill,startIndex:e.startIndex,endIndex:e.endIndex,points:e.points,audioUrl:e.audioUrl,optionCount:e.optionCount,optionMode:e.optionMode,defaultValue:e.defaultValue,defaultQuestion:e.defaultQuestion,viewMode:e.viewMode,instructions:e.instructions,group:e.group,groups:e.groups,groupConfig:e.groupConfig,groupType:e.groupType,articleCount:e.articleCount,questionConfig:e.questionConfig,isGroup:e.isGroup,suggestedAnswers:e.suggestedAnswers,hasSuggestedAnswers:e.hasSuggestedAnswers,category:e.category,sections:e.sections}))}}function i(e){return Object.fromEntries(Object.entries(e).filter(([,e])=>void 0!==e&&""!==e))}
@@ -1 +1 @@
1
- export {};
1
+ export{};
@@ -6,4 +6,4 @@ export declare function createMockExamQuestionsApi(seed: {
6
6
  exam: ExamWithTemplate;
7
7
  questions?: Partial<ExamQuestion>[];
8
8
  }): ExamQuestionsSdkApi;
9
- export type { ExamQuestionsSdkApi, ExamWithTemplate, FetchApiOptions, SaveExamInput, SaveQuestionInput, UpdatePartAudioInput } from './exam-questions.types';
9
+ export type { ExamQuestionsSdkApi, ExamWithTemplate, ExistingQuestionBank, FetchApiOptions, QuestionSearchItem, SaveExamInput, SaveQuestionInput, SaveQuestionResult, SearchQuestionBankInput, SearchQuestionBankResult, UpdatePartAudioInput, } from './exam-questions.types';
@@ -1,98 +1 @@
1
- import { API_BASE_URL, API_ENDPOINTS } from '../shared/constants/ApiConstant';
2
- import { transformQuestionToApiFormat } from '../shared/lib/utils/question-transform';
3
- export function createExamQuestionsApi(options = {}) {
4
- const request = createRequest(options);
5
- return {
6
- getExam: (examId) => request(API_ENDPOINTS.MANAGE.EXAM_DETAIL(examId)),
7
- getQuestions: async (examId) => {
8
- const result = await request(API_ENDPOINTS.MANAGE.EXAM_QUESTIONS(examId));
9
- return Array.isArray(result.data) ? result.data : result.data.questions ?? [];
10
- },
11
- saveQuestion: async ({ examId, question, template }) => {
12
- if (!examId)
13
- throw new Error('examId is required');
14
- // Transform the editor's question shape (type/answer/content) into the API
15
- // payload the exam backend expects (questionType/content/correctAnswer/...).
16
- const apiQuestion = transformQuestionToApiFormat(question, resolveTransformOptions(question, template));
17
- const result = await request(API_ENDPOINTS.MANAGE.EXAM_QUESTIONS(examId), {
18
- method: 'POST',
19
- body: JSON.stringify({ questions: [apiQuestion] }),
20
- });
21
- // Keep the editor-format question (so the card stays editable) and only
22
- // adopt the server-assigned id from the API response.
23
- const savedId = result.data.results?.[0]?.data?.id;
24
- return { ...question, id: savedId ?? question.id, isCompleted: true };
25
- },
26
- saveExam: async () => undefined,
27
- updatePartAudio: (input) => updatePartAudio(request, input),
28
- };
29
- }
30
- export const createExamQuestionsFetchApi = createExamQuestionsApi;
31
- export function createMockExamQuestionsApi(seed) {
32
- const questions = new Map((seed.questions ?? []).map((question) => [(question.questionNumber ?? 1) - 1, question]));
33
- let exam = seed.exam;
34
- return {
35
- getExam: async () => ({ data: exam }),
36
- getQuestions: async () => Array.from(questions.values()),
37
- saveQuestion: async ({ question, questionIndex }) => {
38
- const saved = { ...question, questionNumber: question.questionNumber ?? questionIndex + 1, isCompleted: true };
39
- questions.set(questionIndex, saved);
40
- return saved;
41
- },
42
- saveExam: async () => undefined,
43
- updatePartAudio: async ({ partId, audioUrl }) => {
44
- if (!exam.template || !('parts' in exam.template))
45
- return;
46
- exam = {
47
- ...exam,
48
- template: {
49
- ...exam.template,
50
- parts: exam.template.parts.map((part) => part.id === partId ? { ...part, audioUrl } : part),
51
- },
52
- };
53
- },
54
- };
55
- }
56
- /**
57
- * Build the options the question transform needs from the editor question +
58
- * template: the 1-based part number (derived from the part's position in the
59
- * template) plus the positioning fields the question already carries.
60
- */
61
- function resolveTransformOptions(question, template) {
62
- const partIndex = template?.parts.findIndex((part) => part.id === question.partId) ?? -1;
63
- const partNo = partIndex >= 0 ? partIndex + 1 : undefined;
64
- const { partId, questionNumber } = question;
65
- const sectionId = question.sectionId;
66
- return { partId, partNo, questionNumber, sectionId };
67
- }
68
- function createRequest({ baseUrl = API_BASE_URL ?? '', fetcher = fetch, headers, apiKey }) {
69
- return async function request(path, init) {
70
- const authHeaders = apiKey ? { 'X-API-Key': apiKey } : undefined;
71
- const response = await fetcher(`${baseUrl}${path}`, {
72
- // When authenticating via API key, do NOT send browser cookies.
73
- ...(apiKey ? {} : { credentials: 'include' }),
74
- ...init,
75
- headers: { 'Content-Type': 'application/json', ...authHeaders, ...headers, ...init?.headers },
76
- });
77
- if (!response.ok) {
78
- const body = await response.json().catch(() => ({}));
79
- throw new Error(body.message || response.statusText);
80
- }
81
- return response.json();
82
- };
83
- }
84
- async function updatePartAudio(request, { examId, partId, audioUrl, template }) {
85
- await request(API_ENDPOINTS.MANAGE.EXAM_TEMPLATE(examId), {
86
- method: 'PATCH',
87
- body: JSON.stringify({
88
- id: template.id,
89
- title: template.name,
90
- totalQuestions: template.totalQuestions,
91
- totalParts: template.parts.length,
92
- description: template.description || '',
93
- examLevel: template.level || '',
94
- type: template.type,
95
- parts: template.parts.map((part) => ({ ...part, audioUrl: part.id === partId ? audioUrl : part.audioUrl })),
96
- }),
97
- });
98
- }
1
+ import{API_BASE_URL as t,API_ENDPOINTS as e}from"../shared/constants/ApiConstant";import{transformQuestionToApiFormat as s}from"../shared/lib/utils/question-transform";import{transformApiQuestionsToFrontend as a}from"../shared/lib/utils/question-reverse-transform";export function createExamQuestionsApi(n={}){const i=function({baseUrl:e=t??"",fetcher:s=fetch,headers:a,apiKey:n}){return async function(t,i){const o=n?{"X-API-Key":n}:void 0,r=await s(`${e}${t}`,{...n?{}:{credentials:"include"},...i,headers:{"Content-Type":"application/json",...o,...a,...i?.headers}});if(!r.ok){const t=await r.json().catch(()=>({}));throw new Error(t.message||r.statusText)}return r.json()}}(n);return{getExam:t=>i(e.MANAGE.EXAM_DETAIL(t)),getQuestions:async t=>{const s=await i(e.MANAGE.EXAM_QUESTIONS(t)),n=Array.isArray(s.data)?s.data:s.data.questions??[];return a(n)},saveQuestion:async t=>{const{examId:a,question:n,template:o}=t;if(!a)throw new Error("examId is required");const r=s(n,function(t){const{question:e,template:s,addToQuestionBank:a,questionBankCode:n,questionBankTitle:i,sectionId:o,examLevel:r,subjectId:u}=t,d=s?.parts.findIndex(t=>t.id===e.partId)??-1,p=d>=0?d+1:void 0,c=e.sectionId;return{partId:e.partId,partNo:p,questionNumber:e.questionNumber,sectionId:o??c,addToQuestionBank:a,questionBankCode:n,questionBankTitle:i,examLevel:r,subjectId:u}}(t)),u=await i(e.MANAGE.EXAM_QUESTIONS(a),{method:"POST",body:JSON.stringify({examId:a,questions:[r]})});if((u.data.summary?.errorCount??0)>0)throw new Error("Unable to save question");const d=u.data.results?.[0]?.data,p={...n,id:d?.id??n.id,...d?.questionGroup?{questionGroup:d.questionGroup}:{},isCompleted:!0},c=(m=d?.questionBank,"object"!=typeof m||null===m||Array.isArray(m)?void 0:m);var m;return{question:p,questionBank:c?{id:String(c.id??""),code:String(c.code??""),title:String(c.title??"")}:void 0}},saveExam:async()=>{},updatePartAudio:t=>async function(t,{examId:s,partId:a,audioUrl:n,template:i}){await t(e.MANAGE.EXAM_TEMPLATE(s),{method:"PATCH",body:JSON.stringify({id:i.id,title:i.name,subTitle:"",theme:i.theme||"",totalQuestions:i.totalQuestions,totalParts:i.parts.length,description:i.description||"",examLevel:i.level||"",type:i.type,parts:i.parts.map(t=>({...t,audioUrl:t.id===a?n:t.audioUrl}))})})}(i,t),searchQuestions:async({questionType:t,keyword:s,page:a,size:n})=>{const o=new URLSearchParams;t&&o.set("questionType",t),s&&o.set("keyword",s),o.set("page",String(a)),o.set("size",String(n));const r=o.toString()?`?${o.toString()}`:"";return i(`${e.MANAGE.QUESTION_BANKS}${r}`)}}}export const createExamQuestionsFetchApi=createExamQuestionsApi;export function createMockExamQuestionsApi(t){const e=new Map((t.questions??[]).map(t=>[(t.questionNumber??1)-1,t]));let s=t.exam;return{getExam:async()=>({data:s}),getQuestions:async()=>Array.from(e.values()),saveQuestion:async({question:t,questionIndex:s})=>{const a={...t,questionNumber:t.questionNumber??s+1,isCompleted:!0};return e.set(s,a),{question:a}},saveExam:async()=>{},updatePartAudio:async({partId:t,audioUrl:e})=>{s.template&&"parts"in s.template&&(s={...s,template:{...s.template,parts:s.template.parts.map(s=>s.id===t?{...s,audioUrl:e}:s)}})},searchQuestions:async({questionType:t})=>({data:Array.from(e.values()).filter(e=>!t||e.type===t).map(t=>({id:t.id??String(t.questionNumber??""),questionType:t.type??"",content:t.content,correctAnswer:t.answer,points:t.points,difficulty:t.difficulty,skill:t.skill,explanation:t.explanation})),meta:{totalItems:e.size}})}}
@@ -1,10 +1,12 @@
1
+ import type { QuestionType } from '../shared/types/common.types';
1
2
  import type { ExamData, ExamPart, ExamQuestion, ExamTemplate } from '../shared/types/entities/exam.types';
2
3
  export interface ExamQuestionsSdkApi {
3
4
  getExam: (examId: string) => Promise<ExamQuestionsApiResponse<ExamWithTemplate> | ExamWithTemplate>;
4
5
  getQuestions?: (examId: string) => Promise<ExamQuestionsApiResponse<Partial<ExamQuestion>[]> | Partial<ExamQuestion>[]>;
5
- saveQuestion?: (input: SaveQuestionInput) => Promise<Partial<ExamQuestion> | void>;
6
+ saveQuestion?: (input: SaveQuestionInput) => Promise<Partial<ExamQuestion> | SaveQuestionResult | void>;
6
7
  saveExam?: (input: SaveExamInput) => Promise<void>;
7
8
  updatePartAudio?: (input: UpdatePartAudioInput) => Promise<void>;
9
+ searchQuestions?: (input: SearchQuestionBankInput) => Promise<SearchQuestionBankResult>;
8
10
  }
9
11
  export interface ExamQuestionsApiResponse<T> {
10
12
  data: T;
@@ -24,6 +26,7 @@ export interface EmbeddedExamTemplate extends Omit<ExamTemplate, 'name' | 'level
24
26
  title: string;
25
27
  subTitle?: string;
26
28
  examLevel: string;
29
+ totalParts?: number;
27
30
  parts: Array<ExamPart & {
28
31
  title?: string;
29
32
  }>;
@@ -36,6 +39,20 @@ export interface SaveQuestionInput {
36
39
  examDifficulty?: string;
37
40
  examLevel?: string;
38
41
  subjectId?: string;
42
+ sectionId?: number;
43
+ addToQuestionBank?: boolean;
44
+ questionBankCode?: string;
45
+ questionBankTitle?: string;
46
+ existingQuestionBankId?: string;
47
+ }
48
+ export interface SaveQuestionResult {
49
+ question: Partial<ExamQuestion>;
50
+ questionBank?: ExistingQuestionBank | null;
51
+ }
52
+ export interface ExistingQuestionBank {
53
+ id: string;
54
+ code: string;
55
+ title: string;
39
56
  }
40
57
  export interface SaveExamInput {
41
58
  examId: string;
@@ -49,6 +66,33 @@ export interface UpdatePartAudioInput {
49
66
  audioUrl: string;
50
67
  template: ExamTemplate;
51
68
  }
69
+ export interface SearchQuestionBankInput {
70
+ questionType?: QuestionType;
71
+ keyword?: string;
72
+ page: number;
73
+ size: number;
74
+ }
75
+ export interface QuestionSearchItem {
76
+ id: string;
77
+ questionType: QuestionType | string;
78
+ content: unknown;
79
+ correctAnswer: unknown;
80
+ points?: string | number;
81
+ difficulty?: string;
82
+ skill?: string;
83
+ explanation?: string | null;
84
+ createdAt?: string;
85
+ updatedAt?: string;
86
+ code?: string;
87
+ title?: string | null;
88
+ usageCount?: number;
89
+ }
90
+ export interface SearchQuestionBankResult {
91
+ data: QuestionSearchItem[];
92
+ meta?: {
93
+ totalItems?: number;
94
+ };
95
+ }
52
96
  export interface FetchApiOptions {
53
97
  baseUrl?: string;
54
98
  fetcher?: typeof fetch;
@@ -1 +1 @@
1
- export {};
1
+ export{};
@@ -1,73 +1 @@
1
- import { API_BASE_URL, API_ENDPOINTS } from '../shared/constants/ApiConstant';
2
- export function createExamTakingApi(options = {}) {
3
- const request = createRequest(options);
4
- return {
5
- getActiveAttempt: (examId) => request(API_ENDPOINTS.STUDENT.ATTEMPTS_ACTIVE_BY_ROOM(examId)),
6
- getActive: () => request(API_ENDPOINTS.STUDENT.ATTEMPTS_ACTIVE),
7
- getAttempt: (attemptId, token) => request(`${API_ENDPOINTS.STUDENT.ATTEMPT_DETAIL(attemptId)}${token ? `?token=${encodeURIComponent(token)}` : ''}`),
8
- deleteAttempt: (attemptId) => request(API_ENDPOINTS.STUDENT.ATTEMPT_DELETE(attemptId), { method: 'DELETE' }),
9
- getRoom: (roomId) => request(API_ENDPOINTS.STUDENT.ROOM_DETAIL(roomId)),
10
- startAttempt: (examId) => request(API_ENDPOINTS.STUDENT.ATTEMPTS_START, {
11
- method: 'POST',
12
- body: JSON.stringify({ examRoomId: examId }),
13
- }),
14
- getQuestions: (attemptId) => request(API_ENDPOINTS.STUDENT.ATTEMPT_QUESTIONS(attemptId)),
15
- saveAnswers: async ({ attemptId, answers, keepalive }) => {
16
- await request(API_ENDPOINTS.STUDENT.ATTEMPT_ANSWERS(attemptId), {
17
- method: 'POST',
18
- keepalive,
19
- body: JSON.stringify({ answers }),
20
- });
21
- },
22
- submitAttempt: (attemptId) => request(API_ENDPOINTS.STUDENT.ATTEMPT_SUBMIT(attemptId), { method: 'POST' }),
23
- uploadAudioAnswer: ({ attemptId, questionId, audioBlob, durationMs }) => {
24
- const formData = new FormData();
25
- const ext = audioBlob.type === 'audio/webm' ? 'webm' : audioBlob.type === 'audio/ogg' ? 'ogg' : 'mp3';
26
- formData.append('file', audioBlob, `recording.${ext}`);
27
- formData.append('questionId', questionId);
28
- if (durationMs !== undefined)
29
- formData.append('durationMs', String(durationMs));
30
- return request(API_ENDPOINTS.STUDENT.ATTEMPT_AUDIO_ANSWER(attemptId), { method: 'POST', body: formData });
31
- },
32
- uploadProctoringSnapshot: ({ attemptId, imageBlob }) => {
33
- const formData = new FormData();
34
- formData.append('file', imageBlob, 'snapshot.jpg');
35
- return request(API_ENDPOINTS.STUDENT.ATTEMPT_PROCTORING_SNAPSHOT(attemptId), { method: 'POST', body: formData });
36
- },
37
- realtimeGradeSingle: ({ attemptId, questionId, audioKey }) => request(API_ENDPOINTS.SHARED.SPEAKING_REALTIME_GRADE, { method: 'POST', body: JSON.stringify({ attemptId, questionId, audioKey }) }),
38
- getGradingSummary: (attemptId) => request(API_ENDPOINTS.STUDENT.ATTEMPT_GRADING_SUMMARY(attemptId)),
39
- getAnswerDetail: (attemptId, answerId) => request(API_ENDPOINTS.STUDENT.ATTEMPT_ANSWER_DETAIL(attemptId, answerId)),
40
- getAnswersGradingStatus: (attemptId, answerIds) => request(API_ENDPOINTS.STUDENT.ATTEMPT_ANSWERS_GRADING_STATUS(attemptId), { method: 'POST', body: JSON.stringify({ answerIds }) }),
41
- };
42
- }
43
- export function createMockExamTakingApi(seed) {
44
- const answers = new Map();
45
- return {
46
- getActiveAttempt: async () => seed.attempt,
47
- startAttempt: async () => seed.attempt,
48
- getQuestions: async () => seed.questions,
49
- saveAnswers: async ({ answers: input }) => { input.forEach((item) => answers.set(item.questionId, item.answer)); },
50
- submitAttempt: async () => ({ answers: Object.fromEntries(answers) }),
51
- };
52
- }
53
- function createRequest({ baseUrl = API_BASE_URL ?? '', fetcher = fetch, headers }) {
54
- return async function request(path, init) {
55
- const isFormData = typeof FormData !== 'undefined' && init?.body instanceof FormData;
56
- const response = await fetcher(`${baseUrl}${path}`, {
57
- credentials: 'include',
58
- ...init,
59
- headers: { ...(!isFormData && { 'Content-Type': 'application/json' }), ...headers, ...init?.headers },
60
- });
61
- if (!response.ok)
62
- throw new Error(await readErrorMessage(response));
63
- if (response.status === 204)
64
- return undefined;
65
- return response.json();
66
- };
67
- }
68
- async function readErrorMessage(response) {
69
- const body = await response.json().catch(() => undefined);
70
- if (typeof body === 'object' && body !== null && 'message' in body && typeof body.message === 'string')
71
- return body.message;
72
- return response.statusText;
73
- }
1
+ import{API_BASE_URL as t,API_ENDPOINTS as e}from"../shared/constants/ApiConstant";export function createExamTakingApi(n={}){const T=function({baseUrl:e=t??"",fetcher:n=fetch,headers:T}){return async function(t,a){const o="undefined"!=typeof FormData&&a?.body instanceof FormData,s=await n(`${e}${t}`,{credentials:"include",...a,headers:{...!o&&{"Content-Type":"application/json"},...T,...a?.headers}});if(!s.ok)throw new Error(await async function(t){const e=await t.json().catch(()=>{});return"object"==typeof e&&null!==e&&"message"in e&&"string"==typeof e.message?e.message:t.statusText}(s));if(204!==s.status)return s.json()}}(n);return{getActiveAttempt:t=>T(e.STUDENT.ATTEMPTS_ACTIVE_BY_ROOM(t)),getActive:()=>T(e.STUDENT.ATTEMPTS_ACTIVE),getAttempt:(t,n)=>T(`${e.STUDENT.ATTEMPT_DETAIL(t)}${n?`?token=${encodeURIComponent(n)}`:""}`),deleteAttempt:t=>T(e.STUDENT.ATTEMPT_DELETE(t),{method:"DELETE"}),getRoom:t=>T(e.STUDENT.ROOM_DETAIL(t)),startAttempt:t=>T(e.STUDENT.ATTEMPTS_START,{method:"POST",body:JSON.stringify({examRoomId:t})}),getQuestions:t=>T(e.STUDENT.ATTEMPT_QUESTIONS(t)),saveAnswers:async({attemptId:t,answers:n,keepalive:a})=>{await T(e.STUDENT.ATTEMPT_ANSWERS(t),{method:"POST",keepalive:a,body:JSON.stringify({answers:n})})},submitAttempt:t=>T(e.STUDENT.ATTEMPT_SUBMIT(t),{method:"POST"}),uploadAudioAnswer:({attemptId:t,questionId:n,audioBlob:a,durationMs:o})=>{const s=new FormData,i="audio/webm"===a.type?"webm":"audio/ogg"===a.type?"ogg":"mp3";return s.append("file",a,`recording.${i}`),s.append("questionId",n),void 0!==o&&s.append("durationMs",String(o)),T(e.STUDENT.ATTEMPT_AUDIO_ANSWER(t),{method:"POST",body:s})},uploadProctoringSnapshot:({attemptId:t,imageBlob:n})=>{const a=new FormData;return a.append("file",n,"snapshot.jpg"),T(e.STUDENT.ATTEMPT_PROCTORING_SNAPSHOT(t),{method:"POST",body:a})},realtimeGradeSingle:({attemptId:t,questionId:n,audioKey:a})=>T(e.SHARED.SPEAKING_REALTIME_GRADE,{method:"POST",body:JSON.stringify({attemptId:t,questionId:n,audioKey:a})}),getGradingSummary:t=>T(e.STUDENT.ATTEMPT_GRADING_SUMMARY(t)),getAnswerDetail:(t,n)=>T(e.STUDENT.ATTEMPT_ANSWER_DETAIL(t,n)),getAnswersGradingStatus:(t,n)=>T(e.STUDENT.ATTEMPT_ANSWERS_GRADING_STATUS(t),{method:"POST",body:JSON.stringify({answerIds:n})})}}export function createMockExamTakingApi(t){const e=new Map;return{getActiveAttempt:async()=>t.attempt,startAttempt:async()=>t.attempt,getQuestions:async()=>t.questions,saveAnswers:async({answers:t})=>{t.forEach(t=>e.set(t.questionId,t.answer))},submitAttempt:async()=>({answers:Object.fromEntries(e)})}}
@@ -1 +1 @@
1
- export {};
1
+ export{};
package/dist/api/index.js CHANGED
@@ -1,6 +1 @@
1
- export * from './exam-create';
2
- export * from './exam-questions';
3
- export * from './exam-taking';
4
- export * from './result-review';
5
- export * from '../shared/constants/ApiConstant';
6
- export * from '../shared/constants/QueryKeyConstant';
1
+ export*from"./exam-create";export*from"./exam-questions";export*from"./exam-taking";export*from"./result-review";export*from"../shared/constants/ApiConstant";export*from"../shared/constants/QueryKeyConstant";
@@ -1,50 +1 @@
1
- import { API_BASE_URL, API_ENDPOINTS } from '../shared/constants/ApiConstant';
2
- export function createResultReviewApi(options = {}) {
3
- const request = createRequest(options);
4
- return {
5
- getDetailedResult: (attemptId, token) => request(withToken(API_ENDPOINTS.STUDENT.RESULT_DETAIL(attemptId), token)),
6
- getAttempt: (attemptId, token) => request(withToken(API_ENDPOINTS.STUDENT.ATTEMPT_DETAIL(attemptId), token)),
7
- exportPdf: async (attemptId, examType) => {
8
- const response = await (options.fetcher ?? fetch)(`${options.baseUrl ?? API_BASE_URL ?? ''}${API_ENDPOINTS.STUDENT.EXPORT_NATIVE_PDF}`, {
9
- method: 'POST',
10
- credentials: 'include',
11
- headers: { 'Content-Type': 'application/json', ...options.headers },
12
- body: JSON.stringify({ attemptId, examType }),
13
- });
14
- if (!response.ok)
15
- throw new Error(await readErrorMessage(response));
16
- return { blob: await response.blob(), filename: readFilename(response.headers, attemptId) };
17
- },
18
- };
19
- }
20
- export function createMockResultReviewApi(seed) {
21
- return {
22
- getDetailedResult: async () => seed.detail,
23
- getAttempt: async () => seed.attempt ?? { id: seed.detail.id },
24
- exportPdf: async () => ({ blob: new Blob(['mock pdf'], { type: 'application/pdf' }), filename: `result-${seed.detail.id}.pdf` }),
25
- };
26
- }
27
- function createRequest({ baseUrl = API_BASE_URL ?? '', fetcher = fetch, headers }) {
28
- return async function request(path, init) {
29
- const response = await fetcher(`${baseUrl}${path}`, { credentials: 'include', ...init, headers: { 'Content-Type': 'application/json', ...headers, ...init?.headers } });
30
- if (!response.ok)
31
- throw new Error(await readErrorMessage(response));
32
- return response.json();
33
- };
34
- }
35
- function withToken(path, token) {
36
- if (!token)
37
- return path;
38
- const separator = path.includes('?') ? '&' : '?';
39
- return `${path}${separator}token=${encodeURIComponent(token)}`;
40
- }
41
- function readFilename(headers, attemptId) {
42
- const disposition = headers.get('content-disposition') ?? '';
43
- return disposition.match(/filename="?([^\"]+)"?/)?.[1] ?? `result-${attemptId.slice(0, 8)}.pdf`;
44
- }
45
- async function readErrorMessage(response) {
46
- const body = await response.json().catch(() => undefined);
47
- if (typeof body === 'object' && body !== null && 'message' in body && typeof body.message === 'string')
48
- return body.message;
49
- return response.statusText;
50
- }
1
+ import{API_BASE_URL as e,API_ENDPOINTS as t}from"../shared/constants/ApiConstant";export function createResultReviewApi(i={}){const r=function({baseUrl:t=e??"",fetcher:n=fetch,headers:a}){return async function(e,i){const r=await n(`${t}${e}`,{credentials:"include",...i,headers:{"Content-Type":"application/json",...a,...i?.headers}});if(!r.ok)throw new Error(await o(r));return r.json()}}(i);return{getDetailedResult:(e,a)=>r(n(t.STUDENT.RESULT_DETAIL(e),a)),getAttempt:(e,a)=>r(n(t.STUDENT.ATTEMPT_DETAIL(e),a)),exportPdf:async(n,r)=>{const s=await(i.fetcher??fetch)(`${i.baseUrl??e??""}${t.STUDENT.EXPORT_NATIVE_PDF}`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",...i.headers},body:JSON.stringify({attemptId:n,examType:r})});if(!s.ok)throw new Error(await o(s));return{blob:await s.blob(),filename:a(s.headers,n)}}}}export function createMockResultReviewApi(e){return{getDetailedResult:async()=>e.detail,getAttempt:async()=>e.attempt??{id:e.detail.id},exportPdf:async()=>({blob:new Blob(["mock pdf"],{type:"application/pdf"}),filename:`result-${e.detail.id}.pdf`})}}function n(e,t){if(!t)return e;const n=e.includes("?")?"&":"?";return`${e}${n}token=${encodeURIComponent(t)}`}function a(e,t){const n=e.get("content-disposition")??"";return n.match(/filename="?([^\"]+)"?/)?.[1]??`result-${t.slice(0,8)}.pdf`}async function o(e){const t=await e.json().catch(()=>{});return"object"==typeof t&&null!==t&&"message"in t&&"string"==typeof t.message?t.message:e.statusText}
@@ -1 +1 @@
1
- export {};
1
+ export{};
@@ -1,58 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useEffect, useState } from 'react';
4
- import { useEditor, EditorContent } from '@tiptap/react';
5
- import StarterKit from '@tiptap/starter-kit';
6
- const DEFAULT_CONTENT = `
7
- <p><strong>Describe a topic you know well.</strong></p>
8
- <p>You should say:</p>
9
- <ul>
10
- <li>What it is</li>
11
- <li>When you first learned about it</li>
12
- <li>Why you find it interesting</li>
13
- </ul>
14
- <p><em>and explain how it has impacted your life.</em></p>
15
- `;
16
- export function CueCardEditor({ value, onChange, label, }) {
17
- const [mounted, setMounted] = useState(false);
18
- const editor = useEditor({
19
- immediatelyRender: false,
20
- extensions: [StarterKit],
21
- content: value || DEFAULT_CONTENT,
22
- onUpdate: ({ editor }) => {
23
- onChange(editor.getHTML());
24
- },
25
- editorProps: {
26
- attributes: {
27
- class: 'tiptap-editor focus:outline-none min-h-[250px] p-4 bg-white border border-gray-200 rounded-b-md',
28
- },
29
- },
30
- });
31
- useEffect(() => {
32
- setMounted(true);
33
- }, []);
34
- // Sync value if changed from outside (e.g., initial load)
35
- useEffect(() => {
36
- if (editor && value !== undefined && value !== editor.getHTML()) {
37
- // Small timeout to prevent update loops
38
- setTimeout(() => editor.commands.setContent(value), 0);
39
- }
40
- }, [value, editor]);
41
- if (!mounted)
42
- return null;
43
- return (_jsxs("div", { className: "w-full flex flex-col gap-4", children: [label && _jsx("div", { children: label }), _jsxs("div", { className: "flex flex-col border border-gray-300 rounded-md shadow-sm", children: [_jsxs("div", { className: "flex flex-wrap gap-2 p-2 relative bg-gray-50 border-b border-gray-300 rounded-t-md", children: [_jsx("button", { type: "button", onClick: () => editor?.chain().focus().toggleBold().run(), disabled: !editor?.can().chain().focus().toggleBold().run(), className: `px-3 py-1 rounded text-sm font-bold ${editor?.isActive('bold')
44
- ? 'bg-blue-500 text-white'
45
- : 'bg-white border text-gray-700 hover:bg-gray-100'}`, children: "B" }), _jsx("button", { type: "button", onClick: () => editor?.chain().focus().toggleItalic().run(), disabled: !editor?.can().chain().focus().toggleItalic().run(), className: `px-3 py-1 rounded text-sm italic ${editor?.isActive('italic')
46
- ? 'bg-blue-500 text-white'
47
- : 'bg-white border text-gray-700 hover:bg-gray-100'}`, children: "I" }), _jsx("div", { className: "w-px h-6 bg-gray-300 mx-1 self-center" }), _jsx("button", { type: "button", onClick: () => editor?.chain().focus().toggleHeading({ level: 1 }).run(), className: `px-3 py-1 rounded text-sm font-bold ${editor?.isActive('heading', { level: 1 })
48
- ? 'bg-blue-500 text-white'
49
- : 'bg-white border text-gray-700 hover:bg-gray-100'}`, children: "H1" }), _jsx("button", { type: "button", onClick: () => editor?.chain().focus().toggleHeading({ level: 2 }).run(), className: `px-3 py-1 rounded text-sm font-bold ${editor?.isActive('heading', { level: 2 })
50
- ? 'bg-blue-500 text-white'
51
- : 'bg-white border text-gray-700 hover:bg-gray-100'}`, children: "H2" }), _jsx("button", { type: "button", onClick: () => editor?.chain().focus().toggleHeading({ level: 3 }).run(), className: `px-3 py-1 rounded text-sm font-bold ${editor?.isActive('heading', { level: 3 })
52
- ? 'bg-blue-500 text-white'
53
- : 'bg-white border text-gray-700 hover:bg-gray-100'}`, children: "H3" }), _jsx("div", { className: "w-px h-6 bg-gray-300 mx-1 self-center" }), _jsx("button", { type: "button", onClick: () => editor?.chain().focus().toggleBulletList().run(), className: `px-3 py-1 rounded text-sm ${editor?.isActive('bulletList')
54
- ? 'bg-blue-500 text-white'
55
- : 'bg-white border text-gray-700 hover:bg-gray-100'}`, children: "\u2022 List" }), _jsx("button", { type: "button", onClick: () => editor?.chain().focus().toggleOrderedList().run(), className: `px-3 py-1 rounded text-sm ${editor?.isActive('orderedList')
56
- ? 'bg-blue-500 text-white'
57
- : 'bg-white border text-gray-700 hover:bg-gray-100'}`, children: "1. List" })] }), _jsx(EditorContent, { editor: editor })] })] }));
58
- }
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useEffect as i,useState as o}from"react";import{useEditor as r,EditorContent as n}from"@tiptap/react";import l from"@tiptap/starter-kit";export function CueCardEditor({value:a,onChange:d,label:s}){const[c,g]=o(!1),b=r({immediatelyRender:!1,extensions:[l],content:a||"\n <p><strong>Describe a topic you know well.</strong></p>\n <p>You should say:</p>\n <ul>\n <li>What it is</li>\n <li>When you first learned about it</li>\n <li>Why you find it interesting</li>\n </ul>\n <p><em>and explain how it has impacted your life.</em></p>\n",onUpdate:({editor:e})=>{d(e.getHTML())},editorProps:{attributes:{class:"tiptap-editor focus:outline-none min-h-[250px] p-4 bg-white border border-gray-200 rounded-b-md"}}});return i(()=>{g(!0)},[]),i(()=>{b&&void 0!==a&&a!==b.getHTML()&&setTimeout(()=>b.commands.setContent(a),0)},[a,b]),c?t("div",{className:"w-full flex flex-col gap-4",children:[s&&e("div",{children:s}),t("div",{className:"flex flex-col border border-gray-300 rounded-md shadow-sm",children:[t("div",{className:"flex flex-wrap gap-2 p-2 relative bg-gray-50 border-b border-gray-300 rounded-t-md",children:[e("button",{type:"button",onClick:()=>b?.chain().focus().toggleBold().run(),disabled:!b?.can().chain().focus().toggleBold().run(),className:"px-3 py-1 rounded text-sm font-bold "+(b?.isActive("bold")?"bg-blue-500 text-white":"bg-white border text-gray-700 hover:bg-gray-100"),children:"B"}),e("button",{type:"button",onClick:()=>b?.chain().focus().toggleItalic().run(),disabled:!b?.can().chain().focus().toggleItalic().run(),className:"px-3 py-1 rounded text-sm italic "+(b?.isActive("italic")?"bg-blue-500 text-white":"bg-white border text-gray-700 hover:bg-gray-100"),children:"I"}),e("div",{className:"w-px h-6 bg-gray-300 mx-1 self-center"}),e("button",{type:"button",onClick:()=>b?.chain().focus().toggleHeading({level:1}).run(),className:"px-3 py-1 rounded text-sm font-bold "+(b?.isActive("heading",{level:1})?"bg-blue-500 text-white":"bg-white border text-gray-700 hover:bg-gray-100"),children:"H1"}),e("button",{type:"button",onClick:()=>b?.chain().focus().toggleHeading({level:2}).run(),className:"px-3 py-1 rounded text-sm font-bold "+(b?.isActive("heading",{level:2})?"bg-blue-500 text-white":"bg-white border text-gray-700 hover:bg-gray-100"),children:"H2"}),e("button",{type:"button",onClick:()=>b?.chain().focus().toggleHeading({level:3}).run(),className:"px-3 py-1 rounded text-sm font-bold "+(b?.isActive("heading",{level:3})?"bg-blue-500 text-white":"bg-white border text-gray-700 hover:bg-gray-100"),children:"H3"}),e("div",{className:"w-px h-6 bg-gray-300 mx-1 self-center"}),e("button",{type:"button",onClick:()=>b?.chain().focus().toggleBulletList().run(),className:"px-3 py-1 rounded text-sm "+(b?.isActive("bulletList")?"bg-blue-500 text-white":"bg-white border text-gray-700 hover:bg-gray-100"),children:"• List"}),e("button",{type:"button",onClick:()=>b?.chain().focus().toggleOrderedList().run(),className:"px-3 py-1 rounded text-sm "+(b?.isActive("orderedList")?"bg-blue-500 text-white":"bg-white border text-gray-700 hover:bg-gray-100"),children:"1. List"})]}),e(n,{editor:b})]})]}):null}
@@ -1,15 +1 @@
1
- 'use client';
2
- import { jsx as _jsx } from "react/jsx-runtime";
3
- import { useState } from 'react';
4
- import { ImageOff } from 'lucide-react';
5
- import { Skeleton } from '../../components/ui/skeleton';
6
- import { usePresignedFileUrl } from '../../shared/lib/hooks/usePresignedFileUrl';
7
- export function ResolvedImage({ src, fallback, showLoadingState = true, className, alt = '', ...props }) {
8
- const { previewUrl, isLoading, error } = usePresignedFileUrl(src);
9
- const [hasError, setHasError] = useState(false);
10
- if (isLoading && showLoadingState)
11
- return fallback ?? _jsx(Skeleton, { className: className });
12
- if (error || (!previewUrl && !src) || hasError)
13
- return fallback ?? _jsx("div", { className: `flex items-center justify-center bg-gray-100 ${className}`, children: _jsx(ImageOff, { className: "h-8 w-8 text-gray-400" }) });
14
- return _jsx("img", { src: previewUrl || src, alt: alt, className: className, onError: () => setHasError(true), ...props });
15
- }
1
+ "use client";import{jsx as e}from"react/jsx-runtime";import{useState as r}from"react";import{ImageOff as s}from"lucide-react";import{Skeleton as t}from"../../components/ui/skeleton";import{usePresignedFileUrl as a}from"../../shared/lib/hooks/usePresignedFileUrl";export function ResolvedImage({src:o,fallback:i,showLoadingState:m=!0,className:c,alt:l="",...n}){const{previewUrl:f,isLoading:d,error:g}=a(o),[p,u]=r(!1);return d&&m?i??e(t,{className:c}):g||!f&&!o||p?i??e("div",{className:`flex items-center justify-center bg-gray-100 ${c}`,children:e(s,{className:"h-8 w-8 text-gray-400"})}):e("img",{src:f||o,alt:l,className:c,onError:()=>u(!0),...n})}
@@ -0,0 +1,8 @@
1
+ export interface CompletionDialogProps {
2
+ open: boolean;
3
+ onOpenChange: (open: boolean) => void;
4
+ completedCount: number;
5
+ totalQuestions: number;
6
+ onConfirm: () => void;
7
+ }
8
+ export declare function CompletionDialog({ open, onOpenChange, completedCount, totalQuestions, onConfirm, }: CompletionDialogProps): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as n}from"react/jsx-runtime";import{AlertDialog as t,AlertDialogAction as h,AlertDialogCancel as c,AlertDialogContent as o,AlertDialogDescription as i,AlertDialogFooter as r,AlertDialogHeader as l,AlertDialogTitle as s}from"../../components/ui/alert-dialog";import{AlertTriangle as a,CheckCircle as d}from"lucide-react";export function CompletionDialog({open:m,onOpenChange:p,completedCount:g,totalQuestions:u,onConfirm:x}){const C=g===u;return e(t,{open:m,onOpenChange:p,children:n(o,{children:[n(l,{children:[n(s,{className:"flex items-center gap-2",children:[C?e(d,{className:"h-5 w-5 text-green-500"}):e(a,{className:"h-5 w-5 text-amber-500"}),C?"Lưu đề thi thành công":"Đề thi chưa hoàn thành"]}),e(i,{asChild:!0,children:n("div",{className:"space-y-2 text-sm text-muted-foreground",children:[n("p",{children:["Bạn đã hoàn thành ",n("strong",{children:[g,"/",u]})," câu hỏi."]}),C?e("p",{children:"Đề thi đã sẵn sàng để gán vào phòng thi."}):n("p",{children:["Còn ",e("strong",{children:u-g})," câu chưa được tạo. Bạn có thể tiếp tục tạo sau hoặc thoát và quay lại sau."]})]})})]}),n(r,{children:[e(c,{children:C?"Tiếp tục chỉnh sửa":"Tiếp tục tạo"}),e(h,{onClick:x,className:C?"bg-green-600 hover:bg-green-700":"bg-blue-600 hover:bg-blue-700",children:C?"Xác nhận":"Lưu và thoát"})]})]})})}