@hed-hog/lms 0.0.349 → 0.0.350

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 (409) hide show
  1. package/dist/achievement/achievement.controller.d.ts +62 -0
  2. package/dist/achievement/achievement.controller.d.ts.map +1 -0
  3. package/dist/achievement/achievement.controller.js +90 -0
  4. package/dist/achievement/achievement.controller.js.map +1 -0
  5. package/dist/achievement/achievement.mcp-tools.d.ts +19 -0
  6. package/dist/achievement/achievement.mcp-tools.d.ts.map +1 -0
  7. package/dist/achievement/achievement.mcp-tools.js +157 -0
  8. package/dist/achievement/achievement.mcp-tools.js.map +1 -0
  9. package/dist/achievement/achievement.module.d.ts +3 -0
  10. package/dist/achievement/achievement.module.d.ts.map +1 -0
  11. package/dist/achievement/achievement.module.js +26 -0
  12. package/dist/achievement/achievement.module.js.map +1 -0
  13. package/dist/achievement/achievement.service.d.ts +72 -0
  14. package/dist/achievement/achievement.service.d.ts.map +1 -0
  15. package/dist/achievement/achievement.service.js +200 -0
  16. package/dist/achievement/achievement.service.js.map +1 -0
  17. package/dist/achievement/dto/create-achievement.dto.d.ts +12 -0
  18. package/dist/achievement/dto/create-achievement.dto.d.ts.map +1 -0
  19. package/dist/achievement/dto/create-achievement.dto.js +60 -0
  20. package/dist/achievement/dto/create-achievement.dto.js.map +1 -0
  21. package/dist/achievement/dto/update-achievement.dto.d.ts +11 -0
  22. package/dist/achievement/dto/update-achievement.dto.d.ts.map +1 -0
  23. package/dist/achievement/dto/update-achievement.dto.js +57 -0
  24. package/dist/achievement/dto/update-achievement.dto.js.map +1 -0
  25. package/dist/bitcode-wallet/bitcode-wallet.controller.d.ts +114 -0
  26. package/dist/bitcode-wallet/bitcode-wallet.controller.d.ts.map +1 -0
  27. package/dist/bitcode-wallet/bitcode-wallet.controller.js +102 -0
  28. package/dist/bitcode-wallet/bitcode-wallet.controller.js.map +1 -0
  29. package/dist/bitcode-wallet/bitcode-wallet.mcp-tools.d.ts +25 -0
  30. package/dist/bitcode-wallet/bitcode-wallet.mcp-tools.d.ts.map +1 -0
  31. package/dist/bitcode-wallet/bitcode-wallet.mcp-tools.js +160 -0
  32. package/dist/bitcode-wallet/bitcode-wallet.mcp-tools.js.map +1 -0
  33. package/dist/bitcode-wallet/bitcode-wallet.module.d.ts +3 -0
  34. package/dist/bitcode-wallet/bitcode-wallet.module.d.ts.map +1 -0
  35. package/dist/bitcode-wallet/bitcode-wallet.module.js +26 -0
  36. package/dist/bitcode-wallet/bitcode-wallet.module.js.map +1 -0
  37. package/dist/bitcode-wallet/bitcode-wallet.service.d.ts +127 -0
  38. package/dist/bitcode-wallet/bitcode-wallet.service.d.ts.map +1 -0
  39. package/dist/bitcode-wallet/bitcode-wallet.service.js +264 -0
  40. package/dist/bitcode-wallet/bitcode-wallet.service.js.map +1 -0
  41. package/dist/bitcode-wallet/dto/create-bitcode-wallet-transaction.dto.d.ts +8 -0
  42. package/dist/bitcode-wallet/dto/create-bitcode-wallet-transaction.dto.d.ts.map +1 -0
  43. package/dist/bitcode-wallet/dto/create-bitcode-wallet-transaction.dto.js +33 -0
  44. package/dist/bitcode-wallet/dto/create-bitcode-wallet-transaction.dto.js.map +1 -0
  45. package/dist/bitcode-wallet/dto/create-bitcode-wallet.dto.d.ts +4 -0
  46. package/dist/bitcode-wallet/dto/create-bitcode-wallet.dto.d.ts.map +1 -0
  47. package/dist/bitcode-wallet/dto/create-bitcode-wallet.dto.js +22 -0
  48. package/dist/bitcode-wallet/dto/create-bitcode-wallet.dto.js.map +1 -0
  49. package/dist/bitcode-wallet/dto/update-bitcode-wallet-transaction.dto.d.ts +7 -0
  50. package/dist/bitcode-wallet/dto/update-bitcode-wallet-transaction.dto.d.ts.map +1 -0
  51. package/dist/bitcode-wallet/dto/update-bitcode-wallet-transaction.dto.js +35 -0
  52. package/dist/bitcode-wallet/dto/update-bitcode-wallet-transaction.dto.js.map +1 -0
  53. package/dist/bitcode-wallet/dto/update-bitcode-wallet.dto.d.ts +4 -0
  54. package/dist/bitcode-wallet/dto/update-bitcode-wallet.dto.d.ts.map +1 -0
  55. package/dist/bitcode-wallet/dto/update-bitcode-wallet.dto.js +23 -0
  56. package/dist/bitcode-wallet/dto/update-bitcode-wallet.dto.js.map +1 -0
  57. package/dist/certificate/certificate.controller.d.ts +22 -0
  58. package/dist/certificate/certificate.controller.d.ts.map +1 -1
  59. package/dist/certificate/certificate.controller.js +12 -0
  60. package/dist/certificate/certificate.controller.js.map +1 -1
  61. package/dist/certificate/certificate.mcp-tools.d.ts +24 -0
  62. package/dist/certificate/certificate.mcp-tools.d.ts.map +1 -0
  63. package/dist/certificate/certificate.mcp-tools.js +188 -0
  64. package/dist/certificate/certificate.mcp-tools.js.map +1 -0
  65. package/dist/certificate/certificate.module.d.ts.map +1 -1
  66. package/dist/certificate/certificate.module.js +2 -1
  67. package/dist/certificate/certificate.module.js.map +1 -1
  68. package/dist/certificate/certificate.service.d.ts +25 -2
  69. package/dist/certificate/certificate.service.d.ts.map +1 -1
  70. package/dist/certificate/certificate.service.js +87 -2
  71. package/dist/certificate/certificate.service.js.map +1 -1
  72. package/dist/certificate/dto/update-certificate-public-access.dto.d.ts +4 -0
  73. package/dist/certificate/dto/update-certificate-public-access.dto.d.ts.map +1 -0
  74. package/dist/certificate/dto/update-certificate-public-access.dto.js +21 -0
  75. package/dist/certificate/dto/update-certificate-public-access.dto.js.map +1 -0
  76. package/dist/class-group/class-group.mcp-tools.d.ts +87 -0
  77. package/dist/class-group/class-group.mcp-tools.d.ts.map +1 -0
  78. package/dist/class-group/class-group.mcp-tools.js +553 -0
  79. package/dist/class-group/class-group.mcp-tools.js.map +1 -0
  80. package/dist/class-group/class-group.module.d.ts.map +1 -1
  81. package/dist/class-group/class-group.module.js +2 -1
  82. package/dist/class-group/class-group.module.js.map +1 -1
  83. package/dist/class-group/class-group.service.d.ts +3 -1
  84. package/dist/class-group/class-group.service.d.ts.map +1 -1
  85. package/dist/class-group/class-group.service.js +45 -2
  86. package/dist/class-group/class-group.service.js.map +1 -1
  87. package/dist/course/course-operations-integration.service.d.ts +40 -0
  88. package/dist/course/course-operations-integration.service.d.ts.map +1 -0
  89. package/dist/course/course-operations-integration.service.js +372 -0
  90. package/dist/course/course-operations-integration.service.js.map +1 -0
  91. package/dist/course/course-structure.controller.d.ts +43 -4
  92. package/dist/course/course-structure.controller.d.ts.map +1 -1
  93. package/dist/course/course-structure.controller.js +22 -0
  94. package/dist/course/course-structure.controller.js.map +1 -1
  95. package/dist/course/course-structure.service.d.ts +42 -1
  96. package/dist/course/course-structure.service.d.ts.map +1 -1
  97. package/dist/course/course-structure.service.js +199 -32
  98. package/dist/course/course-structure.service.js.map +1 -1
  99. package/dist/course/course.controller.d.ts +12 -0
  100. package/dist/course/course.controller.d.ts.map +1 -1
  101. package/dist/course/course.mcp-tools.d.ts +90 -0
  102. package/dist/course/course.mcp-tools.d.ts.map +1 -0
  103. package/dist/course/course.mcp-tools.js +520 -0
  104. package/dist/course/course.mcp-tools.js.map +1 -0
  105. package/dist/course/course.module.d.ts.map +1 -1
  106. package/dist/course/course.module.js +8 -1
  107. package/dist/course/course.module.js.map +1 -1
  108. package/dist/course/course.service.d.ts +15 -1
  109. package/dist/course/course.service.d.ts.map +1 -1
  110. package/dist/course/course.service.js +70 -35
  111. package/dist/course/course.service.js.map +1 -1
  112. package/dist/course/dto/create-course.dto.d.ts +1 -0
  113. package/dist/course/dto/create-course.dto.d.ts.map +1 -1
  114. package/dist/course/dto/create-course.dto.js +7 -0
  115. package/dist/course/dto/create-course.dto.js.map +1 -1
  116. package/dist/course/dto/update-course-resources.dto.d.ts +11 -0
  117. package/dist/course/dto/update-course-resources.dto.d.ts.map +1 -0
  118. package/dist/course/dto/update-course-resources.dto.js +51 -0
  119. package/dist/course/dto/update-course-resources.dto.js.map +1 -0
  120. package/dist/course-lesson-discussion/course-lesson-discussion.controller.d.ts +23 -0
  121. package/dist/course-lesson-discussion/course-lesson-discussion.controller.d.ts.map +1 -0
  122. package/dist/course-lesson-discussion/course-lesson-discussion.controller.js +78 -0
  123. package/dist/course-lesson-discussion/course-lesson-discussion.controller.js.map +1 -0
  124. package/dist/course-lesson-discussion/course-lesson-discussion.mcp-tools.d.ts +22 -0
  125. package/dist/course-lesson-discussion/course-lesson-discussion.mcp-tools.d.ts.map +1 -0
  126. package/dist/course-lesson-discussion/course-lesson-discussion.mcp-tools.js +120 -0
  127. package/dist/course-lesson-discussion/course-lesson-discussion.mcp-tools.js.map +1 -0
  128. package/dist/course-lesson-discussion/course-lesson-discussion.module.d.ts +3 -0
  129. package/dist/course-lesson-discussion/course-lesson-discussion.module.d.ts.map +1 -0
  130. package/dist/course-lesson-discussion/course-lesson-discussion.module.js +26 -0
  131. package/dist/course-lesson-discussion/course-lesson-discussion.module.js.map +1 -0
  132. package/dist/course-lesson-discussion/course-lesson-discussion.service.d.ts +49 -0
  133. package/dist/course-lesson-discussion/course-lesson-discussion.service.d.ts.map +1 -0
  134. package/dist/course-lesson-discussion/course-lesson-discussion.service.js +272 -0
  135. package/dist/course-lesson-discussion/course-lesson-discussion.service.js.map +1 -0
  136. package/dist/course-lesson-discussion/dto/create-course-lesson-discussion-topic.dto.d.ts +6 -0
  137. package/dist/course-lesson-discussion/dto/create-course-lesson-discussion-topic.dto.d.ts.map +1 -0
  138. package/dist/course-lesson-discussion/dto/create-course-lesson-discussion-topic.dto.js +33 -0
  139. package/dist/course-lesson-discussion/dto/create-course-lesson-discussion-topic.dto.js.map +1 -0
  140. package/dist/course-lesson-note/course-lesson-note.controller.d.ts +53 -0
  141. package/dist/course-lesson-note/course-lesson-note.controller.d.ts.map +1 -0
  142. package/dist/course-lesson-note/course-lesson-note.controller.js +93 -0
  143. package/dist/course-lesson-note/course-lesson-note.controller.js.map +1 -0
  144. package/dist/course-lesson-note/course-lesson-note.mcp-tools.d.ts +27 -0
  145. package/dist/course-lesson-note/course-lesson-note.mcp-tools.d.ts.map +1 -0
  146. package/dist/course-lesson-note/course-lesson-note.mcp-tools.js +145 -0
  147. package/dist/course-lesson-note/course-lesson-note.mcp-tools.js.map +1 -0
  148. package/dist/course-lesson-note/course-lesson-note.module.d.ts +3 -0
  149. package/dist/course-lesson-note/course-lesson-note.module.d.ts.map +1 -0
  150. package/dist/course-lesson-note/course-lesson-note.module.js +26 -0
  151. package/dist/course-lesson-note/course-lesson-note.module.js.map +1 -0
  152. package/dist/course-lesson-note/course-lesson-note.service.d.ts +59 -0
  153. package/dist/course-lesson-note/course-lesson-note.service.d.ts.map +1 -0
  154. package/dist/course-lesson-note/course-lesson-note.service.js +195 -0
  155. package/dist/course-lesson-note/course-lesson-note.service.js.map +1 -0
  156. package/dist/course-lesson-note/dto/create-course-lesson-note.dto.d.ts +6 -0
  157. package/dist/course-lesson-note/dto/create-course-lesson-note.dto.d.ts.map +1 -0
  158. package/dist/course-lesson-note/dto/create-course-lesson-note.dto.js +33 -0
  159. package/dist/course-lesson-note/dto/create-course-lesson-note.dto.js.map +1 -0
  160. package/dist/course-lesson-note/dto/update-course-lesson-note.dto.d.ts +6 -0
  161. package/dist/course-lesson-note/dto/update-course-lesson-note.dto.d.ts.map +1 -0
  162. package/dist/course-lesson-note/dto/update-course-lesson-note.dto.js +35 -0
  163. package/dist/course-lesson-note/dto/update-course-lesson-note.dto.js.map +1 -0
  164. package/dist/dashboard/dashboard.mcp-tools.d.ts +10 -0
  165. package/dist/dashboard/dashboard.mcp-tools.d.ts.map +1 -0
  166. package/dist/dashboard/dashboard.mcp-tools.js +46 -0
  167. package/dist/dashboard/dashboard.mcp-tools.js.map +1 -0
  168. package/dist/dashboard/dashboard.module.d.ts.map +1 -1
  169. package/dist/dashboard/dashboard.module.js +2 -1
  170. package/dist/dashboard/dashboard.module.js.map +1 -1
  171. package/dist/enterprise/enterprise.mcp-tools.d.ts +82 -0
  172. package/dist/enterprise/enterprise.mcp-tools.d.ts.map +1 -0
  173. package/dist/enterprise/enterprise.mcp-tools.js +516 -0
  174. package/dist/enterprise/enterprise.mcp-tools.js.map +1 -0
  175. package/dist/enterprise/enterprise.module.d.ts.map +1 -1
  176. package/dist/enterprise/enterprise.module.js +2 -1
  177. package/dist/enterprise/enterprise.module.js.map +1 -1
  178. package/dist/enterprise/training/enterprise-training.module.d.ts.map +1 -1
  179. package/dist/enterprise/training/enterprise-training.module.js +11 -1
  180. package/dist/enterprise/training/enterprise-training.module.js.map +1 -1
  181. package/dist/enterprise/training/training-admin.mcp-tools.d.ts +79 -0
  182. package/dist/enterprise/training/training-admin.mcp-tools.d.ts.map +1 -0
  183. package/dist/enterprise/training/training-admin.mcp-tools.js +620 -0
  184. package/dist/enterprise/training/training-admin.mcp-tools.js.map +1 -0
  185. package/dist/enterprise/training/training-instructor.mcp-tools.d.ts +47 -0
  186. package/dist/enterprise/training/training-instructor.mcp-tools.d.ts.map +1 -0
  187. package/dist/enterprise/training/training-instructor.mcp-tools.js +275 -0
  188. package/dist/enterprise/training/training-instructor.mcp-tools.js.map +1 -0
  189. package/dist/enterprise/training/training-student.controller.d.ts +24 -0
  190. package/dist/enterprise/training/training-student.controller.d.ts.map +1 -1
  191. package/dist/enterprise/training/training-student.controller.js +22 -0
  192. package/dist/enterprise/training/training-student.controller.js.map +1 -1
  193. package/dist/enterprise/training/training-student.mcp-tools.d.ts +27 -0
  194. package/dist/enterprise/training/training-student.mcp-tools.d.ts.map +1 -0
  195. package/dist/enterprise/training/training-student.mcp-tools.js +186 -0
  196. package/dist/enterprise/training/training-student.mcp-tools.js.map +1 -0
  197. package/dist/enterprise/training/training-student.service.d.ts +32 -0
  198. package/dist/enterprise/training/training-student.service.d.ts.map +1 -1
  199. package/dist/enterprise/training/training-student.service.js +138 -0
  200. package/dist/enterprise/training/training-student.service.js.map +1 -1
  201. package/dist/evaluation/evaluation.mcp-tools.d.ts +25 -0
  202. package/dist/evaluation/evaluation.mcp-tools.d.ts.map +1 -0
  203. package/dist/evaluation/evaluation.mcp-tools.js +220 -0
  204. package/dist/evaluation/evaluation.mcp-tools.js.map +1 -0
  205. package/dist/evaluation/evaluation.module.d.ts.map +1 -1
  206. package/dist/evaluation/evaluation.module.js +2 -1
  207. package/dist/evaluation/evaluation.module.js.map +1 -1
  208. package/dist/exam/dto/create-exam-question.dto.d.ts +2 -0
  209. package/dist/exam/dto/create-exam-question.dto.d.ts.map +1 -1
  210. package/dist/exam/dto/create-exam-question.dto.js +10 -0
  211. package/dist/exam/dto/create-exam-question.dto.js.map +1 -1
  212. package/dist/exam/dto/create-exam.dto.d.ts +2 -0
  213. package/dist/exam/dto/create-exam.dto.d.ts.map +1 -1
  214. package/dist/exam/dto/create-exam.dto.js +10 -0
  215. package/dist/exam/dto/create-exam.dto.js.map +1 -1
  216. package/dist/exam/dto/create-question-subject.dto.d.ts +5 -0
  217. package/dist/exam/dto/create-question-subject.dto.d.ts.map +1 -0
  218. package/dist/exam/dto/create-question-subject.dto.js +28 -0
  219. package/dist/exam/dto/create-question-subject.dto.js.map +1 -0
  220. package/dist/exam/exam-attempt.controller.d.ts +4 -0
  221. package/dist/exam/exam-attempt.controller.d.ts.map +1 -1
  222. package/dist/exam/exam-attempt.service.d.ts +7 -1
  223. package/dist/exam/exam-attempt.service.d.ts.map +1 -1
  224. package/dist/exam/exam-attempt.service.js +47 -17
  225. package/dist/exam/exam-attempt.service.js.map +1 -1
  226. package/dist/exam/exam.controller.d.ts +34 -0
  227. package/dist/exam/exam.controller.d.ts.map +1 -1
  228. package/dist/exam/exam.controller.js +27 -0
  229. package/dist/exam/exam.controller.js.map +1 -1
  230. package/dist/exam/exam.mcp-tools.d.ts +62 -0
  231. package/dist/exam/exam.mcp-tools.d.ts.map +1 -0
  232. package/dist/exam/exam.mcp-tools.js +430 -0
  233. package/dist/exam/exam.mcp-tools.js.map +1 -0
  234. package/dist/exam/exam.module.d.ts.map +1 -1
  235. package/dist/exam/exam.module.js +2 -1
  236. package/dist/exam/exam.module.js.map +1 -1
  237. package/dist/exam/exam.service.d.ts +38 -0
  238. package/dist/exam/exam.service.d.ts.map +1 -1
  239. package/dist/exam/exam.service.js +114 -17
  240. package/dist/exam/exam.service.js.map +1 -1
  241. package/dist/index.d.ts +9 -0
  242. package/dist/index.d.ts.map +1 -1
  243. package/dist/index.js +9 -0
  244. package/dist/index.js.map +1 -1
  245. package/dist/instructor/instructor.mcp-tools.d.ts +41 -0
  246. package/dist/instructor/instructor.mcp-tools.d.ts.map +1 -0
  247. package/dist/instructor/instructor.mcp-tools.js +326 -0
  248. package/dist/instructor/instructor.mcp-tools.js.map +1 -0
  249. package/dist/instructor/instructor.module.d.ts.map +1 -1
  250. package/dist/instructor/instructor.module.js +2 -1
  251. package/dist/instructor/instructor.module.js.map +1 -1
  252. package/dist/lms.module.d.ts.map +1 -1
  253. package/dist/lms.module.js +15 -0
  254. package/dist/lms.module.js.map +1 -1
  255. package/dist/realtime/lms-realtime.controller.d.ts +7 -0
  256. package/dist/realtime/lms-realtime.controller.d.ts.map +1 -0
  257. package/dist/realtime/lms-realtime.controller.js +34 -0
  258. package/dist/realtime/lms-realtime.controller.js.map +1 -0
  259. package/dist/realtime/lms-realtime.module.d.ts +3 -0
  260. package/dist/realtime/lms-realtime.module.d.ts.map +1 -0
  261. package/dist/realtime/lms-realtime.module.js +25 -0
  262. package/dist/realtime/lms-realtime.module.js.map +1 -0
  263. package/dist/realtime/lms-realtime.service.d.ts +36 -0
  264. package/dist/realtime/lms-realtime.service.d.ts.map +1 -0
  265. package/dist/realtime/lms-realtime.service.js +59 -0
  266. package/dist/realtime/lms-realtime.service.js.map +1 -0
  267. package/dist/realtime/lms-realtime.subscriber.d.ts +10 -0
  268. package/dist/realtime/lms-realtime.subscriber.d.ts.map +1 -0
  269. package/dist/realtime/lms-realtime.subscriber.js +70 -0
  270. package/dist/realtime/lms-realtime.subscriber.js.map +1 -0
  271. package/dist/reports/reports.mcp-tools.d.ts +10 -0
  272. package/dist/reports/reports.mcp-tools.d.ts.map +1 -0
  273. package/dist/reports/reports.mcp-tools.js +50 -0
  274. package/dist/reports/reports.mcp-tools.js.map +1 -0
  275. package/dist/reports/reports.module.d.ts.map +1 -1
  276. package/dist/reports/reports.module.js +2 -1
  277. package/dist/reports/reports.module.js.map +1 -1
  278. package/dist/training/training.mcp-tools.d.ts +20 -0
  279. package/dist/training/training.mcp-tools.d.ts.map +1 -0
  280. package/dist/training/training.mcp-tools.js +181 -0
  281. package/dist/training/training.mcp-tools.js.map +1 -0
  282. package/dist/training/training.module.d.ts.map +1 -1
  283. package/dist/training/training.module.js +2 -1
  284. package/dist/training/training.module.js.map +1 -1
  285. package/hedhog/data/integration_event_catalog.yaml +69 -0
  286. package/hedhog/data/menu.yaml +34 -0
  287. package/hedhog/data/route.yaml +2351 -0
  288. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +168 -103
  289. package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +80 -1
  290. package/hedhog/frontend/app/_components/course-picker.tsx.ejs +228 -0
  291. package/hedhog/frontend/app/_lib/hooks/use-lms-realtime-refresh.ts.ejs +58 -0
  292. package/hedhog/frontend/app/achievements/page.tsx.ejs +844 -0
  293. package/hedhog/frontend/app/bitcodes/page.tsx.ejs +1010 -0
  294. package/hedhog/frontend/app/certificates/issued/page.tsx.ejs +61 -2
  295. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +7 -0
  296. package/hedhog/frontend/app/classes/page.tsx.ejs +55 -2060
  297. package/hedhog/frontend/app/courses/[id]/_components/CourseRelationsCard.tsx.ejs +28 -0
  298. package/hedhog/frontend/app/courses/[id]/_components/course-edit-types.ts.ejs +1 -0
  299. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +0 -9
  300. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +80 -66
  301. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +583 -8
  302. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +527 -57
  303. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +1 -1
  304. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-context-menu.tsx.ejs +106 -4
  305. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +2 -1
  306. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +11 -1
  307. package/hedhog/frontend/app/courses/[id]/structure/_data/services/course-structure.service.ts.ejs +53 -6
  308. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +13 -2
  309. package/hedhog/frontend/app/courses/page.tsx.ejs +175 -29
  310. package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +3 -0
  311. package/hedhog/frontend/app/enterprise/_components/enterprise-course-edit-sheet.tsx.ejs +7 -0
  312. package/hedhog/frontend/app/exams/[id]/questions/page.tsx.ejs +169 -2
  313. package/hedhog/frontend/app/exams/page.tsx.ejs +77 -22
  314. package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +1 -0
  315. package/hedhog/frontend/app/instructors/page.tsx.ejs +1 -0
  316. package/hedhog/frontend/app/paths/page.tsx.ejs +6 -24
  317. package/hedhog/frontend/app/training/page.tsx.ejs +6 -24
  318. package/hedhog/frontend/messages/en.json +314 -12
  319. package/hedhog/frontend/messages/pt.json +314 -12
  320. package/hedhog/query/triggers.sql +53 -0
  321. package/hedhog/table/achievement.yaml +46 -0
  322. package/hedhog/table/bitcode_wallet.yaml +18 -0
  323. package/hedhog/table/bitcode_wallet_transaction.yaml +22 -0
  324. package/hedhog/table/certificate.yaml +3 -0
  325. package/hedhog/table/course.yaml +4 -0
  326. package/hedhog/table/course_file.yaml +23 -0
  327. package/hedhog/table/course_lesson.yaml +5 -0
  328. package/hedhog/table/course_lesson_discussion_like.yaml +21 -0
  329. package/hedhog/table/course_lesson_discussion_topic.yaml +35 -0
  330. package/hedhog/table/course_lesson_note.yaml +34 -0
  331. package/hedhog/table/exam.yaml +5 -0
  332. package/hedhog/table/learning_path_enrollment.yaml +6 -0
  333. package/hedhog/table/question.yaml +10 -0
  334. package/hedhog/table/question_subject.yaml +17 -0
  335. package/hedhog/table/student_activity_streak.yaml +25 -0
  336. package/package.json +6 -6
  337. package/src/achievement/achievement.controller.ts +60 -0
  338. package/src/achievement/achievement.mcp-tools.ts +108 -0
  339. package/src/achievement/achievement.module.ts +13 -0
  340. package/src/achievement/achievement.service.ts +252 -0
  341. package/src/achievement/dto/create-achievement.dto.ts +50 -0
  342. package/src/achievement/dto/update-achievement.dto.ts +47 -0
  343. package/src/bitcode-wallet/bitcode-wallet.controller.ts +69 -0
  344. package/src/bitcode-wallet/bitcode-wallet.mcp-tools.ts +107 -0
  345. package/src/bitcode-wallet/bitcode-wallet.module.ts +13 -0
  346. package/src/bitcode-wallet/bitcode-wallet.service.ts +361 -0
  347. package/src/bitcode-wallet/dto/create-bitcode-wallet-transaction.dto.ts +27 -0
  348. package/src/bitcode-wallet/dto/create-bitcode-wallet.dto.ts +7 -0
  349. package/src/bitcode-wallet/dto/update-bitcode-wallet-transaction.dto.ts +28 -0
  350. package/src/bitcode-wallet/dto/update-bitcode-wallet.dto.ts +8 -0
  351. package/src/certificate/certificate.controller.ts +20 -11
  352. package/src/certificate/certificate.mcp-tools.ts +131 -0
  353. package/src/certificate/certificate.module.ts +2 -1
  354. package/src/certificate/certificate.service.ts +95 -4
  355. package/src/certificate/dto/update-certificate-public-access.dto.ts +6 -0
  356. package/src/class-group/class-group.mcp-tools.ts +435 -0
  357. package/src/class-group/class-group.module.ts +2 -1
  358. package/src/class-group/class-group.service.ts +51 -1
  359. package/src/course/course-operations-integration.service.ts +520 -0
  360. package/src/course/course-structure.controller.ts +22 -8
  361. package/src/course/course-structure.service.ts +215 -23
  362. package/src/course/course.mcp-tools.ts +409 -0
  363. package/src/course/course.module.ts +8 -1
  364. package/src/course/course.service.ts +106 -27
  365. package/src/course/dto/create-course.dto.ts +8 -0
  366. package/src/course/dto/update-course-resources.dto.ts +39 -0
  367. package/src/course-lesson-discussion/course-lesson-discussion.controller.ts +55 -0
  368. package/src/course-lesson-discussion/course-lesson-discussion.mcp-tools.ts +75 -0
  369. package/src/course-lesson-discussion/course-lesson-discussion.module.ts +13 -0
  370. package/src/course-lesson-discussion/course-lesson-discussion.service.ts +354 -0
  371. package/src/course-lesson-discussion/dto/create-course-lesson-discussion-topic.dto.ts +16 -0
  372. package/src/course-lesson-note/course-lesson-note.controller.ts +68 -0
  373. package/src/course-lesson-note/course-lesson-note.mcp-tools.ts +96 -0
  374. package/src/course-lesson-note/course-lesson-note.module.ts +13 -0
  375. package/src/course-lesson-note/course-lesson-note.service.ts +248 -0
  376. package/src/course-lesson-note/dto/create-course-lesson-note.dto.ts +16 -0
  377. package/src/course-lesson-note/dto/update-course-lesson-note.dto.ts +18 -0
  378. package/src/dashboard/dashboard.mcp-tools.ts +23 -0
  379. package/src/dashboard/dashboard.module.ts +2 -1
  380. package/src/enterprise/enterprise.mcp-tools.ts +403 -0
  381. package/src/enterprise/enterprise.module.ts +2 -1
  382. package/src/enterprise/training/enterprise-training.module.ts +11 -1
  383. package/src/enterprise/training/training-admin.mcp-tools.ts +479 -0
  384. package/src/enterprise/training/training-instructor.mcp-tools.ts +210 -0
  385. package/src/enterprise/training/training-student.controller.ts +17 -1
  386. package/src/enterprise/training/training-student.mcp-tools.ts +136 -0
  387. package/src/enterprise/training/training-student.service.ts +167 -1
  388. package/src/evaluation/evaluation.mcp-tools.ts +155 -0
  389. package/src/evaluation/evaluation.module.ts +2 -1
  390. package/src/exam/dto/create-exam-question.dto.ts +8 -0
  391. package/src/exam/dto/create-exam.dto.ts +8 -0
  392. package/src/exam/dto/create-question-subject.dto.ts +12 -0
  393. package/src/exam/exam-attempt.service.ts +46 -14
  394. package/src/exam/exam.controller.ts +19 -0
  395. package/src/exam/exam.mcp-tools.ts +337 -0
  396. package/src/exam/exam.module.ts +2 -1
  397. package/src/exam/exam.service.ts +121 -0
  398. package/src/index.ts +9 -0
  399. package/src/instructor/instructor.mcp-tools.ts +243 -0
  400. package/src/instructor/instructor.module.ts +2 -1
  401. package/src/lms.module.ts +15 -1
  402. package/src/realtime/lms-realtime.controller.ts +12 -0
  403. package/src/realtime/lms-realtime.module.ts +12 -0
  404. package/src/realtime/lms-realtime.service.ts +98 -0
  405. package/src/realtime/lms-realtime.subscriber.ts +61 -0
  406. package/src/reports/reports.mcp-tools.ts +27 -0
  407. package/src/reports/reports.module.ts +2 -1
  408. package/src/training/training.mcp-tools.ts +128 -0
  409. package/src/training/training.module.ts +2 -1
