@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,712 @@
1
+ import { PrismaService } from '@hed-hog/api-prisma';
2
+ import {
3
+ ConflictException,
4
+ Injectable,
5
+ NotFoundException,
6
+ } from '@nestjs/common';
7
+ import { AddEnterpriseClassGroupDto } from './dto/add-enterprise-class-group.dto';
8
+ import { AddEnterpriseCourseDto } from './dto/add-enterprise-course.dto';
9
+ import { AddEnterpriseStudentDto } from './dto/add-enterprise-student.dto';
10
+ import { AddEnterpriseUserDto } from './dto/add-enterprise-user.dto';
11
+ import { CreateEnterpriseDto } from './dto/create-enterprise.dto';
12
+ import { UpdateEnterpriseStudentDto } from './dto/update-enterprise-student.dto';
13
+ import { UpdateEnterpriseUserDto } from './dto/update-enterprise-user.dto';
14
+ import { UpdateEnterpriseDto } from './dto/update-enterprise.dto';
15
+
16
+ type PaginationParams = {
17
+ page?: number;
18
+ pageSize?: number;
19
+ search?: string;
20
+ status?: string;
21
+ role?: string;
22
+ crmPersonId?: number;
23
+ };
24
+
25
+ /** Maps enterprise_user.role to the global role slug that should be granted. */
26
+ const ENTERPRISE_ROLE_TO_GLOBAL_SLUG: Partial<Record<string, string>> = {
27
+ enterprise_admin: 'lms-enterprise-admin',
28
+ hr_manager: 'lms-enterprise-admin',
29
+ };
30
+
31
+ function paginate(params: PaginationParams) {
32
+ const page = Math.max(Number(params.page) || 1, 1);
33
+ const pageSize = Math.max(Number(params.pageSize) || 20, 1);
34
+ const skip = (page - 1) * pageSize;
35
+ return { page, pageSize, skip };
36
+ }
37
+
38
+ @Injectable()
39
+ export class EnterpriseService {
40
+ constructor(private readonly prisma: PrismaService) {}
41
+
42
+ async list(params: PaginationParams) {
43
+ const { page, pageSize, skip } = paginate(params);
44
+
45
+ const where: any = {};
46
+ if (params.search?.trim()) {
47
+ const s = params.search.trim();
48
+ where.OR = [
49
+ { name: { contains: s, mode: 'insensitive' } },
50
+ { slug: { contains: s, mode: 'insensitive' } },
51
+ ];
52
+ }
53
+ if (params.status && params.status !== 'all') {
54
+ where.status = params.status;
55
+ }
56
+ if (params.crmPersonId) {
57
+ where.crm_person_id = params.crmPersonId;
58
+ }
59
+
60
+ const [data, total] = await Promise.all([
61
+ this.prisma.enterprise.findMany({
62
+ where,
63
+ skip,
64
+ take: pageSize,
65
+ orderBy: { name: 'asc' },
66
+ include: {
67
+ person: { select: { id: true, name: true } },
68
+ _count: {
69
+ select: {
70
+ enterprise_user: true,
71
+ enterprise_course: true,
72
+ enterprise_class_group: true,
73
+ },
74
+ },
75
+ },
76
+ }),
77
+ this.prisma.enterprise.count({ where }),
78
+ ]);
79
+
80
+ return {
81
+ total,
82
+ page,
83
+ pageSize,
84
+ lastPage: Math.max(1, Math.ceil(total / pageSize)),
85
+ data: data.map((e) => this.mapEnterprise(e)),
86
+ };
87
+ }
88
+
89
+ async getById(id: number) {
90
+ const e = await this.prisma.enterprise.findUnique({
91
+ where: { id },
92
+ include: {
93
+ person: {
94
+ select: {
95
+ id: true,
96
+ name: true,
97
+ type: true,
98
+ },
99
+ },
100
+ _count: {
101
+ select: {
102
+ enterprise_user: true,
103
+ enterprise_course: true,
104
+ enterprise_class_group: true,
105
+ },
106
+ },
107
+ },
108
+ });
109
+ if (!e) return null;
110
+
111
+ // Compute role breakdowns and fetch person_company separately
112
+ // (person_company shares the same PK as person — no Prisma reverse relation)
113
+ const [studentsCount, managersCount, adminsCount, personCompany] = await Promise.all([
114
+ this.prisma.enterprise_student.count({
115
+ where: { enterprise_id: id },
116
+ }),
117
+ this.prisma.enterprise_user.count({
118
+ where: { enterprise_id: id, role: 'hr_manager' as any },
119
+ }),
120
+ this.prisma.enterprise_user.count({
121
+ where: { enterprise_id: id, role: 'enterprise_admin' as any },
122
+ }),
123
+ e.person?.id
124
+ ? this.prisma.person_company.findUnique({
125
+ where: { id: e.person.id },
126
+ select: {
127
+ trade_name: true,
128
+ industry: true,
129
+ website: true,
130
+ account_lifecycle_stage: true,
131
+ },
132
+ })
133
+ : Promise.resolve(null),
134
+ ]);
135
+
136
+ return this.mapEnterprise(e, { studentsCount, managersCount, adminsCount, personCompany });
137
+ }
138
+
139
+ async create(dto: CreateEnterpriseDto) {
140
+ try {
141
+ return await this.prisma.enterprise.create({ data: dto as any });
142
+ } catch (err: any) {
143
+ if (err?.code === 'P2002') {
144
+ throw new ConflictException(
145
+ `An enterprise with slug "${(dto as any).slug}" already exists.`,
146
+ );
147
+ }
148
+ throw err;
149
+ }
150
+ }
151
+
152
+ async update(id: number, dto: UpdateEnterpriseDto) {
153
+ await this.assertEnterpriseExists(id);
154
+ return this.prisma.enterprise.update({ where: { id }, data: dto as any });
155
+ }
156
+
157
+ async delete(id: number) {
158
+ await this.assertEnterpriseExists(id);
159
+ return this.prisma.enterprise.delete({ where: { id } });
160
+ }
161
+
162
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
163
+
164
+ private async assertEnterpriseExists(id: number) {
165
+ const exists = await this.prisma.enterprise.findUnique({
166
+ where: { id },
167
+ select: { id: true },
168
+ });
169
+ if (!exists) throw new NotFoundException(`Enterprise #${id} not found`);
170
+ return exists;
171
+ }
172
+
173
+ private async resolveRoleId(slug: string): Promise<number | null> {
174
+ const role = await this.prisma.role.findUnique({
175
+ where: { slug },
176
+ select: { id: true },
177
+ });
178
+ return role?.id ?? null;
179
+ }
180
+
181
+ private async upsertRoleUser(userId: number, roleSlug: string) {
182
+ const roleId = await this.resolveRoleId(roleSlug);
183
+ if (!roleId) return; // role not seeded yet — skip silently
184
+ const existing = await this.prisma.role_user.findFirst({
185
+ where: { user_id: userId, role_id: roleId },
186
+ select: { id: true },
187
+ });
188
+ if (!existing) {
189
+ await this.prisma.role_user.create({
190
+ data: { user_id: userId, role_id: roleId },
191
+ });
192
+ }
193
+ }
194
+
195
+ // ─── Users ───────────────────────────────────────────────────────────────────
196
+
197
+ async listUsers(enterpriseId: number, params: PaginationParams) {
198
+ await this.assertEnterpriseExists(enterpriseId);
199
+ const { page, pageSize, skip } = paginate(params);
200
+
201
+ const where: any = { enterprise_id: enterpriseId };
202
+ if (params.search?.trim()) {
203
+ const s = params.search.trim();
204
+ where.user = {
205
+ OR: [
206
+ { name: { contains: s, mode: 'insensitive' } },
207
+ {
208
+ user_identifier: {
209
+ some: { type: 'email', value: { contains: s, mode: 'insensitive' } },
210
+ },
211
+ },
212
+ ],
213
+ };
214
+ }
215
+ if (params.status) where.status = params.status;
216
+ if (params.role) where.role = params.role;
217
+
218
+ const [rows, total] = await Promise.all([
219
+ this.prisma.enterprise_user.findMany({
220
+ where,
221
+ skip,
222
+ take: pageSize,
223
+ orderBy: { created_at: 'desc' },
224
+ include: {
225
+ user: {
226
+ select: {
227
+ id: true,
228
+ name: true,
229
+ last_login_at: true,
230
+ user_identifier: {
231
+ where: { type: 'email' },
232
+ select: { value: true },
233
+ take: 1,
234
+ },
235
+ },
236
+ },
237
+ },
238
+ }),
239
+ this.prisma.enterprise_user.count({ where }),
240
+ ]);
241
+
242
+ return {
243
+ total,
244
+ page,
245
+ pageSize,
246
+ lastPage: Math.max(1, Math.ceil(total / pageSize)),
247
+ data: rows.map((r) => ({
248
+ id: r.id,
249
+ userId: r.user_id,
250
+ personId: r.person_id,
251
+ name: r.user?.name ?? null,
252
+ email: r.user?.user_identifier?.[0]?.value ?? null,
253
+ role: r.role,
254
+ status: r.status,
255
+ lastAccessAt: r.user?.last_login_at ?? null,
256
+ })),
257
+ };
258
+ }
259
+
260
+ async addUser(enterpriseId: number, dto: AddEnterpriseUserDto) {
261
+ await this.assertEnterpriseExists(enterpriseId);
262
+
263
+ const user = await this.prisma.user.findUnique({
264
+ where: { id: dto.user_id },
265
+ select: { id: true },
266
+ });
267
+ if (!user) throw new NotFoundException(`User #${dto.user_id} not found`);
268
+
269
+ const duplicate = await this.prisma.enterprise_user.findFirst({
270
+ where: { enterprise_id: enterpriseId, user_id: dto.user_id },
271
+ select: { id: true },
272
+ });
273
+ if (duplicate)
274
+ throw new ConflictException(
275
+ `User #${dto.user_id} is already linked to this enterprise`,
276
+ );
277
+
278
+ const record = await this.prisma.enterprise_user.create({
279
+ data: { enterprise_id: enterpriseId, ...(dto as any) },
280
+ });
281
+
282
+ // Apply the corresponding global LMS role
283
+ const globalSlug = ENTERPRISE_ROLE_TO_GLOBAL_SLUG[dto.role];
284
+ if (globalSlug) await this.upsertRoleUser(dto.user_id, globalSlug);
285
+
286
+ return record;
287
+ }
288
+
289
+ async updateUser(
290
+ enterpriseId: number,
291
+ userId: number,
292
+ dto: UpdateEnterpriseUserDto,
293
+ ) {
294
+ await this.assertEnterpriseExists(enterpriseId);
295
+ const existing = await this.prisma.enterprise_user.findFirst({
296
+ where: { enterprise_id: enterpriseId, user_id: userId },
297
+ select: { id: true, role: true },
298
+ });
299
+ if (!existing)
300
+ throw new NotFoundException(
301
+ `User #${userId} is not linked to enterprise #${enterpriseId}`,
302
+ );
303
+
304
+ const updated = await this.prisma.enterprise_user.update({
305
+ where: { id: existing.id },
306
+ data: dto as any,
307
+ });
308
+
309
+ // If role changed, upsert the new global role
310
+ if (dto.role && dto.role !== existing.role) {
311
+ const globalSlug = ENTERPRISE_ROLE_TO_GLOBAL_SLUG[dto.role];
312
+ if (globalSlug) await this.upsertRoleUser(userId, globalSlug);
313
+ }
314
+
315
+ return updated;
316
+ }
317
+
318
+ async removeUser(enterpriseId: number, userId: number) {
319
+ await this.assertEnterpriseExists(enterpriseId);
320
+ const existing = await this.prisma.enterprise_user.findFirst({
321
+ where: { enterprise_id: enterpriseId, user_id: userId },
322
+ select: { id: true },
323
+ });
324
+ if (!existing)
325
+ throw new NotFoundException(
326
+ `User #${userId} is not linked to enterprise #${enterpriseId}`,
327
+ );
328
+ return this.prisma.enterprise_user.delete({
329
+ where: { id: existing.id },
330
+ });
331
+ }
332
+
333
+ // ─── Courses ─────────────────────────────────────────────────────────────────
334
+
335
+ async listCourses(enterpriseId: number, params: PaginationParams) {
336
+ await this.assertEnterpriseExists(enterpriseId);
337
+ const { page, pageSize, skip } = paginate(params);
338
+
339
+ const where: any = { enterprise_id: enterpriseId };
340
+ if (params.search?.trim() || params.status) {
341
+ where.course = {
342
+ ...(params.search?.trim() ? { title: { contains: params.search.trim(), mode: 'insensitive' } } : {}),
343
+ ...(params.status ? { status: params.status } : {}),
344
+ };
345
+ }
346
+
347
+ const [rows, total] = await Promise.all([
348
+ this.prisma.enterprise_course.findMany({
349
+ where,
350
+ skip,
351
+ take: pageSize,
352
+ orderBy: { contracted_at: 'desc' },
353
+ include: {
354
+ course: {
355
+ select: {
356
+ id: true,
357
+ title: true,
358
+ slug: true,
359
+ status: true,
360
+ level: true,
361
+ offering_type: true,
362
+ },
363
+ },
364
+ },
365
+ }),
366
+ this.prisma.enterprise_course.count({ where }),
367
+ ]);
368
+
369
+ return {
370
+ total,
371
+ page,
372
+ pageSize,
373
+ lastPage: Math.max(1, Math.ceil(total / pageSize)),
374
+ data: rows.map((r) => ({
375
+ id: r.id,
376
+ courseId: r.course_id,
377
+ contractedAt: r.contracted_at,
378
+ title: r.course?.title ?? null,
379
+ slug: r.course?.slug ?? null,
380
+ status: r.course?.status ?? null,
381
+ level: r.course?.level ?? null,
382
+ modality: r.course?.offering_type ?? null,
383
+ })),
384
+ };
385
+ }
386
+
387
+ async addCourse(enterpriseId: number, dto: AddEnterpriseCourseDto) {
388
+ await this.assertEnterpriseExists(enterpriseId);
389
+
390
+ const course = await this.prisma.course.findUnique({
391
+ where: { id: dto.course_id },
392
+ select: { id: true },
393
+ });
394
+ if (!course) throw new NotFoundException(`Course #${dto.course_id} not found`);
395
+
396
+ const duplicate = await this.prisma.enterprise_course.findFirst({
397
+ where: { enterprise_id: enterpriseId, course_id: dto.course_id },
398
+ select: { id: true },
399
+ });
400
+ if (duplicate)
401
+ throw new ConflictException(
402
+ `Course #${dto.course_id} is already linked to this enterprise`,
403
+ );
404
+
405
+ return this.prisma.enterprise_course.create({
406
+ data: { enterprise_id: enterpriseId, ...(dto as any) },
407
+ });
408
+ }
409
+
410
+ async removeCourse(enterpriseId: number, courseId: number) {
411
+ await this.assertEnterpriseExists(enterpriseId);
412
+ const existing = await this.prisma.enterprise_course.findFirst({
413
+ where: { enterprise_id: enterpriseId, course_id: courseId },
414
+ select: { id: true },
415
+ });
416
+ if (!existing)
417
+ throw new NotFoundException(
418
+ `Course #${courseId} is not linked to enterprise #${enterpriseId}`,
419
+ );
420
+ return this.prisma.enterprise_course.delete({
421
+ where: { id: existing.id },
422
+ });
423
+ }
424
+
425
+ // ─── Class Groups ─────────────────────────────────────────────────────────────
426
+
427
+ async listClassGroups(enterpriseId: number, params: PaginationParams) {
428
+ await this.assertEnterpriseExists(enterpriseId);
429
+ const { page, pageSize, skip } = paginate(params);
430
+
431
+ const where: any = { enterprise_id: enterpriseId };
432
+ {
433
+ const s = params.search?.trim();
434
+ const hasSearch = Boolean(s);
435
+ const hasStatus = Boolean(params.status);
436
+ if (hasSearch || hasStatus) {
437
+ where.course_class_group = {
438
+ ...(hasSearch
439
+ ? { OR: [{ code: { contains: s, mode: 'insensitive' } }, { title: { contains: s, mode: 'insensitive' } }] }
440
+ : {}),
441
+ ...(hasStatus ? { status: params.status } : {}),
442
+ };
443
+ }
444
+ }
445
+
446
+ const [rows, total] = await Promise.all([
447
+ this.prisma.enterprise_class_group.findMany({
448
+ where,
449
+ skip,
450
+ take: pageSize,
451
+ orderBy: { created_at: 'desc' },
452
+ include: {
453
+ course_class_group: {
454
+ select: {
455
+ id: true,
456
+ code: true,
457
+ title: true,
458
+ status: true,
459
+ delivery_mode: true,
460
+ start_date: true,
461
+ end_date: true,
462
+ capacity: true,
463
+ course: { select: { id: true, title: true, slug: true } },
464
+ },
465
+ },
466
+ },
467
+ }),
468
+ this.prisma.enterprise_class_group.count({ where }),
469
+ ]);
470
+
471
+ return {
472
+ total,
473
+ page,
474
+ pageSize,
475
+ lastPage: Math.max(1, Math.ceil(total / pageSize)),
476
+ data: rows.map((r) => ({
477
+ id: r.id,
478
+ courseClassGroupId: r.course_class_group_id,
479
+ code: r.course_class_group?.code ?? null,
480
+ title: r.course_class_group?.title ?? null,
481
+ status: r.course_class_group?.status ?? null,
482
+ deliveryMode: r.course_class_group?.delivery_mode ?? null,
483
+ startDate: r.course_class_group?.start_date ?? null,
484
+ endDate: r.course_class_group?.end_date ?? null,
485
+ capacity: r.course_class_group?.capacity ?? null,
486
+ courseTitle: r.course_class_group?.course?.title ?? null,
487
+ courseSlug: r.course_class_group?.course?.slug ?? null,
488
+ })),
489
+ };
490
+ }
491
+
492
+ async addClassGroup(enterpriseId: number, dto: AddEnterpriseClassGroupDto) {
493
+ await this.assertEnterpriseExists(enterpriseId);
494
+
495
+ const classGroup = await this.prisma.course_class_group.findUnique({
496
+ where: { id: dto.course_class_group_id },
497
+ select: { id: true },
498
+ });
499
+ if (!classGroup)
500
+ throw new NotFoundException(
501
+ `Class group #${dto.course_class_group_id} not found`,
502
+ );
503
+
504
+ const duplicate = await this.prisma.enterprise_class_group.findFirst({
505
+ where: { enterprise_id: enterpriseId, course_class_group_id: dto.course_class_group_id },
506
+ select: { id: true },
507
+ });
508
+ if (duplicate)
509
+ throw new ConflictException(
510
+ `Class group #${dto.course_class_group_id} is already linked to this enterprise`,
511
+ );
512
+
513
+ return this.prisma.enterprise_class_group.create({
514
+ data: { enterprise_id: enterpriseId, ...(dto as any) },
515
+ });
516
+ }
517
+
518
+ async removeClassGroup(enterpriseId: number, classGroupId: number) {
519
+ await this.assertEnterpriseExists(enterpriseId);
520
+ const existing = await this.prisma.enterprise_class_group.findFirst({
521
+ where: { enterprise_id: enterpriseId, course_class_group_id: classGroupId },
522
+ select: { id: true },
523
+ });
524
+ if (!existing)
525
+ throw new NotFoundException(
526
+ `Class group #${classGroupId} is not linked to enterprise #${enterpriseId}`,
527
+ );
528
+ return this.prisma.enterprise_class_group.delete({
529
+ where: { id: existing.id },
530
+ });
531
+ }
532
+
533
+ // ─── Students ─────────────────────────────────────────────────────────────────
534
+
535
+ async listStudents(enterpriseId: number, params: PaginationParams) {
536
+ await this.assertEnterpriseExists(enterpriseId);
537
+ const { page, pageSize, skip } = paginate(params);
538
+
539
+ const where: any = { enterprise_id: enterpriseId };
540
+ if (params.search?.trim()) {
541
+ const s = params.search.trim();
542
+ where.person = {
543
+ name: { contains: s, mode: 'insensitive' },
544
+ };
545
+ }
546
+ if (params.status) where.status = params.status;
547
+
548
+ const [rows, total] = await Promise.all([
549
+ this.prisma.enterprise_student.findMany({
550
+ where,
551
+ skip,
552
+ take: pageSize,
553
+ orderBy: { created_at: 'desc' },
554
+ include: {
555
+ person: {
556
+ select: {
557
+ id: true,
558
+ name: true,
559
+ contact: {
560
+ where: { contact_type: { code: 'EMAIL' }, is_primary: true },
561
+ select: { value: true },
562
+ take: 1,
563
+ },
564
+ },
565
+ },
566
+ },
567
+ }),
568
+ this.prisma.enterprise_student.count({ where }),
569
+ ]);
570
+
571
+ return {
572
+ total,
573
+ page,
574
+ pageSize,
575
+ lastPage: Math.max(1, Math.ceil(total / pageSize)),
576
+ data: rows.map((r) => ({
577
+ id: r.id,
578
+ personId: r.person_id,
579
+ name: r.person?.name ?? null,
580
+ email: r.person?.contact?.[0]?.value ?? null,
581
+ status: r.status,
582
+ createdAt: r.created_at,
583
+ })),
584
+ };
585
+ }
586
+
587
+ async addStudent(enterpriseId: number, dto: AddEnterpriseStudentDto) {
588
+ await this.assertEnterpriseExists(enterpriseId);
589
+
590
+ const person = await this.prisma.person.findUnique({
591
+ where: { id: dto.person_id },
592
+ select: { id: true },
593
+ });
594
+ if (!person) throw new NotFoundException(`Person #${dto.person_id} not found`);
595
+
596
+ const duplicate = await this.prisma.enterprise_student.findFirst({
597
+ where: { enterprise_id: enterpriseId, person_id: dto.person_id },
598
+ select: { id: true },
599
+ });
600
+ if (duplicate)
601
+ throw new ConflictException(
602
+ `Person #${dto.person_id} is already a student of enterprise #${enterpriseId}`,
603
+ );
604
+
605
+ return this.prisma.enterprise_student.create({
606
+ data: { enterprise_id: enterpriseId, ...(dto as any) },
607
+ });
608
+ }
609
+
610
+ async updateStudent(
611
+ enterpriseId: number,
612
+ personId: number,
613
+ dto: UpdateEnterpriseStudentDto,
614
+ ) {
615
+ await this.assertEnterpriseExists(enterpriseId);
616
+ const existing = await this.prisma.enterprise_student.findFirst({
617
+ where: { enterprise_id: enterpriseId, person_id: personId },
618
+ select: { id: true },
619
+ });
620
+ if (!existing)
621
+ throw new NotFoundException(
622
+ `Person #${personId} is not a student of enterprise #${enterpriseId}`,
623
+ );
624
+ return this.prisma.enterprise_student.update({
625
+ where: { id: existing.id },
626
+ data: dto as any,
627
+ });
628
+ }
629
+
630
+ async removeStudent(enterpriseId: number, personId: number) {
631
+ await this.assertEnterpriseExists(enterpriseId);
632
+ const existing = await this.prisma.enterprise_student.findFirst({
633
+ where: { enterprise_id: enterpriseId, person_id: personId },
634
+ select: { id: true },
635
+ });
636
+ if (!existing)
637
+ throw new NotFoundException(
638
+ `Person #${personId} is not a student of enterprise #${enterpriseId}`,
639
+ );
640
+ return this.prisma.enterprise_student.delete({
641
+ where: { id: existing.id },
642
+ });
643
+ }
644
+
645
+ // ─── Stats / options ──────────────────────────────────────────────────────────
646
+
647
+ async getStats() {
648
+ const [total, active, trial, portalEnabled] = await Promise.all([
649
+ this.prisma.enterprise.count(),
650
+ this.prisma.enterprise.count({ where: { status: 'active' } }),
651
+ this.prisma.enterprise.count({ where: { status: 'trial' } }),
652
+ this.prisma.enterprise.count({ where: { portal_enabled: true } }),
653
+ ]);
654
+ return { total, active, trial, portalEnabled };
655
+ }
656
+
657
+ async getCrmOptions() {
658
+ const rows = await this.prisma.enterprise.findMany({
659
+ where: { crm_person_id: { not: null } },
660
+ select: { person: { select: { id: true, name: true } } },
661
+ distinct: ['crm_person_id'],
662
+ });
663
+ return rows
664
+ .filter((r) => r.person !== null)
665
+ .map((r) => ({ id: r.person!.id, name: r.person!.name ?? '' }))
666
+ .sort((a, b) => a.name.localeCompare(b.name));
667
+ }
668
+
669
+ // ─── Mapper ──────────────────────────────────────────────────────────────────
670
+
671
+ private mapEnterprise(
672
+ e: any,
673
+ extra?: {
674
+ studentsCount?: number;
675
+ managersCount?: number;
676
+ adminsCount?: number;
677
+ personCompany?: { trade_name?: string | null; industry?: string | null; website?: string | null; account_lifecycle_stage?: string | null } | null;
678
+ },
679
+ ) {
680
+ const { _count, person } = e;
681
+ const pc = extra?.personCompany ?? null;
682
+ return {
683
+ id: e.id as number,
684
+ name: e.name as string,
685
+ slug: e.slug as string,
686
+ status: e.status as string,
687
+ portalEnabled: e.portal_enabled as boolean,
688
+ licenseLimit: (e.license_limit as number | null) ?? null,
689
+ notes: (e.notes as string | null) ?? null,
690
+ createdAt: e.created_at as Date,
691
+ updatedAt: e.updated_at as Date,
692
+ crmAccountId: (person?.id as number | null) ?? null,
693
+ crmAccountName: (person?.name as string | null) ?? null,
694
+ crmAccount: person
695
+ ? {
696
+ id: person.id as number,
697
+ name: person.name as string,
698
+ tradeName: (pc?.trade_name as string | null) ?? null,
699
+ industry: (pc?.industry as string | null) ?? null,
700
+ website: (pc?.website as string | null) ?? null,
701
+ lifecycleStage: (pc?.account_lifecycle_stage as string | null) ?? null,
702
+ }
703
+ : null,
704
+ usersCount: (_count?.enterprise_user as number) ?? 0,
705
+ studentsCount: extra?.studentsCount ?? 0,
706
+ managersCount: extra?.managersCount ?? 0,
707
+ adminsCount: extra?.adminsCount ?? 0,
708
+ coursesCount: (_count?.enterprise_course as number) ?? 0,
709
+ classesCount: (_count?.enterprise_class_group as number) ?? 0,
710
+ };
711
+ }
712
+ }