@hed-hog/lms 0.0.304 → 0.0.306

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 (458) hide show
  1. package/README.md +413 -401
  2. package/dist/certificate/certificate.controller.d.ts +90 -0
  3. package/dist/certificate/certificate.controller.d.ts.map +1 -0
  4. package/dist/certificate/certificate.controller.js +121 -0
  5. package/dist/certificate/certificate.controller.js.map +1 -0
  6. package/dist/certificate/certificate.module.d.ts +3 -0
  7. package/dist/certificate/certificate.module.d.ts.map +1 -0
  8. package/dist/certificate/certificate.module.js +26 -0
  9. package/dist/certificate/certificate.module.js.map +1 -0
  10. package/dist/certificate/certificate.service.d.ts +115 -0
  11. package/dist/certificate/certificate.service.d.ts.map +1 -0
  12. package/dist/certificate/certificate.service.js +343 -0
  13. package/dist/certificate/certificate.service.js.map +1 -0
  14. package/dist/certificate/dto/create-certificate-template.dto.d.ts +8 -0
  15. package/dist/certificate/dto/create-certificate-template.dto.d.ts.map +1 -0
  16. package/dist/certificate/dto/create-certificate-template.dto.js +44 -0
  17. package/dist/certificate/dto/create-certificate-template.dto.js.map +1 -0
  18. package/dist/certificate/dto/update-certificate-template.dto.d.ts +6 -0
  19. package/dist/certificate/dto/update-certificate-template.dto.d.ts.map +1 -0
  20. package/dist/certificate/dto/update-certificate-template.dto.js +9 -0
  21. package/dist/certificate/dto/update-certificate-template.dto.js.map +1 -0
  22. package/dist/class-group/class-group.controller.d.ts +305 -0
  23. package/dist/class-group/class-group.controller.d.ts.map +1 -0
  24. package/dist/class-group/class-group.controller.js +257 -0
  25. package/dist/class-group/class-group.controller.js.map +1 -0
  26. package/dist/class-group/class-group.module.d.ts +3 -0
  27. package/dist/class-group/class-group.module.d.ts.map +1 -0
  28. package/dist/class-group/class-group.module.js +25 -0
  29. package/dist/class-group/class-group.module.js.map +1 -0
  30. package/dist/class-group/class-group.service.d.ts +354 -0
  31. package/dist/class-group/class-group.service.d.ts.map +1 -0
  32. package/dist/class-group/class-group.service.js +1356 -0
  33. package/dist/class-group/class-group.service.js.map +1 -0
  34. package/dist/class-group/dto/create-class-group.dto.d.ts +33 -0
  35. package/dist/class-group/dto/create-class-group.dto.d.ts.map +1 -0
  36. package/dist/class-group/dto/create-class-group.dto.js +165 -0
  37. package/dist/class-group/dto/create-class-group.dto.js.map +1 -0
  38. package/dist/class-group/dto/create-session.dto.d.ts +22 -0
  39. package/dist/class-group/dto/create-session.dto.d.ts.map +1 -0
  40. package/dist/class-group/dto/create-session.dto.js +117 -0
  41. package/dist/class-group/dto/create-session.dto.js.map +1 -0
  42. package/dist/class-group/dto/enrollment.dto.d.ts +22 -0
  43. package/dist/class-group/dto/enrollment.dto.d.ts.map +1 -0
  44. package/dist/class-group/dto/enrollment.dto.js +89 -0
  45. package/dist/class-group/dto/enrollment.dto.js.map +1 -0
  46. package/dist/class-group/dto/update-class-group.dto.d.ts +6 -0
  47. package/dist/class-group/dto/update-class-group.dto.d.ts.map +1 -0
  48. package/dist/class-group/dto/update-class-group.dto.js +9 -0
  49. package/dist/class-group/dto/update-class-group.dto.js.map +1 -0
  50. package/dist/class-group/dto/update-session.dto.d.ts +7 -0
  51. package/dist/class-group/dto/update-session.dto.d.ts.map +1 -0
  52. package/dist/class-group/dto/update-session.dto.js +24 -0
  53. package/dist/class-group/dto/update-session.dto.js.map +1 -0
  54. package/dist/course/course-structure.controller.d.ts +127 -0
  55. package/dist/course/course-structure.controller.d.ts.map +1 -0
  56. package/dist/course/course-structure.controller.js +115 -0
  57. package/dist/course/course-structure.controller.js.map +1 -0
  58. package/dist/course/course-structure.service.d.ts +142 -0
  59. package/dist/course/course-structure.service.d.ts.map +1 -0
  60. package/dist/course/course-structure.service.js +445 -0
  61. package/dist/course/course-structure.service.js.map +1 -0
  62. package/dist/course/course.controller.d.ts +195 -0
  63. package/dist/course/course.controller.d.ts.map +1 -0
  64. package/dist/course/course.controller.js +104 -0
  65. package/dist/course/course.controller.js.map +1 -0
  66. package/dist/course/course.module.d.ts +3 -0
  67. package/dist/course/course.module.d.ts.map +1 -0
  68. package/dist/course/course.module.js +28 -0
  69. package/dist/course/course.module.js.map +1 -0
  70. package/dist/course/course.service.d.ts +215 -0
  71. package/dist/course/course.service.d.ts.map +1 -0
  72. package/dist/course/course.service.js +743 -0
  73. package/dist/course/course.service.js.map +1 -0
  74. package/dist/course/dto/create-course-structure-lesson.dto.d.ts +24 -0
  75. package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -0
  76. package/dist/course/dto/create-course-structure-lesson.dto.js +118 -0
  77. package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -0
  78. package/dist/course/dto/create-course-structure-session.dto.d.ts +7 -0
  79. package/dist/course/dto/create-course-structure-session.dto.d.ts.map +1 -0
  80. package/dist/course/dto/create-course-structure-session.dto.js +40 -0
  81. package/dist/course/dto/create-course-structure-session.dto.js.map +1 -0
  82. package/dist/course/dto/create-course.dto.d.ts +26 -0
  83. package/dist/course/dto/create-course.dto.d.ts.map +1 -0
  84. package/dist/course/dto/create-course.dto.js +138 -0
  85. package/dist/course/dto/create-course.dto.js.map +1 -0
  86. package/dist/course/dto/update-course-structure-lesson.dto.d.ts +6 -0
  87. package/dist/course/dto/update-course-structure-lesson.dto.d.ts.map +1 -0
  88. package/dist/course/dto/update-course-structure-lesson.dto.js +9 -0
  89. package/dist/course/dto/update-course-structure-lesson.dto.js.map +1 -0
  90. package/dist/course/dto/update-course-structure-session.dto.d.ts +6 -0
  91. package/dist/course/dto/update-course-structure-session.dto.d.ts.map +1 -0
  92. package/dist/course/dto/update-course-structure-session.dto.js +9 -0
  93. package/dist/course/dto/update-course-structure-session.dto.js.map +1 -0
  94. package/dist/course/dto/update-course.dto.d.ts +6 -0
  95. package/dist/course/dto/update-course.dto.d.ts.map +1 -0
  96. package/dist/course/dto/update-course.dto.js +9 -0
  97. package/dist/course/dto/update-course.dto.js.map +1 -0
  98. package/dist/dashboard/dashboard.controller.d.ts +101 -0
  99. package/dist/dashboard/dashboard.controller.d.ts.map +1 -0
  100. package/dist/dashboard/dashboard.controller.js +40 -0
  101. package/dist/dashboard/dashboard.controller.js.map +1 -0
  102. package/dist/dashboard/dashboard.module.d.ts +3 -0
  103. package/dist/dashboard/dashboard.module.d.ts.map +1 -0
  104. package/dist/dashboard/dashboard.module.js +25 -0
  105. package/dist/dashboard/dashboard.module.js.map +1 -0
  106. package/dist/dashboard/dashboard.service.d.ts +130 -0
  107. package/dist/dashboard/dashboard.service.d.ts.map +1 -0
  108. package/dist/dashboard/dashboard.service.js +626 -0
  109. package/dist/dashboard/dashboard.service.js.map +1 -0
  110. package/dist/enterprise/dto/add-enterprise-class-group.dto.d.ts +4 -0
  111. package/dist/enterprise/dto/add-enterprise-class-group.dto.d.ts.map +1 -0
  112. package/dist/enterprise/dto/add-enterprise-class-group.dto.js +22 -0
  113. package/dist/enterprise/dto/add-enterprise-class-group.dto.js.map +1 -0
  114. package/dist/enterprise/dto/add-enterprise-course.dto.d.ts +5 -0
  115. package/dist/enterprise/dto/add-enterprise-course.dto.d.ts.map +1 -0
  116. package/dist/enterprise/dto/add-enterprise-course.dto.js +27 -0
  117. package/dist/enterprise/dto/add-enterprise-course.dto.js.map +1 -0
  118. package/dist/enterprise/dto/add-enterprise-student.dto.d.ts +5 -0
  119. package/dist/enterprise/dto/add-enterprise-student.dto.d.ts.map +1 -0
  120. package/dist/enterprise/dto/add-enterprise-student.dto.js +27 -0
  121. package/dist/enterprise/dto/add-enterprise-student.dto.js.map +1 -0
  122. package/dist/enterprise/dto/add-enterprise-user.dto.d.ts +7 -0
  123. package/dist/enterprise/dto/add-enterprise-user.dto.d.ts.map +1 -0
  124. package/dist/enterprise/dto/add-enterprise-user.dto.js +36 -0
  125. package/dist/enterprise/dto/add-enterprise-user.dto.js.map +1 -0
  126. package/dist/enterprise/dto/create-enterprise.dto.d.ts +10 -0
  127. package/dist/enterprise/dto/create-enterprise.dto.d.ts.map +1 -0
  128. package/dist/enterprise/dto/create-enterprise.dto.js +54 -0
  129. package/dist/enterprise/dto/create-enterprise.dto.js.map +1 -0
  130. package/dist/enterprise/dto/update-enterprise-student.dto.d.ts +4 -0
  131. package/dist/enterprise/dto/update-enterprise-student.dto.d.ts.map +1 -0
  132. package/dist/enterprise/dto/update-enterprise-student.dto.js +22 -0
  133. package/dist/enterprise/dto/update-enterprise-student.dto.js.map +1 -0
  134. package/dist/enterprise/dto/update-enterprise-user.dto.d.ts +5 -0
  135. package/dist/enterprise/dto/update-enterprise-user.dto.d.ts.map +1 -0
  136. package/dist/enterprise/dto/update-enterprise-user.dto.js +27 -0
  137. package/dist/enterprise/dto/update-enterprise-user.dto.js.map +1 -0
  138. package/dist/enterprise/dto/update-enterprise.dto.d.ts +6 -0
  139. package/dist/enterprise/dto/update-enterprise.dto.d.ts.map +1 -0
  140. package/dist/enterprise/dto/update-enterprise.dto.js +9 -0
  141. package/dist/enterprise/dto/update-enterprise.dto.js.map +1 -0
  142. package/dist/enterprise/enterprise.controller.d.ts +269 -0
  143. package/dist/enterprise/enterprise.controller.d.ts.map +1 -0
  144. package/dist/enterprise/enterprise.controller.js +311 -0
  145. package/dist/enterprise/enterprise.controller.js.map +1 -0
  146. package/dist/enterprise/enterprise.module.d.ts +3 -0
  147. package/dist/enterprise/enterprise.module.d.ts.map +1 -0
  148. package/dist/enterprise/enterprise.module.js +25 -0
  149. package/dist/enterprise/enterprise.module.js.map +1 -0
  150. package/dist/enterprise/enterprise.service.d.ts +282 -0
  151. package/dist/enterprise/enterprise.service.d.ts.map +1 -0
  152. package/dist/enterprise/enterprise.service.js +627 -0
  153. package/dist/enterprise/enterprise.service.js.map +1 -0
  154. package/dist/evaluation/evaluation.controller.d.ts +56 -0
  155. package/dist/evaluation/evaluation.controller.d.ts.map +1 -0
  156. package/dist/evaluation/evaluation.controller.js +76 -0
  157. package/dist/evaluation/evaluation.controller.js.map +1 -0
  158. package/dist/evaluation/evaluation.module.d.ts +3 -0
  159. package/dist/evaluation/evaluation.module.d.ts.map +1 -0
  160. package/dist/evaluation/evaluation.module.js +25 -0
  161. package/dist/evaluation/evaluation.module.js.map +1 -0
  162. package/dist/evaluation/evaluation.service.d.ts +67 -0
  163. package/dist/evaluation/evaluation.service.d.ts.map +1 -0
  164. package/dist/evaluation/evaluation.service.js +378 -0
  165. package/dist/evaluation/evaluation.service.js.map +1 -0
  166. package/dist/exam/dto/create-exam-question.dto.d.ts +25 -0
  167. package/dist/exam/dto/create-exam-question.dto.d.ts.map +1 -0
  168. package/dist/exam/dto/create-exam-question.dto.js +117 -0
  169. package/dist/exam/dto/create-exam-question.dto.js.map +1 -0
  170. package/dist/exam/dto/create-exam.dto.d.ts +11 -0
  171. package/dist/exam/dto/create-exam.dto.d.ts.map +1 -0
  172. package/dist/exam/dto/create-exam.dto.js +63 -0
  173. package/dist/exam/dto/create-exam.dto.js.map +1 -0
  174. package/dist/exam/dto/reorder-exam-questions.dto.d.ts +4 -0
  175. package/dist/exam/dto/reorder-exam-questions.dto.d.ts.map +1 -0
  176. package/dist/exam/dto/reorder-exam-questions.dto.js +23 -0
  177. package/dist/exam/dto/reorder-exam-questions.dto.js.map +1 -0
  178. package/dist/exam/dto/save-exam-attempt-answers.dto.d.ts +14 -0
  179. package/dist/exam/dto/save-exam-attempt-answers.dto.d.ts.map +1 -0
  180. package/dist/exam/dto/save-exam-attempt-answers.dto.js +68 -0
  181. package/dist/exam/dto/save-exam-attempt-answers.dto.js.map +1 -0
  182. package/dist/exam/dto/start-exam-attempt.dto.d.ts +4 -0
  183. package/dist/exam/dto/start-exam-attempt.dto.d.ts.map +1 -0
  184. package/dist/exam/dto/start-exam-attempt.dto.js +23 -0
  185. package/dist/exam/dto/start-exam-attempt.dto.js.map +1 -0
  186. package/dist/exam/dto/submit-exam-attempt.dto.d.ts +5 -0
  187. package/dist/exam/dto/submit-exam-attempt.dto.d.ts.map +1 -0
  188. package/dist/exam/dto/submit-exam-attempt.dto.js +23 -0
  189. package/dist/exam/dto/submit-exam-attempt.dto.js.map +1 -0
  190. package/dist/exam/dto/update-exam-question.dto.d.ts +6 -0
  191. package/dist/exam/dto/update-exam-question.dto.d.ts.map +1 -0
  192. package/dist/exam/dto/update-exam-question.dto.js +9 -0
  193. package/dist/exam/dto/update-exam-question.dto.js.map +1 -0
  194. package/dist/exam/dto/update-exam.dto.d.ts +6 -0
  195. package/dist/exam/dto/update-exam.dto.d.ts.map +1 -0
  196. package/dist/exam/dto/update-exam.dto.js +9 -0
  197. package/dist/exam/dto/update-exam.dto.js.map +1 -0
  198. package/dist/exam/exam-attempt.controller.d.ts +273 -0
  199. package/dist/exam/exam-attempt.controller.d.ts.map +1 -0
  200. package/dist/exam/exam-attempt.controller.js +84 -0
  201. package/dist/exam/exam-attempt.controller.js.map +1 -0
  202. package/dist/exam/exam-attempt.service.d.ts +302 -0
  203. package/dist/exam/exam-attempt.service.d.ts.map +1 -0
  204. package/dist/exam/exam-attempt.service.js +776 -0
  205. package/dist/exam/exam-attempt.service.js.map +1 -0
  206. package/dist/exam/exam.controller.d.ts +162 -0
  207. package/dist/exam/exam.controller.d.ts.map +1 -0
  208. package/dist/exam/exam.controller.js +158 -0
  209. package/dist/exam/exam.controller.js.map +1 -0
  210. package/dist/exam/exam.module.d.ts +3 -0
  211. package/dist/exam/exam.module.d.ts.map +1 -0
  212. package/dist/exam/exam.module.js +27 -0
  213. package/dist/exam/exam.module.js.map +1 -0
  214. package/dist/exam/exam.service.d.ts +179 -0
  215. package/dist/exam/exam.service.d.ts.map +1 -0
  216. package/dist/exam/exam.service.js +597 -0
  217. package/dist/exam/exam.service.js.map +1 -0
  218. package/dist/index.d.ts +28 -0
  219. package/dist/index.d.ts.map +1 -1
  220. package/dist/index.js +28 -0
  221. package/dist/index.js.map +1 -1
  222. package/dist/instructor/dto/create-instructor.dto.d.ts +10 -0
  223. package/dist/instructor/dto/create-instructor.dto.d.ts.map +1 -0
  224. package/dist/instructor/dto/create-instructor.dto.js +55 -0
  225. package/dist/instructor/dto/create-instructor.dto.js.map +1 -0
  226. package/dist/instructor/dto/update-instructor.dto.d.ts +9 -0
  227. package/dist/instructor/dto/update-instructor.dto.d.ts.map +1 -0
  228. package/dist/instructor/dto/update-instructor.dto.js +51 -0
  229. package/dist/instructor/dto/update-instructor.dto.js.map +1 -0
  230. package/dist/instructor/instructor.controller.d.ts +52 -0
  231. package/dist/instructor/instructor.controller.d.ts.map +1 -0
  232. package/dist/instructor/instructor.controller.js +98 -0
  233. package/dist/instructor/instructor.controller.js.map +1 -0
  234. package/dist/instructor/instructor.module.d.ts +3 -0
  235. package/dist/instructor/instructor.module.d.ts.map +1 -0
  236. package/dist/instructor/instructor.module.js +25 -0
  237. package/dist/instructor/instructor.module.js.map +1 -0
  238. package/dist/instructor/instructor.service.d.ts +79 -0
  239. package/dist/instructor/instructor.service.d.ts.map +1 -0
  240. package/dist/instructor/instructor.service.js +528 -0
  241. package/dist/instructor/instructor.service.js.map +1 -0
  242. package/dist/lms.module.d.ts.map +1 -1
  243. package/dist/lms.module.js +36 -4
  244. package/dist/lms.module.js.map +1 -1
  245. package/dist/reports/reports.controller.d.ts +69 -0
  246. package/dist/reports/reports.controller.d.ts.map +1 -0
  247. package/dist/reports/reports.controller.js +40 -0
  248. package/dist/reports/reports.controller.js.map +1 -0
  249. package/dist/reports/reports.module.d.ts +3 -0
  250. package/dist/reports/reports.module.d.ts.map +1 -0
  251. package/dist/reports/reports.module.js +25 -0
  252. package/dist/reports/reports.module.js.map +1 -0
  253. package/dist/reports/reports.service.d.ts +80 -0
  254. package/dist/reports/reports.service.d.ts.map +1 -0
  255. package/dist/reports/reports.service.js +366 -0
  256. package/dist/reports/reports.service.js.map +1 -0
  257. package/dist/training/dto/create-training.dto.d.ts +19 -0
  258. package/dist/training/dto/create-training.dto.d.ts.map +1 -0
  259. package/dist/training/dto/create-training.dto.js +98 -0
  260. package/dist/training/dto/create-training.dto.js.map +1 -0
  261. package/dist/training/dto/update-training.dto.d.ts +6 -0
  262. package/dist/training/dto/update-training.dto.d.ts.map +1 -0
  263. package/dist/training/dto/update-training.dto.js +9 -0
  264. package/dist/training/dto/update-training.dto.js.map +1 -0
  265. package/dist/training/training.controller.d.ts +195 -0
  266. package/dist/training/training.controller.d.ts.map +1 -0
  267. package/dist/training/training.controller.js +104 -0
  268. package/dist/training/training.controller.js.map +1 -0
  269. package/dist/training/training.module.d.ts +3 -0
  270. package/dist/training/training.module.d.ts.map +1 -0
  271. package/dist/training/training.module.js +25 -0
  272. package/dist/training/training.module.js.map +1 -0
  273. package/dist/training/training.service.d.ts +213 -0
  274. package/dist/training/training.service.d.ts.map +1 -0
  275. package/dist/training/training.service.js +497 -0
  276. package/dist/training/training.service.js.map +1 -0
  277. package/hedhog/data/dashboard.yaml +6 -0
  278. package/hedhog/data/dashboard_component.yaml +153 -0
  279. package/hedhog/data/dashboard_component_role.yaml +97 -0
  280. package/hedhog/data/dashboard_item.yaml +167 -0
  281. package/hedhog/data/dashboard_role.yaml +6 -0
  282. package/hedhog/data/instructor_qualification.yaml +16 -0
  283. package/hedhog/data/menu.yaml +129 -19
  284. package/hedhog/data/role.yaml +25 -1
  285. package/hedhog/data/route.yaml +867 -0
  286. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +1992 -0
  287. package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +480 -0
  288. package/hedhog/frontend/app/_components/create-lms-instructor-sheet.tsx.ejs +591 -0
  289. package/hedhog/frontend/app/_components/create-lms-person-sheet.tsx.ejs +164 -0
  290. package/hedhog/frontend/app/_components/create-lms-student-person-sheet.tsx.ejs +120 -0
  291. package/hedhog/frontend/app/_components/lms-class-calendar.tsx.ejs +272 -0
  292. package/hedhog/frontend/app/_components/mobile-calendar.tsx.ejs +277 -0
  293. package/hedhog/frontend/app/_lib/editor/canvasInstance.ts.ejs +48 -0
  294. package/hedhog/frontend/app/_lib/editor/pctHelpers.ts.ejs +50 -0
  295. package/hedhog/frontend/app/_lib/editor/templateSerializer.ts.ejs +268 -0
  296. package/hedhog/frontend/app/_lib/editor/types.ts.ejs +94 -0
  297. package/hedhog/frontend/app/_lib/store/useTemplateStore.ts.ejs +284 -0
  298. package/hedhog/frontend/app/certificates/issued/page.tsx.ejs +638 -0
  299. package/hedhog/frontend/app/certificates/models/CanvasStage.tsx.ejs +916 -0
  300. package/hedhog/frontend/app/certificates/models/LeftPanel.tsx.ejs +200 -0
  301. package/hedhog/frontend/app/certificates/models/RightPanel.tsx.ejs +769 -0
  302. package/hedhog/frontend/app/certificates/models/TemplateEditorPage.tsx.ejs +104 -0
  303. package/hedhog/frontend/app/certificates/models/TopBar.tsx.ejs +354 -0
  304. package/hedhog/frontend/app/certificates/models/editor/page.tsx.ejs +5 -0
  305. package/hedhog/frontend/app/certificates/models/page.tsx.ejs +883 -0
  306. package/hedhog/frontend/app/classes/[id]/_components/event-summary-popover.tsx.ejs +279 -0
  307. package/hedhog/frontend/app/classes/[id]/_components/quick-create-session-popover.tsx.ejs +1027 -0
  308. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +3130 -993
  309. package/hedhog/frontend/app/classes/page.tsx.ejs +2731 -759
  310. package/hedhog/frontend/app/courses/[id]/_components/CourseCertificateCard.tsx.ejs +80 -0
  311. package/hedhog/frontend/app/courses/[id]/_components/CourseClassificationCard.tsx.ejs +226 -0
  312. package/hedhog/frontend/app/courses/[id]/_components/CourseContentCard.tsx.ejs +71 -0
  313. package/hedhog/frontend/app/courses/[id]/_components/CourseDangerZoneCard.tsx.ejs +42 -0
  314. package/hedhog/frontend/app/courses/[id]/_components/CourseFlagsCard.tsx.ejs +111 -0
  315. package/hedhog/frontend/app/courses/[id]/_components/CourseMainInfoCard.tsx.ejs +113 -0
  316. package/hedhog/frontend/app/courses/[id]/_components/CourseMediaCard.tsx.ejs +215 -0
  317. package/hedhog/frontend/app/courses/[id]/_components/CourseMultiEntityPicker.tsx.ejs +236 -0
  318. package/hedhog/frontend/app/courses/[id]/_components/CourseRelationsCard.tsx.ejs +141 -0
  319. package/hedhog/frontend/app/courses/[id]/_components/CourseSectionCard.tsx.ejs +57 -0
  320. package/hedhog/frontend/app/courses/[id]/_components/CourseSummaryCard.tsx.ejs +60 -0
  321. package/hedhog/frontend/app/courses/[id]/_components/course-edit-types.ts.ejs +33 -0
  322. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +933 -1103
  323. package/hedhog/frontend/app/courses/[id]/structure/page.tsx.ejs +699 -117
  324. package/hedhog/frontend/app/courses/page.tsx.ejs +1018 -1042
  325. package/hedhog/frontend/app/enterprise/[id]/page.tsx.ejs +317 -0
  326. package/hedhog/frontend/app/enterprise/_components/enterprise-activity-panel.tsx.ejs +88 -0
  327. package/hedhog/frontend/app/enterprise/_components/enterprise-admin-create-sheet.tsx.ejs +318 -0
  328. package/hedhog/frontend/app/enterprise/_components/enterprise-administrators-tab.tsx.ejs +332 -0
  329. package/hedhog/frontend/app/enterprise/_components/enterprise-class-create-sheet.tsx.ejs +58 -0
  330. package/hedhog/frontend/app/enterprise/_components/enterprise-classes-tab.tsx.ejs +390 -0
  331. package/hedhog/frontend/app/enterprise/_components/enterprise-company-identity-card.tsx.ejs +112 -0
  332. package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +183 -0
  333. package/hedhog/frontend/app/enterprise/_components/enterprise-courses-tab.tsx.ejs +363 -0
  334. package/hedhog/frontend/app/enterprise/_components/enterprise-detail-constants.ts.ejs +88 -0
  335. package/hedhog/frontend/app/enterprise/_components/enterprise-detail-sheet.tsx.ejs +548 -0
  336. package/hedhog/frontend/app/enterprise/_components/enterprise-detail-utils.ts.ejs +33 -0
  337. package/hedhog/frontend/app/enterprise/_components/enterprise-mocks.ts.ejs +277 -0
  338. package/hedhog/frontend/app/enterprise/_components/enterprise-person-picker.ts.ejs +31 -0
  339. package/hedhog/frontend/app/enterprise/_components/enterprise-progress-bar.tsx.ejs +21 -0
  340. package/hedhog/frontend/app/enterprise/_components/enterprise-related-tab.tsx.ejs +224 -0
  341. package/hedhog/frontend/app/enterprise/_components/enterprise-sheet.tsx.ejs +397 -0
  342. package/hedhog/frontend/app/enterprise/_components/enterprise-student-create-sheet.tsx.ejs +167 -0
  343. package/hedhog/frontend/app/enterprise/_components/enterprise-students-tab.tsx.ejs +267 -0
  344. package/hedhog/frontend/app/enterprise/_components/enterprise-system-user-picker.ts.ejs +42 -0
  345. package/hedhog/frontend/app/enterprise/_components/enterprise-types.ts.ejs +96 -0
  346. package/hedhog/frontend/app/enterprise/_components/enterprise-user-create-sheet.tsx.ejs +207 -0
  347. package/hedhog/frontend/app/enterprise/_components/enterprise-user-distribution-chart.tsx.ejs +149 -0
  348. package/hedhog/frontend/app/enterprise/page.tsx.ejs +596 -0
  349. package/hedhog/frontend/app/evaluations/page.tsx.ejs +1250 -0
  350. package/hedhog/frontend/app/exams/[id]/attempt/page.tsx.ejs +642 -196
  351. package/hedhog/frontend/app/exams/[id]/page.tsx.ejs +11 -0
  352. package/hedhog/frontend/app/exams/[id]/questions/page.tsx.ejs +1316 -436
  353. package/hedhog/frontend/app/exams/page.tsx.ejs +799 -546
  354. package/hedhog/frontend/app/layout.tsx.ejs +5 -0
  355. package/hedhog/frontend/app/page.tsx.ejs +3 -1220
  356. package/hedhog/frontend/app/reports/courses/page.tsx.ejs +843 -0
  357. package/hedhog/frontend/app/reports/dashboard/page.tsx.ejs +890 -0
  358. package/hedhog/frontend/app/reports/page.tsx.ejs +802 -808
  359. package/hedhog/frontend/app/reports/students/page.tsx.ejs +772 -0
  360. package/hedhog/frontend/app/training/page.tsx.ejs +1873 -628
  361. package/hedhog/frontend/messages/en.json +1606 -111
  362. package/hedhog/frontend/messages/pt.json +1636 -134
  363. package/hedhog/frontend/widgets/active-classes-kpi.tsx.ejs +74 -0
  364. package/hedhog/frontend/widgets/active-courses-kpi.tsx.ejs +74 -0
  365. package/hedhog/frontend/widgets/approval-rate-kpi.tsx.ejs +81 -0
  366. package/hedhog/frontend/widgets/category-distribution-chart.tsx.ejs +119 -0
  367. package/hedhog/frontend/widgets/class-calendar.tsx.ejs +440 -0
  368. package/hedhog/frontend/widgets/completion-rate-kpi.tsx.ejs +81 -0
  369. package/hedhog/frontend/widgets/engagement-chart.tsx.ejs +120 -0
  370. package/hedhog/frontend/widgets/footer-summary.tsx.ejs +80 -0
  371. package/hedhog/frontend/widgets/issued-certificates-kpi.tsx.ejs +74 -0
  372. package/hedhog/frontend/widgets/latest-enrollments.tsx.ejs +166 -0
  373. package/hedhog/frontend/widgets/student-growth-chart.tsx.ejs +89 -0
  374. package/hedhog/frontend/widgets/top-courses-chart.tsx.ejs +104 -0
  375. package/hedhog/frontend/widgets/total-students-kpi.tsx.ejs +78 -0
  376. package/hedhog/frontend/widgets/upcoming-classes.tsx.ejs +152 -0
  377. package/hedhog/table/course.yaml +19 -1
  378. package/hedhog/table/course_class_group.yaml +8 -0
  379. package/hedhog/table/course_class_session.yaml +33 -0
  380. package/hedhog/table/course_instructor.yaml +27 -0
  381. package/hedhog/table/enterprise.yaml +29 -0
  382. package/hedhog/table/enterprise_class_group.yaml +20 -0
  383. package/hedhog/table/enterprise_course.yaml +23 -0
  384. package/hedhog/table/enterprise_student.yaml +24 -0
  385. package/hedhog/table/enterprise_user.yaml +35 -0
  386. package/hedhog/table/instructor_qualification.yaml +26 -0
  387. package/hedhog/table/instructor_qualification_assignment.yaml +22 -0
  388. package/hedhog/table/question.yaml +6 -0
  389. package/package.json +6 -6
  390. package/src/certificate/certificate.controller.ts +83 -0
  391. package/src/certificate/certificate.module.ts +13 -0
  392. package/src/certificate/certificate.service.ts +413 -0
  393. package/src/certificate/dto/create-certificate-template.dto.ts +25 -0
  394. package/src/certificate/dto/update-certificate-template.dto.ts +6 -0
  395. package/src/class-group/class-group.controller.ts +189 -0
  396. package/src/class-group/class-group.module.ts +12 -0
  397. package/src/class-group/class-group.service.ts +1802 -0
  398. package/src/class-group/dto/create-class-group.dto.ts +139 -0
  399. package/src/class-group/dto/create-session.dto.ts +102 -0
  400. package/src/class-group/dto/enrollment.dto.ts +70 -0
  401. package/src/class-group/dto/update-class-group.dto.ts +4 -0
  402. package/src/class-group/dto/update-session.dto.ts +9 -0
  403. package/src/course/course-structure.controller.ts +85 -0
  404. package/src/course/course-structure.service.ts +525 -0
  405. package/src/course/course.controller.ts +69 -0
  406. package/src/course/course.module.ts +15 -0
  407. package/src/course/course.service.ts +920 -0
  408. package/src/course/dto/create-course-structure-lesson.dto.ts +97 -0
  409. package/src/course/dto/create-course-structure-session.dto.ts +22 -0
  410. package/src/course/dto/create-course.dto.ts +111 -0
  411. package/src/course/dto/update-course-structure-lesson.dto.ts +6 -0
  412. package/src/course/dto/update-course-structure-session.dto.ts +6 -0
  413. package/src/course/dto/update-course.dto.ts +4 -0
  414. package/src/dashboard/dashboard.controller.ts +14 -0
  415. package/src/dashboard/dashboard.module.ts +12 -0
  416. package/src/dashboard/dashboard.service.ts +726 -0
  417. package/src/enterprise/dto/add-enterprise-class-group.dto.ts +7 -0
  418. package/src/enterprise/dto/add-enterprise-course.dto.ts +11 -0
  419. package/src/enterprise/dto/add-enterprise-student.dto.ts +16 -0
  420. package/src/enterprise/dto/add-enterprise-user.dto.ts +23 -0
  421. package/src/enterprise/dto/create-enterprise.dto.ts +41 -0
  422. package/src/enterprise/dto/update-enterprise-student.dto.ts +7 -0
  423. package/src/enterprise/dto/update-enterprise-user.dto.ts +11 -0
  424. package/src/enterprise/dto/update-enterprise.dto.ts +4 -0
  425. package/src/enterprise/enterprise.controller.ts +233 -0
  426. package/src/enterprise/enterprise.module.ts +12 -0
  427. package/src/enterprise/enterprise.service.ts +712 -0
  428. package/src/evaluation/evaluation.controller.ts +44 -0
  429. package/src/evaluation/evaluation.module.ts +12 -0
  430. package/src/evaluation/evaluation.service.ts +394 -0
  431. package/src/exam/dto/create-exam-question.dto.ts +103 -0
  432. package/src/exam/dto/create-exam.dto.ts +41 -0
  433. package/src/exam/dto/reorder-exam-questions.dto.ts +8 -0
  434. package/src/exam/dto/save-exam-attempt-answers.dto.ts +55 -0
  435. package/src/exam/dto/start-exam-attempt.dto.ts +8 -0
  436. package/src/exam/dto/submit-exam-attempt.dto.ts +8 -0
  437. package/src/exam/dto/update-exam-question.dto.ts +4 -0
  438. package/src/exam/dto/update-exam.dto.ts +4 -0
  439. package/src/exam/exam-attempt.controller.ts +65 -0
  440. package/src/exam/exam-attempt.service.ts +1008 -0
  441. package/src/exam/exam.controller.ts +102 -0
  442. package/src/exam/exam.module.ts +14 -0
  443. package/src/exam/exam.service.ts +784 -0
  444. package/src/index.ts +29 -0
  445. package/src/instructor/dto/create-instructor.dto.ts +43 -0
  446. package/src/instructor/dto/update-instructor.dto.ts +38 -0
  447. package/src/instructor/instructor.controller.ts +73 -0
  448. package/src/instructor/instructor.module.ts +12 -0
  449. package/src/instructor/instructor.service.ts +646 -0
  450. package/src/lms.module.ts +36 -4
  451. package/src/reports/reports.controller.ts +14 -0
  452. package/src/reports/reports.module.ts +12 -0
  453. package/src/reports/reports.service.ts +485 -0
  454. package/src/training/dto/create-training.dto.ts +81 -0
  455. package/src/training/dto/update-training.dto.ts +4 -0
  456. package/src/training/training.controller.ts +68 -0
  457. package/src/training/training.module.ts +12 -0
  458. package/src/training/training.service.ts +574 -0
