@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,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{};
@@ -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 a}from"../shared/lib/utils/question-transform";export function createExamQuestionsApi(s={}){const n=function({baseUrl:e=t??"",fetcher:a=fetch,headers:s,apiKey:n}){return async function(t,r){const i=n?{"X-API-Key":n}:void 0,o=await a(`${e}${t}`,{...n?{}:{credentials:"include"},...r,headers:{"Content-Type":"application/json",...i,...s,...r?.headers}});if(!o.ok){const t=await o.json().catch(()=>({}));throw new Error(t.message||o.statusText)}return o.json()}}(s);return{getExam:t=>n(e.MANAGE.EXAM_DETAIL(t)),getQuestions:async t=>{const a=await n(e.MANAGE.EXAM_QUESTIONS(t));return Array.isArray(a.data)?a.data:a.data.questions??[]},saveQuestion:async({examId:t,question:s,template:r})=>{if(!t)throw new Error("examId is required");const i=a(s,function(t,e){const a=e?.parts.findIndex(e=>e.id===t.partId)??-1,s=a>=0?a+1:void 0,{partId:n,questionNumber:r}=t,i=t.sectionId;return{partId:n,partNo:s,questionNumber:r,sectionId:i}}(s,r)),o=await n(e.MANAGE.EXAM_QUESTIONS(t),{method:"POST",body:JSON.stringify({questions:[i]})}),u=o.data.results?.[0]?.data?.id;return{...s,id:u??s.id,isCompleted:!0}},saveExam:async()=>{},updatePartAudio:t=>async function(t,{examId:a,partId:s,audioUrl:n,template:r}){await t(e.MANAGE.EXAM_TEMPLATE(a),{method:"PATCH",body:JSON.stringify({id:r.id,title:r.name,totalQuestions:r.totalQuestions,totalParts:r.parts.length,description:r.description||"",examLevel:r.level||"",type:r.type,parts:r.parts.map(t=>({...t,audioUrl:t.id===s?n:t.audioUrl}))})})}(n,t)}}export const createExamQuestionsFetchApi=createExamQuestionsApi;export function createMockExamQuestionsApi(t){const e=new Map((t.questions??[]).map(t=>[(t.questionNumber??1)-1,t]));let a=t.exam;return{getExam:async()=>({data:a}),getQuestions:async()=>Array.from(e.values()),saveQuestion:async({question:t,questionIndex:a})=>{const s={...t,questionNumber:t.questionNumber??a+1,isCompleted:!0};return e.set(a,s),s},saveExam:async()=>{},updatePartAudio:async({partId:t,audioUrl:e})=>{a.template&&"parts"in a.template&&(a={...a,template:{...a.template,parts:a.template.parts.map(a=>a.id===t?{...a,audioUrl:e}:a)}})}}}
@@ -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})}
@@ -1,35 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useCallback, useEffect, useRef } from 'react';
4
- import { Controller } from 'react-hook-form';
5
- import { FileText, Loader2 } from 'lucide-react';
6
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../components/ui/card';
7
- import { Input } from '../../components/ui/input';
8
- import { Label } from '../../components/ui/label';
9
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../components/ui/select';
10
- import { Textarea } from '../../components/ui/textarea';
11
- import { useT } from '../../shared/lib/i18n';
12
- export function CreateExamInfoForm({ form, examLevels, isLoadingExamLevels = false }) {
13
- const t = useT();
14
- const isCodeManuallyEdited = useRef(false);
15
- const nameValue = form.watch('name');
16
- const setValueRef = useRef(form.setValue);
17
- setValueRef.current = form.setValue;
18
- const generateCodeFromName = useCallback((name) => name.normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/đ/g, 'd').replace(/Đ/g, 'D').toLowerCase().trim().replace(/[^a-z0-9\s-]/g, '').replace(/[\s]+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '').substring(0, 100), []);
19
- useEffect(() => {
20
- if (isCodeManuallyEdited.current)
21
- return;
22
- setValueRef.current('code', nameValue ? generateCodeFromName(nameValue) : '', { shouldValidate: false });
23
- }, [nameValue, generateCodeFromName]);
24
- const hasError = (field) => !!form.formState.errors[field];
25
- const error = (field) => form.formState.errors[field]?.message;
26
- return (_jsxs(Card, { className: "border border-gray-200", children: [_jsxs(CardHeader, { className: "border-b border-gray-100 bg-gray-50/50", children: [_jsxs(CardTitle, { className: "flex items-center gap-2 text-base font-semibold", children: [_jsx(FileText, { className: "h-4 w-4 text-blue-600" }), t('admin.exams.form.basicInfo')] }), _jsx(CardDescription, { children: t('admin.exams.form.requiredHint') })] }), _jsxs(CardContent, { className: "space-y-4 pt-6", children: [_jsxs("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2", children: [_jsx(Field, { label: t('admin.exams.form.nameLabel'), error: error('name'), required: true, children: _jsx(Input, { id: "name", maxLength: 255, className: hasError('name') ? 'border-red-500' : '', placeholder: t('admin.exams.form.namePlaceholder'), ...form.register('name') }) }), _jsx(Field, { label: t('admin.exams.form.codeLabel'), error: error('code'), required: true, children: _jsx(CodeInput, { form: form, hasError: hasError('code'), onManualEdit: (value) => { isCodeManuallyEdited.current = value.length > 0; } }) }), _jsx(Field, { label: t('admin.exams.form.totalPoints'), error: error('totalPoints'), required: true, children: _jsx(Controller, { name: "totalPoints", control: form.control, render: ({ field }) => _jsx(Input, { id: "totalPoints", inputMode: "numeric", value: field.value, onBlur: field.onBlur, onChange: (event) => field.onChange(event.target.value.replace(/[^0-9]/g, '')), className: hasError('totalPoints') ? 'border-red-500' : '' }) }) }), _jsx(Field, { label: t('admin.exams.form.level'), error: error('level'), children: _jsx(Controller, { name: "level", control: form.control, render: ({ field }) => _jsxs(Select, { value: field.value, onValueChange: field.onChange, disabled: isLoadingExamLevels, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: isLoadingExamLevels ? t('admin.exams.form.loading') : t('admin.exams.form.levelPlaceholder') }) }), _jsx(SelectContent, { children: isLoadingExamLevels ? _jsx("div", { className: "flex justify-center p-4", children: _jsx(Loader2, { className: "h-4 w-4 animate-spin" }) }) : examLevels.map((level) => _jsx(SelectItem, { value: level.id, children: level.name }, level.id)) })] }) }) }), _jsx(Field, { label: t('admin.exams.form.difficulty'), error: error('difficulty'), children: _jsx(Controller, { name: "difficulty", control: form.control, render: ({ field }) => _jsxs(Select, { value: field.value, onValueChange: field.onChange, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: t('admin.exams.form.difficultyPlaceholder') }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "easy", children: t('admin.exams.difficulty.easy') }), _jsx(SelectItem, { value: "medium", children: t('admin.exams.difficulty.medium') }), _jsx(SelectItem, { value: "hard", children: t('admin.exams.difficulty.hard') })] })] }) }) }), _jsx(Field, { label: t('admin.exams.form.duration'), error: error('duration'), children: _jsx(Input, { id: "duration", inputMode: "numeric", placeholder: "60", className: hasError('duration') ? 'border-red-500' : '', ...form.register('duration') }) })] }), _jsx(Field, { label: t('admin.exams.form.description'), error: error('description'), children: _jsx(Textarea, { id: "description", rows: 4, maxLength: 2000, placeholder: t('admin.exams.form.descriptionPlaceholder'), ...form.register('description') }) })] })] }));
27
- }
28
- function CodeInput({ form, hasError, onManualEdit }) {
29
- const t = useT();
30
- const { onChange, ...register } = form.register('code');
31
- return _jsx(Input, { id: "code", maxLength: 100, className: hasError ? 'border-red-500' : '', placeholder: t('admin.exams.form.codePlaceholder'), ...register, onChange: (event) => { onChange(event); onManualEdit(event.target.value); } });
32
- }
33
- function Field({ label, error, required, children }) {
34
- return _jsxs("div", { children: [_jsxs(Label, { children: [label, " ", required && _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx("div", { className: "mt-1", children: children }), error && _jsx("p", { className: "mt-1 text-sm text-red-600", children: error })] });
35
- }
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useCallback as a,useEffect as l,useRef as o}from"react";import{Controller as n}from"react-hook-form";import{FileText as i,Loader2 as d}from"lucide-react";import{Card as m,CardContent as t,CardDescription as c,CardHeader as s,CardTitle as u}from"../../components/ui/card";import{Input as h}from"../../components/ui/input";import{Label as f}from"../../components/ui/label";import{Select as p,SelectContent as g,SelectItem as x,SelectTrigger as b,SelectValue as v}from"../../components/ui/select";import{Textarea as N}from"../../components/ui/textarea";import{useT as y}from"../../shared/lib/i18n";export function CreateExamInfoForm({form:f,examLevels:L,isLoadingExamLevels:E=!1}){const q=y(),w=o(!1),V=f.watch("name"),j=o(f.setValue);j.current=f.setValue;const M=a(e=>e.normalize("NFD").replace(/[\u0300-\u036f]/g,"").replace(/đ/g,"d").replace(/Đ/g,"D").toLowerCase().trim().replace(/[^a-z0-9\s-]/g,"").replace(/[\s]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"").substring(0,100),[]);l(()=>{w.current||j.current("code",V?M(V):"",{shouldValidate:!1})},[V,M]);const z=e=>!!f.formState.errors[e],B=e=>f.formState.errors[e]?.message;return r(m,{className:"border border-gray-200",children:[r(s,{className:"border-b border-gray-100 bg-gray-50/50",children:[r(u,{className:"flex items-center gap-2 text-base font-semibold",children:[e(i,{className:"h-4 w-4 text-blue-600"}),q("admin.exams.form.basicInfo")]}),e(c,{children:q("admin.exams.form.requiredHint")})]}),r(t,{className:"space-y-4 pt-6",children:[r("div",{className:"grid grid-cols-1 gap-4 md:grid-cols-2",children:[e(P,{label:q("admin.exams.form.nameLabel"),error:B("name"),required:!0,children:e(h,{id:"name",maxLength:255,className:z("name")?"border-red-500":"",placeholder:q("admin.exams.form.namePlaceholder"),...f.register("name")})}),e(P,{label:q("admin.exams.form.codeLabel"),error:B("code"),required:!0,children:e(C,{form:f,hasError:z("code"),onManualEdit:e=>{w.current=e.length>0}})}),e(P,{label:q("admin.exams.form.totalPoints"),error:B("totalPoints"),required:!0,children:e(n,{name:"totalPoints",control:f.control,render:({field:r})=>e(h,{id:"totalPoints",inputMode:"numeric",value:r.value,onBlur:r.onBlur,onChange:e=>r.onChange(e.target.value.replace(/[^0-9]/g,"")),className:z("totalPoints")?"border-red-500":""})})}),e(P,{label:q("admin.exams.form.level"),error:B("level"),children:e(n,{name:"level",control:f.control,render:({field:a})=>r(p,{value:a.value,onValueChange:a.onChange,disabled:E,children:[e(b,{children:e(v,{placeholder:q(E?"admin.exams.form.loading":"admin.exams.form.levelPlaceholder")})}),e(g,{children:E?e("div",{className:"flex justify-center p-4",children:e(d,{className:"h-4 w-4 animate-spin"})}):L.map(r=>e(x,{value:r.id,children:r.name},r.id))})]})})}),e(P,{label:q("admin.exams.form.difficulty"),error:B("difficulty"),children:e(n,{name:"difficulty",control:f.control,render:({field:a})=>r(p,{value:a.value,onValueChange:a.onChange,children:[e(b,{children:e(v,{placeholder:q("admin.exams.form.difficultyPlaceholder")})}),r(g,{children:[e(x,{value:"easy",children:q("admin.exams.difficulty.easy")}),e(x,{value:"medium",children:q("admin.exams.difficulty.medium")}),e(x,{value:"hard",children:q("admin.exams.difficulty.hard")})]})]})})}),e(P,{label:q("admin.exams.form.duration"),error:B("duration"),children:e(h,{id:"duration",inputMode:"numeric",placeholder:"60",className:z("duration")?"border-red-500":"",...f.register("duration")})})]}),e(P,{label:q("admin.exams.form.description"),error:B("description"),children:e(N,{id:"description",rows:4,maxLength:2e3,placeholder:q("admin.exams.form.descriptionPlaceholder"),...f.register("description")})})]})]})}function C({form:r,hasError:a,onManualEdit:l}){const o=y(),{onChange:n,...i}=r.register("code");return e(h,{id:"code",maxLength:100,className:a?"border-red-500":"",placeholder:o("admin.exams.form.codePlaceholder"),...i,onChange:e=>{n(e),l(e.target.value)}})}function P({label:a,error:l,required:o,children:n}){return r("div",{children:[r(f,{children:[a," ",o&&e("span",{className:"text-red-500",children:"*"})]}),e("div",{className:"mt-1",children:n}),l&&e("p",{className:"mt-1 text-sm text-red-600",children:l})]})}