@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
@@ -111,7 +111,7 @@ const LESSON_TYPES: LessonType[] = [
111
111
  const PROVIDERS: VideoProvider[] = [
112
112
  'youtube',
113
113
  'vimeo',
114
- 'bunny',
114
+ 'file_storage',
115
115
  'youtube',
116
116
  'youtube',
117
117
  ];
@@ -21,6 +21,7 @@ import {
21
21
  Plus,
22
22
  Trash2,
23
23
  } from 'lucide-react';
24
+ import { useTranslations } from 'next-intl';
24
25
  import { toast } from 'sonner';
25
26
 
26
27
  import {
@@ -53,6 +54,7 @@ interface TreeContextMenuProps {
53
54
  // ─────────────────────────────────────────────────────────────────────────────
54
55
 
55
56
  function CourseMenu({ data }: { data: Course }) {
57
+ const t = useTranslations('lms.CoursesPage.StructurePage');
56
58
  const expandAll = useStructureStore((s) => s.expandAll);
57
59
  const collapseAll = useStructureStore((s) => s.collapseAll);
58
60
  const copiedType = useStructureStore((s) => s.copiedType);
@@ -80,7 +82,7 @@ function CourseMenu({ data }: { data: Course }) {
80
82
  ) : (
81
83
  <Plus className="size-3.5 mr-2 text-muted-foreground" />
82
84
  )}
83
- Nova sessão
85
+ {t('contextMenu.addSession')}
84
86
  </ContextMenuItem>
85
87
 
86
88
  <ContextMenuSeparator />
@@ -92,7 +94,7 @@ function CourseMenu({ data }: { data: Course }) {
92
94
  }}
93
95
  >
94
96
  <BookOpen className="size-3.5 mr-2 text-muted-foreground" />
95
- Expandir tudo
97
+ {t('tree.expandAll')}
96
98
  </ContextMenuItem>
97
99
 
98
100
  <ContextMenuItem
@@ -102,7 +104,7 @@ function CourseMenu({ data }: { data: Course }) {
102
104
  }}
103
105
  >
104
106
  <Layers className="size-3.5 mr-2 text-muted-foreground" />
105
- Recolher tudo
107
+ {t('tree.collapseAll')}
106
108
  </ContextMenuItem>
107
109
 
108
110
  {hasSessionClipboard && (
@@ -119,7 +121,8 @@ function CourseMenu({ data }: { data: Course }) {
119
121
  ) : (
120
122
  <Clipboard className="size-3.5 mr-2 text-muted-foreground" />
121
123
  )}
122
- Colar sessão{copiedIds.length > 1 ? ` (${copiedIds.length})` : ''}
124
+ {t('contextMenu.pasteSession')}
125
+ {copiedIds.length > 1 ? ` (${copiedIds.length})` : ''}
123
126
  </ContextMenuItem>
124
127
  </>
125
128
  )}
@@ -523,3 +526,102 @@ export function TreeContextMenu({ item, children }: TreeContextMenuProps) {
523
526
  </ContextMenu>
524
527
  );
525
528
  }
