@hed-hog/lms 0.0.347 → 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 (414) 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.controller.d.ts +3 -3
  77. package/dist/class-group/class-group.mcp-tools.d.ts +87 -0
  78. package/dist/class-group/class-group.mcp-tools.d.ts.map +1 -0
  79. package/dist/class-group/class-group.mcp-tools.js +553 -0
  80. package/dist/class-group/class-group.mcp-tools.js.map +1 -0
  81. package/dist/class-group/class-group.module.d.ts.map +1 -1
  82. package/dist/class-group/class-group.module.js +2 -1
  83. package/dist/class-group/class-group.module.js.map +1 -1
  84. package/dist/class-group/class-group.service.d.ts +6 -4
  85. package/dist/class-group/class-group.service.d.ts.map +1 -1
  86. package/dist/class-group/class-group.service.js +45 -2
  87. package/dist/class-group/class-group.service.js.map +1 -1
  88. package/dist/course/course-operations-integration.service.d.ts +40 -0
  89. package/dist/course/course-operations-integration.service.d.ts.map +1 -0
  90. package/dist/course/course-operations-integration.service.js +372 -0
  91. package/dist/course/course-operations-integration.service.js.map +1 -0
  92. package/dist/course/course-structure.controller.d.ts +43 -4
  93. package/dist/course/course-structure.controller.d.ts.map +1 -1
  94. package/dist/course/course-structure.controller.js +22 -0
  95. package/dist/course/course-structure.controller.js.map +1 -1
  96. package/dist/course/course-structure.service.d.ts +42 -1
  97. package/dist/course/course-structure.service.d.ts.map +1 -1
  98. package/dist/course/course-structure.service.js +199 -32
  99. package/dist/course/course-structure.service.js.map +1 -1
  100. package/dist/course/course.controller.d.ts +12 -0
  101. package/dist/course/course.controller.d.ts.map +1 -1
  102. package/dist/course/course.mcp-tools.d.ts +90 -0
  103. package/dist/course/course.mcp-tools.d.ts.map +1 -0
  104. package/dist/course/course.mcp-tools.js +520 -0
  105. package/dist/course/course.mcp-tools.js.map +1 -0
  106. package/dist/course/course.module.d.ts.map +1 -1
  107. package/dist/course/course.module.js +8 -1
  108. package/dist/course/course.module.js.map +1 -1
  109. package/dist/course/course.service.d.ts +15 -1
  110. package/dist/course/course.service.d.ts.map +1 -1
  111. package/dist/course/course.service.js +70 -35
  112. package/dist/course/course.service.js.map +1 -1
  113. package/dist/course/dto/create-course.dto.d.ts +1 -0
  114. package/dist/course/dto/create-course.dto.d.ts.map +1 -1
  115. package/dist/course/dto/create-course.dto.js +7 -0
  116. package/dist/course/dto/create-course.dto.js.map +1 -1
  117. package/dist/course/dto/update-course-resources.dto.d.ts +11 -0
  118. package/dist/course/dto/update-course-resources.dto.d.ts.map +1 -0
  119. package/dist/course/dto/update-course-resources.dto.js +51 -0
  120. package/dist/course/dto/update-course-resources.dto.js.map +1 -0
  121. package/dist/course-lesson-discussion/course-lesson-discussion.controller.d.ts +23 -0
  122. package/dist/course-lesson-discussion/course-lesson-discussion.controller.d.ts.map +1 -0
  123. package/dist/course-lesson-discussion/course-lesson-discussion.controller.js +78 -0
  124. package/dist/course-lesson-discussion/course-lesson-discussion.controller.js.map +1 -0
  125. package/dist/course-lesson-discussion/course-lesson-discussion.mcp-tools.d.ts +22 -0
  126. package/dist/course-lesson-discussion/course-lesson-discussion.mcp-tools.d.ts.map +1 -0
  127. package/dist/course-lesson-discussion/course-lesson-discussion.mcp-tools.js +120 -0
  128. package/dist/course-lesson-discussion/course-lesson-discussion.mcp-tools.js.map +1 -0
  129. package/dist/course-lesson-discussion/course-lesson-discussion.module.d.ts +3 -0
  130. package/dist/course-lesson-discussion/course-lesson-discussion.module.d.ts.map +1 -0
  131. package/dist/course-lesson-discussion/course-lesson-discussion.module.js +26 -0
  132. package/dist/course-lesson-discussion/course-lesson-discussion.module.js.map +1 -0
  133. package/dist/course-lesson-discussion/course-lesson-discussion.service.d.ts +49 -0
  134. package/dist/course-lesson-discussion/course-lesson-discussion.service.d.ts.map +1 -0
  135. package/dist/course-lesson-discussion/course-lesson-discussion.service.js +272 -0
  136. package/dist/course-lesson-discussion/course-lesson-discussion.service.js.map +1 -0
  137. package/dist/course-lesson-discussion/dto/create-course-lesson-discussion-topic.dto.d.ts +6 -0
  138. package/dist/course-lesson-discussion/dto/create-course-lesson-discussion-topic.dto.d.ts.map +1 -0
  139. package/dist/course-lesson-discussion/dto/create-course-lesson-discussion-topic.dto.js +33 -0
  140. package/dist/course-lesson-discussion/dto/create-course-lesson-discussion-topic.dto.js.map +1 -0
  141. package/dist/course-lesson-note/course-lesson-note.controller.d.ts +53 -0
  142. package/dist/course-lesson-note/course-lesson-note.controller.d.ts.map +1 -0
  143. package/dist/course-lesson-note/course-lesson-note.controller.js +93 -0
  144. package/dist/course-lesson-note/course-lesson-note.controller.js.map +1 -0
  145. package/dist/course-lesson-note/course-lesson-note.mcp-tools.d.ts +27 -0
  146. package/dist/course-lesson-note/course-lesson-note.mcp-tools.d.ts.map +1 -0
  147. package/dist/course-lesson-note/course-lesson-note.mcp-tools.js +145 -0
  148. package/dist/course-lesson-note/course-lesson-note.mcp-tools.js.map +1 -0
  149. package/dist/course-lesson-note/course-lesson-note.module.d.ts +3 -0
  150. package/dist/course-lesson-note/course-lesson-note.module.d.ts.map +1 -0
  151. package/dist/course-lesson-note/course-lesson-note.module.js +26 -0
  152. package/dist/course-lesson-note/course-lesson-note.module.js.map +1 -0
  153. package/dist/course-lesson-note/course-lesson-note.service.d.ts +59 -0
  154. package/dist/course-lesson-note/course-lesson-note.service.d.ts.map +1 -0
  155. package/dist/course-lesson-note/course-lesson-note.service.js +195 -0
  156. package/dist/course-lesson-note/course-lesson-note.service.js.map +1 -0
  157. package/dist/course-lesson-note/dto/create-course-lesson-note.dto.d.ts +6 -0
  158. package/dist/course-lesson-note/dto/create-course-lesson-note.dto.d.ts.map +1 -0
  159. package/dist/course-lesson-note/dto/create-course-lesson-note.dto.js +33 -0
  160. package/dist/course-lesson-note/dto/create-course-lesson-note.dto.js.map +1 -0
  161. package/dist/course-lesson-note/dto/update-course-lesson-note.dto.d.ts +6 -0
  162. package/dist/course-lesson-note/dto/update-course-lesson-note.dto.d.ts.map +1 -0
  163. package/dist/course-lesson-note/dto/update-course-lesson-note.dto.js +35 -0
  164. package/dist/course-lesson-note/dto/update-course-lesson-note.dto.js.map +1 -0
  165. package/dist/dashboard/dashboard.mcp-tools.d.ts +10 -0
  166. package/dist/dashboard/dashboard.mcp-tools.d.ts.map +1 -0
  167. package/dist/dashboard/dashboard.mcp-tools.js +46 -0
  168. package/dist/dashboard/dashboard.mcp-tools.js.map +1 -0
  169. package/dist/dashboard/dashboard.module.d.ts.map +1 -1
  170. package/dist/dashboard/dashboard.module.js +2 -1
  171. package/dist/dashboard/dashboard.module.js.map +1 -1
  172. package/dist/enterprise/enterprise.mcp-tools.d.ts +82 -0
  173. package/dist/enterprise/enterprise.mcp-tools.d.ts.map +1 -0
  174. package/dist/enterprise/enterprise.mcp-tools.js +516 -0
  175. package/dist/enterprise/enterprise.mcp-tools.js.map +1 -0
  176. package/dist/enterprise/enterprise.module.d.ts.map +1 -1
  177. package/dist/enterprise/enterprise.module.js +2 -1
  178. package/dist/enterprise/enterprise.module.js.map +1 -1
  179. package/dist/enterprise/training/enterprise-training.module.d.ts.map +1 -1
  180. package/dist/enterprise/training/enterprise-training.module.js +11 -1
  181. package/dist/enterprise/training/enterprise-training.module.js.map +1 -1
  182. package/dist/enterprise/training/training-admin.controller.d.ts +2 -2
  183. package/dist/enterprise/training/training-admin.mcp-tools.d.ts +79 -0
  184. package/dist/enterprise/training/training-admin.mcp-tools.d.ts.map +1 -0
  185. package/dist/enterprise/training/training-admin.mcp-tools.js +620 -0
  186. package/dist/enterprise/training/training-admin.mcp-tools.js.map +1 -0
  187. package/dist/enterprise/training/training-admin.service.d.ts +2 -2
  188. package/dist/enterprise/training/training-instructor.mcp-tools.d.ts +47 -0
  189. package/dist/enterprise/training/training-instructor.mcp-tools.d.ts.map +1 -0
  190. package/dist/enterprise/training/training-instructor.mcp-tools.js +275 -0
  191. package/dist/enterprise/training/training-instructor.mcp-tools.js.map +1 -0
  192. package/dist/enterprise/training/training-student.controller.d.ts +24 -0
  193. package/dist/enterprise/training/training-student.controller.d.ts.map +1 -1
  194. package/dist/enterprise/training/training-student.controller.js +22 -0
  195. package/dist/enterprise/training/training-student.controller.js.map +1 -1
  196. package/dist/enterprise/training/training-student.mcp-tools.d.ts +27 -0
  197. package/dist/enterprise/training/training-student.mcp-tools.d.ts.map +1 -0
  198. package/dist/enterprise/training/training-student.mcp-tools.js +186 -0
  199. package/dist/enterprise/training/training-student.mcp-tools.js.map +1 -0
  200. package/dist/enterprise/training/training-student.service.d.ts +32 -0
  201. package/dist/enterprise/training/training-student.service.d.ts.map +1 -1
  202. package/dist/enterprise/training/training-student.service.js +138 -0
  203. package/dist/enterprise/training/training-student.service.js.map +1 -1
  204. package/dist/evaluation/evaluation.controller.d.ts +4 -4
  205. package/dist/evaluation/evaluation.mcp-tools.d.ts +25 -0
  206. package/dist/evaluation/evaluation.mcp-tools.d.ts.map +1 -0
  207. package/dist/evaluation/evaluation.mcp-tools.js +220 -0
  208. package/dist/evaluation/evaluation.mcp-tools.js.map +1 -0
  209. package/dist/evaluation/evaluation.module.d.ts.map +1 -1
  210. package/dist/evaluation/evaluation.module.js +2 -1
  211. package/dist/evaluation/evaluation.module.js.map +1 -1
  212. package/dist/evaluation/evaluation.service.d.ts +4 -4
  213. package/dist/exam/dto/create-exam-question.dto.d.ts +2 -0
  214. package/dist/exam/dto/create-exam-question.dto.d.ts.map +1 -1
  215. package/dist/exam/dto/create-exam-question.dto.js +10 -0
  216. package/dist/exam/dto/create-exam-question.dto.js.map +1 -1
  217. package/dist/exam/dto/create-exam.dto.d.ts +2 -0
  218. package/dist/exam/dto/create-exam.dto.d.ts.map +1 -1
  219. package/dist/exam/dto/create-exam.dto.js +10 -0
  220. package/dist/exam/dto/create-exam.dto.js.map +1 -1
  221. package/dist/exam/dto/create-question-subject.dto.d.ts +5 -0
  222. package/dist/exam/dto/create-question-subject.dto.d.ts.map +1 -0
  223. package/dist/exam/dto/create-question-subject.dto.js +28 -0
  224. package/dist/exam/dto/create-question-subject.dto.js.map +1 -0
  225. package/dist/exam/exam-attempt.controller.d.ts +4 -0
  226. package/dist/exam/exam-attempt.controller.d.ts.map +1 -1
  227. package/dist/exam/exam-attempt.service.d.ts +7 -1
  228. package/dist/exam/exam-attempt.service.d.ts.map +1 -1
  229. package/dist/exam/exam-attempt.service.js +47 -17
  230. package/dist/exam/exam-attempt.service.js.map +1 -1
  231. package/dist/exam/exam.controller.d.ts +34 -0
  232. package/dist/exam/exam.controller.d.ts.map +1 -1
  233. package/dist/exam/exam.controller.js +27 -0
  234. package/dist/exam/exam.controller.js.map +1 -1
  235. package/dist/exam/exam.mcp-tools.d.ts +62 -0
  236. package/dist/exam/exam.mcp-tools.d.ts.map +1 -0
  237. package/dist/exam/exam.mcp-tools.js +430 -0
  238. package/dist/exam/exam.mcp-tools.js.map +1 -0
  239. package/dist/exam/exam.module.d.ts.map +1 -1
  240. package/dist/exam/exam.module.js +2 -1
  241. package/dist/exam/exam.module.js.map +1 -1
  242. package/dist/exam/exam.service.d.ts +38 -0
  243. package/dist/exam/exam.service.d.ts.map +1 -1
  244. package/dist/exam/exam.service.js +114 -17
  245. package/dist/exam/exam.service.js.map +1 -1
  246. package/dist/index.d.ts +9 -0
  247. package/dist/index.d.ts.map +1 -1
  248. package/dist/index.js +9 -0
  249. package/dist/index.js.map +1 -1
  250. package/dist/instructor/instructor.mcp-tools.d.ts +41 -0
  251. package/dist/instructor/instructor.mcp-tools.d.ts.map +1 -0
  252. package/dist/instructor/instructor.mcp-tools.js +326 -0
  253. package/dist/instructor/instructor.mcp-tools.js.map +1 -0
  254. package/dist/instructor/instructor.module.d.ts.map +1 -1
  255. package/dist/instructor/instructor.module.js +2 -1
  256. package/dist/instructor/instructor.module.js.map +1 -1
  257. package/dist/lms.module.d.ts.map +1 -1
  258. package/dist/lms.module.js +15 -0
  259. package/dist/lms.module.js.map +1 -1
  260. package/dist/realtime/lms-realtime.controller.d.ts +7 -0
  261. package/dist/realtime/lms-realtime.controller.d.ts.map +1 -0
  262. package/dist/realtime/lms-realtime.controller.js +34 -0
  263. package/dist/realtime/lms-realtime.controller.js.map +1 -0
  264. package/dist/realtime/lms-realtime.module.d.ts +3 -0
  265. package/dist/realtime/lms-realtime.module.d.ts.map +1 -0
  266. package/dist/realtime/lms-realtime.module.js +25 -0
  267. package/dist/realtime/lms-realtime.module.js.map +1 -0
  268. package/dist/realtime/lms-realtime.service.d.ts +36 -0
  269. package/dist/realtime/lms-realtime.service.d.ts.map +1 -0
  270. package/dist/realtime/lms-realtime.service.js +59 -0
  271. package/dist/realtime/lms-realtime.service.js.map +1 -0
  272. package/dist/realtime/lms-realtime.subscriber.d.ts +10 -0
  273. package/dist/realtime/lms-realtime.subscriber.d.ts.map +1 -0
  274. package/dist/realtime/lms-realtime.subscriber.js +70 -0
  275. package/dist/realtime/lms-realtime.subscriber.js.map +1 -0
  276. package/dist/reports/reports.mcp-tools.d.ts +10 -0
  277. package/dist/reports/reports.mcp-tools.d.ts.map +1 -0
  278. package/dist/reports/reports.mcp-tools.js +50 -0
  279. package/dist/reports/reports.mcp-tools.js.map +1 -0
  280. package/dist/reports/reports.module.d.ts.map +1 -1
  281. package/dist/reports/reports.module.js +2 -1
  282. package/dist/reports/reports.module.js.map +1 -1
  283. package/dist/training/training.mcp-tools.d.ts +20 -0
  284. package/dist/training/training.mcp-tools.d.ts.map +1 -0
  285. package/dist/training/training.mcp-tools.js +181 -0
  286. package/dist/training/training.mcp-tools.js.map +1 -0
  287. package/dist/training/training.module.d.ts.map +1 -1
  288. package/dist/training/training.module.js +2 -1
  289. package/dist/training/training.module.js.map +1 -1
  290. package/hedhog/data/integration_event_catalog.yaml +69 -0
  291. package/hedhog/data/menu.yaml +34 -0
  292. package/hedhog/data/route.yaml +2351 -0
  293. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +168 -103
  294. package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +80 -1
  295. package/hedhog/frontend/app/_components/course-picker.tsx.ejs +228 -0
  296. package/hedhog/frontend/app/_lib/hooks/use-lms-realtime-refresh.ts.ejs +58 -0
  297. package/hedhog/frontend/app/achievements/page.tsx.ejs +844 -0
  298. package/hedhog/frontend/app/bitcodes/page.tsx.ejs +1010 -0
  299. package/hedhog/frontend/app/certificates/issued/page.tsx.ejs +61 -2
  300. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +7 -0
  301. package/hedhog/frontend/app/classes/page.tsx.ejs +55 -2060
  302. package/hedhog/frontend/app/courses/[id]/_components/CourseRelationsCard.tsx.ejs +28 -0
  303. package/hedhog/frontend/app/courses/[id]/_components/course-edit-types.ts.ejs +1 -0
  304. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +0 -9
  305. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +80 -66
  306. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +583 -8
  307. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +527 -57
  308. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +1 -1
  309. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-context-menu.tsx.ejs +106 -4
  310. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +2 -1
  311. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +11 -1
  312. package/hedhog/frontend/app/courses/[id]/structure/_data/services/course-structure.service.ts.ejs +53 -6
  313. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +13 -2
  314. package/hedhog/frontend/app/courses/page.tsx.ejs +175 -29
  315. package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +3 -0
  316. package/hedhog/frontend/app/enterprise/_components/enterprise-course-edit-sheet.tsx.ejs +7 -0
  317. package/hedhog/frontend/app/exams/[id]/questions/page.tsx.ejs +169 -2
  318. package/hedhog/frontend/app/exams/page.tsx.ejs +77 -22
  319. package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +1 -0
  320. package/hedhog/frontend/app/instructors/page.tsx.ejs +1 -0
  321. package/hedhog/frontend/app/paths/page.tsx.ejs +6 -24
  322. package/hedhog/frontend/app/training/page.tsx.ejs +6 -24
  323. package/hedhog/frontend/messages/en.json +314 -12
  324. package/hedhog/frontend/messages/pt.json +314 -12
  325. package/hedhog/query/triggers.sql +53 -0
  326. package/hedhog/table/achievement.yaml +46 -0
  327. package/hedhog/table/bitcode_wallet.yaml +18 -0
  328. package/hedhog/table/bitcode_wallet_transaction.yaml +22 -0
  329. package/hedhog/table/certificate.yaml +3 -0
  330. package/hedhog/table/course.yaml +4 -0
  331. package/hedhog/table/course_file.yaml +23 -0
  332. package/hedhog/table/course_lesson.yaml +5 -0
  333. package/hedhog/table/course_lesson_discussion_like.yaml +21 -0
  334. package/hedhog/table/course_lesson_discussion_topic.yaml +35 -0
  335. package/hedhog/table/course_lesson_note.yaml +34 -0
  336. package/hedhog/table/exam.yaml +5 -0
  337. package/hedhog/table/learning_path_enrollment.yaml +6 -0
  338. package/hedhog/table/question.yaml +10 -0
  339. package/hedhog/table/question_subject.yaml +17 -0
  340. package/hedhog/table/student_activity_streak.yaml +25 -0
  341. package/package.json +7 -7
  342. package/src/achievement/achievement.controller.ts +60 -0
  343. package/src/achievement/achievement.mcp-tools.ts +108 -0
  344. package/src/achievement/achievement.module.ts +13 -0
  345. package/src/achievement/achievement.service.ts +252 -0
  346. package/src/achievement/dto/create-achievement.dto.ts +50 -0
  347. package/src/achievement/dto/update-achievement.dto.ts +47 -0
  348. package/src/bitcode-wallet/bitcode-wallet.controller.ts +69 -0
  349. package/src/bitcode-wallet/bitcode-wallet.mcp-tools.ts +107 -0
  350. package/src/bitcode-wallet/bitcode-wallet.module.ts +13 -0
  351. package/src/bitcode-wallet/bitcode-wallet.service.ts +361 -0
  352. package/src/bitcode-wallet/dto/create-bitcode-wallet-transaction.dto.ts +27 -0
  353. package/src/bitcode-wallet/dto/create-bitcode-wallet.dto.ts +7 -0
  354. package/src/bitcode-wallet/dto/update-bitcode-wallet-transaction.dto.ts +28 -0
  355. package/src/bitcode-wallet/dto/update-bitcode-wallet.dto.ts +8 -0
  356. package/src/certificate/certificate.controller.ts +20 -11
  357. package/src/certificate/certificate.mcp-tools.ts +131 -0
  358. package/src/certificate/certificate.module.ts +2 -1
  359. package/src/certificate/certificate.service.ts +95 -4
  360. package/src/certificate/dto/update-certificate-public-access.dto.ts +6 -0
  361. package/src/class-group/class-group.mcp-tools.ts +435 -0
  362. package/src/class-group/class-group.module.ts +2 -1
  363. package/src/class-group/class-group.service.ts +51 -1
  364. package/src/course/course-operations-integration.service.ts +520 -0
  365. package/src/course/course-structure.controller.ts +22 -8
  366. package/src/course/course-structure.service.ts +215 -23
  367. package/src/course/course.mcp-tools.ts +409 -0
  368. package/src/course/course.module.ts +8 -1
  369. package/src/course/course.service.ts +106 -27
  370. package/src/course/dto/create-course.dto.ts +8 -0
  371. package/src/course/dto/update-course-resources.dto.ts +39 -0
  372. package/src/course-lesson-discussion/course-lesson-discussion.controller.ts +55 -0
  373. package/src/course-lesson-discussion/course-lesson-discussion.mcp-tools.ts +75 -0
  374. package/src/course-lesson-discussion/course-lesson-discussion.module.ts +13 -0
  375. package/src/course-lesson-discussion/course-lesson-discussion.service.ts +354 -0
  376. package/src/course-lesson-discussion/dto/create-course-lesson-discussion-topic.dto.ts +16 -0
  377. package/src/course-lesson-note/course-lesson-note.controller.ts +68 -0
  378. package/src/course-lesson-note/course-lesson-note.mcp-tools.ts +96 -0
  379. package/src/course-lesson-note/course-lesson-note.module.ts +13 -0
  380. package/src/course-lesson-note/course-lesson-note.service.ts +248 -0
  381. package/src/course-lesson-note/dto/create-course-lesson-note.dto.ts +16 -0
  382. package/src/course-lesson-note/dto/update-course-lesson-note.dto.ts +18 -0
  383. package/src/dashboard/dashboard.mcp-tools.ts +23 -0
  384. package/src/dashboard/dashboard.module.ts +2 -1
  385. package/src/enterprise/enterprise.mcp-tools.ts +403 -0
  386. package/src/enterprise/enterprise.module.ts +2 -1
  387. package/src/enterprise/training/enterprise-training.module.ts +11 -1
  388. package/src/enterprise/training/training-admin.mcp-tools.ts +479 -0
  389. package/src/enterprise/training/training-instructor.mcp-tools.ts +210 -0
  390. package/src/enterprise/training/training-student.controller.ts +17 -1
  391. package/src/enterprise/training/training-student.mcp-tools.ts +136 -0
  392. package/src/enterprise/training/training-student.service.ts +167 -1
  393. package/src/evaluation/evaluation.mcp-tools.ts +155 -0
  394. package/src/evaluation/evaluation.module.ts +2 -1
  395. package/src/exam/dto/create-exam-question.dto.ts +8 -0
  396. package/src/exam/dto/create-exam.dto.ts +8 -0
  397. package/src/exam/dto/create-question-subject.dto.ts +12 -0
  398. package/src/exam/exam-attempt.service.ts +46 -14
  399. package/src/exam/exam.controller.ts +19 -0
  400. package/src/exam/exam.mcp-tools.ts +337 -0
  401. package/src/exam/exam.module.ts +2 -1
  402. package/src/exam/exam.service.ts +121 -0
  403. package/src/index.ts +9 -0
  404. package/src/instructor/instructor.mcp-tools.ts +243 -0
  405. package/src/instructor/instructor.module.ts +2 -1
  406. package/src/lms.module.ts +15 -1
  407. package/src/realtime/lms-realtime.controller.ts +12 -0
  408. package/src/realtime/lms-realtime.module.ts +12 -0
  409. package/src/realtime/lms-realtime.service.ts +98 -0
  410. package/src/realtime/lms-realtime.subscriber.ts +61 -0
  411. package/src/reports/reports.mcp-tools.ts +27 -0
  412. package/src/reports/reports.module.ts +2 -1
  413. package/src/training/training.mcp-tools.ts +128 -0
  414. package/src/training/training.module.ts +2 -1
