@hed-hog/lms 0.0.361 → 0.0.364

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 (329) hide show
  1. package/dist/bitcode-wallet/bitcode-wallet.service.d.ts +65 -0
  2. package/dist/bitcode-wallet/bitcode-wallet.service.d.ts.map +1 -1
  3. package/dist/bitcode-wallet/bitcode-wallet.service.js +72 -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-lesson.controller.d.ts +4 -0
  16. package/dist/course/course-lesson.controller.d.ts.map +1 -1
  17. package/dist/course/course-lesson.controller.js +10 -0
  18. package/dist/course/course-lesson.controller.js.map +1 -1
  19. package/dist/course/course-structure.controller.d.ts +11 -4
  20. package/dist/course/course-structure.controller.d.ts.map +1 -1
  21. package/dist/course/course-structure.controller.js +14 -0
  22. package/dist/course/course-structure.controller.js.map +1 -1
  23. package/dist/course/course-structure.service.d.ts +15 -1
  24. package/dist/course/course-structure.service.d.ts.map +1 -1
  25. package/dist/course/course-structure.service.js +139 -4
  26. package/dist/course/course-structure.service.js.map +1 -1
  27. package/dist/course/course-video-conversion.service.d.ts +8 -0
  28. package/dist/course/course-video-conversion.service.d.ts.map +1 -1
  29. package/dist/course/course-video-conversion.service.js +87 -51
  30. package/dist/course/course-video-conversion.service.js.map +1 -1
  31. package/dist/course/course.controller.d.ts +73 -1
  32. package/dist/course/course.controller.d.ts.map +1 -1
  33. package/dist/course/course.controller.js +27 -3
  34. package/dist/course/course.controller.js.map +1 -1
  35. package/dist/course/course.module.d.ts.map +1 -1
  36. package/dist/course/course.module.js +2 -0
  37. package/dist/course/course.module.js.map +1 -1
  38. package/dist/course/course.service.d.ts +108 -4
  39. package/dist/course/course.service.d.ts.map +1 -1
  40. package/dist/course/course.service.js +631 -30
  41. package/dist/course/course.service.js.map +1 -1
  42. package/dist/course/dto/cleanup-course-storage.dto.d.ts +6 -0
  43. package/dist/course/dto/cleanup-course-storage.dto.d.ts.map +1 -0
  44. package/dist/course/dto/cleanup-course-storage.dto.js +33 -0
  45. package/dist/course/dto/cleanup-course-storage.dto.js.map +1 -0
  46. package/dist/course/dto/cleanup-upload-history.dto.d.ts +9 -0
  47. package/dist/course/dto/cleanup-upload-history.dto.d.ts.map +1 -0
  48. package/dist/course/dto/cleanup-upload-history.dto.js +36 -0
  49. package/dist/course/dto/cleanup-upload-history.dto.js.map +1 -0
  50. package/dist/course/dto/create-course-bulk-job.dto.d.ts +4 -0
  51. package/dist/course/dto/create-course-bulk-job.dto.d.ts.map +1 -0
  52. package/dist/course/dto/create-course-bulk-job.dto.js +21 -0
  53. package/dist/course/dto/create-course-bulk-job.dto.js.map +1 -0
  54. package/dist/course/lms-bulk-upload-automation.service.d.ts +39 -0
  55. package/dist/course/lms-bulk-upload-automation.service.d.ts.map +1 -0
  56. package/dist/course/lms-bulk-upload-automation.service.js +443 -0
  57. package/dist/course/lms-bulk-upload-automation.service.js.map +1 -0
  58. package/dist/course/lms-bulk-upload-infra.service.d.ts +31 -0
  59. package/dist/course/lms-bulk-upload-infra.service.d.ts.map +1 -0
  60. package/dist/course/lms-bulk-upload-infra.service.js +277 -0
  61. package/dist/course/lms-bulk-upload-infra.service.js.map +1 -0
  62. package/dist/course/lms-bulk-upload.constants.d.ts +4 -0
  63. package/dist/course/lms-bulk-upload.constants.d.ts.map +1 -0
  64. package/dist/course/lms-bulk-upload.constants.js +7 -0
  65. package/dist/course/lms-bulk-upload.constants.js.map +1 -0
  66. package/dist/course/lms-bulk-upload.controller.d.ts +116 -0
  67. package/dist/course/lms-bulk-upload.controller.d.ts.map +1 -1
  68. package/dist/course/lms-bulk-upload.controller.js +71 -2
  69. package/dist/course/lms-bulk-upload.controller.js.map +1 -1
  70. package/dist/course/lms-bulk-upload.service.d.ts +142 -3
  71. package/dist/course/lms-bulk-upload.service.d.ts.map +1 -1
  72. package/dist/course/lms-bulk-upload.service.js +606 -21
  73. package/dist/course/lms-bulk-upload.service.js.map +1 -1
  74. package/dist/course/lms-setting.controller.d.ts +4 -1
  75. package/dist/course/lms-setting.controller.d.ts.map +1 -1
  76. package/dist/course/lms-setting.controller.js +21 -6
  77. package/dist/course/lms-setting.controller.js.map +1 -1
  78. package/dist/enterprise/enterprise.controller.d.ts +20 -20
  79. package/dist/enterprise/enterprise.service.d.ts +20 -20
  80. package/dist/enterprise/training/training-admin.controller.d.ts +11 -11
  81. package/dist/enterprise/training/training-admin.service.d.ts +11 -11
  82. package/dist/enterprise/training/training-instructor.controller.d.ts +2 -2
  83. package/dist/enterprise/training/training-instructor.service.d.ts +2 -2
  84. package/dist/enterprise/training/training-student.controller.d.ts +1 -1
  85. package/dist/enterprise/training/training-student.service.d.ts +1 -1
  86. package/dist/enterprise/training/training-viewer.controller.d.ts +2 -2
  87. package/dist/evaluation/evaluation.controller.d.ts +8 -8
  88. package/dist/evaluation/evaluation.service.d.ts +8 -8
  89. package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.d.ts +6 -0
  90. package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.d.ts.map +1 -0
  91. package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.js +34 -0
  92. package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.js.map +1 -0
  93. package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.d.ts +28 -0
  94. package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.d.ts.map +1 -0
  95. package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.js +123 -0
  96. package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.js.map +1 -0
  97. package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.d.ts +4 -0
  98. package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.d.ts.map +1 -0
  99. package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.js +22 -0
  100. package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.js.map +1 -0
  101. package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.d.ts +10 -0
  102. package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.d.ts.map +1 -0
  103. package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.js +52 -0
  104. package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.js.map +1 -0
  105. package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.d.ts +15 -0
  106. package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.d.ts.map +1 -0
  107. package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.js +86 -0
  108. package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.js.map +1 -0
  109. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts +26 -0
  110. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts.map +1 -0
  111. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js +304 -0
  112. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js.map +1 -0
  113. package/dist/lesson-xp-map/lesson-xp-map.controller.d.ts +87 -0
  114. package/dist/lesson-xp-map/lesson-xp-map.controller.d.ts.map +1 -0
  115. package/dist/lesson-xp-map/lesson-xp-map.controller.js +185 -0
  116. package/dist/lesson-xp-map/lesson-xp-map.controller.js.map +1 -0
  117. package/dist/lesson-xp-map/lesson-xp-map.module.d.ts +3 -0
  118. package/dist/lesson-xp-map/lesson-xp-map.module.d.ts.map +1 -0
  119. package/dist/lesson-xp-map/lesson-xp-map.module.js +34 -0
  120. package/dist/lesson-xp-map/lesson-xp-map.module.js.map +1 -0
  121. package/dist/lesson-xp-map/lesson-xp-map.service.d.ts +84 -0
  122. package/dist/lesson-xp-map/lesson-xp-map.service.d.ts.map +1 -0
  123. package/dist/lesson-xp-map/lesson-xp-map.service.js +353 -0
  124. package/dist/lesson-xp-map/lesson-xp-map.service.js.map +1 -0
  125. package/dist/lesson-xp-map/lesson-xp-segment.controller.d.ts +10 -0
  126. package/dist/lesson-xp-map/lesson-xp-segment.controller.d.ts.map +1 -0
  127. package/dist/lesson-xp-map/lesson-xp-segment.controller.js +63 -0
  128. package/dist/lesson-xp-map/lesson-xp-segment.controller.js.map +1 -0
  129. package/dist/lesson-xp-map/lesson-xp-segment.service.d.ts +27 -0
  130. package/dist/lesson-xp-map/lesson-xp-segment.service.d.ts.map +1 -0
  131. package/dist/lesson-xp-map/lesson-xp-segment.service.js +194 -0
  132. package/dist/lesson-xp-map/lesson-xp-segment.service.js.map +1 -0
  133. package/dist/libraries/lms/tsconfig.tsbuildinfo +1 -0
  134. package/dist/lms.module.d.ts.map +1 -1
  135. package/dist/lms.module.js +17 -2
  136. package/dist/lms.module.js.map +1 -1
  137. package/dist/platforma/dto/update-profile.dto.d.ts +17 -0
  138. package/dist/platforma/dto/update-profile.dto.d.ts.map +1 -0
  139. package/dist/platforma/dto/update-profile.dto.js +87 -0
  140. package/dist/platforma/dto/update-profile.dto.js.map +1 -0
  141. package/dist/platforma/platforma.controller.d.ts +88 -1
  142. package/dist/platforma/platforma.controller.d.ts.map +1 -1
  143. package/dist/platforma/platforma.controller.js +85 -2
  144. package/dist/platforma/platforma.controller.js.map +1 -1
  145. package/dist/platforma/platforma.service.d.ts +27 -0
  146. package/dist/platforma/platforma.service.d.ts.map +1 -0
  147. package/dist/platforma/platforma.service.js +274 -0
  148. package/dist/platforma/platforma.service.js.map +1 -0
  149. package/dist/student-xp/student-xp.controller.d.ts +41 -0
  150. package/dist/student-xp/student-xp.controller.d.ts.map +1 -0
  151. package/dist/student-xp/student-xp.controller.js +114 -0
  152. package/dist/student-xp/student-xp.controller.js.map +1 -0
  153. package/dist/student-xp/student-xp.module.d.ts +3 -0
  154. package/dist/student-xp/student-xp.module.d.ts.map +1 -0
  155. package/dist/student-xp/student-xp.module.js +25 -0
  156. package/dist/student-xp/student-xp.module.js.map +1 -0
  157. package/dist/student-xp/student-xp.service.d.ts +65 -0
  158. package/dist/student-xp/student-xp.service.d.ts.map +1 -0
  159. package/dist/student-xp/student-xp.service.js +197 -0
  160. package/dist/student-xp/student-xp.service.js.map +1 -0
  161. package/dist/xp-catalog/dto/create-xp-area.dto.d.ts +12 -0
  162. package/dist/xp-catalog/dto/create-xp-area.dto.d.ts.map +1 -0
  163. package/dist/xp-catalog/dto/create-xp-area.dto.js +63 -0
  164. package/dist/xp-catalog/dto/create-xp-area.dto.js.map +1 -0
  165. package/dist/xp-catalog/dto/create-xp-learning-type.dto.d.ts +11 -0
  166. package/dist/xp-catalog/dto/create-xp-learning-type.dto.d.ts.map +1 -0
  167. package/dist/xp-catalog/dto/create-xp-learning-type.dto.js +57 -0
  168. package/dist/xp-catalog/dto/create-xp-learning-type.dto.js.map +1 -0
  169. package/dist/xp-catalog/dto/create-xp-skill.dto.d.ts +11 -0
  170. package/dist/xp-catalog/dto/create-xp-skill.dto.d.ts.map +1 -0
  171. package/dist/xp-catalog/dto/create-xp-skill.dto.js +57 -0
  172. package/dist/xp-catalog/dto/create-xp-skill.dto.js.map +1 -0
  173. package/dist/xp-catalog/dto/update-xp-area.dto.d.ts +12 -0
  174. package/dist/xp-catalog/dto/update-xp-area.dto.d.ts.map +1 -0
  175. package/dist/xp-catalog/dto/update-xp-area.dto.js +66 -0
  176. package/dist/xp-catalog/dto/update-xp-area.dto.js.map +1 -0
  177. package/dist/xp-catalog/dto/update-xp-learning-type.dto.d.ts +11 -0
  178. package/dist/xp-catalog/dto/update-xp-learning-type.dto.d.ts.map +1 -0
  179. package/dist/xp-catalog/dto/update-xp-learning-type.dto.js +60 -0
  180. package/dist/xp-catalog/dto/update-xp-learning-type.dto.js.map +1 -0
  181. package/dist/xp-catalog/dto/update-xp-skill.dto.d.ts +11 -0
  182. package/dist/xp-catalog/dto/update-xp-skill.dto.d.ts.map +1 -0
  183. package/dist/xp-catalog/dto/update-xp-skill.dto.js +60 -0
  184. package/dist/xp-catalog/dto/update-xp-skill.dto.js.map +1 -0
  185. package/dist/xp-catalog/xp-area.controller.d.ts +25 -0
  186. package/dist/xp-catalog/xp-area.controller.d.ts.map +1 -0
  187. package/dist/xp-catalog/xp-area.controller.js +105 -0
  188. package/dist/xp-catalog/xp-area.controller.js.map +1 -0
  189. package/dist/xp-catalog/xp-area.service.d.ts +35 -0
  190. package/dist/xp-catalog/xp-area.service.d.ts.map +1 -0
  191. package/dist/xp-catalog/xp-area.service.js +168 -0
  192. package/dist/xp-catalog/xp-area.service.js.map +1 -0
  193. package/dist/xp-catalog/xp-catalog.module.d.ts +3 -0
  194. package/dist/xp-catalog/xp-catalog.module.d.ts.map +1 -0
  195. package/dist/xp-catalog/xp-catalog.module.js +29 -0
  196. package/dist/xp-catalog/xp-catalog.module.js.map +1 -0
  197. package/dist/xp-catalog/xp-learning-type.controller.d.ts +20 -0
  198. package/dist/xp-catalog/xp-learning-type.controller.d.ts.map +1 -0
  199. package/dist/xp-catalog/xp-learning-type.controller.js +96 -0
  200. package/dist/xp-catalog/xp-learning-type.controller.js.map +1 -0
  201. package/dist/xp-catalog/xp-learning-type.service.d.ts +30 -0
  202. package/dist/xp-catalog/xp-learning-type.service.d.ts.map +1 -0
  203. package/dist/xp-catalog/xp-learning-type.service.js +146 -0
  204. package/dist/xp-catalog/xp-learning-type.service.js.map +1 -0
  205. package/dist/xp-catalog/xp-skill.controller.d.ts +26 -0
  206. package/dist/xp-catalog/xp-skill.controller.d.ts.map +1 -0
  207. package/dist/xp-catalog/xp-skill.controller.js +113 -0
  208. package/dist/xp-catalog/xp-skill.controller.js.map +1 -0
  209. package/dist/xp-catalog/xp-skill.service.d.ts +37 -0
  210. package/dist/xp-catalog/xp-skill.service.d.ts.map +1 -0
  211. package/dist/xp-catalog/xp-skill.service.js +174 -0
  212. package/dist/xp-catalog/xp-skill.service.js.map +1 -0
  213. package/hedhog/data/menu.yaml +101 -0
  214. package/hedhog/data/route.yaml +512 -0
  215. package/hedhog/data/setting_group.yaml +1 -1
  216. package/hedhog/data/xp_area.yaml +164 -0
  217. package/hedhog/data/xp_learning_type.yaml +131 -0
  218. package/hedhog/data/xp_skill.yaml +1834 -0
  219. package/hedhog/frontend/app/achievements/page.tsx.ejs +108 -118
  220. package/hedhog/frontend/app/bitcodes/page.tsx.ejs +22 -34
  221. package/hedhog/frontend/app/bulk-upload-sessions/page.tsx.ejs +1453 -0
  222. package/hedhog/frontend/app/certificates/models/page.tsx.ejs +21 -45
  223. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +40 -74
  224. package/hedhog/frontend/app/classes/page.tsx.ejs +56 -85
  225. package/hedhog/frontend/app/courses/[id]/_components/CourseDangerZoneCard.tsx.ejs +3 -2
  226. package/hedhog/frontend/app/courses/[id]/_components/CourseMediaCard.tsx.ejs +48 -5
  227. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +4 -4
  228. package/hedhog/frontend/app/courses/[id]/structure/_components/course-operations-tab.tsx.ejs +19 -2
  229. package/hedhog/frontend/app/courses/[id]/structure/_components/course-overview-tab.tsx.ejs +1170 -0
  230. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +16 -0
  231. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +2 -0
  232. package/hedhog/frontend/app/courses/[id]/structure/_components/course-xp-overview-tab.tsx.ejs +623 -0
  233. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson-xp-tab.tsx.ejs +1458 -0
  234. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +55 -2
  235. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +442 -104
  236. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +296 -49
  237. package/hedhog/frontend/app/courses/[id]/structure/_components/sortable-tree-row.tsx.ejs +3 -0
  238. package/hedhog/frontend/app/courses/[id]/structure/_components/store.ts.ejs +1 -0
  239. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs +101 -85
  240. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +21 -1
  241. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row.tsx.ejs +3 -0
  242. package/hedhog/frontend/app/courses/[id]/structure/_components/use-tree-display-settings.ts.ejs +7 -1
  243. package/hedhog/frontend/app/courses/[id]/structure/_components/xp-premium-pills.tsx.ejs +44 -0
  244. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-content-overview.ts.ejs +54 -0
  245. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +52 -0
  246. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-xp-overview.ts.ejs +76 -0
  247. package/hedhog/frontend/app/courses/[id]/structure/_data/use-lesson-xp-map.ts.ejs +128 -0
  248. package/hedhog/frontend/app/courses/[id]/structure/_data/use-transcription-segments.ts.ejs +30 -0
  249. package/hedhog/frontend/app/courses/[id]/structure/_utils/xp-color-config.ts.ejs +115 -0
  250. package/hedhog/frontend/app/courses/_components/CourseDeleteDialog.tsx.ejs +223 -0
  251. package/hedhog/frontend/app/courses/_components/CourseRowActions.tsx.ejs +89 -0
  252. package/hedhog/frontend/app/courses/page.tsx.ejs +400 -230
  253. package/hedhog/frontend/app/enterprise/page.tsx.ejs +39 -63
  254. package/hedhog/frontend/app/exams/[id]/questions/page.tsx.ejs +53 -77
  255. package/hedhog/frontend/app/exams/page.tsx.ejs +54 -90
  256. package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +23 -36
  257. package/hedhog/frontend/app/instructors/page.tsx.ejs +72 -81
  258. package/hedhog/frontend/app/paths/page.tsx.ejs +40 -68
  259. package/hedhog/frontend/app/training/page.tsx.ejs +40 -68
  260. package/hedhog/frontend/app/xp/areas/page.tsx.ejs +782 -0
  261. package/hedhog/frontend/app/xp/learning-types/page.tsx.ejs +690 -0
  262. package/hedhog/frontend/app/xp/skills/page.tsx.ejs +811 -0
  263. package/hedhog/frontend/messages/en.json +386 -3
  264. package/hedhog/frontend/messages/pt.json +386 -3
  265. package/hedhog/table/lesson_xp_map.yaml +50 -0
  266. package/hedhog/table/lesson_xp_segment.yaml +40 -0
  267. package/hedhog/table/lesson_xp_segment_area.yaml +24 -0
  268. package/hedhog/table/lesson_xp_segment_learning_type.yaml +24 -0
  269. package/hedhog/table/lesson_xp_segment_skill.yaml +24 -0
  270. package/hedhog/table/lms_bulk_upload_item.yaml +44 -0
  271. package/hedhog/table/lms_bulk_upload_session.yaml +42 -0
  272. package/hedhog/table/student_area_xp.yaml +30 -0
  273. package/hedhog/table/student_learning_type_xp.yaml +30 -0
  274. package/hedhog/table/student_skill_xp.yaml +30 -0
  275. package/hedhog/table/student_xp_event.yaml +34 -0
  276. package/hedhog/table/xp_area.yaml +39 -0
  277. package/hedhog/table/xp_learning_type.yaml +34 -0
  278. package/hedhog/table/xp_skill.yaml +39 -0
  279. package/package.json +8 -7
  280. package/src/bitcode-wallet/bitcode-wallet.service.ts +113 -0
  281. package/src/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.ts +32 -0
  282. package/src/course/course-audio-transcription.service.ts +58 -21
  283. package/src/course/course-lesson.controller.ts +6 -1
  284. package/src/course/course-structure.controller.ts +10 -0
  285. package/src/course/course-structure.service.ts +174 -1
  286. package/src/course/course-video-conversion.service.ts +113 -75
  287. package/src/course/course.controller.ts +22 -3
  288. package/src/course/course.module.ts +2 -0
  289. package/src/course/course.service.ts +847 -30
  290. package/src/course/dto/cleanup-course-storage.dto.ts +22 -0
  291. package/src/course/dto/cleanup-upload-history.dto.ts +26 -0
  292. package/src/course/dto/create-course-bulk-job.dto.ts +6 -0
  293. package/src/course/lms-bulk-upload-automation.service.ts +560 -0
  294. package/src/course/lms-bulk-upload-infra.service.ts +327 -0
  295. package/src/course/lms-bulk-upload.constants.ts +5 -0
  296. package/src/course/lms-bulk-upload.controller.ts +79 -3
  297. package/src/course/lms-bulk-upload.service.ts +1029 -204
  298. package/src/course/lms-setting.controller.ts +22 -6
  299. package/src/lesson-xp-map/dto/create-lesson-xp-map.dto.ts +17 -0
  300. package/src/lesson-xp-map/dto/create-lesson-xp-segment.dto.ts +102 -0
  301. package/src/lesson-xp-map/dto/review-lesson-xp-map.dto.ts +7 -0
  302. package/src/lesson-xp-map/dto/update-lesson-xp-map.dto.ts +36 -0
  303. package/src/lesson-xp-map/dto/update-lesson-xp-segment.dto.ts +78 -0
  304. package/src/lesson-xp-map/lesson-xp-ai-calculation.service.ts +396 -0
  305. package/src/lesson-xp-map/lesson-xp-map.controller.ts +116 -0
  306. package/src/lesson-xp-map/lesson-xp-map.module.ts +21 -0
  307. package/src/lesson-xp-map/lesson-xp-map.service.ts +442 -0
  308. package/src/lesson-xp-map/lesson-xp-segment.controller.ts +36 -0
  309. package/src/lesson-xp-map/lesson-xp-segment.service.ts +229 -0
  310. package/src/lms.module.ts +17 -2
  311. package/src/platforma/dto/update-profile.dto.ts +59 -0
  312. package/src/platforma/platforma.controller.ts +57 -2
  313. package/src/platforma/platforma.service.ts +268 -0
  314. package/src/student-xp/student-xp.controller.ts +76 -0
  315. package/src/student-xp/student-xp.module.ts +12 -0
  316. package/src/student-xp/student-xp.service.ts +236 -0
  317. package/src/xp-catalog/dto/create-xp-area.dto.ts +40 -0
  318. package/src/xp-catalog/dto/create-xp-learning-type.dto.ts +35 -0
  319. package/src/xp-catalog/dto/create-xp-skill.dto.ts +35 -0
  320. package/src/xp-catalog/dto/update-xp-area.dto.ts +43 -0
  321. package/src/xp-catalog/dto/update-xp-learning-type.dto.ts +38 -0
  322. package/src/xp-catalog/dto/update-xp-skill.dto.ts +38 -0
  323. package/src/xp-catalog/xp-area.controller.ts +64 -0
  324. package/src/xp-catalog/xp-area.service.ts +196 -0
  325. package/src/xp-catalog/xp-catalog.module.ts +16 -0
  326. package/src/xp-catalog/xp-learning-type.controller.ts +59 -0
  327. package/src/xp-catalog/xp-learning-type.service.ts +170 -0
  328. package/src/xp-catalog/xp-skill.controller.ts +71 -0
  329. package/src/xp-catalog/xp-skill.service.ts +205 -0
