@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,646 @@
1
+ import { Prisma, PrismaService } from '@hed-hog/api-prisma';
2
+ import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
3
+ import { CreateInstructorDto } from './dto/create-instructor.dto';
4
+ import { UpdateInstructorDto } from './dto/update-instructor.dto';
5
+
6
+ type DbClient = PrismaService | Prisma.TransactionClient;
7
+
8
+ const DEFAULT_QUALIFICATION_SLUGS = ['course-lessons', 'class-sessions'] as const;
9
+
10
+ @Injectable()
11
+ export class InstructorService {
12
+ constructor(private readonly prisma: PrismaService) {}
13
+
14
+ async list(params: {
15
+ page?: number;
16
+ pageSize?: number;
17
+ search?: string;
18
+ status?: string;
19
+ qualificationSlugs?: string[];
20
+ }) {
21
+ await this.ensureQualificationCatalog();
22
+ await this.backfillLegacyCourseLessonQualifications();
23
+
24
+ const page = Math.max(Number(params.page) || 1, 1);
25
+ const pageSize = Math.max(Number(params.pageSize) || 20, 1);
26
+ const skip = (page - 1) * pageSize;
27
+ const qualificationSlugs = this.normalizeQualificationSlugs(
28
+ params.qualificationSlugs,
29
+ );
30
+ const search = params.search?.trim();
31
+
32
+ const where: Prisma.instructorWhereInput = {};
33
+
34
+ if (params.status && params.status !== 'all') {
35
+ where.status =
36
+ params.status === 'inactive' ? 'inactive' : ('active' as any);
37
+ }
38
+
39
+ if (search) {
40
+ where.person = {
41
+ is: {
42
+ OR: [
43
+ {
44
+ name: {
45
+ contains: search,
46
+ mode: 'insensitive',
47
+ },
48
+ },
49
+ {
50
+ contact: {
51
+ some: {
52
+ value: {
53
+ contains: search,
54
+ mode: 'insensitive',
55
+ },
56
+ },
57
+ },
58
+ },
59
+ ],
60
+ },
61
+ };
62
+ }
63
+
64
+ if (qualificationSlugs.length > 0) {
65
+ where.instructor_qualification_assignment = {
66
+ some: {
67
+ instructor_qualification: {
68
+ slug: {
69
+ in: qualificationSlugs,
70
+ },
71
+ status: 'active',
72
+ },
73
+ },
74
+ };
75
+ }
76
+
77
+ const [rows, total] = await Promise.all([
78
+ this.prisma.instructor.findMany({
79
+ where,
80
+ skip,
81
+ take: pageSize,
82
+ include: {
83
+ person: {
84
+ select: {
85
+ id: true,
86
+ name: true,
87
+ avatar_id: true,
88
+ },
89
+ },
90
+ instructor_qualification_assignment: {
91
+ include: {
92
+ instructor_qualification: {
93
+ select: {
94
+ slug: true,
95
+ },
96
+ },
97
+ },
98
+ },
99
+ },
100
+ orderBy: {
101
+ id: 'asc',
102
+ },
103
+ }),
104
+ this.prisma.instructor.count({ where }),
105
+ ]);
106
+
107
+ return {
108
+ data: rows
109
+ .map((row) => ({
110
+ id: row.id,
111
+ personId: row.person_id,
112
+ name: row.person?.name?.trim() || `Instructor #${row.id}`,
113
+ avatarId: row.person?.avatar_id ?? null,
114
+ status: row.status,
115
+ qualificationSlugs: row.instructor_qualification_assignment
116
+ .map((assignment) => assignment.instructor_qualification.slug)
117
+ .sort(),
118
+ }))
119
+ .sort((left, right) => left.name.localeCompare(right.name)),
120
+ total,
121
+ page,
122
+ pageSize,
123
+ };
124
+ }
125
+
126
+ async create(dto: CreateInstructorDto) {
127
+ return this.prisma.$transaction(async (tx) => {
128
+ await this.ensureQualificationCatalog(tx);
129
+ await this.backfillLegacyCourseLessonQualifications(tx);
130
+
131
+ const normalizedEmail = this.normalizeEmail(dto.email);
132
+ const name = dto.name.trim();
133
+ const phone = this.normalizePhone(dto.phone);
134
+ const requestedPersonId =
135
+ typeof dto.personId === 'number' && dto.personId > 0 ? dto.personId : null;
136
+
137
+ const existingPersonId =
138
+ requestedPersonId ??
139
+ (normalizedEmail
140
+ ? await this.findPersonIdByEmail(tx, normalizedEmail)
141
+ : null);
142
+
143
+ const person = existingPersonId
144
+ ? await tx.person.findUnique({
145
+ where: { id: existingPersonId },
146
+ select: { id: true, name: true, avatar_id: true },
147
+ })
148
+ : await tx.person.create({
149
+ data: {
150
+ name,
151
+ type: 'individual',
152
+ status: 'active',
153
+ avatar_id: dto.avatarId ?? null,
154
+ },
155
+ select: { id: true, name: true, avatar_id: true },
156
+ });
157
+
158
+ if (!person) {
159
+ throw new BadRequestException('Unable to resolve instructor person');
160
+ }
161
+
162
+ if (existingPersonId && dto.avatarId !== undefined) {
163
+ await tx.person.update({
164
+ where: { id: person.id },
165
+ data: {
166
+ avatar_id: dto.avatarId,
167
+ },
168
+ });
169
+ }
170
+
171
+ await this.syncPersonContacts(tx, person.id, normalizedEmail, phone);
172
+
173
+ const instructor =
174
+ (await tx.instructor.findFirst({
175
+ where: { person_id: person.id },
176
+ select: { id: true, person_id: true, status: true },
177
+ })) ??
178
+ (await tx.instructor.create({
179
+ data: {
180
+ person_id: person.id,
181
+ status: dto.status ?? 'active',
182
+ },
183
+ select: { id: true, person_id: true, status: true },
184
+ }));
185
+
186
+ if (instructor.status !== (dto.status ?? instructor.status)) {
187
+ await tx.instructor.update({
188
+ where: { id: instructor.id },
189
+ data: {
190
+ status: dto.status ?? instructor.status,
191
+ },
192
+ });
193
+ }
194
+
195
+ await this.syncInstructorQualifications(
196
+ tx,
197
+ instructor.id,
198
+ dto.qualificationSlugs,
199
+ );
200
+
201
+ return this.getById(instructor.id, tx);
202
+ });
203
+ }
204
+
205
+ async update(id: number, dto: UpdateInstructorDto) {
206
+ return this.prisma.$transaction(async (tx) => {
207
+ await this.ensureQualificationCatalog(tx);
208
+ await this.backfillLegacyCourseLessonQualifications(tx);
209
+
210
+ const instructor = await tx.instructor.findUnique({
211
+ where: { id },
212
+ include: {
213
+ person: {
214
+ select: {
215
+ id: true,
216
+ avatar_id: true,
217
+ },
218
+ },
219
+ },
220
+ });
221
+
222
+ if (!instructor?.person) {
223
+ throw new NotFoundException('Instructor not found');
224
+ }
225
+
226
+ if (dto.name?.trim()) {
227
+ await tx.person.update({
228
+ where: { id: instructor.person.id },
229
+ data: {
230
+ name: dto.name.trim(),
231
+ },
232
+ });
233
+ }
234
+
235
+ await this.syncPersonContacts(
236
+ tx,
237
+ instructor.person.id,
238
+ this.normalizeEmail(dto.email),
239
+ this.normalizePhone(dto.phone),
240
+ );
241
+
242
+ if (dto.avatarId !== undefined) {
243
+ await tx.person.update({
244
+ where: { id: instructor.person.id },
245
+ data: {
246
+ avatar_id: dto.avatarId,
247
+ },
248
+ });
249
+ }
250
+
251
+ if (dto.status) {
252
+ await tx.instructor.update({
253
+ where: { id },
254
+ data: {
255
+ status: dto.status,
256
+ },
257
+ });
258
+ }
259
+
260
+ if (dto.qualificationSlugs) {
261
+ await this.syncInstructorQualifications(
262
+ tx,
263
+ id,
264
+ dto.qualificationSlugs,
265
+ );
266
+ }
267
+
268
+ return this.getById(id, tx);
269
+ });
270
+ }
271
+
272
+ async getById(id: number, db: DbClient = this.prisma) {
273
+ await this.ensureQualificationCatalog(db);
274
+ await this.backfillLegacyCourseLessonQualifications(db);
275
+
276
+ const instructor = await db.instructor.findUnique({
277
+ where: { id },
278
+ include: {
279
+ person: {
280
+ include: {
281
+ contact: {
282
+ where: {
283
+ contact_type: {
284
+ code: {
285
+ in: ['EMAIL', 'PHONE', 'MOBILE', 'WHATSAPP'],
286
+ },
287
+ },
288
+ },
289
+ include: {
290
+ contact_type: {
291
+ select: {
292
+ code: true,
293
+ },
294
+ },
295
+ },
296
+ orderBy: [{ is_primary: 'desc' }, { id: 'asc' }],
297
+ },
298
+ },
299
+ },
300
+ instructor_qualification_assignment: {
301
+ include: {
302
+ instructor_qualification: {
303
+ select: {
304
+ slug: true,
305
+ },
306
+ },
307
+ },
308
+ },
309
+ },
310
+ });
311
+
312
+ if (!instructor?.person) {
313
+ throw new NotFoundException('Instructor not found');
314
+ }
315
+
316
+ return {
317
+ id: instructor.id,
318
+ personId: instructor.person.id,
319
+ name: instructor.person.name?.trim() || `Instructor #${instructor.id}`,
320
+ avatarId: instructor.person.avatar_id ?? null,
321
+ email: this.getPrimaryContactValue(instructor.person.contact, ['EMAIL']),
322
+ phone: this.getPrimaryContactValue(instructor.person.contact, [
323
+ 'PHONE',
324
+ 'MOBILE',
325
+ 'WHATSAPP',
326
+ ]),
327
+ status: instructor.status,
328
+ qualificationSlugs: instructor.instructor_qualification_assignment
329
+ .map((assignment) => assignment.instructor_qualification.slug)
330
+ .sort(),
331
+ };
332
+ }
333
+
334
+ async listQualifiedInstructorOptions(qualificationSlugs: string[]) {
335
+ const result = await this.list({
336
+ page: 1,
337
+ pageSize: 500,
338
+ status: 'active',
339
+ qualificationSlugs,
340
+ });
341
+
342
+ return result.data;
343
+ }
344
+
345
+ async assertQualifiedInstructorIds(
346
+ instructorIds: number[],
347
+ qualificationSlugs: string[],
348
+ ) {
349
+ const uniqueIds = [...new Set(instructorIds.filter((id) => Number(id) > 0))];
350
+ if (uniqueIds.length === 0) return;
351
+
352
+ const qualifiedSet = new Set(
353
+ (
354
+ await this.prisma.instructor.findMany({
355
+ where: {
356
+ id: { in: uniqueIds },
357
+ status: 'active',
358
+ instructor_qualification_assignment: {
359
+ some: {
360
+ instructor_qualification: {
361
+ slug: {
362
+ in: this.normalizeQualificationSlugs(qualificationSlugs),
363
+ },
364
+ status: 'active',
365
+ },
366
+ },
367
+ },
368
+ },
369
+ select: { id: true },
370
+ })
371
+ ).map((item) => item.id),
372
+ );
373
+
374
+ if (uniqueIds.some((id) => !qualifiedSet.has(id))) {
375
+ throw new BadRequestException('One or more instructors are invalid');
376
+ }
377
+ }
378
+
379
+ private normalizeQualificationSlugs(value?: string[]) {
380
+ return [...new Set((value ?? []).map((item) => item?.trim()).filter(Boolean))];
381
+ }
382
+
383
+ private normalizeEmail(value?: string) {
384
+ const normalized = value?.trim().toLowerCase();
385
+ return normalized ? normalized : null;
386
+ }
387
+
388
+ private normalizePhone(value?: string) {
389
+ const normalized = value?.trim();
390
+ return normalized ? normalized : null;
391
+ }
392
+
393
+ private getPrimaryContactValue(
394
+ contacts: Array<{
395
+ value?: string | null;
396
+ is_primary?: boolean | null;
397
+ contact_type?: { code?: string | null } | null;
398
+ }>,
399
+ acceptedCodes: string[],
400
+ ) {
401
+ const normalizedCodes = acceptedCodes.map((code) => code.toUpperCase());
402
+
403
+ const primaryMatch = contacts.find(
404
+ (contact) =>
405
+ normalizedCodes.includes(
406
+ String(contact.contact_type?.code || '').toUpperCase(),
407
+ ) && contact.is_primary,
408
+ );
409
+
410
+ if (primaryMatch?.value) {
411
+ return primaryMatch.value;
412
+ }
413
+
414
+ const fallbackMatch = contacts.find((contact) =>
415
+ normalizedCodes.includes(
416
+ String(contact.contact_type?.code || '').toUpperCase(),
417
+ ),
418
+ );
419
+
420
+ return fallbackMatch?.value ?? null;
421
+ }
422
+
423
+ private async ensureQualificationCatalog(db: DbClient = this.prisma) {
424
+ for (const slug of DEFAULT_QUALIFICATION_SLUGS) {
425
+ await db.instructor_qualification.upsert({
426
+ where: { slug },
427
+ update: {
428
+ status: 'active',
429
+ },
430
+ create: {
431
+ slug,
432
+ status: 'active',
433
+ },
434
+ });
435
+ }
436
+ }
437
+
438
+ private async backfillLegacyCourseLessonQualifications(
439
+ db: DbClient = this.prisma,
440
+ ) {
441
+ await this.ensureQualificationCatalog(db);
442
+
443
+ const courseLessonQualification = await db.instructor_qualification.findUnique({
444
+ where: { slug: 'course-lessons' },
445
+ select: { id: true },
446
+ });
447
+
448
+ if (!courseLessonQualification) {
449
+ return;
450
+ }
451
+
452
+ const legacyInstructors = await db.instructor.findMany({
453
+ where: {
454
+ status: 'active',
455
+ can_teach_courses: true,
456
+ instructor_qualification_assignment: {
457
+ none: {
458
+ qualification_id: courseLessonQualification.id,
459
+ },
460
+ },
461
+ },
462
+ select: { id: true },
463
+ });
464
+
465
+ if (legacyInstructors.length > 0) {
466
+ await db.instructor_qualification_assignment.createMany({
467
+ data: legacyInstructors.map((item) => ({
468
+ instructor_id: item.id,
469
+ qualification_id: courseLessonQualification.id,
470
+ })),
471
+ skipDuplicates: true,
472
+ });
473
+ }
474
+ }
475
+
476
+ private async syncInstructorQualifications(
477
+ db: DbClient,
478
+ instructorId: number,
479
+ qualificationSlugs: string[],
480
+ ) {
481
+ const normalizedQualificationSlugs =
482
+ this.normalizeQualificationSlugs(qualificationSlugs);
483
+
484
+ if (normalizedQualificationSlugs.length === 0) {
485
+ throw new BadRequestException(
486
+ 'At least one instructor qualification is required',
487
+ );
488
+ }
489
+
490
+ const rows = await db.instructor_qualification.findMany({
491
+ where: {
492
+ slug: {
493
+ in: normalizedQualificationSlugs,
494
+ },
495
+ },
496
+ select: {
497
+ id: true,
498
+ slug: true,
499
+ },
500
+ });
501
+
502
+ if (rows.length !== normalizedQualificationSlugs.length) {
503
+ throw new BadRequestException('One or more instructor qualifications are invalid');
504
+ }
505
+
506
+ const qualificationIds = rows.map((row) => row.id);
507
+
508
+ await db.instructor_qualification_assignment.deleteMany({
509
+ where: {
510
+ instructor_id: instructorId,
511
+ qualification_id: {
512
+ notIn: qualificationIds,
513
+ },
514
+ },
515
+ });
516
+
517
+ await db.instructor_qualification_assignment.createMany({
518
+ data: qualificationIds.map((qualificationId) => ({
519
+ instructor_id: instructorId,
520
+ qualification_id: qualificationId,
521
+ })),
522
+ skipDuplicates: true,
523
+ });
524
+ }
525
+
526
+ private async findPersonIdByEmail(db: DbClient, email: string) {
527
+ const contact = await db.contact.findFirst({
528
+ where: {
529
+ value: {
530
+ equals: email,
531
+ mode: 'insensitive',
532
+ },
533
+ contact_type: {
534
+ code: {
535
+ equals: 'EMAIL',
536
+ mode: 'insensitive',
537
+ },
538
+ },
539
+ },
540
+ select: {
541
+ person_id: true,
542
+ },
543
+ });
544
+
545
+ return contact?.person_id ?? null;
546
+ }
547
+
548
+ private async syncPersonContacts(
549
+ db: DbClient,
550
+ personId: number,
551
+ email: string | null,
552
+ phone: string | null,
553
+ ) {
554
+ const contactTypeIds = await this.resolveContactTypeIds(db);
555
+
556
+ if (email && contactTypeIds.email) {
557
+ await this.upsertPrimaryContact(
558
+ db,
559
+ personId,
560
+ contactTypeIds.email,
561
+ email,
562
+ );
563
+ }
564
+
565
+ if (phone && contactTypeIds.phone) {
566
+ await this.upsertPrimaryContact(
567
+ db,
568
+ personId,
569
+ contactTypeIds.phone,
570
+ phone,
571
+ );
572
+ }
573
+ }
574
+
575
+ private async resolveContactTypeIds(db: DbClient) {
576
+ const types = await db.contact_type.findMany({
577
+ where: {
578
+ code: {
579
+ in: ['EMAIL', 'PHONE'],
580
+ },
581
+ },
582
+ select: {
583
+ id: true,
584
+ code: true,
585
+ },
586
+ });
587
+
588
+ const byCode = new Map(types.map((item) => [item.code.toUpperCase(), item.id]));
589
+
590
+ return {
591
+ email: byCode.get('EMAIL') ?? null,
592
+ phone: byCode.get('PHONE') ?? null,
593
+ };
594
+ }
595
+
596
+ private async upsertPrimaryContact(
597
+ db: DbClient,
598
+ personId: number,
599
+ contactTypeId: number,
600
+ value: string,
601
+ ) {
602
+ await db.contact.updateMany({
603
+ where: {
604
+ person_id: personId,
605
+ contact_type_id: contactTypeId,
606
+ },
607
+ data: {
608
+ is_primary: false,
609
+ },
610
+ });
611
+
612
+ const existing = await db.contact.findFirst({
613
+ where: {
614
+ person_id: personId,
615
+ contact_type_id: contactTypeId,
616
+ value: {
617
+ equals: value,
618
+ mode: 'insensitive',
619
+ },
620
+ },
621
+ select: {
622
+ id: true,
623
+ },
624
+ });
625
+
626
+ if (existing) {
627
+ await db.contact.update({
628
+ where: { id: existing.id },
629
+ data: {
630
+ value,
631
+ is_primary: true,
632
+ },
633
+ });
634
+ return;
635
+ }
636
+
637
+ await db.contact.create({
638
+ data: {
639
+ person_id: personId,
640
+ contact_type_id: contactTypeId,
641
+ value,
642
+ is_primary: true,
643
+ },
644
+ });
645
+ }
646
+ }
package/src/lms.module.ts CHANGED
@@ -1,15 +1,47 @@
1
- import { forwardRef, Module } from '@nestjs/common';
2
- import { ConfigModule } from '@nestjs/config';
3
1
  import { LocaleModule } from '@hed-hog/api-locale';