@@ -1,7 +1,6 @@
1
1
  'use client';
2
2
 
3
3
  import { zodResolver } from '@hookform/resolvers/zod';
4
- import { useTranslations } from 'next-intl';
5
4
  import {
6
5
  CircleDot,
7
6
  CircleOff,
@@ -29,6 +28,7 @@ import {
29
28
  X,
30
29
  type LucideIcon,
31
30
  } from 'lucide-react';
31
+ import { useTranslations } from 'next-intl';
32
32
  import { useEffect, useRef, useState } from 'react';
33
33
  import { useForm, useWatch } from 'react-hook-form';
34
34
  import { toast } from 'sonner';
@@ -251,6 +251,93 @@ type FormValues = {
251
251
  questionId?: string | null;
252
252
  };
253
253
 
254
+ type TranscriptionSegment = {
255
+ id: string;
256
+ start: string;
257
+ end: string;
258
+ text: string;
259
+ };
260
+
261
+ function segmentId(): string {
262
+ return Math.random().toString(36).slice(2, 9);
263
+ }
264
+
265
+ function parseTimeToSeconds(input: string): number | null {
266
+ const normalized = input.trim();
267
+ if (!normalized) return null;
268
+ const parts = normalized.split(':').map((part) => Number(part));
269
+ if (parts.some((part) => !Number.isFinite(part) || part < 0)) return null;
270
+ if (parts.length === 2) {
271
+ const mm = parts[0];
272
+ const ss = parts[1];
273
+ if (mm === undefined || ss === undefined) return null;
274
+ if (ss > 59) return null;
275
+ return mm * 60 + ss;
276
+ }
277
+ if (parts.length === 3) {
278
+ const hh = parts[0];
279
+ const mm = parts[1];
280
+ const ss = parts[2];
281
+ if (hh === undefined || mm === undefined || ss === undefined) return null;
282
+ if (mm > 59 || ss > 59) return null;
283
+ return hh * 3600 + mm * 60 + ss;
284
+ }
285
+ return null;
286
+ }
287
+
288
+ function normalizeTimeInput(input: string): string {
289
+ const seconds = parseTimeToSeconds(input);
290
+ if (seconds === null) return input.trim();
291
+ const hh = Math.floor(seconds / 3600);
292
+ const mm = Math.floor((seconds % 3600) / 60);
293
+ const ss = seconds % 60;
294
+ if (hh > 0) {
295
+ return `${String(hh).padStart(2, '0')}:${String(mm).padStart(2, '0')}:${String(ss).padStart(2, '0')}`;
296
+ }
297
+ return `${String(mm).padStart(2, '0')}:${String(ss).padStart(2, '0')}`;
298
+ }
299
+
300
+ function parseTranscriptionSegments(raw?: string): TranscriptionSegment[] {
301
+ const lines = String(raw ?? '')
302
+ .split('\n')
303
+ .map((line) => line.trim())
304
+ .filter(Boolean);
305
+ if (lines.length === 0) {
306
+ return [{ id: segmentId(), start: '00:00', end: '00:15', text: '' }];
307
+ }
308
+ return lines.map((line) => {
309
+ const match = line.match(/^\[(.+?)\s*-->\s*(.+?)\]\s*(.*)$/);
310
+ if (match) {
311
+ return {
312
+ id: segmentId(),
313
+ start: normalizeTimeInput(match[1] ?? ''),
314
+ end: normalizeTimeInput(match[2] ?? ''),
315
+ text: match[3] ?? '',
316
+ };
317
+ }
318
+ return {
319
+ id: segmentId(),
320
+ start: '00:00',
321
+ end: '00:15',
322
+ text: line,
323
+ };
324
+ });
325
+ }
326
+
327
+ function serializeTranscriptionSegments(
328
+ segments: TranscriptionSegment[]
329
+ ): string {
330
+ return segments
331
+ .map((segment) => ({
332
+ start: normalizeTimeInput(segment.start),
333
+ end: normalizeTimeInput(segment.end),
334
+ text: segment.text.trim(),
335
+ }))
336
+ .filter((segment) => segment.text.length > 0)
337
+ .map((segment) => `[${segment.start} --> ${segment.end}] ${segment.text}`)
338
+ .join('\n');
339
+ }
340
+
254
341
  // ── SortableAlternativa ───────────────────────────────────────────────────────
255
342
 
256
343
  function SortableAlternativa({
@@ -327,9 +414,7 @@ function SortableAlternativa({
327
414
  ? 'text-foreground'
328
415
  : 'text-muted-foreground hover:text-foreground'
329
416
  )}
330
- aria-label={
331
- alt.correta ? markIncorrectLabel : markCorrectLabel
332
- }
417
+ aria-label={alt.correta ? markIncorrectLabel : markCorrectLabel}
333
418
  >
334
419
  {alt.correta ? (
335
420
  <CircleDot className="size-5" />
@@ -378,8 +463,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
378
463
  const videoProviders: { value: VideoProvider; label: string }[] = [
379
464
  { value: 'youtube', label: 'YouTube' },
380
465
  { value: 'vimeo', label: 'Vimeo' },
381
- { value: 'bunny', label: 'Bunny.net' },
382
- { value: 'custom', label: t('providers.custom') },
466
+ { value: 'file_storage', label: t('providers.fileStorage') },
383
467
  ];
384
468
  const questionTypeLabels: Record<QuestionType, string> = {
385
469
  multiple_choice: t('questionEditor.types.multipleChoice'),
@@ -404,7 +488,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
404
488
  publicDescription: z.string(),
405
489
  privateDescription: z.string(),
406
490
  videoProvider: z
407
- .enum(['youtube', 'vimeo', 'bunny', 'custom'] as const)
491
+ .enum(['youtube', 'vimeo', 'file_storage'] as const)
408
492
  .optional(),
409
493
  videoUrl: z.string().optional(),
410
494
  transcription: z.string().optional(),
@@ -426,7 +510,10 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
426
510
  visibility: lesson?.visibility ?? 'publico',
427
511
  publicDescription: lesson?.publicDescription ?? '',
428
512
  privateDescription: lesson?.privateDescription ?? '',
429
- videoProvider: lesson?.videoProvider ?? 'youtube',
513
+ videoProvider:
514
+ lesson?.videoProvider === 'youtube' || lesson?.videoProvider === 'vimeo'
515
+ ? lesson.videoProvider
516
+ : 'file_storage',
430
517
  videoUrl: lesson?.videoUrl ?? '',
431
518
  transcription: lesson?.transcription ?? '',
432
519
  postContent: lesson?.postContent ?? '',
@@ -441,6 +528,10 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
441
528
  const { isDirty } = form.formState;
442
529
  const watchedType = useWatch({ control: form.control, name: 'type' });
443
530
  const watchedStatus = useWatch({ control: form.control, name: 'status' });
531
+ const watchedVideoProvider = useWatch({
532
+ control: form.control,
533
+ name: 'videoProvider',
534
+ });
444
535
 
445
536
  useEffect(() => {
446
537
  if (!lesson) return;
@@ -451,9 +542,14 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
451
542
  const [localResources, setLocalResources] = useState<Resource[]>(
452
543
  () => lesson?.resources ?? []
453
544
  );
545
+ const [videoResolution, setVideoResolution] = useState('1080p');
454
546
  const [dragOver, setDragOver] = useState(false);
455
547
  const [isUploading, setIsUploading] = useState(false);
456
548
  const resourceInputRef = useRef<HTMLInputElement>(null);
549
+ const videoInputRef = useRef<HTMLInputElement>(null);
550
+ const [transcriptionSegments, setTranscriptionSegments] = useState<
551
+ TranscriptionSegment[]
552
+ >(() => parseTranscriptionSegments(lesson?.transcription));
457
553
 
458
554
  // ── Instructors state ────────────────────────────────────────────────────
459
555
  const [selectedInstructorIds, setSelectedInstructorIds] = useState<string[]>(
@@ -483,6 +579,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
483
579
  useEffect(() => {
484
580
  setLocalResources(lesson?.resources ?? []);
485
581
  setSelectedInstructorIds(lesson?.instructors?.map((i) => i.id) ?? []);
582
+ setTranscriptionSegments(parseTranscriptionSegments(lesson?.transcription));
486
583
  }, [lesson?.id]); // eslint-disable-line react-hooks/exhaustive-deps
487
584
 
488
585
  if (!lesson) return null;
@@ -539,28 +636,106 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
539
636
  setLocalResources((prev) => prev.filter((r) => r.id !== id));
540
637
  }
541
638
 
542
- function handleResourceDownload(res: Resource) {
543
- if (!res.url) {
639
+ async function resolveResourceUrl(res: Resource): Promise<string | null> {
640
+ if (res.url) return res.url;
641
+ const numId = Number(res.id);
642
+ if (!Number.isInteger(numId) || numId <= 0) return null;
643
+ try {
644
+ const response = await request<{ url?: string }>({
645
+ url: `/file/open/${numId}`,
646
+ method: 'PUT',
647
+ });
648
+ return response?.data?.url ?? null;
649
+ } catch {
650
+ return null;
651
+ }
652
+ }
653
+
654
+ async function openResource(res: Resource) {
655
+ const url = await resolveResourceUrl(res);
656
+ if (!url) {
657
+ toast.error(t('questionEditor.resourceOpenError'));
658
+ return;
659
+ }
660
+ window.open(url, '_blank', 'noopener,noreferrer');
661
+ }
662
+
663
+ async function handleResourceDownload(res: Resource) {
664
+ const resourceUrl = await resolveResourceUrl(res);
665
+ if (!resourceUrl) {
544
666
  toast(t('questionEditor.resourceDownloadSoon', { name: res.name }));
545
667
  return;
546
668
  }
547
669
  const a = document.createElement('a');
548
- a.href = res.url;
670
+ a.href = resourceUrl;
549
671
  a.download = res.name;
550
672
  a.click();
551
673
  }
552
674
 
675
+ async function handleVideoVariantFiles(files: File[]) {
676
+ const resolution = videoResolution.trim();
677
+ if (!resolution) {
678
+ toast.error(t('questionEditor.videoResolutionRequired'));
679
+ return;
680
+ }
681
+ setIsUploading(true);
682
+ try {
683
+ const results = await Promise.allSettled(
684
+ files.map((file) =>
685
+ uploadFile(request, file).then<Resource>((res) => ({
686
+ id: String(res.id),
687
+ name: file.name,
688
+ size: formatFileSize(file.size),
689
+ type: `video/${resolution}`,
690
+ public: false,
691
+ url: undefined,
692
+ }))
693
+ )
694
+ );
695
+
696
+ const succeeded = results
697
+ .filter(
698
+ (result): result is PromiseFulfilledResult<Resource> =>
699
+ result.status === 'fulfilled'
700
+ )
701
+ .map((result) => result.value);
702
+ const failedCount = results.filter(
703
+ (result) => result.status === 'rejected'
704
+ ).length;
705
+
706
+ if (failedCount > 0) {
707
+ toast.error(
708
+ t('questionEditor.videoUploadFailed', {
709
+ count: failedCount,
710
+ })
711
+ );
712
+ }
713
+
714
+ if (succeeded.length > 0) {
715
+ setLocalResources((prev) => [...prev, ...succeeded]);
716
+ }
717
+ } finally {
718
+ setIsUploading(false);
719
+ }
720
+ }
721
+
553
722
  function onSubmit(values: FormValues) {
723
+ const transcriptionValue =
724
+ values.type === 'video'
725
+ ? serializeTranscriptionSegments(transcriptionSegments)
726
+ : values.transcription;
727
+
554
728
  updateLesson.mutate({
555
729
  lessonId,
556
730
  sessionId: lesson!.sessionId,
557
731
  formValues: {
558
732
  ...values,
733
+ transcription: transcriptionValue,
559
734
  resources: localResources,
560
735
  instructorIds: selectedInstructorIds.map(Number),
561
736
  },
562
737
  });
563
- form.reset(values);
738
+ form.reset({ ...values, transcription: transcriptionValue });
564
739
  }
565
740
 
566
741
  // ── Question sheet helpers ────────────────────────────────────────────────
@@ -743,6 +918,9 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
743
918
  <TabsTrigger value="conteudo" className="text-xs h-7 px-2.5">
744
919
  {t('lessonForm.postContent')}
745
920
  </TabsTrigger>
921
+ <TabsTrigger value="transcricao" className="text-xs h-7 px-2.5">
922
+ {t('lessonForm.tabTranscription')}
923
+ </TabsTrigger>
746
924
  <TabsTrigger value="recursos" className="text-xs h-7 px-2.5">
747
925
  {t('lessonForm.tabResources')}
748
926
  </TabsTrigger>
@@ -901,10 +1079,10 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
901
1079
  STATUS_COLORS[val]
902
1080
  )}
903
1081
  >
904
- {lbl}
905
- </span>
906
- </SelectItem>
907
- ))}
1082
+ {lbl}
1083
+ </span>
1084
+ </SelectItem>
1085
+ ))}
908
1086
  </SelectContent>
909
1087
  </Select>
910
1088
  <FormMessage className="text-xs" />
@@ -932,17 +1110,20 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
932
1110
  <SelectContent>
933
1111
  <SelectItem value="publico">
934
1112
  <span className="flex items-center gap-1.5">
935
- <Eye className="size-3" /> {t('lessonForm.public')}
1113
+ <Eye className="size-3" />{' '}
1114
+ {t('lessonForm.public')}
936
1115
  </span>
937
1116
  </SelectItem>
938
1117
  <SelectItem value="privado">
939
1118
  <span className="flex items-center gap-1.5">
940
- <EyeOff className="size-3" /> {t('lessonForm.private')}
1119
+ <EyeOff className="size-3" />{' '}
1120
+ {t('lessonForm.private')}
941
1121
  </span>
942
1122
  </SelectItem>
943
1123
  <SelectItem value="restrito">
944
1124
  <span className="flex items-center gap-1.5">
945
- <Lock className="size-3" /> {t('questionEditor.restricted')}
1125
+ <Lock className="size-3" />{' '}
1126
+ {t('questionEditor.restricted')}
946
1127
  </span>
947
1128
  </SelectItem>
948
1129
  </SelectContent>
@@ -1024,9 +1205,12 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
1024
1205
  prev.filter((id) => id !== sid)
1025
1206
  )
1026
1207
  }