@@ -9,16 +9,22 @@ import {
9
9
  CheckCircle2,
10
10
  CircleDot,
11
11
  Clock,
12
+ Download,
13
+ File as FileIcon,
14
+ FileImage,
15
+ FileText,
12
16
  Layers,
13
17
  Loader2,
14
18
  Plus,
15
19
  Save,
16
20
  Undo2,
21
+ UploadCloud,
17
22
  Video,
23
+ X,
18
24
  } from 'lucide-react';
19
25
  import { useTranslations } from 'next-intl';
20
26
  import { useRouter } from 'next/navigation';
21
- import { type ChangeEvent, useEffect, useMemo, useState } from 'react';
27
+ import { type ChangeEvent, useEffect, useMemo, useRef, useState } from 'react';
22
28
  import { useForm } from 'react-hook-form';
23
29
  import { toast } from 'sonner';
24
30
  import { z } from 'zod';
@@ -56,6 +62,7 @@ import {
56
62
  SheetTitle,
57
63
  } from '@/components/ui/sheet';
58
64
  import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
65
+ import { cn } from '@/lib/utils';
59
66
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
60
67
  import { useMutation, useQueryClient } from '@tanstack/react-query';
61
68
 
@@ -71,9 +78,16 @@ import { CourseDangerZoneCard } from '../../_components/CourseDangerZoneCard';
71
78
  import { CourseFlagsCard } from '../../_components/CourseFlagsCard';
