@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
@@ -0,0 +1,591 @@
1
+ 'use client';
2
+
3
+ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
4
+ import { Button } from '@/components/ui/button';
5
+ import {
6
+ Field,
7
+ FieldDescription,
8
+ FieldError,
9
+ FieldLabel,
10
+ } from '@/components/ui/field';
11
+ import { Input } from '@/components/ui/input';
12
+ import { Progress } from '@/components/ui/progress';
13
+ import {
14
+ Sheet,
15
+ SheetContent,
16
+ SheetDescription,
17
+ SheetFooter,
18
+ SheetHeader,
19
+ SheetTitle,
20
+ } from '@/components/ui/sheet';
21
+ import { Switch } from '@/components/ui/switch';
22
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
23
+ import { zodResolver } from '@hookform/resolvers/zod';
24
+ import { Loader2, Trash2, Upload } from 'lucide-react';
25
+ import { useEffect, useMemo, useRef, useState } from 'react';
26
+ import { Controller, useForm } from 'react-hook-form';
27
+ import { toast } from 'sonner';
28
+ import { z } from 'zod';
29
+
30
+ const createInstructorSchema = z.object({
31
+ nome: z.string().trim().min(3, 'Nome e obrigatorio'),
32
+ email: z
33
+ .string()
34
+ .trim()
35
+ .max(255)
36
+ .refine(
37
+ (value) => !value || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
38
+ 'E-mail invalido'
39
+ ),
40
+ telefone: z
41
+ .string()
42
+ .trim()
43
+ .max(50)
44
+ .refine(
45
+ (value) => !value || /^[0-9+()\s-]{8,20}$/.test(value),
46
+ 'Telefone invalido'
47
+ ),
48
+ qualificationSlugs: z
49
+ .array(z.string())
50
+ .min(1, 'Selecione pelo menos uma atuacao'),
51
+ });
52
+
53
+ type CreateInstructorForm = z.infer<typeof createInstructorSchema>;
54
+
55
+ type CreatedInstructor = {
56
+ id: number;
57
+ personId: number;
58
+ name: string;
59
+ avatarId?: number | null;
60
+ email?: string | null;
61
+ phone?: string | null;
62
+ qualificationSlugs: string[];
63
+ };
64
+
65
+ type InstructorDetails = CreatedInstructor & {
66
+ status?: 'active' | 'inactive';
67
+ };
68
+
69
+ type CreateLmsInstructorSheetProps = {
70
+ open: boolean;
71
+ onOpenChange: (open: boolean) => void;
72
+ onCreated?: (instructor: CreatedInstructor) => void | Promise<void>;
73
+ onSaved?: (instructor: CreatedInstructor) => void | Promise<void>;
74
+ instructorId?: number | null;
75
+ title: string;
76
+ description: string;
77
+ submitLabel: string;
78
+ successMessage: string;
79
+ errorMessage: string;
80
+ defaultQualificationSlugs?: string[];
81
+ };
82
+
83
+ type UploadedFilePayload = {
84
+ id?: number;
85
+ };
86
+
87
+ type OpenFilePayload = {
88
+ url?: string;
89
+ };
90
+
91
+ const QUALIFICATION_OPTIONS = [
92
+ {
93
+ slug: 'course-lessons',
94
+ label: 'Aulas de curso',
95
+ description: 'Pode atuar em aulas gravadas e estrutura de curso.',
96
+ },
97
+ {
98
+ slug: 'class-sessions',
99
+ label: 'Sessoes de turma',
100
+ description: 'Pode atuar em contextos ao vivo e turmas.',
101
+ },
102
+ ];
103
+
104
+ function getInstructorInitials(name?: string | null) {
105
+ return String(name || '')
106
+ .split(' ')
107
+ .filter(Boolean)
108
+ .slice(0, 2)
109
+ .map((part) => part[0]?.toUpperCase() || '')
110
+ .join('');
111
+ }
112
+
113
+ function getInstructorAvatarUrl(fileId?: number | null) {
114
+ return typeof fileId === 'number' && fileId > 0
115
+ ? `${process.env.NEXT_PUBLIC_API_BASE_URL}/person/avatar/${fileId}`
116
+ : '/placeholder.png';
117
+ }
118
+
119
+ export function CreateLmsInstructorSheet({
120
+ open,
121
+ onOpenChange,
122
+ onCreated,
123
+ onSaved,
124
+ instructorId,
125
+ title,
126
+ description,
127
+ submitLabel,
128
+ successMessage,
129
+ errorMessage,
130
+ defaultQualificationSlugs,
131
+ }: CreateLmsInstructorSheetProps) {
132
+ const { request } = useApp();
133
+ const [saving, setSaving] = useState(false);
134
+ const [avatarId, setAvatarId] = useState<number | null>(null);
135
+ const [persistedAvatarId, setPersistedAvatarId] = useState<number | null>(
136
+ null
137
+ );
138
+ const [avatarPreviewUrl, setAvatarPreviewUrl] =
139
+ useState<string>('/placeholder.png');
140
+ const [isUploadingAvatar, setIsUploadingAvatar] = useState(false);
141
+ const [avatarUploadProgress, setAvatarUploadProgress] = useState(0);
142
+ const fileInputRef = useRef<HTMLInputElement>(null);
143
+ const isEditing = typeof instructorId === 'number' && instructorId > 0;
144
+
145
+ const initialQualificationSlugs = useMemo(
146
+ () =>
147
+ defaultQualificationSlugs && defaultQualificationSlugs.length > 0
148
+ ? [...new Set(defaultQualificationSlugs)]
149
+ : ['course-lessons'],
150
+ [defaultQualificationSlugs]
151
+ );
152
+
153
+ const form = useForm<CreateInstructorForm>({
154
+ resolver: zodResolver(createInstructorSchema),
155
+ defaultValues: {
156
+ nome: '',
157
+ email: '',
158
+ telefone: '',
159
+ qualificationSlugs: initialQualificationSlugs,
160
+ },
161
+ });
162
+
163
+ const {
164
+ data: instructorDetails,
165
+ isLoading: loadingInstructorDetails,
166
+ refetch: refetchInstructorDetails,
167
+ } = useQuery<InstructorDetails>({
168
+ queryKey: ['lms-instructor-sheet', instructorId],
169
+ enabled: open && isEditing,
170
+ queryFn: async () => {
171
+ const response = await request<InstructorDetails>({
172
+ url: `/lms/instructors/${instructorId}`,
173
+ method: 'GET',
174
+ });
175
+
176
+ return response.data;
177
+ },
178
+ });
179
+
180
+ useEffect(() => {
181
+ if (!open || !isEditing) {
182
+ return;
183
+ }
184
+
185
+ void refetchInstructorDetails();
186
+ }, [isEditing, open, refetchInstructorDetails]);
187
+
188
+ useEffect(() => {
189
+ if (!open) {
190
+ return;
191
+ }
192
+
193
+ const instructor = isEditing ? instructorDetails : undefined;
194
+
195
+ if (isEditing && !instructor) {
196
+ return;
197
+ }
198
+
199
+ const instructorQualificationSlugs: string[] = Array.isArray(
200
+ instructor?.qualificationSlugs
201
+ )
202
+ ? instructor.qualificationSlugs
203
+ : [];
204
+
205
+ form.reset({
206
+ nome: instructor?.name || '',
207
+ email: instructor?.email || '',
208
+ telefone: instructor?.phone || '',
209
+ qualificationSlugs:
210
+ instructorQualificationSlugs.length > 0
211
+ ? instructorQualificationSlugs
212
+ : initialQualificationSlugs,
213
+ });
214
+
215
+ const nextAvatarId = instructor?.avatarId ?? null;
216
+ setAvatarId(nextAvatarId);
217
+ setPersistedAvatarId(nextAvatarId);
218
+ setAvatarPreviewUrl(getInstructorAvatarUrl(nextAvatarId));
219
+ setIsUploadingAvatar(false);
220
+ setAvatarUploadProgress(0);
221
+ }, [form, initialQualificationSlugs, instructorDetails, isEditing, open]);
222
+
223
+ const deleteFileById = async (fileId?: number | null) => {
224
+ if (!fileId || fileId <= 0) return;
225
+
226
+ try {
227
+ await request({
228
+ url: '/file',
229
+ method: 'DELETE',
230
+ data: { ids: [fileId] },
231
+ });
232
+ } catch {
233
+ // Ignore cleanup failures to avoid interrupting the sheet flow.
234
+ }
235
+ };
236
+
237
+ const cleanupUnsavedAvatar = async () => {
238
+ if (avatarId && avatarId !== persistedAvatarId) {
239
+ await deleteFileById(avatarId);
240
+ }
241
+ };
242
+
243
+ const handleAvatarUpload = async (file: File) => {
244
+ if (!file.type.startsWith('image/')) {
245
+ toast.error('Selecione um arquivo de imagem valido.');
246
+ return;
247
+ }
248
+
249
+ if (file.size > 5 * 1024 * 1024) {
250
+ toast.error('A imagem deve ter no maximo 5MB.');
251
+ return;
252
+ }
253
+
254
+ setIsUploadingAvatar(true);
255
+ setAvatarUploadProgress(0);
256
+
257
+ try {
258
+ const formData = new FormData();
259
+ formData.append('file', file);
260
+ formData.append('destination', 'contact/person/avatar');
261
+
262
+ const previousAvatarId = avatarId;
263
+
264
+ const { data } = await request<UploadedFilePayload>({
265
+ url: '/file',
266
+ method: 'POST',
267
+ data: formData,
268
+ headers: {
269
+ 'Content-Type': 'multipart/form-data',
270
+ },
271
+ onUploadProgress: (event) => {
272
+ if (!event.total) return;
273
+ setAvatarUploadProgress(
274
+ Math.round((event.loaded * 100) / event.total)
275
+ );
276
+ },
277
+ });
278
+
279
+ const nextAvatarId = Number(data?.id);
280
+ if (!nextAvatarId) {
281
+ throw new Error('invalid avatar id');
282
+ }
283
+
284
+ const openResponse = await request<OpenFilePayload>({
285
+ url: `/file/open/${nextAvatarId}`,
286
+ method: 'PUT',
287
+ });
288
+
289
+ const openUrl = String(openResponse?.data?.url || '').trim();
290
+
291
+ if (previousAvatarId && previousAvatarId !== persistedAvatarId) {
292
+ await deleteFileById(previousAvatarId);
293
+ }
294
+
295
+ setAvatarId(nextAvatarId);
296
+ setAvatarPreviewUrl(
297
+ openUrl.length > 0
298
+ ? /^https?:\/\//i.test(openUrl)
299
+ ? openUrl
300
+ : `${String(process.env.NEXT_PUBLIC_API_BASE_URL || '')}${openUrl}`
301
+ : getInstructorAvatarUrl(nextAvatarId)
302
+ );
303
+ setAvatarUploadProgress(100);
304
+ toast.success('Foto enviada com sucesso.');
305
+ } catch {
306
+ toast.error('Nao foi possivel enviar a foto.');
307
+ setAvatarUploadProgress(0);
308
+ } finally {
309
+ setIsUploadingAvatar(false);
310
+ if (fileInputRef.current) {
311
+ fileInputRef.current.value = '';
312
+ }
313
+ }
314
+ };
315
+
316
+ const handleRemoveAvatar = async () => {
317
+ if (isUploadingAvatar) return;
318
+
319
+ if (avatarId && avatarId !== persistedAvatarId) {
320
+ await deleteFileById(avatarId);
321
+ }
322
+
323
+ setAvatarId(null);
324
+ setAvatarPreviewUrl('/placeholder.png');
325
+ setAvatarUploadProgress(0);
326
+ toast.success('Foto removida com sucesso.');
327
+ };
328
+
329
+ const handleSubmit = form.handleSubmit(async (values) => {
330
+ setSaving(true);
331
+ try {
332
+ const response = await request<CreatedInstructor>({
333
+ url: isEditing
334
+ ? `/lms/instructors/${instructorId}`
335
+ : '/lms/instructors',
336
+ method: isEditing ? 'PATCH' : 'POST',
337
+ data: {
338
+ name: values.nome.trim(),
339
+ email: values.email.trim() || undefined,
340
+ phone: values.telefone.trim() || undefined,
341
+ avatarId,
342
+ qualificationSlugs: values.qualificationSlugs,
343
+ status: 'active',
344
+ },
345
+ });
346
+
347
+ const savedInstructor = response.data;
348
+
349
+ if (!savedInstructor?.id || !savedInstructor?.name) {
350
+ throw new Error('invalid instructor payload');
351
+ }
352
+
353
+ const savedAvatarId =
354
+ typeof savedInstructor.avatarId === 'number'
355
+ ? savedInstructor.avatarId
356
+ : null;
357
+
358
+ form.reset({
359
+ nome: savedInstructor.name || values.nome.trim(),
360
+ email: savedInstructor.email || values.email.trim(),
361
+ telefone: savedInstructor.phone || values.telefone.trim(),
362
+ qualificationSlugs:
363
+ savedInstructor.qualificationSlugs?.length > 0
364
+ ? savedInstructor.qualificationSlugs
365
+ : values.qualificationSlugs,
366
+ });
367
+
368
+ setAvatarId(savedAvatarId);
369
+ setPersistedAvatarId(savedAvatarId);
370
+ setAvatarPreviewUrl(getInstructorAvatarUrl(savedAvatarId));
371
+
372
+ if (!isEditing && onCreated) {
373
+ await onCreated(savedInstructor);
374
+ }
375
+
376
+ if (onSaved) {
377
+ await onSaved(savedInstructor);
378
+ }
379
+
380
+ onOpenChange(false);
381
+ toast.success(successMessage);
382
+ } catch {
383
+ toast.error(errorMessage);
384
+ } finally {
385
+ setSaving(false);
386
+ }
387
+ });
388
+
389
+ const handleSheetOpenChange = (nextOpen: boolean) => {
390
+ if (nextOpen) {
391
+ onOpenChange(true);
392
+ return;
393
+ }
394
+
395
+ if (saving || isUploadingAvatar) {
396
+ return;
397
+ }
398
+
399
+ void cleanupUnsavedAvatar();
400
+ onOpenChange(false);
401
+ };
402
+
403
+ return (
404
+ <Sheet open={open} onOpenChange={handleSheetOpenChange}>
405
+ <SheetContent
406
+ side="right"
407
+ className="flex w-full flex-col sm:max-w-lg overflow-y-auto"
408
+ >
409
+ <SheetHeader>
410
+ <SheetTitle>{title}</SheetTitle>
411
+ <SheetDescription>{description}</SheetDescription>
412
+ </SheetHeader>
413
+
414
+ {isEditing && loadingInstructorDetails ? (
415
+ <div className="flex items-center gap-2 px-4 py-10 text-sm text-muted-foreground">
416
+ <Loader2 className="size-4 animate-spin" />
417
+ Carregando dados do instrutor...
418
+ </div>
419
+ ) : (
420
+ <form onSubmit={handleSubmit} className="mt-6 space-y-4 px-4">
421
+ <Field>
422
+ <FieldLabel>Foto</FieldLabel>
423
+ <FieldDescription>
424
+ JPG, PNG ou GIF. Tamanho maximo de 5MB.
425
+ </FieldDescription>
426
+ <div className="flex items-center gap-4 rounded-lg border p-4">
427
+ <Avatar className="h-22 w-22 rounded-full border">
428
+ <AvatarImage
429
+ src={avatarPreviewUrl}
430
+ alt={form.watch('nome') || 'Instrutor'}
431
+ />
432
+ <AvatarFallback className="rounded-md text-sm font-semibold uppercase">
433
+ {getInstructorInitials(form.watch('nome') || 'Instrutor')}
434
+ </AvatarFallback>
435
+ </Avatar>
436
+
437
+ <div className="flex-1 space-y-2">
438
+ <input
439
+ ref={fileInputRef}
440
+ type="file"
441
+ accept="image/*"
442
+ className="hidden"
443
+ onChange={(event) => {
444
+ const file = event.target.files?.[0];
445
+ if (file) {
446
+ void handleAvatarUpload(file);
447
+ }
448
+ }}
449
+ />
450
+
451
+ <div className="flex flex-wrap gap-2">
452
+ <Button
453
+ type="button"
454
+ variant="outline"
455
+ onClick={() => fileInputRef.current?.click()}
456
+ disabled={isUploadingAvatar}
457
+ >
458
+ {isUploadingAvatar ? (
459
+ <Loader2 className="mr-2 size-4 animate-spin" />
460
+ ) : (
461
+ <Upload className="mr-2 size-4" />
462
+ )}
463
+ {avatarId ? 'Trocar foto' : 'Enviar foto'}
464
+ </Button>
465
+
466
+ {(avatarId || isUploadingAvatar) && (
467
+ <Button
468
+ type="button"
469
+ variant="ghost"
470
+ onClick={() => void handleRemoveAvatar()}
471
+ disabled={!avatarId || isUploadingAvatar}
472
+ >
473
+ <Trash2 className="mr-2 size-4" />
474
+ Remover
475
+ </Button>
476
+ )}
477
+ </div>
478
+
479
+ {isUploadingAvatar && (
480
+ <div className="space-y-1">
481
+ <Progress value={avatarUploadProgress} className="h-2" />
482
+ <p className="text-xs text-muted-foreground">
483
+ Enviando foto... {avatarUploadProgress}%
484
+ </p>
485
+ </div>
486
+ )}
487
+ </div>
488
+ </div>
489
+ </Field>
490
+
491
+ <Field>
492
+ <FieldLabel htmlFor="newInstructorName">Nome</FieldLabel>
493
+ <Input
494
+ id="newInstructorName"
495
+ placeholder="Nome completo"
496
+ {...form.register('nome')}
497
+ />
498
+ <FieldError>{form.formState.errors.nome?.message}</FieldError>
499
+ </Field>
500
+
501
+ <Field>
502
+ <FieldLabel htmlFor="newInstructorEmail">Email</FieldLabel>
503
+ <Input
504
+ id="newInstructorEmail"
505
+ type="email"
506
+ placeholder="nome@exemplo.com"
507
+ {...form.register('email')}
508
+ />
509
+ <FieldError>{form.formState.errors.email?.message}</FieldError>
510
+ </Field>
511
+
512
+ <Field>
513
+ <FieldLabel htmlFor="newInstructorPhone">Telefone</FieldLabel>
514
+ <Input
515
+ id="newInstructorPhone"
516
+ placeholder="+55 (11) 90000-0000"
517
+ {...form.register('telefone')}
518
+ />
519
+ <FieldError>{form.formState.errors.telefone?.message}</FieldError>
520
+ </Field>
521
+
522
+ <Field>
523
+ <FieldLabel>Atuacoes</FieldLabel>
524
+ <FieldDescription>
525
+ Selecione em quais contextos esse instrutor pode atuar.
526
+ </FieldDescription>
527
+ <Controller
528
+ control={form.control}
529
+ name="qualificationSlugs"
530
+ render={({ field }) => (
531
+ <div className="space-y-2 rounded-lg border p-3">
532
+ {QUALIFICATION_OPTIONS.map((option) => {
533
+ const checked = field.value.includes(option.slug);
534
+
535
+ return (
536
+ <label
537
+ key={option.slug}
538
+ className="flex items-start justify-between gap-3 rounded-md border px-3 py-2"
539
+ >
540
+ <div className="space-y-1">
541
+ <p className="text-sm font-medium">
542
+ {option.label}
543
+ </p>
544
+ <p className="text-xs text-muted-foreground">
545
+ {option.description}
546
+ </p>
547
+ </div>
548
+ <Switch
549
+ checked={checked}
550
+ onCheckedChange={(nextChecked) => {
551
+ if (nextChecked) {
552
+ field.onChange([...field.value, option.slug]);
553
+ return;
554
+ }
555
+
556
+ field.onChange(
557
+ field.value.filter(
558
+ (value) => value !== option.slug
559
+ )
560
+ );
561
+ }}
562
+ />
563
+ </label>
564
+ );
565
+ })}
566
+ </div>
567
+ )}
568
+ />
569
+ <FieldError>
570
+ {form.formState.errors.qualificationSlugs?.message}
571
+ </FieldError>
572
+ </Field>
573
+
574
+ <SheetFooter className="mt-6 px-0">
575
+ <Button
576
+ type="submit"
577
+ disabled={saving || isUploadingAvatar}
578
+ className="gap-2"
579
+ >
580
+ {(saving || isUploadingAvatar) && (
581
+ <Loader2 className="mr-2 size-4 animate-spin" />
582
+ )}
583
+ {submitLabel}
584
+ </Button>
585
+ </SheetFooter>
586
+ </form>
587
+ )}
588
+ </SheetContent>
589
+ </Sheet>
590
+ );
591
+ }