1027
- aria-label={t('questionEditor.removeInstructor', {
1028
- name: displayName,
1029
- })}
1208
+ aria-label={t(
1209
+ 'questionEditor.removeInstructor',
1210
+ {
1211
+ name: displayName,
1212
+ }
1213
+ )}
1030
1214
  >
1031
1215
  <X className="size-3" />
1032
1216
  </Button>
@@ -1049,7 +1233,8 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
1049
1233
  <Card className="bg-muted/20 py-2 gap-2">
1050
1234
  <CardHeader className="px-3 pt-2 pb-1">
1051
1235
  <CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider flex items-center gap-1.5">
1052
- <Eye className="size-3" /> {t('lessonForm.publicDescription')}
1236
+ <Eye className="size-3" />{' '}
1237
+ {t('lessonForm.publicDescription')}
1053
1238
  </CardTitle>
1054
1239
  </CardHeader>
1055
1240
  <CardContent className="px-3 pb-2">
@@ -1075,7 +1260,8 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
1075
1260
  <Card className="bg-muted/20 py-2 gap-2">
1076
1261
  <CardHeader className="px-3 pt-2 pb-1">
1077
1262
  <CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider flex items-center gap-1.5">
1078
- <Lock className="size-3" /> {t('lessonForm.privateDescription')}
1263
+ <Lock className="size-3" />{' '}
1264
+ {t('lessonForm.privateDescription')}
1079
1265
  </CardTitle>