72
79
  import { CourseMediaCard } from '../../_components/CourseMediaCard';
73
80
  import { CourseRelationsCard } from '../../_components/CourseRelationsCard';
81
+ import {
82
+ deleteFile,
83
+ getCourseResources,
84
+ updateCourseResources,
85
+ uploadFile,
86
+ } from '../_data/services/course-structure.service';
74
87
  import { useCreateSessionMutation } from '../_data/use-course-structure-mutations';
75
88
  import { courseStructureQueryKey } from '../_data/use-course-structure-query';
76
89
  import { useStructureStore } from './store';
90
+ import { Resource } from './types';
77
91
 
78
92
  // ── API types (local to this component) ──────────────────────────────────────
79
93
 
@@ -119,6 +133,9 @@ type ApiCourseDetail = {
119
133
  aspectRatio: string | null;
120
134
  } | null;
121
135
  certificateModel?: string | null;
136
+ operationsProjectId?: number | null;
137
+ operationsProjectCode?: string | null;
138
+ operationsProjectName?: string | null;
122
139
  };
123
140
 
124
141
  type ApiCategory = { id: number; slug: string; name: string };
@@ -165,6 +182,27 @@ type ApiCertificateTemplateList = {
165
182
  pageSize: number;
166
183
  lastPage?: number;
167
184
  };