529
+
530
+ export function TreeRootContextMenu({
531
+ children,
532
+ }: {
533
+ children: React.ReactNode;
534
+ }) {
535
+ const t = useTranslations('lms.CoursesPage.StructurePage');
536
+ const course = useStructureStore((s) => s.course);
537
+ const activeItemId = useStructureStore((s) => s.activeItemId);
538
+ const activeItemType = useStructureStore((s) => s.activeItemType);
539
+ const lessons = useStructureStore((s) => s.lessons);
540
+ const copiedType = useStructureStore((s) => s.copiedType);
541
+ const copiedIds = useStructureStore((s) => s.copiedIds);
542
+
543
+ const createSessionMutation = useCreateSessionMutation();
544
+ const pasteSessionsMutation = usePasteSessionsMutation();
545
+ const pasteLessonsMutation = usePasteLessonsMutation();
546
+
547
+ const hasSessionClipboard = copiedType === 'session' && copiedIds.length > 0;
548
+ const hasLessonClipboard = copiedType === 'lesson' && copiedIds.length > 0;
549
+
550
+ function getActiveSessionId(): string | null {
551
+ if (activeItemType === 'session' && activeItemId) return activeItemId;
552
+ if (activeItemType === 'lesson' && activeItemId) {
553
+ const lesson = lessons.find((item) => item.id === activeItemId);
554
+ return lesson?.sessionId ?? null;
555
+ }
556
+ return null;
557
+ }
558
+
559
+ return (
560
+ <ContextMenu>
561
+ <ContextMenuTrigger className="block h-full w-full">
562
+ {children}
563
+ </ContextMenuTrigger>
564
+ <ContextMenuContent className="w-56">
565
+ <ContextMenuLabel className="text-[0.65rem] text-muted-foreground px-2 py-1">
566
+ {course.title || course.name || t('contextMenu.courseFallback')}
567
+ </ContextMenuLabel>
568
+ <ContextMenuSeparator />
569
+
570
+ <ContextMenuItem
571
+ disabled={createSessionMutation.isPending}
572
+ onSelect={() => createSessionMutation.mutate()}
573
+ >
574
+ {createSessionMutation.isPending ? (
575
+ <Loader2 className="size-3.5 mr-2 animate-spin" />
576
+ ) : (
577
+ <Plus className="size-3.5 mr-2 text-muted-foreground" />
578
+ )}
579
+ {t('contextMenu.addSession')}
580
+ </ContextMenuItem>
581
+
582
+ {hasSessionClipboard && (
583
+ <ContextMenuItem
584
+ disabled={pasteSessionsMutation.isPending}
585
+ onSelect={() => {
586
+ pasteSessionsMutation.mutate({ sessionIds: copiedIds });
587
+ }}
588
+ >
589
+ {pasteSessionsMutation.isPending ? (
590
+ <Loader2 className="size-3.5 mr-2 animate-spin" />
591
+ ) : (
592
+ <Clipboard className="size-3.5 mr-2 text-muted-foreground" />
593
+ )}
594
+ {t('contextMenu.pasteSession')}
595
+ {copiedIds.length > 1 ? ` (${copiedIds.length})` : ''}
596
+ </ContextMenuItem>
597
+ )}
598
+
599
+ {hasLessonClipboard && (
600
+ <ContextMenuItem
601
+ disabled={pasteLessonsMutation.isPending}
602
+ onSelect={() => {
603
+ const targetSessionId = getActiveSessionId();
604
+ if (!targetSessionId) {
605
+ toast.error(t('contextMenu.selectSessionToPasteLessons'));
606
+ return;
607
+ }
608
+ pasteLessonsMutation.mutate({
609
+ targetSessionId,
610
+ lessonIds: copiedIds,
611
+ });
612
+ }}
613
+ >
614
+ {pasteLessonsMutation.isPending ? (
615
+ <Loader2 className="size-3.5 mr-2 animate-spin" />
616
+ ) : (
617
+ <Clipboard className="size-3.5 mr-2 text-muted-foreground" />
618
+ )}
619
+ {copiedIds.length > 1
620
+ ? t('contextMenu.pasteLessons', { count: copiedIds.length })
621
+ : t('contextMenu.pasteLesson')}
622
+ </ContextMenuItem>
623
+ )}
624
+ </ContextMenuContent>
625
+ </ContextMenu>
626
+ );
627
+ }
@@ -3,7 +3,7 @@
3
3
  // ─────────────────────────────────────────────────────────────────────────────
4
4
 
5
5
  export type LessonType = 'video' | 'questao' | 'post' | 'exercicio';
6
- export type VideoProvider = 'youtube' | 'vimeo' | 'bunny' | 'custom';
6
+ export type VideoProvider = 'youtube' | 'vimeo' | 'file_storage';
7
7
  export type ItemType = 'course' | 'session' | 'lesson';
8
8
 
9
9
  export type LessonStatus =
@@ -24,6 +24,7 @@ export interface Course {
24
24
  title: string;
25
25
  description: string;
26
26
  slug: string;
27
+ offeringType?: 'scheduled' | 'on_demand' | 'blended';
27
28
  published: boolean;
28
29
  }
29
30
 
@@ -61,6 +61,15 @@ function normalizeLessonType(tipo: ApiLesson['tipo']): LessonType {
61
61
  return tipo as LessonType;
62
62
  }
63
63
 