1080
1266
  </CardHeader>
1081
1267
  <CardContent className="px-3 pb-2">
@@ -1103,7 +1289,8 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
1103
1289
  <Card className="bg-muted/20 py-2 gap-2">
1104
1290
  <CardHeader className="px-3 pt-2 pb-1">
1105
1291
  <CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider flex items-center gap-1.5">
1106
- <Video className="size-3 text-blue-500" /> {t('types.video')}
1292
+ <Video className="size-3 text-blue-500" />{' '}
1293
+ {t('types.video')}
1107
1294
  </CardTitle>
1108
1295
  </CardHeader>
1109
1296
  <CardContent className="px-3 pb-2 flex flex-col gap-3">
@@ -1150,29 +1337,9 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
1150
1337
  {...field}
1151
1338
  value={field.value ?? ''}
1152
1339
  className="h-8 text-xs font-mono"
1153
- placeholder={t('lessonForm.videoUrlPlaceholder')}
1154
- />
1155
- </FormControl>
1156
- <FormMessage className="text-xs" />
1157
- </FormItem>
1158
- )}
1159
- />
1160
-
1161
- <FormField
1162
- control={form.control}
1163
- name="transcription"
1164
- render={({ field }) => (
1165
- <FormItem>
1166
- <FormLabel className="text-xs">
1167
- {t('lessonForm.tabTranscription')}
1168
- </FormLabel>
1169
- <FormControl>
1170
- <Textarea
1171
- {...field}
1172
- value={field.value ?? ''}
1173
- rows={5}
1174
- className="text-xs resize-none font-mono"
1175
- placeholder={t('lessonForm.transcriptionPlaceholder')}
1340
+ placeholder={t(
1341
+ 'lessonForm.videoUrlPlaceholder'
1342
+ )}
1176
1343
  />
1177
1344
  </FormControl>
1178
1345
  <FormMessage className="text-xs" />
@@ -1248,8 +1415,12 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
1248
1415
  setSelectedQuestion(found);
1249
1416
  }}
