@hed-hog/lms 0.0.361 → 0.0.365

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 (434) hide show
  1. package/dist/bitcode-wallet/bitcode-wallet.service.d.ts +66 -0
  2. package/dist/bitcode-wallet/bitcode-wallet.service.d.ts.map +1 -1
  3. package/dist/bitcode-wallet/bitcode-wallet.service.js +91 -0
  4. package/dist/bitcode-wallet/bitcode-wallet.service.js.map +1 -1
  5. package/dist/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.d.ts +8 -0
  6. package/dist/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.d.ts.map +1 -0
  7. package/dist/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.js +40 -0
  8. package/dist/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.js.map +1 -0
  9. package/dist/class-group/class-group.controller.d.ts +16 -16
  10. package/dist/class-group/class-group.service.d.ts +12 -12
  11. package/dist/course/course-audio-transcription.service.d.ts +3 -2
  12. package/dist/course/course-audio-transcription.service.d.ts.map +1 -1
  13. package/dist/course/course-audio-transcription.service.js +49 -8
  14. package/dist/course/course-audio-transcription.service.js.map +1 -1
  15. package/dist/course/course-export-scorm12-worker.service.d.ts +21 -0
  16. package/dist/course/course-export-scorm12-worker.service.d.ts.map +1 -0
  17. package/dist/course/course-export-scorm12-worker.service.js +109 -0
  18. package/dist/course/course-export-scorm12-worker.service.js.map +1 -0
  19. package/dist/course/course-export-scorm12.service.d.ts +42 -0
  20. package/dist/course/course-export-scorm12.service.d.ts.map +1 -0
  21. package/dist/course/course-export-scorm12.service.js +628 -0
  22. package/dist/course/course-export-scorm12.service.js.map +1 -0
  23. package/dist/course/course-export.service.d.ts +84 -0
  24. package/dist/course/course-export.service.d.ts.map +1 -0
  25. package/dist/course/course-export.service.js +237 -0
  26. package/dist/course/course-export.service.js.map +1 -0
  27. package/dist/course/course-lesson.controller.d.ts +4 -0
  28. package/dist/course/course-lesson.controller.d.ts.map +1 -1
  29. package/dist/course/course-lesson.controller.js +10 -0
  30. package/dist/course/course-lesson.controller.js.map +1 -1
  31. package/dist/course/course-structure.controller.d.ts +24 -9
  32. package/dist/course/course-structure.controller.d.ts.map +1 -1
  33. package/dist/course/course-structure.controller.js +30 -3
  34. package/dist/course/course-structure.controller.js.map +1 -1
  35. package/dist/course/course-structure.service.d.ts +25 -3
  36. package/dist/course/course-structure.service.d.ts.map +1 -1
  37. package/dist/course/course-structure.service.js +234 -24
  38. package/dist/course/course-structure.service.js.map +1 -1
  39. package/dist/course/course-video-conversion.service.d.ts +8 -0
  40. package/dist/course/course-video-conversion.service.d.ts.map +1 -1
  41. package/dist/course/course-video-conversion.service.js +87 -51
  42. package/dist/course/course-video-conversion.service.js.map +1 -1
  43. package/dist/course/course-video-hls.service.d.ts +57 -0
  44. package/dist/course/course-video-hls.service.d.ts.map +1 -0
  45. package/dist/course/course-video-hls.service.js +767 -0
  46. package/dist/course/course-video-hls.service.js.map +1 -0
  47. package/dist/course/course.controller.d.ts +115 -11
  48. package/dist/course/course.controller.d.ts.map +1 -1
  49. package/dist/course/course.controller.js +66 -28
  50. package/dist/course/course.controller.js.map +1 -1
  51. package/dist/course/course.mcp-tools.js +1 -1
  52. package/dist/course/course.mcp-tools.js.map +1 -1
  53. package/dist/course/course.module.d.ts.map +1 -1
  54. package/dist/course/course.module.js +13 -0
  55. package/dist/course/course.module.js.map +1 -1
  56. package/dist/course/course.service.d.ts +112 -11
  57. package/dist/course/course.service.d.ts.map +1 -1
  58. package/dist/course/course.service.js +682 -72
  59. package/dist/course/course.service.js.map +1 -1
  60. package/dist/course/dto/cleanup-course-storage.dto.d.ts +6 -0
  61. package/dist/course/dto/cleanup-course-storage.dto.d.ts.map +1 -0
  62. package/dist/course/dto/cleanup-course-storage.dto.js +34 -0
  63. package/dist/course/dto/cleanup-course-storage.dto.js.map +1 -0
  64. package/dist/course/dto/cleanup-upload-history.dto.d.ts +9 -0
  65. package/dist/course/dto/cleanup-upload-history.dto.d.ts.map +1 -0
  66. package/dist/course/dto/cleanup-upload-history.dto.js +36 -0
  67. package/dist/course/dto/cleanup-upload-history.dto.js.map +1 -0
  68. package/dist/course/dto/create-course-bulk-job.dto.d.ts +5 -0
  69. package/dist/course/dto/create-course-bulk-job.dto.d.ts.map +1 -0
  70. package/dist/course/dto/create-course-bulk-job.dto.js +26 -0
  71. package/dist/course/dto/create-course-bulk-job.dto.js.map +1 -0
  72. package/dist/course/dto/create-course-export.dto.d.ts +14 -0
  73. package/dist/course/dto/create-course-export.dto.d.ts.map +1 -0
  74. package/dist/course/dto/create-course-export.dto.js +71 -0
  75. package/dist/course/dto/create-course-export.dto.js.map +1 -0
  76. package/dist/course/dto/create-course-structure-lesson.dto.d.ts +2 -2
  77. package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
  78. package/dist/course/dto/create-course-structure-lesson.dto.js +3 -2
  79. package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
  80. package/dist/course/lms-bulk-upload-automation.service.d.ts +54 -0
  81. package/dist/course/lms-bulk-upload-automation.service.d.ts.map +1 -0
  82. package/dist/course/lms-bulk-upload-automation.service.js +537 -0
  83. package/dist/course/lms-bulk-upload-automation.service.js.map +1 -0
  84. package/dist/course/lms-bulk-upload-infra.service.d.ts +32 -0
  85. package/dist/course/lms-bulk-upload-infra.service.d.ts.map +1 -0
  86. package/dist/course/lms-bulk-upload-infra.service.js +301 -0
  87. package/dist/course/lms-bulk-upload-infra.service.js.map +1 -0
  88. package/dist/course/lms-bulk-upload.constants.d.ts +4 -0
  89. package/dist/course/lms-bulk-upload.constants.d.ts.map +1 -0
  90. package/dist/course/lms-bulk-upload.constants.js +7 -0
  91. package/dist/course/lms-bulk-upload.constants.js.map +1 -0
  92. package/dist/course/lms-bulk-upload.controller.d.ts +144 -1
  93. package/dist/course/lms-bulk-upload.controller.d.ts.map +1 -1
  94. package/dist/course/lms-bulk-upload.controller.js +114 -4
  95. package/dist/course/lms-bulk-upload.controller.js.map +1 -1
  96. package/dist/course/lms-bulk-upload.service.d.ts +153 -3
  97. package/dist/course/lms-bulk-upload.service.d.ts.map +1 -1
  98. package/dist/course/lms-bulk-upload.service.js +659 -21
  99. package/dist/course/lms-bulk-upload.service.js.map +1 -1
  100. package/dist/course/lms-setting.controller.d.ts +6 -2
  101. package/dist/course/lms-setting.controller.d.ts.map +1 -1
  102. package/dist/course/lms-setting.controller.js +25 -8
  103. package/dist/course/lms-setting.controller.js.map +1 -1
  104. package/dist/course/scorm12-schemas.d.ts +4 -0
  105. package/dist/course/scorm12-schemas.d.ts.map +1 -0
  106. package/dist/course/scorm12-schemas.js +9 -0
  107. package/dist/course/scorm12-schemas.js.map +1 -0
  108. package/dist/enterprise/enterprise.controller.d.ts +20 -20
  109. package/dist/enterprise/enterprise.service.d.ts +20 -20
  110. package/dist/enterprise/training/training-admin.controller.d.ts +11 -11
  111. package/dist/enterprise/training/training-admin.service.d.ts +11 -11
  112. package/dist/enterprise/training/training-instructor.controller.d.ts +2 -2
  113. package/dist/enterprise/training/training-instructor.service.d.ts +2 -2
  114. package/dist/enterprise/training/training-student.controller.d.ts +1 -1
  115. package/dist/enterprise/training/training-student.service.d.ts +52 -1
  116. package/dist/enterprise/training/training-student.service.d.ts.map +1 -1
  117. package/dist/enterprise/training/training-student.service.js +217 -4
  118. package/dist/enterprise/training/training-student.service.js.map +1 -1
  119. package/dist/enterprise/training/training-viewer.controller.d.ts +2 -2
  120. package/dist/evaluation/evaluation.controller.d.ts +8 -8
  121. package/dist/evaluation/evaluation.service.d.ts +26 -8
  122. package/dist/evaluation/evaluation.service.d.ts.map +1 -1
  123. package/dist/evaluation/evaluation.service.js +125 -0
  124. package/dist/evaluation/evaluation.service.js.map +1 -1
  125. package/dist/exam/dto/create-standalone-question.dto.d.ts +12 -0
  126. package/dist/exam/dto/create-standalone-question.dto.d.ts.map +1 -0
  127. package/dist/exam/dto/create-standalone-question.dto.js +70 -0
  128. package/dist/exam/dto/create-standalone-question.dto.js.map +1 -0
  129. package/dist/exam/exam.module.d.ts.map +1 -1
  130. package/dist/exam/exam.module.js +2 -1
  131. package/dist/exam/exam.module.js.map +1 -1
  132. package/dist/exam/exam.service.d.ts +21 -0
  133. package/dist/exam/exam.service.d.ts.map +1 -1
  134. package/dist/exam/exam.service.js +80 -0
  135. package/dist/exam/exam.service.js.map +1 -1
  136. package/dist/exam/question.controller.d.ts +27 -0
  137. package/dist/exam/question.controller.d.ts.map +1 -0
  138. package/dist/exam/question.controller.js +53 -0
  139. package/dist/exam/question.controller.js.map +1 -0
  140. package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.d.ts +6 -0
  141. package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.d.ts.map +1 -0
  142. package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.js +34 -0
  143. package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.js.map +1 -0
  144. package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.d.ts +28 -0
  145. package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.d.ts.map +1 -0
  146. package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.js +123 -0
  147. package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.js.map +1 -0
  148. package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.d.ts +4 -0
  149. package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.d.ts.map +1 -0
  150. package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.js +22 -0
  151. package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.js.map +1 -0
  152. package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.d.ts +10 -0
  153. package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.d.ts.map +1 -0
  154. package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.js +52 -0
  155. package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.js.map +1 -0
  156. package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.d.ts +15 -0
  157. package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.d.ts.map +1 -0
  158. package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.js +86 -0
  159. package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.js.map +1 -0
  160. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts +30 -0
  161. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts.map +1 -0
  162. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js +440 -0
  163. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js.map +1 -0
  164. package/dist/lesson-xp-map/lesson-xp-map.controller.d.ts +87 -0
  165. package/dist/lesson-xp-map/lesson-xp-map.controller.d.ts.map +1 -0
  166. package/dist/lesson-xp-map/lesson-xp-map.controller.js +185 -0
  167. package/dist/lesson-xp-map/lesson-xp-map.controller.js.map +1 -0
  168. package/dist/lesson-xp-map/lesson-xp-map.module.d.ts +3 -0
  169. package/dist/lesson-xp-map/lesson-xp-map.module.d.ts.map +1 -0
  170. package/dist/lesson-xp-map/lesson-xp-map.module.js +34 -0
  171. package/dist/lesson-xp-map/lesson-xp-map.module.js.map +1 -0
  172. package/dist/lesson-xp-map/lesson-xp-map.service.d.ts +84 -0
  173. package/dist/lesson-xp-map/lesson-xp-map.service.d.ts.map +1 -0
  174. package/dist/lesson-xp-map/lesson-xp-map.service.js +353 -0
  175. package/dist/lesson-xp-map/lesson-xp-map.service.js.map +1 -0
  176. package/dist/lesson-xp-map/lesson-xp-segment.controller.d.ts +10 -0
  177. package/dist/lesson-xp-map/lesson-xp-segment.controller.d.ts.map +1 -0
  178. package/dist/lesson-xp-map/lesson-xp-segment.controller.js +63 -0
  179. package/dist/lesson-xp-map/lesson-xp-segment.controller.js.map +1 -0
  180. package/dist/lesson-xp-map/lesson-xp-segment.service.d.ts +27 -0
  181. package/dist/lesson-xp-map/lesson-xp-segment.service.d.ts.map +1 -0
  182. package/dist/lesson-xp-map/lesson-xp-segment.service.js +194 -0
  183. package/dist/lesson-xp-map/lesson-xp-segment.service.js.map +1 -0
  184. package/dist/libraries/lms/tsconfig.tsbuildinfo +1 -0
  185. package/dist/lms-commerce-access.subscriber.d.ts +11 -0
  186. package/dist/lms-commerce-access.subscriber.d.ts.map +1 -0
  187. package/dist/lms-commerce-access.subscriber.js +74 -0
  188. package/dist/lms-commerce-access.subscriber.js.map +1 -0
  189. package/dist/lms.module.d.ts.map +1 -1
  190. package/dist/lms.module.js +21 -5
  191. package/dist/lms.module.js.map +1 -1
  192. package/dist/platforma/dto/update-profile.dto.d.ts +17 -0
  193. package/dist/platforma/dto/update-profile.dto.d.ts.map +1 -0
  194. package/dist/platforma/dto/update-profile.dto.js +87 -0
  195. package/dist/platforma/dto/update-profile.dto.js.map +1 -0
  196. package/dist/platforma/platforma-video.service.d.ts +39 -0
  197. package/dist/platforma/platforma-video.service.d.ts.map +1 -0
  198. package/dist/platforma/platforma-video.service.js +301 -0
  199. package/dist/platforma/platforma-video.service.js.map +1 -0
  200. package/dist/platforma/platforma.controller.d.ts +182 -1
  201. package/dist/platforma/platforma.controller.d.ts.map +1 -1
  202. package/dist/platforma/platforma.controller.js +243 -2
  203. package/dist/platforma/platforma.controller.js.map +1 -1
  204. package/dist/platforma/platforma.service.d.ts +27 -0
  205. package/dist/platforma/platforma.service.d.ts.map +1 -0
  206. package/dist/platforma/platforma.service.js +274 -0
  207. package/dist/platforma/platforma.service.js.map +1 -0
  208. package/dist/student-xp/dto/grant-skill-card-xp.dto.d.ts +5 -0
  209. package/dist/student-xp/dto/grant-skill-card-xp.dto.d.ts.map +1 -0
  210. package/dist/student-xp/dto/grant-skill-card-xp.dto.js +26 -0
  211. package/dist/student-xp/dto/grant-skill-card-xp.dto.js.map +1 -0
  212. package/dist/student-xp/student-xp.controller.d.ts +56 -0
  213. package/dist/student-xp/student-xp.controller.d.ts.map +1 -0
  214. package/dist/student-xp/student-xp.controller.js +138 -0
  215. package/dist/student-xp/student-xp.controller.js.map +1 -0
  216. package/dist/student-xp/student-xp.module.d.ts +3 -0
  217. package/dist/student-xp/student-xp.module.d.ts.map +1 -0
  218. package/dist/student-xp/student-xp.module.js +25 -0
  219. package/dist/student-xp/student-xp.module.js.map +1 -0
  220. package/dist/student-xp/student-xp.service.d.ts +81 -0
  221. package/dist/student-xp/student-xp.service.d.ts.map +1 -0
  222. package/dist/student-xp/student-xp.service.js +247 -0
  223. package/dist/student-xp/student-xp.service.js.map +1 -0
  224. package/dist/xp-catalog/dto/create-xp-area.dto.d.ts +12 -0
  225. package/dist/xp-catalog/dto/create-xp-area.dto.d.ts.map +1 -0
  226. package/dist/xp-catalog/dto/create-xp-area.dto.js +63 -0
  227. package/dist/xp-catalog/dto/create-xp-area.dto.js.map +1 -0
  228. package/dist/xp-catalog/dto/create-xp-learning-type.dto.d.ts +11 -0
  229. package/dist/xp-catalog/dto/create-xp-learning-type.dto.d.ts.map +1 -0
  230. package/dist/xp-catalog/dto/create-xp-learning-type.dto.js +57 -0
  231. package/dist/xp-catalog/dto/create-xp-learning-type.dto.js.map +1 -0
  232. package/dist/xp-catalog/dto/create-xp-skill.dto.d.ts +11 -0
  233. package/dist/xp-catalog/dto/create-xp-skill.dto.d.ts.map +1 -0
  234. package/dist/xp-catalog/dto/create-xp-skill.dto.js +57 -0
  235. package/dist/xp-catalog/dto/create-xp-skill.dto.js.map +1 -0
  236. package/dist/xp-catalog/dto/update-xp-area.dto.d.ts +12 -0
  237. package/dist/xp-catalog/dto/update-xp-area.dto.d.ts.map +1 -0
  238. package/dist/xp-catalog/dto/update-xp-area.dto.js +66 -0
  239. package/dist/xp-catalog/dto/update-xp-area.dto.js.map +1 -0
  240. package/dist/xp-catalog/dto/update-xp-learning-type.dto.d.ts +11 -0
  241. package/dist/xp-catalog/dto/update-xp-learning-type.dto.d.ts.map +1 -0
  242. package/dist/xp-catalog/dto/update-xp-learning-type.dto.js +60 -0
  243. package/dist/xp-catalog/dto/update-xp-learning-type.dto.js.map +1 -0
  244. package/dist/xp-catalog/dto/update-xp-skill.dto.d.ts +11 -0
  245. package/dist/xp-catalog/dto/update-xp-skill.dto.d.ts.map +1 -0
  246. package/dist/xp-catalog/dto/update-xp-skill.dto.js +60 -0
  247. package/dist/xp-catalog/dto/update-xp-skill.dto.js.map +1 -0
  248. package/dist/xp-catalog/xp-area.controller.d.ts +25 -0
  249. package/dist/xp-catalog/xp-area.controller.d.ts.map +1 -0
  250. package/dist/xp-catalog/xp-area.controller.js +105 -0
  251. package/dist/xp-catalog/xp-area.controller.js.map +1 -0
  252. package/dist/xp-catalog/xp-area.service.d.ts +35 -0
  253. package/dist/xp-catalog/xp-area.service.d.ts.map +1 -0
  254. package/dist/xp-catalog/xp-area.service.js +168 -0
  255. package/dist/xp-catalog/xp-area.service.js.map +1 -0
  256. package/dist/xp-catalog/xp-catalog.module.d.ts +3 -0
  257. package/dist/xp-catalog/xp-catalog.module.d.ts.map +1 -0
  258. package/dist/xp-catalog/xp-catalog.module.js +29 -0
  259. package/dist/xp-catalog/xp-catalog.module.js.map +1 -0
  260. package/dist/xp-catalog/xp-learning-type.controller.d.ts +20 -0
  261. package/dist/xp-catalog/xp-learning-type.controller.d.ts.map +1 -0
  262. package/dist/xp-catalog/xp-learning-type.controller.js +96 -0
  263. package/dist/xp-catalog/xp-learning-type.controller.js.map +1 -0
  264. package/dist/xp-catalog/xp-learning-type.service.d.ts +30 -0
  265. package/dist/xp-catalog/xp-learning-type.service.d.ts.map +1 -0
  266. package/dist/xp-catalog/xp-learning-type.service.js +146 -0
  267. package/dist/xp-catalog/xp-learning-type.service.js.map +1 -0
  268. package/dist/xp-catalog/xp-skill.controller.d.ts +26 -0
  269. package/dist/xp-catalog/xp-skill.controller.d.ts.map +1 -0
  270. package/dist/xp-catalog/xp-skill.controller.js +113 -0
  271. package/dist/xp-catalog/xp-skill.controller.js.map +1 -0
  272. package/dist/xp-catalog/xp-skill.service.d.ts +37 -0
  273. package/dist/xp-catalog/xp-skill.service.d.ts.map +1 -0
  274. package/dist/xp-catalog/xp-skill.service.js +174 -0
  275. package/dist/xp-catalog/xp-skill.service.js.map +1 -0
  276. package/hedhog/data/evaluation_topic.yaml +17 -0
  277. package/hedhog/data/menu.yaml +91 -7
  278. package/hedhog/data/queue_definition.yaml +48 -0
  279. package/hedhog/data/route.yaml +511 -29
  280. package/hedhog/data/setting_group.yaml +20 -20
  281. package/hedhog/data/xp_area.yaml +164 -0
  282. package/hedhog/data/xp_learning_type.yaml +131 -0
  283. package/hedhog/data/xp_skill.yaml +1834 -0
  284. package/hedhog/frontend/app/achievements/page.tsx.ejs +108 -118
  285. package/hedhog/frontend/app/bitcodes/page.tsx.ejs +22 -34
  286. package/hedhog/frontend/app/bulk-upload-sessions/page.tsx.ejs +1749 -0
  287. package/hedhog/frontend/app/certificates/models/page.tsx.ejs +21 -45
  288. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +40 -74
  289. package/hedhog/frontend/app/classes/page.tsx.ejs +56 -85
  290. package/hedhog/frontend/app/courses/[id]/_components/CourseDangerZoneCard.tsx.ejs +3 -2
  291. package/hedhog/frontend/app/courses/[id]/_components/CourseMediaCard.tsx.ejs +48 -5
  292. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +73 -8
  293. package/hedhog/frontend/app/courses/[id]/structure/_components/course-export-sheet.tsx.ejs +420 -0
  294. package/hedhog/frontend/app/courses/[id]/structure/_components/course-exports-tab.tsx.ejs +308 -0
  295. package/hedhog/frontend/app/courses/[id]/structure/_components/course-operations-tab.tsx.ejs +19 -2
  296. package/hedhog/frontend/app/courses/[id]/structure/_components/course-overview-tab.tsx.ejs +1172 -0
  297. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +16 -0
  298. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +2 -0
  299. package/hedhog/frontend/app/courses/[id]/structure/_components/course-xp-overview-tab.tsx.ejs +623 -0
  300. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson-xp-tab.tsx.ejs +1458 -0
  301. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +87 -46
  302. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-panel.tsx.ejs +8 -3
  303. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +31 -8
  304. package/hedhog/frontend/app/courses/[id]/structure/_components/drag-overlay.tsx.ejs +16 -9
  305. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +618 -480
  306. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +672 -737
  307. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +1 -2
  308. package/hedhog/frontend/app/courses/[id]/structure/_components/sortable-tree-row.tsx.ejs +3 -0
  309. package/hedhog/frontend/app/courses/[id]/structure/_components/store.ts.ejs +1 -0
  310. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs +101 -85
  311. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +24 -10
  312. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row.tsx.ejs +3 -0
  313. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +1 -1
  314. package/hedhog/frontend/app/courses/[id]/structure/_components/use-tree-display-settings.ts.ejs +7 -1
  315. package/hedhog/frontend/app/courses/[id]/structure/_components/xp-premium-pills.tsx.ejs +44 -0
  316. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +6 -10
  317. package/hedhog/frontend/app/courses/[id]/structure/_data/services/course-structure.service.ts.ejs +49 -0
  318. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +4 -3
  319. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-content-overview.ts.ejs +53 -0
  320. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-exports.ts.ejs +106 -0
  321. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +80 -1
  322. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-xp-overview.ts.ejs +76 -0
  323. package/hedhog/frontend/app/courses/[id]/structure/_data/use-lesson-xp-map.ts.ejs +128 -0
  324. package/hedhog/frontend/app/courses/[id]/structure/_data/use-lms-settings-query.ts.ejs +0 -2
  325. package/hedhog/frontend/app/courses/[id]/structure/_data/use-transcription-segments.ts.ejs +30 -0
  326. package/hedhog/frontend/app/courses/[id]/structure/_utils/xp-color-config.ts.ejs +115 -0
  327. package/hedhog/frontend/app/courses/_components/CourseDeleteDialog.tsx.ejs +223 -0
  328. package/hedhog/frontend/app/courses/_components/CourseRowActions.tsx.ejs +89 -0
  329. package/hedhog/frontend/app/courses/page.tsx.ejs +445 -230
  330. package/hedhog/frontend/app/enterprise/page.tsx.ejs +39 -63
  331. package/hedhog/frontend/app/exams/[id]/questions/page.tsx.ejs +53 -77
  332. package/hedhog/frontend/app/exams/page.tsx.ejs +54 -90
  333. package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +23 -36
  334. package/hedhog/frontend/app/instructors/page.tsx.ejs +72 -81
  335. package/hedhog/frontend/app/paths/page.tsx.ejs +40 -68
  336. package/hedhog/frontend/app/training/page.tsx.ejs +40 -68
  337. package/hedhog/frontend/app/xp/areas/page.tsx.ejs +782 -0
  338. package/hedhog/frontend/app/xp/learning-types/page.tsx.ejs +690 -0
  339. package/hedhog/frontend/app/xp/skills/page.tsx.ejs +811 -0
  340. package/hedhog/frontend/messages/en.json +412 -31
  341. package/hedhog/frontend/messages/pt.json +412 -31
  342. package/hedhog/table/course_export.yaml +62 -0
  343. package/hedhog/table/lesson_xp_map.yaml +50 -0
  344. package/hedhog/table/lesson_xp_segment.yaml +40 -0
  345. package/hedhog/table/lesson_xp_segment_area.yaml +24 -0
  346. package/hedhog/table/lesson_xp_segment_learning_type.yaml +24 -0
  347. package/hedhog/table/lesson_xp_segment_skill.yaml +24 -0
  348. package/hedhog/table/lms_bulk_upload_item.yaml +44 -0
  349. package/hedhog/table/lms_bulk_upload_session.yaml +42 -0
  350. package/hedhog/table/student_area_xp.yaml +30 -0
  351. package/hedhog/table/student_learning_type_xp.yaml +30 -0
  352. package/hedhog/table/student_skill_xp.yaml +30 -0
  353. package/hedhog/table/student_xp_event.yaml +34 -0
  354. package/hedhog/table/xp_area.yaml +39 -0
  355. package/hedhog/table/xp_learning_type.yaml +34 -0
  356. package/hedhog/table/xp_skill.yaml +39 -0
  357. package/package.json +13 -8
  358. package/src/bitcode-wallet/bitcode-wallet.service.ts +152 -0
  359. package/src/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.ts +32 -0
  360. package/src/course/course-audio-transcription.service.ts +58 -21
  361. package/src/course/course-export-scorm12-worker.service.ts +124 -0
  362. package/src/course/course-export-scorm12.service.ts +668 -0
  363. package/src/course/course-export.service.ts +280 -0
  364. package/src/course/course-lesson.controller.ts +6 -1
  365. package/src/course/course-structure.controller.ts +23 -1
  366. package/src/course/course-structure.service.ts +273 -7
  367. package/src/course/course-video-conversion.service.ts +113 -75
  368. package/src/course/course-video-hls.service.ts +946 -0
  369. package/src/course/course.controller.ts +54 -21
  370. package/src/course/course.mcp-tools.ts +1 -1
  371. package/src/course/course.module.ts +13 -0
  372. package/src/course/course.service.ts +906 -76
  373. package/src/course/dto/cleanup-course-storage.dto.ts +23 -0
  374. package/src/course/dto/cleanup-upload-history.dto.ts +26 -0
  375. package/src/course/dto/create-course-bulk-job.dto.ts +10 -0
  376. package/src/course/dto/create-course-export.dto.ts +56 -0
  377. package/src/course/dto/create-course-structure-lesson.dto.ts +4 -3
  378. package/src/course/lms-bulk-upload-automation.service.ts +707 -0
  379. package/src/course/lms-bulk-upload-infra.service.ts +360 -0
  380. package/src/course/lms-bulk-upload.constants.ts +5 -0
  381. package/src/course/lms-bulk-upload.controller.ts +110 -4
  382. package/src/course/lms-bulk-upload.service.ts +1092 -204
  383. package/src/course/lms-setting.controller.ts +26 -8
  384. package/src/course/scorm12-schemas.ts +9 -0
  385. package/src/enterprise/training/training-student.service.ts +221 -2
  386. package/src/evaluation/evaluation.service.ts +123 -0
  387. package/src/exam/dto/create-standalone-question.dto.ts +66 -0
  388. package/src/exam/exam.module.ts +2 -1
  389. package/src/exam/exam.service.ts +86 -0
  390. package/src/exam/question.controller.ts +28 -0
  391. package/src/lesson-xp-map/dto/create-lesson-xp-map.dto.ts +17 -0
  392. package/src/lesson-xp-map/dto/create-lesson-xp-segment.dto.ts +102 -0
  393. package/src/lesson-xp-map/dto/review-lesson-xp-map.dto.ts +7 -0
  394. package/src/lesson-xp-map/dto/update-lesson-xp-map.dto.ts +36 -0
  395. package/src/lesson-xp-map/dto/update-lesson-xp-segment.dto.ts +78 -0
  396. package/src/lesson-xp-map/lesson-xp-ai-calculation.service.ts +570 -0
  397. package/src/lesson-xp-map/lesson-xp-map.controller.ts +116 -0
  398. package/src/lesson-xp-map/lesson-xp-map.module.ts +21 -0
  399. package/src/lesson-xp-map/lesson-xp-map.service.ts +442 -0
  400. package/src/lesson-xp-map/lesson-xp-segment.controller.ts +36 -0
  401. package/src/lesson-xp-map/lesson-xp-segment.service.ts +229 -0
  402. package/src/lms-commerce-access.subscriber.ts +88 -0
  403. package/src/lms.module.ts +21 -5
  404. package/src/platforma/dto/update-profile.dto.ts +59 -0
  405. package/src/platforma/platforma-video.service.ts +346 -0
  406. package/src/platforma/platforma.controller.ts +152 -3
  407. package/src/platforma/platforma.service.ts +268 -0
  408. package/src/student-xp/dto/grant-skill-card-xp.dto.ts +10 -0
  409. package/src/student-xp/student-xp.controller.ts +92 -0
  410. package/src/student-xp/student-xp.module.ts +12 -0
  411. package/src/student-xp/student-xp.service.ts +318 -0
  412. package/src/xp-catalog/dto/create-xp-area.dto.ts +40 -0
  413. package/src/xp-catalog/dto/create-xp-learning-type.dto.ts +35 -0
  414. package/src/xp-catalog/dto/create-xp-skill.dto.ts +35 -0
  415. package/src/xp-catalog/dto/update-xp-area.dto.ts +43 -0
  416. package/src/xp-catalog/dto/update-xp-learning-type.dto.ts +38 -0
  417. package/src/xp-catalog/dto/update-xp-skill.dto.ts +38 -0
  418. package/src/xp-catalog/xp-area.controller.ts +64 -0
  419. package/src/xp-catalog/xp-area.service.ts +196 -0
  420. package/src/xp-catalog/xp-catalog.module.ts +16 -0
  421. package/src/xp-catalog/xp-learning-type.controller.ts +59 -0
  422. package/src/xp-catalog/xp-learning-type.service.ts +170 -0
  423. package/src/xp-catalog/xp-skill.controller.ts +71 -0
  424. package/src/xp-catalog/xp-skill.service.ts +205 -0
  425. package/hedhog/data/video_resolution_profile.yaml +0 -7
  426. package/hedhog/frontend/app/video-resolution-profiles/page.tsx.ejs +0 -607
  427. package/hedhog/table/course_video_resolution_profile.yaml +0 -22
  428. package/hedhog/table/video_resolution_profile.yaml +0 -18
  429. package/src/video-resolution-profile/dto/create-video-resolution-profile.dto.ts +0 -16
  430. package/src/video-resolution-profile/dto/update-video-resolution-profile.dto.ts +0 -16
  431. package/src/video-resolution-profile/video-resolution-profile.controller.ts +0 -62
  432. package/src/video-resolution-profile/video-resolution-profile.mcp-tools.ts +0 -128
  433. package/src/video-resolution-profile/video-resolution-profile.module.ts +0 -13
  434. package/src/video-resolution-profile/video-resolution-profile.service.ts +0 -117