64
+ function normalizeVideoProvider(
65
+ provider?: ApiLesson['videoProvedor']
66
+ ): Lesson['videoProvider'] {
67
+ if (provider === 'youtube' || provider === 'vimeo') return provider;
68
+ if (provider === 'file_storage') return 'file_storage';
69
+ if (provider === 'bunny' || provider === 'custom') return 'file_storage';
70
+ return undefined;
71
+ }
72
+
64
73
  /** Normalize a raw API resource to the frontend Resource shape. */
65
74
  function normalizeResource(raw: ApiLessonResource): Resource {
66
75
  return {
@@ -96,6 +105,7 @@ export function normalizeCourse(raw: ApiCourse): Course {
96
105
  title: raw.titulo,
97
106
  description: raw.descricao,
98
107
  slug: raw.slug,
108
+ offeringType: raw.offeringType,
99
109
  published: false,
100
110
  };
101
111
  }
@@ -149,7 +159,7 @@ export function normalizeLesson(raw: ApiLesson, order = 0): Lesson {
149
159
  duration: raw.duracao,
150
160
  sessionId: raw.sessaoId,
151
161
  order,
152
- videoProvider: raw.videoProvedor,
162
+ videoProvider: normalizeVideoProvider(raw.videoProvedor),
153
163
  videoUrl: raw.videoUrl,
154
164
  autoDuration: raw.duracaoAutomatica,
155
165
  transcription: raw.transcricao,
@@ -21,6 +21,7 @@
21
21
  */
22
22
 
23
23
  import type {
24
+ ApiCourseResource,
24
25
  ApiCreateLessonPayload,
25
26
  ApiCreateSessionPayload,
26
27
  ApiDuplicateSessionResponse,
@@ -107,6 +108,47 @@ export async function getCourseStructure(
107
108
  return extractData<ApiGetStructureResponse>(response);
108
109
  }
109
110
 
111
+ /**
112
+ * GET /lms/courses/:courseId/structure/resources
113
+ *
114
+ * Lists resources attached directly to the course (not tied to lessons).
115
+ */
116
+ export async function getCourseResources(
117
+ request: RequestFn,
118
+ courseId: string | number
119
+ ): Promise<ApiCourseResource[]> {
120
+ const response = await request({
121
+ url: `${STRUCTURE_BASE(courseId)}/resources`,
122
+ method: 'GET',
123
+ });
124
+ return extractData<ApiCourseResource[]>(response);
125
+ }
126
+
127
+ /**
128
+ * PATCH /lms/courses/:courseId/structure/resources
129
+ *
130
+ * Replaces the full set of course-level resources.
131
+ */
132
+ export async function updateCourseResources(
133
+ request: RequestFn,
134
+ courseId: string | number,
135
+ payload: {
136
+ recursos: Array<{
137
+ nome: string;
138
+ fileId?: number;
139
+ tipo?: string;
140
+ publico?: boolean;
141
+ }>;
142
+ }
143
+ ): Promise<ApiCourseResource[]> {
144
+ const response = await request({
145
+ url: `${STRUCTURE_BASE(courseId)}/resources`,
146
+ method: 'PATCH',
147
+ data: payload,
148
+ });
149
+ return extractData<ApiCourseResource[]>(response);
150
+ }
151
+
110
152
  /**
111
153
  * PATCH /lms/courses/:courseId
112
154
  *
@@ -391,11 +433,12 @@ export async function pasteLessons(
391
433
  */
392
434
  export async function uploadFile(
393
435
  request: RequestFn,
394
- file: File
436
+ file: File,
437
+ destination = 'lms/lessons'
395
438
  ): Promise<{ id: number; filename: string }> {
396
439
  const formData = new FormData();
397
440
  formData.append('file', file);
398
- formData.append('destination', 'lms/lessons');
441
+ formData.append('destination', destination);
399
442
 
400
443
  const response = await request({
401
444
  url: '/file',
@@ -407,14 +450,18 @@ export async function uploadFile(
407
450
  }
408
451
 
409
452
  /**
410
- * DELETE /file/:fileId
453
+ * DELETE /file
411
454
  *
412
- * Permanently deletes a stored file by its ID.
413
- * Called when the user removes a resource from a lesson.
455
+ * Permanently deletes stored files by ID list.
456
+ * Called when the user removes a resource from lesson/course.
414
457
  */
415
458
  export async function deleteFile(
416
459
  request: RequestFn,
417
460
  fileId: number
418
461
  ): Promise<void> {
419
- await request({ url: `/file/${fileId}`, method: 'DELETE' });
462
+ await request({
463
+ url: '/file',
464
+ method: 'DELETE',
465
+ data: { ids: [fileId] },
466
+ });
420
467
  }
@@ -31,6 +31,15 @@ export interface ApiLessonResource {
31
31
  url?: string;
32
32
  }
33
33
 
34
+ /** Resource (arquivo de suporte) attached directly to a course. */
35
+ export interface ApiCourseResource {
36
+ id: number;
37
+ nome: string;
38
+ fileId?: number | null;
39
+ tipo?: string | null;
40
+ publico?: boolean;
41
+ }
42
+
34
43
  /** Instructor linked to a lesson or available in the course pool. */
35
44
  export interface ApiLessonInstructor {
36
45
  id: number;
@@ -63,7 +72,7 @@ export interface ApiLesson {
63
72
  duracao: number;
64
73
  /** ID of the parent session (course_module). */
65
74
  sessaoId: string;
66
- videoProvedor?: 'youtube' | 'vimeo' | 'bunny' | 'custom';
75
+ videoProvedor?: 'youtube' | 'vimeo' | 'file_storage' | 'bunny' | 'custom';
67
76
  videoUrl?: string;
68
77
  duracaoAutomatica?: boolean;
69
78
  transcricao?: string;
@@ -105,6 +114,8 @@ export interface ApiCourse {
105
114
  slug: string;
106
115
  titulo: string;
107
116
  descricao: string;
117
+ offeringType?: 'scheduled' | 'on_demand' | 'blended';
118
+ recursos?: ApiCourseResource[];
108
119
  }
109
120
 
110
121
  /**
@@ -173,7 +184,7 @@ export interface ApiCreateLessonPayload {
173
184
  duracao: number;
174
185
  descricaoPublica?: string;
175
186
  descricaoPrivada?: string;
176
- videoProvedor?: 'youtube' | 'vimeo' | 'bunny' | 'custom';
187
+ videoProvedor?: 'youtube' | 'vimeo' | 'file_storage' | 'bunny' | 'custom';
177
188
  videoUrl?: string;
178
189
  duracaoAutomatica?: boolean;
179
190
  transcricao?: string;
@@ -27,8 +27,18 @@ import {
27
27
  DropdownMenuSeparator,
28
28
  DropdownMenuTrigger,
29
29
  } from '@/components/ui/dropdown-menu';
30
+ import { Input } from '@/components/ui/input';
30
31
  import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
32
+ import { Label } from '@/components/ui/label';
31
33
  import { Separator } from '@/components/ui/separator';
34
+ import {
35
+ Sheet,
36
+ SheetContent,
37
+ SheetDescription,
38
+ SheetFooter,
39
+ SheetHeader,
40
+ SheetTitle,
41
+ } from '@/components/ui/sheet';
32
42
  import { Skeleton } from '@/components/ui/skeleton';
33
43
  import {
34
44
  Table,
@@ -38,6 +48,7 @@ import {
38
48
  TableHeader,
39
49
  TableRow,
40
50
  } from '@/components/ui/table';
51
+ import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
41
52
  import { usePersistedViewMode } from '@/hooks/use-persisted-view-mode';
42
53
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
43
54
  import { zodResolver } from '@hookform/resolvers/zod';
@@ -59,8 +70,7 @@ import {
59
70
  } from 'lucide-react';
60
71
  import { useTranslations } from 'next-intl';
61
72
  import { useRouter } from 'next/navigation';
62
- import { useEffect, useMemo, useRef, useState } from 'react';
63
- import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
73
+ import { useEffect, useMemo, useState } from 'react';
64
74
  import { useForm, useWatch } from 'react-hook-form';
65
75
  import { toast } from 'sonner';
66
76
  import { CourseAvatar } from '../_components/course-avatar';
@@ -93,6 +103,7 @@ interface Curso {
93
103
  alunosInscritos: number;
94
104
  criadoEm: string;
95
105
  logoFileId?: number | null;
106
+ operationsProjectId?: number | null;
96
107
  }
97
108
 
98
109
  type ApiCourse = {
@@ -114,6 +125,7 @@ type ApiCourse = {
114
125
  enrollmentCount: number;
115
126
  createdAt: string;
116
127
  logoFileId?: number | null;
128
+ operationsProjectId?: number | null;
117
129
  };
118
130
 
119
131
  type ApiCourseList = {
@@ -218,6 +230,7 @@ function mapApiCourse(course: ApiCourse): Curso {
218
230
  alunosInscritos: course.enrollmentCount ?? 0,
219
231
  criadoEm: course.createdAt ?? '',
220
232
  logoFileId: course.logoFileId ?? null,
233
+ operationsProjectId: course.operationsProjectId ?? null,
221
234
  };
222
235
  }
223
236
 
@@ -259,6 +272,8 @@ const STATUS_VARIANT: Record<string, 'default' | 'secondary' | 'outline'> = {
259
272
  arquivado: 'outline',
260
273
  };
261
274
 
275
+ const PAGE_SIZES = [6, 12, 24] as const;
276
+
262
277
  // ── Seed Data ─────────────────────────────────────────────────────────────────
263
278
 
264
279
  // ── Animations ────────────────────────────────────────────────────────────────
@@ -283,7 +298,12 @@ export default function CursosPage() {
283
298
  // UI
284
299
  const [sheetOpen, setSheetOpen] = useState(false);
285
300
  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
301
+ const [createCategorySheetOpen, setCreateCategorySheetOpen] = useState(false);
286
302
  const [saving, setSaving] = useState(false);
303
+ const [creatingCategory, setCreatingCategory] = useState(false);
304
+ const [newCategoryName, setNewCategoryName] = useState('');
305
+ const [newCategorySlug, setNewCategorySlug] = useState('');
306
+ const [newCategorySlugTouched, setNewCategorySlugTouched] = useState(false);
287
307
 
288
308
  // Data
289
309
  const [editingCurso, setEditingCurso] = useState<Curso | null>(null);
@@ -311,14 +331,9 @@ export default function CursosPage() {
311
331
  const [pageSize, setPageSize] = usePersistedPageSize({
312
332
  storageKey: 'pagination:global:pageSize',
313
333
  defaultValue: 12,
314
- allowedValues: [6, 12, 24],
334
+ allowedValues: PAGE_SIZES,
315
335
  });
316
336
 
317
- // Double-click tracking
318
- const clickTimers = useRef<Map<number, ReturnType<typeof setTimeout>>>(
319
- new Map()
320
- );
321
-
322
337
  const form = useForm<CourseSheetFormValues>({
323
338
  resolver: zodResolver(getCourseSheetSchema(t)),
324
339
  defaultValues: DEFAULT_COURSE_FORM_VALUES,
@@ -557,22 +572,6 @@ export default function CursosPage() {
557
572
  setDeleteDialogOpen(true);
558
573
  }
559
574
 
560
- // ── Double-click to navigate ──────────────────────────────────────────────
561
-
562
- function handleCardClick(curso: Curso) {
563
- const existing = clickTimers.current.get(curso.id);
564
- if (existing) {
565
- clearTimeout(existing);
566
- clickTimers.current.delete(curso.id);
567
- router.push(`/lms/courses/${curso.id}`);
568
- } else {
569
- const t = setTimeout(() => {
570
- clickTimers.current.delete(curso.id);
571
- }, 300);
572
- clickTimers.current.set(curso.id, t);
573
- }
574
- }
575
-
576
575
  // ── Selection ─────────────────────────────────────────────────────────────
577
576
 
578
577
  function toggleSelect(id: number, e: React.MouseEvent) {
@@ -609,6 +608,9 @@ export default function CursosPage() {
609
608
  nivel: curso.nivel,
610
609
  status: curso.status,
611
610
  categorias: curso.categorias,
611
+ operationsProjectId: curso.operationsProjectId
612
+ ? String(curso.operationsProjectId)
613
+ : '',
612
614
  logoFileId: curso.logoFileId ?? null,
613
615
  });
614
616
  setSheetOpen(true);
@@ -629,6 +631,9 @@ export default function CursosPage() {
629
631
  primaryContrastColor: getContrastColor(data.primaryColor),
630
632
  secondaryColor: data.secondaryColor,
631
633
  secondaryContrastColor: getContrastColor(data.secondaryColor),
634
+ operationsProjectId: data.operationsProjectId
635
+ ? Number(data.operationsProjectId)
636
+ : null,
632
637
  logoFileId: data.logoFileId ?? null,
633
638
  };
634
639
 
@@ -729,7 +734,7 @@ export default function CursosPage() {
729
734
  },
730
735
  ];
731
736
 
732
- const { locales } = useApp();
737
+ const { locales, currentLocaleCode } = useApp();
733
738
 
734
739
  const handleNewCourse = (): void => {
735
740
  const nextLocaleData: Record<string, { name: string }> = {};
@@ -742,6 +747,76 @@ export default function CursosPage() {
742
747
  openCreateSheet();
743
748
  };
744
749
 
750
+ useEffect(() => {
751
+ if (newCategorySlugTouched) return;
752
+
753
+ const generatedSlug = newCategoryName
754
+ .trim()
755
+ .toLowerCase()
756
+ .normalize('NFD')
757
+ .replace(/[\u0300-\u036f]/g, '')
758
+ .replace(/[^a-z0-9]+/g, '-')
759
+ .replace(/^-+|-+$/g, '');
760
+
761
+ setNewCategorySlug(generatedSlug);
762
+ }, [newCategoryName, newCategorySlugTouched]);
763
+
764
+ function openCreateCategorySheet() {
765
+ setNewCategoryName('');
766
+ setNewCategorySlug('');
767
+ setNewCategorySlugTouched(false);
768
+ setCreateCategorySheetOpen(true);
769
+ }
770
+
771
+ async function handleCreateCategory() {
772
+ const trimmedName = newCategoryName.trim();
773
+ const trimmedSlug = newCategorySlug.trim().toLowerCase();
774
+
775
+ if (!trimmedName || !trimmedSlug) {
776
+ toast.error('Informe nome e slug da categoria.');
777
+ return;
778
+ }
779
+
780
+ const localeCode = currentLocaleCode || locales[0]?.code || 'pt-BR';
781
+
782
+ setCreatingCategory(true);
783
+ try {
784
+ const response = await request<{ slug?: string }>({
785
+ url: '/category',
786
+ method: 'POST',
787
+ data: {
788
+ locale: {
789
+ [localeCode]: {
790
+ name: trimmedName,
791
+ },
792
+ },
793
+ slug: trimmedSlug,
794
+ category_id: null,
795
+ color: '#000000',
796
+ icon: '',
797
+ status: 'active',
798
+ },
799
+ });
800
+
801
+ const createdSlug = response.data?.slug || trimmedSlug;
802
+ const existingCategories = form.getValues('categorias') ?? [];
803
+ if (!existingCategories.includes(createdSlug)) {
804
+ form.setValue('categorias', [...existingCategories, createdSlug], {
805
+ shouldDirty: true,
806
+ shouldTouch: true,
807
+ });
808
+ }
809
+
810
+ await refetchCategoryOptions();
811
+ setCreateCategorySheetOpen(false);
812
+ toast.success('Categoria criada com sucesso.');
813
+ } catch {
814
+ toast.error('Nao foi possivel criar a categoria.');
815
+ } finally {
816
+ setCreatingCategory(false);
817
+ }
818
+ }
819
+
745
820
  // ── Render ────────────────────────────────────────────────────────────────
746
821
 
747
822
  return (
@@ -1057,7 +1132,7 @@ export default function CursosPage() {
1057
1132
  >
1058
1133
  <Card
1059
1134
  className={`group relative flex h-full cursor-pointer flex-col overflow-hidden border-border/70 shadow-sm transition-all duration-200 hover:border-border hover:shadow-md ${isSelected ? 'ring-2 ring-primary ring-offset-2' : ''}`}
1060
- onClick={() => handleCardClick(curso)}
1135
+ onDoubleClick={() => openEditSheet(curso)}
1061
1136
  title={t('cards.tooltip')}
1062
1137
  >
1063
1138
  <div
@@ -1243,7 +1318,7 @@ export default function CursosPage() {
1243
1318
  key={curso.id}
1244
1319
  className="cursor-pointer"
1245
1320
  data-state={isSelected ? 'selected' : undefined}
1246
- onClick={() => handleCardClick(curso)}
1321
+ onDoubleClick={() => openEditSheet(curso)}
1247
1322
  title={t('cards.tooltip')}
1248
1323
  >
1249
1324
  <TableCell onClick={(e) => e.stopPropagation()}>
@@ -1388,7 +1463,7 @@ export default function CursosPage() {
1388
1463
  setPageSize(nextPageSize);
1389
1464
  setCurrentPage(1);
1390
1465
  }}
1391
- pageSizeOptions={[6, 12, 24]}
1466
+ pageSizeOptions={PAGE_SIZES}
1392
1467
  selectedCount={selectedIds.size}
1393
1468
  />
1394
1469
  </div>
@@ -1407,11 +1482,82 @@ export default function CursosPage() {
1407
1482
  form={form}
1408
1483
  onSubmit={onSubmit}
1409
1484
  categories={categoryOptions}
1410
- onCreateCategory={() => router.push('/category?new=1')}
1485
+ onCreateCategory={openCreateCategorySheet}
1411
1486
  initialLogoFileId={editingCurso?.logoFileId ?? null}
1412
1487
  t={t}
1413
1488
  />
1414
1489
 
1490
+ <Sheet
1491
+ open={createCategorySheetOpen}
1492
+ onOpenChange={(open) => {
1493
+ setCreateCategorySheetOpen(open);
1494
+ if (!open) {
1495
+ setNewCategoryName('');
1496
+ setNewCategorySlug('');
1497
+ setNewCategorySlugTouched(false);
1498
+ }
1499
+ }}
1500
+ >
1501
+ <SheetContent side="right" className="w-full sm:max-w-md">
1502
+ <SheetHeader>
1503
+ <SheetTitle>{t('form.fields.categories.createAction')}</SheetTitle>
1504
+ <SheetDescription>
1505
+ Crie uma categoria sem sair do cadastro de curso.
1506
+ </SheetDescription>
1507
+ </SheetHeader>
1508
+
1509
+ <div className="space-y-4 px-4">
1510
+ <div className="space-y-2">
1511
+ <Label htmlFor="new-category-name">Nome</Label>
1512
+ <Input
1513
+ id="new-category-name"
1514
+ value={newCategoryName}
1515
+ onChange={(event) => setNewCategoryName(event.target.value)}
1516
+ placeholder="Ex.: Desenvolvimento Web"
1517
+ />
1518
+ </div>
1519
+
1520
+ <div className="space-y-2">
1521
+ <Label htmlFor="new-category-slug">Slug</Label>
1522
+ <Input
1523
+ id="new-category-slug"
1524
+ value={newCategorySlug}
1525
+ onChange={(event) => {
1526
+ setNewCategorySlugTouched(true);
1527
+ setNewCategorySlug(event.target.value);
1528
+ }}
1529
+ placeholder="desenvolvimento-web"
1530
+ />
1531
+ </div>
1532
+ </div>
1533
+
1534
+ <SheetFooter>
1535
+ <Button
1536
+ type="button"
1537
+ variant="outline"
1538
+ onClick={() => setCreateCategorySheetOpen(false)}
1539
+ disabled={creatingCategory}
1540
+ >
1541
+ {t('deleteDialog.actions.cancel')}
1542
+ </Button>
1543
+ <Button
1544
+ type="button"
1545
+ onClick={handleCreateCategory}
1546
+ disabled={creatingCategory}
1547
+ >
1548
+ {creatingCategory ? (
1549
+ <>
1550
+ <Loader2 className="mr-2 size-4 animate-spin" />
1551
+ Criando...
1552
+ </>
1553
+ ) : (
1554
+ t('form.fields.categories.createAction')
1555
+ )}
1556
+ </Button>
1557
+ </SheetFooter>
1558
+ </SheetContent>
1559
+ </Sheet>
1560
+
1415
1561
  {/* ── Delete dialog ────────────────────���────────────────────────────── */}
1416
1562
  <Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
1417
1563
  <DialogContent className="max-w-3xl">
@@ -143,6 +143,9 @@ export function EnterpriseCourseCreateSheet({
143
143
  primaryContrastColor: getContrastColor(data.primaryColor),
144
144
  secondaryColor: data.secondaryColor,
145
145
  secondaryContrastColor: getContrastColor(data.secondaryColor),
146
+ operationsProjectId: data.operationsProjectId
147
+ ? Number(data.operationsProjectId)
148
+ : null,
146
149
  };
147
150
 
148
151
  const response = await request<ApiCreatedCourse>({