@@ -6,12 +6,14 @@ import { zodResolver } from '@hookform/resolvers/zod';
6
6
  import {
7
7
  AlertTriangle,
8
8
  BookOpen,
9
+ Briefcase,
9
10
  CheckCircle2,
10
11
  CircleDot,
11
12
  Clock,
12
13
  Download,
13
14
  Layers,
14
15
  Loader2,
16
+ Mic,
15
17
  Pencil,
16
18
  Plus,
17
19
  Save,
@@ -19,6 +21,7 @@ import {
19
21
  UploadCloud,
20
22
  Video,
21
23
  X,
24
+ Zap,
22
25
  } from 'lucide-react';
23
26
  import { useTranslations } from 'next-intl';
24
27
  import { useRouter } from 'next/navigation';
@@ -31,17 +34,27 @@ import { createDefaultTemplate } from '@/app/(app)/(libraries)/lms/_lib/editor/t
31
34
  import { FfmpegParamsEditor } from '@/components/ffmpeg-params-editor';
32
35
  import { FileTypeIcon } from '@/components/file-type-icon';
33
36
  import { RichTextEditor } from '@/components/rich-text-editor';
37
+ import {
38
+ AlertDialog,
39
+ AlertDialogAction,
40
+ AlertDialogCancel,
41
+ AlertDialogContent,
42
+ AlertDialogDescription,
43
+ AlertDialogFooter,
44
+ AlertDialogHeader,
45
+ AlertDialogTitle,
46
+ } from '@/components/ui/alert-dialog';
34
47
  import { Badge } from '@/components/ui/badge';
35
48
  import { Button } from '@/components/ui/button';
36
49
  import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
37
50
  import {
38
- Dialog,
39
- DialogContent,
40
- DialogDescription,
41
- DialogFooter,
42
- DialogHeader,
43
- DialogTitle,
44
- } from '@/components/ui/dialog';
51
+ DropdownMenu,
52
+ DropdownMenuContent,
53
+ DropdownMenuItem,
54
+ DropdownMenuLabel,
55
+ DropdownMenuSeparator,
56
+ DropdownMenuTrigger,
57
+ } from '@/components/ui/dropdown-menu';
45
58
  import { EntityPicker } from '@/components/ui/entity-picker';