1250
1417
  placeholder={t('questionEditor.selectQuestion')}
1251
- searchPlaceholder={t('questionEditor.searchQuestion')}
1252
- emptyLabel={t('questionEditor.noQuestionsFound')}
1418
+ searchPlaceholder={t(
1419
+ 'questionEditor.searchQuestion'
1420
+ )}
1421
+ emptyLabel={t(
1422
+ 'questionEditor.noQuestionsFound'
1423
+ )}
1253
1424
  entityLabel={t('questionEditor.questionEntity')}
1254
1425
  options={MOCK_QUESTIONS}
1255
1426
  getOptionValue={(o) => o.id}
@@ -1295,10 +1466,309 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
1295
1466
  </ScrollArea>
1296
1467
  </TabsContent>
1297
1468
 
1469
+ {/* ── Tab Transcrição ─────────────────────────────────────────── */}
1470
+ <TabsContent value="transcricao" className="flex-1 min-h-0 mt-0">
1471
+ <ScrollArea className="h-full">
1472
+ <div className="flex flex-col gap-3 p-3">
1473
+ {watchedType !== 'video' ? (
1474
+ <Card className="bg-muted/20 py-2 gap-2">
1475
+ <CardContent className="px-3 py-4">
1476
+ <p className="text-xs text-muted-foreground">
1477
+ {t('lessonForm.transcriptionVideoOnly')}
1478
+ </p>
1479
+ </CardContent>
1480
+ </Card>
1481
+ ) : (
1482
+ <Card className="bg-muted/20 py-2 gap-2">
1483
+ <CardHeader className="px-3 pt-2 pb-1">
1484
+ <CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider flex items-center justify-between gap-2">
1485
+ <span>{t('lessonForm.transcriptionSegments')}</span>
1486
+ <Button
1487
+ type="button"
1488
+ variant="outline"
1489
+ size="sm"
1490
+ className="h-6 text-xs px-2"
1491
+ onClick={() =>
1492
+ setTranscriptionSegments((prev) => [
1493
+ ...prev,
1494
+ {
1495
+ id: segmentId(),
1496
+ start: '00:00',
1497
+ end: '00:15',
1498
+ text: '',
1499
+ },
1500
+ ])
1501
+ }
1502
+ >
1503
+ <Plus className="size-3 mr-1" />
1504
+ {t('lessonForm.newTranscriptionSegment')}
1505
+ </Button>
1506
+ </CardTitle>
1507
+ </CardHeader>
1508
+ <CardContent className="px-3 pb-2 flex flex-col gap-2">
1509
+ {transcriptionSegments.map((segment, index) => (
1510
+ <div
1511
+ key={segment.id}
1512
+ className="rounded-md border bg-background/80 p-2"
1513
+ >
1514
+ <div className="grid grid-cols-1 md:grid-cols-[92px_92px_1fr_auto] gap-2 items-start">
1515
+ <Input
1516
+ value={segment.start}
1517
+ onChange={(event) =>
1518
+ setTranscriptionSegments((prev) =>
1519
+ prev.map((item) =>
1520
+ item.id === segment.id
1521
+ ? {
1522
+ ...item,
1523
+ start: event.target.value,
1524
+ }
1525
+ : item
1526
+ )
1527
+ )
1528
+ }
1529
+ onBlur={() =>
1530
+ setTranscriptionSegments((prev) =>
1531
+ prev.map((item) =>
1532
+ item.id === segment.id
1533
+ ? {
1534
+ ...item,
1535
+ start: normalizeTimeInput(item.start),
1536
+ }
1537
+ : item
1538
+ )
1539
+ )
1540
+ }
1541
+ className="h-8 text-xs font-mono"
1542
+ placeholder="00:00"
1543
+ />
1544
+ <Input
1545
+ value={segment.end}
1546
+ onChange={(event) =>
1547
+ setTranscriptionSegments((prev) =>
1548
+ prev.map((item) =>
1549
+ item.id === segment.id
1550
+ ? {
1551
+ ...item,
1552
+ end: event.target.value,
1553
+ }
1554
+ : item
1555
+ )
1556
+ )
1557
+ }
1558
+ onBlur={() =>
1559
+ setTranscriptionSegments((prev) =>
1560
+ prev.map((item) =>
1561
+ item.id === segment.id
1562
+ ? {
1563
+ ...item,
1564
+ end: normalizeTimeInput(item.end),
1565
+ }
1566
+ : item
1567
+ )
1568
+ )
1569
+ }
1570
+ className="h-8 text-xs font-mono"
1571
+ placeholder="00:15"
1572
+ />
1573
+ <Textarea
1574
+ value={segment.text}
1575
+ onChange={(event) =>
1576
+ setTranscriptionSegments((prev) =>
1577
+ prev.map((item) =>
1578
+ item.id === segment.id
1579
+ ? {
1580
+ ...item,
1581
+ text: event.target.value,
1582
+ }
1583
+ : item
1584
+ )
1585
+ )
1586
+ }
1587
+ rows={2}
1588
+ className="text-xs resize-y"
1589
+ placeholder={t(
1590
+ 'lessonForm.transcriptionSegmentPlaceholder',
1591
+ {
1592
+ count: index + 1,
1593
+ }
1594
+ )}
1595
+ />
1596
+ <Button
1597
+ type="button"
1598
+ variant="ghost"
1599
+ size="icon"
1600
+ className="size-8 text-muted-foreground hover:text-destructive"
1601
+ onClick={() =>
1602
+ setTranscriptionSegments((prev) => {
1603
+ if (prev.length === 1) {
1604
+ const first = prev[0];
1605
+ return first ? [{ ...first, text: '' }] : [];
1606
+ }
1607
+
1608
+ return prev.filter(
1609
+ (item) => item.id !== segment.id
1610
+ );
1611
+ })
1612
+ }
1613
+ aria-label={t(
1614
+ 'lessonForm.removeTranscriptionSegment',
1615
+ {
1616
+ count: index + 1,
1617
+ }
1618
+ )}
1619
+ >
1620
+ <X className="size-3.5" />
1621
+ </Button>
1622
+ </div>
1623
+ </div>
1624
+ ))}
1625
+ <p className="text-[0.65rem] text-muted-foreground">
1626
+ {t('lessonForm.transcriptionAcceptedFormats')}
1627
+ </p>
1628
+ </CardContent>
1629
+ </Card>
1630
+ )}
1631
+ </div>
1632
+ </ScrollArea>
1633
+ </TabsContent>
1634
+
1298
1635
  {/* ── Tab Recursos ─────────────────────────────────────────────── */}