@@ -8,28 +8,15 @@ import {
8
8
  SearchBar,
9
9
  ViewModeToggle,
10
10
  } from '@/components/entity-list';
11
+ import { useNotifications } from '@/components/notification-bell';
11
12
  import { Badge } from '@/components/ui/badge';
12
13
  import { Button } from '@/components/ui/button';
13
14
  import { Card, CardContent } from '@/components/ui/card';
14
15
  import { Checkbox } from '@/components/ui/checkbox';
15
- import {
16
- Dialog,
17
- DialogContent,
18
- DialogDescription,
19
- DialogFooter,
20
- DialogHeader,
21
- DialogTitle,
22
- } from '@/components/ui/dialog';
23
- import {
24
- DropdownMenu,
25
- DropdownMenuContent,
26
- DropdownMenuItem,
27
- DropdownMenuSeparator,
28
- DropdownMenuTrigger,
29
- } from '@/components/ui/dropdown-menu';
30
16
  import { Input } from '@/components/ui/input';
31
17
  import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
32
18
  import { Label } from '@/components/ui/label';
19
+ import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
33
20
  import { Separator } from '@/components/ui/separator';
34
21
  import {
35
22
  Sheet,
@@ -38,7 +25,6 @@ import {
38
25
  SheetHeader,
39
26
  SheetTitle,
40
27
  } from '@/components/ui/sheet';
41
- import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
42
28
  import { Skeleton } from '@/components/ui/skeleton';
43
29
  import {
44
30
  Table,
@@ -54,16 +40,12 @@ import { useApp, useQuery } from '@hed-hog/next-app-provider';
54
40
  import { zodResolver } from '@hookform/resolvers/zod';
55
41
  import { AnimatePresence, motion } from 'framer-motion';
56
42
  import {
57
- AlertTriangle,
58
43
  Archive,
59
44
  Award,
60
45
  BookOpen,
61
- Eye,
62
46
  Layers,
63
47
  Loader2,
64
- MoreHorizontal,
65
48
  Paperclip,
66
- Pencil,
67
49
  Plus,
68
50
  Star,
69
51
  Trash2,
@@ -83,6 +65,8 @@ import {
83
65
  DEFAULT_COURSE_FORM_VALUES,
84
66
  getCourseSheetSchema,
85
67
  } from '../_components/course-form-sheet';
68
+ import { CourseDeleteDialog } from './_components/CourseDeleteDialog';
69
+ import { CourseRowActions } from './_components/CourseRowActions';
86
70
 
87
71
  const API_COURSES_CACHE_KEY = 'lms:courses:api-cache';
88
72
 
@@ -111,6 +95,7 @@ interface Curso {
111
95
  criadoEm: string;
112
96
  logoFileId?: number | null;
113
97
  operationsProjectId?: number | null;
98
+ processando?: boolean;
114
99
  }
115
100
 
116
101
  type ApiCourse = {
@@ -138,6 +123,7 @@ type ApiCourse = {
138
123
  createdAt: string;
139
124
  logoFileId?: number | null;
140
125
  operationsProjectId?: number | null;
126
+ hasRunningJobs?: boolean;
141
127
  };
142
128
 
143
129
  type ApiCourseList = {
@@ -169,6 +155,26 @@ type ApiCategoryList = {
169
155
  pageSize: number;
170
156
  };
171
157
 
158
+ type ApiCourseDeletionImpact = {
159
+ fileCount: number;
160
+ totalBytes: number;
161
+ formattedSize: string;
162
+ countsBySource?: Record<string, number>;
163
+ enrollmentCount?: number;
164
+ };
165
+
166
+ type ApiCourseDetail = ApiCourse & {
167
+ deletionImpact?: ApiCourseDeletionImpact;
168
+ };
169
+
170
+ type ApiQueuedDeleteResponse = {
171
+ success: boolean;
172
+ status: 'queued';
173
+ queueJobId: number;
174
+ notificationId: number;
175
+ deletionImpact?: ApiCourseDeletionImpact;
176
+ };
177
+
172
178
  type Locale = {
173
179
  id?: number;
174
180
  code: string;
@@ -177,6 +183,8 @@ type Locale = {
177
183
 
178
184
  type ViewMode = 'cards' | 'list';
179
185
 
186
+ // ── Helpers ───────────────────────────────────────────────────────────────────
187
+
180
188
  function normalizeEnumValue(value?: string | null) {
181
189
  return String(value ?? '')
182
190
  .trim()
@@ -185,52 +193,29 @@ function normalizeEnumValue(value?: string | null) {
185
193
  .toLowerCase();
186
194
  }
187
195
 
188
- function toPtLevel(level?: string | null): CourseSheetFormValues['nivel'] {
189
- const normalizedLevel = normalizeEnumValue(level);
190
-
191
- if (normalizedLevel === 'iniciante' || normalizedLevel === 'beginner') {
192
- return 'iniciante';
193
- }
194
- if (
195
- normalizedLevel === 'intermediario' ||
196
- normalizedLevel === 'intermediate'
197
- ) {
198
- return 'intermediario';
199
- }
200
- if (normalizedLevel === 'avancado' || normalizedLevel === 'advanced') {
201
- return 'avancado';
202
- }
203
-
196
+ function toPtLevel(level?: string | null): Curso['nivel'] {
197
+ const v = normalizeEnumValue(level);
198
+ if (v === 'iniciante' || v === 'beginner') return 'iniciante';
199
+ if (v === 'intermediario' || v === 'intermediate') return 'intermediario';
200
+ if (v === 'avancado' || v === 'advanced') return 'avancado';
204
201
  return 'iniciante';
205
202
  }
206
203
 
204
+ function toPtStatus(status?: string | null): Curso['status'] {
205
+ const v = normalizeEnumValue(status);
206
+ if (v === 'publicado' || v === 'published' || v === 'active' || v === 'ativo')
207
+ return 'publicado';
208
+ if (v === 'rascunho' || v === 'draft') return 'rascunho';
209
+ if (v === 'arquivado' || v === 'archived') return 'arquivado';
210
+ return 'rascunho';
211
+ }
212
+
207
213
  function toPtOfferingType(type?: string | null): Curso['tipoCurso'] {
208
214
  if (type === 'scheduled') return 'agendado';
209
215
  if (type === 'blended') return 'hibrido';
210
216
  return 'on_demand';
211
217
  }
212
218
 
213
- function toPtStatus(status?: string | null): CourseSheetFormValues['status'] {
214
- const normalizedStatus = normalizeEnumValue(status);
215
-
216
- if (
217
- normalizedStatus === 'publicado' ||
218
- normalizedStatus === 'ativo' ||
219
- normalizedStatus === 'active' ||
220
- normalizedStatus === 'published'
221
- ) {
222
- return 'publicado';
223
- }
224
- if (normalizedStatus === 'rascunho' || normalizedStatus === 'draft') {
225
- return 'rascunho';
226
- }
227
- if (normalizedStatus === 'arquivado' || normalizedStatus === 'archived') {
228
- return 'arquivado';
229
- }
230
-
231
- return 'rascunho';
232
- }
233
-
234
219
  function mapApiCourse(course: ApiCourse): Curso {
235
220
  return {
236
221
  id: course.id,
@@ -255,6 +240,7 @@ function mapApiCourse(course: ApiCourse): Curso {
255
240
  criadoEm: course.createdAt ?? '',
256
241
  logoFileId: course.logoFileId ?? null,
257
242
  operationsProjectId: course.operationsProjectId ?? null,
243
+ processando: course.hasRunningJobs ?? false,
258
244
  };
259
245
  }
260
246
 
@@ -288,6 +274,24 @@ function getContrastColor(hex: string) {
288
274
  return luminance > 0.6 ? '#111827' : '#FFFFFF';
289
275
  }
290
276
 
277
+ function formatBytes(bytes: number) {
278
+ if (!Number.isFinite(bytes) || bytes <= 0) {
279
+ return '0 B';
280
+ }
281
+
282
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
283
+ let value = bytes;
284
+ let unitIndex = 0;
285
+
286
+ while (value >= 1024 && unitIndex < units.length - 1) {
287
+ value /= 1024;
288
+ unitIndex += 1;
289
+ }
290
+
291
+ const digits = unitIndex === 0 ? 0 : value >= 10 ? 1 : 2;
292
+ return `${value.toFixed(digits)} ${units[unitIndex]}`;
293
+ }
294
+
291
295
  // ── Constants ─────────────────────────────────────────────────────────────────
292
296
 
293
297
  const NIVEL_COLOR: Record<string, string> = {
@@ -340,8 +344,12 @@ export default function CursosPage() {
340
344
  // UI
341
345
  const [sheetOpen, setSheetOpen] = useState(false);
342
346
  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
347
+ const [confirmationInput, setConfirmationInput] = useState('');
343
348
  const [createCategorySheetOpen, setCreateCategorySheetOpen] = useState(false);
344
349
  const [saving, setSaving] = useState(false);
350
+ const [archivingCourse, setArchivingCourse] = useState(false);
351
+ const [queuingDelete, setQueuingDelete] = useState(false);
352
+ const [loadingDeleteImpact, setLoadingDeleteImpact] = useState(false);
345
353
  const [creatingCategory, setCreatingCategory] = useState(false);
346
354
  const [newCategoryName, setNewCategoryName] = useState('');
347
355
  const [newCategorySlug, setNewCategorySlug] = useState('');
@@ -349,12 +357,23 @@ export default function CursosPage() {
349
357
 
350
358
  // Data
351
359
  const [cursoToDelete, setCursoToDelete] = useState<Curso | null>(null);
360
+ const [deleteImpact, setDeleteImpact] =
361
+ useState<ApiCourseDeletionImpact | null>(null);
352
362
  const [cachedCourseList, setCachedCourseList] =
353
363
  useState<ApiCourseList | null>(null);
354
364
 
355
365
  // Selection
356
366
  const [selectedIds, setSelectedIds] = useState<Set<number>>(new Set());
357
367
 
368
+ // Job tracking
369
+ const [deletingCourseIds, setDeletingCourseIds] = useState<Set<number>>(
370
+ new Set()
371
+ );
372
+ const [notificationIdToCourseId, setNotificationIdToCourseId] = useState<
373
+ Map<number, number>
374
+ >(new Map());
375
+ const { notifications } = useNotifications();
376
+
358
377
  // Search/filter state (reactive)
359
378
  const [buscaInput, setBuscaInput] = useState('');
360
379
  const [filtroStatusInput, setFiltroStatusInput] = useState('todos');
@@ -428,6 +447,8 @@ export default function CursosPage() {
428
447
  });
429
448
  return response.data;
430
449
  },
450
+ refetchInterval: 30_000,
451
+ refetchIntervalInBackground: false,
431
452
  });
432
453
 
433
454
  useEffect(() => {
@@ -446,6 +467,23 @@ export default function CursosPage() {
446
467
  filtroCatInput,
447
468
  ]);
448
469
 
470
+ // Track active deletion jobs from notifications
471
+ useEffect(() => {
472
+ const activeNotifications = notifications.filter(
473
+ (n) => n.progress != null && n.progress < 100
474
+ );
475
+
476
+ const coursesInProgress = new Set<number>();
477
+ activeNotifications.forEach((notification) => {
478
+ const courseId = notificationIdToCourseId.get(notification.id);
479
+ if (courseId) {
480
+ coursesInProgress.add(courseId);
481
+ }
482
+ });
483
+
484
+ setDeletingCourseIds(coursesInProgress);
485
+ }, [notifications, notificationIdToCourseId]);
486
+
449
487
  const { data: statsData } = useQuery<ApiCourseStats>({
450
488
  queryKey: ['lms-courses-stats'],
451
489
  queryFn: async () => {
@@ -455,6 +493,8 @@ export default function CursosPage() {
455
493
  });
456
494
  return response.data;
457
495
  },
496
+ refetchInterval: 30_000,
497
+ refetchIntervalInBackground: false,
458
498
  });
459
499
 
460
500
  const { data: categoryListData, refetch: refetchCategoryOptions } =
@@ -553,15 +593,6 @@ export default function CursosPage() {
553
593
  () => cursos.filter((curso) => selectedIds.has(curso.id)),
554
594
  [cursos, selectedIds]
555
595
  );
556
- const selectedArchivedIds = useMemo(
557
- () =>
558
- selectedCourses
559
- .filter((curso) => curso.status === 'arquivado')
560
- .map((curso) => curso.id),
561
- [selectedCourses]
562
- );
563
- const hasSelectedNonArchived =
564
- selectedCourses.length > selectedArchivedIds.length;
565
596
  const totalPages = Math.max(1, effectiveCourseList?.lastPage ?? 1);
566
597
  const safePage = Math.min(currentPage, totalPages);
567
598
  function clearFilters() {
@@ -591,16 +622,63 @@ export default function CursosPage() {
591
622
  );
592
623
  }
593
624
 
594
- function openDeleteDialog(curso: Curso, e: React.MouseEvent) {
595
- e.stopPropagation();
625
+ const deleteConfirmMatches =
626
+ confirmationInput === cursoToDelete?.tituloComercial;
627
+ const deleteRequiresArchive = cursoToDelete?.status !== 'arquivado';
628
+ const canDeleteCourse =
629
+ Boolean(cursoToDelete) &&
630
+ !deleteRequiresArchive &&
631
+ deleteConfirmMatches &&
632
+ !loadingDeleteImpact &&
633
+ !queuingDelete &&
634
+ !archivingCourse &&
635
+ Boolean(deleteImpact);
636
+
637
+ async function loadDeleteImpact(cursoId: number) {
638
+ setLoadingDeleteImpact(true);
639
+ setDeleteImpact(null);
596
640
 
597
- if (curso.status !== 'arquivado') {
598
- toast.error(t('toasts.deleteOnlyArchived'));
599
- return;
641
+ try {
642
+ const response = await request<ApiCourseDetail>({
643
+ url: `/lms/courses/${cursoId}`,
644
+ method: 'GET',
645
+ });
646
+
647
+ const impact = response.data.deletionImpact;
648
+ if (impact) {
649
+ setDeleteImpact({
650
+ ...impact,
651
+ formattedSize:
652
+ impact.formattedSize || formatBytes(impact.totalBytes ?? 0),
653
+ });
654
+ }
655
+
656
+ setCursoToDelete((previous) =>
657
+ previous && previous.id === cursoId
658
+ ? {
659
+ ...previous,
660
+ status: toPtStatus(response.data.status),
661
+ alunosInscritos:
662
+ typeof impact?.enrollmentCount === 'number'
663
+ ? impact.enrollmentCount
664
+ : previous.alunosInscritos,
665
+ }
666
+ : previous
667
+ );
668
+ } catch {
669
+ toast.error(t('toasts.courseDeleteImpactLoadError'));
670
+ } finally {
671
+ setLoadingDeleteImpact(false);
600
672
  }
673
+ }
601
674
 
675
+ async function openDeleteDialog(curso: Curso, e?: React.MouseEvent) {
676
+ e?.stopPropagation();
677
+ setConfirmationInput('');
678
+ setDeleteImpact(null);
602
679
  setCursoToDelete(curso);
603
680
  setDeleteDialogOpen(true);
681
+ await loadDeleteImpact(curso.id);
604
682
  }
605
683
 
606
684
  // ── Selection ─────────────────────────────────────────────────────────────
@@ -630,6 +708,10 @@ export default function CursosPage() {
630
708
  router.push(`/lms/courses/${curso.id}`);
631
709
  }
632
710
 
711
+ function openCourseInNewTab(curso: Curso) {
712
+ window.open(`/lms/courses/${curso.id}`, '_blank', 'noopener,noreferrer');
713
+ }
714
+
633
715
  async function onSubmit(data: CourseSheetFormValues) {
634
716
  setSaving(true);
635
717
  try {
@@ -673,26 +755,101 @@ export default function CursosPage() {
673
755
 
674
756
  async function confirmDelete() {
675
757
  if (!cursoToDelete) return;
758
+ if (confirmationInput !== cursoToDelete.tituloComercial) {
759
+ toast.error(
760
+ t('form.validationErrors.confirmationMismatch') ||
761
+ 'Nome do curso não corresponde.'
762
+ );
763
+ return;
764
+ }
765
+
676
766
  if (cursoToDelete.status !== 'arquivado') {
677
767
  toast.error(t('toasts.deleteOnlyArchived'));
678
768
  return;
679
769
  }
680
770
 
681
- await request({
682
- url: `/lms/courses/${cursoToDelete.id}`,
683
- method: 'DELETE',
684
- });
685
- setSelectedIds((prev) => {
686
- const n = new Set(prev);
687
- n.delete(cursoToDelete.id);
688
- return n;
689
- });
690
- toast.success(
691
- t('toasts.courseRemoved', { title: cursoToDelete.tituloComercial })
692
- );
693
- setCursoToDelete(null);
694
- setDeleteDialogOpen(false);
695
- await refetchCourses();
771
+ if (!deleteImpact) {
772
+ toast.error(t('toasts.courseDeleteImpactLoadError'));
773
+ return;
774
+ }
775
+
776
+ setQueuingDelete(true);
777
+ try {
778
+ const response = await request<ApiQueuedDeleteResponse>({
779
+ url: `/lms/courses/${cursoToDelete.id}`,
780
+ method: 'DELETE',
781
+ });
782
+
783
+ // Track the deletion job with the course ID
784
+ const notifId = response.data?.notificationId ?? 0;
785
+ if (notifId > 0) {
786
+ setNotificationIdToCourseId((prev) => {
787
+ const next = new Map(prev);
788
+ next.set(notifId, cursoToDelete.id);
789
+ return next;
790
+ });
791
+ setDeletingCourseIds((prev) => {
792
+ const next = new Set(prev);
793
+ next.add(cursoToDelete.id);
794
+ return next;
795
+ });
796
+ }
797
+
798
+ setSelectedIds((prev) => {
799
+ const n = new Set(prev);
800
+ n.delete(cursoToDelete.id);
801
+ return n;
802
+ });
803
+ toast.success(
804
+ t('toasts.courseDeleteQueued', {
805
+ title: cursoToDelete.tituloComercial,
806
+ files:
807
+ response.data?.deletionImpact?.fileCount ?? deleteImpact.fileCount,
808
+ })
809
+ );
810
+ setCursoToDelete(null);
811
+ setDeleteImpact(null);
812
+ setDeleteDialogOpen(false);
813
+ setConfirmationInput('');
814
+ await refetchCourses();
815
+ } catch (error) {
816
+ const message =
817
+ error instanceof Error && error.message ? error.message : '';
818
+
819
+ if (message.includes('ONLY_ARCHIVED_COURSE_CAN_BE_DELETED')) {
820
+ toast.error(t('toasts.deleteOnlyArchived'));
821
+ } else {
822
+ toast.error(t('toasts.courseDeleteQueueError'));
823
+ }
824
+ } finally {
825
+ setQueuingDelete(false);
826
+ }
827
+ }
828
+
829
+ async function archiveCourseForDelete() {
830
+ if (!cursoToDelete || cursoToDelete.status === 'arquivado') return;
831
+
832
+ setArchivingCourse(true);
833
+ try {
834
+ await request({
835
+ url: `/lms/courses/${cursoToDelete.id}`,
836
+ method: 'PATCH',
837
+ data: { status: 'archived' },
838
+ });
839
+
840
+ setCursoToDelete((previous) =>
841
+ previous ? { ...previous, status: 'arquivado' } : previous
842
+ );
843
+ toast.success(
844
+ t('toasts.courseArchived', { title: cursoToDelete.tituloComercial })
845
+ );
846
+ await refetchCourses();
847
+ await loadDeleteImpact(cursoToDelete.id);
848
+ } catch {
849
+ toast.error(t('toasts.courseArchiveError'));
850
+ } finally {
851
+ setArchivingCourse(false);
852
+ }
696
853
  }
697
854
 
698
855
  // ── KPI counts ────────────────────────────────────────────────────────────
@@ -993,10 +1150,21 @@ export default function CursosPage() {
993
1150
  variant="secondary"
994
1151
  className="gap-1.5"
995
1152
  onClick={() => {
996
- toast.info(
997
- t('toasts.coursesArchived', { count: selectedIds.size })
998
- );
999
- setSelectedIds(new Set());
1153
+ Promise.all(
1154
+ Array.from(selectedIds).map((id) =>
1155
+ request({
1156
+ url: `/lms/courses/${id}`,
1157
+ method: 'PATCH',
1158
+ data: { status: 'archived' },
1159
+ })
1160
+ )
1161
+ ).then(async () => {
1162
+ toast.success(
1163
+ t('toasts.coursesArchived', { count: selectedIds.size })
1164
+ );
1165
+ setSelectedIds(new Set());
1166
+ await refetchCourses();
1167
+ });
1000
1168
  }}
1001
1169
  >
1002
1170
  <Archive className="size-3.5" /> {t('bulkActions.archive')}
@@ -1006,28 +1174,7 @@ export default function CursosPage() {
1006
1174
  variant="secondary"
1007
1175
  className="gap-1.5 text-destructive hover:text-destructive"
1008
1176
  onClick={() => {
1009
- if (selectedArchivedIds.length === 0) {
1010
- toast.error(t('toasts.deleteOnlyArchived'));
1011
- return;
1012
- }
1013
-
1014
- if (hasSelectedNonArchived) {
1015
- toast.warning(t('toasts.bulkDeleteArchivedOnly'));
1016
- }
1017
-
1018
- Promise.all(
1019
- selectedArchivedIds.map((id) =>
1020
- request({ url: `/lms/courses/${id}`, method: 'DELETE' })
1021
- )
1022
- ).then(async () => {
1023
- toast.success(
1024
- t('toasts.coursesRemoved', {
1025
- count: selectedArchivedIds.length,
1026
- })
1027
- );
1028
- setSelectedIds(new Set());
1029
- await refetchCourses();
1030
- });
1177
+ toast.info(t('toasts.bulkDeleteUseSingleFlow'));
1031
1178
  }}
1032
1179
  >
1033
1180
  <Trash2 className="size-3.5" /> {t('bulkActions.delete')}
@@ -1082,6 +1229,12 @@ export default function CursosPage() {
1082
1229
  <TableHead>{t('table.headers.level')}</TableHead>
1083
1230
  <TableHead>{t('table.headers.status')}</TableHead>
1084
1231
  <TableHead>{t('table.headers.categories')}</TableHead>
1232
+ <TableHead className="text-right">
1233
+ {t('table.headers.sessions')}
1234
+ </TableHead>
1235
+ <TableHead className="text-right">
1236
+ {t('table.headers.lessons')}
1237
+ </TableHead>
1085
1238
  <TableHead className="text-right">
1086
1239
  {t('cards.studentsLabel')}
1087
1240
  </TableHead>
@@ -1109,6 +1262,12 @@ export default function CursosPage() {
1109
1262
  <TableCell>
1110
1263
  <Skeleton className="h-5 w-28 rounded-full" />
1111
1264
  </TableCell>
1265
+ <TableCell className="text-right">
1266
+ <Skeleton className="ml-auto h-4 w-8" />
1267
+ </TableCell>
1268
+ <TableCell className="text-right">
1269
+ <Skeleton className="ml-auto h-4 w-8" />
1270
+ </TableCell>
1112
1271
  <TableCell className="text-right">
1113
1272
  <Skeleton className="ml-auto h-4 w-12" />
1114
1273
  </TableCell>
@@ -1158,6 +1317,12 @@ export default function CursosPage() {
1158
1317
  onDoubleClick={() =>
1159
1318
  router.push(`/lms/courses/${curso.id}`)
1160
1319
  }
1320
+ onMouseDown={(e) => {
1321
+ if (e.button === 1) {
1322
+ e.preventDefault();
1323
+ openCourseInNewTab(curso);
1324
+ }
1325
+ }}
1161
1326
  title={t('cards.tooltip')}
1162
1327
  >
1163
1328
  <div
@@ -1186,57 +1351,29 @@ export default function CursosPage() {
1186
1351
  <div className="flex items-start gap-2.5">
1187
1352
  <CourseAvatar
1188
1353
  fileId={curso.logoFileId}
1189
- title={curso.nomeInterno}
1354
+ title={curso.tituloComercial}
1190
1355
  className="size-10 shrink-0 rounded-lg"
1191
1356
  iconSize="size-5"
1192
1357
  />
1193
1358
  <div className="min-w-0 flex-1">
1194
- <div className="flex items-start justify-between gap-1">
1195
- <h3 className="line-clamp-1 text-sm font-semibold leading-snug text-foreground">
1196
- {curso.nomeInterno}
1197
- </h3>
1198
- <DropdownMenu>
1199
- <DropdownMenuTrigger asChild>
1200
- <Button
1201
- variant="ghost"
1202
- size="icon"
1203
- className="-mr-1.5 -mt-0.5 size-7 shrink-0"
1204
- onClick={(e) => e.stopPropagation()}
1205
- aria-label={t('table.actions.label')}
1206
- >
1207
- <MoreHorizontal className="size-3.5" />
1208
- </Button>
1209
- </DropdownMenuTrigger>
1210
- <DropdownMenuContent
1211
- align="end"
1212
- className="w-48"
1213
- >
1214
- <DropdownMenuItem
1215
- onClick={(e) => {
1216
- e.stopPropagation();
1217
- goToCourseDetails(curso, e);
1218
- }}
1219
- >
1220
- <Eye className="mr-2 size-4" />{' '}
1221
- {t('table.actions.viewDetails')}
1222
- </DropdownMenuItem>
1223
- <DropdownMenuItem
1224
- onClick={(e) => goToCourseDetails(curso, e)}
1225
- >
1226
- <Pencil className="mr-2 size-4" />{' '}
1227
- {t('table.actions.edit')}
1228
- </DropdownMenuItem>
1229
- <DropdownMenuSeparator />
1230
- <DropdownMenuItem
1231
- disabled={curso.status !== 'arquivado'}
1232
- className="text-destructive focus:text-destructive"
1233
- onClick={(e) => openDeleteDialog(curso, e)}
1234
- >
1235
- <Trash2 className="mr-2 size-4" />{' '}
1236
- {t('table.actions.delete')}
1237
- </DropdownMenuItem>
1238
- </DropdownMenuContent>
1239
- </DropdownMenu>
1359
+ <div
1360
+ className="flex items-center justify-between gap-1"
1361
+ onClick={(e) => e.stopPropagation()}
1362
+ >
1363
+ <p className="truncate text-sm font-semibold leading-tight text-foreground">
1364
+ {curso.tituloComercial}
1365
+ </p>
1366
+ <CourseRowActions
1367
+ className="ml-auto -mr-1"
1368
+ onView={() => goToCourseDetails(curso)}
1369
+ onEdit={() => goToCourseDetails(curso)}
1370
+ onDelete={(event) =>
1371
+ void openDeleteDialog(curso, event)
1372
+ }
1373
+ viewLabel={t('table.actions.viewDetails')}
1374
+ editLabel={t('table.actions.edit')}
1375
+ deleteLabel={t('table.actions.delete')}
1376
+ />
1240
1377
  </div>
1241
1378
 
1242
1379
  <div className="mt-1.5 flex flex-wrap items-center gap-1">
@@ -1251,6 +1388,47 @@ export default function CursosPage() {
1251
1388
  >
1252
1389
  {getStatusLabel(curso)}
1253
1390
  </Badge>
1391
+ {deletingCourseIds.has(curso.id) && (
1392
+ <motion.div
1393
+ initial={{ opacity: 0, scale: 0.8 }}
1394
+ animate={{ opacity: 1, scale: 1 }}
1395
+ exit={{ opacity: 0, scale: 0.8 }}
1396
+ transition={{
1397
+ type: 'spring',
1398
+ stiffness: 300,
1399
+ damping: 30,
1400
+ }}
1401
+ >
1402
+ <Badge
1403
+ variant="secondary"
1404
+ className="gap-1 rounded-full px-2 py-0.5 text-[10px]"
1405
+ >
1406
+ <Loader2 className="size-2.5 animate-spin" />
1407
+ Deletando...
1408
+ </Badge>
1409
+ </motion.div>
1410
+ )}
1411
+ {curso.processando &&
1412
+ !deletingCourseIds.has(curso.id) && (
1413
+ <motion.div
1414
+ initial={{ opacity: 0, scale: 0.8 }}
1415
+ animate={{ opacity: 1, scale: 1 }}
1416
+ exit={{ opacity: 0, scale: 0.8 }}
1417
+ transition={{
1418
+ type: 'spring',
1419
+ stiffness: 300,
1420
+ damping: 30,
1421
+ }}
1422
+ >
1423
+ <Badge
1424
+ variant="secondary"
1425
+ className="gap-1 rounded-full px-2 py-0.5 text-[10px]"
1426
+ >
1427
+ <Loader2 className="size-2.5 animate-spin" />
1428
+ Processando...
1429
+ </Badge>
1430
+ </motion.div>
1431
+ )}
1254
1432
  </div>
1255
1433
  </div>
1256
1434
  </div>
@@ -1296,6 +1474,12 @@ export default function CursosPage() {
1296
1474
  <TableHead>{t('table.headers.level')}</TableHead>
1297
1475
  <TableHead>{t('table.headers.status')}</TableHead>
1298
1476
  <TableHead>{t('table.headers.categories')}</TableHead>
1477
+ <TableHead className="text-right">
1478
+ {t('table.headers.sessions')}
1479
+ </TableHead>
1480
+ <TableHead className="text-right">
1481
+ {t('table.headers.lessons')}
1482
+ </TableHead>
1299
1483
  <TableHead className="text-right">
1300
1484
  {t('cards.studentsLabel')}
1301
1485
  </TableHead>
@@ -1316,6 +1500,12 @@ export default function CursosPage() {
1316
1500
  onDoubleClick={() =>
1317
1501
  router.push(`/lms/courses/${curso.id}`)
1318
1502
  }
1503
+ onMouseDown={(e) => {
1504
+ if (e.button === 1) {
1505
+ e.preventDefault();
1506
+ openCourseInNewTab(curso);
1507
+ }
1508
+ }}
1319
1509
  title={t('cards.tooltip')}
1320
1510
  >
1321
1511
  <TableCell onClick={(e) => e.stopPropagation()}>
@@ -1368,6 +1558,47 @@ export default function CursosPage() {
1368
1558
  >
1369
1559
  {getStatusLabel(curso)}
1370
1560
  </Badge>
1561
+ {deletingCourseIds.has(curso.id) && (
1562
+ <motion.div
1563
+ initial={{ opacity: 0, scale: 0.8 }}
1564
+ animate={{ opacity: 1, scale: 1 }}
1565
+ exit={{ opacity: 0, scale: 0.8 }}
1566
+ transition={{
1567
+ type: 'spring',
1568
+ stiffness: 300,
1569
+ damping: 30,
1570
+ }}
1571
+ >
1572
+ <Badge
1573
+ variant="secondary"
1574
+ className="gap-1 text-[11px]"
1575
+ >
1576
+ <Loader2 className="size-2.5 animate-spin" />
1577
+ Deletando...
1578
+ </Badge>
1579
+ </motion.div>
1580
+ )}
1581
+ {curso.processando &&
1582
+ !deletingCourseIds.has(curso.id) && (
1583
+ <motion.div
1584
+ initial={{ opacity: 0, scale: 0.8 }}
1585
+ animate={{ opacity: 1, scale: 1 }}
1586
+ exit={{ opacity: 0, scale: 0.8 }}
1587
+ transition={{
1588
+ type: 'spring',
1589
+ stiffness: 300,
1590
+ damping: 30,
1591
+ }}
1592
+ >
1593
+ <Badge
1594
+ variant="secondary"
1595
+ className="gap-1 text-[11px]"
1596
+ >
1597
+ <Loader2 className="size-2.5 animate-spin" />
1598
+ Processando...
1599
+ </Badge>
1600
+ </motion.div>
1601
+ )}
1371
1602
  {curso.destaque && (
1372
1603
  <span className="inline-flex items-center gap-1 rounded-full border border-amber-200 bg-amber-50 px-2 py-0.5 text-[11px] font-medium text-amber-700">
1373
1604
  <Star className="size-3 fill-amber-400 text-amber-400" />
@@ -1397,45 +1628,27 @@ export default function CursosPage() {
1397
1628
  )}
1398
1629
  </div>
1399
1630
  </TableCell>
1631
+ <TableCell className="text-right font-medium">
1632
+ {curso.quantidadeSessoes}
1633
+ </TableCell>
1634
+ <TableCell className="text-right font-medium">
1635
+ {curso.quantidadeAulas}
1636
+ </TableCell>
1400
1637
  <TableCell className="text-right font-medium">
1401
1638
  {curso.alunosInscritos.toLocaleString('pt-BR')}
1402
1639
  </TableCell>
1403
1640
  <TableCell onClick={(e) => e.stopPropagation()}>
1404
- <DropdownMenu>
1405
- <DropdownMenuTrigger asChild>
1406
- <Button
1407
- variant="ghost"
1408
- size="icon"
1409
- className="ml-auto size-8"
1410
- aria-label={t('table.actions.label')}
1411
- >
1412
- <MoreHorizontal className="size-4" />
1413
- </Button>
1414
- </DropdownMenuTrigger>
1415
- <DropdownMenuContent align="end" className="w-48">
1416
- <DropdownMenuItem
1417
- onClick={(e) => goToCourseDetails(curso, e)}
1418
- >
1419
- <Eye className="mr-2 size-4" />{' '}
1420
- {t('table.actions.viewDetails')}
1421
- </DropdownMenuItem>
1422
- <DropdownMenuItem
1423
- onClick={(e) => goToCourseDetails(curso, e)}
1424
- >
1425
- <Pencil className="mr-2 size-4" />{' '}
1426
- {t('table.actions.edit')}
1427
- </DropdownMenuItem>
1428
- <DropdownMenuSeparator />
1429
- <DropdownMenuItem
1430
- disabled={curso.status !== 'arquivado'}
1431
- className="text-destructive focus:text-destructive"
1432
- onClick={(e) => openDeleteDialog(curso, e)}
1433
- >
1434
- <Trash2 className="mr-2 size-4" />{' '}
1435
- {t('table.actions.delete')}
1436
- </DropdownMenuItem>
1437
- </DropdownMenuContent>
1438
- </DropdownMenu>
1641
+ <CourseRowActions
1642
+ className="ml-auto"
1643
+ onView={() => goToCourseDetails(curso)}
1644
+ onEdit={() => goToCourseDetails(curso)}
1645
+ onDelete={(event) =>
1646
+ void openDeleteDialog(curso, event)
1647
+ }
1648
+ viewLabel={t('table.actions.viewDetails')}
1649
+ editLabel={t('table.actions.edit')}
1650
+ deleteLabel={t('table.actions.delete')}
1651
+ />
1439
1652
  </TableCell>
1440
1653
  </TableRow>
1441
1654
  );
@@ -1559,42 +1772,44 @@ export default function CursosPage() {
1559
1772
  </Sheet>
1560
1773
 
1561
1774
  {/* ── Delete dialog ────────────────────���────────────────────────────── */}
1562
- <Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
1563
- <DialogContent className="max-w-3xl">
1564
- <DialogHeader>
1565
- <DialogTitle className="flex items-center gap-2">
1566
- <AlertTriangle className="size-5 text-destructive" />
1567
- {t('deleteDialog.title')}
1568
- </DialogTitle>
1569
- <DialogDescription>
1570
- {t('deleteDialog.description')}{' '}
1571
- <strong>{cursoToDelete?.tituloComercial}</strong>?
1572
- {(cursoToDelete?.alunosInscritos ?? 0) > 0 && (
1573
- <span className="mt-2 block rounded-md bg-destructive/10 p-2 text-destructive text-sm">
1574
- {t('deleteDialog.warning', {
1575
- count: cursoToDelete?.alunosInscritos ?? 0,
1576
- })}
1577
- </span>
1578
- )}
1579
- </DialogDescription>
1580
- </DialogHeader>
1581
- <DialogFooter className="gap-2">
1582
- <Button
1583
- variant="outline"
1584
- onClick={() => setDeleteDialogOpen(false)}
1585
- >
1586
- {t('deleteDialog.actions.cancel')}
1587
- </Button>
1588
- <Button
1589
- variant="destructive"
1590
- onClick={confirmDelete}
1591
- className="gap-2"
1592
- >
1593
- <Trash2 className="size-4" /> {t('deleteDialog.actions.delete')}
1594
- </Button>
1595
- </DialogFooter>
1596
- </DialogContent>
1597
- </Dialog>
1775
+ <CourseDeleteDialog
1776
+ open={deleteDialogOpen}
1777
+ onOpenChange={(open) => {
1778
+ setDeleteDialogOpen(open);
1779
+ if (!open) {
1780
+ setConfirmationInput('');
1781
+ setDeleteImpact(null);
1782
+ setCursoToDelete(null);
1783
+ }
1784
+ }}
1785
+ course={
1786
+ cursoToDelete
1787
+ ? {
1788
+ title: cursoToDelete.tituloComercial,
1789
+ status: cursoToDelete.status as
1790
+ | 'publicado'
1791
+ | 'rascunho'
1792
+ | 'arquivado',
1793
+ enrollmentCount: cursoToDelete.alunosInscritos,
1794
+ }
1795
+ : null
1796
+ }
1797
+ impact={deleteImpact}
1798
+ loadingImpact={loadingDeleteImpact}
1799
+ confirmationInput={confirmationInput}
1800
+ onConfirmationInputChange={setConfirmationInput}
1801
+ archiving={archivingCourse}
1802
+ queuingDelete={queuingDelete}
1803
+ canDelete={canDeleteCourse}
1804
+ onArchive={() => void archiveCourseForDelete()}
1805
+ onDelete={() => void confirmDelete()}
1806
+ t={
1807
+ t as unknown as (
1808
+ key: string,
1809
+ values?: Record<string, string | number | Date>
1810
+ ) => string
1811
+ }
1812
+ />
1598
1813
  </Page>
1599
1814
  );
1600
1815
  }