@tinyweb_dev/oe-exam-sdk 0.1.2 → 0.1.4

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 (328) hide show
  1. package/README.md +58 -4
  2. package/dist/api/contest-rooms.d.ts +13 -0
  3. package/dist/api/contest-rooms.js +1 -0
  4. package/dist/api/contest-rooms.types.d.ts +47 -0
  5. package/dist/api/contest-rooms.types.js +1 -0
  6. package/dist/api/exam-entry.d.ts +14 -0
  7. package/dist/api/exam-entry.js +1 -0
  8. package/dist/api/exam-entry.types.d.ts +93 -0
  9. package/dist/api/exam-entry.types.js +1 -0
  10. package/dist/api/exam-questions.d.ts +1 -1
  11. package/dist/api/exam-questions.js +1 -1
  12. package/dist/api/exam-questions.types.d.ts +45 -1
  13. package/dist/api/index.d.ts +2 -0
  14. package/dist/api/index.js +1 -1
  15. package/dist/components/exams/CompletionDialog.d.ts +8 -0
  16. package/dist/components/exams/CompletionDialog.js +1 -0
  17. package/dist/components/exams/CreateExamInfoForm.d.ts +4 -1
  18. package/dist/components/exams/CreateExamInfoForm.js +1 -1
  19. package/dist/components/exams/CreateExamPageContainer.d.ts +3 -0
  20. package/dist/components/exams/CreateExamPageContainer.js +1 -1
  21. package/dist/components/exams/ExamCreator.d.ts +9 -3
  22. package/dist/components/exams/ExamCreator.js +1 -1
  23. package/dist/components/exams/ExamPartTabs.d.ts +1 -1
  24. package/dist/components/exams/ExamPartTabs.js +1 -1
  25. package/dist/components/exams/ExamPreviewDialog.d.ts +20 -0
  26. package/dist/components/exams/ExamPreviewDialog.js +1 -0
  27. package/dist/components/exams/ExamQuestionGrid.d.ts +1 -1
  28. package/dist/components/exams/ExamQuestionGrid.js +1 -1
  29. package/dist/components/exams/ExamQuestionsPageContainer.d.ts +3 -0
  30. package/dist/components/exams/ExamQuestionsPageContainer.js +1 -1
  31. package/dist/components/exams/ExamViewDialog.d.ts +24 -0
  32. package/dist/components/exams/ExamViewDialog.js +1 -0
  33. package/dist/components/exams/TemplateSelector.js +1 -1
  34. package/dist/components/exams/UnsavedChangesDialog.d.ts +8 -0
  35. package/dist/components/exams/UnsavedChangesDialog.js +1 -0
  36. package/dist/components/exams/api.d.ts +1 -1
  37. package/dist/components/exams/create-exam.texts.d.ts +58 -0
  38. package/dist/components/exams/create-exam.texts.js +1 -0
  39. package/dist/components/exams/create-exam.validation.js +1 -1
  40. package/dist/components/exams/entry/EntryRestartDialog.d.ts +12 -0
  41. package/dist/components/exams/entry/EntryRestartDialog.js +1 -0
  42. package/dist/components/exams/entry/StudentExamEntryPageContainer.d.ts +31 -0
  43. package/dist/components/exams/entry/StudentExamEntryPageContainer.js +1 -0
  44. package/dist/components/exams/entry/exam-entry.states.d.ts +22 -0
  45. package/dist/components/exams/entry/exam-entry.states.js +1 -0
  46. package/dist/components/exams/entry/exam-entry.utils.d.ts +16 -0
  47. package/dist/components/exams/entry/exam-entry.utils.js +1 -0
  48. package/dist/components/exams/entry/fullscreen-loading.d.ts +5 -0
  49. package/dist/components/exams/entry/fullscreen-loading.js +1 -0
  50. package/dist/components/exams/entry/index.d.ts +7 -0
  51. package/dist/components/exams/entry/index.js +1 -0
  52. package/dist/components/exams/exam-question-status.d.ts +9 -0
  53. package/dist/components/exams/exam-question-status.js +1 -0
  54. package/dist/components/exams/index.d.ts +19 -1
  55. package/dist/components/exams/index.js +1 -1
  56. package/dist/components/exams/rooms/StudentContestRoomsPageContainer.d.ts +18 -0
  57. package/dist/components/exams/rooms/StudentContestRoomsPageContainer.js +1 -0
  58. package/dist/components/exams/rooms/contest-rooms.utils.d.ts +32 -0
  59. package/dist/components/exams/rooms/contest-rooms.utils.js +1 -0
  60. package/dist/components/exams/rooms/index.d.ts +3 -0
  61. package/dist/components/exams/rooms/index.js +1 -0
  62. package/dist/components/exams/take/ExamTakingApiContext.d.ts +10 -0
  63. package/dist/components/exams/take/ExamTakingApiContext.js +1 -0
  64. package/dist/components/exams/take/ExamTakingPageContainer.d.ts +28 -4
  65. package/dist/components/exams/take/ExamTakingPageContainer.js +1 -1
  66. package/dist/components/exams/take/components/QuestionRenderer.d.ts +25 -0
  67. package/dist/components/exams/take/components/QuestionRenderer.js +1 -0
  68. package/dist/components/exams/take/components/index.d.ts +10 -0
  69. package/dist/components/exams/take/components/index.js +1 -0
  70. package/dist/components/exams/take/components/question-renderers/ArticlesViewer.d.ts +7 -0
  71. package/dist/components/exams/take/components/question-renderers/ArticlesViewer.js +1 -0
  72. package/dist/components/exams/take/components/question-renderers/MoversAnswerTheQuestionRenderer.d.ts +22 -0
  73. package/dist/components/exams/take/components/question-renderers/MoversAnswerTheQuestionRenderer.js +1 -0
  74. package/dist/components/exams/take/components/question-renderers/MoversChooseAdjectiveRenderer.d.ts +14 -0
  75. package/dist/components/exams/take/components/question-renderers/MoversChooseAdjectiveRenderer.js +1 -0
  76. package/dist/components/exams/take/components/question-renderers/MoversChooseBestAnswerRenderer.d.ts +2 -0
  77. package/dist/components/exams/take/components/question-renderers/MoversChooseBestAnswerRenderer.js +1 -0
  78. package/dist/components/exams/take/components/question-renderers/MoversColoringRenderer.d.ts +3 -0
  79. package/dist/components/exams/take/components/question-renderers/MoversColoringRenderer.js +1 -0
  80. package/dist/components/exams/take/components/question-renderers/MoversFillWordHintRenderer.d.ts +2 -0
  81. package/dist/components/exams/take/components/question-renderers/MoversFillWordHintRenderer.js +1 -0
  82. package/dist/components/exams/take/components/question-renderers/MoversGridFillRenderer.d.ts +2 -0
  83. package/dist/components/exams/take/components/question-renderers/MoversGridFillRenderer.js +1 -0
  84. package/dist/components/exams/take/components/question-renderers/MoversLabelThePictureRenderer.d.ts +2 -0
  85. package/dist/components/exams/take/components/question-renderers/MoversLabelThePictureRenderer.js +1 -0
  86. package/dist/components/exams/take/components/question-renderers/MoversListenAndWriteRenderer.d.ts +21 -0
  87. package/dist/components/exams/take/components/question-renderers/MoversListenAndWriteRenderer.js +1 -0
  88. package/dist/components/exams/take/components/question-renderers/MoversMatchByWritingAnswerRenderer.d.ts +19 -0
  89. package/dist/components/exams/take/components/question-renderers/MoversMatchByWritingAnswerRenderer.js +1 -0
  90. package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.d.ts +2 -0
  91. package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.js +1 -0
  92. package/dist/components/exams/take/components/question-renderers/MoversPictureFillBlankChooseRenderer.d.ts +2 -0
  93. package/dist/components/exams/take/components/question-renderers/MoversPictureFillBlankChooseRenderer.js +1 -0
  94. package/dist/components/exams/take/components/question-renderers/MoversReadingPassageRenderer.d.ts +2 -0
  95. package/dist/components/exams/take/components/question-renderers/MoversReadingPassageRenderer.js +1 -0
  96. package/dist/components/exams/take/components/question-renderers/MoversTrueFalseRenderer.d.ts +20 -0
  97. package/dist/components/exams/take/components/question-renderers/MoversTrueFalseRenderer.js +1 -0
  98. package/dist/components/exams/take/components/question-renderers/MoversWordFillParagraphRenderer.d.ts +32 -0
  99. package/dist/components/exams/take/components/question-renderers/MoversWordFillParagraphRenderer.js +1 -0
  100. package/dist/components/exams/take/components/question-renderers/MoversWordFillStructuredFormRenderer.d.ts +18 -0
  101. package/dist/components/exams/take/components/question-renderers/MoversWordFillStructuredFormRenderer.js +1 -0
  102. package/dist/components/exams/take/components/question-renderers/MoversWordOrderingRenderer.d.ts +19 -0
  103. package/dist/components/exams/take/components/question-renderers/MoversWordOrderingRenderer.js +1 -0
  104. package/dist/components/exams/take/components/question-renderers/MoversWriteSentencesRenderer.d.ts +22 -0
  105. package/dist/components/exams/take/components/question-renderers/MoversWriteSentencesRenderer.js +1 -0
  106. package/dist/components/exams/take/components/question-renderers/MoversWritingRenderer.d.ts +18 -0
  107. package/dist/components/exams/take/components/question-renderers/MoversWritingRenderer.js +1 -0
  108. package/dist/components/exams/take/components/question-renderers/index.d.ts +19 -0
  109. package/dist/components/exams/take/components/question-renderers/index.js +1 -0
  110. package/dist/components/exams/take/components/speaking/FindObjectInPicture.module.css +133 -0
  111. package/dist/components/exams/take/components/speaking/SpeakingAudioPlayer.d.ts +9 -0
  112. package/dist/components/exams/take/components/speaking/SpeakingAudioPlayer.js +1 -0
  113. package/dist/components/exams/take/components/speaking/SpeakingCompareImages.module.css +153 -0
  114. package/dist/components/exams/take/components/speaking/SpeakingContentFrame.d.ts +7 -0
  115. package/dist/components/exams/take/components/speaking/SpeakingContentFrame.js +1 -0
  116. package/dist/components/exams/take/components/speaking/SpeakingDragObjects.module.css +238 -0
  117. package/dist/components/exams/take/components/speaking/SpeakingExamLayout.d.ts +27 -0
  118. package/dist/components/exams/take/components/speaking/SpeakingExamLayout.js +1 -0
  119. package/dist/components/exams/take/components/speaking/SpeakingHeader.d.ts +6 -0
  120. package/dist/components/exams/take/components/speaking/SpeakingHeader.js +1 -0
  121. package/dist/components/exams/take/components/speaking/SpeakingInfoExchange.module.css +193 -0
  122. package/dist/components/exams/take/components/speaking/SpeakingInfoExchangeSummerSky.module.css +238 -0
  123. package/dist/components/exams/take/components/speaking/SpeakingInstructionBar.d.ts +5 -0
  124. package/dist/components/exams/take/components/speaking/SpeakingInstructionBar.js +1 -0
  125. package/dist/components/exams/take/components/speaking/SpeakingMicButton.d.ts +14 -0
  126. package/dist/components/exams/take/components/speaking/SpeakingMicButton.js +1 -0
  127. package/dist/components/exams/take/components/speaking/SpeakingNavButton.d.ts +7 -0
  128. package/dist/components/exams/take/components/speaking/SpeakingNavButton.js +1 -0
  129. package/dist/components/exams/take/components/speaking/SpeakingPartBadge.d.ts +6 -0
  130. package/dist/components/exams/take/components/speaking/SpeakingPartBadge.js +1 -0
  131. package/dist/components/exams/take/components/speaking/SpeakingReadDisplayedContent.module.css +115 -0
  132. package/dist/components/exams/take/components/speaking/SpeakingRecordingPreview.d.ts +11 -0
  133. package/dist/components/exams/take/components/speaking/SpeakingRecordingPreview.js +1 -0
  134. package/dist/components/exams/take/components/speaking/SpeakingReportErrorButton.d.ts +1 -0
  135. package/dist/components/exams/take/components/speaking/SpeakingReportErrorButton.js +1 -0
  136. package/dist/components/exams/take/components/speaking/SpeakingStoryImages.module.css +231 -0
  137. package/dist/components/exams/take/components/speaking/SpeakingVideoIntro.d.ts +13 -0
  138. package/dist/components/exams/take/components/speaking/SpeakingVideoIntro.js +1 -0
  139. package/dist/components/exams/take/components/speaking/index.d.ts +1 -0
  140. package/dist/components/exams/take/components/speaking/index.js +1 -0
  141. package/dist/components/exams/take/components/speaking/renderers/SpeakingChooseAnswerGroupRenderer.d.ts +16 -0
  142. package/dist/components/exams/take/components/speaking/renderers/SpeakingChooseAnswerGroupRenderer.js +1 -0
  143. package/dist/components/exams/take/components/speaking/renderers/SpeakingChooseObjectsRenderer.d.ts +16 -0
  144. package/dist/components/exams/take/components/speaking/renderers/SpeakingChooseObjectsRenderer.js +1 -0
  145. package/dist/components/exams/take/components/speaking/renderers/SpeakingCompareImagesRenderer.d.ts +17 -0
  146. package/dist/components/exams/take/components/speaking/renderers/SpeakingCompareImagesRenderer.js +1 -0
  147. package/dist/components/exams/take/components/speaking/renderers/SpeakingConversationRenderer.d.ts +16 -0
  148. package/dist/components/exams/take/components/speaking/renderers/SpeakingConversationRenderer.js +1 -0
  149. package/dist/components/exams/take/components/speaking/renderers/SpeakingCueCardRenderer.d.ts +17 -0
  150. package/dist/components/exams/take/components/speaking/renderers/SpeakingCueCardRenderer.js +1 -0
  151. package/dist/components/exams/take/components/speaking/renderers/SpeakingDescribeImageRenderer.d.ts +18 -0
  152. package/dist/components/exams/take/components/speaking/renderers/SpeakingDescribeImageRenderer.js +1 -0
  153. package/dist/components/exams/take/components/speaking/renderers/SpeakingDragObjectsRenderer.d.ts +16 -0
  154. package/dist/components/exams/take/components/speaking/renderers/SpeakingDragObjectsRenderer.js +1 -0
  155. package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.d.ts +16 -0
  156. package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.js +1 -0
  157. package/dist/components/exams/take/components/speaking/renderers/SpeakingInfoExchangeRenderer.d.ts +16 -0
  158. package/dist/components/exams/take/components/speaking/renderers/SpeakingInfoExchangeRenderer.js +1 -0
  159. package/dist/components/exams/take/components/speaking/renderers/SpeakingListenAndSpeakAnswerRenderer.d.ts +16 -0
  160. package/dist/components/exams/take/components/speaking/renderers/SpeakingListenAndSpeakAnswerRenderer.js +1 -0
  161. package/dist/components/exams/take/components/speaking/renderers/SpeakingOddOneOutRenderer.d.ts +16 -0
  162. package/dist/components/exams/take/components/speaking/renderers/SpeakingOddOneOutRenderer.js +1 -0
  163. package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionListRenderer.d.ts +26 -0
  164. package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionListRenderer.js +1 -0
  165. package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionRenderer.d.ts +32 -0
  166. package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionRenderer.js +1 -0
  167. package/dist/components/exams/take/components/speaking/renderers/SpeakingReadDisplayedContentRenderer.d.ts +34 -0
  168. package/dist/components/exams/take/components/speaking/renderers/SpeakingReadDisplayedContentRenderer.js +1 -0
  169. package/dist/components/exams/take/components/speaking/renderers/SpeakingSpontaneousQARenderer.d.ts +17 -0
  170. package/dist/components/exams/take/components/speaking/renderers/SpeakingSpontaneousQARenderer.js +1 -0
  171. package/dist/components/exams/take/components/speaking/renderers/SpeakingStoryImagesRenderer.d.ts +17 -0
  172. package/dist/components/exams/take/components/speaking/renderers/SpeakingStoryImagesRenderer.js +1 -0
  173. package/dist/components/exams/take/components/speaking/renderers/index.d.ts +13 -0
  174. package/dist/components/exams/take/components/speaking/renderers/index.js +1 -0
  175. package/dist/components/exams/take/hooks/index.d.ts +2 -0
  176. package/dist/components/exams/take/hooks/index.js +1 -0
  177. package/dist/components/exams/take/hooks/useAnswerAutosave.d.ts +27 -0
  178. package/dist/components/exams/take/hooks/useAnswerAutosave.js +1 -0
  179. package/dist/components/exams/take/hooks/useAttemptSubmit.d.ts +29 -0
  180. package/dist/components/exams/take/hooks/useAttemptSubmit.js +1 -0
  181. package/dist/components/exams/take/hooks/useAutoSave.d.ts +20 -0
  182. package/dist/components/exams/take/hooks/useAutoSave.js +1 -0
  183. package/dist/components/exams/take/hooks/useExamCountdown.d.ts +22 -0
  184. package/dist/components/exams/take/hooks/useExamCountdown.js +1 -0
  185. package/dist/components/exams/take/hooks/useExamTimer.d.ts +21 -0
  186. package/dist/components/exams/take/hooks/useExamTimer.js +1 -0
  187. package/dist/components/exams/take/hooks/useProctoringCamera.d.ts +19 -0
  188. package/dist/components/exams/take/hooks/useProctoringCamera.js +1 -0
  189. package/dist/components/exams/take/hooks/useProctoringSnapshot.d.ts +25 -0
  190. package/dist/components/exams/take/hooks/useProctoringSnapshot.js +1 -0
  191. package/dist/components/exams/take/hooks/useSpeakingConversationAudio.d.ts +19 -0
  192. package/dist/components/exams/take/hooks/useSpeakingConversationAudio.js +1 -0
  193. package/dist/components/exams/take/hooks/useSpeakingNavigation.d.ts +37 -0
  194. package/dist/components/exams/take/hooks/useSpeakingNavigation.js +1 -0
  195. package/dist/components/exams/take/hooks/useSpeakingRecorder.d.ts +45 -5
  196. package/dist/components/exams/take/hooks/useSpeakingRecorder.js +1 -1
  197. package/dist/components/exams/take/hooks/useSpeakingRendererSetup.d.ts +39 -0
  198. package/dist/components/exams/take/hooks/useSpeakingRendererSetup.js +1 -0
  199. package/dist/components/exams/take/hooks/useToeicAudio.d.ts +17 -0
  200. package/dist/components/exams/take/hooks/useToeicAudio.js +1 -0
  201. package/dist/components/exams/take/index.d.ts +9 -1
  202. package/dist/components/exams/take/index.js +1 -1
  203. package/dist/components/exams/take/types/speaking-cue-card.types.d.ts +10 -0
  204. package/dist/components/exams/take/types/speaking-cue-card.types.js +1 -0
  205. package/dist/components/exams/take/types/speaking.types.d.ts +153 -0
  206. package/dist/components/exams/take/types/speaking.types.js +1 -0
  207. package/dist/components/exams/take/types.d.ts +286 -1
  208. package/dist/components/exams/take/utils/answer-transformers.d.ts +26 -0
  209. package/dist/components/exams/take/utils/answer-transformers.js +1 -0
  210. package/dist/components/exams/take/utils/coloring-assignments.d.ts +5 -0
  211. package/dist/components/exams/take/utils/coloring-assignments.js +1 -0
  212. package/dist/components/exams/take/utils/question-transformers.d.ts +362 -0
  213. package/dist/components/exams/take/utils/question-transformers.js +1 -0
  214. package/dist/components/features/exam-entry/entry-state-cards.d.ts +19 -0
  215. package/dist/components/features/exam-entry/entry-state-cards.js +1 -0
  216. package/dist/components/features/exam-entry/index.d.ts +2 -0
  217. package/dist/components/features/exam-entry/index.js +1 -0
  218. package/dist/components/features/exam-entry/themed/CambridgeYleWaitingRoom.d.ts +12 -0
  219. package/dist/components/features/exam-entry/themed/CambridgeYleWaitingRoom.js +1 -0
  220. package/dist/components/features/exam-entry/themed/ThemedWaitingRoom.d.ts +14 -0
  221. package/dist/components/features/exam-entry/themed/ThemedWaitingRoom.js +1 -0
  222. package/dist/components/features/exam-entry/themed/ToeicWaitingRoom.d.ts +16 -0
  223. package/dist/components/features/exam-entry/themed/ToeicWaitingRoom.js +1 -0
  224. package/dist/components/features/exam-entry/themed/components/CloseButton.d.ts +9 -0
  225. package/dist/components/features/exam-entry/themed/components/CloseButton.js +1 -0
  226. package/dist/components/features/exam-entry/themed/components/ExamInfoPanel.d.ts +13 -0
  227. package/dist/components/features/exam-entry/themed/components/ExamInfoPanel.js +1 -0
  228. package/dist/components/features/exam-entry/themed/components/ModeSelector.d.ts +7 -0
  229. package/dist/components/features/exam-entry/themed/components/ModeSelector.js +1 -0
  230. package/dist/components/features/exam-entry/themed/components/StartButton.d.ts +11 -0
  231. package/dist/components/features/exam-entry/themed/components/StartButton.js +1 -0
  232. package/dist/components/features/exam-entry/themed/components/index.d.ts +4 -0
  233. package/dist/components/features/exam-entry/themed/components/index.js +1 -0
  234. package/dist/components/features/exam-entry/themed/index.d.ts +1 -0
  235. package/dist/components/features/exam-entry/themed/index.js +1 -0
  236. package/dist/components/results/ResultReviewPageContainer.d.ts +3 -1
  237. package/dist/components/results/ResultReviewPageContainer.js +1 -1
  238. package/dist/components/results/hooks/useResultReviewPolling.d.ts +27 -0
  239. package/dist/components/results/hooks/useResultReviewPolling.js +1 -0
  240. package/dist/components/results/index.d.ts +3 -0
  241. package/dist/components/results/index.js +1 -1
  242. package/dist/components/results/review-status.utils.d.ts +12 -0
  243. package/dist/components/results/review-status.utils.js +1 -0
  244. package/dist/components/shared/audio/SpeakingAudioPlayer.d.ts +12 -0
  245. package/dist/components/shared/audio/SpeakingAudioPlayer.js +1 -0
  246. package/dist/components/shared/audio/SpeakingAudioPlayer.module.css +115 -0
  247. package/dist/components/shared/audio/assets/fr-image-part1-border.png +0 -0
  248. package/dist/components/shared/audio/assets/ic-speaker.webp +0 -0
  249. package/dist/components/themes/cambridge-yle/CambridgeYleTimer.js +1 -1
  250. package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.d.ts +11 -3
  251. package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.js +1 -1
  252. package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.d.ts +5 -1
  253. package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.js +1 -1
  254. package/dist/components/themes/index.d.ts +11 -0
  255. package/dist/components/themes/index.js +1 -1
  256. package/dist/components/themes/spaceship/SpaceshipAudioPlayer.d.ts +8 -0
  257. package/dist/components/themes/spaceship/SpaceshipAudioPlayer.js +1 -0
  258. package/dist/components/themes/spaceship/SpaceshipLayout.d.ts +2 -0
  259. package/dist/components/themes/spaceship/SpaceshipLayout.js +1 -0
  260. package/dist/components/themes/spaceship/assets/images/X-sunset.png +0 -0
  261. package/dist/components/themes/spaceship/assets/images/X.webp +0 -0
  262. package/dist/components/themes/spaceship/assets/images/audio-bar.webp +0 -0
  263. package/dist/components/themes/spaceship/assets/images/background.webp +0 -0
  264. package/dist/components/themes/spaceship/assets/images/bg_sunset.png +0 -0
  265. package/dist/components/themes/spaceship/assets/images/big-blue-badge-sunset.png +0 -0
  266. package/dist/components/themes/spaceship/assets/images/big-blue-badge.png +0 -0
  267. package/dist/components/themes/spaceship/assets/images/big-blue-badge.webp +0 -0
  268. package/dist/components/themes/spaceship/assets/images/bingo.webp +0 -0
  269. package/dist/components/themes/spaceship/assets/images/blue-badge-sunset.png +0 -0
  270. package/dist/components/themes/spaceship/assets/images/blue-badge.png +0 -0
  271. package/dist/components/themes/spaceship/assets/images/blue-wrapper-sunset.png +0 -0
  272. package/dist/components/themes/spaceship/assets/images/blue-wrapper.webp +0 -0
  273. package/dist/components/themes/spaceship/assets/images/board_red.png +0 -0
  274. package/dist/components/themes/spaceship/assets/images/button_feedback_vi.png +0 -0
  275. package/dist/components/themes/spaceship/assets/images/circle-btn-right-sunset.png +0 -0
  276. package/dist/components/themes/spaceship/assets/images/circle-btn-right.png +0 -0
  277. package/dist/components/themes/spaceship/assets/images/circle-btn-sunset.png +0 -0
  278. package/dist/components/themes/spaceship/assets/images/circle-btn.webp +0 -0
  279. package/dist/components/themes/spaceship/assets/images/focus-vector.webp +0 -0
  280. package/dist/components/themes/spaceship/assets/images/head-phone_red.png +0 -0
  281. package/dist/components/themes/spaceship/assets/images/ic-mute-sunset.png +0 -0
  282. package/dist/components/themes/spaceship/assets/images/ic-mute.png +0 -0
  283. package/dist/components/themes/spaceship/assets/images/ic-play-sunset.png +0 -0
  284. package/dist/components/themes/spaceship/assets/images/ic-play-violet.webp +0 -0
  285. package/dist/components/themes/spaceship/assets/images/ic-play.webp +0 -0
  286. package/dist/components/themes/spaceship/assets/images/micro.png +0 -0
  287. package/dist/components/themes/spaceship/assets/images/next-small.webp +0 -0
  288. package/dist/components/themes/spaceship/assets/images/next-sunset.png +0 -0
  289. package/dist/components/themes/spaceship/assets/images/next.webp +0 -0
  290. package/dist/components/themes/spaceship/assets/images/purple-circle.webp +0 -0
  291. package/dist/components/themes/spaceship/assets/images/question-badge-sunset.png +0 -0
  292. package/dist/components/themes/spaceship/assets/images/question-badge.webp +0 -0
  293. package/dist/components/themes/spaceship/assets/images/red-badge-sunset.png +0 -0
  294. package/dist/components/themes/spaceship/assets/images/red-badge.webp +0 -0
  295. package/dist/components/themes/spaceship/assets/images/red-wrapper-sunset.png +0 -0
  296. package/dist/components/themes/spaceship/assets/images/red-wrapper.webp +0 -0
  297. package/dist/components/themes/spaceship/assets/images/robot-avatar.png +0 -0
  298. package/dist/components/themes/spaceship/assets/images/sound_red.png +0 -0
  299. package/dist/components/themes/spaceship/assets/images/student-avatar.png +0 -0
  300. package/dist/components/themes/spaceship/assets/images/teacher-play-sunset.png +0 -0
  301. package/dist/components/themes/spaceship/assets/images/teacher-play.webp +0 -0
  302. package/dist/components/themes/spaceship/assets/images/timer-badge.png +0 -0
  303. package/dist/components/themes/spaceship/assets/styles/SpaceshipLayout.module.css +347 -0
  304. package/dist/components/themes/spaceship/assets/styles/SpaceshipWaitingRoom.module.css +395 -0
  305. package/dist/components/ui/badge.d.ts +1 -1
  306. package/dist/index.d.ts +4 -4
  307. package/dist/index.js +1 -1
  308. package/dist/shared/index.d.ts +3 -0
  309. package/dist/shared/index.js +1 -1
  310. package/dist/shared/lib/stores/examTakingStore.d.ts +54 -0
  311. package/dist/shared/lib/stores/examTakingStore.js +1 -0
  312. package/dist/shared/lib/stores/gradingTransientStore.d.ts +23 -0
  313. package/dist/shared/lib/stores/gradingTransientStore.js +1 -0
  314. package/dist/shared/lib/themes/config/starters.config.d.ts +3 -0
  315. package/dist/shared/lib/themes/config/starters.config.js +1 -0
  316. package/dist/shared/lib/themes/index.d.ts +18 -0
  317. package/dist/shared/lib/themes/index.js +1 -0
  318. package/dist/shared/lib/themes/types.d.ts +39 -0
  319. package/dist/shared/lib/themes/types.js +1 -0
  320. package/dist/shared/lib/toast.d.ts +19 -0
  321. package/dist/shared/lib/toast.js +1 -0
  322. package/dist/shared/lib/utils/question-reverse-transform.d.ts +6 -0
  323. package/dist/shared/lib/utils/question-reverse-transform.js +1 -0
  324. package/dist/shared/lib/utils/question-transform-validation.d.ts +6 -0
  325. package/dist/shared/lib/utils/question-transform-validation.js +1 -0
  326. package/dist/shared/lib/utils/question-transform.d.ts +6 -2
  327. package/dist/shared/lib/utils/question-transform.js +1 -1
  328. package/package.json +19 -4
