@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,31 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { Loader2 } from 'lucide-react';
4
- import { cn } from '../../../shared/lib/utils';
5
- import { EnglishCertificationBrand } from './EnglishCertificationBrand';
6
- /**
7
- * Formats seconds into HH:MM:SS string.
8
- * Always shows hours for the 2-hour standardized exams.
9
- */
10
- function formatTime(seconds) {
11
- const h = Math.floor(seconds / 3600);
12
- const m = Math.floor((seconds % 3600) / 60);
13
- const s = seconds % 60;
14
- return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
15
- }
16
- /**
17
- * Formats a Date into HH:MM local time string.
18
- */
19
- function formatSavedAt(date) {
20
- return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false });
21
- }
22
- /**
23
- * Branded Ocean Edu top header bar for English Certification exam taking.
24
- *
25
- * Layout (desktop): brand block · test name · part/range meta · timer · submit.
26
- */
27
- export function EnglishCertificationHeader({ examName, remainingSeconds, totalQuestions, answeredCount, isSubmitting, onSubmit, lastSavedAt, isSaving, partLabel, partSubLabel, questionRange, }) {
28
- const isWarning = remainingSeconds <= 900 && remainingSeconds > 300; // 5-15 min
29
- const isCritical = remainingSeconds <= 300; // ≤5 min
30
- return (_jsxs("header", { className: cn('sticky top-0 z-50 flex h-[74px] items-center gap-4 border-b border-[#d9e4f2] px-4 lg:px-6', 'bg-white/95 shadow-[0_8px_26px_rgba(10,61,154,0.08)] backdrop-blur-md'), children: [_jsx("div", { className: "flex shrink-0 items-center gap-3", children: _jsx("div", { className: "hidden border-r border-[#d9e4f2] pr-4 sm:block", children: _jsx(EnglishCertificationBrand, {}) }) }), _jsxs("div", { className: "hidden min-w-0 lg:block", children: [_jsx("strong", { className: "block truncate text-[17px] font-semibold leading-tight text-[#0f1730]", children: examName }), _jsx("span", { className: "mt-1 block text-sm font-bold text-[#64748b]", children: "English Certification" })] }), _jsxs("div", { className: "flex flex-1 items-center justify-center gap-5 text-center", children: [partLabel && (_jsxs("div", { className: "leading-tight text-[#0b57d0]", children: [_jsx("div", { className: "text-sm font-black tracking-wide", children: partLabel }), partSubLabel && (_jsx("div", { className: "mt-1 text-xs font-semibold text-[#27324a]", children: partSubLabel }))] })), questionRange && (_jsxs("div", { className: "whitespace-nowrap text-[15px] font-bold text-[#0f1730]", children: ["Questions ", _jsx("span", { className: "text-[#0b57d0]", children: questionRange })] }))] }), _jsxs("div", { className: "flex shrink-0 flex-col items-center border-l border-[#d9e4f2] pl-4", children: [_jsx("span", { className: cn('font-mono text-xl font-black leading-none tracking-wide tabular-nums lg:text-2xl', !isWarning && !isCritical && 'text-[#0b57d0]', isWarning && 'text-orange-500', isCritical && 'animate-pulse text-red-500'), "aria-live": "polite", "aria-label": `Time remaining: ${formatTime(remainingSeconds)}`, children: formatTime(remainingSeconds) }), _jsx("span", { className: "mt-1 text-[11px] font-semibold text-[#64748b]", children: "Time Remaining" }), (isSaving || lastSavedAt) && (_jsx("span", { className: "mt-0.5 flex items-center gap-1 text-[10px] text-slate-400", children: isSaving ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-2.5 w-2.5 animate-spin" }), "Saving..."] })) : lastSavedAt ? (_jsxs(_Fragment, { children: ["Saved ", formatSavedAt(lastSavedAt)] })) : null }))] }), _jsxs("div", { className: "flex shrink-0 items-center gap-3", children: [_jsxs("div", { className: "hidden text-right sm:block", children: [_jsxs("div", { className: "text-sm font-semibold text-[#0f1730]", children: [_jsx("span", { className: "text-[#0b57d0]", children: answeredCount }), _jsxs("span", { className: "text-slate-400", children: ["/", totalQuestions] })] }), _jsx("div", { className: "text-[11px] text-slate-400", children: "Answered" })] }), _jsx("button", { type: "button", onClick: onSubmit, disabled: isSubmitting, className: cn('inline-flex h-12 items-center gap-1.5 rounded-lg px-5 text-sm font-black tracking-wide text-white', 'bg-gradient-to-b from-[#ffad31] to-[#ff8a00] shadow-[0_12px_24px_rgba(255,138,0,0.3)]', 'transition-all hover:-translate-y-0.5 hover:brightness-[0.98]', 'disabled:cursor-not-allowed disabled:opacity-60'), children: isSubmitting ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), "Submitting..."] })) : ('SUBMIT TEST') })] })] }));
31
- }
1
+ "use client";import{jsx as e,jsxs as t,Fragment as a}from"react/jsx-runtime";import{Loader2 as i}from"lucide-react";import{cn as n}from"../../../shared/lib/utils";import{EnglishCertificationBrand as s}from"./EnglishCertificationBrand";function l(e){const t=Math.floor(e/3600),a=Math.floor(e%3600/60),i=e%60;return`${String(t).padStart(2,"0")}:${String(a).padStart(2,"0")}:${String(i).padStart(2,"0")}`}export function EnglishCertificationHeader({examName:r,remainingSeconds:d,totalQuestions:c,answeredCount:o,isSubmitting:m,onSubmit:x,lastSavedAt:h,isSaving:b,partLabel:p,partSubLabel:g,questionRange:f}){const u=d<=900&&d>300,N=d<=300;return t("header",{className:n("sticky top-0 z-50 flex h-[74px] items-center gap-4 border-b border-[#d9e4f2] px-4 lg:px-6","bg-white/95 shadow-[0_8px_26px_rgba(10,61,154,0.08)] backdrop-blur-md"),children:[e("div",{className:"flex shrink-0 items-center gap-3",children:e("div",{className:"hidden border-r border-[#d9e4f2] pr-4 sm:block",children:e(s,{})})}),t("div",{className:"hidden min-w-0 lg:block",children:[e("strong",{className:"block truncate text-[17px] font-semibold leading-tight text-[#0f1730]",children:r}),e("span",{className:"mt-1 block text-sm font-bold text-[#64748b]",children:"English Certification"})]}),t("div",{className:"flex flex-1 items-center justify-center gap-5 text-center",children:[p&&t("div",{className:"leading-tight text-[#0b57d0]",children:[e("div",{className:"text-sm font-black tracking-wide",children:p}),g&&e("div",{className:"mt-1 text-xs font-semibold text-[#27324a]",children:g})]}),f&&t("div",{className:"whitespace-nowrap text-[15px] font-bold text-[#0f1730]",children:["Questions ",e("span",{className:"text-[#0b57d0]",children:f})]})]}),t("div",{className:"flex shrink-0 flex-col items-center border-l border-[#d9e4f2] pl-4",children:[e("span",{className:n("font-mono text-xl font-black leading-none tracking-wide tabular-nums lg:text-2xl",!u&&!N&&"text-[#0b57d0]",u&&"text-orange-500",N&&"animate-pulse text-red-500"),"aria-live":"polite","aria-label":`Time remaining: ${l(d)}`,children:l(d)}),e("span",{className:"mt-1 text-[11px] font-semibold text-[#64748b]",children:"Time Remaining"}),(b||h)&&e("span",{className:"mt-0.5 flex items-center gap-1 text-[10px] text-slate-400",children:b?t(a,{children:[e(i,{className:"h-2.5 w-2.5 animate-spin"}),"Saving..."]}):h?t(a,{children:["Saved ",(v=h,v.toLocaleTimeString("en-US",{hour:"2-digit",minute:"2-digit",hour12:!1}))]}):null})]}),t("div",{className:"flex shrink-0 items-center gap-3",children:[t("div",{className:"hidden text-right sm:block",children:[t("div",{className:"text-sm font-semibold text-[#0f1730]",children:[e("span",{className:"text-[#0b57d0]",children:o}),t("span",{className:"text-slate-400",children:["/",c]})]}),e("div",{className:"text-[11px] text-slate-400",children:"Answered"})]}),e("button",{type:"button",onClick:x,disabled:m,className:n("inline-flex h-12 items-center gap-1.5 rounded-lg px-5 text-sm font-black tracking-wide text-white","bg-gradient-to-b from-[#ffad31] to-[#ff8a00] shadow-[0_12px_24px_rgba(255,138,0,0.3)]","transition-all hover:-translate-y-0.5 hover:brightness-[0.98]","disabled:cursor-not-allowed disabled:opacity-60"),children:m?t(a,{children:[e(i,{className:"h-4 w-4 animate-spin"}),"Submitting..."]}):"SUBMIT TEST"})]})]});var v}
@@ -1,71 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useRef, useEffect, useMemo } from 'react';
4
- import { cn } from '../../../shared/lib/utils';
5
- /** Returns true when an answer entry counts as "answered". */
6
- function isAnswered(answer) {
7
- if (answer === undefined || answer === null || answer === '')
8
- return false;
9
- if (typeof answer === 'object' && 'optionId' in answer) {
10
- return Boolean(answer.optionId);
11
- }
12
- return true;
13
- }
14
- /**
15
- * Branded Ocean Edu "Answer Sheet" navigation panel.
16
- *
17
- * Shows an Answered / Unanswered / Marked summary, a per-part question grid
18
- * (answered, current, marked states) and a legend, bound to real store data.
19
- */
20
- export function EnglishCertificationNavigationPanel({ parts, questions, answers, flaggedQuestions, currentIndex, onQuestionClick, onPartClick, }) {
21
- const buttonRefs = useRef(new Map());
22
- // Scroll current button into view when currentIndex changes.
23
- useEffect(() => {
24
- const btn = buttonRefs.current.get(currentIndex);
25
- btn?.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
26
- }, [currentIndex]);
27
- const { answeredCount, markedCount, unansweredCount } = useMemo(() => {
28
- const answered = questions.filter((q) => isAnswered(answers[q.id])).length;
29
- const marked = questions.filter((q) => flaggedQuestions.has(q.id)).length;
30
- return {
31
- answeredCount: answered,
32
- markedCount: marked,
33
- unansweredCount: questions.length - answered,
34
- };
35
- }, [questions, answers, flaggedQuestions]);
36
- return (_jsxs("div", { className: "flex h-full flex-col", children: [_jsxs("div", { className: "grid grid-cols-3 gap-3 p-4 pb-2", children: [_jsx(SummaryBox, { label: "Answered", value: answeredCount, variant: "answered" }), _jsx(SummaryBox, { label: "Unanswered", value: unansweredCount, variant: "unanswered" }), _jsx(SummaryBox, { label: "Marked", value: markedCount, variant: "marked" })] }), _jsxs("div", { className: "flex-1 overflow-y-auto px-4 pb-4", children: [parts.map((part, partIdx) => {
37
- const startGlobal = part.startIndex ?? 0;
38
- const endGlobal = part.endIndex ?? startGlobal;
39
- const totalInPart = endGlobal - startGlobal + 1;
40
- return (_jsxs("section", { className: "mb-5", children: [_jsx("button", { type: "button", onClick: () => onPartClick(partIdx), className: cn('mb-3 w-full text-left text-[15px] font-black text-[#17213a] transition-colors hover:text-[#0b57d0]', currentIndex >= startGlobal &&
41
- currentIndex <= endGlobal &&
42
- 'text-[#0b57d0]'), children: part.name || `Part ${partIdx + 1}` }), _jsx("div", { className: "grid grid-cols-5 gap-2.5", children: Array.from({ length: totalInPart }, (_, i) => {
43
- const globalIdx = startGlobal + i;
44
- const question = questions[globalIdx];
45
- if (!question)
46
- return null;
47
- const answered = isAnswered(answers[question.id]);
48
- const flagged = flaggedQuestions.has(question.id);
49
- const current = globalIdx === currentIndex;
50
- const displayNum = globalIdx + 1;
51
- return (_jsxs("button", { ref: (el) => {
52
- if (el)
53
- buttonRefs.current.set(globalIdx, el);
54
- else
55
- buttonRefs.current.delete(globalIdx);
56
- }, type: "button", onClick: () => onQuestionClick(globalIdx), className: cn('relative flex h-[50px] items-center justify-center rounded-md border text-[15px] font-bold transition-all', 'focus-visible:ring-2 focus-visible:ring-[#1167e8] focus-visible:ring-offset-1', answered
57
- ? 'border-[#b9edc8] bg-[#ddf8e7] text-[#111827]'
58
- : 'border-[#d9e4f2] bg-white text-[#111827]', current &&
59
- 'border-[#1167e8] text-[#0b57d0] shadow-[inset_0_0_0_1px_#1167e8]'), "aria-label": `Question ${displayNum}${answered ? ', answered' : ', unanswered'}${flagged ? ', marked' : ''}`, "aria-current": current ? 'true' : undefined, children: [displayNum, flagged && (_jsx("span", { className: "absolute -top-px right-2 h-[18px] w-[9px] bg-[#ff8a00]", style: {
60
- clipPath: 'polygon(0 0, 100% 0, 100% 100%, 50% 70%, 0 100%)',
61
- }, "aria-hidden": "true" }))] }, question.id));
62
- }) })] }, partIdx));
63
- }), _jsxs("div", { className: "mt-2 rounded-md border border-[#d9e4f2] bg-white/70 p-4", children: [_jsx(LegendRow, { variant: "current", children: "Current Question" }), _jsx(LegendRow, { variant: "answered", children: "Answered" }), _jsx(LegendRow, { variant: "none", children: "Not Answered" }), _jsx(LegendRow, { variant: "marked", children: "Marked for Review" })] })] })] }));
64
- }
65
- function SummaryBox({ label, value, variant, }) {
66
- return (_jsx("div", { className: "grid min-h-[78px] place-items-center rounded-md border border-[#d9e4f2] bg-white text-center", children: _jsxs("div", { children: [_jsx("span", { className: cn('block text-[11px] font-black uppercase tracking-wide', variant === 'answered' && 'text-[#0a3d9a]', variant === 'unanswered' && 'text-[#17213a]', variant === 'marked' && 'text-[#f97316]'), children: label }), _jsx("strong", { className: cn('mt-2 block text-[28px] leading-none', variant === 'answered' && 'text-[#0b57d0]', variant === 'unanswered' && 'text-[#17213a]', variant === 'marked' && 'text-[#f97316]'), children: value })] }) }));
67
- }
68
- function LegendRow({ variant, children, }) {
69
- return (_jsxs("div", { className: "my-3 flex items-center gap-3.5 text-sm font-semibold text-[#334155]", children: [variant === 'marked' ? (_jsx("span", { className: "h-[18px] w-2.5 bg-[#ff8a00]", style: { clipPath: 'polygon(0 0, 100% 0, 100% 100%, 50% 70%, 0 100%)' } })) : (_jsx("span", { className: cn('h-[22px] w-[22px] rounded border border-[#d9e4f2] bg-white', variant === 'current' &&
70
- 'border-[#1167e8] shadow-[inset_0_0_0_1px_#1167e8]', variant === 'answered' && 'border-[#b9edc8] bg-[#ddf8e7]') })), children] }));
71
- }
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useRef as r,useEffect as n,useMemo as a}from"react";import{cn as d}from"../../../shared/lib/utils";function i(e){return null!=e&&""!==e&&("object"!=typeof e||!("optionId"in e)||Boolean(e.optionId))}export function EnglishCertificationNavigationPanel({parts:o,questions:c,answers:u,flaggedQuestions:b,currentIndex:m,onQuestionClick:f,onPartClick:x}){const h=r(new Map);n(()=>{const e=h.current.get(m);e?.scrollIntoView({block:"nearest",behavior:"smooth"})},[m]);const{answeredCount:p,markedCount:g,unansweredCount:w}=a(()=>{const e=c.filter(e=>i(u[e.id])).length;return{answeredCount:e,markedCount:c.filter(e=>b.has(e.id)).length,unansweredCount:c.length-e}},[c,u,b]);return t("div",{className:"flex h-full flex-col",children:[t("div",{className:"grid grid-cols-3 gap-3 p-4 pb-2",children:[e(s,{label:"Answered",value:p,variant:"answered"}),e(s,{label:"Unanswered",value:w,variant:"unanswered"}),e(s,{label:"Marked",value:g,variant:"marked"})]}),t("div",{className:"flex-1 overflow-y-auto px-4 pb-4",children:[o.map((r,n)=>{const a=r.startIndex??0,s=r.endIndex??a,l=s-a+1;return t("section",{className:"mb-5",children:[e("button",{type:"button",onClick:()=>x(n),className:d("mb-3 w-full text-left text-[15px] font-black text-[#17213a] transition-colors hover:text-[#0b57d0]",m>=a&&m<=s&&"text-[#0b57d0]"),children:r.name||`Part ${n+1}`}),e("div",{className:"grid grid-cols-5 gap-2.5",children:Array.from({length:l},(r,n)=>{const s=a+n,l=c[s];if(!l)return null;const o=i(u[l.id]),x=b.has(l.id),p=s===m,g=s+1;return t("button",{ref:e=>{e?h.current.set(s,e):h.current.delete(s)},type:"button",onClick:()=>f(s),className:d("relative flex h-[50px] items-center justify-center rounded-md border text-[15px] font-bold transition-all","focus-visible:ring-2 focus-visible:ring-[#1167e8] focus-visible:ring-offset-1",o?"border-[#b9edc8] bg-[#ddf8e7] text-[#111827]":"border-[#d9e4f2] bg-white text-[#111827]",p&&"border-[#1167e8] text-[#0b57d0] shadow-[inset_0_0_0_1px_#1167e8]"),"aria-label":`Question ${g}${o?", answered":", unanswered"}${x?", marked":""}`,"aria-current":p?"true":void 0,children:[g,x&&e("span",{className:"absolute -top-px right-2 h-[18px] w-[9px] bg-[#ff8a00]",style:{clipPath:"polygon(0 0, 100% 0, 100% 100%, 50% 70%, 0 100%)"},"aria-hidden":"true"})]},l.id)})})]},n)}),t("div",{className:"mt-2 rounded-md border border-[#d9e4f2] bg-white/70 p-4",children:[e(l,{variant:"current",children:"Current Question"}),e(l,{variant:"answered",children:"Answered"}),e(l,{variant:"none",children:"Not Answered"}),e(l,{variant:"marked",children:"Marked for Review"})]})]})]})}function s({label:r,value:n,variant:a}){return e("div",{className:"grid min-h-[78px] place-items-center rounded-md border border-[#d9e4f2] bg-white text-center",children:t("div",{children:[e("span",{className:d("block text-[11px] font-black uppercase tracking-wide","answered"===a&&"text-[#0a3d9a]","unanswered"===a&&"text-[#17213a]","marked"===a&&"text-[#f97316]"),children:r}),e("strong",{className:d("mt-2 block text-[28px] leading-none","answered"===a&&"text-[#0b57d0]","unanswered"===a&&"text-[#17213a]","marked"===a&&"text-[#f97316]"),children:n})]})})}function l({variant:r,children:n}){return t("div",{className:"my-3 flex items-center gap-3.5 text-sm font-semibold text-[#334155]",children:[e("span","marked"===r?{className:"h-[18px] w-2.5 bg-[#ff8a00]",style:{clipPath:"polygon(0 0, 100% 0, 100% 100%, 50% 70%, 0 100%)"}}:{className:d("h-[22px] w-[22px] rounded border border-[#d9e4f2] bg-white","current"===r&&"border-[#1167e8] shadow-[inset_0_0_0_1px_#1167e8]","answered"===r&&"border-[#b9edc8] bg-[#ddf8e7]")}),n]})}
@@ -1,33 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { cn } from '../../../shared/lib/utils';
4
- import { CheckCircle2, XCircle } from 'lucide-react';
5
- /**
6
- * Shared A–D answer selector for all English Certification question types.
7
- *
8
- * Renders either large branded cards or compact rows, and shows
9
- * correct/incorrect indicators in review mode.
10
- */
11
- export function EnglishCertificationOptionSelector({ options, selectedOptionId, onSelect, disabled = false, isReviewMode = false, correctOptionId, variant = 'cards', }) {
12
- if (variant === 'compact') {
13
- return (_jsx("div", { className: "flex flex-col gap-1", role: "radiogroup", children: options.map((option) => {
14
- const isSelected = option.id === selectedOptionId;
15
- const isCorrect = isReviewMode && option.id === correctOptionId;
16
- const isWrong = isReviewMode && isSelected && option.id !== correctOptionId;
17
- return (_jsxs("button", { type: "button", role: "radio", "aria-checked": isSelected, disabled: disabled, onClick: () => !disabled && onSelect(option.id), className: cn('flex items-center gap-2.5 rounded-md border border-transparent px-2.5 py-1.5 text-left text-base transition-all', 'hover:border-[#c8def7] hover:bg-[#f4f9ff]', isSelected &&
18
- !isReviewMode &&
19
- 'border-[#99c5f8] bg-[#eff7ff] shadow-[inset_0_0_0_1px_rgba(17,103,232,0.08)]', isCorrect && 'border-[#b9edc8] bg-[#ddf8e7]', isWrong && 'border-red-300 bg-red-50', disabled && 'cursor-not-allowed opacity-60'), children: [_jsx("span", { className: cn('grid h-4 w-4 shrink-0 place-items-center rounded-full border-2 border-[#94a3b8]', isSelected && !isReviewMode && 'border-[#0b57d0]', isCorrect && 'border-[#22c55e]', isWrong && 'border-red-500'), children: isSelected && (_jsx("span", { className: cn('h-2 w-2 rounded-full bg-[#0b57d0]', isCorrect && 'bg-[#22c55e]', isWrong && 'bg-red-500') })) }), _jsx("span", { className: "font-bold text-[#23304c]", children: option.label }), _jsx("span", { className: "flex-1 leading-snug text-[#1e293b]", children: option.text }), isReviewMode && isCorrect && (_jsx(CheckCircle2, { className: "h-4 w-4 shrink-0 text-[#22c55e]", "aria-label": "Correct" })), isReviewMode && isWrong && (_jsx(XCircle, { className: "h-4 w-4 shrink-0 text-red-500", "aria-label": "Incorrect" }))] }, option.id));
20
- }) }));
21
- }
22
- // 'cards' variant — large A–D answer cards.
23
- return (_jsx("div", { className: "grid gap-4 sm:gap-6", style: { gridTemplateColumns: `repeat(${Math.min(options.length, 4)}, minmax(0, 1fr))` }, role: "radiogroup", children: options.map((option) => {
24
- const isSelected = option.id === selectedOptionId;
25
- const isCorrect = isReviewMode && option.id === correctOptionId;
26
- const isWrong = isReviewMode && isSelected && option.id !== correctOptionId;
27
- const isActive = isSelected && !isReviewMode;
28
- return (_jsxs("button", { type: "button", role: "radio", "aria-checked": isSelected, disabled: disabled, onClick: () => !disabled && onSelect(option.id), className: cn('relative flex min-h-[96px] flex-col items-center justify-center rounded-[10px] border p-3 transition-all', 'border-[#d9e4f2] bg-white shadow-[0_5px_16px_rgba(13,71,161,0.08)]', 'hover:-translate-y-0.5', isActive &&
29
- 'border-[#0b57d0] text-white shadow-[0_16px_30px_rgba(11,87,208,0.25)]', isCorrect && 'border-[#22c55e] bg-[#ddf8e7]', isWrong && 'border-red-400 bg-red-50', disabled && 'cursor-not-allowed opacity-60'), style: isActive
30
- ? { background: 'linear-gradient(180deg, #1f7cff, #0b57d0)' }
31
- : undefined, children: [_jsx("span", { className: cn('grid h-[52px] w-[52px] place-items-center rounded-full border-[1.5px] text-2xl font-black', 'border-[#0b57d0] bg-white text-[#0b57d0]', isActive && 'border-white', isCorrect && 'border-[#22c55e] text-[#22c55e]', isWrong && 'border-red-500 text-red-500'), children: option.label }), option.text && (_jsx("span", { className: cn('mt-2 line-clamp-2 text-center text-sm font-semibold text-[#0f1730]', isActive && 'text-white', isCorrect && 'text-[#15803d]', isWrong && 'text-red-700'), children: option.text })), (isActive || isCorrect) && (_jsx("span", { className: "absolute right-2.5 top-2.5 grid h-[23px] w-[23px] place-items-center rounded-full bg-white text-[15px] font-black text-[#0b57d0]", children: "\u2713" }))] }, option.id));
32
- }) }));
33
- }
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{cn as t}from"../../../shared/lib/utils";import{CheckCircle2 as d,XCircle as a}from"lucide-react";export function EnglishCertificationOptionSelector({options:o,selectedOptionId:l,onSelect:i,disabled:n=!1,isReviewMode:s=!1,correctOptionId:c,variant:b="cards"}){return e("div","compact"===b?{className:"flex flex-col gap-1",role:"radiogroup",children:o.map(o=>{const b=o.id===l,p=s&&o.id===c,x=s&&b&&o.id!==c;return r("button",{type:"button",role:"radio","aria-checked":b,disabled:n,onClick:()=>!n&&i(o.id),className:t("flex items-center gap-2.5 rounded-md border border-transparent px-2.5 py-1.5 text-left text-base transition-all","hover:border-[#c8def7] hover:bg-[#f4f9ff]",b&&!s&&"border-[#99c5f8] bg-[#eff7ff] shadow-[inset_0_0_0_1px_rgba(17,103,232,0.08)]",p&&"border-[#b9edc8] bg-[#ddf8e7]",x&&"border-red-300 bg-red-50",n&&"cursor-not-allowed opacity-60"),children:[e("span",{className:t("grid h-4 w-4 shrink-0 place-items-center rounded-full border-2 border-[#94a3b8]",b&&!s&&"border-[#0b57d0]",p&&"border-[#22c55e]",x&&"border-red-500"),children:b&&e("span",{className:t("h-2 w-2 rounded-full bg-[#0b57d0]",p&&"bg-[#22c55e]",x&&"bg-red-500")})}),e("span",{className:"font-bold text-[#23304c]",children:o.label}),e("span",{className:"flex-1 leading-snug text-[#1e293b]",children:o.text}),s&&p&&e(d,{className:"h-4 w-4 shrink-0 text-[#22c55e]","aria-label":"Correct"}),s&&x&&e(a,{className:"h-4 w-4 shrink-0 text-red-500","aria-label":"Incorrect"})]},o.id)})}:{className:"grid gap-4 sm:gap-6",style:{gridTemplateColumns:`repeat(${Math.min(o.length,4)}, minmax(0, 1fr))`},role:"radiogroup",children:o.map(d=>{const a=d.id===l,o=s&&d.id===c,b=s&&a&&d.id!==c,p=a&&!s;return r("button",{type:"button",role:"radio","aria-checked":a,disabled:n,onClick:()=>!n&&i(d.id),className:t("relative flex min-h-[96px] flex-col items-center justify-center rounded-[10px] border p-3 transition-all","border-[#d9e4f2] bg-white shadow-[0_5px_16px_rgba(13,71,161,0.08)]","hover:-translate-y-0.5",p&&"border-[#0b57d0] text-white shadow-[0_16px_30px_rgba(11,87,208,0.25)]",o&&"border-[#22c55e] bg-[#ddf8e7]",b&&"border-red-400 bg-red-50",n&&"cursor-not-allowed opacity-60"),style:p?{background:"linear-gradient(180deg, #1f7cff, #0b57d0)"}:void 0,children:[e("span",{className:t("grid h-[52px] w-[52px] place-items-center rounded-full border-[1.5px] text-2xl font-black","border-[#0b57d0] bg-white text-[#0b57d0]",p&&"border-white",o&&"border-[#22c55e] text-[#22c55e]",b&&"border-red-500 text-red-500"),children:d.label}),d.text&&e("span",{className:t("mt-2 line-clamp-2 text-center text-sm font-semibold text-[#0f1730]",p&&"text-white",o&&"text-[#15803d]",b&&"text-red-700"),children:d.text}),(p||o)&&e("span",{className:"absolute right-2.5 top-2.5 grid h-[23px] w-[23px] place-items-center rounded-full bg-white text-[15px] font-black text-[#0b57d0]",children:"✓"})]},d.id)})})}
@@ -1,16 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useImageUrl } from '../../../shared/lib/hooks/useImageUrl';
4
- /**
5
- * Reading passage panel for grouped English Certification parts.
6
- *
7
- * Extracted into its own component so it can call {@link useImageUrl} to resolve
8
- * the passage image. The API persists images as S3 keys (e.g.
9
- * `questions/images/...`) which must be exchanged for a presigned URL before
10
- * they can be rendered. Local paths (`/...`) and absolute URLs pass through
11
- * unchanged.
12
- */
13
- export function EnglishCertificationPassagePanel({ passage, passageTitle, passageSubtitle, passageImageUrl, passageIsHtml, partName, }) {
14
- const resolvedImageUrl = useImageUrl(passageImageUrl || null);
15
- return (_jsxs("article", { className: "flex flex-col overflow-hidden rounded-xl border border-[#d9e4f2] bg-white/90 shadow-[0_14px_35px_rgba(13,71,161,0.12)] backdrop-blur-md", children: [_jsx("div", { className: "flex h-[52px] items-end border-b border-[#d9e4f2] bg-white/70 px-5", children: _jsx("span", { className: "flex h-full items-center border-b-[3px] border-[#0b57d0] text-sm font-black tracking-wide text-[#0a3d9a]", children: "PASSAGE" }) }), _jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "px-6 pt-6 leading-relaxed", children: [passageTitle && (_jsx("h1", { className: "mb-1 text-[clamp(20px,1.6vw,28px)] font-black leading-tight text-[#061a55]", children: passageTitle })), passageSubtitle && (_jsx("p", { className: "mb-4 text-[15px] font-semibold text-[#475569]", children: passageSubtitle })), resolvedImageUrl && (_jsx("div", { className: "mb-4 overflow-hidden rounded-lg border border-[#d9e4f2]", children: _jsx("img", { src: resolvedImageUrl, alt: "passage", className: "h-auto w-full object-cover" }) })), passageIsHtml ? (_jsx("div", { className: "ec-passage-html text-[15.8px] leading-relaxed text-[#1e293b] [&_p]:mb-3 [&_strong]:font-bold [&_ul]:my-3 [&_ul]:list-disc [&_ul]:pl-6 [&_table]:my-3 [&_td]:border [&_td]:border-[#d9e4f2] [&_td]:px-2 [&_td]:py-1", dangerouslySetInnerHTML: { __html: passage } })) : (_jsx("div", { className: "whitespace-pre-wrap text-[15.8px] text-[#1e293b]", children: passage }))] }), _jsxs("div", { className: "mt-4 flex items-center gap-2.5 border-t border-[#d9e4f2] bg-white/92 px-5 py-3 text-[13px] font-bold text-[#334155] backdrop-blur-md", children: [_jsx("span", { className: "grid h-[18px] w-[18px] place-items-center rounded-full border-2 border-[#1167e8] text-[11px] font-black text-[#1167e8]", children: "i" }), partName || 'Reading', " \u00B7 Reading passage"] })] })] }));
16
- }
1
+ "use client";import{jsx as e,jsxs as a}from"react/jsx-runtime";import{useImageUrl as t}from"../../../shared/lib/hooks/useImageUrl";export function EnglishCertificationPassagePanel({passage:l,passageTitle:r,passageSubtitle:d,passageImageUrl:s,passageIsHtml:i,partName:o}){const c=t(s||null);return a("article",{className:"flex flex-col overflow-hidden rounded-xl border border-[#d9e4f2] bg-white/90 shadow-[0_14px_35px_rgba(13,71,161,0.12)] backdrop-blur-md",children:[e("div",{className:"flex h-[52px] items-end border-b border-[#d9e4f2] bg-white/70 px-5",children:e("span",{className:"flex h-full items-center border-b-[3px] border-[#0b57d0] text-sm font-black tracking-wide text-[#0a3d9a]",children:"PASSAGE"})}),a("div",{className:"flex-1",children:[a("div",{className:"px-6 pt-6 leading-relaxed",children:[r&&e("h1",{className:"mb-1 text-[clamp(20px,1.6vw,28px)] font-black leading-tight text-[#061a55]",children:r}),d&&e("p",{className:"mb-4 text-[15px] font-semibold text-[#475569]",children:d}),c&&e("div",{className:"mb-4 overflow-hidden rounded-lg border border-[#d9e4f2]",children:e("img",{src:c,alt:"passage",className:"h-auto w-full object-cover"})}),e("div",i?{className:"ec-passage-html text-[15.8px] leading-relaxed text-[#1e293b] [&_p]:mb-3 [&_strong]:font-bold [&_ul]:my-3 [&_ul]:list-disc [&_ul]:pl-6 [&_table]:my-3 [&_td]:border [&_td]:border-[#d9e4f2] [&_td]:px-2 [&_td]:py-1",dangerouslySetInnerHTML:{__html:l}}:{className:"whitespace-pre-wrap text-[15.8px] text-[#1e293b]",children:l})]}),a("div",{className:"mt-4 flex items-center gap-2.5 border-t border-[#d9e4f2] bg-white/92 px-5 py-3 text-[13px] font-bold text-[#334155] backdrop-blur-md",children:[e("span",{className:"grid h-[18px] w-[18px] place-items-center rounded-full border-2 border-[#1167e8] text-[11px] font-black text-[#1167e8]",children:"i"}),o||"Reading"," · Reading passage"]})]})]})}
@@ -1,22 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { EnglishCertificationOptionSelector } from './EnglishCertificationOptionSelector';
4
- import { EnglishCertificationFlagButton } from './EnglishCertificationFlagButton';
5
- /**
6
- * Single MCQ rendered as a standalone Ocean Edu white card.
7
- *
8
- * Replaces the former shared-section/divider layout so each question matches the
9
- * target mockup (own card, number chip, flag button, compact options).
10
- */
11
- export function EnglishCertificationQuestionCard({ question, globalQuestionNumber, answer, isFlagged, sharedInstruction, onAnswerChange, onToggleFlag, isReviewMode = false, }) {
12
- const content = question.content || {};
13
- const rawOptions = content.options || [];
14
- const questionText = content.question?.trim() ?? '';
15
- // Skip per-question text when it merely repeats the shared part instruction.
16
- const showQuestionText = questionText.length > 0 && questionText !== sharedInstruction;
17
- return (_jsx("article", { className: "rounded-xl border border-[#d9e4f2] bg-white/90 px-4 py-4 shadow-[0_5px_16px_rgba(13,71,161,0.08)] backdrop-blur-md transition-colors", children: _jsxs("div", { className: "grid grid-cols-[44px_1fr_32px] gap-1", children: [_jsx("div", { className: "grid h-9 w-9 place-items-center rounded-full bg-[#eef5ff] text-base font-black leading-none text-[#0b57d0]", children: globalQuestionNumber }), _jsxs("div", { className: "min-w-0", children: [showQuestionText && (_jsx("div", { className: "mb-2 text-base font-black leading-snug text-[#10172f]", children: questionText })), _jsx(EnglishCertificationOptionSelector, { options: rawOptions.map((option, optIdx) => ({
18
- id: option?.id || String.fromCharCode(65 + optIdx),
19
- label: option?.id || String.fromCharCode(65 + optIdx),
20
- text: option?.text || String(option ?? ''),
21
- })), selectedOptionId: answer?.optionId ?? null, onSelect: (optionId) => onAnswerChange(question.id, { optionId }), isReviewMode: isReviewMode, disabled: isReviewMode, variant: "compact" })] }), _jsx(EnglishCertificationFlagButton, { isFlagged: isFlagged, onToggle: () => onToggleFlag(question.id), size: "sm", className: "self-start justify-self-end" })] }) }));
22
- }
1
+ "use client";import{jsx as e,jsxs as i}from"react/jsx-runtime";import{EnglishCertificationOptionSelector as t}from"./EnglishCertificationOptionSelector";import{EnglishCertificationFlagButton as n}from"./EnglishCertificationFlagButton";export function EnglishCertificationQuestionCard({question:o,globalQuestionNumber:r,answer:s,isFlagged:a,sharedInstruction:l,onAnswerChange:d,onToggleFlag:c,isReviewMode:g=!1}){const m=o.content||{},f=m.options||[],p=m.question?.trim()??"",u=p.length>0&&p!==l;return e("article",{className:"rounded-xl border border-[#d9e4f2] bg-white/90 px-4 py-4 shadow-[0_5px_16px_rgba(13,71,161,0.08)] backdrop-blur-md transition-colors",children:i("div",{className:"grid grid-cols-[44px_1fr_32px] gap-1",children:[e("div",{className:"grid h-9 w-9 place-items-center rounded-full bg-[#eef5ff] text-base font-black leading-none text-[#0b57d0]",children:r}),i("div",{className:"min-w-0",children:[u&&e("div",{className:"mb-2 text-base font-black leading-snug text-[#10172f]",children:p}),e(t,{options:f.map((e,i)=>({id:e?.id||String.fromCharCode(65+i),label:e?.id||String.fromCharCode(65+i),text:e?.text||String(e??"")})),selectedOptionId:s?.optionId??null,onSelect:e=>d(o.id,{optionId:e}),isReviewMode:g,disabled:g,variant:"compact"})]}),e(n,{isFlagged:a,onToggle:()=>c(o.id),size:"sm",className:"self-start justify-self-end"})]})})}
@@ -1,37 +1 @@
1
- 'use client';
2
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
3
- import { useMemo, useState } from 'react';
4
- import { Dialog, DialogContent, DialogHeader, DialogTitle, } from '../../../components/ui/dialog';
5
- import { ResolvedImage } from '../../../components/common/ResolvedImage';
6
- import { EnglishCertificationOptionSelector } from './EnglishCertificationOptionSelector';
7
- import { EnglishCertificationFlagButton } from './EnglishCertificationFlagButton';
8
- import { ENGLISH_CERT_CONTENT_WIDTH_CLASS } from './english-certification-theme';
9
- /**
10
- * Ocean Edu branded card for a single multiple-choice question.
11
- *
12
- * Renders the question badge, instruction, optional photo with zoom and the
13
- * A–D answer cards. Audio is intentionally NOT rendered here — listening parts
14
- * show one shared audio bar at the top of the question list.
15
- */
16
- export function EnglishCertificationQuestionItem({ question, questionNumber, questionConfig, defaultQuestion, isListeningPart = false, answer, isFlagged, onAnswerChange, onToggleFlag, isReviewMode = false, domId, }) {
17
- const [isImageOpen, setIsImageOpen] = useState(false);
18
- const selectedOptionId = answer?.optionId ?? null;
19
- const options = useMemo(() => {
20
- const rawOptions = question.content?.options;
21
- if (!Array.isArray(rawOptions))
22
- return [];
23
- return rawOptions.map((opt, idx) => ({
24
- id: String(opt?.id || `${question.id}-opt-${idx}`),
25
- label: String.fromCharCode(65 + idx),
26
- text: String(opt?.text || opt?.value || opt || ''),
27
- }));
28
- }, [question.content?.options, question.id]);
29
- const hasImageConfig = questionConfig.includes('imageUrl');
30
- const imageUrl = question.content?.imageUrl;
31
- const shouldShowImage = hasImageConfig && Boolean(imageUrl);
32
- const questionText = question.content?.question?.trim() || defaultQuestion || '';
33
- const fallbackInstruction = isListeningPart
34
- ? 'Look at the picture and listen to the four statements. Choose the best answer.'
35
- : 'Read the question carefully and choose the best answer.';
36
- return (_jsxs("div", { id: domId, className: `${ENGLISH_CERT_CONTENT_WIDTH_CLASS} scroll-mt-24 overflow-hidden rounded-xl border border-[#d9e4f2] bg-white/90 shadow-[0_14px_35px_rgba(13,71,161,0.12)] backdrop-blur-md`, children: [_jsxs("div", { className: "flex items-center justify-between px-9 pt-6", children: [_jsxs("span", { className: "inline-flex h-[42px] items-center rounded-lg px-[18px] text-base font-black text-white shadow-[0_8px_16px_rgba(11,87,208,0.18)]", style: { background: 'linear-gradient(180deg, #1f7cff, #0b57d0)' }, children: ["Question ", questionNumber] }), _jsx(EnglishCertificationFlagButton, { isFlagged: isFlagged, onToggle: () => onToggleFlag(question.id) })] }), _jsxs("div", { className: "px-9 pb-8", children: [_jsxs("div", { className: "my-4 flex items-center gap-3 text-[17px] font-bold text-[#0f1730]", children: [isListeningPart && (_jsx("span", { className: "grid h-7 w-7 shrink-0 place-items-center rounded-full bg-[#0b57d0] text-white", children: "\uD83D\uDD0A" })), _jsx("span", { children: questionText || fallbackInstruction })] }), shouldShowImage && imageUrl && (_jsxs("button", { type: "button", onClick: () => setIsImageOpen(true), className: "group relative mx-auto mb-6 block w-full overflow-hidden rounded-[13px] border border-[#cbdcf0] shadow-[0_16px_35px_rgba(6,26,85,0.13)]", "aria-label": "Open image in large view", children: [_jsx(ResolvedImage, { src: imageUrl, alt: `Question ${questionNumber} visual prompt`, className: "h-auto max-h-[360px] w-full object-cover transition-transform group-hover:scale-[1.01]" }), _jsx("span", { className: "absolute right-3 top-3 grid h-9 w-9 place-items-center rounded-full bg-black/55 text-lg text-white", children: "\u2315" })] })), _jsx("div", { className: "w-full", children: _jsx(EnglishCertificationOptionSelector, { options: options, selectedOptionId: selectedOptionId, onSelect: (optionId) => onAnswerChange(question.id, { optionId }), disabled: isReviewMode, isReviewMode: isReviewMode, variant: "cards" }) })] }), shouldShowImage && imageUrl && (_jsx(Dialog, { open: isImageOpen, onOpenChange: setIsImageOpen, children: _jsxs(DialogContent, { className: "max-w-4xl border-none bg-transparent p-0 shadow-none", children: [_jsx(DialogHeader, { className: "sr-only", children: _jsx(DialogTitle, { children: "Question image" }) }), _jsx("div", { className: "overflow-hidden rounded-lg bg-white", children: _jsx(ResolvedImage, { src: imageUrl, alt: `Question ${questionNumber} enlarged visual prompt`, className: "h-auto w-full object-contain" }) })] }) }))] }));
37
- }
1
+ "use client";import{jsxs as e,jsx as t}from"react/jsx-runtime";import{useMemo as n,useState as o}from"react";import{Dialog as i,DialogContent as r,DialogHeader as a,DialogTitle as s}from"../../../components/ui/dialog";import{ResolvedImage as l}from"../../../components/common/ResolvedImage";import{EnglishCertificationOptionSelector as d}from"./EnglishCertificationOptionSelector";import{EnglishCertificationFlagButton as c}from"./EnglishCertificationFlagButton";import{ENGLISH_CERT_CONTENT_WIDTH_CLASS as m}from"./english-certification-theme";export function EnglishCertificationQuestionItem({question:p,questionNumber:h,questionConfig:u,defaultQuestion:g,isListeningPart:f=!1,answer:b,isFlagged:x,onAnswerChange:w,onToggleFlag:v,isReviewMode:N=!1,domId:C}){const[_,k]=o(!1),y=b?.optionId??null,j=n(()=>{const e=p.content?.options;return Array.isArray(e)?e.map((e,t)=>({id:String(e?.id||`${p.id}-opt-${t}`),label:String.fromCharCode(65+t),text:String(e?.text||e?.value||e||"")})):[]},[p.content?.options,p.id]),I=u.includes("imageUrl"),Q=p.content?.imageUrl,q=I&&Boolean(Q),S=p.content?.question?.trim()||g||"",$=f?"Look at the picture and listen to the four statements. Choose the best answer.":"Read the question carefully and choose the best answer.";return e("div",{id:C,className:`${m} scroll-mt-24 overflow-hidden rounded-xl border border-[#d9e4f2] bg-white/90 shadow-[0_14px_35px_rgba(13,71,161,0.12)] backdrop-blur-md`,children:[e("div",{className:"flex items-center justify-between px-9 pt-6",children:[e("span",{className:"inline-flex h-[42px] items-center rounded-lg px-[18px] text-base font-black text-white shadow-[0_8px_16px_rgba(11,87,208,0.18)]",style:{background:"linear-gradient(180deg, #1f7cff, #0b57d0)"},children:["Question ",h]}),t(c,{isFlagged:x,onToggle:()=>v(p.id)})]}),e("div",{className:"px-9 pb-8",children:[e("div",{className:"my-4 flex items-center gap-3 text-[17px] font-bold text-[#0f1730]",children:[f&&t("span",{className:"grid h-7 w-7 shrink-0 place-items-center rounded-full bg-[#0b57d0] text-white",children:"🔊"}),t("span",{children:S||$})]}),q&&Q&&e("button",{type:"button",onClick:()=>k(!0),className:"group relative mx-auto mb-6 block w-full overflow-hidden rounded-[13px] border border-[#cbdcf0] shadow-[0_16px_35px_rgba(6,26,85,0.13)]","aria-label":"Open image in large view",children:[t(l,{src:Q,alt:`Question ${h} visual prompt`,className:"h-auto max-h-[360px] w-full object-cover transition-transform group-hover:scale-[1.01]"}),t("span",{className:"absolute right-3 top-3 grid h-9 w-9 place-items-center rounded-full bg-black/55 text-lg text-white",children:"⌕"})]}),t("div",{className:"w-full",children:t(d,{options:j,selectedOptionId:y,onSelect:e=>w(p.id,{optionId:e}),disabled:N,isReviewMode:N,variant:"cards"})})]}),q&&Q&&t(i,{open:_,onOpenChange:k,children:e(r,{className:"max-w-4xl border-none bg-transparent p-0 shadow-none",children:[t(a,{className:"sr-only",children:t(s,{children:"Question image"})}),t("div",{className:"overflow-hidden rounded-lg bg-white",children:t(l,{src:Q,alt:`Question ${h} enlarged visual prompt`,className:"h-auto w-full object-contain"})})]})})]})}
@@ -1,72 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { SkillEnum } from '../../../shared/types/common.types';
4
- import { EnglishCertificationSingleQuestionList } from './EnglishCertificationSingleQuestionList';
5
- import { EnglishCertificationGroupedQuestion } from './EnglishCertificationGroupedQuestion';
6
- import { EnglishCertificationAudioPlayer } from './EnglishCertificationAudioPlayer';
7
- import { ENGLISH_CERT_CONTENT_WIDTH_CLASS } from './english-certification-theme';
8
- import { EcWordFillStructuredForm } from './EcWordFillStructuredForm';
9
- import { EcMatchByWritingAnswer } from './EcMatchByWritingAnswer';
10
- import { EcAnswerTheQuestion } from './EcAnswerTheQuestion';
11
- import { EcWordFillParagraph } from './EcWordFillParagraph';
12
- import { EcWritingTask } from './EcWritingTask';
13
- /**
14
- * Question types that the branded English Certification theme renders natively
15
- * as multiple-choice cards (TOEIC + IELTS MCQ variants such as TRUE/FALSE/NOT GIVEN).
16
- */
17
- const EC_NATIVE_MCQ_TYPES = new Set(['CHOOSE_THE_CORRECT_ANSWER']);
18
- /**
19
- * Dispatcher for English Certification question rendering based on part config.
20
- *
21
- * - CHOOSE_THE_CORRECT_ANSWER → branded EC MCQ list / grouped split-view.
22
- * - DOCUMENT-group Reading parts → branded split-view (passage + questions).
23
- * - All other IELTS types are rendered by DEDICATED branded EC renderers
24
- * (full-width, structured to match TOEIC) — never delegated to the shared
25
- * Movers renderer, which squeezed IELTS tables into narrow columns.
26
- */
27
- export function EnglishCertificationQuestionRenderer({ part, partConfig, answers, flaggedQuestions, onAnswerChange, onToggleFlag, isReviewMode = false, }) {
28
- if (!part.questions.length) {
29
- return (_jsx("div", { className: "flex h-full items-center justify-center rounded-xl border border-red-200 bg-red-50 p-8 text-red-600", children: "No questions found for this part" }));
30
- }
31
- // Part-level audio (Listening sections always show the audio bar so students
32
- // see the player even before the admin uploads the audio file).
33
- const isListeningPart = partConfig.category === SkillEnum.LISTENING;
34
- const partAudioUrl = isListeningPart
35
- ? part.audioUrl ?? partConfig.audioUrl ?? ''
36
- : undefined;
37
- const questionType = part.questionType;
38
- // Reading parts with a DOCUMENT passage (TOEIC Part 6/7 and IELTS Reading)
39
- // use the branded split view: passage on the left, all questions on the
40
- // right. IELTS Reading mixes sub-section types, handled inside the renderer.
41
- const hasDocumentGroup = Boolean(part.isGroup) && part.group?.type === 'DOCUMENT';
42
- const isGroupedMcq = partConfig.isGroup && EC_NATIVE_MCQ_TYPES.has(questionType);
43
- // Mixed multi-section parts (e.g. Listening SECTION 2) are kept whole by
44
- // groupQuestionsByPart with their sections attached. Route them to the
45
- // grouped renderer, which stacks each sub-section via ReadingSection.
46
- const hasSections = Boolean(part.sections?.length);
47
- if (hasDocumentGroup || isGroupedMcq || hasSections) {
48
- return (_jsx("div", { className: "h-full", children: _jsx(EnglishCertificationGroupedQuestion, { part: part, partConfig: partConfig, answers: answers, flaggedQuestions: flaggedQuestions, onAnswerChange: onAnswerChange, onToggleFlag: onToggleFlag, isReviewMode: isReviewMode, isListeningPart: isListeningPart, partAudioUrl: partAudioUrl }) }));
49
- }
50
- // Native branded MCQ list (non-grouped CHOOSE_THE_CORRECT_ANSWER).
51
- if (EC_NATIVE_MCQ_TYPES.has(questionType)) {
52
- return (_jsx(EnglishCertificationSingleQuestionList, { part: part, partConfig: partConfig, answers: answers, flaggedQuestions: flaggedQuestions, onAnswerChange: onAnswerChange, onToggleFlag: onToggleFlag, isReviewMode: isReviewMode, isListeningPart: isListeningPart, partAudioUrl: partAudioUrl }));
53
- }
54
- // Dedicated branded EC renderers for the remaining IELTS question types.
55
- const renderDedicated = () => {
56
- switch (questionType) {
57
- case 'WORD_FILL_STRUCTURED_FORM':
58
- return (_jsx(EcWordFillStructuredForm, { part: part, answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
59
- case 'MATCH_BY_WRITING_ANSWER':
60
- return (_jsx(EcMatchByWritingAnswer, { part: part, answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
61
- case 'ANSWER_THE_QUESTION':
62
- return (_jsx(EcAnswerTheQuestion, { part: part, answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
63
- case 'WORD_FILL_PARAGRAPH':
64
- return (_jsx(EcWordFillParagraph, { part: part, answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
65
- case 'WRITE_A_SHORT_LETTER':
66
- return (_jsx(EcWritingTask, { part: part, answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
67
- default:
68
- return (_jsxs("div", { className: "rounded-xl border border-amber-200 bg-amber-50 p-6 text-sm text-amber-700", children: ["Lo\u1EA1i c\u00E2u h\u1ECFi \"", questionType, "\" ch\u01B0a \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3 trong giao di\u1EC7n n\u00E0y."] }));
69
- }
70
- };
71
- return (_jsxs("div", { className: "flex flex-col items-center gap-5 pb-4", children: [isListeningPart && (_jsx("div", { className: ENGLISH_CERT_CONTENT_WIDTH_CLASS, children: _jsx(EnglishCertificationAudioPlayer, { audioUrl: partAudioUrl ?? '', partNumber: part.partNo }) })), _jsx("div", { className: `${ENGLISH_CERT_CONTENT_WIDTH_CLASS} w-full`, children: renderDedicated() })] }));
72
- }
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{SkillEnum as i}from"../../../shared/types/common.types";import{EnglishCertificationSingleQuestionList as n}from"./EnglishCertificationSingleQuestionList";import{EnglishCertificationGroupedQuestion as o}from"./EnglishCertificationGroupedQuestion";import{EnglishCertificationAudioPlayer as s}from"./EnglishCertificationAudioPlayer";import{ENGLISH_CERT_CONTENT_WIDTH_CLASS as t}from"./english-certification-theme";import{EcWordFillStructuredForm as a}from"./EcWordFillStructuredForm";import{EcMatchByWritingAnswer as l}from"./EcMatchByWritingAnswer";import{EcAnswerTheQuestion as d}from"./EcAnswerTheQuestion";import{EcWordFillParagraph as c}from"./EcWordFillParagraph";import{EcWritingTask as g}from"./EcWritingTask";const u=new Set(["CHOOSE_THE_CORRECT_ANSWER"]);export function EnglishCertificationQuestionRenderer({part:p,partConfig:m,answers:f,flaggedQuestions:h,onAnswerChange:w,onToggleFlag:R,isReviewMode:E=!1}){if(!p.questions.length)return e("div",{className:"flex h-full items-center justify-center rounded-xl border border-red-200 bg-red-50 p-8 text-red-600",children:"No questions found for this part"});const A=m.category===i.LISTENING,C=A?p.audioUrl??m.audioUrl??"":void 0,T=p.questionType,N=Boolean(p.isGroup)&&"DOCUMENT"===p.group?.type,_=m.isGroup&&u.has(T),v=Boolean(p.sections?.length);if(N||_||v)return e("div",{className:"h-full",children:e(o,{part:p,partConfig:m,answers:f,flaggedQuestions:h,onAnswerChange:w,onToggleFlag:R,isReviewMode:E,isListeningPart:A,partAudioUrl:C})});if(u.has(T))return e(n,{part:p,partConfig:m,answers:f,flaggedQuestions:h,onAnswerChange:w,onToggleFlag:R,isReviewMode:E,isListeningPart:A,partAudioUrl:C});return r("div",{className:"flex flex-col items-center gap-5 pb-4",children:[A&&e("div",{className:t,children:e(s,{audioUrl:C??"",partNumber:p.partNo})}),e("div",{className:`${t} w-full`,children:(()=>{switch(T){case"WORD_FILL_STRUCTURED_FORM":return e(a,{part:p,answers:f,onAnswerChange:w,isReviewMode:E});case"MATCH_BY_WRITING_ANSWER":return e(l,{part:p,answers:f,onAnswerChange:w,isReviewMode:E});case"ANSWER_THE_QUESTION":return e(d,{part:p,answers:f,onAnswerChange:w,isReviewMode:E});case"WORD_FILL_PARAGRAPH":return e(c,{part:p,answers:f,onAnswerChange:w,isReviewMode:E});case"WRITE_A_SHORT_LETTER":return e(g,{part:p,answers:f,onAnswerChange:w,isReviewMode:E});default:return r("div",{className:"rounded-xl border border-amber-200 bg-amber-50 p-6 text-sm text-amber-700",children:['Loại câu hỏi "',T,'" chưa được hỗ trợ trong giao diện này.']})}})()})]})}
@@ -1,90 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { EnglishCertificationOptionSelector } from './EnglishCertificationOptionSelector';
4
- import { EnglishCertificationFlagButton } from './EnglishCertificationFlagButton';
5
- import { EcWordFillStructuredForm } from './EcWordFillStructuredForm';
6
- import { EcMatchByWritingAnswer } from './EcMatchByWritingAnswer';
7
- import { EcAnswerTheQuestion } from './EcAnswerTheQuestion';
8
- import { EcWordFillParagraph } from './EcWordFillParagraph';
9
- import { EcWritingTask } from './EcWritingTask';
10
- /**
11
- * Maps a question's options to the selector shape.
12
- *
13
- * Handles three shapes:
14
- * - `{ id, text }` objects (standard MCQ options).
15
- * - plain strings from `section.defaultValue` (IELTS TRUE/FALSE/NOT GIVEN),
16
- * where the string itself is both the stored value and the displayed text.
17
- */
18
- function mapOptions(rawOptions) {
19
- return rawOptions.map((option, optIdx) => {
20
- const letter = String.fromCharCode(65 + optIdx);
21
- if (typeof option === 'string') {
22
- // IELTS TRUE/FALSE/NOT GIVEN — store the string as the option id.
23
- return { id: option, label: letter, text: option };
24
- }
25
- return {
26
- id: String(option?.id || letter),
27
- label: String(option?.id || letter),
28
- text: String(option?.text ?? option?.value ?? option ?? ''),
29
- };
30
- });
31
- }
32
- /**
33
- * Renders one sub-section of an IELTS Reading part inside the right-hand
34
- * question column.
35
- *
36
- * - CHOOSE_THE_CORRECT_ANSWER (incl. TRUE/FALSE/NOT GIVEN) → branded MCQ cards.
37
- * - Any other type (ANSWER_THE_QUESTION, WORD_FILL_PARAGRAPH,
38
- * WORD_FILL_STRUCTURED_FORM, MATCH_BY_WRITING_ANSWER, …) is delegated to the
39
- * shared TOEIC-style renderer so the proven transformers/answer shapes apply.
40
- */
41
- export function EnglishCertificationReadingSection({ part, section, questions, startIndex, answers, flaggedQuestions, onAnswerChange, onToggleFlag, isReviewMode = false, }) {
42
- if (!questions.length)
43
- return null;
44
- const sectionInstruction = (section.instructions || section.name || '').trim();
45
- const isMcq = section.questionType === 'CHOOSE_THE_CORRECT_ANSWER';
46
- if (isMcq) {
47
- return (_jsxs("div", { className: "flex flex-col gap-3", children: [sectionInstruction && (_jsx("p", { className: "text-sm font-semibold leading-relaxed text-[#0b57d0]", children: sectionInstruction })), _jsx("div", { className: "divide-y divide-[#e8eef7]", children: questions.map((question) => {
48
- const globalNumber = part.questions.indexOf(question) + startIndex + 1;
49
- const content = question.content || {};
50
- const rawOptions = Array.isArray(content.options)
51
- ? content.options
52
- : section.defaultValue ?? [];
53
- const questionText = content.question?.trim() ?? '';
54
- const answer = answers[question.id];
55
- return (_jsxs("article", { className: "grid grid-cols-[44px_1fr_32px] gap-1 py-4", children: [_jsxs("div", { className: "text-[18px] font-black leading-snug text-[#0b57d0]", children: [globalNumber, "."] }), _jsxs("div", { className: "min-w-0", children: [questionText && (_jsx("div", { className: "mb-2 text-[15px] font-black leading-snug text-[#10172f]", children: questionText })), _jsx(EnglishCertificationOptionSelector, { options: mapOptions(rawOptions), selectedOptionId: answer?.optionId ?? null, onSelect: (optionId) => onAnswerChange(question.id, { optionId }), isReviewMode: isReviewMode, disabled: isReviewMode, variant: "compact" })] }), _jsx(EnglishCertificationFlagButton, { isFlagged: flaggedQuestions.has(question.id), onToggle: () => onToggleFlag(question.id), size: "sm", className: "self-start justify-self-end" })] }, question.id));
56
- }) })] }));
57
- }
58
- // Build a synthetic sub-part carrying only this section's questions so the
59
- // dedicated branded EC renderers can transform/render it standalone.
60
- const subPart = {
61
- ...part,
62
- questionType: section.questionType,
63
- questions,
64
- instructions: sectionInstruction || part.instructions,
65
- isGroup: false,
66
- group: undefined,
67
- sections: undefined,
68
- viewMode: section.viewMode,
69
- wordBankConfig: section.wordBankConfig,
70
- startIndex: section.startIndex,
71
- endIndex: section.endIndex,
72
- };
73
- const renderSubSection = () => {
74
- switch (section.questionType) {
75
- case 'WORD_FILL_STRUCTURED_FORM':
76
- return (_jsx(EcWordFillStructuredForm, { part: subPart, answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
77
- case 'MATCH_BY_WRITING_ANSWER':
78
- return (_jsx(EcMatchByWritingAnswer, { part: subPart, answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
79
- case 'ANSWER_THE_QUESTION':
80
- return (_jsx(EcAnswerTheQuestion, { part: subPart, answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
81
- case 'WORD_FILL_PARAGRAPH':
82
- return (_jsx(EcWordFillParagraph, { part: subPart, answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
83
- case 'WRITE_A_SHORT_LETTER':
84
- return (_jsx(EcWritingTask, { part: subPart, answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
85
- default:
86
- return (_jsxs("div", { className: "rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-700", children: ["Lo\u1EA1i c\u00E2u h\u1ECFi \"", section.questionType, "\" ch\u01B0a \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3."] }));
87
- }
88
- };
89
- return (_jsxs("div", { className: "flex flex-col gap-3", children: [sectionInstruction && (_jsx("p", { className: "text-sm font-semibold leading-relaxed text-[#0b57d0]", children: sectionInstruction })), renderSubSection()] }));
90
- }
1
+ "use client";import{jsx as e,jsxs as i}from"react/jsx-runtime";import{EnglishCertificationOptionSelector as t}from"./EnglishCertificationOptionSelector";import{EnglishCertificationFlagButton as n}from"./EnglishCertificationFlagButton";import{EcWordFillStructuredForm as r}from"./EcWordFillStructuredForm";import{EcMatchByWritingAnswer as s}from"./EcMatchByWritingAnswer";import{EcAnswerTheQuestion as o}from"./EcAnswerTheQuestion";import{EcWordFillParagraph as a}from"./EcWordFillParagraph";import{EcWritingTask as d}from"./EcWritingTask";function l(e){return e.map((e,i)=>{const t=String.fromCharCode(65+i);return"string"==typeof e?{id:e,label:t,text:e}:{id:String(e?.id||t),label:String(e?.id||t),text:String(e?.text??e?.value??e??"")}})}export function EnglishCertificationReadingSection({part:c,section:m,questions:u,startIndex:p,answers:g,flaggedQuestions:f,onAnswerChange:x,onToggleFlag:h,isReviewMode:w=!1}){if(!u.length)return null;const R=(m.instructions||m.name||"").trim();if("CHOOSE_THE_CORRECT_ANSWER"===m.questionType)return i("div",{className:"flex flex-col gap-3",children:[R&&e("p",{className:"text-sm font-semibold leading-relaxed text-[#0b57d0]",children:R}),e("div",{className:"divide-y divide-[#e8eef7]",children:u.map(r=>{const s=c.questions.indexOf(r)+p+1,o=r.content||{},a=Array.isArray(o.options)?o.options:m.defaultValue??[],d=o.question?.trim()??"",u=g[r.id];return i("article",{className:"grid grid-cols-[44px_1fr_32px] gap-1 py-4",children:[i("div",{className:"text-[18px] font-black leading-snug text-[#0b57d0]",children:[s,"."]}),i("div",{className:"min-w-0",children:[d&&e("div",{className:"mb-2 text-[15px] font-black leading-snug text-[#10172f]",children:d}),e(t,{options:l(a),selectedOptionId:u?.optionId??null,onSelect:e=>x(r.id,{optionId:e}),isReviewMode:w,disabled:w,variant:"compact"})]}),e(n,{isFlagged:f.has(r.id),onToggle:()=>h(r.id),size:"sm",className:"self-start justify-self-end"})]},r.id)})})]});const v={...c,questionType:m.questionType,questions:u,instructions:R||c.instructions,isGroup:!1,group:void 0,sections:void 0,viewMode:m.viewMode,wordBankConfig:m.wordBankConfig,startIndex:m.startIndex,endIndex:m.endIndex};return i("div",{className:"flex flex-col gap-3",children:[R&&e("p",{className:"text-sm font-semibold leading-relaxed text-[#0b57d0]",children:R}),(()=>{switch(m.questionType){case"WORD_FILL_STRUCTURED_FORM":return e(r,{part:v,answers:g,onAnswerChange:x,isReviewMode:w});case"MATCH_BY_WRITING_ANSWER":return e(s,{part:v,answers:g,onAnswerChange:x,isReviewMode:w});case"ANSWER_THE_QUESTION":return e(o,{part:v,answers:g,onAnswerChange:x,isReviewMode:w});case"WORD_FILL_PARAGRAPH":return e(a,{part:v,answers:g,onAnswerChange:x,isReviewMode:w});case"WRITE_A_SHORT_LETTER":return e(d,{part:v,answers:g,onAnswerChange:x,isReviewMode:w});default:return i("div",{className:"rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-700",children:['Loại câu hỏi "',m.questionType,'" chưa được hỗ trợ.']})}})()]})}
@@ -1,18 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { EnglishCertificationAudioPlayer } from './EnglishCertificationAudioPlayer';
4
- import { EnglishCertificationQuestionItem } from './EnglishCertificationQuestionItem';
5
- import { ENGLISH_CERT_CONTENT_WIDTH_CLASS } from './english-certification-theme';
6
- /**
7
- * Ocean Edu branded renderer for a single (non-grouped) question.
8
- *
9
- * Thin wrapper around {@link EnglishCertificationQuestionItem} that prepends the
10
- * shared audio bar for listening parts. Kept for review mode and any
11
- * one-question-per-screen flow; multi-question screens use the question list.
12
- */
13
- export function EnglishCertificationSingleQuestion({ question, questionNumber, questionConfig, defaultQuestion, partAudioUrl, isListeningPart = false, answer, isFlagged, onAnswerChange, onToggleFlag, isReviewMode = false, }) {
14
- // Resolve effective audio: prefer the question's own audio, otherwise fall
15
- // back to the part-level audio (TOEIC Listening Part 1/2 define audio per part).
16
- const effectiveAudioUrl = question.audio_url || partAudioUrl || '';
17
- return (_jsxs("div", { className: "flex flex-col items-center gap-5", children: [effectiveAudioUrl && (_jsx("div", { className: ENGLISH_CERT_CONTENT_WIDTH_CLASS, children: _jsx(EnglishCertificationAudioPlayer, { audioUrl: effectiveAudioUrl, partNumber: question.part_no }) })), _jsx(EnglishCertificationQuestionItem, { question: question, questionNumber: questionNumber, questionConfig: questionConfig, defaultQuestion: defaultQuestion, isListeningPart: isListeningPart, answer: answer, isFlagged: isFlagged, onAnswerChange: onAnswerChange, onToggleFlag: onToggleFlag, isReviewMode: isReviewMode })] }));
18
- }
1
+ "use client";import{jsx as e,jsxs as i}from"react/jsx-runtime";import{EnglishCertificationAudioPlayer as n}from"./EnglishCertificationAudioPlayer";import{EnglishCertificationQuestionItem as t}from"./EnglishCertificationQuestionItem";import{ENGLISH_CERT_CONTENT_WIDTH_CLASS as o}from"./english-certification-theme";export function EnglishCertificationSingleQuestion({question:s,questionNumber:r,questionConfig:a,defaultQuestion:l,partAudioUrl:u,isListeningPart:g=!1,answer:m,isFlagged:c,onAnswerChange:f,onToggleFlag:d,isReviewMode:h=!1}){const p=s.audio_url||u||"";return i("div",{className:"flex flex-col items-center gap-5",children:[p&&e("div",{className:o,children:e(n,{audioUrl:p,partNumber:s.part_no})}),e(t,{question:s,questionNumber:r,questionConfig:a,defaultQuestion:l,isListeningPart:g,answer:m,isFlagged:c,onAnswerChange:f,onToggleFlag:d,isReviewMode:h})]})}
@@ -1,29 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { EnglishCertificationAudioPlayer } from './EnglishCertificationAudioPlayer';
4
- import { EnglishCertificationQuestionItem } from './EnglishCertificationQuestionItem';
5
- import { ENGLISH_CERT_CONTENT_WIDTH_CLASS } from './english-certification-theme';
6
- /** Stable DOM id for a question card, used for Answer-Sheet scroll-to. */
7
- export function englishCertQuestionDomId(questionId) {
8
- return `ec-question-${questionId}`;
9
- }
10
- /**
11
- * Renders every question of a single-question part (or section) on one screen.
12
- *
13
- * Listening parts show a single shared audio bar at the top; all questions then
14
- * stack vertically. Navigation across parts/sections is handled by the footer.
15
- */
16
- export function EnglishCertificationSingleQuestionList({ part, partConfig, answers, flaggedQuestions, onAnswerChange, onToggleFlag, isReviewMode = false, isListeningPart = false, partAudioUrl, }) {
17
- const startIndex = partConfig.startIndex ?? 0;
18
- const questionConfig = Array.isArray(partConfig.questionConfig)
19
- ? partConfig.questionConfig
20
- : [];
21
- const defaultQuestion = partConfig.defaultQuestion;
22
- const showAudio = isListeningPart && Boolean(partAudioUrl);
23
- return (_jsxs("div", { className: "flex flex-col items-center gap-5 pb-4", children: [showAudio && (_jsx("div", { className: ENGLISH_CERT_CONTENT_WIDTH_CLASS, children: _jsx(EnglishCertificationAudioPlayer, { audioUrl: partAudioUrl, partNumber: part.partNo }) })), part.questions.map((question, idx) => {
24
- const effectiveQuestionConfig = question.audio_url
25
- ? Array.from(new Set([...questionConfig, 'audioUrl']))
26
- : questionConfig;
27
- return (_jsx(EnglishCertificationQuestionItem, { domId: englishCertQuestionDomId(question.id), question: question, questionNumber: startIndex + idx + 1, questionConfig: effectiveQuestionConfig, defaultQuestion: defaultQuestion, isListeningPart: isListeningPart, answer: answers[question.id], isFlagged: flaggedQuestions.has(question.id), onAnswerChange: onAnswerChange, onToggleFlag: onToggleFlag, isReviewMode: isReviewMode }, question.id));
28
- })] }));
29
- }
1
+ "use client";import{jsx as e,jsxs as i}from"react/jsx-runtime";import{EnglishCertificationAudioPlayer as n}from"./EnglishCertificationAudioPlayer";import{EnglishCertificationQuestionItem as t}from"./EnglishCertificationQuestionItem";import{ENGLISH_CERT_CONTENT_WIDTH_CLASS as o}from"./english-certification-theme";export function englishCertQuestionDomId(e){return`ec-question-${e}`}export function EnglishCertificationSingleQuestionList({part:r,partConfig:s,answers:a,flaggedQuestions:u,onAnswerChange:l,onToggleFlag:g,isReviewMode:d=!1,isListeningPart:m=!1,partAudioUrl:f}){const c=s.startIndex??0,p=Array.isArray(s.questionConfig)?s.questionConfig:[],h=s.defaultQuestion,C=m&&Boolean(f);return i("div",{className:"flex flex-col items-center gap-5 pb-4",children:[C&&e("div",{className:o,children:e(n,{audioUrl:f,partNumber:r.partNo})}),r.questions.map((i,n)=>{const o=i.audio_url?Array.from(new Set([...p,"audioUrl"])):p;return e(t,{domId:englishCertQuestionDomId(i.id),question:i,questionNumber:c+n+1,questionConfig:o,defaultQuestion:h,isListeningPart:m,answer:a[i.id],isFlagged:u.has(i.id),onAnswerChange:l,onToggleFlag:g,isReviewMode:d},i.id)})]})}
@@ -1,58 +1 @@
1
- /**
2
- * English Certification theme — shared design tokens and reusable class fragments.
3
- *
4
- * A branded Ocean Edu look shared across standardized English certification
5
- * exams (TOEIC, IELTS, …). Centralises colours, radii, shadows and branded SVG
6
- * assets so the look stays consistent and easy to tune.
7
- */
8
- /** Brand palette (hex) reused by inline styles and gradients. */
9
- export const ENGLISH_CERT_COLORS = {
10
- blue950: '#061a55',
11
- blue900: '#072b7a',
12
- blue800: '#0a3d9a',
13
- blue700: '#0b57d0',
14
- blue600: '#1167e8',
15
- blue500: '#1f7cff',
16
- blue100: '#e9f3ff',
17
- blue50: '#f4f9ff',
18
- orange600: '#f97316',
19
- orange500: '#ff8a00',
20
- orange100: '#fff1db',
21
- green500: '#22c55e',
22
- green100: '#ddf8e7',
23
- red500: '#ef4444',
24
- text: '#0f1730',
25
- muted: '#64748b',
26
- border: '#d9e4f2',
27
- };
28
- /** Fixed layout dimensions (px). */
29
- export const ENGLISH_CERT_LAYOUT = {
30
- headerHeight: 74,
31
- footerHeight: 76,
32
- radius: 12,
33
- /** Shared max content width (px) for question card, image, audio bar and answers. */
34
- contentMaxWidth: 940,
35
- };
36
- /**
37
- * Shared content-width class fragment (Tailwind).
38
- * Centres the inner content blocks (image, audio bar, answer grid) so they line
39
- * up consistently across every question screen.
40
- */
41
- export const ENGLISH_CERT_CONTENT_WIDTH_CLASS = 'mx-auto w-full max-w-[940px]';
42
- /**
43
- * Full-page gradient background applied to the app shell.
44
- * Combines two soft radial highlights with a vertical base gradient.
45
- */
46
- export const ENGLISH_CERT_APP_BACKGROUND = 'radial-gradient(circle at 20% -10%, rgba(31,124,255,0.13), transparent 28%),' +
47
- 'radial-gradient(circle at 70% 0%, rgba(0,184,255,0.13), transparent 24%),' +
48
- 'linear-gradient(180deg, #f9fdff 0%, #edf7ff 52%, #f9fdff 100%)';
49
- /** Reusable glass-card class fragment (Tailwind). */
50
- export const ENGLISH_CERT_CARD_CLASS = 'rounded-xl border border-[#d9e4f2] bg-white/90 shadow-[0_14px_35px_rgba(13,71,161,0.12)] backdrop-blur-md';
51
- /** Branded gradient used for the answer-sheet header and primary actions. */
52
- export const ENGLISH_CERT_PRIMARY_GRADIENT = 'linear-gradient(180deg, #1f7cff, #0b57d0)';
53
- /**
54
- * Decorative footer wave (URL-encoded SVG) shown along the bottom action bar.
55
- */
56
- export const ENGLISH_CERT_FOOTER_WAVE = "url(\"data:image/svg+xml,%3Csvg width='1600' height='80' viewBox='0 0 1600 80' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 44C190 20 334 28 520 44C746 64 850 15 1070 34C1258 50 1408 38 1600 28V80H0V44Z' fill='%230B57D0'/%3E%3Cpath d='M0 50C230 26 346 50 540 55C760 61 872 26 1088 45C1280 62 1430 40 1600 38V80H0V50Z' fill='%23051A55' fill-opacity='0.35'/%3E%3C/svg%3E\")";
57
- /** Encouraging tagline shown in the branded footer. */
58
- export const ENGLISH_CERT_FOOTER_NOTE = 'Believe in yourself. You are stronger than you think.';
1
+ export const ENGLISH_CERT_COLORS={blue950:"#061a55",blue900:"#072b7a",blue800:"#0a3d9a",blue700:"#0b57d0",blue600:"#1167e8",blue500:"#1f7cff",blue100:"#e9f3ff",blue50:"#f4f9ff",orange600:"#f97316",orange500:"#ff8a00",orange100:"#fff1db",green500:"#22c55e",green100:"#ddf8e7",red500:"#ef4444",text:"#0f1730",muted:"#64748b",border:"#d9e4f2"};export const ENGLISH_CERT_LAYOUT={headerHeight:74,footerHeight:76,radius:12,contentMaxWidth:940};export const ENGLISH_CERT_CONTENT_WIDTH_CLASS="mx-auto w-full max-w-[940px]";export const ENGLISH_CERT_APP_BACKGROUND="radial-gradient(circle at 20% -10%, rgba(31,124,255,0.13), transparent 28%),radial-gradient(circle at 70% 0%, rgba(0,184,255,0.13), transparent 24%),linear-gradient(180deg, #f9fdff 0%, #edf7ff 52%, #f9fdff 100%)";export const ENGLISH_CERT_CARD_CLASS="rounded-xl border border-[#d9e4f2] bg-white/90 shadow-[0_14px_35px_rgba(13,71,161,0.12)] backdrop-blur-md";export const ENGLISH_CERT_PRIMARY_GRADIENT="linear-gradient(180deg, #1f7cff, #0b57d0)";export const ENGLISH_CERT_FOOTER_WAVE="url(\"data:image/svg+xml,%3Csvg width='1600' height='80' viewBox='0 0 1600 80' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 44C190 20 334 28 520 44C746 64 850 15 1070 34C1258 50 1408 38 1600 28V80H0V44Z' fill='%230B57D0'/%3E%3Cpath d='M0 50C230 26 346 50 540 55C760 61 872 26 1088 45C1280 62 1430 40 1600 38V80H0V50Z' fill='%23051A55' fill-opacity='0.35'/%3E%3C/svg%3E\")";export const ENGLISH_CERT_FOOTER_NOTE="Believe in yourself. You are stronger than you think.";