@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,72 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Check, X } from 'lucide-react';
4
- import { Badge } from '../../components/ui/badge';
5
- import { ResolvedImage } from '../../components/common/ResolvedImage';
6
- import { asRecord, normalizeRecord, normalizeSelected, readQuestionText, readString, transformAdjectiveQuestions, transformChoiceQuestions, transformColoring, transformFillQuestions } from './review-renderer.utils';
7
- export function ReviewQuestionRenderer({ part, reviewData }) {
8
- if (['CHOOSE_THE_CORRECT_ANSWER', 'LOOK_PICTURE_CHOOSE_CORRECT_ANSWER', 'TRUE_FALSE', 'CHOOSE_CORRECT_ADJECTIVE'].includes(part.questionType)) {
9
- return _jsx(ChoiceReview, { part: part, reviewData: reviewData });
10
- }
11
- if (['FILL_IN_BLANK', 'WRITE_CORRECT_VERB_FORM', 'ANSWER_THE_QUESTION', 'WRITE_SENTENCES'].includes(part.questionType)) {
12
- return _jsx(FillReview, { part: part, reviewData: reviewData });
13
- }
14
- if (part.questionType === 'READ_AND_COLOR_OBJECTS' || part.questionType === 'IMAGE_OBJECT_MATCHING') {
15
- return _jsx(ColoringReview, { part: part, reviewData: reviewData });
16
- }
17
- return _jsx(GenericReview, { part: part, reviewData: reviewData });
18
- }
19
- function ChoiceReview({ part, reviewData }) {
20
- const questions = part.questionType === 'CHOOSE_CORRECT_ADJECTIVE' ? transformAdjectiveQuestions(part.questions) : transformChoiceQuestions(part.questions);
21
- return _jsx("div", { className: "space-y-4", children: questions.map((question) => {
22
- const selected = normalizeSelected(reviewData.studentAnswers[question.id]);
23
- const correct = normalizeSelected(reviewData.correctOptionIdMap[question.id]);
24
- return _jsxs(QuestionShell, { question: part.questions.find((item) => item.id === question.id), reviewData: reviewData, children: [question.imageUrl && _jsx(ResolvedImage, { src: question.imageUrl, alt: "Question", className: "mb-3 max-h-64 rounded object-contain" }), _jsx("p", { className: "mb-3 font-medium", children: 'questionText' in question ? question.questionText : `${question.sentenceBefore} ____ ${question.sentenceAfter}` }), _jsx("div", { className: "grid gap-2 md:grid-cols-2", children: question.options.map((option) => _jsxs("div", { className: `rounded-lg border p-3 ${correct.includes(option.id) ? 'border-green-400 bg-green-50' : selected.includes(option.id) ? 'border-red-300 bg-red-50' : 'border-slate-200'}`, children: [_jsxs("span", { className: "font-bold", children: [option.label, "."] }), " ", option.imageUrl ? _jsx(ResolvedImage, { src: option.imageUrl, alt: option.label, className: "mt-2 max-h-24 rounded object-contain" }) : option.text] }, option.id)) })] }, question.id);
25
- }) });
26
- }
27
- function FillReview({ part, reviewData }) {
28
- const questions = transformFillQuestions(part.questions);
29
- return _jsx("div", { className: "space-y-4", children: questions.map((question) => _jsxs(QuestionShell, { question: part.questions.find((item) => item.id === question.id), reviewData: reviewData, children: [_jsxs("p", { className: "text-base", children: [_jsx("span", { children: question.textBefore }), " ", _jsx(AnswerPill, { value: reviewData.studentAnswers[question.id], correct: reviewData.correctAnswerTextMap[question.id] }), " ", _jsx("span", { children: question.textAfter })] }), question.imageUrl && _jsx(ResolvedImage, { src: question.imageUrl, alt: "Question", className: "mt-3 max-h-64 rounded object-contain" })] }, question.id)) });
30
- }
31
- function ColoringReview({ part, reviewData }) {
32
- const data = transformColoring(part.questions);
33
- return _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "rounded-xl border bg-white p-4", children: [_jsx("p", { className: "font-semibold", children: data.instruction || part.instructions }), data.imageUrl && _jsx(ResolvedImage, { src: data.imageUrl, alt: "Coloring", className: "mt-3 max-h-96 rounded object-contain" }), _jsx("div", { className: "mt-3 flex flex-wrap gap-2", children: data.hintItems.map((item) => _jsx("span", { className: "rounded-full border px-3 py-1 text-sm", style: { backgroundColor: item.color || undefined }, children: item.label || item.id }, item.id)) })] }), part.questions.map((question) => _jsx(QuestionShell, { question: question, reviewData: reviewData, children: _jsx("pre", { className: "overflow-auto rounded bg-slate-50 p-3 text-xs", children: JSON.stringify({ studentAnswer: reviewData.rawStudentAnswerMap[question.id], correctAnswer: reviewData.rawCorrectAnswerMap[question.id] }, null, 2) }) }, question.id))] });
34
- }
35
- function GenericReview({ part, reviewData }) {
36
- return _jsx("div", { className: "space-y-4", children: part.questions.map((question) => _jsxs(QuestionShell, { question: question, reviewData: reviewData, children: [_jsx("p", { className: "mb-3 whitespace-pre-wrap text-sm text-slate-700", children: readQuestionText(question) }), _jsx("pre", { className: "overflow-auto rounded bg-slate-50 p-3 text-xs", children: JSON.stringify({ content: question.content, studentAnswer: reviewData.rawStudentAnswerMap[question.id], correctAnswer: reviewData.rawCorrectAnswerMap[question.id] }, null, 2) })] }, question.id)) });
37
- }
38
- function QuestionShell({ question, reviewData, children }) {
39
- if (!question)
40
- return null;
41
- const score = reviewData.questionScoreMap[question.id];
42
- const isCorrect = reviewData.isCorrectMap[question.id];
43
- return _jsxs("div", { className: "rounded-xl border bg-white p-4", children: [_jsxs("div", { className: "mb-3 flex flex-wrap items-center justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("strong", { children: ["C\u00E2u ", question.question_number] }), _jsx(ResultBadge, { isCorrect: isCorrect })] }), score && _jsxs(Badge, { variant: "outline", children: [formatScore(score.score), " / ", formatScore(score.maxScore)] })] }), children, _jsx(Explanation, { question: question })] });
44
- }
45
- function ResultBadge({ isCorrect }) {
46
- if (isCorrect === true)
47
- return _jsxs(Badge, { className: "bg-green-600", children: [_jsx(Check, { className: "mr-1 h-3 w-3" }), "\u0110\u00FAng"] });
48
- if (isCorrect === false)
49
- return _jsxs(Badge, { variant: "destructive", children: [_jsx(X, { className: "mr-1 h-3 w-3" }), "Sai"] });
50
- return _jsx(Badge, { variant: "outline", children: "Ch\u01B0a ch\u1EA5m" });
51
- }
52
- function AnswerPill({ value, correct }) {
53
- return _jsxs("span", { className: "inline-flex rounded-md border border-blue-200 bg-blue-50 px-2 py-1 font-semibold text-blue-700", children: [stringifyAnswer(value), correct ? _jsxs("span", { className: "ml-2 text-green-700", children: ["(", correct, ")"] }) : null] });
54
- }
55
- function Explanation({ question }) {
56
- const explanation = readString(question, 'explanation') || readString(asRecord(question.content), 'explanation');
57
- return explanation ? _jsxs("div", { className: "mt-3 rounded-lg bg-amber-50 p-3 text-sm text-amber-900", children: [_jsx("strong", { children: "Gi\u1EA3i th\u00EDch:" }), " ", explanation] }) : null;
58
- }
59
- function stringifyAnswer(value) {
60
- if (value == null || value === '')
61
- return '—';
62
- if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean')
63
- return String(value);
64
- const record = normalizeRecord(value);
65
- const text = Object.values(record).join(', ');
66
- return text || JSON.stringify(value);
67
- }
68
- function formatScore(score) {
69
- if (score == null || !Number.isFinite(score))
70
- return '0';
71
- return score < 10 ? score.toFixed(1) : String(Math.round(score));
72
- }
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{Check as n,X as t}from"lucide-react";import{Badge as i}from"../../components/ui/badge";import{ResolvedImage as a}from"../../components/common/ResolvedImage";import{asRecord as s,normalizeRecord as o,normalizeSelected as c,readQuestionText as l,readString as d,transformAdjectiveQuestions as u,transformChoiceQuestions as m,transformColoring as p,transformFillQuestions as h}from"./review-renderer.utils";export function ReviewQuestionRenderer({part:r,reviewData:n}){return["CHOOSE_THE_CORRECT_ANSWER","LOOK_PICTURE_CHOOSE_CORRECT_ANSWER","TRUE_FALSE","CHOOSE_CORRECT_ADJECTIVE"].includes(r.questionType)?e(b,{part:r,reviewData:n}):["FILL_IN_BLANK","WRITE_CORRECT_VERB_FORM","ANSWER_THE_QUESTION","WRITE_SENTENCES"].includes(r.questionType)?e(f,{part:r,reviewData:n}):"READ_AND_COLOR_OBJECTS"===r.questionType||"IMAGE_OBJECT_MATCHING"===r.questionType?e(N,{part:r,reviewData:n}):e(g,{part:r,reviewData:n})}function b({part:n,reviewData:t}){const i="CHOOSE_CORRECT_ADJECTIVE"===n.questionType?u(n.questions):m(n.questions);return e("div",{className:"space-y-4",children:i.map(i=>{const s=c(t.studentAnswers[i.id]),o=c(t.correctOptionIdMap[i.id]);return r(w,{question:n.questions.find(e=>e.id===i.id),reviewData:t,children:[i.imageUrl&&e(a,{src:i.imageUrl,alt:"Question",className:"mb-3 max-h-64 rounded object-contain"}),e("p",{className:"mb-3 font-medium",children:"questionText"in i?i.questionText:`${i.sentenceBefore} ____ ${i.sentenceAfter}`}),e("div",{className:"grid gap-2 md:grid-cols-2",children:i.options.map(n=>r("div",{className:"rounded-lg border p-3 "+(o.includes(n.id)?"border-green-400 bg-green-50":s.includes(n.id)?"border-red-300 bg-red-50":"border-slate-200"),children:[r("span",{className:"font-bold",children:[n.label,"."]})," ",n.imageUrl?e(a,{src:n.imageUrl,alt:n.label,className:"mt-2 max-h-24 rounded object-contain"}):n.text]},n.id))})]},i.id)})})}function f({part:n,reviewData:t}){const i=h(n.questions);return e("div",{className:"space-y-4",children:i.map(i=>r(w,{question:n.questions.find(e=>e.id===i.id),reviewData:t,children:[r("p",{className:"text-base",children:[e("span",{children:i.textBefore})," ",e(v,{value:t.studentAnswers[i.id],correct:t.correctAnswerTextMap[i.id]})," ",e("span",{children:i.textAfter})]}),i.imageUrl&&e(a,{src:i.imageUrl,alt:"Question",className:"mt-3 max-h-64 rounded object-contain"})]},i.id))})}function N({part:n,reviewData:t}){const i=p(n.questions);return r("div",{className:"space-y-4",children:[r("div",{className:"rounded-xl border bg-white p-4",children:[e("p",{className:"font-semibold",children:i.instruction||n.instructions}),i.imageUrl&&e(a,{src:i.imageUrl,alt:"Coloring",className:"mt-3 max-h-96 rounded object-contain"}),e("div",{className:"mt-3 flex flex-wrap gap-2",children:i.hintItems.map(r=>e("span",{className:"rounded-full border px-3 py-1 text-sm",style:{backgroundColor:r.color||void 0},children:r.label||r.id},r.id))})]}),n.questions.map(r=>e(w,{question:r,reviewData:t,children:e("pre",{className:"overflow-auto rounded bg-slate-50 p-3 text-xs",children:JSON.stringify({studentAnswer:t.rawStudentAnswerMap[r.id],correctAnswer:t.rawCorrectAnswerMap[r.id]},null,2)})},r.id))]})}function g({part:n,reviewData:t}){return e("div",{className:"space-y-4",children:n.questions.map(n=>r(w,{question:n,reviewData:t,children:[e("p",{className:"mb-3 whitespace-pre-wrap text-sm text-slate-700",children:l(n)}),e("pre",{className:"overflow-auto rounded bg-slate-50 p-3 text-xs",children:JSON.stringify({content:n.content,studentAnswer:t.rawStudentAnswerMap[n.id],correctAnswer:t.rawCorrectAnswerMap[n.id]},null,2)})]},n.id))})}function w({question:n,reviewData:t,children:a}){if(!n)return null;const s=t.questionScoreMap[n.id],o=t.isCorrectMap[n.id];return r("div",{className:"rounded-xl border bg-white p-4",children:[r("div",{className:"mb-3 flex flex-wrap items-center justify-between gap-2",children:[r("div",{className:"flex items-center gap-2",children:[r("strong",{children:["Câu ",n.question_number]}),e(x,{isCorrect:o})]}),s&&r(i,{variant:"outline",children:[_(s.score)," / ",_(s.maxScore)]})]}),a,e(E,{question:n})]})}function x({isCorrect:a}){return!0===a?r(i,{className:"bg-green-600",children:[e(n,{className:"mr-1 h-3 w-3"}),"Đúng"]}):!1===a?r(i,{variant:"destructive",children:[e(t,{className:"mr-1 h-3 w-3"}),"Sai"]}):e(i,{variant:"outline",children:"Chưa chấm"})}function v({value:e,correct:n}){return r("span",{className:"inline-flex rounded-md border border-blue-200 bg-blue-50 px-2 py-1 font-semibold text-blue-700",children:[C(e),n?r("span",{className:"ml-2 text-green-700",children:["(",n,")"]}):null]})}function E({question:n}){const t=d(n,"explanation")||d(s(n.content),"explanation");return t?r("div",{className:"mt-3 rounded-lg bg-amber-50 p-3 text-sm text-amber-900",children:[e("strong",{children:"Giải thích:"})," ",t]}):null}function C(e){if(null==e||""===e)return"—";if("string"==typeof e||"number"==typeof e||"boolean"==typeof e)return String(e);const r=o(e);return Object.values(r).join(", ")||JSON.stringify(e)}function _(e){return null!=e&&Number.isFinite(e)?e<10?e.toFixed(1):String(Math.round(e)):"0"}
@@ -1,2 +1 @@
1
- export { ResultReviewPageContainer } from './ResultReviewPageContainer';
2
- export { ReviewQuestionRenderer } from './ReviewQuestionRenderer';
1
+ export{ResultReviewPageContainer}from"./ResultReviewPageContainer";export{ReviewQuestionRenderer}from"./ReviewQuestionRenderer";
@@ -1,6 +1 @@
1
- export function unwrap(value) {
2
- return hasData(value) ? value.data : value;
3
- }
4
- function hasData(value) {
5
- return typeof value === 'object' && value !== null && 'data' in value;
6
- }
1
+ export function unwrap(n){return function(n){return"object"==typeof n&&null!==n&&"data"in n}(n)?n.data:n}
@@ -1,150 +1 @@
1
- export function transformResultAnswersForReview(answers, templateParts = []) {
2
- const groups = new Map();
3
- for (const answer of answers) {
4
- const key = [answer.partId ?? 'unknown', answer.questionGroup?.id ?? 'none', answer.questionType].join(':');
5
- groups.set(key, [...(groups.get(key) ?? []), answer]);
6
- }
7
- const result = {
8
- parts: [],
9
- studentAnswers: {},
10
- isCorrectMap: {},
11
- correctAnswerTextMap: {},
12
- correctOptionIdMap: {},
13
- essayGradingMap: {},
14
- partScoresMap: {},
15
- rawCorrectAnswerMap: {},
16
- rawStudentAnswerMap: {},
17
- questionScoreMap: {},
18
- };
19
- const partScores = [];
20
- for (const groupAnswers of groups.values()) {
21
- groupAnswers.sort((a, b) => a.questionNumber - b.questionNumber);
22
- const first = groupAnswers[0];
23
- if (!first)
24
- continue;
25
- const template = templateParts.find((part) => String(part.id) === String(first.partId));
26
- const partNo = first.partNo ?? parsePartNo(template?.name) ?? 0;
27
- result.parts.push({
28
- partId: first.partId,
29
- partNo,
30
- questionType: first.questionType,
31
- name: template?.name,
32
- instructions: template?.instructions,
33
- audioUrl: template?.audioUrl,
34
- questions: groupAnswers.map(toQuestion),
35
- });
36
- const scored = groupAnswers.filter((answer) => !answer.isExample);
37
- partScores.push({
38
- score: scored.reduce((sum, answer) => sum + (answer.score ?? 0), 0),
39
- maxScore: scored.reduce((sum, answer) => sum + answer.maxScore, 0),
40
- correctCount: scored.filter((answer) => answer.isCorrect === true).length,
41
- totalCount: scored.length,
42
- });
43
- for (const answer of groupAnswers)
44
- addAnswerMaps(result, answer);
45
- }
46
- const order = result.parts.map((_, index) => index).sort((a, b) => {
47
- const byPart = result.parts[a].partNo - result.parts[b].partNo;
48
- return byPart || ((result.parts[a].questions[0]?.question_number ?? 0) - (result.parts[b].questions[0]?.question_number ?? 0));
49
- });
50
- result.parts = order.map((index) => result.parts[index]);
51
- order.forEach((original, sorted) => { result.partScoresMap[sorted] = partScores[original]; });
52
- return result;
53
- }
54
- function toQuestion(answer) {
55
- return {
56
- id: answer.questionId,
57
- content: answer.questionContent,
58
- correct_answer: answer.correctAnswer,
59
- total_points: String(answer.maxScore),
60
- question_type: answer.questionType,
61
- is_example: answer.isExample ?? false,
62
- explanation: answer.explanation ?? '',
63
- difficult_level: 'MEDIUM',
64
- question_skill_type: '',
65
- exam_id: '',
66
- part_id: answer.partId ?? '',
67
- part_no: answer.partNo ?? 0,
68
- question_number: answer.questionNumber,
69
- questionGroup: answer.questionGroup ?? null,
70
- };
71
- }
72
- function addAnswerMaps(result, answer) {
73
- const student = transformStudentAnswer(answer.questionId, answer.questionType, answer.studentAnswer);
74
- if (student !== undefined)
75
- result.studentAnswers[answer.questionId] = student;
76
- result.isCorrectMap[answer.questionId] = answer.isCorrect;
77
- result.rawCorrectAnswerMap[answer.questionId] = answer.correctAnswer;
78
- result.rawStudentAnswerMap[answer.questionId] = answer.studentAnswer;
79
- result.questionScoreMap[answer.questionId] = { score: answer.score, maxScore: answer.maxScore };
80
- const correctText = readAnswerText(answer.correctAnswer);
81
- if (correctText)
82
- result.correctAnswerTextMap[answer.questionId] = correctText;
83
- const correctOption = readOptionId(answer.questionId, answer.correctAnswer);
84
- if (correctOption)
85
- result.correctOptionIdMap[answer.questionId] = correctOption;
86
- if (answer.questionType === 'CHOOSE_CORRECT_ADJECTIVE') {
87
- const selected = readNumberProp(answer.studentAnswer, 'answer');
88
- const correct = readNumberProp(answer.correctAnswer, 'answer');
89
- if (selected !== undefined)
90
- result.studentAnswers[answer.questionId] = `${answer.questionId}-opt-${selected}`;
91
- if (correct !== undefined)
92
- result.correctOptionIdMap[answer.questionId] = `${answer.questionId}-opt-${correct}`;
93
- }
94
- if (answer.questionType === 'WRITE_A_SHORT_LETTER' || answer.questionType === 'WRITE_SHORT_PARAGRAPH') {
95
- result.essayGradingMap[answer.questionId] = {
96
- score: answer.teacherScore ?? answer.score,
97
- maxScore: answer.maxScore,
98
- teacherFeedback: answer.teacherFeedback ?? null,
99
- };
100
- }
101
- }
102
- function transformStudentAnswer(questionId, questionType, value) {
103
- const record = asRecord(value);
104
- if (value == null)
105
- return undefined;
106
- if (questionType === 'CHOOSE_THE_CORRECT_ANSWER')
107
- return readOptionId(questionId, value);
108
- if (['FILL_IN_BLANK', 'WRITE_CORRECT_VERB_FORM', 'ANSWER_THE_QUESTION', 'TRUE_FALSE', 'WRITE_SENTENCES'].includes(questionType)) {
109
- return typeof value === 'string' ? value : record.answer ?? value;
110
- }
111
- if (questionType === 'READ_AND_COLOR_OBJECTS' || questionType === 'IMAGE_OBJECT_MATCHING')
112
- return record.answers ?? value;
113
- if (questionType.startsWith('LISTEN_AND_'))
114
- return value;
115
- return record.answer ?? value;
116
- }
117
- function readOptionId(questionId, value) {
118
- const answer = asRecord(value).answer;
119
- if (typeof answer === 'number')
120
- return `${questionId}-opt-${answer}`;
121
- if (typeof answer === 'string')
122
- return answer;
123
- if (Array.isArray(answer))
124
- return answer.map((item) => typeof item === 'number' ? `${questionId}-opt-${item}` : String(item)).join(',');
125
- return '';
126
- }
127
- function readAnswerText(value) {
128
- const record = asRecord(value);
129
- if (typeof record.answer === 'string')
130
- return record.answer;
131
- if (typeof record.correctAnswer === 'string')
132
- return record.correctAnswer;
133
- const answers = record.answers;
134
- if (Array.isArray(answers))
135
- return String(answers[0] ?? '');
136
- if (answers && typeof answers === 'object')
137
- return String(Object.values(answers)[0] ?? '');
138
- return '';
139
- }
140
- function asRecord(value) {
141
- return typeof value === 'object' && value !== null ? value : {};
142
- }
143
- function readNumberProp(value, key) {
144
- const item = asRecord(value)[key];
145
- return typeof item === 'number' ? item : undefined;
146
- }
147
- function parsePartNo(value) {
148
- const match = value?.match(/\d+/);
149
- return match ? Number(match[0]) : undefined;
150
- }
1
+ export function transformResultAnswersForReview(r,n=[]){const o=new Map;for(const t of r){const e=[t.partId??"unknown",t.questionGroup?.id??"none",t.questionType].join(":");o.set(e,[...o.get(e)??[],t])}const i={parts:[],studentAnswers:{},isCorrectMap:{},correctAnswerTextMap:{},correctOptionIdMap:{},essayGradingMap:{},partScoresMap:{},rawCorrectAnswerMap:{},rawStudentAnswerMap:{},questionScoreMap:{}},u=[];for(const r of o.values()){r.sort((t,e)=>t.questionNumber-e.questionNumber);const o=r[0];if(!o)continue;const a=n.find(t=>String(t.id)===String(o.partId)),c=o.partNo??s(a?.name)??0;i.parts.push({partId:o.partId,partNo:c,questionType:o.questionType,name:a?.name,instructions:a?.instructions,audioUrl:a?.audioUrl,questions:r.map(t)});const p=r.filter(t=>!t.isExample);u.push({score:p.reduce((t,e)=>t+(e.score??0),0),maxScore:p.reduce((t,e)=>t+e.maxScore,0),correctCount:p.filter(t=>!0===t.isCorrect).length,totalCount:p.length});for(const t of r)e(i,t)}const a=i.parts.map((t,e)=>e).sort((t,e)=>i.parts[t].partNo-i.parts[e].partNo||(i.parts[t].questions[0]?.question_number??0)-(i.parts[e].questions[0]?.question_number??0));return i.parts=a.map(t=>i.parts[t]),a.forEach((t,e)=>{i.partScoresMap[e]=u[t]}),i}function t(t){return{id:t.questionId,content:t.questionContent,correct_answer:t.correctAnswer,total_points:String(t.maxScore),question_type:t.questionType,is_example:t.isExample??!1,explanation:t.explanation??"",difficult_level:"MEDIUM",question_skill_type:"",exam_id:"",part_id:t.partId??"",part_no:t.partNo??0,question_number:t.questionNumber,questionGroup:t.questionGroup??null}}function e(t,e){const s=function(t,e,o){const s=n(o);if(null==o)return;if("CHOOSE_THE_CORRECT_ANSWER"===e)return r(t,o);if(["FILL_IN_BLANK","WRITE_CORRECT_VERB_FORM","ANSWER_THE_QUESTION","TRUE_FALSE","WRITE_SENTENCES"].includes(e))return"string"==typeof o?o:s.answer??o;return"READ_AND_COLOR_OBJECTS"===e||"IMAGE_OBJECT_MATCHING"===e?s.answers??o:e.startsWith("LISTEN_AND_")?o:s.answer??o}(e.questionId,e.questionType,e.studentAnswer);void 0!==s&&(t.studentAnswers[e.questionId]=s),t.isCorrectMap[e.questionId]=e.isCorrect,t.rawCorrectAnswerMap[e.questionId]=e.correctAnswer,t.rawStudentAnswerMap[e.questionId]=e.studentAnswer,t.questionScoreMap[e.questionId]={score:e.score,maxScore:e.maxScore};const i=function(t){const e=n(t);if("string"==typeof e.answer)return e.answer;if("string"==typeof e.correctAnswer)return e.correctAnswer;const r=e.answers;return Array.isArray(r)?String(r[0]??""):r&&"object"==typeof r?String(Object.values(r)[0]??""):""}(e.correctAnswer);i&&(t.correctAnswerTextMap[e.questionId]=i);const u=r(e.questionId,e.correctAnswer);if(u&&(t.correctOptionIdMap[e.questionId]=u),"CHOOSE_CORRECT_ADJECTIVE"===e.questionType){const r=o(e.studentAnswer,"answer"),n=o(e.correctAnswer,"answer");void 0!==r&&(t.studentAnswers[e.questionId]=`${e.questionId}-opt-${r}`),void 0!==n&&(t.correctOptionIdMap[e.questionId]=`${e.questionId}-opt-${n}`)}"WRITE_A_SHORT_LETTER"!==e.questionType&&"WRITE_SHORT_PARAGRAPH"!==e.questionType||(t.essayGradingMap[e.questionId]={score:e.teacherScore??e.score,maxScore:e.maxScore,teacherFeedback:e.teacherFeedback??null})}function r(t,e){const r=n(e).answer;return"number"==typeof r?`${t}-opt-${r}`:"string"==typeof r?r:Array.isArray(r)?r.map(e=>"number"==typeof e?`${t}-opt-${e}`:String(e)).join(","):""}function n(t){return"object"==typeof t&&null!==t?t:{}}function o(t,e){const r=n(t)[e];return"number"==typeof r?r:void 0}function s(t){const e=t?.match(/\d+/);return e?Number(e[0]):void 0}
@@ -1,97 +1 @@
1
- export function asRecord(value) {
2
- return typeof value === 'object' && value !== null ? value : {};
3
- }
4
- export function readString(value, ...keys) {
5
- const record = asRecord(value);
6
- for (const key of keys) {
7
- const item = record[key];
8
- if (typeof item === 'string')
9
- return item;
10
- }
11
- return '';
12
- }
13
- export function readArray(value, key) {
14
- const item = asRecord(value)[key];
15
- return Array.isArray(item) ? item : [];
16
- }
17
- export function readQuestionText(question) {
18
- return readString(question.content, 'questionText', 'question', 'text', 'sentence') || String(question.content ?? '');
19
- }
20
- export function transformChoiceQuestions(questions) {
21
- return questions.map((question) => {
22
- const content = asRecord(question.content);
23
- const optionType = readString(content, 'optionType');
24
- return {
25
- id: question.id,
26
- questionNumber: question.question_number,
27
- questionText: readQuestionText(question),
28
- imageUrl: readString(content, 'imageUrl'),
29
- optionType,
30
- multipleAnswers: Boolean(content.multipleAnswers),
31
- options: readOptions(content, question.id, optionType),
32
- };
33
- });
34
- }
35
- export function transformFillQuestions(questions) {
36
- return questions.map((question) => {
37
- const content = asRecord(question.content);
38
- return {
39
- id: question.id,
40
- questionNumber: question.question_number,
41
- textBefore: readString(content, 'textBefore', 'sentenceBefore', 'prefixText'),
42
- textAfter: readString(content, 'textAfter', 'sentenceAfter', 'suffixText'),
43
- imageUrl: readString(content, 'imageUrl'),
44
- };
45
- });
46
- }
47
- export function transformAdjectiveQuestions(questions) {
48
- return questions.map((question) => {
49
- const content = asRecord(question.content);
50
- return {
51
- id: question.id,
52
- questionNumber: question.question_number,
53
- textBefore: '',
54
- textAfter: '',
55
- sentenceBefore: readString(content, 'sentenceBefore', 'textBefore'),
56
- sentenceAfter: readString(content, 'sentenceAfter', 'textAfter'),
57
- imageUrl: readString(content, 'imageUrl'),
58
- options: readOptions(content, question.id, 'text'),
59
- };
60
- });
61
- }
62
- export function transformColoring(questions) {
63
- const first = asRecord(questions[0]?.content);
64
- return {
65
- instruction: readString(first, 'instruction'),
66
- instructions: readArray(first, 'instructions').filter((item) => typeof item === 'string'),
67
- imageUrl: readString(first, 'imageUrl', 'backgroundImageUrl'),
68
- audioUrl: readString(first, 'audioUrl'),
69
- hintItems: readArray(first, 'hintItems').map((item) => {
70
- const row = asRecord(item);
71
- return { id: String(row.id ?? ''), label: readString(row, 'label'), color: readString(row, 'color') };
72
- }).filter((item) => item.id),
73
- regions: readArray(first, 'regions').map((item) => {
74
- const row = asRecord(item);
75
- return { id: String(row.id ?? ''), x: Number(row.x ?? 0), y: Number(row.y ?? 0), width: Number(row.width ?? 0), height: Number(row.height ?? 0) };
76
- }).filter((item) => item.id),
77
- };
78
- }
79
- function readOptions(content, questionId, optionType) {
80
- return readArray(content, 'options').map((item, index) => {
81
- const option = asRecord(item);
82
- const id = String(option.id ?? `${questionId}-opt-${index}`);
83
- const text = readString(option, 'text', 'label', 'value', 'imageUrl');
84
- return { id, label: readString(option, 'label') || String.fromCharCode(65 + index), text, imageUrl: optionType === 'image' ? text : readString(option, 'imageUrl') };
85
- });
86
- }
87
- export function normalizeSelected(value) {
88
- if (Array.isArray(value))
89
- return value.map(String);
90
- if (typeof value === 'string' && value)
91
- return value.split(',').map((item) => item.trim()).filter(Boolean);
92
- return [];
93
- }
94
- export function normalizeRecord(value) {
95
- const record = asRecord(value);
96
- return Object.fromEntries(Object.entries(record).map(([key, item]) => [key, String(item ?? '')]));
97
- }
1
+ export function asRecord(e){return"object"==typeof e&&null!==e?e:{}}export function readString(e,...r){const t=asRecord(e);for(const e of r){const r=t[e];if("string"==typeof r)return r}return""}export function readArray(e,r){const t=asRecord(e)[r];return Array.isArray(t)?t:[]}export function readQuestionText(e){return readString(e.content,"questionText","question","text","sentence")||String(e.content??"")}export function transformChoiceQuestions(r){return r.map(r=>{const t=asRecord(r.content),n=readString(t,"optionType");return{id:r.id,questionNumber:r.question_number,questionText:readQuestionText(r),imageUrl:readString(t,"imageUrl"),optionType:n,multipleAnswers:Boolean(t.multipleAnswers),options:e(t,r.id,n)}})}export function transformFillQuestions(e){return e.map(e=>{const r=asRecord(e.content);return{id:e.id,questionNumber:e.question_number,textBefore:readString(r,"textBefore","sentenceBefore","prefixText"),textAfter:readString(r,"textAfter","sentenceAfter","suffixText"),imageUrl:readString(r,"imageUrl")}})}export function transformAdjectiveQuestions(r){return r.map(r=>{const t=asRecord(r.content);return{id:r.id,questionNumber:r.question_number,textBefore:"",textAfter:"",sentenceBefore:readString(t,"sentenceBefore","textBefore"),sentenceAfter:readString(t,"sentenceAfter","textAfter"),imageUrl:readString(t,"imageUrl"),options:e(t,r.id,"text")}})}export function transformColoring(e){const r=asRecord(e[0]?.content);return{instruction:readString(r,"instruction"),instructions:readArray(r,"instructions").filter(e=>"string"==typeof e),imageUrl:readString(r,"imageUrl","backgroundImageUrl"),audioUrl:readString(r,"audioUrl"),hintItems:readArray(r,"hintItems").map(e=>{const r=asRecord(e);return{id:String(r.id??""),label:readString(r,"label"),color:readString(r,"color")}}).filter(e=>e.id),regions:readArray(r,"regions").map(e=>{const r=asRecord(e);return{id:String(r.id??""),x:Number(r.x??0),y:Number(r.y??0),width:Number(r.width??0),height:Number(r.height??0)}}).filter(e=>e.id)}}function e(e,r,t){return readArray(e,"options").map((e,n)=>{const i=asRecord(e),o=String(i.id??`${r}-opt-${n}`),a=readString(i,"text","label","value","imageUrl");return{id:o,label:readString(i,"label")||String.fromCharCode(65+n),text:a,imageUrl:"image"===t?a:readString(i,"imageUrl")}})}export function normalizeSelected(e){return Array.isArray(e)?e.map(String):"string"==typeof e&&e?e.split(",").map(e=>e.trim()).filter(Boolean):[]}export function normalizeRecord(e){const r=asRecord(e);return Object.fromEntries(Object.entries(r).map(([e,r])=>[e,String(r??"")]))}
@@ -1 +1 @@
1
- export {};
1
+ export{};
@@ -1,138 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useRef, useState, useEffect, useMemo } from 'react';
4
- import { Pause, Play, RotateCcw } from 'lucide-react';
5
- import { usePresignedFileUrl } from '../../../shared/lib/hooks/usePresignedFileUrl';
6
- const PLAYBACK_SPEEDS = [0.75, 1, 1.25, 1.5];
7
- const WAVEFORM_BARS = 48;
8
- /** Deterministic pseudo-random bar heights — stable per render, no flicker on re-render. */
9
- function useWaveformHeights(count) {
10
- return useMemo(() => {
11
- const heights = [];
12
- let seed = 7;
13
- for (let i = 0; i < count; i++) {
14
- // LCG-like deterministic noise
15
- seed = (seed * 9301 + 49297) % 233280;
16
- const rnd = seed / 233280;
17
- // Bias towards mid-range heights for a natural-looking waveform
18
- const h = 0.25 + rnd * 0.75;
19
- // Soft envelope — taller in the middle, shorter at edges
20
- const envelope = Math.sin((i / (count - 1)) * Math.PI) * 0.25 + 0.75;
21
- heights.push(Math.max(0.2, h * envelope));
22
- }
23
- return heights;
24
- }, [count]);
25
- }
26
- export default function CambridgeYleAudioPlayer({ audioSrc, onPlay, onPause, compact = false, }) {
27
- const { previewUrl: resolvedAudioSrc } = usePresignedFileUrl(audioSrc);
28
- const audioRef = useRef(null);
29
- const [isPlaying, setIsPlaying] = useState(false);
30
- const [currentTime, setCurrentTime] = useState(0);
31
- const [duration, setDuration] = useState(0);
32
- const [speed, setSpeed] = useState(1);
33
- const barHeights = useWaveformHeights(WAVEFORM_BARS);
34
- useEffect(() => {
35
- const audio = audioRef.current;
36
- if (!audio)
37
- return;
38
- const updateDuration = () => {
39
- if (audio.duration && !isNaN(audio.duration) && isFinite(audio.duration)) {
40
- setDuration(audio.duration);
41
- }
42
- };
43
- const handleTimeUpdate = () => {
44
- setCurrentTime(audio.currentTime);
45
- updateDuration();
46
- };
47
- const handleEnded = () => {
48
- setIsPlaying(false);
49
- setCurrentTime(0);
50
- };
51
- updateDuration();
52
- audio.addEventListener('loadedmetadata', updateDuration);
53
- audio.addEventListener('durationchange', updateDuration);
54
- audio.addEventListener('canplay', updateDuration);
55
- audio.addEventListener('timeupdate', handleTimeUpdate);
56
- audio.addEventListener('ended', handleEnded);
57
- return () => {
58
- audio.removeEventListener('loadedmetadata', updateDuration);
59
- audio.removeEventListener('durationchange', updateDuration);
60
- audio.removeEventListener('canplay', updateDuration);
61
- audio.removeEventListener('timeupdate', handleTimeUpdate);
62
- audio.removeEventListener('ended', handleEnded);
63
- };
64
- }, []);
65
- useEffect(() => {
66
- if (audioRef.current)
67
- audioRef.current.playbackRate = speed;
68
- }, [speed]);
69
- const handlePlayPause = () => {
70
- const audio = audioRef.current;
71
- if (!audio)
72
- return;
73
- if (isPlaying) {
74
- audio.pause();
75
- setIsPlaying(false);
76
- onPause?.();
77
- }
78
- else {
79
- audio.play().catch(() => {
80
- // Source unplayable — reset UI instead of leaving a floating rejection.
81
- setIsPlaying(false);
82
- });
83
- setIsPlaying(true);
84
- onPlay?.();
85
- }
86
- };
87
- const handleRewind = () => {
88
- const audio = audioRef.current;
89
- if (!audio)
90
- return;
91
- audio.currentTime = Math.max(0, audio.currentTime - 5);
92
- setCurrentTime(audio.currentTime);
93
- };
94
- const handleWaveformClick = (e) => {
95
- const audio = audioRef.current;
96
- if (!audio || !duration)
97
- return;
98
- const rect = e.currentTarget.getBoundingClientRect();
99
- const ratio = Math.min(1, Math.max(0, (e.clientX - rect.left) / rect.width));
100
- const newTime = ratio * duration;
101
- audio.currentTime = newTime;
102
- setCurrentTime(newTime);
103
- };
104
- const formatTime = (time) => {
105
- const safe = Math.max(0, time);
106
- const minutes = Math.floor(safe / 60);
107
- const seconds = Math.floor(safe % 60);
108
- return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
109
- };
110
- const progress = duration > 0 ? currentTime / duration : 0;
111
- const progressPct = progress * 100;
112
- const remaining = Math.max(0, duration - currentTime);
113
- // --------------------- COMPACT MODE ---------------------
114
- if (compact) {
115
- return (_jsxs("div", { className: "relative overflow-hidden rounded-2xl border-2 border-blue-100 bg-white p-3 shadow-sm transition-all hover:border-blue-200 hover:shadow-md", children: [_jsx("audio", { ref: audioRef, src: resolvedAudioSrc || undefined, preload: "metadata" }), _jsxs("div", { className: "flex items-center gap-3 sm:gap-4", children: [_jsxs("button", { type: "button", onClick: handlePlayPause, className: "relative flex h-12 w-12 shrink-0 items-center justify-center rounded-full bg-coral-500 text-white shadow-lg shadow-coral-500/30 transition-all duration-200 hover:scale-105 hover:bg-coral-600 active:scale-95", "aria-label": isPlaying ? 'Pause' : 'Play', children: [isPlaying ? _jsx(Pause, { className: "h-5 w-5 fill-current" }) : _jsx(Play, { className: "ml-1 h-5 w-5 fill-current" }), isPlaying && (_jsx("span", { className: "pointer-events-none absolute inset-0 animate-ping rounded-full bg-coral-400 opacity-40" }))] }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-2", children: [_jsxs("div", { className: "flex items-center justify-between px-1", children: [_jsx("span", { className: "text-sm font-black uppercase tracking-widest text-blue-900 font-[family-name:var(--font-nunito)]", children: "Audio" }), _jsxs("div", { className: "flex items-center gap-1.5 font-mono text-xs font-bold text-slate-500 tabular-nums", children: [_jsx("span", { className: "text-blue-600", children: formatTime(currentTime) }), _jsx("span", { children: "/" }), _jsx("span", { children: formatTime(duration) })] })] }), _jsxs("div", { className: "relative flex items-center", children: [_jsx("div", { className: "h-3 w-full overflow-hidden rounded-full bg-blue-50 shadow-inner", children: _jsx("div", { className: "h-full rounded-full bg-blue-500 transition-[width] duration-150", style: { width: `${progressPct}%` } }) }), _jsx("input", { type: "range", min: "0", max: duration || 0, step: "0.1", value: currentTime, onChange: (e) => {
116
- const newTime = parseFloat(e.target.value);
117
- if (audioRef.current)
118
- audioRef.current.currentTime = newTime;
119
- setCurrentTime(newTime);
120
- }, className: "absolute inset-0 w-full cursor-pointer opacity-0", "aria-label": "Seek" })] })] })] })] }));
121
- }
122
- // --------------------- FULL MODE ---------------------
123
- // Circular progress ring math
124
- const RING_SIZE = 56;
125
- const RING_STROKE = 3;
126
- const RING_RADIUS = (RING_SIZE - RING_STROKE) / 2;
127
- const RING_CIRC = 2 * Math.PI * RING_RADIUS;
128
- return (_jsxs("div", { className: "relative overflow-hidden rounded-2xl border-2 border-blue-100 bg-white p-3.5 shadow-sm transition-shadow hover:border-blue-200 hover:shadow-md", children: [_jsx("audio", { ref: audioRef, src: resolvedAudioSrc || undefined, preload: "metadata" }), _jsx("div", { className: "pointer-events-none absolute -right-6 -top-6 h-20 w-20 rounded-full bg-blue-100 opacity-60 blur-xl" }), _jsxs("div", { className: "relative flex items-center gap-3", children: [_jsxs("button", { type: "button", onClick: handleRewind, className: "relative flex h-9 w-9 shrink-0 items-center justify-center rounded-full text-slate-500 transition-all hover:bg-slate-100 hover:text-blue-600 active:scale-95", "aria-label": "Rewind 5 seconds", title: "Quay l\u1EA1i 5 gi\u00E2y", children: [_jsx(RotateCcw, { className: "h-4 w-4", strokeWidth: 2.5 }), _jsx("span", { className: "pointer-events-none absolute bottom-1 right-1 text-[8px] font-black leading-none", children: "5" })] }), _jsxs("div", { className: "relative shrink-0", style: { width: RING_SIZE, height: RING_SIZE }, children: [_jsxs("svg", { width: RING_SIZE, height: RING_SIZE, className: "absolute inset-0 -rotate-90", "aria-hidden": true, children: [_jsx("circle", { cx: RING_SIZE / 2, cy: RING_SIZE / 2, r: RING_RADIUS, fill: "none", stroke: "rgb(239 246 255)", strokeWidth: RING_STROKE }), _jsx("circle", { cx: RING_SIZE / 2, cy: RING_SIZE / 2, r: RING_RADIUS, fill: "none", stroke: "url(#audio-ring-gradient)", strokeWidth: RING_STROKE, strokeLinecap: "round", strokeDasharray: RING_CIRC, strokeDashoffset: RING_CIRC * (1 - progress), className: "transition-[stroke-dashoffset] duration-150" }), _jsx("defs", { children: _jsxs("linearGradient", { id: "audio-ring-gradient", x1: "0", y1: "0", x2: "1", y2: "1", children: [_jsx("stop", { offset: "0%", stopColor: "#3B82F6" }), _jsx("stop", { offset: "100%", stopColor: "#06b6d4" })] }) })] }), _jsxs("button", { type: "button", onClick: handlePlayPause, className: "absolute inset-1.5 flex items-center justify-center rounded-full bg-coral-500 text-white shadow-lg shadow-coral-500/30 transition-all hover:shadow-xl hover:shadow-coral-500/50 hover:bg-coral-600 active:scale-95", "aria-label": isPlaying ? 'Pause' : 'Play', children: [isPlaying ? (_jsx(Pause, { className: "h-4 w-4 fill-current" })) : (_jsx(Play, { className: "ml-0.5 h-4 w-4 fill-current" })), isPlaying && (_jsx("span", { className: "pointer-events-none absolute inset-0 animate-ping rounded-full bg-coral-400 opacity-40" }))] })] }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [_jsx("div", { onClick: handleWaveformClick, className: "group/wave relative flex h-8 cursor-pointer items-center gap-[2px]", role: "slider", "aria-label": "Seek", "aria-valuemin": 0, "aria-valuemax": duration || 0, "aria-valuenow": currentTime, children: barHeights.map((h, i) => {
129
- const barPos = (i + 0.5) / WAVEFORM_BARS;
130
- const isFilled = barPos <= progress;
131
- const isCurrent = Math.abs(barPos - progress) < 1 / WAVEFORM_BARS / 2;
132
- return (_jsx("span", { className: `flex-1 rounded-full transition-all duration-200 ${isFilled
133
- ? 'bg-blue-500'
134
- : 'bg-slate-200 group-hover/wave:bg-slate-300'} ${isCurrent && isPlaying ? 'animate-wave-pulse' : ''}`, style: { height: `${h * 100}%` } }, i));
135
- }) }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-baseline gap-1.5", children: [_jsxs("span", { className: "font-mono text-sm font-bold tabular-nums text-slate-800", children: ["\u2212", formatTime(remaining)] }), _jsxs("span", { className: "font-mono text-[10px] tabular-nums text-slate-400", children: ["/ ", formatTime(duration)] })] }), _jsx("div", { className: "flex items-center gap-0.5 rounded-full border border-slate-200 bg-white p-0.5 shadow-sm", children: PLAYBACK_SPEEDS.map((s) => (_jsxs("button", { type: "button", onClick: () => setSpeed(s), className: `rounded-full px-2 py-0.5 font-mono text-[10px] font-semibold tabular-nums transition-all ${speed === s
136
- ? 'bg-coral-500 text-white shadow-sm'
137
- : 'text-slate-500 hover:bg-slate-100 hover:text-slate-700'}`, children: [s, "\u00D7"] }, s))) })] })] })] })] }));
138
- }
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useRef as a,useState as r,useEffect as n,useMemo as l}from"react";import{Pause as s,Play as i,RotateCcw as o}from"lucide-react";import{usePresignedFileUrl as c}from"../../../shared/lib/hooks/usePresignedFileUrl";const d=[.75,1,1.25,1.5];export default function u({audioSrc:u,onPlay:h,onPause:m,compact:f=!1}){const{previewUrl:p}=c(u),v=a(null),[b,x]=r(!1),[g,w]=r(0),[N,y]=r(0),[k,M]=r(1),L=l(()=>{const e=[];let t=7;for(let a=0;a<E;a++){t=(9301*t+49297)%233280;const r=.25+t/233280*.75,n=.25*Math.sin(a/(E-1)*Math.PI)+.75;e.push(Math.max(.2,r*n))}return e},[E=48]);var E;n(()=>{const e=v.current;if(!e)return;const t=()=>{e.duration&&!isNaN(e.duration)&&isFinite(e.duration)&&y(e.duration)},a=()=>{w(e.currentTime),t()},r=()=>{x(!1),w(0)};return t(),e.addEventListener("loadedmetadata",t),e.addEventListener("durationchange",t),e.addEventListener("canplay",t),e.addEventListener("timeupdate",a),e.addEventListener("ended",r),()=>{e.removeEventListener("loadedmetadata",t),e.removeEventListener("durationchange",t),e.removeEventListener("canplay",t),e.removeEventListener("timeupdate",a),e.removeEventListener("ended",r)}},[]),n(()=>{v.current&&(v.current.playbackRate=k)},[k]);const j=()=>{const e=v.current;e&&(b?(e.pause(),x(!1),m?.()):(e.play().catch(()=>{x(!1)}),x(!0),h?.()))},C=e=>{const t=Math.max(0,e),a=Math.floor(t/60),r=Math.floor(t%60);return`${String(a).padStart(2,"0")}:${String(r).padStart(2,"0")}`},P=N>0?g/N:0,S=100*P,T=Math.max(0,N-g);if(f)return t("div",{className:"relative overflow-hidden rounded-2xl border-2 border-blue-100 bg-white p-3 shadow-sm transition-all hover:border-blue-200 hover:shadow-md",children:[e("audio",{ref:v,src:p||void 0,preload:"metadata"}),t("div",{className:"flex items-center gap-3 sm:gap-4",children:[t("button",{type:"button",onClick:j,className:"relative flex h-12 w-12 shrink-0 items-center justify-center rounded-full bg-coral-500 text-white shadow-lg shadow-coral-500/30 transition-all duration-200 hover:scale-105 hover:bg-coral-600 active:scale-95","aria-label":b?"Pause":"Play",children:[b?e(s,{className:"h-5 w-5 fill-current"}):e(i,{className:"ml-1 h-5 w-5 fill-current"}),b&&e("span",{className:"pointer-events-none absolute inset-0 animate-ping rounded-full bg-coral-400 opacity-40"})]}),t("div",{className:"flex min-w-0 flex-1 flex-col justify-center gap-2",children:[t("div",{className:"flex items-center justify-between px-1",children:[e("span",{className:"text-sm font-black uppercase tracking-widest text-blue-900 font-[family-name:var(--font-nunito)]",children:"Audio"}),t("div",{className:"flex items-center gap-1.5 font-mono text-xs font-bold text-slate-500 tabular-nums",children:[e("span",{className:"text-blue-600",children:C(g)}),e("span",{children:"/"}),e("span",{children:C(N)})]})]}),t("div",{className:"relative flex items-center",children:[e("div",{className:"h-3 w-full overflow-hidden rounded-full bg-blue-50 shadow-inner",children:e("div",{className:"h-full rounded-full bg-blue-500 transition-[width] duration-150",style:{width:`${S}%`}})}),e("input",{type:"range",min:"0",max:N||0,step:"0.1",value:g,onChange:e=>{const t=parseFloat(e.target.value);v.current&&(v.current.currentTime=t),w(t)},className:"absolute inset-0 w-full cursor-pointer opacity-0","aria-label":"Seek"})]})]})]})]});const $=56,F=26.5,R=2*Math.PI*F;return t("div",{className:"relative overflow-hidden rounded-2xl border-2 border-blue-100 bg-white p-3.5 shadow-sm transition-shadow hover:border-blue-200 hover:shadow-md",children:[e("audio",{ref:v,src:p||void 0,preload:"metadata"}),e("div",{className:"pointer-events-none absolute -right-6 -top-6 h-20 w-20 rounded-full bg-blue-100 opacity-60 blur-xl"}),t("div",{className:"relative flex items-center gap-3",children:[t("button",{type:"button",onClick:()=>{const e=v.current;e&&(e.currentTime=Math.max(0,e.currentTime-5),w(e.currentTime))},className:"relative flex h-9 w-9 shrink-0 items-center justify-center rounded-full text-slate-500 transition-all hover:bg-slate-100 hover:text-blue-600 active:scale-95","aria-label":"Rewind 5 seconds",title:"Quay lại 5 giây",children:[e(o,{className:"h-4 w-4",strokeWidth:2.5}),e("span",{className:"pointer-events-none absolute bottom-1 right-1 text-[8px] font-black leading-none",children:"5"})]}),t("div",{className:"relative shrink-0",style:{width:$,height:$},children:[t("svg",{width:$,height:$,className:"absolute inset-0 -rotate-90","aria-hidden":!0,children:[e("circle",{cx:28,cy:28,r:F,fill:"none",stroke:"rgb(239 246 255)",strokeWidth:3}),e("circle",{cx:28,cy:28,r:F,fill:"none",stroke:"url(#audio-ring-gradient)",strokeWidth:3,strokeLinecap:"round",strokeDasharray:R,strokeDashoffset:R*(1-P),className:"transition-[stroke-dashoffset] duration-150"}),e("defs",{children:t("linearGradient",{id:"audio-ring-gradient",x1:"0",y1:"0",x2:"1",y2:"1",children:[e("stop",{offset:"0%",stopColor:"#3B82F6"}),e("stop",{offset:"100%",stopColor:"#06b6d4"})]})})]}),t("button",{type:"button",onClick:j,className:"absolute inset-1.5 flex items-center justify-center rounded-full bg-coral-500 text-white shadow-lg shadow-coral-500/30 transition-all hover:shadow-xl hover:shadow-coral-500/50 hover:bg-coral-600 active:scale-95","aria-label":b?"Pause":"Play",children:[b?e(s,{className:"h-4 w-4 fill-current"}):e(i,{className:"ml-0.5 h-4 w-4 fill-current"}),b&&e("span",{className:"pointer-events-none absolute inset-0 animate-ping rounded-full bg-coral-400 opacity-40"})]})]}),t("div",{className:"flex min-w-0 flex-1 flex-col gap-1",children:[e("div",{onClick:e=>{const t=v.current;if(!t||!N)return;const a=e.currentTarget.getBoundingClientRect(),r=Math.min(1,Math.max(0,(e.clientX-a.left)/a.width))*N;t.currentTime=r,w(r)},className:"group/wave relative flex h-8 cursor-pointer items-center gap-[2px]",role:"slider","aria-label":"Seek","aria-valuemin":0,"aria-valuemax":N||0,"aria-valuenow":g,children:L.map((t,a)=>{const r=(a+.5)/48,n=r<=P,l=Math.abs(r-P)<1/48/2;return e("span",{className:`flex-1 rounded-full transition-all duration-200 ${n?"bg-blue-500":"bg-slate-200 group-hover/wave:bg-slate-300"} ${l&&b?"animate-wave-pulse":""}`,style:{height:100*t+"%"}},a)})}),t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-baseline gap-1.5",children:[t("span",{className:"font-mono text-sm font-bold tabular-nums text-slate-800",children:["−",C(T)]}),t("span",{className:"font-mono text-[10px] tabular-nums text-slate-400",children:["/ ",C(N)]})]}),e("div",{className:"flex items-center gap-0.5 rounded-full border border-slate-200 bg-white p-0.5 shadow-sm",children:d.map(e=>t("button",{type:"button",onClick:()=>M(e),className:"rounded-full px-2 py-0.5 font-mono text-[10px] font-semibold tabular-nums transition-all "+(k===e?"bg-coral-500 text-white shadow-sm":"text-slate-500 hover:bg-slate-100 hover:text-slate-700"),children:[e,"×"]},e))})]})]})]})]})}
@@ -1,12 +1 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { ChevronLeft, ChevronRight } from 'lucide-react';
3
- import { cn } from '../../../shared/lib/utils';
4
- import Image from 'next/image';
5
- import rangsanhoImg from './assets/images/rangsanho.png';
6
- export function CambridgeYleBottomNav({ onPrevious, onNext, isFirstQuestion, isLastQuestion, isNavDisabled, className, }) {
7
- return (_jsxs("div", { className: cn("flex items-center justify-between py-2 relative", className), children: [_jsxs("button", { onClick: onPrevious, disabled: isFirstQuestion || isNavDisabled, className: cn("flex items-center gap-2 px-8 py-3 rounded-full font-bold text-[15px] transition-all duration-300 z-10", (isFirstQuestion || isNavDisabled)
8
- ? "opacity-50 cursor-not-allowed bg-white border-2 border-gray-200 text-gray-400"
9
- : "bg-white border-2 border-blue-200 text-blue-600 hover:bg-blue-50 hover:shadow-md cursor-pointer active:scale-95 shadow-sm"), children: [_jsx(ChevronLeft, { size: 20, strokeWidth: 3 }), "Previous"] }), _jsx("div", { className: "absolute left-1/2 bottom-[-4px] -translate-x-1/2 flex items-end pointer-events-none z-0", children: _jsx(Image, { src: rangsanhoImg, alt: "R\u1EB7ng san h\u00F4", className: "h-[72px] w-auto object-contain" }) }), _jsxs("button", { onClick: onNext, disabled: isLastQuestion || isNavDisabled, className: cn("flex items-center gap-2 px-10 py-3 rounded-full font-bold text-[15px] transition-all duration-300 shadow-md z-10 border-2 border-transparent", (isLastQuestion || isNavDisabled)
10
- ? "opacity-50 cursor-not-allowed bg-gray-300 text-gray-500 shadow-none"
11
- : "bg-blue-600 text-white hover:bg-blue-700 hover:shadow-lg cursor-pointer active:scale-95"), children: ["Next", _jsx(ChevronRight, { size: 20, strokeWidth: 3 })] })] }));
12
- }
1
+ import{jsx as e,jsxs as t}from"react/jsx-runtime";import{ChevronLeft as o,ChevronRight as r}from"lucide-react";import{cn as s}from"../../../shared/lib/utils";import a from"next/image";import i from"./assets/images/rangsanho.png";export function CambridgeYleBottomNav({onPrevious:n,onNext:l,isFirstQuestion:d,isLastQuestion:c,isNavDisabled:b,className:m}){return t("div",{className:s("flex items-center justify-between py-2 relative",m),children:[t("button",{onClick:n,disabled:d||b,className:s("flex items-center gap-2 px-8 py-3 rounded-full font-bold text-[15px] transition-all duration-300 z-10",d||b?"opacity-50 cursor-not-allowed bg-white border-2 border-gray-200 text-gray-400":"bg-white border-2 border-blue-200 text-blue-600 hover:bg-blue-50 hover:shadow-md cursor-pointer active:scale-95 shadow-sm"),children:[e(o,{size:20,strokeWidth:3}),"Previous"]}),e("div",{className:"absolute left-1/2 bottom-[-4px] -translate-x-1/2 flex items-end pointer-events-none z-0",children:e(a,{src:i,alt:"Rặng san hô",className:"h-[72px] w-auto object-contain"})}),t("button",{onClick:l,disabled:c||b,className:s("flex items-center gap-2 px-10 py-3 rounded-full font-bold text-[15px] transition-all duration-300 shadow-md z-10 border-2 border-transparent",c||b?"opacity-50 cursor-not-allowed bg-gray-300 text-gray-500 shadow-none":"bg-blue-600 text-white hover:bg-blue-700 hover:shadow-lg cursor-pointer active:scale-95"),children:["Next",e(r,{size:20,strokeWidth:3})]})]})}
@@ -1,18 +1 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { CambridgeYleHeader } from './CambridgeYleHeader';
3
- import { CambridgeYleSidebar } from './CambridgeYleSidebar';
4
- import { CambridgeYleBottomNav } from './CambridgeYleBottomNav';
5
- import { CambridgeYleNavButton } from './CambridgeYleNavButton';
6
- import { cn } from '../../../shared/lib/utils';
7
- import backgroundSummerSky from '../summer-sky/assets/images/background-v2.png';
8
- import oceanFrameImg from '../summer-sky/assets/images/ocean-frame.png';
9
- export function CambridgeYleExamLayout({ children, onPrevious, onNext, onClose, isFirstQuestion, isLastQuestion, isNavDisabled, headerProps, sidebarProps, partName, }) {
10
- // No longer using dynamic decor images for title badge
11
- return (_jsxs("div", { className: "relative flex h-screen flex-col overflow-hidden bg-cover bg-center bg-no-repeat", style: {
12
- backgroundImage: `url(${backgroundSummerSky.src})`,
13
- }, children: [_jsx("div", { className: "absolute inset-0 bg-black/10" }), _jsx("div", { className: "relative z-50", children: _jsx(CambridgeYleHeader, { ...headerProps, onClose: onClose }) }), _jsxs("div", { className: "relative z-10 flex flex-1 min-h-0 items-stretch justify-center p-2 pt-0 pb-2 sm:px-4 sm:pt-0 sm:pb-4 lg:px-[2%] lg:pt-0 lg:pb-[20px] max-w-[1500px] mx-auto w-full gap-2 xl:gap-4", children: [_jsx("div", { className: "hidden xl:flex shrink-0 items-center", children: _jsx(CambridgeYleNavButton, { direction: "prev", onClick: onPrevious, disabled: isFirstQuestion || isNavDisabled }) }), _jsxs("div", { className: "flex flex-1 w-full relative drop-shadow-xl min-h-0", children: [_jsx("div", { className: "absolute inset-0 pointer-events-none", style: {
14
- background: `url(${oceanFrameImg.src}) no-repeat center center`,
15
- backgroundSize: '100% 100%',
16
- zIndex: 0
17
- } }), _jsxs("div", { className: "flex flex-1 w-full relative z-10 p-[12px] sm:p-[16px] md:p-[24px]", children: [sidebarProps && (_jsx("div", { className: "hidden lg:flex w-[30%] shrink-0 bg-[#eef7ff] rounded-l-[20px] md:rounded-l-[32px] border-r-2 border-blue-100 flex-col overflow-hidden", children: _jsx(CambridgeYleSidebar, { ...sidebarProps }) })), _jsxs("div", { className: cn("flex w-full min-h-0 flex-col flex-1 relative bg-white overflow-hidden", sidebarProps ? "rounded-r-[20px] md:rounded-r-[32px]" : "rounded-[20px] md:rounded-[32px]"), children: [_jsx("div", { id: "exam-main-scroll-area", className: "flex-1 min-h-0 overflow-auto", children: children }), _jsx("div", { className: "hidden shrink-0 bg-white p-1 sm:px-6 border-t border-blue-200/50", children: _jsx(CambridgeYleBottomNav, { onPrevious: onPrevious, onNext: onNext, isFirstQuestion: isFirstQuestion, isLastQuestion: isLastQuestion, isNavDisabled: isNavDisabled }) })] })] })] }), _jsx("div", { className: "hidden xl:flex shrink-0 items-center", children: _jsx(CambridgeYleNavButton, { direction: "next", onClick: onNext, disabled: isLastQuestion || isNavDisabled }) })] })] }));
18
- }
1
+ import{jsx as e,jsxs as r}from"react/jsx-runtime";import{CambridgeYleHeader as i}from"./CambridgeYleHeader";import{CambridgeYleSidebar as l}from"./CambridgeYleSidebar";import{CambridgeYleBottomNav as s}from"./CambridgeYleBottomNav";import{CambridgeYleNavButton as a}from"./CambridgeYleNavButton";import{cn as d}from"../../../shared/lib/utils";import o from"../summer-sky/assets/images/background-v2.png";import n from"../summer-sky/assets/images/ocean-frame.png";export function CambridgeYleExamLayout({children:t,onPrevious:m,onNext:c,onClose:p,isFirstQuestion:x,isLastQuestion:f,isNavDisabled:u,headerProps:b,sidebarProps:h,partName:v}){return r("div",{className:"relative flex h-screen flex-col overflow-hidden bg-cover bg-center bg-no-repeat",style:{backgroundImage:`url(${o.src})`},children:[e("div",{className:"absolute inset-0 bg-black/10"}),e("div",{className:"relative z-50",children:e(i,{...b,onClose:p})}),r("div",{className:"relative z-10 flex flex-1 min-h-0 items-stretch justify-center p-2 pt-0 pb-2 sm:px-4 sm:pt-0 sm:pb-4 lg:px-[2%] lg:pt-0 lg:pb-[20px] max-w-[1500px] mx-auto w-full gap-2 xl:gap-4",children:[e("div",{className:"hidden xl:flex shrink-0 items-center",children:e(a,{direction:"prev",onClick:m,disabled:x||u})}),r("div",{className:"flex flex-1 w-full relative drop-shadow-xl min-h-0",children:[e("div",{className:"absolute inset-0 pointer-events-none",style:{background:`url(${n.src}) no-repeat center center`,backgroundSize:"100% 100%",zIndex:0}}),r("div",{className:"flex flex-1 w-full relative z-10 p-[12px] sm:p-[16px] md:p-[24px]",children:[h&&e("div",{className:"hidden lg:flex w-[30%] shrink-0 bg-[#eef7ff] rounded-l-[20px] md:rounded-l-[32px] border-r-2 border-blue-100 flex-col overflow-hidden",children:e(l,{...h})}),r("div",{className:d("flex w-full min-h-0 flex-col flex-1 relative bg-white overflow-hidden",h?"rounded-r-[20px] md:rounded-r-[32px]":"rounded-[20px] md:rounded-[32px]"),children:[e("div",{id:"exam-main-scroll-area",className:"flex-1 min-h-0 overflow-auto",children:t}),e("div",{className:"hidden shrink-0 bg-white p-1 sm:px-6 border-t border-blue-200/50",children:e(s,{onPrevious:m,onNext:c,isFirstQuestion:x,isLastQuestion:f,isNavDisabled:u})})]})]})]}),e("div",{className:"hidden xl:flex shrink-0 items-center",children:e(a,{direction:"next",onClick:c,disabled:f||u})})]})]})}
@@ -1,11 +1 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- export default function CambridgeYleExampleText({ text, highlightedWord, }) {
3
- const renderText = () => {
4
- if (!highlightedWord) {
5
- return _jsx("span", { children: text });
6
- }
7
- const parts = text.split(highlightedWord);
8
- return (_jsx(_Fragment, { children: parts.map((part, index) => (_jsxs("span", { children: [part, index < parts.length - 1 && (_jsx("span", { className: "font-bold", children: highlightedWord }))] }, index))) }));
9
- };
10
- return (_jsxs("div", { className: "text-base", children: [_jsx("span", { className: "font-bold", children: "Example:" }), " ", renderText()] }));
11
- }
1
+ import{jsx as e,jsxs as n,Fragment as s}from"react/jsx-runtime";export default function t({text:t,highlightedWord:a}){return n("div",{className:"text-base",children:[e("span",{className:"font-bold",children:"Example:"})," ",(()=>{if(!a)return e("span",{children:t});const r=t.split(a);return e(s,{children:r.map((s,t)=>n("span",{children:[s,t<r.length-1&&e("span",{className:"font-bold",children:a})]},t))})})()]})}