@@ -1 +1 @@
1
- "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{CheckCircle2 as s,Circle as r,Layers as a,Lock as l,Play as n}from"lucide-react";import{cn as d}from"../../shared/lib/utils";export function ExamPartTabs({template:s,currentPartId:r,completedQuestions:l,onPartChange:n}){const i=e=>{const t=e.endIndex-e.startIndex+1;let s=0;for(let t=e.startIndex;t<=e.endIndex;t++)l.has(t)&&s++;return{completed:s,total:t,percentage:t>0?Math.round(s/t*100):0}},o=e=>{const t=i(e);return 0===t.completed?"not-started":t.completed===t.total?"completed":"in-progress"};return t("div",{className:"space-y-4",children:[t("div",{className:"flex items-center gap-2 px-1",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg bg-blue-600 shadow-sm",children:e(a,{className:"h-4 w-4 text-white"})}),t("div",{children:[e("h3",{className:"text-sm font-bold text-gray-900",children:"Các phần thi"}),t("p",{className:"text-xs text-gray-500",children:[s.parts.length," phần"]})]})]}),e("div",{className:"space-y-3",children:s.parts.map((a,l)=>{const m=i(a),h=o(a),p=a.id===r,u=(x=l,s.parts.slice(0,x).every(e=>"completed"===o(e)));var x;return e("button",{onClick:()=>u&&n(a.id),disabled:!u,className:d("w-full rounded-xl border-2 p-4 text-left transition-all",p&&u?"border-transparent bg-blue-600 text-white shadow-lg":"border-gray-200 bg-white hover:border-gray-300",!u&&"cursor-not-allowed opacity-60"),children:t("div",{className:"flex items-start gap-3",children:[e(c,{status:h,active:p,unlocked:u}),t("div",{className:"min-w-0 flex-1",children:[t("div",{className:"flex items-center justify-between gap-2",children:[e("span",{className:"truncate text-sm font-semibold",children:a.name}),t("span",{className:"rounded-full bg-black/10 px-2 py-0.5 text-xs font-bold",children:[m.completed,"/",m.total]})]}),a.description&&e("p",{className:d("mt-1 text-xs",p?"text-blue-100":"text-gray-500"),children:a.description}),e("div",{className:d("mt-2 h-1.5 overflow-hidden rounded-full",p?"bg-white/20":"bg-gray-200"),children:e("div",{className:d("h-full rounded-full",p?"bg-white":"bg-green-500"),style:{width:`${m.percentage}%`}})})]})]})},a.id)})})]})}function c({status:t,active:a,unlocked:c}){const i=d("h-4 w-4",a?"text-white":"text-gray-500");return e(c?"completed"===t?s:"in-progress"===t?n:r:l,{className:i})}
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{CheckCircle2 as r,ChevronRight as a,Circle as s,Layers as n,Lock as l,Play as i}from"lucide-react";import{cn as o}from"../../shared/lib/utils";export function ExamPartTabs({template:d,currentPartId:c,completedQuestions:m,onPartChange:g}){const h=e=>{let t=0;for(let r=e.startIndex;r<=e.endIndex;r++)m.has(r)&&t++;const r=e.endIndex-e.startIndex+1;return{completed:t,total:r,percentage:r>0?Math.round(t/r*100):0}},b=e=>{const t=h(e);return 0===t.completed?"not-started":t.completed===t.total?"completed":"in-progress"},x=e=>{if(0===e)return!0;for(let t=0;t<e;t++){const e=d.parts[t];if("completed"!==b(e))return!1}return!0},p=(t,a,n)=>n?"completed"===t?e(r,{className:o("h-4 w-4",a?"text-white":"text-green-500")}):"in-progress"===t?e(i,{className:o("h-4 w-4",a?"text-white":"text-amber-500")}):e(s,{className:o("h-4 w-4",a?"text-white/70":"text-gray-400")}):e(l,{className:"h-4 w-4 text-gray-400"}),u=(r,s,n,i,d,c)=>t("button",{onClick:()=>c&&g(r.id),disabled:!c,className:o("group relative w-full rounded-xl border-2 p-4 text-left transition-all duration-200","focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none",{"border-transparent bg-gradient-to-r from-blue-500 to-indigo-600 text-white shadow-lg shadow-blue-200/50":d&&c,"border-green-200 bg-gradient-to-r from-green-50 to-emerald-50 hover:border-green-300 hover:shadow-md":!d&&"completed"===i&&c,"border-amber-200 bg-gradient-to-r from-amber-50 to-orange-50 hover:border-amber-300 hover:shadow-md":!d&&"in-progress"===i&&c,"border-gray-200 bg-white hover:border-gray-300 hover:shadow-md":!d&&"not-started"===i&&c,"cursor-not-allowed border-gray-100 bg-gray-50/50 opacity-60":!c}),children:[d&&e("div",{className:"absolute -left-[2px] top-1/2 h-8 w-1 -translate-y-1/2 rounded-full bg-white shadow-sm"}),t("div",{className:"flex items-start gap-3",children:[e("div",{className:o("flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg transition-colors",d?"bg-white/20":"completed"===i?"bg-green-100":"in-progress"===i?"bg-amber-100":"bg-gray-100"),children:p(i,d,c)}),t("div",{className:"flex-1 min-w-0",children:[t("div",{className:"flex items-center justify-between gap-2",children:[e("span",{className:o("text-sm font-semibold truncate",d?"text-white":"completed"===i?"text-green-800":"in-progress"===i?"text-amber-800":"text-gray-800"),children:r.name}),t("span",{className:o("flex-shrink-0 rounded-full px-2 py-0.5 text-xs font-bold",d?"bg-white/20 text-white":"completed"===i?"bg-green-100 text-green-700":"in-progress"===i?"bg-amber-100 text-amber-700":"bg-gray-100 text-gray-600"),children:[n.completed,"/",n.total]})]}),r.description&&e("p",{className:o("mt-1 text-xs leading-relaxed",d?"text-blue-100":"text-gray-500"),children:r.description}),t("div",{className:"mt-2",children:[e("div",{className:o("h-1.5 overflow-hidden rounded-full",d?"bg-white/20":"bg-gray-200"),children:e("div",{className:o("h-full rounded-full transition-all duration-500 ease-out",d?"bg-white":"completed"===i?"bg-gradient-to-r from-green-400 to-emerald-500":"in-progress"===i?"bg-gradient-to-r from-amber-400 to-orange-500":"bg-gray-300"),style:{width:`${n.percentage}%`}})}),t("div",{className:o("mt-1 flex items-center justify-between text-xs",d?"text-blue-100":"text-gray-500"),children:[t("span",{children:[r.questionCount," câu hỏi"]}),t("span",{className:"font-medium",children:[n.percentage,"%"]})]})]}),r.isGroup&&r.groups&&r.groups.length>0&&t("div",{className:o("mt-2 rounded-lg p-2 text-xs",d?"bg-white/10":"bg-gray-100/80"),children:[t("div",{className:o("mb-1 font-medium",d?"text-blue-100":"text-gray-600"),children:[r.groups.length," nhóm câu hỏi:"]}),e("div",{className:"space-y-0.5",children:r.groups.map((r,s)=>t("div",{className:o("flex items-center justify-between",d?"text-blue-100":"text-gray-500"),children:[t("span",{className:"flex items-center gap-1",children:[e(a,{className:"h-3 w-3"}),"Nhóm ",r.number]}),t("span",{className:"font-medium",children:[r.questionCount," câu"]})]},s))})]}),r.sections&&r.sections.length>0&&t("div",{className:o("mt-2 rounded-lg p-2 text-xs",d?"bg-white/10":"bg-gray-100/80"),children:[t("div",{className:o("mb-1 font-medium",d?"text-blue-100":"text-gray-600"),children:[r.sections.length," phần nhỏ:"]}),e("div",{className:"space-y-1",children:r.sections.map((r,s)=>{let n=0;for(let e=r.startIndex;e<=r.endIndex;e++)m.has(e)&&n++;const l=r.endIndex-r.startIndex+1;return t("div",{className:o("flex items-center justify-between",d?"text-blue-100":"text-gray-500"),children:[t("span",{className:"flex items-center gap-1 truncate flex-1 mr-2",children:[e(a,{className:"h-3 w-3 flex-shrink-0"}),e("span",{className:"truncate",children:r.name})]}),t("span",{className:"font-medium flex-shrink-0",children:[n,"/",l]})]},s)})})]}),!c&&t("div",{className:"mt-2 flex items-center gap-1 text-xs text-gray-400",children:[e(l,{className:"h-3 w-3"}),e("span",{children:"Hoàn thành phần trước để mở khóa"})]})]})]})]},r.id);return t("div",{className:"space-y-4",children:[t("div",{className:"flex items-center gap-2 px-1",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg bg-blue-600 shadow-sm",children:e(n,{className:"h-4 w-4 text-white"})}),t("div",{children:[e("h3",{className:"text-sm font-bold text-gray-900",children:"Các phần thi"}),t("p",{className:"text-xs text-gray-500",children:[d.parts.length," phần"]})]})]}),e("div",{className:"space-y-4",children:(()=>{const r=[];d.parts.forEach((e,t)=>{const a=e.category||"",s=r[r.length-1];s&&s.groupKey===a?s.parts.push({part:e,originalIndex:t}):r.push({groupKey:a,parts:[{part:e,originalIndex:t}]})});const a={LISTENING:"🎧 LISTENING",READING_AND_WRITING:"📝 READING AND WRITING",READING:"📖 READING",WRITING:"✍️ WRITING",SPEAKING:"🗣️ SPEAKING"},s={LISTENING:{bg:"bg-gradient-to-r from-pink-100 via-rose-100 to-pink-50",border:"border-pink-300",text:"text-pink-900",connector:"bg-pink-300"},READING_AND_WRITING:{bg:"bg-gradient-to-r from-teal-100 via-cyan-100 to-teal-50",border:"border-teal-300",text:"text-teal-900",connector:"bg-teal-300"},READING:{bg:"bg-gradient-to-r from-amber-100 via-yellow-100 to-amber-50",border:"border-amber-300",text:"text-amber-900",connector:"bg-amber-300"},WRITING:{bg:"bg-gradient-to-r from-indigo-100 via-blue-100 to-indigo-50",border:"border-indigo-300",text:"text-indigo-900",connector:"bg-indigo-300"},SPEAKING:{bg:"bg-gradient-to-r from-violet-100 via-purple-100 to-violet-50",border:"border-violet-300",text:"text-violet-900",connector:"bg-violet-300"}};return r.length<=1&&!d.parts.some(e=>e.category)?d.parts.map((e,t)=>{const r=h(e),a=b(e),s=c===e.id,n=x(t);return u(e,0,r,a,s,n)}):r.map((r,n)=>{const l=s[r.groupKey]||{bg:"bg-gray-50",border:"border-gray-200",text:"text-gray-800",connector:"bg-gray-300"},i=a[r.groupKey]||r.groupKey;return t("div",{className:"space-y-2",children:[e("div",{className:o("rounded-lg border-2 px-3 py-2 text-center",l.bg,l.border),children:e("span",{className:o("text-xs font-bold tracking-wide uppercase",l.text),children:i})}),e("div",{className:"relative ml-3 pl-5",children:e("div",{className:"space-y-3",children:r.parts.map(({part:a,originalIndex:s},n)=>{const i=h(a),d=b(a),m=c===a.id,g=x(s),p=0===n,N=n===r.parts.length-1;return t("div",{className:"relative",children:[!p&&e("div",{className:o("absolute -left-5 top-0 h-1/2 w-[2px]",l.connector)}),!N&&e("div",{className:o("absolute -left-5 top-1/2 w-[2px]",l.connector),style:{height:"calc(50% + 12px)"}}),p&&e("div",{className:o("absolute -left-5 -top-2 h-[calc(50%+8px)] w-[2px]",l.connector)}),e("div",{className:o("absolute -left-5 top-1/2 -translate-y-1/2 h-[2px] w-5",l.connector)}),u(a,0,i,d,m,g)]},a.id)})})})]},`group-${n}`)})})()}),t("div",{className:"rounded-xl border border-gray-200 bg-gradient-to-br from-gray-50 to-slate-100 p-4 shadow-sm",children:[e("h4",{className:"mb-3 text-xs font-bold uppercase tracking-wide text-gray-500",children:"Tổng quan"}),t("div",{className:"space-y-2",children:[t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-2",children:[e("div",{className:"h-2 w-2 rounded-full bg-blue-500"}),e("span",{className:"text-sm text-gray-600",children:"Tổng câu hỏi"})]}),e("span",{className:"text-sm font-bold text-gray-900",children:d.totalQuestions})]}),t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-2",children:[e("div",{className:"h-2 w-2 rounded-full bg-green-500"}),e("span",{className:"text-sm text-gray-600",children:"Đã hoàn thành"})]}),e("span",{className:"text-sm font-bold text-green-600",children:m.size})]}),t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-2",children:[e("div",{className:"h-2 w-2 rounded-full bg-amber-500"}),e("span",{className:"text-sm text-gray-600",children:"Còn lại"})]}),e("span",{className:"text-sm font-bold text-amber-600",children:d.totalQuestions-m.size})]})]}),t("div",{className:"mt-3 pt-3 border-t border-gray-200",children:[t("div",{className:"flex items-center justify-between mb-1",children:[e("span",{className:"text-xs text-gray-500",children:"Tiến độ tổng"}),t("span",{className:"text-xs font-bold text-blue-600",children:[Math.round(m.size/d.totalQuestions*100),"%"]})]}),e("div",{className:"h-2 overflow-hidden rounded-full bg-gray-200",children:e("div",{className:"h-full rounded-full bg-gradient-to-r from-blue-500 to-indigo-600 transition-all duration-500",style:{width:m.size/d.totalQuestions*100+"%"}})})]})]})]})}
@@ -0,0 +1,20 @@
1
+ import type { ExamQuestionsSdkApi } from '../../api/exam-questions.types';
2
+ export interface ExamPreviewDialogProps {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ /** Exam (đề) to preview. `null` while nothing is selected. */
6
+ examId: string | null;
7
+ /** Questions API adapter — provides `getExam` + `getQuestions`. */
8
+ api: ExamQuestionsSdkApi;
9
+ /** Fallback heading shown until the exam name loads. */
10
+ title?: string;
11
+ }
12
+ /**
13
+ * Read-only popup that previews an exam (đề) by id: the layout mirrors the
14
+ * exam taking experience (left sidebar with exam info + progress + question
15
+ * navigation, right pane showing one question at a time) but creates NO attempt
16
+ * — exam + questions are fetched through {@link ExamQuestionsSdkApi}, so it
17
+ * matches exactly how the đề looks. Each question renders via {@link QuestionViewer}
18
+ * in review mode (correct answers highlighted).
19
+ */
20
+ export declare function ExamPreviewDialog({ open, onOpenChange, examId, api, title }: ExamPreviewDialogProps): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useEffect as a,useMemo as n,useRef as i,useState as s}from"react";import{ArrowLeft as r,ArrowRight as l,Eye as d,Loader2 as m}from"lucide-react";import{Badge as c}from"../../components/ui/badge";import{Button as o}from"../../components/ui/button";import{Card as p,CardContent as x,CardHeader as u,CardTitle as h}from"../../components/ui/card";import{Dialog as g,DialogContent as f,DialogHeader as N,DialogTitle as v}from"../../components/ui/dialog";import{Progress as b}from"../../components/ui/progress";import{useT as y}from"../../shared/lib/i18n";import{QuestionViewer as w}from"../questions/viewer/QuestionViewer";function j(e){return e&&"object"==typeof e&&"data"in e?e.data:e}function I(e){switch(e?.toUpperCase()){case"EASY":return"bg-green-100 text-green-800";case"MEDIUM":return"bg-orange-100 text-orange-800";case"HARD":return"bg-red-100 text-red-800";default:return"bg-gray-100 text-gray-800"}}export function ExamPreviewDialog({open:q,onOpenChange:S,examId:C,api:Q,title:k}){const E=y(),[P,z]=s(null),[A,M]=s([]),[$,D]=s(0),[T,O]=s("all"),[R,U]=s(!1),[H,V]=s(!1),[Y,B]=s(null),[F,G]=s(0),J=i(null);a(()=>{if(!q||!C)return;let e=!1;return V(!0),B(null),D(0),O("all"),(async()=>{try{const t=j(await Q.getExam(C)),a=Q.getQuestions?j(await Q.getQuestions(C)):[];if(e)return;const n=[...a].sort((e,t)=>(e.questionNumber??0)-(t.questionNumber??0));z(t),M(n)}catch{e||B(E("admin.exams.preview.loadError"))}finally{e||V(!1)}})(),()=>{e=!0}},[q,C,F,Q,E]),a(()=>{q||(z(null),M([]),D(0),O("all"),U(!1),B(null))},[q]);const K=n(()=>{const e=(P?.template?.parts??[]).map(e=>({id:e.id,name:e.name||e.title})),t=new Set(e.map(e=>e.id));for(const a of A){const n=a.partId??"";n&&!t.has(n)&&(t.add(n),e.push({id:n}))}return e},[P,A]),L=e=>{const t=K.findIndex(t=>t.id===(e??""));return t>=0?t+1:1},W=n(()=>{const e=new Map;for(const t of A){const a=t.partId??"";if(!e.has(a)){const t=K.findIndex(e=>e.id===a),n=t>=0?t+1:1;e.set(a,{partId:a,partNo:n,partName:K[t]?.name||`Part ${n}`,questions:[]})}e.get(a).questions.push(t)}return Array.from(e.values()).sort((e,t)=>e.partNo-t.partNo)},[A,K]),X=P?.name??k??"",Z=P?.template?.totalTime??P?.duration??0,_=function(e){return e.reduce((e,t)=>e+(Number(t.points)||0),0)}(A),ee=A[$],te=A.length>0?($+1)/A.length*100:0,ae=e=>{D(e),J.current?.scrollTo({top:0,behavior:"smooth"})},ne="all"===T?A:A.filter(e=>L(e.partId)===T);return e(g,{open:q,onOpenChange:S,children:t(f,{className:"max-h-[95vh] max-w-7xl overflow-y-auto",children:[e(N,{children:t(v,{className:"flex items-center gap-2",children:[e(d,{className:"h-5 w-5 text-blue-600"}),E("admin.exams.preview.title",{name:X})]})}),t("div",{className:"flex h-[80vh] flex-col gap-4 sm:flex-row",children:[e("div",{className:"w-full overflow-y-auto border-b border-gray-200 sm:w-80 sm:border-b-0 sm:border-r",children:t("div",{className:"space-y-4 p-4",children:[t(p,{children:[e(u,{className:"pb-3",children:e(h,{className:"text-base",children:E("admin.exams.preview.examInfo")})}),t(x,{className:"space-y-3",children:[t("div",{className:"flex items-center justify-between",children:[e("span",{className:"text-sm text-gray-600",children:E("admin.exams.preview.duration")}),e("span",{className:"text-sm font-medium",children:E("admin.exams.view.minutes",{count:String(Z)})})]}),t("div",{className:"flex items-center justify-between",children:[e("span",{className:"text-sm text-gray-600",children:E("admin.exams.preview.questionCount")}),e("span",{className:"text-sm font-medium",children:H?"...":E("admin.exams.preview.questions",{count:String(A.length)})})]}),t("div",{className:"flex items-center justify-between",children:[e("span",{className:"text-sm text-gray-600",children:E("admin.exams.preview.totalPoints")}),e("span",{className:"text-sm font-medium",children:E("admin.exams.preview.points",{count:String(_)})})]})]})]}),!H&&A.length>0&&e(p,{children:e(x,{className:"p-4",children:t("div",{className:"space-y-2",children:[t("div",{className:"flex justify-between text-sm",children:[e("span",{children:E("admin.exams.preview.progress")}),t("span",{className:"font-medium",children:[$+1,"/",A.length]})]}),e(b,{value:te,className:"h-2"})]})})}),H?e("div",{className:"flex items-center justify-center py-4",children:t("div",{className:"flex items-center gap-2 text-gray-500",children:[e(m,{className:"h-4 w-4 animate-spin"}),e("span",{className:"text-sm",children:E("admin.exams.preview.loading")})]})}):A.length>0&&t("div",{className:"space-y-3",children:[e("h4",{className:"text-sm font-medium text-gray-700",children:E("admin.exams.preview.questionNavigation")}),t("div",{className:"flex flex-wrap gap-2",children:[e(o,{variant:"all"===T?"default":"outline",size:"sm",onClick:()=>O("all"),className:"h-8 px-3 text-xs font-medium",children:E("admin.exams.preview.all")}),W.map(e=>t(o,{variant:T===e.partNo?"default":"outline",size:"sm",onClick:()=>{O(e.partNo);const t=e.questions[0];if(t){const e=A.findIndex(e=>e.id===t.id);-1!==e&&ae(e)}},className:"h-8 px-3 text-xs font-medium",children:["P",e.partNo]},e.partId||`part-${e.partNo}`))]}),e("div",{className:"rounded bg-gray-50 p-2 text-xs",children:e("span",{className:"font-medium",children:"all"===T?E("admin.exams.preview.allQuestions",{count:String(A.length)}):E("admin.exams.preview.partQuestions",{part:String(T),count:String(W.find(e=>e.partNo===T)?.questions.length??0)})})}),e("div",{className:"rounded-lg bg-gray-50 p-3",children:e("div",{className:"grid max-h-64 grid-cols-6 gap-2 overflow-y-auto pr-2",children:ne.map(t=>{const a=A.findIndex(e=>e.id===t.id);return e(o,{variant:"ghost",size:"sm",onClick:()=>ae(a),className:"h-8 w-full p-0 text-xs font-medium transition-all duration-200 "+(a===$?"border-2 border-blue-500 bg-blue-500 text-white hover:bg-blue-600":"border border-gray-300 bg-white hover:bg-gray-100"),children:t.questionNumber},t.id)})})})]}),!H&&A.length>0&&t("div",{children:[t("div",{className:"mb-2 flex items-center justify-between",children:[e("h4",{className:"text-sm font-medium text-gray-700",children:X}),e(o,{variant:"ghost",size:"sm",onClick:()=>U(e=>!e),className:"h-6 px-2 text-xs text-gray-600 hover:text-gray-900",children:E(R?"admin.exams.preview.collapse":"admin.exams.preview.detail")})]}),e("div",{className:"rounded-lg bg-gray-50 p-3",children:t("div",{className:"flex items-center justify-between",children:[t("div",{children:[t("span",{className:"text-sm font-medium text-gray-900",children:["Total: ",E("admin.exams.preview.questions",{count:String(A.length)})]}),e("div",{className:"mt-1 flex gap-3",children:t("span",{className:"text-xs text-gray-600",children:[W.length," part(s)"]})})]}),e("div",{className:"text-xs text-gray-500",children:E("admin.exams.view.minutes",{count:String(Z)})})]})}),R&&e("div",{className:"mt-3 space-y-2",children:W.map(a=>{const n=L(ee?.partId)===a.partNo;return e("div",{className:"cursor-pointer rounded-lg border-2 p-3 transition-all duration-200 "+(n?"border-blue-400 bg-blue-50 shadow-md":"border-gray-200 bg-white hover:border-gray-300"),onClick:()=>{const e=a.questions[0];if(e){const t=A.findIndex(t=>t.id===e.id);-1!==t&&(ae(t),O(a.partNo))}},children:t("div",{className:"flex items-center justify-between",children:[e("span",{className:"text-sm font-medium "+(n?"text-blue-900":"text-gray-900"),children:a.partName}),e(c,{variant:"secondary",className:"text-xs "+(n?"bg-blue-200 text-blue-800":"bg-gray-100 text-gray-700"),children:E("admin.exams.preview.questions",{count:String(a.questions.length)})})]})},a.partId||`part-${a.partNo}`)})})]})]})}),e("div",{className:"flex-1 overflow-y-auto",children:t("div",{className:"flex h-full flex-col",children:[t("div",{className:"flex items-center justify-between border-b pb-4",children:[t("div",{children:[e("h3",{className:"text-lg font-semibold",children:H?e("span",{className:"text-gray-400",children:E("admin.exams.preview.loading")}):A.length>0?E("admin.exams.preview.questionIndex",{index:String($+1),total:String(A.length)}):E("admin.exams.preview.noQuestions")}),ee&&e("div",{className:"mt-1 inline-flex items-center gap-2 rounded-lg border border-blue-200 bg-blue-50 px-3 py-1.5",children:e("span",{className:"text-sm font-semibold text-blue-700",children:(e=>{const t=K.findIndex(t=>t.id===(e??""));return K[t]?.name||`Part ${t>=0?t+1:1}`})(ee.partId)})})]}),ee&&t("div",{className:"flex items-center gap-2",children:[e(c,{variant:"outline",children:E("admin.exams.preview.points",{count:String(Number(ee.points)||1)})}),e(c,{className:I(ee.difficulty),children:ee.difficulty??"N/A"})]})]}),e("div",{ref:J,className:"flex-1 overflow-y-auto py-4",children:H?t("div",{className:"flex flex-col items-center justify-center space-y-4 py-12",children:[e(m,{className:"h-8 w-8 animate-spin text-blue-500"}),e("p",{className:"text-gray-600",children:E("admin.exams.preview.loadingQuestions")})]}):Y?e("div",{className:"flex flex-col items-center justify-center space-y-4 py-12",children:t("div",{className:"rounded-lg border border-red-200 bg-red-50 p-6 text-center",children:[e("p",{className:"font-medium text-red-800",children:Y}),e(o,{variant:"outline",onClick:()=>G(e=>e+1),className:"mt-4",children:E("admin.exams.preview.retry")})]})}):0===A.length?e("div",{className:"flex flex-col items-center justify-center space-y-4 py-12",children:e("div",{className:"rounded-lg border border-yellow-200 bg-yellow-50 p-6 text-center",children:e("p",{className:"font-medium text-yellow-800",children:E("admin.exams.preview.examNoQuestions")})})}):ee?e(w,{question:ee,isReviewMode:!0}):null}),!H&&A.length>0&&t("div",{className:"mt-auto flex items-center justify-between border-t border-gray-200 bg-gray-50 px-4 py-3",children:[t(o,{variant:"outline",onClick:()=>{$>0&&ae($-1)},disabled:0===$,className:"flex h-9 items-center gap-2 px-3 text-sm",children:[e(r,{className:"h-4 w-4"}),e("span",{className:"hidden sm:inline",children:E("admin.exams.preview.prevQuestion")}),e("span",{className:"sm:hidden",children:E("admin.exams.preview.prev")})]}),t("div",{className:"text-sm text-gray-600",children:[$+1," / ",A.length]}),t(o,{variant:"outline",onClick:()=>{$<A.length-1&&ae($+1)},disabled:$===A.length-1,className:"flex h-9 items-center gap-2 px-3 text-sm",children:[e("span",{className:"hidden sm:inline",children:E("admin.exams.preview.nextQuestion")}),e("span",{className:"sm:hidden",children:E("admin.exams.preview.next")}),e(l,{className:"h-4 w-4"})]})]})]})})]}),e("div",{className:"border-t pt-4",children:t("div",{className:"flex items-center justify-between",children:[e("div",{className:"text-sm text-gray-600",children:E("admin.exams.preview.footerNote")}),e(o,{variant:"outline",onClick:()=>S(!1),className:"border-gray-300",children:E("admin.exams.preview.close")})]})})]})})}
@@ -5,5 +5,5 @@ interface ExamQuestionGridProps {
5
5
  completedQuestions: Set<number>;
6
6
  onQuestionChange: (questionIndex: number) => void;
7
7
  }