46
59
  import {
47
60
  Form,
@@ -74,6 +87,7 @@ import { cn } from '@/lib/utils';
74
87
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
75
88
  import { useMutation, useQueryClient } from '@tanstack/react-query';
76
89
 
90
+ import { CourseDeleteDialog } from '../../../_components/CourseDeleteDialog';
77
91
  import type {
78
92
  CourseEditFormValues,
79
93
  PickerOption,
@@ -83,7 +97,6 @@ import { CourseClassificationCard } from '../../_components/CourseClassification
83
97
  import { CourseContentCard } from '../../_components/CourseContentCard';
84
98
  import { CourseDangerZoneCard } from '../../_components/CourseDangerZoneCard';
85
99
  import { CourseFlagsCard } from '../../_components/CourseFlagsCard';
86
- import { CourseInstructorsSummaryCard } from './CourseInstructorsSummaryCard';
87
100
  import { CourseMediaCard } from '../../_components/CourseMediaCard';
88
101
  import { CourseRelationsCard } from '../../_components/CourseRelationsCard';
89
102
  import {
@@ -94,8 +107,12 @@ import {
94
107
  } from '../_data/services/course-structure.service';
95
108
  import { useCreateSessionMutation } from '../_data/use-course-structure-mutations';
96
109
  import { courseStructureQueryKey } from '../_data/use-course-structure-query';
97
- import { IconActionTooltip } from './icon-action-tooltip';
110
+ import { useLmsSettingsQuery } from '../_data/use-lms-settings-query';
98
111
  import { CourseOperationsTab } from './course-operations-tab';
112
+ import { CourseOverviewTab } from './course-overview-tab';
113
+ import { CourseXpOverviewTab } from './course-xp-overview-tab';
114
+ import { CourseInstructorsSummaryCard } from './CourseInstructorsSummaryCard';
115
+ import { IconActionTooltip } from './icon-action-tooltip';
99
116
  import { useStructureStore } from './store';
100
117
  import { Resource } from './types';
101
118
 
@@ -129,6 +146,8 @@ type ApiCourseDetail = {
129
146
  instructors?: Array<{ id: number; name: string; avatarId: number | null }>;
130
147
  logoFileId?: number | null;
131
148
  logoFilename?: string | null;
149
+ logoFileSize?: number | null;
150
+ logoMimeType?: string | null;
132
151
  logoImageType?: {
133
152
  suggestedWidth: number | null;
134
153
  suggestedHeight: number | null;
@@ -137,6 +156,8 @@ type ApiCourseDetail = {
137
156
  } | null;
138
157
  bannerFileId?: number | null;
139
158
  bannerFilename?: string | null;
159
+ bannerFileSize?: number | null;
160
+ bannerMimeType?: string | null;
140
161
  bannerImageType?: {
141
162
  suggestedWidth: number | null;
142
163
  suggestedHeight: number | null;
@@ -147,6 +168,26 @@ type ApiCourseDetail = {
147
168
  operationsProjectId?: number | null;
148
169
  operationsProjectCode?: string | null;
149
170
  operationsProjectName?: string | null;
171
+ deletionImpact?: ApiCourseDeletionImpact;
172
+ };
173
+
174
+ type ApiCourseDeletionImpact = {
175
+ fileCount: number;
176
+ totalBytes: number;
177
+ formattedSize: string;
178
+ countsBySource?: Record<string, number>;
179
+ enrollmentCount?: number;
180
+ };
181
+
182
+ type ApiQueuedDeleteResponse = {
183
+ success: boolean;
184
+ status: 'queued';
185
+ queueJobId: number;
186
+ notificationId: number;
187
+ deletionImpact?: Pick<
188
+ ApiCourseDeletionImpact,
189
+ 'fileCount' | 'totalBytes' | 'formattedSize'
190
+ >;
150
191
  };
151
192
 
152
193
  type VideoProfileOption = {
@@ -316,6 +357,24 @@ function formatFileSize(bytes: number): string {
316
357
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
317
358
  }
318
359
 
360
+ function formatBytes(bytes: number) {
361
+ if (!Number.isFinite(bytes) || bytes <= 0) {
362
+ return '0 B';
363
+ }
364
+
365
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
366
+ let value = bytes;
367
+ let unitIndex = 0;
368
+
369
+ while (value >= 1024 && unitIndex < units.length - 1) {
370
+ value /= 1024;
371
+ unitIndex += 1;
372
+ }
373
+
374
+ const digits = unitIndex === 0 ? 0 : value >= 10 ? 1 : 2;
375
+ return `${value.toFixed(digits)} ${units[unitIndex]}`;
376
+ }
377
+
319
378
  function mapApiCourseResourceToLocal(
320
379
  item: CourseResourceApi
321
380
  ): CourseResourceItem {
@@ -378,6 +437,65 @@ function buildSchema(t: (key: string) => string) {
378
437
  });
379
438
  }
380
439
 
440
+ // ── Skeleton ──────────────────────────────────────────────────────────────────
441
+
442
+ function EditorCourseSkeleton() {
443
+ return (
444
+ <div className="flex flex-col h-full min-h-0">
445
+ {/* Header */}
446
+ <div className="flex items-center gap-3 px-4 py-3 border-b bg-muted/30 shrink-0">
447
+ <Skeleton className="size-9 rounded-lg shrink-0" />
448
+ <div className="flex-1 min-w-0 flex flex-col gap-1">
449
+ <Skeleton className="h-4 w-16" />
450
+ <Skeleton className="h-3 w-32" />
451
+ </div>
452
+ <div className="flex items-center gap-1.5">
453
+ <Skeleton className="h-5 w-12 rounded-full" />
454
+ <Skeleton className="h-5 w-12 rounded-full" />
455
+ <Skeleton className="h-5 w-14 rounded-full" />
456
+ <Skeleton className="h-5 w-16 rounded-full" />
457
+ </div>
458
+ </div>
459
+ {/* Tabs bar */}
460
+ <div className="flex items-center gap-1 px-3 py-1 border-b bg-muted/50 shrink-0 overflow-hidden">
461
+ {Array.from({ length: 6 }).map((_, i) => (
462
+ <Skeleton key={i} className="h-7 w-16 rounded-md shrink-0" />
463
+ ))}
464
+ </div>
465
+ {/* Tab content — estrutura */}
466
+ <div className="flex-1 min-h-0 overflow-y-auto p-3 flex flex-col gap-3">
467
+ <div className="rounded-lg border bg-muted/20 p-3 flex flex-col gap-3">
468
+ <Skeleton className="h-3 w-28" />
469
+ {[80, 56, 96].map((w, i) => (
470
+ <div key={i} className="flex flex-col gap-1">
471
+ <Skeleton className="h-3 w-20" />
472
+ <Skeleton className={`h-8 w-full max-w-[${w}%]`} />
473
+ </div>
474
+ ))}
475
+ </div>
476
+ <div className="rounded-lg border bg-muted/20 p-3 flex flex-col gap-3">
477
+ <Skeleton className="h-3 w-24" />
478
+ <div className="flex flex-col gap-1">
479
+ <Skeleton className="h-3 w-16" />
480
+ <Skeleton className="h-20 w-full" />
481
+ </div>
482
+ </div>
483
+ <div className="rounded-lg border bg-muted/20 p-3 flex flex-col gap-3">
484
+ <Skeleton className="h-3 w-20" />
485
+ <div className="grid grid-cols-2 gap-2">
486
+ {[1, 2, 3, 4].map((i) => (
487
+ <div key={i} className="flex flex-col gap-1">
488
+ <Skeleton className="h-3 w-14" />
489
+ <Skeleton className="h-8 w-full" />
490
+ </div>
491
+ ))}
492
+ </div>
493
+ </div>
494
+ </div>
495
+ </div>
496
+ );
497
+ }
498
+
381
499
  // ── Component ─────────────────────────────────────────────────────────────────
382
500
 
383
501
  export function EditorCourse() {
@@ -389,6 +507,7 @@ export function EditorCourse() {
389
507
 
390
508
  const { request, currentLocaleCode, locales } = useApp();
391
509
  const t = useTranslations('lms.CursoEditPage');
510
+ const courseListT = useTranslations('lms.CoursesPage');
392
511
  const courseLocaleLabel = currentLocaleCode?.startsWith('en')
393
512
  ? 'Course Language'
394
513
  : 'Idioma do Curso';
@@ -396,9 +515,12 @@ export function EditorCourse() {
396
515
  const isMobile = useIsMobile();
397
516
  const queryClient = useQueryClient();
398
517
  const createSessionMutation = useCreateSessionMutation();
518
+ const lmsSettings = useLmsSettingsQuery();
399
519
 
400
520
  // ── UI state ────────────────────────────────────────────────────────────────
401
- const [activeTab, setActiveTab] = useState('estrutura');
521
+ const [activeTab, setActiveTab] = useState('visao-geral');
522
+ const [pendingBulkJob, setPendingBulkJob] = useState<'transcription' | 'xp_recalculation' | null>(null);
523
+ const [isBulkJobPending, setIsBulkJobPending] = useState(false);
402
524
  const [categoryEditSheetOpen, setCategoryEditSheetOpen] = useState(false);
403
525
  const [editingCategoryId, setEditingCategoryId] = useState<number | null>(
404
526
  null
@@ -417,7 +539,15 @@ export function EditorCourse() {
417
539
  >('active');
418
540
  const [savingCategoryEdit, setSavingCategoryEdit] = useState(false);
419
541
  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
420
- const [deleting, setDeleting] = useState(false);
542
+ const [confirmationInput, setConfirmationInput] = useState('');
543
+ const [archivingCourse, setArchivingCourse] = useState(false);
544
+ const [queuingDelete, setQueuingDelete] = useState(false);
545
+ const [loadingDeleteImpact, setLoadingDeleteImpact] = useState(false);
546
+ const [deleteImpact, setDeleteImpact] =
547
+ useState<ApiCourseDeletionImpact | null>(null);
548
+ const [deleteCourseStatus, setDeleteCourseStatus] = useState<
549
+ CourseEditFormValues['status'] | null
550
+ >(null);
421
551
  const [logoPreview, setLogoPreview] = useState<string | null>(null);
422
552
  const [bannerPreview, setBannerPreview] = useState<string | null>(null);
423
553
  const [uploadingLogo, setUploadingLogo] = useState(false);
@@ -453,19 +583,21 @@ export function EditorCourse() {
453
583
  const [savingVideoProfileEdit, setSavingVideoProfileEdit] = useState(false);
454
584
 
455
585
  // ── Queries ─────────────────────────────────────────────────────────────────
456
- const { data: apiCourse, refetch: refetchCourse } = useQuery<ApiCourseDetail>(
457
- {
458
- queryKey: ['lms-course-detail', courseId],
459
- enabled: Boolean(courseId),
460
- queryFn: async () => {
461
- const response = await request<ApiCourseDetail>({
462
- url: `/lms/courses/${courseId}`,
463
- method: 'GET',
464
- });
465
- return response.data;
466
- },
467
- }
468
- );
586
+ const {
587
+ data: apiCourse,
588
+ refetch: refetchCourse,
589
+ isLoading: isLoadingCourse,
590
+ } = useQuery<ApiCourseDetail>({
591
+ queryKey: ['lms-course-detail', courseId],
592
+ enabled: Boolean(courseId),
593
+ queryFn: async () => {
594
+ const response = await request<ApiCourseDetail>({
595
+ url: `/lms/courses/${courseId}`,
596
+ method: 'GET',
597
+ });
598
+ return response.data;
599
+ },
600
+ });
469
601
 
470
602
  const { data: categoryListData, refetch: refetchCategoryOptions } =
471
603
  useQuery<ApiCategoryList>({
@@ -571,6 +703,12 @@ export function EditorCourse() {
571
703
 
572
704
  const showOperationsTab = operationsConfig?.isAvailable === true;
573
705
 
706
+ useEffect(() => {
707
+ if (apiCourse?.status) {
708
+ setDeleteCourseStatus(toPtStatus(apiCourse.status));
709
+ }
710
+ }, [apiCourse?.status]);
711
+
574
712
  // ── Save mutation ───────────────────────────────────────────────────────────
575
713
  const { mutate: saveCourse, isPending: saving } = useMutation({
576
714
  mutationFn: async (data: CourseEditFormValues) => {
@@ -913,7 +1051,7 @@ export function EditorCourse() {
913
1051
 
914
1052
  useEffect(() => {
915
1053
  if (!showVideoTab && activeTab === 'videos') {
916
- setActiveTab('estrutura');
1054
+ setActiveTab('detalhes');
917
1055
  }
918
1056
  }, [showVideoTab, activeTab]);
919
1057
 
@@ -1380,15 +1518,132 @@ export function EditorCourse() {
1380
1518
  }
1381
1519
  }
1382
1520
 
1521
+ const deleteCourseTitle = apiCourse?.title?.trim() || course.title.trim();
1522
+ const deleteConfirmMatches = confirmationInput === deleteCourseTitle;
1523
+ const deleteRequiresArchive = deleteCourseStatus !== 'arquivado';
1524
+ const canDeleteCourse =
1525
+ Boolean(courseId) &&
1526
+ !deleteRequiresArchive &&
1527
+ deleteConfirmMatches &&
1528
+ !loadingDeleteImpact &&
1529
+ !queuingDelete &&
1530
+ !archivingCourse &&
1531
+ Boolean(deleteImpact);
1532
+
1533
+ async function loadDeleteImpact() {
1534
+ if (!courseId) return;
1535
+
1536
+ setLoadingDeleteImpact(true);
1537
+ setDeleteImpact(null);
1538
+
1539
+ try {
1540
+ const response = await request<ApiCourseDetail>({
1541
+ url: `/lms/courses/${courseId}`,
1542
+ method: 'GET',
1543
+ });
1544
+
1545
+ const impact = response.data.deletionImpact;
1546
+
1547
+ setDeleteCourseStatus(toPtStatus(response.data.status));
1548
+ setDeleteImpact(
1549
+ impact
1550
+ ? {
1551
+ ...impact,
1552
+ formattedSize:
1553
+ impact.formattedSize || formatBytes(impact.totalBytes ?? 0),
1554
+ }
1555
+ : null
1556
+ );
1557
+ } catch {
1558
+ toast.error(courseListT('toasts.courseDeleteImpactLoadError'));
1559
+ } finally {
1560
+ setLoadingDeleteImpact(false);
1561
+ }
1562
+ }
1563
+
1564
+ function openDeleteDialog() {
1565
+ setConfirmationInput('');
1566
+ setDeleteImpact(null);
1567
+ setDeleteDialogOpen(true);
1568
+ void loadDeleteImpact();
1569
+ }
1570
+
1571
+ async function archiveCourseForDelete() {
1572
+ if (!courseId || deleteCourseStatus === 'arquivado') return;
1573
+
1574
+ setArchivingCourse(true);
1575
+ try {
1576
+ await request({
1577
+ url: `/lms/courses/${courseId}`,
1578
+ method: 'PATCH',
1579
+ data: { status: 'archived' },
1580
+ });
1581
+
1582
+ setDeleteCourseStatus('arquivado');
1583
+ form.setValue('status', 'arquivado', {
1584
+ shouldDirty: false,
1585
+ shouldTouch: false,
1586
+ shouldValidate: false,
1587
+ });
1588
+ toast.success(
1589
+ courseListT('toasts.courseArchived', { title: deleteCourseTitle })
1590
+ );
1591
+ await refetchCourse();
1592
+ await loadDeleteImpact();
1593
+ } catch {
1594
+ toast.error(courseListT('toasts.courseArchiveError'));
1595
+ } finally {
1596
+ setArchivingCourse(false);
1597
+ }
1598
+ }
1599
+
1383
1600
  async function handleDelete() {
1384
- setDeleting(true);
1601
+ if (!courseId) return;
1602
+ if (confirmationInput !== deleteCourseTitle) {
1603
+ toast.error(
1604
+ courseListT('form.validationErrors.confirmationMismatch') ||
1605
+ 'Nome do curso não corresponde.'
1606
+ );
1607
+ return;
1608
+ }
1609
+
1610
+ if (deleteCourseStatus !== 'arquivado') {
1611
+ toast.error(courseListT('toasts.deleteOnlyArchived'));
1612
+ return;
1613
+ }
1614
+
1615
+ if (!deleteImpact) {
1616
+ toast.error(courseListT('toasts.courseDeleteImpactLoadError'));
1617
+ return;
1618
+ }
1619
+
1620
+ setQueuingDelete(true);
1385
1621
  try {
1386
- await request({ url: `/lms/courses/${courseId}`, method: 'DELETE' });
1622
+ const response = await request<ApiQueuedDeleteResponse>({
1623
+ url: `/lms/courses/${courseId}`,
1624
+ method: 'DELETE',
1625
+ });
1626
+
1387
1627
  setDeleteDialogOpen(false);
1388
- toast.success(t('toasts.courseDeleted'));
1628
+ toast.success(
1629
+ courseListT('toasts.courseDeleteQueued', {
1630
+ title: deleteCourseTitle,
1631
+ files:
1632
+ response.data?.deletionImpact?.fileCount ?? deleteImpact.fileCount,
1633
+ })
1634
+ );
1389
1635
  router.push('/lms/courses');
1636
+ } catch (error) {
1637
+ const message =
1638
+ error instanceof Error && error.message ? error.message : '';
1639
+
1640
+ if (message.includes('ONLY_ARCHIVED_COURSE_CAN_BE_DELETED')) {
1641
+ toast.error(courseListT('toasts.deleteOnlyArchived'));
1642
+ } else {
1643
+ toast.error(courseListT('toasts.courseDeleteQueueError'));
1644
+ }
1390
1645
  } finally {
1391
- setDeleting(false);
1646
+ setQueuingDelete(false);
1392
1647
  }
1393
1648
  }
1394
1649
 
@@ -1431,7 +1686,29 @@ export function EditorCourse() {
1431
1686
  toast.error(first?.message ?? 'Verifique os campos obrigatórios');
1432
1687
  }
1433
1688
 
1689
+ // ── Bulk jobs ─────────────────────────────────────────────────────────────────
1690
+ const handleBulkJob = async (jobType: 'transcription' | 'xp_recalculation') => {
1691
+ setIsBulkJobPending(true);
1692
+ try {
1693
+ const res = await request<{ queued: number; skipped: number }>({
1694
+ url: `/lms/courses/${courseId}/structure/bulk-jobs`,
1695
+ method: 'POST',
1696
+ data: { jobType },
1697
+ });
1698
+ toast.success(`${res.data?.queued ?? 0} job(s) enfileirado(s), ${res.data?.skipped ?? 0} ignorado(s).`);
1699
+ } catch {
1700
+ toast.error('Erro ao enfileirar jobs em massa.');
1701
+ } finally {
1702
+ setIsBulkJobPending(false);
1703
+ setPendingBulkJob(null);
1704
+ }
1705
+ };
1706
+
1434
1707
  // ── Render ────────────────────────────────────────────────────────────────────
1708
+ if (isLoadingCourse && !apiCourse) {
1709
+ return <EditorCourseSkeleton />;
1710
+ }
1711
+
1435
1712
  return (
1436
1713
  <Form {...form}>
1437
1714
  <form
@@ -1553,6 +1830,73 @@ export function EditorCourse() {
1553
1830
  </TooltipContent>
1554
1831
  </Tooltip>
1555
1832
  </TooltipProvider>
1833
+
1834
+ {/* ── Bulk jobs menu ────────────────────────────────────────── */}
1835
+ <DropdownMenu>
1836
+ <DropdownMenuTrigger asChild>
1837
+ <Button
1838
+ type="button"
1839
+ variant="ghost"
1840
+ size="icon"
1841
+ className="size-6 shrink-0"
1842
+ disabled={isBulkJobPending}
1843
+ >
1844
+ {isBulkJobPending ? (
1845
+ <Loader2 className="size-3.5 animate-spin" />
1846
+ ) : (
1847
+ <Briefcase className="size-3.5" />
1848
+ )}
1849
+ </Button>
1850
+ </DropdownMenuTrigger>
1851
+ <DropdownMenuContent align="end" className="w-56">
1852
+ <DropdownMenuLabel className="text-xs">Jobs em massa</DropdownMenuLabel>
1853
+ <DropdownMenuSeparator />
1854
+ {lmsSettings.transcriptionEnabled && (
1855
+ <DropdownMenuItem
1856
+ onSelect={() => setPendingBulkJob('transcription')}
1857
+ className="gap-2"
1858
+ >
1859
+ <Mic className="size-4 text-muted-foreground" />
1860
+ Refazer transcrição de todas as aulas
1861
+ </DropdownMenuItem>
1862
+ )}
1863
+ <DropdownMenuItem
1864
+ onSelect={() => setPendingBulkJob('xp_recalculation')}
1865
+ className="gap-2"
1866
+ >
1867
+ <Zap className="size-4 text-muted-foreground" />
1868
+ Recalcular XP de todas as aulas
1869
+ </DropdownMenuItem>
1870
+ </DropdownMenuContent>
1871
+ </DropdownMenu>
1872
+
1873
+ <AlertDialog
1874
+ open={pendingBulkJob !== null}
1875
+ onOpenChange={(open) => { if (!open) setPendingBulkJob(null); }}
1876
+ >
1877
+ <AlertDialogContent>
1878
+ <AlertDialogHeader>
1879
+ <AlertDialogTitle>
1880
+ {pendingBulkJob === 'transcription'
1881
+ ? 'Refazer transcrição de todas as aulas?'
1882
+ : 'Recalcular XP de todas as aulas?'}
1883
+ </AlertDialogTitle>
1884
+ <AlertDialogDescription>
1885
+ {pendingBulkJob === 'transcription'
1886
+ ? `Um job de transcrição será enfileirado para cada aula que possuir arquivo de áudio (${lessons.length} aulas no total). Aulas sem áudio serão ignoradas.`
1887
+ : `Um job de recálculo de XP será enfileirado para cada aula que possuir transcrição (${lessons.length} aulas no total). Aulas sem transcrição serão ignoradas.`}
1888
+ </AlertDialogDescription>
1889
+ </AlertDialogHeader>
1890
+ <AlertDialogFooter>
1891
+ <AlertDialogCancel>Cancelar</AlertDialogCancel>
1892
+ <AlertDialogAction
1893
+ onClick={() => pendingBulkJob && handleBulkJob(pendingBulkJob)}
1894
+ >
1895
+ Confirmar
1896
+ </AlertDialogAction>
1897
+ </AlertDialogFooter>
1898
+ </AlertDialogContent>
1899
+ </AlertDialog>
1556
1900
  </div>
1557
1901
  </div>
1558
1902
 
@@ -1573,14 +1917,15 @@ export function EditorCourse() {
1573
1917
  >
1574
1918
  {(
1575
1919
  [
1576
- 'estrutura',
1577
- 'sobre',
1920
+ 'visao-geral',
1921
+ 'detalhes',
1922
+ 'xp',
1578
1923
  'midia',
1579
1924
  'recursos',
1580
- 'extra',
1581
1925
  ...(showVideoTab ? ['videos'] : []),
1582
1926
  ...(showOperationsTab ? ['operacoes'] : []),
1583
1927
  'publicacao',
1928
+ 'extra',
1584
1929
  ] as const
1585
1930
  ).map((tab) => (
1586
1931
  <TabsTrigger
@@ -1593,30 +1938,40 @@ export function EditorCourse() {
1593
1938
  : 'h-8 px-3 text-xs'
1594
1939
  )}
1595
1940
  >
1596
- {tab === 'estrutura'
1597
- ? t('structureEditor.tabs.structure')
1598
- : tab === 'sobre'
1599
- ? t('structureEditor.tabs.about')
1600
- : tab === 'midia'
1601
- ? t('structureEditor.tabs.media')
1602
- : tab === 'recursos'
1603
- ? t('structureEditor.tabs.resources')
1604
- : tab === 'extra'
1605
- ? t('structureEditor.tabs.extra')
1606
- : tab === 'videos'
1607
- ? t('structureEditor.tabs.videoProfiles')
1608
- : tab === 'operacoes'
1609
- ? 'Operações'
1610
- : t('structureEditor.tabs.publish')}
1941
+ {tab === 'visao-geral'
1942
+ ? t('structureEditor.tabs.overview')
1943
+ : tab === 'detalhes'
1944
+ ? t('structureEditor.tabs.details')
1945
+ : tab === 'xp'
1946
+ ? t('structureEditor.tabs.xp')
1947
+ : tab === 'midia'
1948
+ ? t('structureEditor.tabs.media')
1949
+ : tab === 'recursos'
1950
+ ? t('structureEditor.tabs.resources')
1951
+ : tab === 'extra'
1952
+ ? t('structureEditor.tabs.extra')
1953
+ : tab === 'videos'
1954
+ ? t('structureEditor.tabs.videoProfiles')
1955
+ : tab === 'operacoes'
1956
+ ? 'Operações'
1957
+ : t('structureEditor.tabs.publish')}
1611
1958
  </TabsTrigger>
1612
1959
  ))}
1613
1960
  </TabsList>
1614
1961
 
1615
1962
  <div className="flex-1 min-h-0 min-w-0 overflow-x-hidden overflow-y-auto overscroll-contain">
1616
1963
  <div className="flex min-w-0 flex-col gap-3 p-2 sm:p-3">
1617
- {/* ── Tab: Estrutura ──────────────────────────────────────── */}
1964
+ {/* ── Tab: Visão Geral ─────────────────────────────────────── */}
1965
+ <TabsContent value="visao-geral" className="mt-0 min-w-0">
1966
+ <CourseOverviewTab
1967
+ courseId={courseId}
1968
+ locale={currentLocaleCode || 'pt-BR'}
1969
+ />
1970
+ </TabsContent>
1971
+
1972
+ {/* ── Tab: Detalhes ───────────────────────────────────────── */}
1618
1973
  <TabsContent
1619
- value="estrutura"
1974
+ value="detalhes"
1620
1975
  className="mt-0 flex min-w-0 flex-col gap-3"
1621
1976
  >
1622
1977
  {/* Dados principais */}
@@ -1828,13 +2183,6 @@ export function EditorCourse() {
1828
2183
  />
1829
2184
  </CardContent>
1830
2185
  </Card>
1831
- </TabsContent>
1832
-
1833
- {/* ── Tab: Sobre ──────────────────────────────────────────── */}
1834
- <TabsContent
1835
- value="sobre"
1836
- className="mt-0 flex min-w-0 flex-col gap-3"
1837
- >
1838
2186
  <CourseClassificationCard
1839
2187
  form={form}
1840
2188
  t={t}
@@ -1859,6 +2207,13 @@ export function EditorCourse() {
1859
2207
  <CourseContentCard form={form} compact />
1860
2208
  </TabsContent>
1861
2209
 
2210
+ <TabsContent value="xp" className="mt-0 min-w-0">
2211
+ <CourseXpOverviewTab
2212
+ courseId={courseId}
2213
+ locale={currentLocaleCode || 'pt-BR'}
2214
+ />
2215
+ </TabsContent>
2216
+
1862
2217
  {/* ── Tab: Mídia ──────────────────────────────────────────── */}
1863
2218
  <TabsContent value="midia" className="mt-0 min-w-0">
1864
2219
  <CourseMediaCard
@@ -1879,6 +2234,8 @@ export function EditorCourse() {
1879
2234
  name:
1880
2235
  apiCourse.logoFilename ||
1881
2236
  `#${apiCourse.logoFileId}`,
2237
+ size: apiCourse.logoFileSize ?? null,
2238
+ mimeType: apiCourse.logoMimeType ?? null,
1882
2239
  }
1883
2240
  : undefined
1884
2241
  }
@@ -1889,6 +2246,8 @@ export function EditorCourse() {
1889
2246
  name:
1890
2247
  apiCourse.bannerFilename ||
1891
2248
  `#${apiCourse.bannerFileId}`,
2249
+ size: apiCourse.bannerFileSize ?? null,
2250
+ mimeType: apiCourse.bannerMimeType ?? null,
1892
2251
  }
1893
2252
  : undefined
1894
2253
  }
@@ -2121,7 +2480,7 @@ export function EditorCourse() {
2121
2480
  <CourseFlagsCard form={form} t={t} compact />
2122
2481
  <CourseDangerZoneCard
2123
2482
  t={t}
2124
- onDelete={() => setDeleteDialogOpen(true)}
2483
+ onDelete={openDeleteDialog}
2125
2484
  compact
2126
2485
  />
2127
2486
  </TabsContent>
@@ -2556,53 +2915,32 @@ export function EditorCourse() {
2556
2915
  </ResizableSheetContent>
2557
2916
  </Sheet>
2558
2917
 
2559
- {/* ── Delete dialog ────────────────────────────────────────────────── */}
2560
- <Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
2561
- <DialogContent>
2562
- <DialogHeader>
2563
- <DialogTitle className="flex items-center gap-2">
2564
- <AlertTriangle className="size-5 text-destructive" />
2565
- {t('deleteDialog.title')}
2566
- </DialogTitle>
2567
- <DialogDescription asChild>
2568
- <div className="flex flex-col gap-3">
2569
- <p>
2570
- {t('deleteDialog.description')}{' '}
2571
- <strong className="text-foreground">{course.title}</strong>?
2572
- </p>
2573
- {(apiCourse?.enrollmentCount ?? 0) > 0 ? (
2574
- <div className="flex items-center gap-2 rounded-md bg-amber-50 px-3 py-2.5 text-xs font-medium text-amber-700">
2575
- <AlertTriangle className="size-3.5 shrink-0" />
2576
- <span>
2577
- {t('deleteDialog.warning', {
2578
- students: apiCourse?.enrollmentCount ?? 0,
2579
- certificates: apiCourse?.certificatesIssued ?? 0,
2580
- })}
2581
- </span>
2582
- </div>
2583
- ) : null}
2584
- </div>
2585
- </DialogDescription>
2586
- </DialogHeader>
2587
- <DialogFooter className="gap-2">
2588
- <Button
2589
- variant="outline"
2590
- onClick={() => setDeleteDialogOpen(false)}
2591
- >
2592
- {t('deleteDialog.actions.cancel')}
2593
- </Button>
2594
- <Button
2595
- variant="destructive"
2596
- onClick={handleDelete}
2597
- disabled={deleting}
2598
- className="gap-2"
2599
- >
2600
- {deleting ? <Loader2 className="size-4 animate-spin" /> : null}
2601
- {t('deleteDialog.actions.delete')}
2602
- </Button>
2603
- </DialogFooter>
2604
- </DialogContent>
2605
- </Dialog>
2918
+ <CourseDeleteDialog
2919
+ open={deleteDialogOpen}
2920
+ onOpenChange={(open) => {
2921
+ setDeleteDialogOpen(open);
2922
+ if (!open) {
2923
+ setConfirmationInput('');
2924
+ setDeleteImpact(null);
2925
+ }
2926
+ }}
2927
+ course={{
2928
+ title: deleteCourseTitle || course.title,
2929
+ status: deleteCourseStatus ?? 'rascunho',
2930
+ enrollmentCount:
2931
+ deleteImpact?.enrollmentCount ?? apiCourse?.enrollmentCount ?? 0,
2932
+ }}
2933
+ impact={deleteImpact}
2934
+ loadingImpact={loadingDeleteImpact}
2935
+ confirmationInput={confirmationInput}
2936
+ onConfirmationInputChange={setConfirmationInput}
2937
+ archiving={archivingCourse}
2938
+ queuingDelete={queuingDelete}
2939
+ canDelete={canDeleteCourse}
2940
+ onArchive={archiveCourseForDelete}
2941
+ onDelete={handleDelete}
2942
+ t={courseListT}
2943
+ />
2606
2944
  </Form>
2607
2945
  );
2608
2946
  }