185
+ type ApiOperationsProjectOption = {
186
+ id: number;
187
+ code?: string | null;
188
+ name: string;
189
+ label?: string;
190
+ clientName?: string | null;
191
+ status?: string;
192
+ };
193
+
194
+ type CourseResourceApi = {
195
+ id: number;
196
+ nome: string;
197
+ fileId?: number | null;
198
+ tipo?: string | null;
199
+ publico?: boolean;
200
+ };
201
+
202
+ type CourseResourceItem = Resource & {
203
+ fileId?: number | null;
204
+ };
205
+
168
206
  type Locale = { id?: number; code: string; name: string };
169
207
 
170
208
  // ── Helpers ───────────────────────────────────────────────────────────────────
@@ -248,6 +286,37 @@ function getInstructorAvatarUrl(avatarId?: number | null) {
248
286
  : null;
249
287
  }
250
288
 
289
+ function getResourceIcon(type: string): typeof FileIcon {
290
+ if (type === 'application/pdf' || type.endsWith('pdf')) return FileText;
291
+ if (type.startsWith('image/')) return FileImage;
292
+ return FileIcon;
293
+ }
294
+
295
+ function getResourceIconColor(type: string): string {
296
+ if (type === 'application/pdf' || type.endsWith('pdf')) return 'text-red-500';
297
+ if (type.startsWith('image/')) return 'text-blue-500';
298
+ return 'text-muted-foreground';
299
+ }
300
+
301
+ function formatFileSize(bytes: number): string {
302
+ if (bytes < 1024) return `${bytes} B`;
303
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
304
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
305
+ }
306
+
307
+ function mapApiCourseResourceToLocal(
308
+ item: CourseResourceApi
309
+ ): CourseResourceItem {
310
+ return {
311
+ id: String(item.id),
312
+ fileId: item.fileId ?? null,
313
+ name: item.nome,
314
+ size: '',
315
+ type: item.tipo ?? 'file',
316
+ public: item.publico ?? true,
317
+ };
318
+ }
319
+
251
320
  // ── Schema ────────────────────────────────────────────────────────────────────