1299
1636
  <TabsContent value="recursos" className="flex-1 min-h-0 mt-0">
1300
1637
  <ScrollArea className="h-full">
1301
1638
  <div className="flex flex-col gap-3 p-3">
1639
+ {watchedType === 'video' &&
1640
+ watchedVideoProvider === 'file_storage' && (
1641
+ <Card className="bg-muted/20 py-2 gap-2">
1642
+ <CardHeader className="px-3 pt-2 pb-1">
1643
+ <CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
1644
+ {t('lessonForm.fileStorageVideosByResolution')}
1645
+ </CardTitle>
1646
+ </CardHeader>
1647
+ <CardContent className="px-3 pb-2 flex flex-col gap-3">
1648
+ <div className="grid grid-cols-1 md:grid-cols-[160px_1fr] gap-2">
1649
+ <Input
1650
+ value={videoResolution}
1651
+ onChange={(event) =>
1652
+ setVideoResolution(event.target.value)
1653
+ }
1654
+ className="h-8 text-xs"
1655
+ placeholder={t(
1656
+ 'lessonForm.videoResolutionPlaceholder'
1657
+ )}
1658
+ />
1659
+ <Button
1660
+ type="button"
1661
+ variant="outline"
1662
+ className="h-8 text-xs"
1663
+ disabled={isUploading}
1664
+ onClick={() => videoInputRef.current?.click()}
1665
+ >
1666
+ <UploadCloud className="size-3.5 mr-1" />
1667
+ {t('lessonForm.uploadVideoForResolution')}
1668
+ </Button>
1669
+ <input
1670
+ ref={videoInputRef}
1671
+ type="file"
1672
+ multiple
1673
+ accept="video/*"
1674
+ className="hidden"
1675
+ onChange={(event) => {
1676
+ if (event.target.files) {
1677
+ void handleVideoVariantFiles(
1678
+ Array.from(event.target.files)
1679
+ );
1680
+ event.target.value = '';
1681
+ }
1682
+ }}
1683
+ />
1684
+ </div>
1685
+
1686
+ {localResources.filter((res) =>
1687
+ res.type.startsWith('video/')
1688
+ ).length === 0 ? (
1689
+ <p className="text-xs text-muted-foreground">
1690
+ {t('lessonForm.noResolutionVideosYet')}
1691
+ </p>
1692
+ ) : (
1693
+ <div className="flex flex-col gap-1">
1694
+ {localResources
1695
+ .filter((res) => res.type.startsWith('video/'))
1696
+ .map((res) => {
1697
+ const resolution =
1698
+ res.type.replace('video/', '') || 'N/A';
1699
+ return (
1700
+ <div
1701
+ key={res.id}
1702
+ className="flex items-center gap-2 rounded-md border bg-background/80 px-2.5 py-2"
1703
+ >
1704
+ <Video className="size-3.5 shrink-0 text-blue-500" />
1705
+ <div className="flex-1 min-w-0">
1706
+ <p className="text-xs truncate font-medium">
1707
+ {res.name}
1708
+ </p>
1709
+ <p className="text-[0.65rem] text-muted-foreground">
1710
+ {resolution}
1711
+ {res.size ? ` · ${res.size}` : ''}
1712
+ </p>
1713
+ </div>
1714
+ <Button
1715
+ type="button"
1716
+ variant="ghost"
1717
+ size="icon"
1718
+ className="size-6 shrink-0"
1719
+ onClick={() => void openResource(res)}
1720
+ aria-label={t(
1721
+ 'lessonForm.openVideoAria',
1722
+ {
1723
+ name: res.name,
1724
+ }
1725
+ )}
1726
+ >
1727
+ <ExternalLink className="size-3" />
1728
+ </Button>
1729
+ <Button
1730
+ type="button"
1731
+ variant="ghost"
1732
+ size="icon"
1733
+ className="size-6 shrink-0"
1734
+ onClick={() =>
1735
+ void handleResourceDownload(res)
1736
+ }
1737
+ aria-label={t(
1738
+ 'lessonForm.downloadVideoAria',
1739
+ {
1740
+ name: res.name,
1741
+ }
1742
+ )}
1743
+ >
1744
+ <Download className="size-3" />
1745
+ </Button>
1746
+ <Button
1747
+ type="button"
1748
+ variant="ghost"
1749
+ size="icon"
1750
+ className="size-6 shrink-0 text-muted-foreground hover:text-destructive"
1751
+ onClick={() =>
1752
+ void removeResource(res.id)
1753
+ }
1754
+ aria-label={t(
1755
+ 'lessonForm.removeVideoAria',
1756
+ {
1757
+ name: res.name,
1758
+ }
1759
+ )}
1760
+ >
1761
+ <X className="size-3" />
1762
+ </Button>
1763
+ </div>
1764
+ );
1765
+ })}
1766
+ </div>
1767
+ )}
1768
+ </CardContent>
1769
+ </Card>
1770
+ )}
1771
+
1302
1772
  {/* Drop zone */}
1303
1773
  <div
1304
1774
  role="button"
@@ -1548,13 +2018,13 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
1548
2018
  <SelectValue />
1549
2019
  </SelectTrigger>
1550
2020
  <SelectContent>
1551
- {(
1552
- Object.keys(questionTypeLabels) as QuestionType[]
1553
- ).map((t) => (
1554
- <SelectItem key={t} value={t}>
1555
- {questionTypeLabels[t]}
1556
- </SelectItem>
1557
- ))}
2021
+ {(Object.keys(questionTypeLabels) as QuestionType[]).map(
2022
+ (t) => (
2023
+ <SelectItem key={t} value={t}>
2024
+ {questionTypeLabels[t]}
2025
+ </SelectItem>
2026
+ )
2027
+ )}
1558
2028
  </SelectContent>
1559
2029
  </Select>
1560
2030
  </div>