4
2
  import { PaginationModule } from '@hed-hog/api-pagination';
5
3
  import { PrismaModule } from '@hed-hog/api-prisma';
4
+ import { forwardRef, Module } from '@nestjs/common';
5
+ import { ConfigModule } from '@nestjs/config';
6
+ import { LmsCertificateModule } from './certificate/certificate.module';
7
+ import { ClassGroupModule } from './class-group/class-group.module';
8
+ import { CourseModule } from './course/course.module';
9
+ import { LmsDashboardModule } from './dashboard/dashboard.module';
10
+ import { EnterpriseModule } from './enterprise/enterprise.module';
11
+ import { EvaluationModule } from './evaluation/evaluation.module';
12
+ import { ExamModule } from './exam/exam.module';
13
+ import { InstructorModule } from './instructor/instructor.module';
14
+ import { LmsReportsModule } from './reports/reports.module';
15
+ import { TrainingModule } from './training/training.module';
6
16
 
7
17
  @Module({
8
18
  imports: [
9
19
  ConfigModule.forRoot(),
10
20
  forwardRef(() => PaginationModule),
11
21
  forwardRef(() => PrismaModule),
12
- forwardRef(() => LocaleModule)
13
- ]
22
+ forwardRef(() => LocaleModule),
23
+ forwardRef(() => LmsCertificateModule),
24
+ forwardRef(() => ClassGroupModule),
25
+ forwardRef(() => CourseModule),
26
+ forwardRef(() => LmsDashboardModule),
27
+ forwardRef(() => EnterpriseModule),
28
+ forwardRef(() => EvaluationModule),
29
+ forwardRef(() => ExamModule),
30
+ forwardRef(() => InstructorModule),
31
+ forwardRef(() => LmsReportsModule),
32
+ forwardRef(() => TrainingModule),
33
+ ],
34
+ exports: [
35
+ forwardRef(() => ClassGroupModule),
36
+ forwardRef(() => LmsCertificateModule),
37
+ forwardRef(() => CourseModule),
38
+ forwardRef(() => LmsDashboardModule),
39
+ forwardRef(() => EnterpriseModule),
40
+ forwardRef(() => EvaluationModule),
41
+ forwardRef(() => ExamModule),
42
+ forwardRef(() => InstructorModule),
43
+ forwardRef(() => LmsReportsModule),
44
+ forwardRef(() => TrainingModule),
45
+ ],
14
46
  })
15
47
  export class LmsModule {}
@@ -0,0 +1,14 @@
1
+ import { Role } from '@hed-hog/api';
2
+ import { Controller, Get, Query } from '@nestjs/common';
3
+ import { LmsReportsService } from './reports.service';
4
+
5
+ @Role()
6
+ @Controller('lms/reports')
7
+ export class LmsReportsController {
8
+ constructor(private readonly reportsService: LmsReportsService) {}
9
+
10
+ @Get()
11
+ getReports(@Query('period') period?: string) {
12
+ return this.reportsService.getReports(period ?? '12m');
13
+ }
14
+ }
@@ -0,0 +1,12 @@
1
+ import { PrismaModule } from '@hed-hog/api-prisma';
2
+ import { forwardRef, Module } from '@nestjs/common';
3
+ import { LmsReportsController } from './reports.controller';
4
+ import { LmsReportsService } from './reports.service';
5
+
6
+ @Module({
7
+ imports: [forwardRef(() => PrismaModule)],
8
+ controllers: [LmsReportsController],
9
+ providers: [LmsReportsService],
10
+ exports: [LmsReportsService],
11
+ })
12
+ export class LmsReportsModule {}