8
- export declare function ExamQuestionGrid({ template, currentQuestionIndex, completedQuestions, onQuestionChange }: ExamQuestionGridProps): import("react").JSX.Element;
8
+ export declare function ExamQuestionGrid({ template, currentQuestionIndex, completedQuestions, onQuestionChange, }: ExamQuestionGridProps): import("react").JSX.Element;
9
9
  export {};
@@ -1 +1 @@
1
- "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{CheckCircle2 as r,Circle as a,ListOrdered as s,Zap as n}from"lucide-react";import{cn as i}from"../../shared/lib/utils";import{getPartByQuestionIndex as l,getSectionForQuestionIndex as o}from"./exam-template.utils";export function ExamQuestionGrid({template:i,currentQuestionIndex:h,completedQuestions:m,onQuestionChange:u}){return t("div",{className:"space-y-5",children:[t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-9 w-9 items-center justify-center rounded-lg bg-blue-600 shadow-sm",children:e(s,{className:"h-4 w-4 text-white"})}),t("div",{children:[e("h3",{className:"text-sm font-bold text-gray-900",children:"Danh sách câu hỏi"}),e("p",{className:"text-xs text-gray-500",children:"Click để chuyển câu"})]})]}),e("div",{className:"rounded-xl border border-gray-200 bg-gradient-to-b from-gray-50 to-white p-4 shadow-inner",children:e("div",{className:"grid max-h-[320px] grid-cols-6 justify-center gap-3 overflow-y-auto px-3 py-2",children:Array.from({length:i.totalQuestions},(t,r)=>{const a=l(i,r),s=a?o(a,r):void 0;return e("button",{onClick:()=>u(r),className:c(r,h,m),title:[`Câu ${r+1}`,a?.name,s?.name].filter(Boolean).join(" - "),children:r+1},r)})})}),t("div",{className:"rounded-xl border border-gray-100 bg-white p-3 shadow-sm",children:[e("div",{className:"mb-2 text-xs font-bold uppercase tracking-wide text-gray-400",children:"Chú thích"}),e(d,{icon:e(n,{className:"h-3.5 w-3.5 text-white"}),label:"Câu đang làm",className:"bg-blue-600"}),e(d,{icon:e(r,{className:"h-3.5 w-3.5 text-white"}),label:"Đã hoàn thành",className:"bg-green-500"}),e(d,{icon:e(a,{className:"h-3.5 w-3.5 text-gray-400"}),label:"Chưa tạo",className:"border bg-white"})]})]})}function c(e,t,r){return i("h-8 w-8 rounded-lg border-2 text-xs font-semibold shadow-sm transition-all hover:scale-105",e===t&&"border-transparent bg-blue-600 text-white ring-2 ring-blue-400",e!==t&&r.has(e)&&"border-transparent bg-green-500 text-white",e!==t&&!r.has(e)&&"border-gray-200 bg-white text-gray-500 hover:bg-gray-50")}function d({icon:r,label:a,className:s}){return t("div",{className:"mb-2 flex items-center gap-3",children:[e("div",{className:i("flex h-7 w-7 items-center justify-center rounded-lg",s),children:r}),e("span",{className:"text-xs font-medium text-gray-700",children:a})]})}
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{CheckCircle2 as r,Circle as a,ListOrdered as s,Zap as n}from"lucide-react";import{cn as i}from"../../shared/lib/utils";import{getPartByQuestionIndex as o,getSectionForQuestionIndex as d}from"./exam-template.utils";export function ExamQuestionGrid({template:l,currentQuestionIndex:c,completedQuestions:m,onQuestionChange:h}){const g=e=>{const t=(e=>e===c?"current":m.has(e)?"completed":"not-started")(e);return i("h-8 w-8 rounded-lg text-xs font-semibold transition-all duration-200","hover:scale-105 focus:outline-none focus:ring-2 focus:ring-offset-1","border-2 cursor-pointer shadow-sm",{"bg-gradient-to-br from-blue-500 to-indigo-600 text-white border-transparent shadow-lg shadow-blue-300/50 ring-2 ring-blue-400 ring-offset-1":"current"===t,"bg-gradient-to-br from-green-400 to-emerald-500 text-white border-transparent hover:shadow-md hover:shadow-green-200":"completed"===t,"bg-white text-gray-500 border-gray-200 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700":"not-started"===t})},u=Array.from({length:l.totalQuestions},(e,t)=>t);return t("div",{className:"space-y-5",children:[t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-9 w-9 items-center justify-center rounded-lg bg-blue-600 shadow-sm",children:e(s,{className:"h-4 w-4 text-white"})}),t("div",{children:[e("h3",{className:"text-sm font-bold text-gray-900",children:"Danh sách câu hỏi"}),e("p",{className:"text-xs text-gray-500",children:"Click để chuyển câu"})]})]}),e("div",{className:"rounded-xl border border-gray-200 bg-gradient-to-b from-gray-50 to-white p-4 shadow-inner",children:e("div",{className:"grid max-h-[320px] grid-cols-6 gap-3 overflow-y-auto px-3 py-2 justify-center",children:u.map(t=>{const r=t+1,a=o(l,t),s=a?d(a,t):void 0,n=[`Câu ${r}`,a?.name||"Unknown Part"];return s&&n.push(s.name),e("button",{onClick:()=>h(t),className:g(t),title:n.join(" - "),children:r},t)})})}),t("div",{className:"rounded-xl border border-gray-100 bg-white p-3 shadow-sm",children:[e("div",{className:"mb-2 text-xs font-bold uppercase tracking-wide text-gray-400",children:"Chú thích"}),t("div",{className:"grid grid-cols-1 gap-2",children:[t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 shadow-md",children:e(n,{className:"h-3.5 w-3.5 text-white"})}),e("span",{className:"text-xs font-medium text-gray-700",children:"Câu đang làm"})]}),t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-green-400 to-emerald-500 shadow-sm",children:e(r,{className:"h-3.5 w-3.5 text-white"})}),e("span",{className:"text-xs font-medium text-gray-700",children:"Đã hoàn thành"})]}),t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-7 w-7 items-center justify-center rounded-lg border-2 border-gray-200 bg-white",children:e(a,{className:"h-3.5 w-3.5 text-gray-400"})}),e("span",{className:"text-xs font-medium text-gray-700",children:"Chưa tạo"})]})]})]})]})}
@@ -1,10 +1,13 @@
1
1
  import type { UserRole } from '../../shared/types/common.types';