@@ -60,8 +60,9 @@ import {
60
60
  verticalListSortingStrategy,
61
61
  } from '@dnd-kit/sortable';
62
62
  import { CSS } from '@dnd-kit/utilities';
63
+ import { useApp } from '@hed-hog/next-app-provider';
63
64
  import { zodResolver } from '@hookform/resolvers/zod';
64
- import { AnimatePresence, motion, type Variants } from 'framer-motion';
65
+ import { AnimatePresence, motion } from 'framer-motion';
65
66
  import {
66
67
  AlertTriangle,
67
68
  Archive,
@@ -102,7 +103,7 @@ import {
102
103
  import { useTranslations } from 'next-intl';
103
104
  import { usePathname } from 'next/navigation';
104
105
  import { use, useCallback, useEffect, useRef, useState } from 'react';
105
- import { Controller, useForm } from 'react-hook-form';
106
+ import { Controller, useForm, useWatch } from 'react-hook-form';
106
107
  import { toast } from 'sonner';
107
108
  import { z } from 'zod';
108
109
 
@@ -130,6 +131,12 @@ interface Recurso {
130
131
  publico: boolean;
131
132
  }
132
133
 
134
+ interface AulaInstructor {
135
+ id: string;
136
+ role?: 'lead' | 'assistant';
137
+ nome?: string;
138
+ }
139
+
133
140
  interface Aula {
134
141
  id: string;
135
142
  codigo: string;
@@ -150,6 +157,12 @@ interface Aula {
150
157
  conteudoPost?: string;
151
158
  // Recursos
152
159
  recursos: Recurso[];
160
+ instrutores?: AulaInstructor[];
161
+ }
162
+
163
+ interface InstructorOption {
164
+ id: number;
165
+ name: string;
153
166
  }
154
167
 
155
168
  interface Sessao {
@@ -209,6 +222,18 @@ type AulaFormType = {
209
222
  conteudoPost?: string;
210
223
  };
211
224
 
225
+ type CourseStructureResponse = {
226
+ sessoes: Sessao[];
227
+ aulas: Aula[];
228
+ instructors?: InstructorOption[];
229
+ };
230
+
231
+ type Locale = {
232
+ id?: number;
233
+ code: string;
234
+ name: string;
235
+ };
236
+
212
237
  // ── Constants ───────────────────────────────────────────────────────────────
213
238
 
214
239
  function getTipoAulaMap(
@@ -578,10 +603,17 @@ const initialAulas: Aula[] = [
578
603
 
579
604
  // ── Animations ──────────────────────────────────────────────────────────────
580
605
 
581
- const stagger = { hidden: {}, show: { transition: { staggerChildren: 0.04 } } };
582
- const fadeUp: Variants = {
606
+ const stagger: any = {
607
+ hidden: {},
608
+ show: { transition: { staggerChildren: 0.04 } },
609
+ };
610
+ const fadeUp: any = {
583
611
  hidden: { opacity: 0, y: 12 },
584
- show: { opacity: 1, y: 0, transition: { duration: 0.3, ease: 'easeOut' } },
612
+ show: {
613
+ opacity: 1,
614
+ y: 0,
615
+ transition: { duration: 0.3 },
616
+ },
585
617
  };
586
618
 
587
619
  // ═══════════════════════════════════════════════════════════════════════════
@@ -594,6 +626,8 @@ export default function EstruturaPage({
594
626
  params: Promise<{ id: string }>;
595
627
  }) {
596
628
  const { id } = use(params);
629
+ const courseId = Number(id);
630
+ const { request } = useApp();
597
631
  const pathname = usePathname();
598
632
  const t = useTranslations('lms.CoursesPage.StructurePage');
599
633
 
@@ -602,8 +636,8 @@ export default function EstruturaPage({
602
636
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
603
637
 
604
638
  // Data
605
- const [sessoes, setSessoes] = useState<Sessao[]>(initialSessoes);
606
- const [aulas, setAulas] = useState<Aula[]>(initialAulas);
639
+ const [sessoes, setSessoes] = useState<Sessao[]>([]);
640
+ const [aulas, setAulas] = useState<Aula[]>([]);
607
641
 
608
642
  // Selection
609
643
  const [selectedAulas, setSelectedAulas] = useState<Set<string>>(new Set());
@@ -621,6 +655,12 @@ export default function EstruturaPage({
621
655
  const [targetSessaoId, setTargetSessaoId] = useState<string | null>(null);
622
656
  const [saving, setSaving] = useState(false);
623
657
  const [aulaSheetTab, setAulaSheetTab] = useState('dados');
658
+ const [autoSaveStatus, setAutoSaveStatus] = useState<
659
+ 'idle' | 'saving' | 'saved' | 'error'
660
+ >('idle');
661
+ const [sessionAutoSaveStatus, setSessionAutoSaveStatus] = useState<
662
+ 'idle' | 'saving' | 'saved' | 'error'
663
+ >('idle');
624
664
 
625
665
  // Dialogs
626
666
  const [deleteSessaoDialog, setDeleteSessaoDialog] = useState(false);
@@ -631,10 +671,24 @@ export default function EstruturaPage({
631
671
 
632
672
  // Recursos management in sheet
633
673
  const [sheetRecursos, setSheetRecursos] = useState<Recurso[]>([]);
674
+ const [sheetInstructorIds, setSheetInstructorIds] = useState<string[]>([]);
634
675
  const [transcricaoFile, setTranscricaoFile] = useState<string | null>(null);
676
+ const [videoUploadFile, setVideoUploadFile] = useState<string | null>(null);
677
+ const [availableInstructors, setAvailableInstructors] = useState<
678
+ InstructorOption[]
679
+ >([]);
680
+ const videoUploadInputRef = useRef<HTMLInputElement | null>(null);
635
681
 
636
682
  // Refs for shift-select
637
683
  const aulasOrderRef = useRef<string[]>([]);
684
+ const autoSaveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
685
+ const autoSaveBaselineRef = useRef<string>('');
686
+ const lastAutoSavedRef = useRef<string>('');
687
+ const sessionAutoSaveTimeoutRef = useRef<ReturnType<
688
+ typeof setTimeout
689
+ > | null>(null);
690
+ const sessionAutoSaveBaselineRef = useRef<string>('');
691
+ const lastSessionAutoSavedRef = useRef<string>('');
638
692
 
639
693
  // Translation functions
640
694
  const sessaoSchema = getSessaoSchema(t);
@@ -667,6 +721,28 @@ export default function EstruturaPage({
667
721
  });
668
722
 
669
723
  const watchTipo = aulaForm.watch('tipo');
724
+ const watchedSessaoValues = useWatch({
725
+ control: sessaoForm.control,
726
+ });
727
+ const watchedAulaValues = useWatch({
728
+ control: aulaForm.control,
729
+ });
730
+
731
+ const buildLessonPayload = useCallback(
732
+ (data: AulaFormType) => ({
733
+ ...data,
734
+ exameVinculado: data.exameVinculado
735
+ ? Number(data.exameVinculado)
736
+ : undefined,
737
+ recursos: sheetRecursos.map((recurso) => ({
738
+ nome: recurso.nome,
739
+ tipo: recurso.tipo,
740
+ publico: recurso.publico,
741
+ })),
742
+ instructorIds: sheetInstructorIds.map((value) => Number(value)),
743
+ }),
744
+ [sheetRecursos, sheetInstructorIds]
745
+ );
670
746
 
671
747
  // DnD sensors
672
748
  const sensors = useSensors(
@@ -674,10 +750,29 @@ export default function EstruturaPage({
674
750
  useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
675
751
  );
676
752
 
753
+ const loadStructure = useCallback(async () => {
754
+ setLoading(true);
755
+ try {
756
+ const { data } = await request<CourseStructureResponse>({
757
+ url: `/lms/courses/${courseId}/structure`,
758
+ method: 'GET',
759
+ });
760
+
761
+ setSessoes(data.sessoes ?? []);
762
+ setAulas(data.aulas ?? []);
763
+ setAvailableInstructors(data.instructors ?? []);
764
+ } catch {
765
+ toast.error(t('toasts.errorLoadingData'));
766
+ setSessoes([]);
767
+ setAulas([]);
768
+ } finally {
769
+ setLoading(false);
770
+ }
771
+ }, [courseId, request, t]);
772
+
677
773
  useEffect(() => {
678
- const t = setTimeout(() => setLoading(false), 700);
679
- return () => clearTimeout(t);
680
- }, []);
774
+ loadStructure();
775
+ }, [loadStructure]);
681
776
 
682
777
  // Keep a flat ordered list of aulas for shift-select
683
778
  useEffect(() => {
@@ -690,6 +785,18 @@ export default function EstruturaPage({
690
785
  aulasOrderRef.current = ordered;
691
786
  }, [sessoes, aulas]);
692
787
 
788
+ useEffect(() => {
789
+ return () => {
790
+ if (autoSaveTimeoutRef.current) {
791
+ clearTimeout(autoSaveTimeoutRef.current);
792
+ }
793
+
794
+ if (sessionAutoSaveTimeoutRef.current) {
795
+ clearTimeout(sessionAutoSaveTimeoutRef.current);
796
+ }
797
+ };
798
+ }, []);
799
+
693
800
  // ── Helpers ─────────────────────────────────────────────────────────────
694
801
 
695
802
  const getAulasBySessao = useCallback(
@@ -702,6 +809,14 @@ export default function EstruturaPage({
702
809
  [aulas]
703
810
  );
704
811
 
812
+ const mapSheetInstructors = useCallback((): AulaInstructor[] => {
813
+ return sheetInstructorIds.map((id, index) => ({
814
+ id,
815
+ role: index === 0 ? 'lead' : 'assistant',
816
+ nome: availableInstructors.find((item) => String(item.id) === id)?.name,
817
+ }));
818
+ }, [sheetInstructorIds, availableInstructors]);
819
+
705
820
  function isSessaoId(uid: UniqueIdentifier): boolean {
706
821
  return sessoes.some((s) => s.id === String(uid));
707
822
  }
@@ -710,6 +825,22 @@ export default function EstruturaPage({
710
825
  return aulas.some((a) => a.id === String(uid));
711
826
  }
712
827
 
828
+ function getNextCode(items: Array<{ codigo: string }>, prefix: 'S' | 'A') {
829
+ const regex = new RegExp(`^${prefix}(\\d+)$`, 'i');
830
+
831
+ const maxCodeNumber = items.reduce((maxValue, item) => {
832
+ const match = item.codigo?.trim().match(regex);
833
+ if (!match) return maxValue;
834
+
835
+ const numericCode = Number(match[1]);
836
+ if (!Number.isFinite(numericCode)) return maxValue;
837
+
838
+ return Math.max(maxValue, numericCode);
839
+ }, 0);
840
+
841
+ return `${prefix}${String(maxCodeNumber + 1).padStart(2, '0')}`;
842
+ }
843
+
713
844
  // ── Multi-select ────────────────────────────────────────────────────────
714
845
 
715
846
  function handleAulaClick(aulaId: string, e: React.MouseEvent) {
@@ -837,53 +968,169 @@ export default function EstruturaPage({
837
968
 
838
969
  function openCreateSessao() {
839
970
  setEditingSessao(null);
840
- sessaoForm.reset({ codigo: '', titulo: '', duracao: 30 });
971
+ setSessionAutoSaveStatus('idle');
972
+ sessionAutoSaveBaselineRef.current = '';
973
+ lastSessionAutoSavedRef.current = '';
974
+ const nextSessionCode = getNextCode(sessoes, 'S');
975
+ sessaoForm.reset({ codigo: nextSessionCode, titulo: '', duracao: 30 });
841
976
  setSessaoSheetOpen(true);
842
977
  }
843
978
 
844
979
  function openEditSessao(sessao: Sessao) {
980
+ if (sessionAutoSaveTimeoutRef.current) {
981
+ clearTimeout(sessionAutoSaveTimeoutRef.current);
982
+ sessionAutoSaveTimeoutRef.current = null;
983
+ }
984
+
845
985
  setEditingSessao(sessao);
986
+ setSessionAutoSaveStatus('idle');
846
987
  sessaoForm.reset({
847
988
  codigo: sessao.codigo,
848
989
  titulo: sessao.titulo,
849
990
  duracao: sessao.duracao,
850
991
  });
992
+
993
+ const initialPayload = {
994
+ codigo: sessao.codigo,
995
+ titulo: sessao.titulo,
996
+ duracao: sessao.duracao,
997
+ };
998
+
999
+ const initialKey = JSON.stringify(initialPayload);
1000
+ sessionAutoSaveBaselineRef.current = initialKey;
1001
+ lastSessionAutoSavedRef.current = initialKey;
851
1002
  setSessaoSheetOpen(true);
852
1003
  }
853
1004
 
1005
+ useEffect(() => {
1006
+ if (!sessaoSheetOpen || !editingSessao) return;
1007
+
1008
+ const parsed = sessaoSchema.safeParse(watchedSessaoValues);
1009
+ if (!parsed.success) return;
1010
+
1011
+ const values = parsed.data;
1012
+
1013
+ setSessoes((prev) => {
1014
+ let changed = false;
1015
+
1016
+ const next = prev.map((item) => {
1017
+ if (item.id !== editingSessao.id) return item;
1018
+
1019
+ const nextItem: Sessao = {
1020
+ ...item,
1021
+ codigo: values.codigo,
1022
+ titulo: values.titulo,
1023
+ duracao: values.duracao,
1024
+ };
1025
+
1026
+ const isEqual =
1027
+ item.codigo === nextItem.codigo &&
1028
+ item.titulo === nextItem.titulo &&
1029
+ item.duracao === nextItem.duracao;
1030
+
1031
+ if (isEqual) return item;
1032
+
1033
+ changed = true;
1034
+ return nextItem;
1035
+ });
1036
+
1037
+ return changed ? next : prev;
1038
+ });
1039
+ }, [editingSessao, sessaoSchema, sessaoSheetOpen, watchedSessaoValues]);
1040
+
1041
+ useEffect(() => {
1042
+ if (!sessaoSheetOpen || !editingSessao) return;
1043
+
1044
+ const parsed = sessaoSchema.safeParse(watchedSessaoValues);
1045
+ if (!parsed.success) {
1046
+ setSessionAutoSaveStatus('idle');
1047
+ return;
1048
+ }
1049
+
1050
+ const payload = parsed.data;
1051
+ const payloadKey = JSON.stringify(payload);
1052
+
1053
+ if (
1054
+ payloadKey === sessionAutoSaveBaselineRef.current ||
1055
+ payloadKey === lastSessionAutoSavedRef.current
1056
+ ) {
1057
+ return;
1058
+ }
1059
+
1060
+ if (sessionAutoSaveTimeoutRef.current) {
1061
+ clearTimeout(sessionAutoSaveTimeoutRef.current);
1062
+ }
1063
+
1064
+ sessionAutoSaveTimeoutRef.current = setTimeout(async () => {
1065
+ try {
1066
+ setSessionAutoSaveStatus('saving');
1067
+ await request({
1068
+ url: `/lms/courses/${courseId}/structure/sessions/${Number(editingSessao.id)}`,
1069
+ method: 'PATCH',
1070
+ data: payload,
1071
+ });
1072
+ lastSessionAutoSavedRef.current = payloadKey;
1073
+ setSessionAutoSaveStatus('saved');
1074
+ } catch {
1075
+ setSessionAutoSaveStatus('error');
1076
+ toast.error(t('toasts.sessionAutoSaveFailed'));
1077
+ }
1078
+ }, 800);
1079
+
1080
+ return () => {
1081
+ if (sessionAutoSaveTimeoutRef.current) {
1082
+ clearTimeout(sessionAutoSaveTimeoutRef.current);
1083
+ }
1084
+ };
1085
+ }, [
1086
+ courseId,
1087
+ editingSessao,
1088
+ request,
1089
+ sessaoSchema,
1090
+ sessaoSheetOpen,
1091
+ t,
1092
+ watchedSessaoValues,
1093
+ ]);
1094
+
854
1095
  async function onSubmitSessao(data: SessaoForm) {
855
1096
  setSaving(true);
856
- await new Promise((r) => setTimeout(r, 400));
857
- if (editingSessao) {
858
- setSessoes((prev) =>
859
- prev.map((s) => (s.id === editingSessao.id ? { ...s, ...data } : s))
860
- );
861
- toast.success(t('toasts.sessionUpdated'));
862
- } else {
863
- const newSessao: Sessao = {
864
- id: `s-${Date.now()}`,
865
- ...data,
866
- collapsed: false,
867
- };
868
- setSessoes((prev) => [...prev, newSessao]);
869
- toast.success(t('toasts.sessionCreated'));
1097
+ try {
1098
+ if (editingSessao) {
1099
+ await request({
1100
+ url: `/lms/courses/${courseId}/structure/sessions/${Number(editingSessao.id)}`,
1101
+ method: 'PATCH',
1102
+ data,
1103
+ });
1104
+ lastSessionAutoSavedRef.current = JSON.stringify(data);
1105
+ setSessionAutoSaveStatus('saved');
1106
+ toast.success(t('toasts.sessionUpdated'));
1107
+ } else {
1108
+ await request({
1109
+ url: `/lms/courses/${courseId}/structure/sessions`,
1110
+ method: 'POST',
1111
+ data,
1112
+ });
1113
+ toast.success(t('toasts.sessionCreated'));
1114
+ }
1115
+
1116
+ await loadStructure();
1117
+ setSessaoSheetOpen(false);
1118
+ setSessionAutoSaveStatus('idle');
1119
+ } finally {
1120
+ setSaving(false);
870
1121
  }
871
- setSaving(false);
872
- setSessaoSheetOpen(false);
873
1122
  }
874
1123
 
875
- function confirmDeleteSessao() {
1124
+ async function confirmDeleteSessao() {
876
1125
  if (sessaoToDelete) {
877
- setAulas((prev) => prev.filter((a) => a.sessaoId !== sessaoToDelete.id));
878
- setSessoes((prev) => prev.filter((s) => s.id !== sessaoToDelete.id));
879
- setSelectedAulas((prev) => {
880
- const next = new Set(prev);
881
- aulas
882
- .filter((a) => a.sessaoId === sessaoToDelete.id)
883
- .forEach((a) => next.delete(a.id));
884
- return next;
1126
+ await request({
1127
+ url: `/lms/courses/${courseId}/structure/sessions/${Number(sessaoToDelete.id)}`,
1128
+ method: 'DELETE',
885
1129
  });
1130
+
1131
+ await loadStructure();
886
1132
  toast.success(t('toasts.sessionDeleted'));
1133
+ setSelectedAulas(new Set());
887
1134
  setSessaoToDelete(null);
888
1135
  setDeleteSessaoDialog(false);
889
1136
  }
@@ -895,10 +1142,16 @@ export default function EstruturaPage({
895
1142
  setEditingAula(null);
896
1143
  setTargetSessaoId(sessaoId);
897
1144
  setAulaSheetTab('dados');
1145
+ setAutoSaveStatus('idle');
1146
+ autoSaveBaselineRef.current = '';
1147
+ lastAutoSavedRef.current = '';
898
1148
  setSheetRecursos([]);
1149
+ setSheetInstructorIds([]);
899
1150
  setTranscricaoFile(null);
1151
+ setVideoUploadFile(null);
1152
+ const nextLessonCode = getNextCode(getAulasBySessao(sessaoId), 'A');
900
1153
  aulaForm.reset({
901
- codigo: '',
1154
+ codigo: nextLessonCode,
902
1155
  titulo: '',
903
1156
  descricaoPublica: '',
904
1157
  descricaoPrivada: '',
@@ -914,11 +1167,19 @@ export default function EstruturaPage({
914
1167
  }
915
1168
 
916
1169
  function openEditAula(aula: Aula) {
1170
+ if (autoSaveTimeoutRef.current) {
1171
+ clearTimeout(autoSaveTimeoutRef.current);
1172
+ autoSaveTimeoutRef.current = null;
1173
+ }
1174
+
917
1175
  setEditingAula(aula);
918
1176
  setTargetSessaoId(aula.sessaoId);
919
1177
  setAulaSheetTab('dados');
1178
+ setAutoSaveStatus('idle');
920
1179
  setSheetRecursos([...aula.recursos]);
1180
+ setSheetInstructorIds((aula.instrutores ?? []).map((item) => item.id));
921
1181
  setTranscricaoFile(aula.transcricao ? 'transcricao.txt' : null);
1182
+ setVideoUploadFile(null);
922
1183
  aulaForm.reset({
923
1184
  codigo: aula.codigo,
924
1185
  titulo: aula.titulo,
@@ -932,68 +1193,196 @@ export default function EstruturaPage({
932
1193
  exameVinculado: aula.exameVinculado ?? '',
933
1194
  conteudoPost: aula.conteudoPost ?? '',
934
1195
  });
1196
+
1197
+ const initialPayload = {
1198
+ codigo: aula.codigo,
1199
+ titulo: aula.titulo,
1200
+ descricaoPublica: aula.descricaoPublica,
1201
+ descricaoPrivada: aula.descricaoPrivada,
1202
+ tipo: aula.tipo,
1203
+ duracao: aula.duracao,
1204
+ videoProvedor: aula.videoProvedor ?? 'youtube',
1205
+ videoUrl: aula.videoUrl ?? '',
1206
+ duracaoAutomatica: aula.duracaoAutomatica ?? false,
1207
+ exameVinculado: aula.exameVinculado
1208
+ ? Number(aula.exameVinculado)
1209
+ : undefined,
1210
+ conteudoPost: aula.conteudoPost ?? '',
1211
+ recursos: aula.recursos.map((recurso) => ({
1212
+ nome: recurso.nome,
1213
+ tipo: recurso.tipo,
1214
+ publico: recurso.publico,
1215
+ })),
1216
+ instructorIds: (aula.instrutores ?? []).map((item) => Number(item.id)),
1217
+ };
1218
+
1219
+ const initialKey = JSON.stringify(initialPayload);
1220
+ autoSaveBaselineRef.current = initialKey;
1221
+ lastAutoSavedRef.current = initialKey;
935
1222
  setAulaSheetOpen(true);
936
1223
  }
937
1224
 
1225
+ useEffect(() => {
1226
+ if (!aulaSheetOpen || !editingAula) return;
1227
+
1228
+ const parsed = aulaSchema.safeParse(watchedAulaValues);
1229
+ if (!parsed.success) return;
1230
+
1231
+ const values = parsed.data;
1232
+ const mappedInstructors = mapSheetInstructors();
1233
+
1234
+ setAulas((prev) => {
1235
+ let changed = false;
1236
+
1237
+ const next = prev.map((item) => {
1238
+ if (item.id !== editingAula.id) return item;
1239
+
1240
+ const nextItem: Aula = {
1241
+ ...item,
1242
+ codigo: values.codigo,
1243
+ titulo: values.titulo,
1244
+ descricaoPublica: values.descricaoPublica,
1245
+ descricaoPrivada: values.descricaoPrivada,
1246
+ tipo: values.tipo,
1247
+ duracao: values.duracao,
1248
+ videoProvedor: values.videoProvedor as VideoProvider | undefined,
1249
+ videoUrl: values.videoUrl,
1250
+ duracaoAutomatica: values.duracaoAutomatica,
1251
+ exameVinculado: values.exameVinculado,
1252
+ conteudoPost: values.conteudoPost,
1253
+ recursos: sheetRecursos,
1254
+ instrutores: mappedInstructors,
1255
+ };
1256
+
1257
+ const isEqual =
1258
+ item.codigo === nextItem.codigo &&
1259
+ item.titulo === nextItem.titulo &&
1260
+ item.descricaoPublica === nextItem.descricaoPublica &&
1261
+ item.descricaoPrivada === nextItem.descricaoPrivada &&
1262
+ item.tipo === nextItem.tipo &&
1263
+ item.duracao === nextItem.duracao &&
1264
+ item.videoProvedor === nextItem.videoProvedor &&
1265
+ item.videoUrl === nextItem.videoUrl &&
1266
+ item.duracaoAutomatica === nextItem.duracaoAutomatica &&
1267
+ item.exameVinculado === nextItem.exameVinculado &&
1268
+ item.conteudoPost === nextItem.conteudoPost &&
1269
+ JSON.stringify(item.recursos) === JSON.stringify(nextItem.recursos) &&
1270
+ JSON.stringify(item.instrutores ?? []) ===
1271
+ JSON.stringify(nextItem.instrutores ?? []);
1272
+
1273
+ if (isEqual) return item;
1274
+
1275
+ changed = true;
1276
+ return nextItem;
1277
+ });
1278
+
1279
+ return changed ? next : prev;
1280
+ });
1281
+ }, [
1282
+ aulaSchema,
1283
+ aulaSheetOpen,
1284
+ editingAula,
1285
+ mapSheetInstructors,
1286
+ sheetRecursos,
1287
+ watchedAulaValues,
1288
+ ]);
1289
+
1290
+ useEffect(() => {
1291
+ if (!aulaSheetOpen || !editingAula) return;
1292
+
1293
+ const parsed = aulaSchema.safeParse(watchedAulaValues);
1294
+ if (!parsed.success) {
1295
+ setAutoSaveStatus('idle');
1296
+ return;
1297
+ }
1298
+
1299
+ const payload = buildLessonPayload(parsed.data);
1300
+ const payloadKey = JSON.stringify(payload);
1301
+
1302
+ if (
1303
+ payloadKey === autoSaveBaselineRef.current ||
1304
+ payloadKey === lastAutoSavedRef.current
1305
+ ) {
1306
+ return;
1307
+ }
1308
+
1309
+ if (autoSaveTimeoutRef.current) {
1310
+ clearTimeout(autoSaveTimeoutRef.current);
1311
+ }
1312
+
1313
+ autoSaveTimeoutRef.current = setTimeout(async () => {
1314
+ try {
1315
+ setAutoSaveStatus('saving');
1316
+ await request({
1317
+ url: `/lms/courses/${courseId}/structure/sessions/${Number(editingAula.sessaoId)}/lessons/${Number(editingAula.id)}`,
1318
+ method: 'PATCH',
1319
+ data: payload,
1320
+ });
1321
+ lastAutoSavedRef.current = payloadKey;
1322
+ setAutoSaveStatus('saved');
1323
+ } catch {
1324
+ setAutoSaveStatus('error');
1325
+ toast.error(t('toasts.autoSaveFailed'));
1326
+ }
1327
+ }, 800);
1328
+
1329
+ return () => {
1330
+ if (autoSaveTimeoutRef.current) {
1331
+ clearTimeout(autoSaveTimeoutRef.current);
1332
+ }
1333
+ };
1334
+ }, [
1335
+ aulaSchema,
1336
+ aulaSheetOpen,
1337
+ buildLessonPayload,
1338
+ courseId,
1339
+ editingAula,
1340
+ request,
1341
+ t,
1342
+ watchedAulaValues,
1343
+ ]);
1344
+
938
1345
  async function onSubmitAula(data: AulaFormType) {
939
1346
  setSaving(true);
940
- await new Promise((r) => setTimeout(r, 400));
941
- if (editingAula) {
942
- setAulas((prev) =>
943
- prev.map((a) =>
944
- a.id === editingAula.id
945
- ? {
946
- ...a,
947
- codigo: data.codigo,
948
- titulo: data.titulo,
949
- descricaoPublica: data.descricaoPublica,
950
- descricaoPrivada: data.descricaoPrivada,
951
- tipo: data.tipo as AulaTipo,
952
- duracao: data.duracao,
953
- videoProvedor: data.videoProvedor as VideoProvider | undefined,
954
- videoUrl: data.videoUrl,
955
- duracaoAutomatica: data.duracaoAutomatica,
956
- exameVinculado: data.exameVinculado,
957
- conteudoPost: data.conteudoPost,
958
- recursos: sheetRecursos,
959
- }
960
- : a
961
- )
962
- );
963
- toast.success(t('toasts.lessonUpdated'));
964
- } else {
965
- const newAula: Aula = {
966
- id: `a-${Date.now()}`,
967
- codigo: data.codigo,
968
- titulo: data.titulo,
969
- descricaoPublica: data.descricaoPublica,
970
- descricaoPrivada: data.descricaoPrivada,
971
- tipo: data.tipo as AulaTipo,
972
- duracao: data.duracao,
973
- sessaoId: targetSessaoId!,
974
- videoProvedor: data.videoProvedor as VideoProvider | undefined,
975
- videoUrl: data.videoUrl,
976
- duracaoAutomatica: data.duracaoAutomatica,
977
- exameVinculado: data.exameVinculado,
978
- conteudoPost: data.conteudoPost,
979
- recursos: sheetRecursos,
980
- };
981
- setAulas((prev) => [...prev, newAula]);
982
- toast.success(t('toasts.lessonCreated'));
1347
+ const payload = buildLessonPayload(data);
1348
+
1349
+ try {
1350
+ if (editingAula) {
1351
+ await request({
1352
+ url: `/lms/courses/${courseId}/structure/sessions/${Number(editingAula.sessaoId)}/lessons/${Number(editingAula.id)}`,
1353
+ method: 'PATCH',
1354
+ data: payload,
1355
+ });
1356
+ lastAutoSavedRef.current = JSON.stringify(payload);
1357
+ setAutoSaveStatus('saved');
1358
+ toast.success(t('toasts.lessonUpdated'));
1359
+ } else {
1360
+ await request({
1361
+ url: `/lms/courses/${courseId}/structure/sessions/${Number(targetSessaoId)}/lessons`,
1362
+ method: 'POST',
1363
+ data: payload,
1364
+ });
1365
+ toast.success(t('toasts.lessonCreated'));
1366
+ }
1367
+
1368
+ await loadStructure();
1369
+ setAulaSheetOpen(false);
1370
+ setAutoSaveStatus('idle');
1371
+ } finally {
1372
+ setSaving(false);
983
1373
  }
984
- setSaving(false);
985
- setAulaSheetOpen(false);
986
1374
  }
987
1375
 
988
- function confirmDeleteAula() {
1376
+ async function confirmDeleteAula() {
989
1377
  if (aulaToDelete) {
990
- setAulas((prev) => prev.filter((a) => a.id !== aulaToDelete.id));
991
- setSelectedAulas((prev) => {
992
- const next = new Set(prev);
993
- next.delete(aulaToDelete.id);
994
- return next;
1378
+ await request({
1379
+ url: `/lms/courses/${courseId}/structure/sessions/${Number(aulaToDelete.sessaoId)}/lessons/${Number(aulaToDelete.id)}`,
1380
+ method: 'DELETE',
995
1381
  });
1382
+
1383
+ await loadStructure();
996
1384
  toast.success(t('toasts.lessonDeleted'));
1385
+ setSelectedAulas(new Set());
997
1386
  setAulaToDelete(null);
998
1387
  setDeleteAulaDialog(false);
999
1388
  }
@@ -1020,10 +1409,27 @@ export default function EstruturaPage({
1020
1409
  }
1021
1410
 
1022
1411
  function removeRecurso(recursoId: string) {
1412
+ const removed = sheetRecursos.find((r) => r.id === recursoId);
1413
+
1414
+ if (removed && videoUploadFile === removed.nome) {
1415
+ setVideoUploadFile(null);
1416
+ }
1417
+
1023
1418
  setSheetRecursos((prev) => prev.filter((r) => r.id !== recursoId));
1024
1419
  toast.success(t('toasts.resourceRemoved'));
1025
1420
  }
1026
1421
 
1422
+ function clearVideoUpload() {
1423
+ if (videoUploadFile) {
1424
+ setSheetRecursos((prev) =>
1425
+ prev.filter((resource) => resource.nome !== videoUploadFile)
1426
+ );
1427
+ }
1428
+
1429
+ setVideoUploadFile(null);
1430
+ toast.success(t('toasts.resourceRemoved'));
1431
+ }
1432
+
1027
1433
  // ── Bulk operations ───────────────────────────────────────────────────
1028
1434
 
1029
1435
  function confirmBulkDelete() {
@@ -1091,6 +1497,31 @@ export default function EstruturaPage({
1091
1497
  ? sessoes.find((s) => s.id === String(activeId))
1092
1498
  : null;
1093
1499
 
1500
+ const { locales } = useApp();
1501
+
1502
+ const handleNewOrder = (): void => {
1503
+ const nextLocaleData: Record<string, { name: string }> = {};
1504
+ locales.forEach((locale: Locale) => {
1505
+ nextLocaleData[locale.code] = {
1506
+ name: '',
1507
+ };
1508
+ });
1509
+ void nextLocaleData;
1510
+ toast.success(t('toasts.structureSaved'));
1511
+ };
1512
+
1513
+ const handleNewSection = (): void => {
1514
+ const nextLocaleData: Record<string, { name: string }> = {};
1515
+ locales.forEach((locale: Locale) => {
1516
+ nextLocaleData[locale.code] = {
1517
+ name: '',
1518
+ };
1519
+ });
1520
+ void nextLocaleData;
1521
+
1522
+ openCreateSessao();
1523
+ };
1524
+
1094
1525
  // ── Render ────────────────────────────────────────────────────────────
1095
1526
 
1096
1527
  return (
@@ -1117,22 +1548,20 @@ export default function EstruturaPage({
1117
1548
  label: t('breadcrumbs.structure'),
1118
1549
  },
1119
1550
  ]}
1120
- actions={
1121
- <div className="flex flex-wrap items-center gap-2">
1122
- <Button
1123
- variant="outline"
1124
- className="gap-2"
1125
- onClick={() => toast.success(t('toasts.structureSaved'))}
1126
- >
1127
- <Save className="size-4" />
1128
- {t('actions.saveOrder')}
1129
- </Button>
1130
- <Button className="gap-2" onClick={openCreateSessao}>
1131
- <Plus className="size-4" />
1132
- {t('actions.newSession')}
1133
- </Button>
1134
- </div>
1135
- }
1551
+ actions={[
1552
+ {
1553
+ label: t('actions.saveOrder'),
1554
+ icon: <Save className="size-4" />,
1555
+ onClick: () => handleNewOrder(),
1556
+ variant: 'default',
1557
+ },
1558
+ {
1559
+ label: t('actions.newSession'),
1560
+ icon: <Plus className="size-4" />,
1561
+ onClick: () => handleNewSection(),
1562
+ variant: 'default',
1563
+ },
1564
+ ]}
1136
1565
  />
1137
1566
 
1138
1567
  {/* ── Main ───────────────────────────────────────────────────────────── */}
@@ -1330,7 +1759,10 @@ export default function EstruturaPage({
1330
1759
  SHEET: SESSAO
1331
1760
  ═══════════════════════════════════════════════════════════════════════ */}
1332
1761
  <Sheet open={sessaoSheetOpen} onOpenChange={setSessaoSheetOpen}>
1333
- <SheetContent className="flex flex-col overflow-y-auto sm:max-w-md">
1762
+ <SheetContent
1763
+ side="right"
1764
+ className="flex w-full flex-col sm:max-w-lg overflow-y-auto"
1765
+ >
1334
1766
  <SheetHeader>
1335
1767
  <SheetTitle>
1336
1768
  {editingSessao
@@ -1403,6 +1835,16 @@ export default function EstruturaPage({
1403
1835
  </Field>
1404
1836
 
1405
1837
  <SheetFooter className="mt-auto p-0">
1838
+ {editingSessao && (
1839
+ <span className="mr-auto text-xs text-muted-foreground">
1840
+ {sessionAutoSaveStatus === 'saving' &&
1841
+ t('sessionForm.autoSaveSaving')}
1842
+ {sessionAutoSaveStatus === 'saved' &&
1843
+ t('sessionForm.autoSaveSaved')}
1844
+ {sessionAutoSaveStatus === 'error' &&
1845
+ t('sessionForm.autoSaveError')}
1846
+ </span>
1847
+ )}
1406
1848
  <Button type="submit" disabled={saving} className="gap-2">
1407
1849
  {saving && <Loader2 className="size-4 animate-spin" />}
1408
1850
  {editingSessao
@@ -1576,6 +2018,51 @@ export default function EstruturaPage({
1576
2018
  )}
1577
2019
  </Field>
1578
2020
 
2021
+ <Field>
2022
+ <FieldLabel>{t('lessonForm.lessonInstructors')}</FieldLabel>
2023
+ {availableInstructors.length === 0 ? (
2024
+ <FieldDescription>
2025
+ {t('lessonForm.noInstructors')}
2026
+ </FieldDescription>
2027
+ ) : (
2028
+ <div className="grid grid-cols-1 gap-2 rounded-lg border bg-muted/20 p-3 sm:grid-cols-2">
2029
+ {availableInstructors.map((instructor) => {
2030
+ const selected = sheetInstructorIds.includes(
2031
+ String(instructor.id)
2032
+ );
2033
+
2034
+ return (
2035
+ <label
2036
+ key={instructor.id}
2037
+ className="flex cursor-pointer items-center gap-2 rounded-md border bg-background px-3 py-2"
2038
+ >
2039
+ <Switch
2040
+ checked={selected}
2041
+ onCheckedChange={(checked) => {
2042
+ setSheetInstructorIds((prev) => {
2043
+ const idValue = String(instructor.id);
2044
+ if (checked) {
2045
+ if (prev.includes(idValue)) return prev;
2046
+ return [...prev, idValue];
2047
+ }
2048
+
2049
+ return prev.filter(
2050
+ (value) => value !== idValue
2051
+ );
2052
+ });
2053
+ }}
2054
+ />
2055
+ <span className="text-sm">{instructor.name}</span>
2056
+ </label>
2057
+ );
2058
+ })}
2059
+ </div>
2060
+ )}
2061
+ <FieldDescription>
2062
+ {t('lessonForm.leadInstructorHint')}
2063
+ </FieldDescription>
2064
+ </Field>
2065
+
1579
2066
  {/* ── Conditional: VIDEO ──────────────────────────── */}
1580
2067
  <AnimatePresence mode="wait">
1581
2068
  {watchTipo === 'video' && (
@@ -1874,7 +2361,61 @@ export default function EstruturaPage({
1874
2361
 
1875
2362
  {/* ── Tab: Transcricao ────────────────────────────────── */}
1876
2363
  <TabsContent value="transcricao" className="flex-1 mt-0">
1877
- <div className="flex flex-col gap-4 py-4">
2364
+ <div
2365
+ className={`flex flex-col gap-4 py-4 ${watchTipo === 'video' ? 'cursor-pointer' : ''}`}
2366
+ onClick={(event) => {
2367
+ if (watchTipo !== 'video') return;
2368
+
2369
+ const target = event.target as HTMLElement;
2370
+ if (
2371
+ target.closest(
2372
+ 'button, input, textarea, select, [role="button"], a, label'
2373
+ )
2374
+ ) {
2375
+ return;
2376
+ }
2377
+
2378
+ videoUploadInputRef.current?.click();
2379
+ }}
2380
+ >
2381
+ {watchTipo === 'video' && (
2382
+ <div className="rounded-lg border bg-muted/30 p-4">
2383
+ <Field>
2384
+ <FieldLabel>{t('lessonForm.videoUpload')}</FieldLabel>
2385
+ {videoUploadFile ? (
2386
+ <div className="flex items-center justify-between rounded-md border bg-background px-3 py-2">
2387
+ <div className="flex items-center gap-2">
2388
+ <Video className="size-4 text-muted-foreground" />
2389
+ <span className="text-sm">{videoUploadFile}</span>
2390
+ </div>
2391
+ <Button
2392
+ type="button"
2393
+ variant="ghost"
2394
+ size="icon"
2395
+ className="size-7"
2396
+ onClick={clearVideoUpload}
2397
+ >
2398
+ <X className="size-3.5" />
2399
+ </Button>
2400
+ </div>
2401
+ ) : (
2402
+ <div
2403
+ className="flex cursor-pointer flex-col items-center gap-2 rounded-lg border-2 border-dashed bg-background px-4 py-6 text-center transition-colors hover:border-foreground/30 hover:bg-muted/50"
2404
+ onClick={() => videoUploadInputRef.current?.click()}
2405
+ >
2406
+ <Upload className="size-6 text-muted-foreground" />
2407
+ <span className="text-sm text-muted-foreground">
2408
+ {t('lessonForm.videoUploadText')}
2409
+ </span>
2410
+ </div>
2411
+ )}
2412
+ <FieldDescription>
2413
+ {t('lessonForm.videoUploadHelp')}
2414
+ </FieldDescription>
2415
+ </Field>
2416
+ </div>
2417
+ )}
2418
+
1878
2419
  {editingAula?.transcricao ? (
1879
2420
  <>
1880
2421
  <div className="flex items-center justify-between">
@@ -1938,6 +2479,50 @@ export default function EstruturaPage({
1938
2479
  </div>
1939
2480
  </div>
1940
2481
  )}
2482
+
2483
+ {watchTipo === 'video' && (
2484
+ <input
2485
+ ref={videoUploadInputRef}
2486
+ type="file"
2487
+ accept="video/*"
2488
+ className="hidden"
2489
+ onChange={(e) => {
2490
+ const file = e.target.files?.[0];
2491
+ if (!file) return;
2492
+
2493
+ if (!file.type.startsWith('video/')) {
2494
+ toast.error(t('toasts.invalidVideoType'));
2495
+ e.target.value = '';
2496
+ return;
2497
+ }
2498
+
2499
+ const extension =
2500
+ file.name.split('.').pop()?.toLowerCase() ?? 'video';
2501
+
2502
+ setVideoUploadFile(file.name);
2503
+ setSheetRecursos((prev) => {
2504
+ const exists = prev.some(
2505
+ (resource) => resource.nome === file.name
2506
+ );
2507
+ if (exists) return prev;
2508
+
2509
+ return [
2510
+ ...prev,
2511
+ {
2512
+ id: `video-${Date.now()}`,
2513
+ nome: file.name,
2514
+ tamanho: `${(file.size / 1024 / 1024).toFixed(1)} MB`,
2515
+ tipo: extension,
2516
+ publico: true,
2517
+ },
2518
+ ];
2519
+ });
2520
+
2521
+ toast.success(t('toasts.videoUploaded'));
2522
+ e.target.value = '';
2523
+ }}
2524
+ />
2525
+ )}
1941
2526
  </div>
1942
2527
  </TabsContent>
1943
2528
 
@@ -1953,16 +2538,6 @@ export default function EstruturaPage({
1953
2538
  })}
1954
2539
  </span>
1955
2540
  </div>
1956
- <Button
1957
- type="button"
1958
- variant="outline"
1959
- size="sm"
1960
- className="gap-2"
1961
- onClick={addRecurso}
1962
- >
1963
- <Plus className="size-3.5" />
1964
- {t('lessonForm.upload')}
1965
- </Button>
1966
2541
  </div>
1967
2542
 
1968
2543
  {/* Upload zone */}
@@ -2065,6 +2640,13 @@ export default function EstruturaPage({
2065
2640
  </Tabs>
2066
2641
 
2067
2642
  <SheetFooter className="mt-auto border-t pt-4">
2643
+ {editingAula && (
2644
+ <span className="mr-auto text-xs text-muted-foreground">
2645
+ {autoSaveStatus === 'saving' && t('lessonForm.autoSaveSaving')}
2646
+ {autoSaveStatus === 'saved' && t('lessonForm.autoSaveSaved')}
2647
+ {autoSaveStatus === 'error' && t('lessonForm.autoSaveError')}
2648
+ </span>
2649
+ )}
2068
2650
  <Button
2069
2651
  type="submit"
2070
2652
  form="aula-form"
@@ -2134,7 +2716,7 @@ export default function EstruturaPage({
2134
2716
 
2135
2717
  {/* Delete Aula */}
2136
2718
  <Dialog open={deleteAulaDialog} onOpenChange={setDeleteAulaDialog}>
2137
- <DialogContent>
2719
+ <DialogContent className="max-w-3xl">
2138
2720
  <DialogHeader>
2139
2721
  <DialogTitle className="flex items-center gap-2">
2140
2722
  <AlertTriangle className="size-5 text-destructive" />