@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,1027 @@
1
+ 'use client';
2
+
3
+ import { Button } from '@/components/ui/button';
4
+ import { Calendar as DateCalendar } from '@/components/ui/calendar';
5
+ import { Checkbox } from '@/components/ui/checkbox';
6
+ import { Input } from '@/components/ui/input';
7
+ import {
8
+ PopoverContent,
9
+ PopoverTrigger,
10
+ Popover as UiPopover,
11
+ } from '@/components/ui/popover';
12
+ import {
13
+ Select,
14
+ SelectContent,
15
+ SelectItem,
16
+ SelectTrigger,
17
+ SelectValue,
18
+ } from '@/components/ui/select';
19
+ import { Switch } from '@/components/ui/switch';
20
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
21
+ import { cn } from '@/lib/utils';
22
+ import { useApp } from '@hed-hog/next-app-provider';
23
+ import { zodResolver } from '@hookform/resolvers/zod';
24
+ import { format } from 'date-fns';
25
+ import { enUS, ptBR } from 'date-fns/locale';
26
+ import {
27
+ CalendarIcon,
28
+ CalendarPlus,
29
+ Clock3,
30
+ Loader2,
31
+ Search,
32
+ X,
33
+ } from 'lucide-react';
34
+ import { useLocale, useTranslations } from 'next-intl';
35
+ import { useCallback, useEffect, useRef, useState } from 'react';
36
+ import { createPortal } from 'react-dom';
37
+ import { useForm, useWatch } from 'react-hook-form';
38
+ import { toast } from 'sonner';
39
+ import { z } from 'zod';
40
+
41
+ // ── Types ─────────────────────────────────────────────────────────────────────
42
+
43
+ interface Aula {
44
+ id: number;
45
+ titulo: string;
46
+ data: string;
47
+ horaInicio: string;
48
+ horaFim: string;
49
+ local: string;
50
+ meetingUrl: string;
51
+ tipo: 'presencial' | 'online';
52
+ status: string;
53
+ instructorId?: number | null;
54
+ instructorName?: string;
55
+ recurrence?: {
56
+ frequency: 'daily' | 'weekly' | 'monthly' | 'yearly';
57
+ interval: number;
58
+ until: string;
59
+ daysOfWeek?: Array<'MO' | 'TU' | 'WE' | 'TH' | 'FR' | 'SA' | 'SU'>;
60
+ } | null;
61
+ recurrenceId?: string | null;
62
+ occurrenceIndex?: number | null;
63
+ isException?: boolean;
64
+ isRecurring?: boolean;
65
+ cor?: string | null;
66
+ color?: string | null;
67
+ }
68
+
69
+ type SessionRecurrenceFrequency =
70
+ | 'none'
71
+ | 'daily'
72
+ | 'weekly'
73
+ | 'monthly'
74
+ | 'yearly';
75
+
76
+ type SessionRecurrenceDay = 'MO' | 'TU' | 'WE' | 'TH' | 'FR' | 'SA' | 'SU';
77
+
78
+ interface QuickCreateSlot {
79
+ date: string; // YYYY-MM-DD
80
+ startTime: string; // HH:MM
81
+ endTime: string; // HH:MM
82
+ }
83
+
84
+ interface QuickCreateAttendanceItem {
85
+ alunoId: number;
86
+ selecionado: boolean;
87
+ presente: boolean;
88
+ }
89
+
90
+ interface QuickCreateStudent {
91
+ id: number;
92
+ nome: string;
93
+ }
94
+
95
+ interface QuickCreateSessionPopoverProps {
96
+ open: boolean;
97
+ onOpenChange: (open: boolean) => void;
98
+ slot: QuickCreateSlot | null;
99
+ anchor: { x: number; y: number } | null;
100
+ classId: string;
101
+ students: QuickCreateStudent[];
102
+ onSaved: (aula: Aula) => void;
103
+ onMoreOptions: (values: {
104
+ titulo: string;
105
+ data: string;
106
+ horaInicio: string;
107
+ horaFim: string;
108
+ cor: string;
109
+ recurrenceFrequency?: SessionRecurrenceFrequency;
110
+ recurrenceUntil?: string;
111
+ recurrenceDaysOfWeek?: SessionRecurrenceDay[];
112
+ attendance: QuickCreateAttendanceItem[];
113
+ initialTab: 'aulas' | 'chamada';
114
+ }) => void;
115
+ }
116
+
117
+ // ── Schema ────────────────────────────────────────────────────────────────────
118
+
119
+ const SESSION_RECURRENCE_FREQUENCIES = [
120
+ 'none',
121
+ 'daily',
122
+ 'weekly',
123
+ 'monthly',
124
+ 'yearly',
125
+ ] as const;
126
+
127
+ const SESSION_RECURRENCE_DAYS = [
128
+ 'MO',
129
+ 'TU',
130
+ 'WE',
131
+ 'TH',
132
+ 'FR',
133
+ 'SA',
134
+ 'SU',
135
+ ] as const;
136
+
137
+ const SESSION_COLOR_PALETTE = [
138
+ '#3b82f6',
139
+ '#22c55e',
140
+ '#f59e0b',
141
+ '#ef4444',
142
+ '#8b5cf6',
143
+ '#06b6d4',
144
+ '#f97316',
145
+ '#64748b',
146
+ ] as const;
147
+
148
+ const SESSION_DEFAULT_COLOR = SESSION_COLOR_PALETTE[0];
149
+
150
+ const quickCreateSchema = z
151
+ .object({
152
+ titulo: z.string().min(1, 'Título é obrigatório').max(255),
153
+ data: z.string().min(1, 'Data é obrigatória'),
154
+ horaInicio: z
155
+ .string()
156
+ .regex(/^([01]\d|2[0-3]):([0-5]\d)$/, 'Hora inválida'),
157
+ horaFim: z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/, 'Hora inválida'),
158
+ cor: z
159
+ .string()
160
+ .regex(/^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/, 'Cor inválida')
161
+ .default(SESSION_DEFAULT_COLOR),
162
+ recurrenceFrequency: z.enum(SESSION_RECURRENCE_FREQUENCIES).default('none'),
163
+ recurrenceUntil: z.string().optional(),
164
+ recurrenceDaysOfWeek: z.array(z.enum(SESSION_RECURRENCE_DAYS)).default([]),
165
+ })
166
+ .superRefine((values, context) => {
167
+ if (values.recurrenceFrequency === 'none') {
168
+ return;
169
+ }
170
+
171
+ if (!values.recurrenceUntil) {
172
+ context.addIssue({
173
+ code: z.ZodIssueCode.custom,
174
+ message: 'Data final é obrigatória',
175
+ path: ['recurrenceUntil'],
176
+ });
177
+ return;
178
+ }
179
+
180
+ if (values.recurrenceUntil < values.data) {
181
+ context.addIssue({
182
+ code: z.ZodIssueCode.custom,
183
+ message: 'A data final deve ser igual ou posterior à data inicial',
184
+ path: ['recurrenceUntil'],
185
+ });
186
+ }
187
+ });
188
+
189
+ type QuickCreateForm = z.infer<typeof quickCreateSchema>;
190
+
191
+ const PANEL_W = 360;
192
+ const PANEL_H = 520;
193
+ const TIME_HOURS = Array.from({ length: 24 }, (_, index) =>
194
+ String(index).padStart(2, '0')
195
+ );
196
+ const TIME_MINUTES = Array.from({ length: 60 }, (_, index) =>
197
+ String(index).padStart(2, '0')
198
+ );
199
+
200
+ const RECURRENCE_DAY_LABELS: Array<{
201
+ value: SessionRecurrenceDay;
202
+ labelKey: string;
203
+ }> = [
204
+ { value: 'MO', labelKey: 'quickCreate.recurrence.days.monday' },
205
+ { value: 'TU', labelKey: 'quickCreate.recurrence.days.tuesday' },
206
+ { value: 'WE', labelKey: 'quickCreate.recurrence.days.wednesday' },
207
+ { value: 'TH', labelKey: 'quickCreate.recurrence.days.thursday' },
208
+ { value: 'FR', labelKey: 'quickCreate.recurrence.days.friday' },
209
+ { value: 'SA', labelKey: 'quickCreate.recurrence.days.saturday' },
210
+ { value: 'SU', labelKey: 'quickCreate.recurrence.days.sunday' },
211
+ ];
212
+
213
+ function parseTimeParts(value?: string) {
214
+ const [hour = '00', minute = '00'] = (value ?? '00:00').split(':');
215
+
216
+ return {
217
+ hour: /^\d{2}$/.test(hour) ? hour : '00',
218
+ minute: /^\d{2}$/.test(minute) ? minute : '00',
219
+ };
220
+ }
221
+
222
+ function buildTimeValue(hour: string, minute: string) {
223
+ return `${hour}:${minute}`;
224
+ }
225
+
226
+ function getDayCodeFromDate(value: string): SessionRecurrenceDay {
227
+ const day = new Date(`${value}T12:00:00`).getDay();
228
+ return ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'][
229
+ day
230
+ ] as SessionRecurrenceDay;
231
+ }
232
+
233
+ function buildRecurrencePayload(values: QuickCreateForm) {
234
+ if (values.recurrenceFrequency === 'none') {
235
+ return undefined;
236
+ }
237
+
238
+ return {
239
+ frequency: values.recurrenceFrequency,
240
+ interval: 1,
241
+ until: values.recurrenceUntil,
242
+ ...(values.recurrenceFrequency === 'weekly'
243
+ ? {
244
+ daysOfWeek:
245
+ values.recurrenceDaysOfWeek.length > 0
246
+ ? values.recurrenceDaysOfWeek
247
+ : [getDayCodeFromDate(values.data)],
248
+ }
249
+ : {}),
250
+ };
251
+ }
252
+
253
+ function TimePickerField({
254
+ value,
255
+ onChange,
256
+ }: {
257
+ value?: string;
258
+ onChange: (value: string) => void;
259
+ }) {
260
+ const [open, setOpen] = useState(false);
261
+ const { hour, minute } = parseTimeParts(value);
262
+
263
+ return (
264
+ <UiPopover open={open} onOpenChange={setOpen}>
265
+ <PopoverTrigger asChild>
266
+ <Button
267
+ type="button"
268
+ variant="outline"
269
+ className="w-full justify-start text-left font-normal"
270
+ >
271
+ <Clock3 className="mr-2 size-4" />
272
+ {buildTimeValue(hour, minute)}
273
+ </Button>
274
+ </PopoverTrigger>
275
+ <PopoverContent className="w-55 p-3" align="start">
276
+ <div className="grid grid-cols-2 gap-2">
277
+ <Select
278
+ value={hour}
279
+ onValueChange={(nextHour) =>
280
+ onChange(buildTimeValue(nextHour, minute))
281
+ }
282
+ >
283
+ <SelectTrigger className="h-9 text-sm">
284
+ <SelectValue placeholder="00" />
285
+ </SelectTrigger>
286
+ <SelectContent className="max-h-64">
287
+ {TIME_HOURS.map((timeHour) => (
288
+ <SelectItem key={timeHour} value={timeHour}>
289
+ {timeHour}
290
+ </SelectItem>
291
+ ))}
292
+ </SelectContent>
293
+ </Select>
294
+ <Select
295
+ value={minute}
296
+ onValueChange={(nextMinute) => {
297
+ onChange(buildTimeValue(hour, nextMinute));
298
+ setOpen(false);
299
+ }}
300
+ >
301
+ <SelectTrigger className="h-9 text-sm">
302
+ <SelectValue placeholder="00" />
303
+ </SelectTrigger>
304
+ <SelectContent className="max-h-64">
305
+ {TIME_MINUTES.map((timeMinute) => (
306
+ <SelectItem key={timeMinute} value={timeMinute}>
307
+ {timeMinute}
308
+ </SelectItem>
309
+ ))}
310
+ </SelectContent>
311
+ </Select>
312
+ </div>
313
+ </PopoverContent>
314
+ </UiPopover>
315
+ );
316
+ }
317
+
318
+ function computePosition(anchor: { x: number; y: number }) {
319
+ const GAP = 10;
320
+ const MARGIN = 12;
321
+ const vw = typeof window !== 'undefined' ? window.innerWidth : 1024;
322
+ const vh = typeof window !== 'undefined' ? window.innerHeight : 768;
323
+
324
+ let left = anchor.x + GAP;
325
+ let top = anchor.y + GAP;
326
+
327
+ if (left + PANEL_W > vw - MARGIN) {
328
+ left = anchor.x - PANEL_W - GAP;
329
+ }
330
+ if (top + PANEL_H > vh - MARGIN) {
331
+ top = anchor.y - PANEL_H - GAP;
332
+ }
333
+
334
+ return {
335
+ top: Math.max(MARGIN, top),
336
+ left: Math.max(MARGIN, left),
337
+ };
338
+ }
339
+
340
+ // ── Component ─────────────────────────────────────────────────────────────────
341
+
342
+ export function QuickCreateSessionPopover({
343
+ open,
344
+ onOpenChange,
345
+ slot,
346
+ anchor,
347
+ classId,
348
+ students,
349
+ onSaved,
350
+ onMoreOptions,
351
+ }: QuickCreateSessionPopoverProps) {
352
+ const t = useTranslations('lms.ClassesPage.DetailPage');
353
+ const localeCode = useLocale();
354
+ const { request } = useApp();
355
+ const titleRef = useRef<HTMLInputElement | null>(null);
356
+ const [activeTab, setActiveTab] = useState<'aulas' | 'chamada'>('aulas');
357
+ const [attendanceSearch, setAttendanceSearch] = useState('');
358
+ const [attendanceList, setAttendanceList] = useState<
359
+ QuickCreateAttendanceItem[]
360
+ >(() =>
361
+ students.map((student) => ({
362
+ alunoId: student.id,
363
+ selecionado: false,
364
+ presente: false,
365
+ }))
366
+ );
367
+
368
+ const form = useForm<QuickCreateForm>({
369
+ resolver: zodResolver(quickCreateSchema),
370
+ defaultValues: {
371
+ titulo: '',
372
+ data: slot?.date ?? '',
373
+ horaInicio: slot?.startTime ?? '19:00',
374
+ horaFim: slot?.endTime ?? '22:00',
375
+ cor: SESSION_DEFAULT_COLOR,
376
+ recurrenceFrequency: 'none',
377
+ recurrenceUntil: '',
378
+ recurrenceDaysOfWeek: [],
379
+ },
380
+ });
381
+
382
+ // Reset form with new slot defaults each time the popover opens
383
+ useEffect(() => {
384
+ if (open && slot) {
385
+ form.reset({
386
+ titulo: '',
387
+ data: slot.date,
388
+ horaInicio: slot.startTime,
389
+ horaFim: slot.endTime,
390
+ cor: SESSION_DEFAULT_COLOR,
391
+ recurrenceFrequency: 'none',
392
+ recurrenceUntil: '',
393
+ recurrenceDaysOfWeek: [],
394
+ });
395
+ requestAnimationFrame(() => titleRef.current?.focus());
396
+ }
397
+ }, [open, slot, form, students]);
398
+
399
+ const handleClose = useCallback(() => {
400
+ setActiveTab('aulas');
401
+ setAttendanceSearch('');
402
+ setAttendanceList(
403
+ students.map((student) => ({
404
+ alunoId: student.id,
405
+ selecionado: false,
406
+ presente: false,
407
+ }))
408
+ );
409
+ onOpenChange(false);
410
+ }, [onOpenChange, students]);
411
+
412
+ useEffect(() => {
413
+ if (!open) return;
414
+
415
+ const handler = (event: KeyboardEvent) => {
416
+ if (event.key === 'Escape') {
417
+ handleClose();
418
+ }
419
+ };
420
+
421
+ document.addEventListener('keydown', handler);
422
+
423
+ return () => {
424
+ document.removeEventListener('keydown', handler);
425
+ };
426
+ }, [open, handleClose]);
427
+
428
+ const {
429
+ register,
430
+ handleSubmit,
431
+ getValues,
432
+ setValue,
433
+ control,
434
+ formState: { errors, isSubmitting },
435
+ } = form;
436
+
437
+ const { ref: titleFormRef, ...titleInputProps } = register('titulo');
438
+ const selectedDateValue = useWatch({ control, name: 'data' });
439
+ const startTimeValue = useWatch({ control, name: 'horaInicio' });
440
+ const endTimeValue = useWatch({ control, name: 'horaFim' });
441
+ const recurrenceFrequency = useWatch({
442
+ control,
443
+ name: 'recurrenceFrequency',
444
+ });
445
+ const recurrenceUntilValue = useWatch({
446
+ control,
447
+ name: 'recurrenceUntil',
448
+ });
449
+ const recurrenceDaysOfWeek =
450
+ useWatch({ control, name: 'recurrenceDaysOfWeek' }) ?? [];
451
+ const selectedColor = useWatch({ control, name: 'cor' });
452
+ const calendarLocale = localeCode.startsWith('pt') ? ptBR : enUS;
453
+ const selectedDate = selectedDateValue
454
+ ? new Date(`${selectedDateValue}T00:00:00`)
455
+ : undefined;
456
+ const selectedRecurrenceUntil = recurrenceUntilValue
457
+ ? new Date(`${recurrenceUntilValue}T00:00:00`)
458
+ : undefined;
459
+ const filteredStudents = students.filter((student) =>
460
+ student.nome.toLowerCase().includes(attendanceSearch.trim().toLowerCase())
461
+ );
462
+
463
+ const togglePresenca = (alunoId: number) => {
464
+ setAttendanceList((prev) =>
465
+ prev.map((item) =>
466
+ item.alunoId === alunoId
467
+ ? { ...item, selecionado: true, presente: !item.presente }
468
+ : item
469
+ )
470
+ );
471
+ };
472
+
473
+ const toggleParticipante = (alunoId: number) => {
474
+ setAttendanceList((prev) =>
475
+ prev.map((item) =>
476
+ item.alunoId === alunoId
477
+ ? {
478
+ ...item,
479
+ selecionado: !item.selecionado,
480
+ presente: item.selecionado ? false : item.presente,
481
+ }
482
+ : item
483
+ )
484
+ );
485
+ };
486
+
487
+ const onSave = handleSubmit(async (values) => {
488
+ try {
489
+ const res = await request<Aula>({
490
+ url: `/lms/classes/${classId}/sessions`,
491
+ method: 'POST',
492
+ data: {
493
+ title: values.titulo,
494
+ sessionDate: values.data,
495
+ startTime: values.horaInicio,
496
+ endTime: values.horaFim,
497
+ color: values.cor,
498
+ recurrence: buildRecurrencePayload(values),
499
+ },
500
+ });
501
+
502
+ const selectedAttendance = attendanceList.filter(
503
+ (item) => item.selecionado
504
+ );
505
+
506
+ if (selectedAttendance.length > 0) {
507
+ try {
508
+ await request({
509
+ url: `/lms/classes/${classId}/sessions/${res.data.id}/attendance`,
510
+ method: 'POST',
511
+ data: {
512
+ attendance: selectedAttendance.map((item) => ({
513
+ studentId: item.alunoId,
514
+ present: item.presente,
515
+ })),
516
+ },
517
+ });
518
+ } catch {
519
+ toast.error(t('quickCreate.attendancePartialError'));
520
+ }
521
+ }
522
+
523
+ onSaved(res.data);
524
+ handleClose();
525
+ toast.success(t('toasts.lessonCreated'));
526
+ } catch {
527
+ toast.error(t('toasts.error'));
528
+ }
529
+ });
530
+
531
+ const onMoreOptionsClick = () => {
532
+ const values = getValues();
533
+ handleClose();
534
+ onMoreOptions({
535
+ titulo: values.titulo,
536
+ data: values.data,
537
+ horaInicio: values.horaInicio,
538
+ horaFim: values.horaFim,
539
+ cor: values.cor,
540
+ recurrenceFrequency: values.recurrenceFrequency,
541
+ recurrenceUntil: values.recurrenceUntil,
542
+ recurrenceDaysOfWeek: values.recurrenceDaysOfWeek,
543
+ attendance: attendanceList,
544
+ initialTab: activeTab,
545
+ });
546
+ };
547
+
548
+ if (!open || typeof document === 'undefined') {
549
+ return null;
550
+ }
551
+
552
+ const pos = anchor ? computePosition(anchor) : { top: 80, left: 80 };
553
+
554
+ return createPortal(
555
+ <>
556
+ <div className="fixed inset-0 z-40" onClick={handleClose} />
557
+
558
+ <div
559
+ className="fixed z-50 w-90 max-h-[calc(100vh-24px)] overflow-y-auto rounded-lg border bg-popover p-4 shadow-lg outline-none"
560
+ style={{ top: pos.top, left: pos.left }}
561
+ onClick={(event) => event.stopPropagation()}
562
+ >
563
+ <div className="mb-3 flex items-center justify-between">
564
+ <div className="flex items-center gap-2">
565
+ <CalendarPlus className="size-4 text-muted-foreground" />
566
+ <p className="text-sm font-semibold">{t('quickCreate.title')}</p>
567
+ </div>
568
+ <button
569
+ type="button"
570
+ onClick={handleClose}
571
+ className="rounded p-0.5 text-muted-foreground hover:text-foreground"
572
+ aria-label="Fechar"
573
+ >
574
+ <X className="size-3.5" />
575
+ </button>
576
+ </div>
577
+
578
+ <form onSubmit={onSave} className="space-y-3">
579
+ <Tabs
580
+ value={activeTab}
581
+ onValueChange={(value) =>
582
+ setActiveTab(value as 'aulas' | 'chamada')
583
+ }
584
+ className="space-y-3"
585
+ >
586
+ <TabsList className="grid w-full grid-cols-2">
587
+ <TabsTrigger value="aulas">
588
+ {t('quickCreate.tabs.lessons')}
589
+ </TabsTrigger>
590
+ <TabsTrigger value="chamada">
591
+ {t('quickCreate.tabs.attendance')}
592
+ </TabsTrigger>
593
+ </TabsList>
594
+
595
+ <TabsContent value="aulas" className="mt-0 space-y-3">
596
+ <div>
597
+ <Input
598
+ {...titleInputProps}
599
+ ref={(element) => {
600
+ titleFormRef(element);
601
+ titleRef.current = element;
602
+ }}
603
+ placeholder={t('quickCreate.titlePlaceholder')}
604
+ autoComplete="off"
605
+ />
606
+ {errors.titulo && (
607
+ <p className="mt-1 text-xs text-destructive">
608
+ {errors.titulo.message}
609
+ </p>
610
+ )}
611
+ </div>
612
+
613
+ <div>
614
+ <UiPopover>
615
+ <PopoverTrigger asChild>
616
+ <Button
617
+ type="button"
618
+ variant="outline"
619
+ className={cn(
620
+ 'w-full justify-start text-left font-normal',
621
+ !selectedDate && 'text-muted-foreground'
622
+ )}
623
+ >
624
+ <CalendarIcon className="mr-2 size-4" />
625
+ {selectedDate
626
+ ? format(selectedDate, 'dd/MM/yyyy', {
627
+ locale: calendarLocale,
628
+ })
629
+ : t('quickCreate.datePlaceholder')}
630
+ </Button>
631
+ </PopoverTrigger>
632
+ <PopoverContent className="w-auto p-0" align="start">
633
+ <DateCalendar
634
+ mode="single"
635
+ selected={selectedDate}
636
+ onSelect={(date) => {
637
+ if (!date) return;
638
+
639
+ const year = date.getFullYear();
640
+ const month = String(date.getMonth() + 1).padStart(
641
+ 2,
642
+ '0'
643
+ );
644
+ const day = String(date.getDate()).padStart(2, '0');
645
+ setValue('data', `${year}-${month}-${day}`, {
646
+ shouldDirty: true,
647
+ shouldValidate: true,
648
+ });
649
+ }}
650
+ locale={calendarLocale}
651
+ initialFocus
652
+ />
653
+ </PopoverContent>
654
+ </UiPopover>
655
+ {errors.data && (
656
+ <p className="mt-1 text-xs text-destructive">
657
+ {errors.data.message}
658
+ </p>
659
+ )}
660
+ </div>
661
+
662
+ <div className="grid grid-cols-2 gap-2">
663
+ <div>
664
+ <TimePickerField
665
+ value={startTimeValue}
666
+ onChange={(value) =>
667
+ setValue('horaInicio', value, {
668
+ shouldDirty: true,
669
+ shouldValidate: true,
670
+ })
671
+ }
672
+ />
673
+ {errors.horaInicio && (
674
+ <p className="mt-1 text-xs text-destructive">
675
+ {errors.horaInicio.message}
676
+ </p>
677
+ )}
678
+ </div>
679
+ <div>
680
+ <TimePickerField
681
+ value={endTimeValue}
682
+ onChange={(value) =>
683
+ setValue('horaFim', value, {
684
+ shouldDirty: true,
685
+ shouldValidate: true,
686
+ })
687
+ }
688
+ />
689
+ {errors.horaFim && (
690
+ <p className="mt-1 text-xs text-destructive">
691
+ {errors.horaFim.message}
692
+ </p>
693
+ )}
694
+ </div>
695
+ </div>
696
+
697
+ <div className="space-y-1.5">
698
+ <p className="text-xs font-medium text-foreground">
699
+ {t('quickCreate.color.label')}
700
+ </p>
701
+ <div className="flex flex-wrap gap-2">
702
+ {SESSION_COLOR_PALETTE.map((colorOption) => {
703
+ const selected = selectedColor === colorOption;
704
+
705
+ return (
706
+ <button
707
+ key={colorOption}
708
+ type="button"
709
+ aria-label={colorOption}
710
+ title={colorOption}
711
+ className={`h-7 w-7 cursor-pointer rounded-full border-2 transition ${selected ? 'border-foreground scale-110' : 'border-transparent hover:scale-105'}`}
712
+ style={{ backgroundColor: colorOption }}
713
+ onClick={() =>
714
+ setValue('cor', colorOption, {
715
+ shouldDirty: true,
716
+ shouldValidate: true,
717
+ })
718
+ }
719
+ />
720
+ );
721
+ })}
722
+ </div>
723
+ <p className="text-[11px] text-muted-foreground">
724
+ {t('quickCreate.color.hint')}
725
+ </p>
726
+ {errors.cor && (
727
+ <p className="text-xs text-destructive">
728
+ {errors.cor.message}
729
+ </p>
730
+ )}
731
+ </div>
732
+
733
+ <div className="space-y-3 rounded-md border border-border/60 bg-muted/20 p-3">
734
+ <div className="grid grid-cols-2 gap-2">
735
+ <div className="space-y-1.5">
736
+ <p className="text-xs font-medium text-foreground">
737
+ {t('quickCreate.recurrence.label')}
738
+ </p>
739
+ <Select
740
+ value={recurrenceFrequency}
741
+ onValueChange={(value) =>
742
+ setValue(
743
+ 'recurrenceFrequency',
744
+ value as SessionRecurrenceFrequency,
745
+ {
746
+ shouldDirty: true,
747
+ shouldValidate: true,
748
+ }
749
+ )
750
+ }
751
+ >
752
+ <SelectTrigger>
753
+ <SelectValue />
754
+ </SelectTrigger>
755
+ <SelectContent>
756
+ <SelectItem value="none">
757
+ {t('quickCreate.recurrence.options.none')}
758
+ </SelectItem>
759
+ <SelectItem value="daily">
760
+ {t('quickCreate.recurrence.options.daily')}
761
+ </SelectItem>
762
+ <SelectItem value="weekly">
763
+ {t('quickCreate.recurrence.options.weekly')}
764
+ </SelectItem>
765
+ <SelectItem value="monthly">
766
+ {t('quickCreate.recurrence.options.monthly')}
767
+ </SelectItem>
768
+ <SelectItem value="yearly">
769
+ {t('quickCreate.recurrence.options.yearly')}
770
+ </SelectItem>
771
+ </SelectContent>
772
+ </Select>
773
+ </div>
774
+
775
+ {recurrenceFrequency !== 'none' ? (
776
+ <div className="space-y-1.5">
777
+ <p className="text-xs font-medium text-foreground">
778
+ {t('quickCreate.recurrence.until')}
779
+ </p>
780
+ <UiPopover>
781
+ <PopoverTrigger asChild>
782
+ <Button
783
+ type="button"
784
+ variant="outline"
785
+ className={cn(
786
+ 'w-full justify-start text-left font-normal',
787
+ !selectedRecurrenceUntil &&
788
+ 'text-muted-foreground'
789
+ )}
790
+ >
791
+ <CalendarIcon className="mr-2 size-4" />
792
+ {selectedRecurrenceUntil
793
+ ? format(selectedRecurrenceUntil, 'dd/MM/yyyy', {
794
+ locale: calendarLocale,
795
+ })
796
+ : t('quickCreate.recurrence.until')}
797
+ </Button>
798
+ </PopoverTrigger>
799
+ <PopoverContent className="w-auto p-0" align="start">
800
+ <DateCalendar
801
+ mode="single"
802
+ selected={selectedRecurrenceUntil}
803
+ onSelect={(date) => {
804
+ if (!date) return;
805
+
806
+ const year = date.getFullYear();
807
+ const month = String(
808
+ date.getMonth() + 1
809
+ ).padStart(2, '0');
810
+ const day = String(date.getDate()).padStart(
811
+ 2,
812
+ '0'
813
+ );
814
+
815
+ setValue(
816
+ 'recurrenceUntil',
817
+ `${year}-${month}-${day}`,
818
+ {
819
+ shouldDirty: true,
820
+ shouldValidate: true,
821
+ }
822
+ );
823
+ }}
824
+ locale={calendarLocale}
825
+ initialFocus
826
+ />
827
+ </PopoverContent>
828
+ </UiPopover>
829
+ {errors.recurrenceUntil && (
830
+ <p className="text-xs text-destructive">
831
+ {errors.recurrenceUntil.message}
832
+ </p>
833
+ )}
834
+ </div>
835
+ ) : null}
836
+ </div>
837
+
838
+ {recurrenceFrequency === 'weekly' ? (
839
+ <div className="space-y-2">
840
+ <p className="text-xs font-medium text-foreground">
841
+ {t('quickCreate.recurrence.daysLabel')}
842
+ </p>
843
+ <div className="grid grid-cols-4 gap-2">
844
+ {RECURRENCE_DAY_LABELS.map((day) => {
845
+ const selected = recurrenceDaysOfWeek.includes(
846
+ day.value
847
+ );
848
+
849
+ return (
850
+ <Button
851
+ key={day.value}
852
+ type="button"
853
+ variant={selected ? 'default' : 'outline'}
854
+ size="sm"
855
+ className="h-8"
856
+ onClick={() => {
857
+ const nextValue = selected
858
+ ? recurrenceDaysOfWeek.filter(
859
+ (value) => value !== day.value
860
+ )
861
+ : [...recurrenceDaysOfWeek, day.value];
862
+
863
+ setValue('recurrenceDaysOfWeek', nextValue, {
864
+ shouldDirty: true,
865
+ shouldValidate: true,
866
+ });
867
+ }}
868
+ >
869
+ {t(day.labelKey)}
870
+ </Button>
871
+ );
872
+ })}
873
+ </div>
874
+ <p className="text-[11px] text-muted-foreground">
875
+ {t('quickCreate.recurrence.weeklyHint')}
876
+ </p>
877
+ </div>
878
+ ) : null}
879
+ </div>
880
+ </TabsContent>
881
+
882
+ <TabsContent value="chamada" className="mt-0 space-y-3">
883
+ {students.length === 0 ? (
884
+ <p className="text-xs text-muted-foreground">
885
+ {t('quickCreate.noStudents')}
886
+ </p>
887
+ ) : (
888
+ <>
889
+ <div className="relative">
890
+ <Search className="pointer-events-none absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
891
+ <Input
892
+ value={attendanceSearch}
893
+ onChange={(event) =>
894
+ setAttendanceSearch(event.currentTarget.value)
895
+ }
896
+ className="pl-8"
897
+ placeholder={t('quickCreate.searchPlaceholder')}
898
+ />
899
+ </div>
900
+
901
+ <div className="flex items-center justify-between text-xs text-muted-foreground">
902
+ <span>
903
+ {t('quickCreate.attendanceSummary', {
904
+ present: attendanceList.filter(
905
+ (item) => item.selecionado && item.presente
906
+ ).length,
907
+ total: attendanceList.filter((item) => item.selecionado)
908
+ .length,
909
+ })}
910
+ </span>
911
+ <div className="flex gap-1">
912
+ <Button
913
+ type="button"
914
+ variant="outline"
915
+ size="sm"
916
+ className="h-7 px-2 text-[11px]"
917
+ onClick={() =>
918
+ setAttendanceList((prev) =>
919
+ prev.map((item) =>
920
+ item.selecionado
921
+ ? { ...item, presente: true }
922
+ : item
923
+ )
924
+ )
925
+ }
926
+ >
927
+ {t('quickCreate.allPresent')}
928
+ </Button>
929
+ <Button
930
+ type="button"
931
+ variant="outline"
932
+ size="sm"
933
+ className="h-7 px-2 text-[11px]"
934
+ onClick={() =>
935
+ setAttendanceList((prev) =>
936
+ prev.map((item) =>
937
+ item.selecionado
938
+ ? { ...item, presente: false }
939
+ : item
940
+ )
941
+ )
942
+ }
943
+ >
944
+ {t('quickCreate.allAbsent')}
945
+ </Button>
946
+ </div>
947
+ </div>
948
+
949
+ <div className="space-y-2">
950
+ {filteredStudents.length === 0 ? (
951
+ <p className="text-xs text-muted-foreground">
952
+ {t('quickCreate.noStudentsFound')}
953
+ </p>
954
+ ) : null}
955
+
956
+ {filteredStudents.map((student) => {
957
+ const item = attendanceList.find(
958
+ (entry) => entry.alunoId === student.id
959
+ );
960
+ if (!item) return null;
961
+
962
+ return (
963
+ <div
964
+ key={student.id}
965
+ className={`flex items-center justify-between rounded-md border px-2.5 py-2 ${item.selecionado ? (item.presente ? 'border-emerald-200 bg-emerald-50/50' : 'border-red-200 bg-red-50/50') : 'border-border/60 bg-muted/30'}`}
966
+ >
967
+ <div className="flex items-center gap-2">
968
+ <Checkbox
969
+ checked={item.selecionado}
970
+ onCheckedChange={() =>
971
+ toggleParticipante(student.id)
972
+ }
973
+ aria-label={t('quickCreate.participantLabel')}
974
+ />
975
+ <span className="max-w-52 truncate text-sm font-medium">
976
+ {student.nome}
977
+ </span>
978
+ </div>
979
+ <div className="flex items-center gap-2">
980
+ <span className="text-[11px] text-muted-foreground">
981
+ {item.selecionado
982
+ ? item.presente
983
+ ? t('quickCreate.present')
984
+ : t('quickCreate.absent')
985
+ : t('quickCreate.notParticipant')}
986
+ </span>
987
+ <Switch
988
+ checked={item.presente}
989
+ disabled={!item.selecionado}
990
+ onCheckedChange={() => togglePresenca(student.id)}
991
+ />
992
+ </div>
993
+ </div>
994
+ );
995
+ })}
996
+ </div>
997
+ </>
998
+ )}
999
+ </TabsContent>
1000
+ </Tabs>
1001
+
1002
+ <div className="flex items-center justify-between gap-2 pt-1">
1003
+ <Button
1004
+ type="button"
1005
+ variant="ghost"
1006
+ size="sm"
1007
+ className="text-xs"
1008
+ onClick={onMoreOptionsClick}
1009
+ >
1010
+ {t('quickCreate.moreOptions')}
1011
+ </Button>
1012
+ <Button
1013
+ type="submit"
1014
+ size="sm"
1015
+ disabled={isSubmitting}
1016
+ className="gap-1.5"
1017
+ >
1018
+ {isSubmitting && <Loader2 className="size-3 animate-spin" />}
1019
+ {t('quickCreate.save')}
1020
+ </Button>
1021
+ </div>
1022
+ </form>
1023
+ </div>
1024
+ </>,
1025
+ document.body
1026
+ );
1027
+ }