2
2
  import type { ExamQuestionsSdkApi } from '../../api/exam-questions.types';
3
+ import type { CreateExamTexts } from './create-exam.texts';
3
4
  export interface ExamQuestionsPageContainerProps {
4
5
  examId: string;
5
6
  role?: UserRole.ADMIN | UserRole.TEACHER;
6
7
  api: ExamQuestionsSdkApi;
7
8
  listPath?: string;
8
9
  onNavigate?: (href: string) => void;
10
+ /** Optional label overrides for embedded contexts. */
11
+ texts?: CreateExamTexts;
9
12
  }
10
13
  export declare function ExamQuestionsPageContainer(props: ExamQuestionsPageContainerProps): import("react").JSX.Element;
@@ -1 +1 @@
1
- "use client";import{jsx as e,jsxs as a}from"react/jsx-runtime";import{Suspense as t,useEffect as i,useMemo as n,useState as s}from"react";import{ArrowLeft as r,Hash as l,Loader2 as o,Save as m}from"lucide-react";import{Badge as c}from"../../components/ui/badge";import{Button as d}from"../../components/ui/button";import{useToast as u}from"../../shared/lib/hooks/useToast";import{useT as x}from"../../shared/lib/i18n";import{ExamCreator as h}from"./ExamCreator";function p({api:t,examId:o,listPath:p="/manage/exams",onNavigate:b}){const N=x(),{error:y,success:w}=u(),[k,j]=s(null),[E,q]=s([]),[C,I]=s({}),[P,Q]=s(!0),A=e=>{b?b(e):"undefined"!=typeof window&&(window.location.href=e)};i(()=>{let e=!1;return Q(!0),Promise.all([t.getExam(o),t.getQuestions?.(o)]).then(([a,t])=>{var i;e||(j(v(a)),q((void 0===(i=t)?void 0:v(i))??[]))}).catch(e=>y({message:N("admin.exams.questions.error"),description:e instanceof Error?e.message:N("admin.exams.questions.loadError")})).finally(()=>!e&&Q(!1)),()=>{e=!0}},[t,o,y,N]);const L=n(()=>k?.template?function(e){return"name"in e?e:{...e,name:e.title,description:e.description||e.subTitle||"",level:e.examLevel,parts:e.parts.map(e=>({...e,name:e.name||e.title||""}))}}(k.template):null,[k?.template]);return P?e(f,{label:N("admin.exams.questions.loading")}):k&&L?a("div",{className:"-m-4 flex h-[calc(100vh-4rem)] flex-col overflow-hidden md:-m-6 md:-mb-10",children:[a("div",{className:"flex flex-shrink-0 items-center gap-4 border-b border-gray-200/60 bg-white px-4 py-3",children:[e(d,{variant:"ghost",size:"icon",onClick:()=>A(p),className:"h-8 w-8",children:e(r,{className:"h-4 w-4"})}),e("div",{className:"min-w-0 flex-1",children:a("h1",{className:"truncate text-lg font-semibold tracking-tight text-gray-900",children:[k.name,k.code&&a(c,{variant:"outline",className:"ml-2",children:[e(l,{className:"mr-0.5 h-3 w-3"}),k.code]})]})}),a(d,{onClick:()=>A(p),className:"gap-2 bg-blue-600 hover:bg-blue-700",size:"sm",children:[e(m,{className:"h-4 w-4"}),"Thoát"]})]}),e(h,{template:L,examId:o,examDifficulty:k.difficulty,examLevel:k.examLevel??void 0,subjectId:k.subject?.id,initialQuestions:E,onSaveQuestion:t.saveQuestion,onSave:async e=>{L&&(await(t.saveExam?.({examId:o,questions:e.questions,template:L,examData:e})),A(p))},onCancel:()=>A(p),onPartAudioChange:t.updatePartAudio?async(e,a)=>{if(!L)return;const i=C[e];I(t=>({...t,[e]:a}));try{await(t.updatePartAudio?.({examId:o,partId:e,audioUrl:a,template:L})),w({message:N("admin.exams.partEditor.audioFile")})}catch(a){I(a=>{const t={...a};return void 0===i?delete t[e]:t[e]=i,t}),y({message:N("admin.exams.questions.error"),description:a instanceof Error?a.message:void 0})}}:void 0,partAudioOverrides:C})]}):e(g,{onBack:()=>A(p),label:N("admin.exams.questions.noTemplate")})}export function ExamQuestionsPageContainer(a){return e(t,{fallback:e(f,{label:"Loading..."}),children:e(p,{...a})})}function f({label:t}){return e("div",{className:"flex h-screen items-center justify-center",children:a("div",{className:"flex flex-col items-center gap-3",children:[e(o,{className:"h-8 w-8 animate-spin text-blue-600"}),e("p",{className:"text-sm text-gray-600",children:t})]})})}function g({label:t,onBack:i}){return e("div",{className:"flex h-screen items-center justify-center",children:a("div",{className:"text-center",children:[e("p",{className:"text-lg text-gray-900",children:t}),e(d,{onClick:i,className:"mt-4",children:"Quay lại"})]})})}function v(e){return"object"==typeof e&&null!==e&&"data"in e?e.data:e}
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{Suspense as a,useEffect as i,useMemo as n,useRef as s,useState as r}from"react";import{ArrowLeft as o,Hash as l,Loader2 as m,Save as c}from"lucide-react";import{Badge as u}from"../../components/ui/badge";import{Button as d}from"../../components/ui/button";import{useToast as f}from"../../shared/lib/hooks/useToast";import{useT as h}from"../../shared/lib/i18n";import{transformApiQuestionsToFrontend as p}from"../../shared/lib/utils/question-reverse-transform";import{ExamCreator as x}from"./ExamCreator";import{initializeQuestionStatusesFromAPI as b}from"./exam-question-status";function g({api:a,examId:m,listPath:g="/manage/exams",onNavigate:w,texts:q}){const Q=h(),{error:k,success:E}=f(),L=s(null),[j,C]=r(null),[I,P]=r([]),[T,A]=r({}),[z,B]=r(!0),D=e=>{w?w(e):"undefined"!=typeof window&&(window.location.href=e)};i(()=>{let e=!1;return B(!0),Promise.all([a.getExam(m),a.getQuestions?.(m)]).then(([t,a])=>{if(e)return;const i=y(t),n=function(e){return 0===e.length?[]:e.some(e=>"question_type"in e||"questionType"in e)?p(e):e}((void 0===(s=a)?void 0:y(s))??[]);var s;C(i),P(n);const r=function(e){return e?.totalQuestions??0}(i.template);r>0&&b(m,n.map(e=>e.questionNumber??0),r)}).catch(e=>k({message:Q("admin.exams.questions.error"),description:e instanceof Error?e.message:Q("admin.exams.questions.loadError")})).finally(()=>!e&&B(!1)),()=>{e=!0}},[a,m,k,Q]);const S=n(()=>j?.template?function(e){return"name"in e?e:{...e,name:e.title,description:e.description||e.subTitle||"",level:e.examLevel,parts:e.parts.map(e=>({...e,name:e.name||e.title||""}))}}(j.template):null,[j?.template]);return z?e(v,{label:q?.questionsLoadingLabel??Q("admin.exams.questions.loading")}):j&&S?t("div",{className:"-m-4 flex h-[calc(100vh-4rem)] flex-col overflow-hidden md:-m-6 md:-mb-10",children:[t("div",{className:"flex flex-shrink-0 items-center gap-4 border-b border-gray-200/60 bg-white px-4 py-3",children:[e(d,{variant:"ghost",size:"icon",onClick:()=>D(g),className:"h-8 w-8",children:e(o,{className:"h-4 w-4"})}),e("div",{className:"min-w-0 flex-1",children:t("h1",{className:"truncate text-lg font-semibold tracking-tight text-gray-900",children:[j.name,j.code&&t(u,{variant:"outline",className:"ml-2 align-middle border-blue-200 bg-blue-50 text-blue-700 text-xs font-normal",children:[e(l,{className:"mr-0.5 h-3 w-3"}),j.code]})]})}),t(d,{onClick:()=>L.current?.(),className:"gap-2 bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 shadow-sm",size:"sm",children:[e(c,{className:"h-4 w-4"}),q?.saveQuestionsLabel??"Lưu đề thi"]})]}),e(x,{template:S,examId:m,examDifficulty:j.difficulty,examLevel:j.examLevel??void 0,subjectId:j.subject?.id,initialQuestions:I,onSaveQuestion:a.saveQuestion,onSave:async e=>{S&&(await(a.saveExam?.({examId:m,questions:e.questions,template:S,examData:e})),D(g))},onCancel:()=>D(g),onPartAudioChange:a.updatePartAudio?async(e,t)=>{if(!S)return;const i=T[e];A(a=>({...a,[e]:t}));try{await(a.updatePartAudio?.({examId:m,partId:e,audioUrl:t,template:S})),E({message:Q("admin.exams.partEditor.audioFile"),description:"Đã lưu file âm thanh cho part."})}catch(t){A(t=>{const a={...t};return void 0===i?delete a[e]:a[e]=i,a}),k({message:Q("admin.exams.questions.error"),description:t instanceof Error?t.message:void 0})}}:void 0,partAudioOverrides:T,searchQuestions:a.searchQuestions,saveExamRef:L})]}):e(N,{onBack:()=>D(g),label:q?.questionsNoTemplateLabel??Q("admin.exams.questions.noTemplate")})}export function ExamQuestionsPageContainer(t){return e(a,{fallback:e(v,{label:"Loading..."}),children:e(g,{...t})})}function v({label:a}){return e("div",{className:"flex h-screen items-center justify-center",children:t("div",{className:"flex flex-col items-center gap-3",children:[e(m,{className:"h-8 w-8 animate-spin text-blue-600"}),e("p",{className:"text-sm text-gray-600",children:a})]})})}function N({label:a,onBack:i}){return e("div",{className:"flex h-screen items-center justify-center",children:t("div",{className:"text-center",children:[e("p",{className:"text-lg text-gray-900",children:a}),e(d,{onClick:i,className:"mt-4",children:"Quay lại"})]})})}function y(e){return"object"==typeof e&&null!==e&&"data"in e?e.data:e}
@@ -0,0 +1,24 @@
1
+ export declare const EXAM_DIFFICULTY: {
2
+ readonly EASY: "EASY";
3
+ readonly MEDIUM: "MEDIUM";
4
+ readonly HARD: "HARD";
5
+ };
6
+ export type ExamDifficulty = (typeof EXAM_DIFFICULTY)[keyof typeof EXAM_DIFFICULTY];
7
+ export interface ExamViewDialogExam {
8
+ id: string;
9
+ code?: string;
10
+ name: string;
11
+ subject: string;
12
+ level: string;
13
+ duration: number;
14
+ questionCount: number;
15
+ totalPoints: number;
16
+ difficulty: ExamDifficulty;
17
+ description?: string;
18
+ }
19
+ export interface ExamViewDialogProps {
20
+ open: boolean;
21
+ onOpenChange: (open: boolean) => void;
22
+ exam: ExamViewDialogExam | null;
23
+ }
24
+ export declare function ExamViewDialog({ open, onOpenChange, exam }: ExamViewDialogProps): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as a}from"react/jsx-runtime";import{Dialog as n,DialogContent as i,DialogDescription as r,DialogFooter as s,DialogHeader as l,DialogTitle as t}from"../../components/ui/dialog";import{Button as o}from"../../components/ui/button";import{Badge as c}from"../../components/ui/badge";import{BookOpen as m,Clock as d,FileText as g,GraduationCap as b,Hash as u,Star as x,Target as h,X as N}from"lucide-react";import{useT as p}from"../../shared/lib/i18n";export const EXAM_DIFFICULTY={EASY:"EASY",MEDIUM:"MEDIUM",HARD:"HARD"};function v({exam:n}){return e("div",{className:"bg-gradient-to-r from-blue-600 to-indigo-600 px-6 py-5 text-white",children:e(l,{className:"space-y-2",children:e("div",{className:"flex items-start justify-between",children:a("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-12 w-12 items-center justify-center rounded-xl bg-white/20 backdrop-blur-sm",children:e(g,{className:"h-6 w-6 text-white"})}),a("div",{children:[e(t,{className:"text-xl font-bold text-white",children:n.name}),e(r,{className:"text-blue-100 mt-1",children:a("span",{className:"inline-flex items-center gap-1.5",children:[e(u,{className:"h-3.5 w-3.5"}),n.code||n.id]})})]})]})})})})}function C({label:n,value:i,icon:r,iconContainerClassName:s,iconClassName:l,hoverClassName:t}){return e("div",{className:`rounded-xl border border-gray-200 bg-gray-50/50 p-4 transition-all ${t}`,children:a("div",{className:"flex items-center gap-3",children:[e("div",{className:`flex h-10 w-10 items-center justify-center rounded-lg ${s}`,children:e(r,{className:`h-5 w-5 ${l}`})}),a("div",{children:[e("p",{className:"text-xs font-medium text-gray-500 uppercase tracking-wide",children:n}),e("p",{className:"text-sm font-semibold text-gray-900 mt-0.5",children:i})]})]})})}function f({label:n,config:i}){const r=i.icon;return e("div",{className:"rounded-xl border border-gray-200 bg-gray-50/50 p-4 transition-all hover:border-gray-300",children:a("div",{className:"flex items-center gap-3",children:[e("div",{className:`flex h-10 w-10 items-center justify-center rounded-lg ${i.iconContainerClassName}`,children:e(r,{className:`h-5 w-5 ${i.iconClassName}`})}),a("div",{children:[e("p",{className:"text-xs font-medium text-gray-500 uppercase tracking-wide",children:n}),e(c,{className:`mt-1 ${i.badgeClassName} border`,children:i.label})]})]})})}function y({description:n,title:i}){return a("div",{className:"rounded-xl border border-gray-200 bg-gray-50/50 p-4",children:[a("h4",{className:"text-sm font-semibold text-gray-900 mb-2 flex items-center gap-2",children:[e(g,{className:"h-4 w-4 text-gray-500"}),i]}),e("p",{className:"text-sm text-gray-600 leading-relaxed",children:n})]})}function w({closeLabel:n,onClose:i}){return e(s,{className:"border-t border-gray-200 bg-gray-50/50 px-6 py-4",children:a(o,{variant:"outline",onClick:i,className:"gap-2",children:[e(N,{className:"h-4 w-4"}),n]})})}export function ExamViewDialog({open:r,onOpenChange:s,exam:l}){const t=p();if(!l)return null;const o=function(e,a){return{[EXAM_DIFFICULTY.EASY]:{label:a("admin.exams.difficulty.easy"),badgeClassName:"bg-green-50 text-green-700 border-green-200",iconContainerClassName:"bg-green-100",iconClassName:"text-green-600",icon:x},[EXAM_DIFFICULTY.MEDIUM]:{label:a("admin.exams.difficulty.medium"),badgeClassName:"bg-yellow-50 text-yellow-700 border-yellow-200",iconContainerClassName:"bg-yellow-100",iconClassName:"text-yellow-600",icon:x},[EXAM_DIFFICULTY.HARD]:{label:a("admin.exams.difficulty.hard"),badgeClassName:"bg-red-50 text-red-700 border-red-200",iconContainerClassName:"bg-red-100",iconClassName:"text-red-600",icon:x}}[e]}(l.difficulty,t);return e(n,{open:r,onOpenChange:s,children:a(i,{className:"max-w-2xl p-0 overflow-hidden",children:[e(v,{exam:l}),a("div",{className:"px-6 py-5 space-y-6",children:[a("div",{className:"grid grid-cols-2 md:grid-cols-3 gap-4",children:[e(C,{label:t("admin.exams.view.subject"),value:l.subject,icon:m,iconContainerClassName:"bg-blue-100",iconClassName:"text-blue-600",hoverClassName:"hover:border-blue-200 hover:bg-blue-50/30"}),e(C,{label:t("admin.exams.view.level"),value:l.level,icon:b,iconContainerClassName:"bg-purple-100",iconClassName:"text-purple-600",hoverClassName:"hover:border-purple-200 hover:bg-purple-50/30"}),e(C,{label:t("admin.exams.view.duration"),value:t("admin.exams.view.minutes",{count:String(l.duration)}),icon:d,iconContainerClassName:"bg-orange-100",iconClassName:"text-orange-600",hoverClassName:"hover:border-orange-200 hover:bg-orange-50/30"}),e(C,{label:t("admin.exams.view.questionCount"),value:t("admin.exams.view.questions",{count:String(l.questionCount)}),icon:g,iconContainerClassName:"bg-cyan-100",iconClassName:"text-cyan-600",hoverClassName:"hover:border-cyan-200 hover:bg-cyan-50/30"}),e(C,{label:t("admin.exams.view.totalPoints"),value:t("admin.exams.view.points",{count:String(l.totalPoints)}),icon:h,iconContainerClassName:"bg-green-100",iconClassName:"text-green-600",hoverClassName:"hover:border-green-200 hover:bg-green-50/30"}),e(f,{label:t("admin.exams.view.difficulty"),config:o})]}),l.description&&e(y,{title:t("admin.exams.view.description"),description:l.description})]}),e(w,{closeLabel:t("admin.exams.view.close"),onClose:()=>s(!1)})]})})}
@@ -1 +1 @@
1
- "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useEffect as a,useState as l}from"react";import{ChevronLeft as s,ChevronRight as i,Loader2 as n,Search as r}from"lucide-react";import{Button as c}from"../../components/ui/button";import{Card as o,CardContent as m}from"../../components/ui/card";import{Input as d}from"../../components/ui/input";import{useT as p}from"../../shared/lib/i18n";import{mapTemplateDetailToExamTemplate as h}from"../../api/exam-create";export function TemplateSelector({api:o,onTemplateSelect:m,onCancel:x}){const f=p(),[g,v]=l(""),[N,y]=l(1),[b,w]=l([]),[j,k]=l(1),[C,E]=l(0),[T,S]=l(!0),[P,q]=l(),[z,L]=l();a(()=>{y(1)},[g]),a(()=>{let e=!1;return S(!0),o.getExamTemplates({page:N,search:g||void 0}).then(t=>{e||(w(t.items),k(t.totalPages),E(t.totalItems))}).catch(t=>{e||q(t instanceof Error?t.message:f("admin.exams.questions.loadError"))}).finally(()=>{e||S(!1)}),()=>{e=!0}},[o,N,g,f]);const D=async e=>{q(void 0),L(e.id);try{const a="data"in(t=await o.getExamTemplateDetail(e.id))?t.data:t;m(h(a))}catch(e){q(e instanceof Error?e.message:f("admin.exams.questions.loadError"))}finally{L(void 0)}var t},I=N>1&&!T,M=N<j&&!T;return t("div",{className:"flex min-h-0 flex-1 flex-col gap-4",children:[t("div",{className:"relative",children:[e(r,{className:"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400"}),e(d,{value:g,onChange:e=>v(e.target.value),placeholder:f("admin.templates.searchPlaceholder"),className:"pl-9"})]}),T?e("div",{className:"flex flex-1 items-center justify-center",children:e(n,{className:"h-6 w-6 animate-spin text-blue-600"})}):null,!T&&P?e("p",{className:"text-sm text-red-600",children:P}):null,T||P?null:e("div",{className:"grid flex-1 content-start gap-3 overflow-y-auto md:grid-cols-2 lg:grid-cols-3",children:b.map(t=>e(u,{template:t,onSelect:D,isLoading:z===t.id,disabled:!!z},t.id))}),T||P||0!==b.length?null:e("p",{className:"flex-1 text-center text-sm text-gray-500",children:f("admin.templates.table.empty")}),!T&&!P&&C>0?t("div",{className:"flex shrink-0 items-center justify-between border-t border-gray-100 pt-3",children:[e("span",{className:"text-sm text-gray-500",children:f("admin.templates.table.showing",{count:b.length,total:C})}),t("div",{className:"flex items-center gap-2",children:[t(c,{variant:"outline",size:"sm",disabled:!I,onClick:()=>y(e=>Math.max(1,e-1)),children:[e(s,{className:"h-4 w-4"}),f("common.pagination.previous")]}),e("span",{className:"text-sm text-gray-600",children:f("admin.schedules.list.page",{current:N,total:j})}),t(c,{variant:"outline",size:"sm",disabled:!M,onClick:()=>y(e=>e+1),children:[f("common.pagination.next"),e(i,{className:"h-4 w-4"})]})]})]}):null,x&&e("div",{className:"flex shrink-0 justify-end",children:e(c,{variant:"outline",onClick:x,children:f("common.cancel")})})]})}function u({template:a,onSelect:l,isLoading:s,disabled:i}){return e(o,{className:"relative cursor-pointer transition hover:border-blue-300 hover:shadow-sm "+(i?"pointer-events-none opacity-60":""),onClick:()=>l(a),children:t(m,{className:"space-y-2 p-4",children:[e("h3",{className:"line-clamp-2 font-medium text-gray-900",children:a.title}),e("p",{className:"line-clamp-2 text-sm text-gray-500",children:a.subTitle}),t("p",{className:"text-xs text-gray-500",children:[a.totalQuestions," câu hỏi ",a.totalParts," phần"]}),s?e("div",{className:"absolute inset-0 flex items-center justify-center rounded-xl bg-white/60",children:e(n,{className:"h-5 w-5 animate-spin text-blue-600"})}):null]})})}
1
+ "use client";import{jsx as e,jsxs as a}from"react/jsx-runtime";import{useEffect as t,useState as l}from"react";import{BookOpen as s,ChevronLeft as n,ChevronRight as i,Clock as r,Loader2 as c,Search as o,Users as m}from"lucide-react";import{Button as d}from"../../components/ui/button";import{Card as p,CardContent as g,CardDescription as h,CardHeader as x,CardTitle as u}from"../../components/ui/card";import{Input as N}from"../../components/ui/input";import{useT as f}from"../../shared/lib/i18n";import{mapTemplateDetailToExamTemplate as v}from"../../api/exam-create";export function TemplateSelector({api:s,onTemplateSelect:r,onCancel:m}){const p=f(),[g,h]=l(""),[x,u]=l(1),[b,y]=l([]),[k,j]=l(1),[C,E]=l(0),[T,L]=l(!0),[S,D]=l(),[P,q]=l();t(()=>{u(1)},[g]),t(()=>{let e=!1;return L(!0),s.getExamTemplates({page:x,search:g||void 0}).then(a=>{e||(y(a.items),j(a.totalPages),E(a.totalItems))}).catch(a=>{e||D(a instanceof Error?a.message:p("admin.exams.questions.loadError"))}).finally(()=>{e||L(!1)}),()=>{e=!0}},[s,x,g,p]);const z=async e=>{D(void 0),q(e.id);try{const t="data"in(a=await s.getExamTemplateDetail(e.id))?a.data:a;r(v(t))}catch(e){D(e instanceof Error?e.message:p("admin.exams.questions.loadError"))}finally{q(void 0)}var a},A=x>1&&!T,I=x<k&&!T;return a("div",{className:"flex min-h-0 flex-1 flex-col gap-4",children:[a("div",{className:"relative",children:[e(o,{className:"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400"}),e(N,{value:g,onChange:e=>h(e.target.value),placeholder:p("admin.templates.searchPlaceholder"),className:"pl-9"})]}),T?e("div",{className:"flex flex-1 items-center justify-center",children:e(c,{className:"h-6 w-6 animate-spin text-blue-600"})}):null,!T&&S?e("p",{className:"text-sm text-red-600",children:S}):null,T||S?null:e("div",{className:"grid flex-1 content-start gap-3 overflow-y-auto md:grid-cols-2 lg:grid-cols-3",children:b.map((a,t)=>e(w,{template:a,index:t,onSelect:z,isLoading:P===a.id,disabled:!!P},a.id))}),T||S||0!==b.length?null:e("p",{className:"flex-1 text-center text-sm text-gray-500",children:p("admin.templates.table.empty")}),!T&&!S&&C>0?a("div",{className:"flex shrink-0 items-center justify-between border-t border-gray-100 pt-3",children:[e("span",{className:"text-sm text-gray-500",children:p("admin.templates.table.showing",{count:b.length,total:C})}),a("div",{className:"flex items-center gap-2",children:[a(d,{variant:"outline",size:"sm",disabled:!A,onClick:()=>u(e=>Math.max(1,e-1)),children:[e(n,{className:"h-4 w-4"}),p("common.pagination.previous")]}),e("span",{className:"text-sm text-gray-600",children:p("admin.schedules.list.page",{current:x,total:k})}),a(d,{variant:"outline",size:"sm",disabled:!I,onClick:()=>u(e=>e+1),children:[p("common.pagination.next"),e(i,{className:"h-4 w-4"})]})]})]}):null,m&&e("div",{className:"flex shrink-0 justify-end",children:e(d,{variant:"outline",onClick:m,children:p("common.cancel")})})]})}const b=["blue","green","purple","orange","pink","indigo"],y={blue:"bg-blue-100 text-blue-600",green:"bg-green-100 text-green-600",purple:"bg-purple-100 text-purple-600",orange:"bg-orange-100 text-orange-600",pink:"bg-pink-100 text-pink-600",indigo:"bg-indigo-100 text-indigo-600"};function w({template:t,index:l,onSelect:n,isLoading:i,disabled:o}){const d=b[l%b.length];return a(p,{className:"relative cursor-pointer border-2 border-gray-200 bg-white transition-all duration-200 hover:border-gray-300 hover:bg-gray-50 hover:shadow-md "+(o?"pointer-events-none opacity-60":""),onClick:()=>n(t),children:[e(x,{className:"px-4 py-3",children:a("div",{className:"flex items-center gap-2",children:[e("div",{className:`rounded-md p-1.5 ${y[d]}`,children:e(s,{className:"h-4 w-4"})}),a("div",{className:"min-w-0 flex-1",children:[e(u,{className:"truncate text-sm font-semibold",children:t.title}),t.subTitle&&e(h,{className:"truncate text-[11px]",children:t.subTitle})]})]})}),e(g,{className:"space-y-2 px-4 pb-3 pt-0",children:a("div",{className:"grid grid-cols-2 gap-x-3 gap-y-1 text-xs",children:[a("div",{className:"flex items-center gap-1.5",children:[e(s,{className:"h-3 w-3 text-gray-500"}),a("span",{className:"text-gray-600",children:[t.totalQuestions," câu hỏi"]})]}),a("div",{className:"flex items-center gap-1.5",children:[e("div",{className:"h-3 w-3 rounded-full bg-gray-300"}),a("span",{className:"text-gray-600",children:[t.totalParts," phần"]})]}),t.examLevel&&a("div",{className:"flex items-center gap-1.5",children:[e(m,{className:"h-3 w-3 text-gray-500"}),a("span",{className:"text-gray-600",children:["Cấp độ ",t.examLevel]})]}),t.createdAt&&a("div",{className:"flex items-center gap-1.5",children:[e(r,{className:"h-3 w-3 text-gray-500"}),e("span",{className:"text-gray-600",children:new Date(t.createdAt).toLocaleDateString("vi-VN")})]})]})}),i?e("div",{className:"absolute inset-0 flex items-center justify-center rounded-xl bg-white/60",children:e(c,{className:"h-5 w-5 animate-spin text-blue-600"})}):null]})}
@@ -0,0 +1,8 @@
1
+ export interface UnsavedChangesDialogProps {
2
+ open: boolean;
3
+ onOpenChange: (open: boolean) => void;
4
+ onConfirm: () => void;
5
+ onCancel: () => void;
6
+ questionNumber?: number;
7
+ }
8
+ export declare function UnsavedChangesDialog({ open, onOpenChange, onConfirm, onCancel, questionNumber, }: UnsavedChangesDialogProps): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as a}from"react/jsx-runtime";import{AlertDialog as n,AlertDialogAction as c,AlertDialogCancel as r,AlertDialogContent as l,AlertDialogDescription as s,AlertDialogFooter as t,AlertDialogHeader as i,AlertDialogTitle as h}from"../../components/ui/alert-dialog";import{AlertTriangle as m,Save as o,X as d}from"lucide-react";export function UnsavedChangesDialog({open:g,onOpenChange:x,onConfirm:p,onCancel:u,questionNumber:b}){return e(n,{open:g,onOpenChange:x,children:a(l,{className:"max-w-md",children:[a("div",{className:"flex flex-col items-center pt-2 pb-4",children:[e("div",{className:"flex h-16 w-16 items-center justify-center rounded-full bg-amber-100 mb-4",children:e(m,{className:"h-8 w-8 text-amber-600"})}),a(i,{className:"text-center space-y-2",children:[e(h,{className:"text-xl font-semibold text-gray-900",children:"Xác nhận chuyển câu hỏi"}),e(s,{className:"text-center text-gray-600",children:b?a("span",{children:["Câu hỏi ",e("strong",{className:"text-gray-800",children:b})," có những thay đổi chưa được lưu."]}):e("span",{children:"Câu hỏi hiện tại có những thay đổi chưa được lưu."})})]})]}),e("div",{className:"rounded-lg border border-amber-200 bg-amber-50 p-4 mb-4",children:e("p",{className:"text-sm text-amber-800",children:"Nếu bạn tiếp tục mà không lưu, các thay đổi sẽ bị mất và không thể khôi phục."})}),a(t,{className:"flex flex-col-reverse sm:flex-row gap-2 sm:gap-3",children:[a(r,{onClick:u,className:"flex-1 gap-2 border-gray-300 hover:bg-gray-50",children:[e(o,{className:"h-4 w-4"}),"Quay lại chỉnh sửa"]}),a(c,{onClick:p,className:"flex-1 gap-2 bg-red-500 hover:bg-red-600 text-white",children:[e(d,{className:"h-4 w-4"}),"Hủy thay đổi"]})]})]})})}
@@ -2,4 +2,4 @@ export { buildExamTemplatePayload, createExamCreateApi, createMockCreateExamApi
2
2
  export type { CreateExamFetchApiOptions, CreateExamPayload, CreateExamSdkApi, ExamFormData, ExamLevelOption, ExamResponse, ExamTemplatePayload, UpdateExamTemplateInput } from '../../api/exam-create';
3
3
  export { createExamQuestionsApi, createExamQuestionsFetchApi, createMockExamQuestionsApi } from '../../api/exam-questions';
4
4
  export { createExamTakingApi, createMockExamTakingApi } from '../../api/exam-taking';
5
- export type { ExamQuestionsSdkApi, ExamWithTemplate, FetchApiOptions, SaveExamInput, SaveQuestionInput, UpdatePartAudioInput } from '../../api/exam-questions';
5
+ export type { ExamQuestionsSdkApi, ExamWithTemplate, ExistingQuestionBank, FetchApiOptions, QuestionSearchItem, SaveExamInput, SaveQuestionInput, SaveQuestionResult, SearchQuestionBankInput, SearchQuestionBankResult, UpdatePartAudioInput } from '../../api/exam-questions';
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Optional label overrides for the exam-creation flow. Lets a consumer reuse
3
+ * these containers in a non-exam context (e.g. authoring homework) by passing
4
+ * its own wording. Every field is optional; when omitted the component falls
5
+ * back to its default i18n string, so existing usages are unaffected.
6
+ */
7
+ export interface CreateExamTexts {
8
+ /** Page title — default 'Tạo đề thi mới'. */
9
+ title?: string;
10
+ /** Step subtitle — default 'Bước 1: Nhập thông tin cơ bản của đề thi'. */
11
+ step1?: string;
12
+ /** Basic-info card title — default 'Thông tin cơ bản'. */
13
+ basicInfo?: string;
14
+ /** Required-fields hint shown under the basic-info card title. */
15
+ requiredHint?: string;
16
+ /** Name field label — default 'Tên đề thi'. */
17
+ nameLabel?: string;
18
+ /** Name field placeholder — default 'Nhập tên đề thi...'. */
19
+ namePlaceholder?: string;
20
+ /** Code field label — default 'Mã đề thi'. */
21
+ codeLabel?: string;
22
+ /** Code field placeholder — default 'Nhập mã đề thi...'. */
23
+ codePlaceholder?: string;
24
+ /** Duration field label — default 'Thời gian thi (phút)'. */
25
+ durationLabel?: string;
26
+ /** Description field label — default 'Mô tả'. */
27
+ descriptionLabel?: string;
28
+ /** Description field placeholder — default exam description placeholder. */
29
+ descriptionPlaceholder?: string;
30
+ /** Template section title — default 'Chọn Template'. */
31
+ templateSectionTitle?: string;
32
+ /** Empty template hint — default 'Chưa chọn template đề thi'. */
33
+ noTemplateText?: string;
34
+ /** Template picker dialog title — default 'Chọn template đề thi'. */
35
+ templateDialogTitle?: string;
36
+ /** Template picker dialog description — default 'Chọn loại đề thi bạn muốn tạo.'. */
37
+ templateDialogDescription?: string;
38
+ /** Missing-template warning description. */
39
+ missingTemplateDescription?: string;
40
+ /** Button text for picking a template. */
41
+ selectTemplateLabel?: string;
42
+ /** Button text for changing a selected template. */
43
+ changeTemplateLabel?: string;
44
+ /** Cancel button label. */
45
+ cancelLabel?: string;
46
+ /** Saving button label. */
47
+ savingLabel?: string;
48
+ /** Submit button label. */
49
+ saveAndContinueLabel?: string;
50
+ /** Success toast description. Use {name} as placeholder for the created name. */
51
+ successDescription?: string;
52
+ /** Loading label for question-authoring screen. */
53
+ questionsLoadingLabel?: string;
54
+ /** Empty-template label for question-authoring screen. */
55
+ questionsNoTemplateLabel?: string;
56
+ /** Save button label for question-authoring screen. */
57
+ saveQuestionsLabel?: string;
58
+ }
@@ -0,0 +1 @@
1
+ export{};
@@ -1 +1 @@
1
- import{z as t}from"zod";export const examFormSchema=t.object({code:t.string().min(1,"Mã đề thi không được để trống").max(100,"Mã đề thi không được vượt quá 100 ký tự").regex(/^[a-zA-Z0-9_-]+$/,"Mã đề thi chỉ được chứa chữ cái, số, dấu gạch ngang (-) và gạch dưới (_)").trim(),name:t.string().min(3,"Tên đề thi phải có ít nhất 3 ký tự").max(255,"Tên đề thi không được vượt quá 255 ký tự").trim(),level:t.string(),difficulty:t.string(),duration:t.string().refine(t=>{if(!t)return!0;const n=Number.parseInt(t,10);return!Number.isNaN(n)&&n>=5&&n<=300},{message:"Thời gian thi phải từ 5 đến 300 phút"}),totalPoints:t.string().min(1,"Vui lòng nhập tổng điểm").refine(t=>/^[1-9]\d*$/.test(t.trim()),{message:"Tổng điểm phải là số nguyên dương"}).refine(t=>{const n=Number.parseInt(t,10);return n>=1&&n<=1e3},{message:"Tổng điểm phải từ 1 đến 1000"}),description:t.string().max(2e3,"Mô tả không được vượt quá 2000 ký tự").trim()});
1
+ import{z as t}from"zod";export const examFormSchema=t.object({code:t.string().min(1,"Mã đề kiểm tra không được để trống").max(100,"Mã đề kiểm tra không được vượt quá 100 ký tự").regex(/^[a-zA-Z0-9_-]+$/,"Mã đề kiểm tra chỉ được chứa chữ cái, số, dấu gạch ngang (-) và gạch dưới (_)").trim(),name:t.string().min(3,"Tên đề kiểm tra phải có ít nhất 3 ký tự").max(255,"Tên đề kiểm tra không được vượt quá 255 ký tự").trim(),level:t.string(),difficulty:t.string(),duration:t.string().refine(t=>{if(!t)return!0;const n=Number.parseInt(t,10);return!Number.isNaN(n)&&n>=5&&n<=300},{message:"Thời gian kiểm tra phải từ 5 đến 300 phút"}),totalPoints:t.string().min(1,"Vui lòng nhập tổng điểm").refine(t=>/^[1-9]\d*$/.test(t.trim()),{message:"Tổng điểm phải là số nguyên dương"}).refine(t=>{const n=Number.parseInt(t,10);return n>=1&&n<=1e3},{message:"Tổng điểm phải từ 1 đến 1000"}),description:t.string().max(2e3,"Mô tả không được vượt quá 2000 ký tự").trim()});
@@ -0,0 +1,12 @@
1
+ export interface EntryRestartDialogProps {
2
+ open: boolean;
3
+ onOpenChange: (open: boolean) => void;
4
+ isStarting: boolean;
5
+ onContinue: () => void;
6
+ onRestart: () => void;
7
+ }
8
+ /**
9
+ * Shown when the student already has an IN_PROGRESS attempt for this room.
10
+ * Lets them resume the existing attempt or discard it and start fresh.
11
+ */
12
+ export declare function EntryRestartDialog({ open, onOpenChange, isStarting, onContinue, onRestart, }: EntryRestartDialogProps): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as n,jsxs as i,Fragment as e}from"react/jsx-runtime";import{AlertDialog as t,AlertDialogAction as r,AlertDialogCancel as l,AlertDialogContent as a,AlertDialogDescription as c,AlertDialogFooter as o,AlertDialogHeader as m,AlertDialogTitle as s}from"../../../components/ui/alert-dialog";import{Loader2 as d}from"lucide-react";export function EntryRestartDialog({open:h,onOpenChange:p,isStarting:u,onContinue:g,onRestart:b}){return n(t,{open:h,onOpenChange:p,children:i(a,{children:[i(m,{children:[n(s,{children:"Bạn đang có bài thi dở"}),n(c,{children:"Bạn đã bắt đầu làm bài thi này trước đó. Bạn muốn tiếp tục làm bài hay bắt đầu lại từ đầu?"})]}),i(o,{children:[n(l,{disabled:u,onClick:g,children:"Làm tiếp"}),n(r,{disabled:u,onClick:b,children:u?i(e,{children:[n(d,{className:"mr-2 h-4 w-4 animate-spin"}),"Đang xử lý..."]}):"Làm lại từ đầu"})]})]})})}
@@ -0,0 +1,31 @@
1
+ import type { StudentRoom } from '../../../shared/types/student.types';
2
+ import type { ExamEntrySdkApi, ExamEntryRoom } from '../../../api/exam-entry';
3
+ export interface StudentExamEntryPageContainerProps {
4
+ /** Room id from the route (`/student/exams/:roomId/enter`). */
5
+ roomId: string;
6
+ /** Optional invite token (`?invite=...`) to silently join the room first. */
7
+ inviteToken?: string | null;
8
+ /** API adapter; create one via `createExamEntryApi`. */
9
+ api: ExamEntrySdkApi;
10
+ /** Navigation callback; falls back to `window.location.href` when omitted. */
11
+ onNavigate?: (href: string) => void;
12
+ /** Path to navigate to when starting/continuing the attempt. */
13
+ takePath?: (roomId: string) => string;
14
+ /** Path to navigate to on close/back. Overrides the default contest/discover logic. */
15
+ closePath?: (room: StudentRoom | null) => string;
16
+ /** Slot to render the host app's registration form when required. */
17
+ renderRegistrationForm?: (props: {
18
+ room: ExamEntryRoom;
19
+ onSubmit: (formData: Record<string, unknown>) => Promise<void> | void;
20
+ isSubmitting: boolean;
21
+ }) => React.ReactNode;
22
+ }
23
+ /**
24
+ * SDK container for the fullscreen student exam entry screen. Ports the host
25
+ * app's `/student/exams/:roomId/enter` flow: invite join, access/exam/
26
+ * registration gating, room-window state machine, active-attempt resume, the
27
+ * restart-or-continue dialog, and the themed waiting rooms (TOEIC / Cambridge
28
+ * YLE / Summer Sky). Infrastructure is injected via {@link ExamEntrySdkApi},
29
+ * `onNavigate`, and `renderRegistrationForm`.
30
+ */
31
+ export declare function StudentExamEntryPageContainer({ roomId, inviteToken, api, onNavigate, takePath, closePath, renderRegistrationForm, }: StudentExamEntryPageContainerProps): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,Fragment as t,jsxs as a}from"react/jsx-runtime";import{useState as r,useEffect as n,useCallback as i,useMemo as o}from"react";import{Dialog as s,DialogContent as l,DialogDescription as d,DialogTitle as c}from"../../../components/ui/dialog";import{t as m}from"../../../shared/lib/i18n";import{ROUTES as h}from"../../../shared/constants/routes";import{normalizeExamTheme as x,resolveThemeConfig as u}from"../../../shared/lib/themes";import{unwrap as p}from"../take/exam-taking.utils";import{toast as g}from"sonner";import{Loader2 as b,Clock as f,Trophy as v,ArrowLeft as w}from"lucide-react";import y from"next/image";import{BeforeStartCard as N,ExpiredCard as _,AlreadySubmittedCard as k}from"../../../components/features/exam-entry";import{CloseButton as T}from"../../../components/features/exam-entry/themed/components";import{ToeicWaitingRoom as E}from"../../../components/features/exam-entry/themed/ToeicWaitingRoom";import{CambridgeYleWaitingRoom as D}from"../../../components/features/exam-entry/themed/CambridgeYleWaitingRoom";import{SummerSkyWaitingRoom as S}from"../../../components/themes/summer-sky/SummerSkyWaitingRoom";import{FullscreenLoading as R}from"./fullscreen-loading";export function StudentExamEntryPageContainer({roomId:I,inviteToken:A=null,api:O,onNavigate:M,takePath:C=e=>`/student/exams/${e}/take`,closePath:B,renderRegistrationForm:W}){const L=i(e=>{M?M(e):"undefined"!=typeof window&&(window.location.href=e)},[M]),[j,F]=r(null),[z,U]=r(null),[$,P]=r("LOADING"),[X,Y]=r(0),[H,V]=r(5),[G,K]=r(!1),[Q,q]=r(!1),[J,Z]=r(null),[ee,te]=r(!1),[ae,re]=r(!1),[ne,ie]=r(!1),oe=o(()=>{const e=j?.exam?.theme,t=j?.exam?.template?.theme;return u({examTheme:e,templateTheme:t,level:j?.exam?.level})},[j?.exam,I,j]),se=o(()=>B?B(j):j?.contest?.id?h.STUDENT.CONTEST_ROOMS(j.contest.id):h.STUDENT.CONTESTS_DISCOVER,[B,j]),le=i((e,t)=>{const a=!0===e.allowMultipleSubmissions;if(t){const e=t.status;if("SUBMITTED"===e||"GRADED"===e||"PUBLISHED"===e)return a?"WITHIN_WINDOW":"ALREADY_SUBMITTED";if("IN_PROGRESS"===e)return"WITHIN_WINDOW"}const r=(new Date).getTime(),n=e.startDatetime?new Date(e.startDatetime).getTime():null,i=e.endDatetime?new Date(e.endDatetime).getTime():null;return!a&&i&&r>i?"EXPIRED":n&&r<n?"BEFORE_START":"WITHIN_WINDOW"},[]);n(()=>{(async()=>{try{if(A)try{await(O.joinViaInvite?.(I,A))}catch{}const e=await O.getRoom(I),t=p(e);if(F(t),!t.canAccess&&!A)return void P("NO_ACCESS");if(t.registrationFormConfig?.enabled&&t.registrationFormConfig.fields?.length>0&&!t.hasSubmittedFormData)return void re(!0);if(!t.exam)return void P("NO_EXAM");const a=await O.getActiveAttemptByRoom(I),r=p(a);r&&(Z(r),U(r));const n=!0===t.allowMultipleSubmissions;if(t.hasJoined&&!r&&!n)return void P("ALREADY_SUBMITTED");const i=le(t,r??null);if(P(i),"BEFORE_START"===i&&t.startDatetime){const e=new Date(t.startDatetime).getTime(),a=(new Date).getTime();Y(Math.max(0,Math.floor((e-a)/1e3)))}}catch(e){console.error("Failed to load room data:",e),g.error(m("student.exam.entry.loadError"))}})()},[I,A,le]),n(()=>{if("BEFORE_START"!==$||X<=0)return;const e=setInterval(()=>{Y(e=>e<=1?(P("WITHIN_WINDOW"),0):e-1)},1e3);return()=>clearInterval(e)},[$,X]);const de=i(async()=>{try{K(!0),await O.startAttempt(I),L(C(I))}catch(e){const t=e,a=t.response?.data?.errorCode;"ROOM_NO_EXAM"===a?P("NO_EXAM"):"ROOM_NOT_STARTED"===a?(g.error(m("student.exam.entry.notStarted")),P("BEFORE_START"),q(!1),V(5)):"ROOM_ENDED"===a?(g.error(m("student.exam.entry.expired")),P("EXPIRED")):"ATTEMPT_ALREADY_SUBMITTED"===a?(g.error("Bạn đã hoàn thành bài thi này rồi."),P("ALREADY_SUBMITTED")):g.error(m("student.exam.entry.startError")),K(!1)}},[I]),ce=i(()=>{J?te(!0):de()},[J,de]),me=i(()=>{te(!1),g.success(m("student.exam.entry.continue")),L(C(I))},[I]),he=i(async()=>{if(J)try{te(!1),K(!0),await O.deleteAttempt(J.id),Z(null),await de()}catch(e){console.error("Failed to restart attempt:",e),g.error(m("student.exam.entry.restartError")),K(!1)}},[J,de]);n(()=>{if(!Q||H<=0)return;const e=setInterval(()=>{V(e=>e<=1?(L(C(I)),0):e-1)},1e3);return()=>clearInterval(e)},[Q,H,I]);const xe=i(async e=>{if(j?.contest?.id){ie(!0);try{await(O.registerContest?.(j.contest.id,e)),re(!1);const t=await O.getRoom(I),a=p(t);if(F(a),!a.exam)return void P("NO_EXAM");const r=await O.getActiveAttemptByRoom(I),n=p(r);n&&(Z(n),U(n));const i=!0===a.allowMultipleSubmissions;if(a.hasJoined&&!n&&!i)return void P("ALREADY_SUBMITTED");const o=le(a,n??null);if(P(o),"BEFORE_START"===o&&a.startDatetime){const e=new Date(a.startDatetime).getTime(),t=(new Date).getTime();Y(Math.max(0,Math.floor((e-t)/1e3)))}}catch{g.error("Không thể gửi thông tin đăng ký. Vui lòng thử lại.")}finally{ie(!1)}}},[j?.contest?.id,I,le]);if(ae&&j?.registrationFormConfig&&W)return a(t,{children:[e(R,{}),W({room:j,onSubmit:xe,isSubmitting:ne})]});if("LOADING"===$)return e(R,{});if("NO_EXAM"===$)return e("div",{className:"fixed inset-0 z-50 flex items-center justify-center overflow-hidden bg-gradient-to-b from-sky-100 via-blue-50 to-indigo-100",children:a("div",{className:"mx-auto max-w-md p-4 text-center",children:[e("div",{className:"mb-6 text-7xl",children:"📝✨"}),e("h2",{className:"mb-3 text-2xl font-bold text-sky-700",children:"Bài thi đang được chuẩn bị"}),j&&a("div",{className:"mb-4 rounded-xl bg-white/60 px-5 py-3 text-left text-sm text-sky-700 shadow-sm backdrop-blur-sm",children:[e("p",{className:"font-semibold text-base",children:j.name}),j.code&&a("p",{className:"mt-1 text-sky-500",children:["Mã phòng: ",e("span",{className:"font-mono font-medium",children:j.code})]}),(j.startDatetime||j.endDatetime)&&a("p",{className:"mt-1 text-sky-500",children:["Thời gian thi:"," ",j.startDatetime?new Date(j.startDatetime).toLocaleString("vi-VN"):"—"," → ",j.endDatetime?new Date(j.endDatetime).toLocaleString("vi-VN"):"—"]})]}),e("p",{className:"mb-6 text-base text-sky-600",children:"Thầy cô đang chuẩn bị đề thi cho phòng này. Bạn quay lại sau nhé! 😊"}),e("button",{className:"mx-auto inline-flex items-center gap-2 rounded-2xl border-b-4 border-sky-600 bg-sky-500 px-8 py-3 text-lg font-bold text-white shadow-md transition-all hover:brightness-110 active:translate-y-0.5 active:border-b-2 active:shadow-sm",onClick:()=>window.history.back(),children:"← Quay lại"})]})});if("NO_ACCESS"===$)return e("div",{className:"fixed inset-0 z-50 flex items-center justify-center overflow-hidden bg-gradient-to-b from-red-50 via-orange-50 to-amber-50",children:a("div",{className:"mx-auto max-w-md p-4 text-center",children:[e("div",{className:"mb-6 text-7xl",children:"🔒"}),e("h2",{className:"mb-3 text-2xl font-bold text-red-600",children:"Không có quyền truy cập"}),e("p",{className:"mb-6 text-base text-red-500",children:"Bạn chưa được đăng ký vào phòng thi này. Hãy liên hệ thầy cô để được thêm vào phòng thi nhé!"}),e("button",{className:"mx-auto inline-flex items-center gap-2 rounded-2xl border-b-4 border-red-600 bg-red-500 px-8 py-3 text-lg font-bold text-white shadow-md transition-all hover:brightness-110 active:translate-y-0.5 active:border-b-2 active:shadow-sm",onClick:()=>window.history.back(),children:"← Quay lại"})]})});const ue="TOEIC"===j?.exam?.template?.subTitle,pe="SUMMER_SKY"===x(j?.exam?.theme||j?.exam?.template?.theme);if("WITHIN_WINDOW"===$&&j)return a(t,{children:[ue?e(E,{room:j,onStart:ce,isStarting:G,showCountdown:Q,autoStartCountdown:H,closeRedirectUrl:se,onClose:()=>{L(se)}}):pe?e(S,{room:j,onStart:ce,isStarting:G,onExit:()=>{L(se)}}):e(D,{room:j,onStart:ce,isStarting:G,showCountdown:Q,autoStartCountdown:H,closeRedirectUrl:se,onClose:()=>{L(se)}}),e(s,{open:ee,onOpenChange:te,children:e(l,{className:"overflow-visible border-0 bg-transparent p-0 shadow-none sm:max-w-md",children:a("div",{className:"relative overflow-visible rounded-[28px] bg-gradient-to-br from-cyan-300/25 via-white/5 to-white/0 p-px shadow-[0_30px_80px_-20px_rgba(10,75,140,0.85)] backdrop-blur-xl",children:[e("div",{className:"pointer-events-none absolute -left-16 -top-16 h-48 w-48 rounded-full bg-cyan-400/35 blur-3xl"}),e("div",{className:"pointer-events-none absolute -right-16 -bottom-16 h-48 w-48 rounded-full bg-sky-500/30 blur-3xl"}),e("div",{className:"pointer-events-none absolute left-1/2 top-0 h-24 w-1/2 -translate-x-1/2 rounded-full bg-cyan-300/15 blur-2xl"}),a("div",{className:"relative overflow-hidden rounded-[calc(28px-1px)] bg-gradient-to-b from-[#0e3d70]/97 via-[#0a4b8c]/97 to-[#072d54]/97 ring-1 ring-cyan-300/25",children:[e("div",{className:"pointer-events-none absolute inset-0 opacity-[0.04]",style:{backgroundImage:"radial-gradient(circle at 1px 1px, white 1px, transparent 0)",backgroundSize:"24px 24px"}}),e("div",{className:"pointer-events-none absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-cyan-300/40 to-transparent"}),a("div",{className:"relative flex flex-col items-center gap-4 px-8 pt-10 pb-6",children:[a("div",{className:"relative",children:[e("div",{className:"absolute inset-0 animate-ping rounded-full bg-cyan-300/30",style:{animationDuration:"2.8s"}}),e("div",{className:"absolute -inset-3 rounded-full bg-gradient-to-br from-cyan-300/15 to-sky-500/10 blur-md"}),e("div",{className:"absolute -inset-1.5 rounded-full bg-gradient-to-br from-cyan-200/30 to-sky-400/20"}),e("div",{className:"relative flex h-16 w-16 items-center justify-center rounded-full bg-gradient-to-br from-cyan-300 via-sky-500 to-sky-700 shadow-[0_10px_30px_-6px_rgba(34,211,238,0.7),inset_0_2px_4px_rgba(255,255,255,0.4)] ring-[3px] ring-cyan-200/40",children:a("svg",{className:"h-8 w-8 text-white drop-shadow-[0_2px_3px_rgba(0,0,0,0.3)]",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round",children:[e("path",{d:"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"}),e("line",{x1:"12",y1:"9",x2:"12",y2:"13"}),e("line",{x1:"12",y1:"17",x2:"12.01",y2:"17"})]})})]}),a("div",{className:"flex flex-col items-center gap-2",children:[e(c,{className:"text-center text-xl font-black tracking-tight text-white drop-shadow-[0_2px_8px_rgba(34,211,238,0.4)]",children:"Bạn đang có bài thi dở"}),e(d,{className:"max-w-sm text-center text-[13px] font-medium leading-relaxed text-cyan-100/75",children:"Bạn đã bắt đầu làm bài thi này trước đó. Bạn muốn tiếp tục làm bài hay bắt đầu lại từ đầu?"})]})]}),e("div",{className:"mx-8 h-px bg-gradient-to-r from-transparent via-cyan-300/30 to-transparent"}),a("div",{className:"relative flex flex-col-reverse gap-3 px-8 py-6 sm:flex-row sm:justify-center",children:[a("button",{onClick:me,className:"group relative inline-flex min-w-[140px] cursor-pointer items-center justify-center gap-2 overflow-hidden rounded-full bg-white/[0.08] px-7 py-3 text-sm font-bold text-white ring-1 ring-cyan-200/30 backdrop-blur-md transition-all duration-200 hover:-translate-y-0.5 hover:bg-white/[0.14] hover:ring-cyan-200/60 hover:shadow-[0_8px_20px_-4px_rgba(34,211,238,0.3)] active:translate-y-0",children:[e("span",{className:"pointer-events-none absolute inset-0 -translate-x-full bg-gradient-to-r from-transparent via-white/20 to-transparent transition-transform duration-700 group-hover:translate-x-full"}),e("span",{className:"relative",children:"Làm tiếp"})]}),a("button",{onClick:he,disabled:G,className:"group relative inline-flex min-w-[160px] cursor-pointer items-center justify-center gap-2 overflow-hidden rounded-full bg-gradient-to-b from-[#ffd54a] via-[#ffb733] to-[#ff8a1f] px-7 py-3 text-sm font-black uppercase tracking-wide text-white shadow-[0_6px_0_0_#c46410,0_10px_22px_rgba(255,138,31,0.5)] ring-2 ring-orange-300/70 transition-all duration-150 hover:-translate-y-0.5 hover:brightness-110 hover:shadow-[0_8px_0_0_#c46410,0_14px_26px_rgba(255,138,31,0.55)] active:translate-y-1.5 active:shadow-[0_1px_0_0_#c46410,0_3px_8px_rgba(255,138,31,0.3)] disabled:cursor-not-allowed disabled:opacity-70",style:{textShadow:"0 2px 0 rgba(180,80,10,0.55)"},children:[e("span",{className:"pointer-events-none absolute inset-x-0 top-0 h-1/2 bg-gradient-to-b from-white/30 to-transparent"}),G?a(t,{children:[e(b,{className:"relative h-4 w-4 animate-spin"}),e("span",{className:"relative",children:"Đang xử lý..."})]}):e("span",{className:"relative",children:"Làm lại từ đầu"})]})]})]})]})})})]});const ge=oe.visuals?.backgroundFilter??"grayscale(1) blur(10px) saturate(0.85) brightness(0.9)",be=oe.visuals?.backgroundOverlay??"linear-gradient(to bottom, rgba(0, 0, 0, 0.2), transparent, rgba(0, 0, 0, 0.3))",fe=oe.visuals?.cardBackground??"radial-gradient(60% 60% at 50% 40%, rgba(184, 229, 157, 0.85) 0%, rgba(115, 213, 133, 0.85) 100%)";return a("div",{className:"fixed inset-0 z-50 flex items-center justify-center overflow-hidden",children:[e("div",{className:"absolute inset-0 scale-110 bg-cover bg-center",style:{backgroundImage:`url(${oe.assets.background})`,filter:ge}}),e("div",{className:"absolute inset-0",style:{background:be}}),a("div",{className:"relative z-10 w-[88%] max-w-2xl sm:w-[70%] md:w-[50%] lg:w-[42%]",children:[e(T,{imageSrc:oe.assets.closeButton,onClick:()=>L(se),className:"absolute -right-3 -top-7 z-30",size:72}),a("div",{className:"absolute left-1/2 top-0 z-10 -translate-x-1/2 -translate-y-1/2",children:[e(y,{src:oe.assets.titleBar,alt:"",width:260,height:56,className:"drop-shadow-lg",priority:!0,unoptimized:!0}),e("span",{className:"absolute inset-0 mt-2.5 flex items-center justify-center pb-1 text-sm font-extrabold tracking-wider text-white drop-shadow",children:oe.displayName})]}),a("div",{className:"relative overflow-hidden rounded-3xl shadow-2xl ring-1 ring-white/20",children:[e("div",{className:"h-2 w-full",style:{backgroundImage:`url(${oe.assets.cardFrame})`,backgroundSize:"100% 100%"}}),a("div",{className:"relative p-6 pt-12 md:p-8 md:pt-14",style:{background:fe},children:[a("div",{className:"mb-4 rounded-2xl bg-black/20 px-4 py-3.5 text-center ring-1 ring-white/10 backdrop-blur-sm",children:[e("h2",{className:"text-base font-extrabold tracking-wide text-white drop-shadow-sm",children:j?.name}),j?.exam?.name&&e("p",{className:"mt-0.5 text-xs font-medium text-white/80",children:j.exam.name})]}),(j?.exam?.durationMinutes||j?.exam?.totalScore)&&a("div",{className:"mb-4 grid grid-cols-2 gap-3",children:[j?.exam?.durationMinutes&&a("div",{className:"flex flex-col items-center rounded-2xl bg-black/20 px-2 py-4 ring-1 ring-white/10 backdrop-blur-sm",children:[e("div",{className:"mb-2 flex h-8 w-8 items-center justify-center rounded-full bg-white/10",children:e(f,{className:"h-4 w-4 text-white"})}),e("span",{className:"text-2xl font-extrabold text-white",children:j.exam.durationMinutes}),e("span",{className:"text-xs font-semibold text-white/80",children:"Phút"})]}),j?.exam?.totalScore&&a("div",{className:"flex flex-col items-center rounded-2xl bg-black/20 px-2 py-4 ring-1 ring-white/10 backdrop-blur-sm",children:[e("div",{className:"mb-2 flex h-8 w-8 items-center justify-center rounded-full bg-white/10",children:e(v,{className:"h-4 w-4 text-white"})}),e("span",{className:"text-2xl font-extrabold text-white",children:j.exam.totalScore}),e("span",{className:"text-xs font-semibold text-white/80",children:"Thang điểm"})]})]}),"BEFORE_START"===$&&e(N,{countdown:X,formatCountdown:e=>{const t=Math.floor(e/86400),a=Math.floor(e%86400/3600),r=Math.floor(e%3600/60),n=e%60;return t>0?`${t} ngày ${a} giờ ${r} phút`:a>0?`${a} giờ ${r} phút ${n} giây`:r>0?`${r} phút ${n} giây`:`${n} giây`}}),"EXPIRED"===$&&e(_,{}),"ALREADY_SUBMITTED"===$&&e(k,{attempt:z}),e("div",{className:"mt-5 flex justify-center",children:a("button",{className:"group relative inline-flex cursor-pointer items-center gap-2 rounded-full bg-[#0473f9] px-8 py-3 shadow-[0_4px_0_0_#1d4ed8] transition-all duration-150 hover:brightness-110 active:translate-y-[2px] active:shadow-[0_2px_0_0_#1d4ed8]",onClick:()=>L(se),children:[e(w,{className:"h-5 w-5 text-white transition-transform group-hover:-translate-x-0.5"}),e("span",{className:"text-sm font-extrabold uppercase tracking-wider text-white",children:"Quay lại"})]})}),e("div",{className:"pointer-events-none absolute -bottom-1 -left-1 opacity-60",children:e(y,{src:oe.assets.bottomLeft,alt:"",width:80,height:80,unoptimized:!0})}),e("div",{className:"pointer-events-none absolute -bottom-1 -right-1 opacity-60",children:e(y,{src:oe.assets.bottomRight,alt:"",width:80,height:80,unoptimized:!0})})]})]})]})]})}
@@ -0,0 +1,22 @@
1
+ import type { ExamEntryAttempt, ExamEntryRoom } from '../../../api/exam-entry.types';
2
+ /** Fullscreen loader used while room/attempt data is being fetched. */
3
+ export declare function EntryLoading(): import("react").JSX.Element;
4
+ /** Child-friendly message screen shared by NO_EXAM / NO_ACCESS states. */
5
+ export declare function EntryMessage({ emoji, title, description, room, tone, onBack, }: {
6
+ emoji: string;
7
+ title: string;
8
+ description: string;
9
+ room?: ExamEntryRoom | null;
10
+ tone: 'sky' | 'red';
11
+ onBack: () => void;
12
+ }): import("react").JSX.Element;
13
+ /** Default (non-themed) waiting room card with exam stats and a start button. */
14
+ export declare function EntryWaitingRoom({ room, state, countdownLabel, attempt, isStarting, onStart, onBack, }: {
15
+ room: ExamEntryRoom;
16
+ state: 'BEFORE_START' | 'WITHIN_WINDOW' | 'EXPIRED' | 'ALREADY_SUBMITTED';
17
+ countdownLabel?: string;
18
+ attempt?: ExamEntryAttempt | null;
19
+ isStarting: boolean;
20
+ onStart: () => void;
21
+ onBack: () => void;
22
+ }): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{ArrowLeft as a,Clock as l,Loader2 as n,Trophy as s}from"lucide-react";import{Button as i}from"../../../components/ui/button";export function EntryLoading(){return e("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-gradient-to-b from-sky-100 via-blue-50 to-indigo-100",children:e(n,{className:"h-10 w-10 animate-spin text-sky-600"})})}export function EntryMessage({emoji:a,title:l,description:n,room:s,tone:i,onBack:r}){const o="red"===i?"text-red-600":"text-sky-700",m="red"===i?"border-red-600 bg-red-500":"border-sky-600 bg-sky-500";return e("div",{className:`fixed inset-0 z-50 flex items-center justify-center overflow-hidden bg-gradient-to-b ${"red"===i?"from-red-50 via-orange-50 to-amber-50":"from-sky-100 via-blue-50 to-indigo-100"}`,children:t("div",{className:"mx-auto max-w-md p-4 text-center",children:[e("div",{className:"mb-6 text-7xl",children:a}),e("h2",{className:`mb-3 text-2xl font-bold ${o}`,children:l}),s&&t("div",{className:"mb-4 rounded-xl bg-white/60 px-5 py-3 text-left text-sm text-sky-700 shadow-sm backdrop-blur-sm",children:[e("p",{className:"text-base font-semibold",children:s.name}),s.code&&t("p",{className:"mt-1 text-sky-500",children:["Mã phòng: ",e("span",{className:"font-mono font-medium",children:s.code})]})]}),e("p",{className:"mb-6 text-base "+("red"===i?"text-red-500":"text-sky-600"),children:n}),e("button",{className:`mx-auto inline-flex items-center gap-2 rounded-2xl border-b-4 px-8 py-3 text-lg font-bold text-white shadow-md transition-all hover:brightness-110 active:translate-y-0.5 active:border-b-2 active:shadow-sm ${m}`,onClick:r,children:"← Quay lại"})]})})}export function EntryWaitingRoom({room:o,state:m,countdownLabel:d,attempt:c,isStarting:x,onStart:b,onBack:h}){return e("div",{className:"fixed inset-0 z-50 flex items-center justify-center overflow-hidden bg-gradient-to-b from-sky-100 via-blue-50 to-indigo-100",children:t("div",{className:"relative z-10 w-[88%] max-w-lg rounded-3xl bg-white p-6 shadow-2xl md:p-8",children:[t("div",{className:"mb-4 rounded-2xl bg-slate-50 px-4 py-3.5 text-center",children:[e("h2",{className:"text-base font-extrabold tracking-wide text-slate-800",children:o.name}),o.exam?.name&&e("p",{className:"mt-0.5 text-xs font-medium text-slate-500",children:o.exam.name})]}),(o.exam?.durationMinutes||o.exam?.totalScore)&&t("div",{className:"mb-4 grid grid-cols-2 gap-3",children:[null!=o.exam?.durationMinutes&&e(r,{icon:e(l,{className:"h-4 w-4"}),value:o.exam.durationMinutes,label:"Phút"}),null!=o.exam?.totalScore&&e(r,{icon:e(s,{className:"h-4 w-4"}),value:o.exam.totalScore,label:"Thang điểm"})]}),"BEFORE_START"===m&&t("p",{className:"mb-5 text-center text-sm font-semibold text-slate-600",children:["Bài thi sẽ bắt đầu sau ",d]}),"EXPIRED"===m&&e("p",{className:"mb-5 text-center text-sm font-semibold text-red-500",children:"Bài thi đã kết thúc."}),"ALREADY_SUBMITTED"===m&&t("p",{className:"mb-5 text-center text-sm font-semibold text-slate-600",children:["Bạn đã hoàn thành bài thi này",null!=c?.totalScore?` • Điểm: ${c.totalScore}`:"","."]}),t("div",{className:"flex flex-col items-center gap-3",children:["WITHIN_WINDOW"===m&&t(i,{onClick:b,disabled:x,className:"min-w-[180px] bg-blue-600 hover:bg-blue-700",children:[x?e(n,{className:"mr-2 h-4 w-4 animate-spin"}):null,"Bắt đầu làm bài"]}),t(i,{variant:"outline",onClick:h,children:[e(a,{className:"mr-2 h-4 w-4"}),"Quay lại"]})]})]})})}function r({icon:a,value:l,label:n}){return t("div",{className:"flex flex-col items-center rounded-2xl bg-slate-50 px-2 py-4",children:[e("div",{className:"mb-2 flex h-8 w-8 items-center justify-center rounded-full bg-slate-100 text-slate-600",children:a}),e("span",{className:"text-2xl font-extrabold text-slate-800",children:l}),e("span",{className:"text-xs font-semibold text-slate-500",children:n})]})}
@@ -0,0 +1,16 @@
1
+ import type { ApiData, ExamEntryAttempt, ExamEntryRoom } from '../../../api/exam-entry.types';
2
+ /** Entry screen state machine, mirrors the host app's EntryState. */
3
+ export type ExamEntryState = 'LOADING' | 'BEFORE_START' | 'WITHIN_WINDOW' | 'EXPIRED' | 'ALREADY_SUBMITTED' | 'NO_EXAM' | 'NO_ACCESS' | 'REGISTRATION_REQUIRED';
4
+ export declare function unwrap<T>(value: ApiData<T> | T): T;
5
+ export declare function unwrapAttempt(value: ExamEntryAttempt | {
6
+ attempt: ExamEntryAttempt;
7
+ }): ExamEntryAttempt;
8
+ /**
9
+ * Determine the entry state from room + attempt data. Ported from the host
10
+ * app's `determineState`, including the `allowMultipleSubmissions` handling.
11
+ */
12
+ export declare function determineEntryState(room: ExamEntryRoom, attempt: ExamEntryAttempt | null): ExamEntryState;
13
+ /** Seconds remaining until a room's start time (clamped at 0). */
14
+ export declare function getSecondsUntilStart(room: ExamEntryRoom): number;
15
+ /** Human-friendly countdown string (days/hours/minutes/seconds). */
16
+ export declare function formatCountdown(seconds: number): string;
@@ -0,0 +1 @@
1
+ const t=new Set(["SUBMITTED","GRADED","PUBLISHED"]);export function unwrap(t){return function(t){return"object"==typeof t&&null!==t&&"data"in t}(t)?t.data:t}export function unwrapAttempt(t){return"attempt"in t?t.attempt:t}export function determineEntryState(e,n){const r=!0===e.allowMultipleSubmissions;if(n){const e=n.status??"";if(t.has(e))return r?"WITHIN_WINDOW":"ALREADY_SUBMITTED";if("IN_PROGRESS"===e)return"WITHIN_WINDOW"}const a=Date.now(),o=e.startDatetime?new Date(e.startDatetime).getTime():null,i=e.endDatetime?new Date(e.endDatetime).getTime():null;return!r&&i&&a>i?"EXPIRED":o&&a<o?"BEFORE_START":"WITHIN_WINDOW"}export function getSecondsUntilStart(t){if(!t.startDatetime)return 0;const e=new Date(t.startDatetime).getTime();return Math.max(0,Math.floor((e-Date.now())/1e3))}export function formatCountdown(t){const e=Math.floor(t/86400),n=Math.floor(t%86400/3600),r=Math.floor(t%3600/60),a=t%60;return e>0?`${e} ngày ${n} giờ ${r} phút`:n>0?`${n} giờ ${r} phút ${a} giây`:r>0?`${r} phút ${a} giây`:`${a} giây`}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Fullscreen loading component for exam entry/taking.
3
+ * Matches the themed waiting-room visual style with a blurred background.
4
+ */
5
+ export declare function FullscreenLoading(): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{Loader2 as s}from"lucide-react";import{getTheme as a}from"../../../shared/lib/themes";export function FullscreenLoading(){const r=a(),i=r.visuals?.backgroundFilter??"grayscale(1) blur(8px) saturate(0.85) brightness(0.9)",l=r.visuals?.backgroundOverlay??"linear-gradient(to bottom, rgba(0, 0, 0, 0.1), transparent, rgba(0, 0, 0, 0.2))";return t("div",{className:"fixed inset-0 z-50 flex items-center justify-center overflow-hidden",children:[e("div",{className:"absolute inset-0 scale-110 bg-cover bg-center",style:{backgroundImage:`url(${r.assets.background})`,filter:i}}),e("div",{className:"absolute inset-0",style:{background:l}}),t("div",{className:"relative z-10 flex flex-col items-center gap-4",children:[e(s,{className:"h-12 w-12 animate-spin text-white drop-shadow-lg"}),e("p",{className:"text-lg font-medium text-white drop-shadow-lg",children:"Đang tải..."})]})]})}
@@ -0,0 +1,7 @@
1
+ export { StudentExamEntryPageContainer } from './StudentExamEntryPageContainer';
2
+ export type { StudentExamEntryPageContainerProps } from './StudentExamEntryPageContainer';
3
+ export { EntryRestartDialog } from './EntryRestartDialog';
4
+ export type { EntryRestartDialogProps } from './EntryRestartDialog';
5
+ export { EntryLoading, EntryMessage, EntryWaitingRoom } from './exam-entry.states';
6
+ export { determineEntryState, formatCountdown, getSecondsUntilStart, unwrap, unwrapAttempt, } from './exam-entry.utils';
7
+ export type { ExamEntryState } from './exam-entry.utils';
@@ -0,0 +1 @@
1
+ export{StudentExamEntryPageContainer}from"./StudentExamEntryPageContainer";export{EntryRestartDialog}from"./EntryRestartDialog";export{EntryLoading,EntryMessage,EntryWaitingRoom}from"./exam-entry.states";export{determineEntryState,formatCountdown,getSecondsUntilStart,unwrap,unwrapAttempt}from"./exam-entry.utils";
@@ -0,0 +1,9 @@
1
+ export declare enum QuestionStatus {
2
+ NEW = "new",
3
+ SAVED = "saved",
4
+ UNSAVED = "unsaved"
5
+ }
6
+ export declare function getQuestionStatus(examId: string, questionNumber: number): QuestionStatus;
7
+ export declare function setQuestionStatus(examId: string, questionNumber: number, status: QuestionStatus): void;
8
+ export declare function clearQuestionStatuses(examId: string): void;
9
+ export declare function initializeQuestionStatusesFromAPI(examId: string, savedQuestionNumbers: number[], totalQuestions: number): void;
@@ -0,0 +1 @@
1
+ export var QuestionStatus;!function(t){t.NEW="new",t.SAVED="saved",t.UNSAVED="unsaved"}(QuestionStatus||(QuestionStatus={}));const t=t=>`oe-exam-question-status:${t}`;function e(e){if("undefined"==typeof window)return{};const n=window.localStorage.getItem(t(e));if(!n)return{};try{return JSON.parse(n)}catch{return{}}}function n(e,n){"undefined"!=typeof window&&window.localStorage.setItem(t(e),JSON.stringify(n))}export function getQuestionStatus(t,n){return e(t)[n]??QuestionStatus.NEW}export function setQuestionStatus(t,o,i){n(t,{...e(t),[o]:i})}export function clearQuestionStatuses(e){"undefined"!=typeof window&&window.localStorage.removeItem(t(e))}export function initializeQuestionStatusesFromAPI(t,e,o){const i=new Set(e.filter(t=>Number.isFinite(t))),u={};for(let t=1;t<=o;t+=1)u[t]=i.has(t)?QuestionStatus.SAVED:QuestionStatus.NEW;n(t,u)}
@@ -1,8 +1,17 @@
1
1
  export * from './take';
2
+ export { StudentExamEntryPageContainer } from './entry';
3
+ export type { StudentExamEntryPageContainerProps } from './entry';
4
+ export { EntryRestartDialog, EntryLoading, EntryMessage, EntryWaitingRoom } from './entry';
5
+ export type { EntryRestartDialogProps } from './entry';
6
+ export { determineEntryState, formatCountdown, getSecondsUntilStart } from './entry';
7
+ export type { ExamEntryState } from './entry';
8
+ export { StudentContestRoomsPageContainer } from './rooms';
9
+ export type { StudentContestRoomsPageContainerProps } from './rooms';
2
10
  export { CreateExamPageContainer } from './CreateExamPageContainer';
3
11
  export type { CreateExamPageContainerProps } from './CreateExamPageContainer';
4
12
  export { CreateExamInfoForm } from './CreateExamInfoForm';
5
13
  export type { CreateExamInfoFormProps } from './CreateExamInfoForm';
14
+ export type { CreateExamTexts } from './create-exam.texts';
6
15
  export { TemplateSelector } from './TemplateSelector';
7
16
  export type { TemplateSelectorProps } from './TemplateSelector';
8
17
  export { ExamCreator } from './ExamCreator';
@@ -12,6 +21,15 @@ export { ExamQuestionGrid } from './ExamQuestionGrid';
12
21
  export { PartAudioUploader } from './PartAudioUploader';
13
22
  export type { PartAudioUploaderProps } from './PartAudioUploader';
14
23
  export { createExamQuestionsApi, createExamQuestionsFetchApi, createMockExamQuestionsApi } from './api';
15
- export type { ExamQuestionsSdkApi, ExamWithTemplate, FetchApiOptions, SaveExamInput, SaveQuestionInput, UpdatePartAudioInput } from './api';
24
+ export type { ExamQuestionsSdkApi, ExamWithTemplate, ExistingQuestionBank, FetchApiOptions, QuestionSearchItem, SaveExamInput, SaveQuestionInput, SaveQuestionResult, SearchQuestionBankInput, SearchQuestionBankResult, UpdatePartAudioInput } from './api';
16
25
  export { ExamQuestionsPageContainer } from './ExamQuestionsPageContainer';
17
26
  export type { ExamQuestionsPageContainerProps } from './ExamQuestionsPageContainer';
27
+ export { UnsavedChangesDialog } from './UnsavedChangesDialog';
28
+ export type { UnsavedChangesDialogProps } from './UnsavedChangesDialog';
29
+ export { CompletionDialog } from './CompletionDialog';
30
+ export type { CompletionDialogProps } from './CompletionDialog';
31
+ export { EXAM_DIFFICULTY, ExamViewDialog } from './ExamViewDialog';
32
+ export type { ExamDifficulty, ExamViewDialogExam, ExamViewDialogProps } from './ExamViewDialog';
33
+ export { ExamPreviewDialog } from './ExamPreviewDialog';
34
+ export type { ExamPreviewDialogProps } from './ExamPreviewDialog';
35
+ export { QuestionStatus, clearQuestionStatuses, getQuestionStatus, initializeQuestionStatusesFromAPI, setQuestionStatus } from './exam-question-status';