252
321
 
253
322
  function buildSchema(t: (key: string) => string) {
@@ -270,6 +339,7 @@ function buildSchema(t: (key: string) => string) {
270
339
  status: z.enum(['ativo', 'rascunho', 'arquivado']),
271
340
  tipoOferta: z.enum(['agendado', 'sob_demanda', 'hibrido']),
272
341
  categorias: z.array(z.string()).optional().default([]),
342
+ operationsProjectId: z.string().optional(),
273
343
  primaryColor: z
274
344
  .string()
275
345
  .regex(/^#([0-9A-Fa-f]{6})$/, t('validation.colorInvalid')),
@@ -337,6 +407,13 @@ export function EditorCourse() {
337
407
  >([]);
338
408
  const [persistedCertificateModel, setPersistedCertificateModel] =
339
409
  useState('');
410
+ const [courseResources, setCourseResources] = useState<CourseResourceItem[]>(
411
+ []
412
+ );
413
+ const [resourcesDragOver, setResourcesDragOver] = useState(false);
414
+ const [isUploadingResources, setIsUploadingResources] = useState(false);
415
+ const [isSavingResources, setIsSavingResources] = useState(false);
416
+ const resourcesInputRef = useRef<HTMLInputElement>(null);
340
417
 
341
418
  // ── Queries ─────────────────────────────────────────────────────────────────
342
419
  const { data: apiCourse, refetch: refetchCourse } = useQuery<ApiCourseDetail>(
@@ -412,7 +489,7 @@ export function EditorCourse() {
412
489
  >({
413
490
  url: '/lms/certificates/templates',
414
491
  method: 'GET',
415
- params: { page: 1, pageSize: 100 },
492
+ params: { page: 1, pageSize: 100, status: 'all' },
416
493
  });
417
494
  const payload = response.data;
418
495
  if (Array.isArray(payload))
@@ -428,6 +505,26 @@ export function EditorCourse() {
428
505
  initialData: { data: [], total: 0, page: 1, pageSize: 100, lastPage: 1 },
429
506
  });
430
507
 
508
+ const { data: operationsProjectOptionsData } = useQuery<
509
+ { data: ApiOperationsProjectOption[] } | ApiOperationsProjectOption[]
510
+ >({
511
+ queryKey: ['lms-course-operations-project-options'],
512
+ queryFn: async () => {
513
+ try {
514
+ const response = await request<
515
+ { data: ApiOperationsProjectOption[] } | ApiOperationsProjectOption[]
516
+ >({
517
+ url: '/operations/projects/options?pageSize=200&sortField=name&sortOrder=asc',
518
+ method: 'GET',
519
+ });
520
+ return response.data;
521
+ } catch {
522
+ return { data: [] };
523
+ }
524
+ },
525
+ initialData: { data: [] },
526
+ });
527
+
431
528
  // ── Save mutation ───────────────────────────────────────────────────────────
432
529
  const { mutate: saveCourse, isPending: saving } = useMutation({
433
530
  mutationFn: async (data: CourseEditFormValues) => {
@@ -455,6 +552,9 @@ export function EditorCourse() {
455
552
  isListed: data.listado,
456
553
  certificateModel: data.modeloCertificado.trim() || null,
457
554
  instructorIds: (data.instrutores ?? []).map(Number),
555
+ operationsProjectId: data.operationsProjectId
556
+ ? Number(data.operationsProjectId)
557
+ : null,
458
558
  },
459
559
  });
460
560
  return data;
@@ -495,6 +595,7 @@ export function EditorCourse() {
495
595
  status: 'rascunho',
496
596
  tipoOferta: 'sob_demanda',
497
597
  categorias: [],
598
+ operationsProjectId: '',
498
599
  primaryColor: '#1D4ED8',
499
600
  secondaryColor: '#111827',
500
601
  instrutores: [],
@@ -556,6 +657,35 @@ export function EditorCourse() {
556
657
  );
557
658
  }, [categoryListData, createdCategoryOptions]);
558
659
 
660
+ const projectOptions = useMemo<PickerOption[]>(() => {
661
+ const payload = Array.isArray(operationsProjectOptionsData)
662
+ ? operationsProjectOptionsData
663
+ : (operationsProjectOptionsData?.data ?? []);
664
+
665
+ const currentOption =
666
+ apiCourse?.operationsProjectId && apiCourse?.operationsProjectName
667
+ ? [
668
+ {
669
+ value: String(apiCourse.operationsProjectId),
670
+ label: apiCourse.operationsProjectName,
671
+ description: apiCourse.operationsProjectCode ?? null,
672
+ },
673
+ ]
674
+ : [];
675
+
676
+ const remoteOptions = payload.map((project) => ({
677
+ value: String(project.id),
678
+ label: project.name,
679
+ description: project.code ?? project.clientName ?? null,
680
+ meta: project.status ?? null,
681
+ }));
682
+
683
+ return [...currentOption, ...remoteOptions].filter(
684
+ (item, index, list) =>
685
+ list.findIndex((candidate) => candidate.value === item.value) === index
686
+ );
687
+ }, [apiCourse, operationsProjectOptionsData]);
688
+
559
689
  const instructorOptions = useMemo(() => {
560
690
  const fromCourse = (apiCourse?.instructors ?? []).map((item) => ({
561
691
  value: String(item.id),
@@ -594,6 +724,64 @@ export function EditorCourse() {
594
724
  (l) => l.visibility === 'publico' || l.status === 'publicada'
595
725
  ).length;
596
726
 
727
+ const formValues = form.watch();
728
+ const descriptionText = String(formValues.descricaoPublica ?? '')
729
+ .replace(/<[^>]+>/g, '')
730
+ .trim();
731
+ const checklistItems = [
732
+ {
733
+ id: 'title',
734
+ label: t('structureEditor.publishChecklist.items.title'),
735
+ done: formValues.tituloComercial.trim().length >= 3,
736
+ required: true,
737
+ },
738
+ {
739
+ id: 'description',
740
+ label: t('structureEditor.publishChecklist.items.description'),
741
+ done: descriptionText.length > 0,
742
+ required: true,
743
+ },
744
+ {
745
+ id: 'media',
746
+ label: t('structureEditor.publishChecklist.items.media'),
747
+ done: Boolean(apiCourse?.logoFileId || apiCourse?.bannerFileId),
748
+ required: true,
749
+ },
750
+ {
751
+ id: 'session',
752
+ label: t('structureEditor.publishChecklist.items.session'),
753
+ done: sessions.length > 0,
754
+ required: true,
755
+ },
756
+ {
757
+ id: 'lesson',
758
+ label: t('structureEditor.publishChecklist.items.lesson'),
759
+ done: lessons.length > 0,
760
+ required: true,
761
+ },
762
+ {
763
+ id: 'published-lessons',
764
+ label: t('structureEditor.publishChecklist.items.publishedLessons'),
765
+ done: publishedCount > 0,
766
+ required: true,
767
+ },
768
+ {
769
+ id: 'certificate',
770
+ label: t('structureEditor.publishChecklist.items.certificate'),
771
+ done:
772
+ !formValues.certificado ||
773
+ String(formValues.modeloCertificado ?? '').trim().length > 0,
774
+ required: formValues.certificado,
775
+ },
776
+ ];
777
+ const requiredChecklist = checklistItems.filter((item) => item.required);
778
+ const completedRequired = requiredChecklist.filter(
779
+ (item) => item.done
780
+ ).length;
781
+ const isReadyToPublish =
782
+ requiredChecklist.length > 0 &&
783
+ completedRequired === requiredChecklist.length;
784
+
597
785
  // ── Effects ──────────────────────────────────────────────────────────────────
598
786
  useEffect(() => {
599
787
  if (!apiCourse) return;
@@ -610,6 +798,9 @@ export function EditorCourse() {
610
798
  status: toPtStatus(apiCourse.status),
611
799
  tipoOferta: toPtOfferingType(apiCourse.offeringType),
612
800
  categorias: apiCourse.categories ?? [],
801
+ operationsProjectId: apiCourse.operationsProjectId
802
+ ? String(apiCourse.operationsProjectId)
803
+ : '',
613
804
  primaryColor: apiCourse.primaryColor || '#1D4ED8',
614
805
  secondaryColor: apiCourse.secondaryColor || '#111827',
615
806
  instrutores: (apiCourse.instructorIds ?? []).map(String),
@@ -643,6 +834,138 @@ export function EditorCourse() {
643
834
  });
644
835
  }, [apiCourse?.bannerFileId, apiCourse?.logoFileId, request]);
645
836
 
837
+ useEffect(() => {
838
+ if (!courseId) return;
839
+
840
+ let active = true;
841
+
842
+ void getCourseResources(request, courseId)
843
+ .then((items) => {
844
+ if (!active) return;
845
+ setCourseResources(items.map(mapApiCourseResourceToLocal));
846
+ })
847
+ .catch(() => {
848
+ if (!active) return;
849
+ setCourseResources([]);
850
+ toast.error('Nao foi possivel carregar os recursos do curso.');
851
+ });
852
+
853
+ return () => {
854
+ active = false;
855
+ };
856
+ }, [courseId, request]);
857
+
858
+ async function syncCourseResources(nextResources: CourseResourceItem[]) {
859
+ setIsSavingResources(true);
860
+ try {
861
+ const saved = await updateCourseResources(request, courseId, {
862
+ recursos: nextResources.map((item) => ({
863
+ nome: item.name,
864
+ ...(typeof item.fileId === 'number' && item.fileId > 0
865
+ ? { fileId: item.fileId }
866
+ : {}),
867
+ ...(item.type ? { tipo: item.type } : {}),
868
+ publico: item.public,
869
+ })),
870
+ });
871
+
872
+ setCourseResources(saved.map(mapApiCourseResourceToLocal));
873
+ toast.success('Recursos do curso atualizados.');
874
+ } catch {
875
+ toast.error('Nao foi possivel salvar os recursos do curso.');
876
+ void getCourseResources(request, courseId)
877
+ .then((items) =>
878
+ setCourseResources(items.map(mapApiCourseResourceToLocal))
879
+ )
880
+ .catch(() => undefined);
881
+ } finally {
882
+ setIsSavingResources(false);
883
+ }
884
+ }
885
+
886
+ async function handleCourseResourceFiles(files: File[]) {
887
+ if (files.length === 0) return;
888
+
889
+ setIsUploadingResources(true);
890
+ try {
891
+ const results = await Promise.allSettled(
892
+ files.map((file) =>
893
+ uploadFile(request, file, 'lms/courses/resources').then(
894
+ (uploaded): CourseResourceItem => ({
895
+ id: `new-${uploaded.id}`,
896
+ fileId: uploaded.id,
897
+ name: file.name,
898
+ size: formatFileSize(file.size),
899
+ type: file.type || file.name.split('.').pop() || 'file',
900
+ public: true,
901
+ })
902
+ )
903
+ )
904
+ );
905
+
906
+ const succeeded = results
907
+ .filter(
908
+ (result): result is PromiseFulfilledResult<CourseResourceItem> =>
909
+ result.status === 'fulfilled'
910
+ )
911
+ .map((result) => result.value);
912
+
913
+ const failedCount = results.filter(
914
+ (result) => result.status === 'rejected'
915
+ ).length;
916
+
917
+ if (failedCount > 0) {
918
+ toast.error(`Falha ao enviar ${failedCount} arquivo(s).`);
919
+ }
920
+
921
+ if (succeeded.length > 0) {
922
+ const nextResources = [...courseResources, ...succeeded];
923
+ await syncCourseResources(nextResources);
924
+ }
925
+ } finally {
926
+ setIsUploadingResources(false);
927
+ }
928
+ }
929
+
930
+ async function handleRemoveCourseResource(item: CourseResourceItem) {
931
+ const nextResources = courseResources.filter(
932
+ (resource) => resource.id !== item.id
933
+ );
934
+ await syncCourseResources(nextResources);
935
+
936
+ if (item.fileId) {
937
+ try {
938
+ await deleteFile(request, item.fileId);
939
+ } catch {
940
+ toast.error(
941
+ 'Recurso removido do curso, mas nao foi possivel apagar o arquivo fisico.'
942
+ );
943
+ }
944
+ }
945
+ }
946
+
947
+ async function handleDownloadCourseResource(item: CourseResourceItem) {
948
+ if (!item.fileId) {
949
+ toast.error('Recurso sem arquivo para download.');
950
+ return;
951
+ }
952
+
953
+ try {
954
+ const response = await request<{ url?: string }>({
955
+ url: `/file/open/${item.fileId}`,
956
+ method: 'PUT',
957
+ });
958
+ const url = response?.data?.url;
959
+ if (!url) {
960
+ toast.error('Nao foi possivel gerar o link de download.');
961
+ return;
962
+ }
963
+ window.open(url, '_blank', 'noopener,noreferrer');
964
+ } catch {
965
+ toast.error('Nao foi possivel baixar o recurso.');
966
+ }
967
+ }
968
+
646
969
  // ── File handlers ────────────────────────────────────────────────────────────
647
970
  async function openUploadedFile(fileId?: number | null) {
648
971
  if (!fileId) return;
@@ -967,20 +1290,33 @@ export function EditorCourse() {
967
1290
  onValueChange={setActiveTab}
968
1291
  className="flex flex-col flex-1 min-h-0"
969
1292
  >
970
- <TabsList className="mx-3 mt-3 h-auto shrink-0 grid w-[calc(100%-1.5rem)] grid-cols-4 rounded-lg bg-muted/80 p-1">
971
- {(['estrutura', 'sobre', 'midia', 'extra'] as const).map((tab) => (
1293
+ <TabsList className="mx-3 mt-3 h-auto shrink-0 grid w-[calc(100%-1.5rem)] grid-cols-6 rounded-lg bg-muted/80 p-1">
1294
+ {(
1295
+ [
1296
+ 'estrutura',
1297
+ 'sobre',
1298
+ 'midia',
1299
+ 'recursos',
1300
+ 'extra',
1301
+ 'publicacao',
1302
+ ] as const
1303
+ ).map((tab) => (
972
1304
  <TabsTrigger
973
1305
  key={tab}
974
1306
  value={tab}
975
1307
  className="h-8 px-2 text-xs font-medium"
976
1308
  >
977
1309
  {tab === 'estrutura'
978
- ? 'Estrutura'
1310
+ ? t('structureEditor.tabs.structure')
979
1311
  : tab === 'sobre'
980
- ? 'Sobre'
1312
+ ? t('structureEditor.tabs.about')
981
1313
  : tab === 'midia'
982
- ? 'Mídia'
983
- : 'Extra'}
1314
+ ? t('structureEditor.tabs.media')
1315
+ : tab === 'recursos'
1316
+ ? t('structureEditor.tabs.resources')
1317
+ : tab === 'extra'
1318
+ ? t('structureEditor.tabs.extra')
1319
+ : t('structureEditor.tabs.publish')}
984
1320
  </TabsTrigger>
985
1321
  ))}
986
1322
  </TabsList>
@@ -1130,6 +1466,7 @@ export function EditorCourse() {
1130
1466
  form={form}
1131
1467
  t={t}
1132
1468
  categoryOptions={categoryOptions}
1469
+ projectOptions={projectOptions}
1133
1470
  instructorOptions={instructorOptions}
1134
1471
  onCreateCategory={handleCreateCategory}
1135
1472
  onCreateInstructor={handleCreateInstructor}
@@ -1194,6 +1531,169 @@ export function EditorCourse() {
1194
1531
  />
1195
1532
  </TabsContent>
1196
1533
 
1534
+ {/* ── Tab: Recursos ─────────────────────────────────────── */}
1535
+ <TabsContent
1536
+ value="recursos"
1537
+ className="mt-0 flex flex-col gap-3"
1538
+ >
1539
+ <Card className="bg-muted/20 py-2 gap-2">
1540
+ <CardHeader className="px-3 pt-2 pb-0">
1541
+ <CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
1542
+ Recursos para download do curso
1543
+ </CardTitle>
1544
+ </CardHeader>
1545
+ <CardContent className="px-3 pb-2 flex flex-col gap-3">
1546
+ <div
1547
+ role="button"
1548
+ tabIndex={0}
1549
+ aria-label="Soltar arquivo ou clicar para selecionar"
1550
+ aria-disabled={
1551
+ isUploadingResources || isSavingResources
1552
+ }
1553
+ onDragOver={(event) => {
1554
+ event.preventDefault();
1555
+ if (!isUploadingResources && !isSavingResources)
1556
+ setResourcesDragOver(true);
1557
+ }}
1558
+ onDragLeave={() => setResourcesDragOver(false)}
1559
+ onDrop={(event) => {
1560
+ event.preventDefault();
1561
+ setResourcesDragOver(false);
1562
+ if (!isUploadingResources && !isSavingResources) {
1563
+ void handleCourseResourceFiles(
1564
+ Array.from(event.dataTransfer.files)
1565
+ );
1566
+ }
1567
+ }}
1568
+ onClick={() => {
1569
+ if (!isUploadingResources && !isSavingResources) {
1570
+ resourcesInputRef.current?.click();
1571
+ }
1572
+ }}
1573
+ onKeyDown={(event) => {
1574
+ if (event.key === 'Enter' || event.key === ' ') {
1575
+ event.preventDefault();
1576
+ if (!isUploadingResources && !isSavingResources) {
1577
+ resourcesInputRef.current?.click();
1578
+ }
1579
+ }
1580
+ }}
1581
+ className={cn(
1582
+ 'flex flex-col items-center justify-center gap-2 py-7 rounded-lg border-2 border-dashed transition-colors select-none',
1583
+ isUploadingResources || isSavingResources
1584
+ ? 'cursor-wait border-border opacity-60'
1585
+ : 'cursor-pointer',
1586
+ !isUploadingResources &&
1587
+ !isSavingResources &&
1588
+ resourcesDragOver
1589
+ ? 'border-primary/70 bg-primary/5'
1590
+ : !isUploadingResources && !isSavingResources
1591
+ ? 'border-border hover:border-primary/40 hover:bg-muted/30'
1592
+ : ''
1593
+ )}
1594
+ >
1595
+ {isUploadingResources || isSavingResources ? (
1596
+ <Loader2 className="size-7 animate-spin text-muted-foreground/50" />
1597
+ ) : (
1598
+ <UploadCloud
1599
+ className={cn(
1600
+ 'size-7 transition-colors',
1601
+ resourcesDragOver
1602
+ ? 'text-primary'
1603
+ : 'text-muted-foreground/50'
1604
+ )}
1605
+ />
1606
+ )}
1607
+ <div className="text-center">
1608
+ <p className="text-xs font-medium">
1609
+ {isUploadingResources
1610
+ ? 'Enviando arquivos...'
1611
+ : isSavingResources
1612
+ ? 'Salvando recursos...'
1613
+ : 'Arraste arquivos para adicionar'}
1614
+ </p>
1615
+ {!isUploadingResources && !isSavingResources && (
1616
+ <p className="text-xs text-muted-foreground">
1617
+ Clique para selecionar arquivos
1618
+ </p>
1619
+ )}
1620
+ </div>
1621
+ <input
1622
+ ref={resourcesInputRef}
1623
+ type="file"
1624
+ multiple
1625
+ className="hidden"
1626
+ onChange={(event) => {
1627
+ if (event.target.files) {
1628
+ void handleCourseResourceFiles(
1629
+ Array.from(event.target.files)
1630
+ );
1631
+ event.target.value = '';
1632
+ }
1633
+ }}
1634
+ />
1635
+ </div>
1636
+
1637
+ {courseResources.length === 0 ? (
1638
+ <p className="text-center text-xs text-muted-foreground py-1">
1639
+ Nenhum recurso de curso cadastrado ainda.
1640
+ </p>
1641
+ ) : (
1642
+ <div className="flex flex-col gap-1">
1643
+ {courseResources.map((item) => {
1644
+ const ItemIcon = getResourceIcon(item.type);
1645
+ return (
1646
+ <div
1647
+ key={item.id}
1648
+ className="flex items-center gap-2 rounded-md border bg-muted/20 px-2.5 py-2"
1649
+ >
1650
+ <ItemIcon
1651
+ className={cn(
1652
+ 'size-3.5 shrink-0',
1653
+ getResourceIconColor(item.type)
1654
+ )}
1655
+ />
1656
+ <div className="flex-1 min-w-0">
1657
+ <p className="text-xs truncate font-medium">
1658
+ {item.name}
1659
+ </p>
1660
+ <p className="text-[0.65rem] text-muted-foreground">
1661
+ {item.size || 'Arquivo anexado'}
1662
+ </p>
1663
+ </div>
1664
+ <Button
1665
+ type="button"
1666
+ variant="ghost"
1667
+ size="icon"
1668
+ className="size-6 shrink-0 text-muted-foreground hover:text-foreground"
1669
+ onClick={() =>
1670
+ void handleDownloadCourseResource(item)
1671
+ }
1672
+ aria-label={`Baixar ${item.name}`}
1673
+ >
1674
+ <Download className="size-3" />
1675
+ </Button>
1676
+ <Button
1677
+ type="button"
1678
+ variant="ghost"
1679
+ size="icon"
1680
+ className="size-6 shrink-0 text-muted-foreground hover:text-destructive"
1681
+ onClick={() =>
1682
+ void handleRemoveCourseResource(item)
1683
+ }
1684
+ aria-label={`Remover ${item.name}`}
1685
+ >
1686
+ <X className="size-3" />
1687
+ </Button>
1688
+ </div>
1689
+ );
1690
+ })}
1691
+ </div>
1692
+ )}
1693
+ </CardContent>
1694
+ </Card>
1695
+ </TabsContent>
1696
+
1197
1697
  {/* ── Tab: Extra ──────────────────────────────────────────── */}
1198
1698
  <TabsContent value="extra" className="mt-0 flex flex-col gap-3">
1199
1699
  <CourseCertificateCard
@@ -1208,6 +1708,81 @@ export function EditorCourse() {
1208
1708
  onDelete={() => setDeleteDialogOpen(true)}
1209
1709
  />
1210
1710
  </TabsContent>
1711
+
1712
+ {/* ── Tab: Publicação ─────────────────────────────────────── */}
1713
+ <TabsContent
1714
+ value="publicacao"
1715
+ className="mt-0 flex flex-col gap-3"
1716
+ >
1717
+ <Card className="bg-muted/20 py-2 gap-2">
1718
+ <CardHeader className="px-3 pt-2 pb-0">
1719
+ <CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
1720
+ {t('structureEditor.publishChecklist.title')}
1721
+ </CardTitle>
1722
+ </CardHeader>
1723
+ <CardContent className="px-3 pb-2 flex flex-col gap-2">
1724
+ {checklistItems.map((item) => (
1725
+ <div
1726
+ key={item.id}
1727
+ className="flex items-center gap-2 rounded-md border bg-background/80 px-2.5 py-2"
1728
+ >
1729
+ {item.done ? (
1730
+ <CheckCircle2 className="size-4 shrink-0 text-emerald-500" />
1731
+ ) : (
1732
+ <AlertTriangle className="size-4 shrink-0 text-amber-500" />
1733
+ )}
1734
+ <div className="flex-1 min-w-0">
1735
+ <p className="text-xs font-medium truncate">
1736
+ {item.label}
1737
+ </p>
1738
+ {!item.done && (
1739
+ <p className="text-[0.65rem] text-muted-foreground">
1740
+ {item.required
1741
+ ? t(
1742
+ 'structureEditor.publishChecklist.required'
1743
+ )
1744
+ : t(
1745
+ 'structureEditor.publishChecklist.recommended'
1746
+ )}
1747
+ </p>
1748
+ )}
1749
+ </div>
1750
+ <Badge
1751
+ variant={item.done ? 'default' : 'secondary'}
1752
+ className="text-[0.65rem]"
1753
+ >
1754
+ {item.done
1755
+ ? t('structureEditor.publishChecklist.done')
1756
+ : t('structureEditor.publishChecklist.missing')}
1757
+ </Badge>
1758
+ </div>
1759
+ ))}
1760
+ </CardContent>
1761
+ </Card>
1762
+
1763
+ <Card className="bg-muted/20 py-2 gap-2">
1764
+ <CardHeader className="px-3 pt-2 pb-0">
1765
+ <CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
1766
+ {t('structureEditor.publishChecklist.statusTitle')}
1767
+ </CardTitle>
1768
+ </CardHeader>
1769
+ <CardContent className="px-3 pb-2 flex items-center justify-between gap-2">
1770
+ <p className="text-xs text-muted-foreground">
1771
+ {t('structureEditor.publishChecklist.statusProgress', {
1772
+ completed: completedRequired,
1773
+ total: requiredChecklist.length,
1774
+ })}
1775
+ </p>
1776
+ <Badge
1777
+ variant={isReadyToPublish ? 'default' : 'secondary'}
1778
+ >
1779
+ {isReadyToPublish
1780
+ ? t('structureEditor.publishChecklist.ready')
1781
+ : t('structureEditor.publishChecklist.notReady')}
1782
+ </Badge>
1783
+ </CardContent>
1784
+ </Card>
1785
+ </TabsContent>
1211
1786
  </div>
1212
1787
  </ScrollArea>
1213
1788
  </div>