@hed-hog/lms 0.0.319 → 0.0.321

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 (128) hide show
  1. package/dist/class-group/class-group.controller.d.ts +64 -1
  2. package/dist/class-group/class-group.controller.d.ts.map +1 -1
  3. package/dist/class-group/class-group.controller.js +35 -0
  4. package/dist/class-group/class-group.controller.js.map +1 -1
  5. package/dist/class-group/class-group.service.d.ts +66 -1
  6. package/dist/class-group/class-group.service.d.ts.map +1 -1
  7. package/dist/class-group/class-group.service.js +164 -13
  8. package/dist/class-group/class-group.service.js.map +1 -1
  9. package/dist/class-group/dto/create-class-group.dto.d.ts.map +1 -1
  10. package/dist/class-group/dto/create-class-group.dto.js +2 -1
  11. package/dist/class-group/dto/create-class-group.dto.js.map +1 -1
  12. package/dist/class-group/dto/material.dto.d.ts +18 -0
  13. package/dist/class-group/dto/material.dto.d.ts.map +1 -0
  14. package/dist/class-group/dto/material.dto.js +86 -0
  15. package/dist/class-group/dto/material.dto.js.map +1 -0
  16. package/dist/course/course.service.d.ts +2 -0
  17. package/dist/course/course.service.d.ts.map +1 -1
  18. package/dist/course/course.service.js +27 -2
  19. package/dist/course/course.service.js.map +1 -1
  20. package/dist/course/dto/create-course.dto.d.ts +2 -2
  21. package/dist/course/dto/create-course.dto.d.ts.map +1 -1
  22. package/dist/course/dto/create-course.dto.js.map +1 -1
  23. package/dist/enterprise/enterprise.controller.d.ts +7 -1
  24. package/dist/enterprise/enterprise.controller.d.ts.map +1 -1
  25. package/dist/enterprise/enterprise.controller.js +72 -2
  26. package/dist/enterprise/enterprise.controller.js.map +1 -1
  27. package/dist/enterprise/enterprise.module.d.ts.map +1 -1
  28. package/dist/enterprise/enterprise.module.js +2 -1
  29. package/dist/enterprise/enterprise.module.js.map +1 -1
  30. package/dist/enterprise/enterprise.service.d.ts +3 -0
  31. package/dist/enterprise/enterprise.service.d.ts.map +1 -1
  32. package/dist/enterprise/enterprise.service.js +84 -1
  33. package/dist/enterprise/enterprise.service.js.map +1 -1
  34. package/dist/enterprise/training/enterprise-training.module.d.ts +3 -0
  35. package/dist/enterprise/training/enterprise-training.module.d.ts.map +1 -0
  36. package/dist/enterprise/training/enterprise-training.module.js +40 -0
  37. package/dist/enterprise/training/enterprise-training.module.js.map +1 -0
  38. package/dist/enterprise/training/training-admin.controller.d.ts +525 -0
  39. package/dist/enterprise/training/training-admin.controller.d.ts.map +1 -0
  40. package/dist/enterprise/training/training-admin.controller.js +385 -0
  41. package/dist/enterprise/training/training-admin.controller.js.map +1 -0
  42. package/dist/enterprise/training/training-admin.service.d.ts +582 -0
  43. package/dist/enterprise/training/training-admin.service.d.ts.map +1 -0
  44. package/dist/enterprise/training/training-admin.service.js +2283 -0
  45. package/dist/enterprise/training/training-admin.service.js.map +1 -0
  46. package/dist/enterprise/training/training-instructor.controller.d.ts +260 -0
  47. package/dist/enterprise/training/training-instructor.controller.d.ts.map +1 -0
  48. package/dist/enterprise/training/training-instructor.controller.js +199 -0
  49. package/dist/enterprise/training/training-instructor.controller.js.map +1 -0
  50. package/dist/enterprise/training/training-instructor.service.d.ts +280 -0
  51. package/dist/enterprise/training/training-instructor.service.d.ts.map +1 -0
  52. package/dist/enterprise/training/training-instructor.service.js +1218 -0
  53. package/dist/enterprise/training/training-instructor.service.js.map +1 -0
  54. package/dist/enterprise/training/training-student.controller.d.ts +168 -0
  55. package/dist/enterprise/training/training-student.controller.d.ts.map +1 -0
  56. package/dist/enterprise/training/training-student.controller.js +104 -0
  57. package/dist/enterprise/training/training-student.controller.js.map +1 -0
  58. package/dist/enterprise/training/training-student.service.d.ts +185 -0
  59. package/dist/enterprise/training/training-student.service.d.ts.map +1 -0
  60. package/dist/enterprise/training/training-student.service.js +674 -0
  61. package/dist/enterprise/training/training-student.service.js.map +1 -0
  62. package/dist/enterprise/training/training-viewer.controller.d.ts +298 -0
  63. package/dist/enterprise/training/training-viewer.controller.d.ts.map +1 -0
  64. package/dist/enterprise/training/training-viewer.controller.js +178 -0
  65. package/dist/enterprise/training/training-viewer.controller.js.map +1 -0
  66. package/dist/evaluation/dto/create-evaluation-topic.dto.d.ts +18 -0
  67. package/dist/evaluation/dto/create-evaluation-topic.dto.d.ts.map +1 -0
  68. package/dist/evaluation/dto/create-evaluation-topic.dto.js +59 -0
  69. package/dist/evaluation/dto/create-evaluation-topic.dto.js.map +1 -0
  70. package/dist/evaluation/dto/update-evaluation-topic.dto.d.ts +6 -0
  71. package/dist/evaluation/dto/update-evaluation-topic.dto.d.ts.map +1 -0
  72. package/dist/evaluation/dto/update-evaluation-topic.dto.js +9 -0
  73. package/dist/evaluation/dto/update-evaluation-topic.dto.js.map +1 -0
  74. package/dist/evaluation/evaluation.controller.d.ts +66 -0
  75. package/dist/evaluation/evaluation.controller.d.ts.map +1 -1
  76. package/dist/evaluation/evaluation.controller.js +73 -0
  77. package/dist/evaluation/evaluation.controller.js.map +1 -1
  78. package/dist/evaluation/evaluation.service.d.ts +71 -0
  79. package/dist/evaluation/evaluation.service.d.ts.map +1 -1
  80. package/dist/evaluation/evaluation.service.js +121 -0
  81. package/dist/evaluation/evaluation.service.js.map +1 -1
  82. package/dist/instructor/instructor.service.js +6 -6
  83. package/dist/instructor/instructor.service.js.map +1 -1
  84. package/dist/lms.module.d.ts.map +1 -1
  85. package/dist/lms.module.js +3 -0
  86. package/dist/lms.module.js.map +1 -1
  87. package/hedhog/data/menu.yaml +19 -2
  88. package/hedhog/data/route.yaml +730 -0
  89. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +74 -8
  90. package/hedhog/frontend/app/_components/course-avatar.tsx.ejs +10 -30
  91. package/hedhog/frontend/app/classes/[id]/_components/event-summary-popover.tsx.ejs +3 -3
  92. package/hedhog/frontend/app/classes/[id]/_components/quick-create-session-popover.tsx.ejs +1 -1
  93. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +2141 -308
  94. package/hedhog/frontend/app/classes/page.tsx.ejs +8 -7
  95. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +14 -1
  96. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +6 -2
  97. package/hedhog/frontend/app/evaluations/_components/evaluation-topic-form-sheet.tsx.ejs +201 -0
  98. package/hedhog/frontend/app/evaluations/_components/evaluation-topic-types.ts.ejs +49 -0
  99. package/hedhog/frontend/app/evaluations/page.tsx.ejs +483 -1112
  100. package/hedhog/frontend/app/instructors/page.tsx.ejs +22 -20
  101. package/hedhog/frontend/app/reports/evaluations/page.tsx.ejs +1278 -0
  102. package/hedhog/frontend/messages/en.json +98 -7
  103. package/hedhog/frontend/messages/pt.json +98 -7
  104. package/hedhog/table/course_class_group_material.yaml +45 -0
  105. package/package.json +8 -8
  106. package/src/class-group/class-group.controller.ts +30 -0
  107. package/src/class-group/class-group.service.ts +176 -5
  108. package/src/class-group/dto/create-class-group.dto.ts +2 -2
  109. package/src/class-group/dto/material.dto.ts +69 -0
  110. package/src/course/course.service.ts +41 -8
  111. package/src/course/dto/create-course.dto.ts +2 -2
  112. package/src/enterprise/enterprise.controller.ts +62 -1
  113. package/src/enterprise/enterprise.module.ts +2 -1
  114. package/src/enterprise/enterprise.service.ts +84 -1
  115. package/src/enterprise/training/enterprise-training.module.ts +27 -0
  116. package/src/enterprise/training/training-admin.controller.ts +278 -0
  117. package/src/enterprise/training/training-admin.service.ts +2523 -0
  118. package/src/enterprise/training/training-instructor.controller.ts +141 -0
  119. package/src/enterprise/training/training-instructor.service.ts +1303 -0
  120. package/src/enterprise/training/training-student.controller.ts +65 -0
  121. package/src/enterprise/training/training-student.service.ts +762 -0
  122. package/src/enterprise/training/training-viewer.controller.ts +115 -0
  123. package/src/evaluation/dto/create-evaluation-topic.dto.ts +48 -0
  124. package/src/evaluation/dto/update-evaluation-topic.dto.ts +6 -0
  125. package/src/evaluation/evaluation.controller.ts +63 -1
  126. package/src/evaluation/evaluation.service.ts +150 -1
  127. package/src/instructor/instructor.service.ts +4 -4
  128. package/src/lms.module.ts +3 -0
@@ -6,7 +6,6 @@ import {
6
6
  IsNotEmpty,
7
7
  IsOptional,
8
8
  IsString,
9
- IsUrl,
10
9
  Matches,
11
10
  MaxLength,
12
11
  Min,
@@ -128,7 +127,8 @@ export class CreateClassGroupDto {
128
127
  @IsOptional()
129
128
  location?: string;
130
129
 
131
- @IsUrl({ require_tld: false })
130
+ @IsString()
131
+ @MaxLength(500)
132
132
  @IsOptional()
133
133
  virtualRoomUrl?: string;
134
134
 
@@ -0,0 +1,69 @@
1
+ import {
2
+ IsEnum,
3
+ IsInt,
4
+ IsNotEmpty,
5
+ IsOptional,
6
+ IsString,
7
+ MaxLength,
8
+ Min
9
+ } from 'class-validator';
10
+
11
+ export type MaterialType = 'file' | 'link';
12
+
13
+ export class CreateMaterialDto {
14
+ @IsString()
15
+ @IsNotEmpty()
16
+ @MaxLength(255)
17
+ title: string;
18
+
19
+ @IsString()
20
+ @IsOptional()
21
+ description?: string;
22
+
23
+ @IsEnum(['file', 'link'])
24
+ materialType: MaterialType;
25
+
26
+ @IsInt()
27
+ @Min(1)
28
+ @IsOptional()
29
+ fileId?: number;
30
+
31
+ @IsString()
32
+ @MaxLength(500)
33
+ @IsOptional()
34
+ url?: string;
35
+
36
+ @IsInt()
37
+ @IsOptional()
38
+ sessionId?: number;
39
+
40
+ @IsInt()
41
+ @IsOptional()
42
+ sortOrder?: number;
43
+ }
44
+
45
+ export class UpdateMaterialDto {
46
+ @IsString()
47
+ @IsNotEmpty()
48
+ @MaxLength(255)
49
+ @IsOptional()
50
+ title?: string;
51
+
52
+ @IsString()
53
+ @IsOptional()
54
+ description?: string;
55
+
56
+ @IsInt()
57
+ @Min(1)
58
+ @IsOptional()
59
+ fileId?: number;
60
+
61
+ @IsString()
62
+ @MaxLength(500)
63
+ @IsOptional()
64
+ url?: string;
65
+
66
+ @IsInt()
67
+ @IsOptional()
68
+ sortOrder?: number;
69
+ }
@@ -825,21 +825,54 @@ export class CourseService {
825
825
  private async syncCourseImages(
826
826
  courseId: number,
827
827
  data: {
828
- logoFileId?: number;
829
- bannerFileId?: number;
828
+ logoFileId?: number | null;
829
+ bannerFileId?: number | null;
830
830
  },
831
831
  ) {
832
832
  if (data.logoFileId !== undefined) {
833
- await this.upsertCourseImage(courseId, data.logoFileId, 'course-logo');
833
+ await this.syncCourseImage(courseId, data.logoFileId, 'course-logo');
834
834
  }
835
835
 
836
836
  if (data.bannerFileId !== undefined) {
837
- await this.upsertCourseImage(
838
- courseId,
839
- data.bannerFileId,
840
- 'course-banner',
841
- );
837
+ await this.syncCourseImage(courseId, data.bannerFileId, 'course-banner');
838
+ }
839
+ }
840
+
841
+ private async syncCourseImage(
842
+ courseId: number,
843
+ fileId: number | null,
844
+ imageTypeSlug: CourseImageTypeSlug,
845
+ ) {
846
+ if (fileId === null) {
847
+ await this.removeCourseImage(courseId, imageTypeSlug);
848
+ return;
842
849
  }
850
+
851
+ await this.upsertCourseImage(courseId, fileId, imageTypeSlug);
852
+ }
853
+
854
+ private async removeCourseImage(
855
+ courseId: number,
856
+ imageTypeSlug: CourseImageTypeSlug,
857
+ ) {
858
+ const imageType = await this.prisma.image_type.findFirst({
859
+ where: {
860
+ slug: imageTypeSlug,
861
+ applies_to_course: true,
862
+ },
863
+ select: { id: true },
864
+ });
865
+
866
+ if (!imageType) {
867
+ return;
868
+ }
869
+
870
+ await this.prisma.course_image.deleteMany({
871
+ where: {
872
+ course_id: courseId,
873
+ image_type_id: imageType.id,
874
+ },
875
+ });
843
876
  }
844
877
 
845
878
  private async upsertCourseImage(
@@ -106,9 +106,9 @@ export class CreateCourseDto {
106
106
 
107
107
  @IsInt()
108
108
  @IsOptional()
109
- logoFileId?: number;
109
+ logoFileId?: number | null;
110
110
 
111
111
  @IsInt()
112
112
  @IsOptional()
113
- bannerFileId?: number;
113
+ bannerFileId?: number | null;
114
114
  }
@@ -1,15 +1,21 @@
1
1
  import { Public, Role, UserOptional } from '@hed-hog/api';
2
+ import { Locale } from '@hed-hog/api-locale';
3
+ import { FileService } from '@hed-hog/core';
2
4
  import {
3
5
  Body,
4
6
  Controller,
5
7
  Delete,
8
+ forwardRef,
6
9
  Get,
10
+ Inject,
7
11
  Param,
8
12
  ParseIntPipe,
9
13
  Patch,
10
14
  Post,
11
15
  Query,
16
+ Res,
12
17
  } from '@nestjs/common';
18
+ import { Response } from 'express';
13
19
  import { AddEnterpriseClassGroupDto } from './dto/add-enterprise-class-group.dto';
14
20
  import { AddEnterpriseCourseDto } from './dto/add-enterprise-course.dto';
15
21
  import { AddEnterpriseStudentDto } from './dto/add-enterprise-student.dto';
@@ -23,7 +29,62 @@ import { EnterpriseService } from './enterprise.service';
23
29
  @Role()
24
30
  @Controller('lms/enterprise')
25
31
  export class EnterpriseController {
26
- constructor(private readonly enterpriseService: EnterpriseService) {}
32
+ constructor(
33
+ private readonly enterpriseService: EnterpriseService,
34
+ @Inject(forwardRef(() => FileService))
35
+ private readonly fileService: FileService,
36
+ ) {}
37
+
38
+ @Public()
39
+ @Get('course/:courseId/logo')
40
+ async courseLogo(
41
+ @Param('courseId', ParseIntPipe) courseId: number,
42
+ @Res() res: Response,
43
+ @Locale() locale: string,
44
+ ) {
45
+ const fileId = await this.enterpriseService.getCourseLogoFileId(courseId);
46
+ const { stream, filename, mimetype, size } = await this.fileService.openById(locale, fileId);
47
+ res.set({
48
+ 'Content-Type': mimetype || 'image/octet-stream',
49
+ 'Content-Disposition': `inline; filename="${filename}"`,
50
+ });
51
+ if (typeof size === 'number') res.set('Content-Length', `${size}`);
52
+ stream.pipe(res);
53
+ }
54
+
55
+ @Public()
56
+ @Get('class/:classId/logo')
57
+ async classLogo(
58
+ @Param('classId', ParseIntPipe) classId: number,
59
+ @Res() res: Response,
60
+ @Locale() locale: string,
61
+ ) {
62
+ const fileId = await this.enterpriseService.getClassLogoFileId(classId);
63
+ const { stream, filename, mimetype, size } = await this.fileService.openById(locale, fileId);
64
+ res.set({
65
+ 'Content-Type': mimetype || 'image/octet-stream',
66
+ 'Content-Disposition': `inline; filename="${filename}"`,
67
+ });
68
+ if (typeof size === 'number') res.set('Content-Length', `${size}`);
69
+ stream.pipe(res);
70
+ }
71
+
72
+ @Public()
73
+ @Get('class/:classId/banner')
74
+ async classBanner(
75
+ @Param('classId', ParseIntPipe) classId: number,
76
+ @Res() res: Response,
77
+ @Locale() locale: string,
78
+ ) {
79
+ const fileId = await this.enterpriseService.getClassBannerFileId(classId);
80
+ const { stream, filename, mimetype, size } = await this.fileService.openById(locale, fileId);
81
+ res.set({
82
+ 'Content-Type': mimetype || 'image/octet-stream',
83
+ 'Content-Disposition': `inline; filename="${filename}"`,
84
+ });
85
+ if (typeof size === 'number') res.set('Content-Length', `${size}`);
86
+ stream.pipe(res);
87
+ }
27
88
 
28
89
  @Get()
29
90
  list(
@@ -1,10 +1,11 @@
1
+ import { FileModule } from '@hed-hog/core';
1
2
  import { PrismaModule } from '@hed-hog/api-prisma';
2
3
  import { forwardRef, Module } from '@nestjs/common';
3
4
  import { EnterpriseController } from './enterprise.controller';
4
5
  import { EnterpriseService } from './enterprise.service';
5
6
 
6
7
  @Module({
7
- imports: [forwardRef(() => PrismaModule)],
8
+ imports: [forwardRef(() => PrismaModule), forwardRef(() => FileModule)],
8
9
  controllers: [EnterpriseController],
9
10
  providers: [EnterpriseService],
10
11
  exports: [forwardRef(() => EnterpriseService)],
@@ -531,9 +531,33 @@ export class EnterpriseService {
531
531
  `Class group #${dto.course_class_group_id} is already linked to this enterprise`,
532
532
  );
533
533
 
534
- return this.prisma.enterprise_class_group.create({
534
+ const link = await this.prisma.enterprise_class_group.create({
535
535
  data: { enterprise_id: enterpriseId, ...(dto as any) },
536
536
  });
537
+
538
+ // Backfill existing enrolled students into enterprise_student
539
+ const enrolledPersons = await this.prisma.course_enrollment.findMany({
540
+ where: {
541
+ course_class_group_id: dto.course_class_group_id,
542
+ status: { not: 'cancelled' },
543
+ },
544
+ select: { person_id: true },
545
+ distinct: ['person_id'],
546
+ });
547
+
548
+ for (const { person_id } of enrolledPersons) {
549
+ const exists = await this.prisma.enterprise_student.findFirst({
550
+ where: { enterprise_id: enterpriseId, person_id },
551
+ select: { id: true },
552
+ });
553
+ if (!exists) {
554
+ await this.prisma.enterprise_student.create({
555
+ data: { enterprise_id: enterpriseId, person_id, status: 'active' },
556
+ });
557
+ }
558
+ }
559
+
560
+ return link;
537
561
  }
538
562
 
539
563
  async removeClassGroup(enterpriseId: number, classGroupId: number) {
@@ -691,6 +715,65 @@ export class EnterpriseService {
691
715
 
692
716
  // ─── Training Auth Profiles ──────────────────────────────────────────────────
693
717
 
718
+ async getCourseLogoFileId(courseId: number): Promise<number> {
719
+ const row = await this.prisma.course.findFirst({
720
+ where: { id: courseId },
721
+ select: {
722
+ course_image: {
723
+ where: { image_type: { slug: 'course-logo' } },
724
+ orderBy: { is_primary: 'desc' as const },
725
+ take: 1,
726
+ select: { file: { select: { id: true } } },
727
+ },
728
+ },
729
+ });
730
+ const fileId = row?.course_image?.[0]?.file?.id;
731
+ if (!fileId) throw new NotFoundException(`Logo not found for course #${courseId}`);
732
+ return fileId;
733
+ }
734
+
735
+ async getClassLogoFileId(classId: number): Promise<number> {
736
+ const row = await this.prisma.course_class_group.findFirst({
737
+ where: { id: classId },
738
+ select: {
739
+ course: {
740
+ select: {
741
+ course_image: {
742
+ where: { image_type: { slug: 'course-logo' } },
743
+ orderBy: { is_primary: 'desc' as const },
744
+ take: 1,
745
+ select: { file: { select: { id: true } } },
746
+ },
747
+ },
748
+ },
749
+ },
750
+ });
751
+ const fileId = row?.course?.course_image?.[0]?.file?.id;
752
+ if (!fileId) throw new NotFoundException(`Logo not found for class #${classId}`);
753
+ return fileId;
754
+ }
755
+
756
+ async getClassBannerFileId(classId: number): Promise<number> {
757
+ const row = await this.prisma.course_class_group.findFirst({
758
+ where: { id: classId },
759
+ select: {
760
+ course: {
761
+ select: {
762
+ course_image: {
763
+ where: { image_type: { slug: 'course-banner' } },
764
+ orderBy: { is_primary: 'desc' as const },
765
+ take: 1,
766
+ select: { file: { select: { id: true } } },
767
+ },
768
+ },
769
+ },
770
+ },
771
+ });
772
+ const fileId = row?.course?.course_image?.[0]?.file?.id;
773
+ if (!fileId) throw new NotFoundException(`Banner not found for class #${classId}`);
774
+ return fileId;
775
+ }
776
+
694
777
  async getProfilesForUser(userId: number): Promise<EnterpriseProfile[]> {
695
778
  const ROLE_TO_SLUG: Record<string, string> = {
696
779
  hr_manager: 'lms-enterprise-admin',
@@ -0,0 +1,27 @@
1
+ import { PrismaModule } from '@hed-hog/api-prisma';
2
+ import { FileModule } from '@hed-hog/core';
3
+ import { forwardRef, Module } from '@nestjs/common';
4
+ import { TrainingAdminController } from './training-admin.controller';
5
+ import { TrainingAdminService } from './training-admin.service';
6
+ import { TrainingInstructorController } from './training-instructor.controller';
7
+ import { TrainingInstructorService } from './training-instructor.service';
8
+ import { TrainingStudentController } from './training-student.controller';
9
+ import { TrainingStudentService } from './training-student.service';
10
+ import { TrainingViewerController } from './training-viewer.controller';
11
+
12
+ @Module({
13
+ imports: [forwardRef(() => PrismaModule), forwardRef(() => FileModule)],
14
+ controllers: [
15
+ TrainingAdminController,
16
+ TrainingViewerController,
17
+ TrainingInstructorController,
18
+ TrainingStudentController,
19
+ ],
20
+ providers: [TrainingAdminService, TrainingInstructorService, TrainingStudentService],
21
+ exports: [
22
+ forwardRef(() => TrainingAdminService),
23
+ forwardRef(() => TrainingInstructorService),
24
+ forwardRef(() => TrainingStudentService),
25
+ ],
26
+ })
27
+ export class EnterpriseTrainingModule {}
@@ -0,0 +1,278 @@
1
+ import { Role, User } from '@hed-hog/api';
2
+ import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Query } from '@nestjs/common';
3
+ import { TrainingAdminService } from './training-admin.service';
4
+
5
+ @Role()
6
+ @Controller('lms/enterprise/training/admin')
7
+ export class TrainingAdminController {
8
+ constructor(private readonly trainingAdminService: TrainingAdminService) {}
9
+
10
+ @Get()
11
+ getDashboard(
12
+ @User('id') userId: number,
13
+ @Query('enterpriseId', new ParseIntPipe({ optional: true })) enterpriseId?: number,
14
+ ) {
15
+ return this.trainingAdminService.getDashboard(userId, enterpriseId);
16
+ }
17
+
18
+ @Get('courses/stats')
19
+ getCourseStats(
20
+ @User('id') userId: number,
21
+ @Query('enterpriseId', new ParseIntPipe({ optional: true })) enterpriseId?: number,
22
+ ) {
23
+ return this.trainingAdminService.getCourseStats(userId, enterpriseId);
24
+ }
25
+
26
+ @Get('courses/:courseId')
27
+ getCourseDetail(
28
+ @User('id') userId: number,
29
+ @Param('courseId', ParseIntPipe) courseId: number,
30
+ @Query('enterpriseId', new ParseIntPipe({ optional: true })) enterpriseId?: number,
31
+ ) {
32
+ return this.trainingAdminService.getCourseDetail(userId, courseId, enterpriseId);
33
+ }
34
+
35
+ @Get('courses')
36
+ getCourses(
37
+ @User('id') userId: number,
38
+ @Query('enterpriseId', new ParseIntPipe({ optional: true })) enterpriseId?: number,
39
+ @Query('page', new ParseIntPipe({ optional: true })) page?: number,
40
+ @Query('pageSize', new ParseIntPipe({ optional: true })) pageSize?: number,
41
+ @Query('search') search?: string,
42
+ @Query('level') level?: string,
43
+ @Query('category') category?: string,
44
+ ) {
45
+ return this.trainingAdminService.getCourses(userId, { enterpriseId, page, pageSize, search, level, category });
46
+ }
47
+
48
+ @Get('class-groups')
49
+ getClassGroups(
50
+ @User('id') userId: number,
51
+ @Query('enterpriseId', new ParseIntPipe({ optional: true })) enterpriseId?: number,
52
+ @Query('search') search?: string,
53
+ @Query('status') status?: string,
54
+ @Query('deliveryMode') deliveryMode?: string,
55
+ @Query('instructorId', new ParseIntPipe({ optional: true })) instructorId?: number,
56
+ ) {
57
+ return this.trainingAdminService.getClassGroups(userId, {
58
+ enterpriseId,
59
+ search,
60
+ status,
61
+ deliveryMode,
62
+ instructorId,
63
+ });
64
+ }
65
+
66
+ @Get('class-groups/:id')
67
+ getClassGroupDetail(
68
+ @User('id') userId: number,
69
+ @Param('id', ParseIntPipe) classGroupId: number,
70
+ ) {
71
+ return this.trainingAdminService.getClassGroupDetail(userId, classGroupId);
72
+ }
73
+
74
+ @Get('class-groups/:id/students')
75
+ getClassGroupStudents(
76
+ @User('id') userId: number,
77
+ @Param('id', ParseIntPipe) classGroupId: number,
78
+ @Query('page', new ParseIntPipe({ optional: true })) page?: number,
79
+ @Query('pageSize', new ParseIntPipe({ optional: true })) pageSize?: number,
80
+ @Query('search') search?: string,
81
+ @Query('status') status?: string,
82
+ ) {
83
+ return this.trainingAdminService.getClassGroupStudents(userId, classGroupId, {
84
+ page,
85
+ pageSize,
86
+ search,
87
+ status,
88
+ });
89
+ }
90
+
91
+ @Get('class-groups/:id/sessions')
92
+ getClassGroupSessions(
93
+ @User('id') userId: number,
94
+ @Param('id', ParseIntPipe) classGroupId: number,
95
+ ) {
96
+ return this.trainingAdminService.getClassGroupSessions(userId, classGroupId);
97
+ }
98
+
99
+ @Get('class-groups/:id/materials')
100
+ getClassGroupMaterials(
101
+ @User('id') userId: number,
102
+ @Param('id', ParseIntPipe) classGroupId: number,
103
+ ) {
104
+ return this.trainingAdminService.getClassGroupMaterials(userId, classGroupId);
105
+ }
106
+
107
+ @Get('license-info')
108
+ getLicenseAvailability(@User('id') userId: number) {
109
+ return this.trainingAdminService.getLicenseAvailability(userId);
110
+ }
111
+
112
+ @Get('licenses')
113
+ listLicenses(
114
+ @User('id') userId: number,
115
+ @Query('page', new ParseIntPipe({ optional: true })) page?: number,
116
+ @Query('pageSize', new ParseIntPipe({ optional: true })) pageSize?: number,
117
+ @Query('search') search?: string,
118
+ @Query('status') status?: string,
119
+ @Query('department') department?: string,
120
+ ) {
121
+ return this.trainingAdminService.listLicenses(userId, { page, pageSize, search, status, department });
122
+ }
123
+
124
+ @Post('licenses')
125
+ assignLicense(
126
+ @User('id') userId: number,
127
+ @Body() dto: { name: string; email?: string; department?: string },
128
+ ) {
129
+ return this.trainingAdminService.assignLicense(userId, dto);
130
+ }
131
+
132
+ @Delete('licenses/:personId')
133
+ revokeLicense(
134
+ @User('id') userId: number,
135
+ @Param('personId', ParseIntPipe) personId: number,
136
+ ) {
137
+ return this.trainingAdminService.revokeLicense(userId, personId);
138
+ }
139
+
140
+ @Get('class-groups/:id/enrollable-students')
141
+ getEnrollableStudents(
142
+ @User('id') userId: number,
143
+ @Param('id', ParseIntPipe) classGroupId: number,
144
+ @Query('search') search?: string,
145
+ ) {
146
+ return this.trainingAdminService.getEnrollableStudents(userId, classGroupId, search);
147
+ }
148
+
149
+ @Post('class-groups/:id/enroll')
150
+ enrollExistingStudent(
151
+ @User('id') userId: number,
152
+ @Param('id', ParseIntPipe) classGroupId: number,
153
+ @Body() dto: { personId: number },
154
+ ) {
155
+ return this.trainingAdminService.enrollExistingStudent(userId, classGroupId, dto.personId);
156
+ }
157
+
158
+ @Post('class-groups/:id/enroll/create')
159
+ createAndEnrollNewStudent(
160
+ @User('id') userId: number,
161
+ @Param('id', ParseIntPipe) classGroupId: number,
162
+ @Body() dto: { name: string; email?: string; department?: string },
163
+ ) {
164
+ return this.trainingAdminService.createAndEnrollNewStudent(userId, classGroupId, dto);
165
+ }
166
+
167
+ @Get('students/stats')
168
+ getStudentStats(
169
+ @User('id') userId: number,
170
+ @Query('enterpriseId', new ParseIntPipe({ optional: true })) enterpriseId?: number,
171
+ ) {
172
+ return this.trainingAdminService.getStudentStats(userId, enterpriseId);
173
+ }
174
+
175
+ @Get('students/:personId')
176
+ getStudentDetail(
177
+ @User('id') userId: number,
178
+ @Param('personId', ParseIntPipe) personId: number,
179
+ @Query('enterpriseId', new ParseIntPipe({ optional: true })) enterpriseId?: number,
180
+ ) {
181
+ return this.trainingAdminService.getStudentDetail(userId, personId, enterpriseId);
182
+ }
183
+
184
+ @Get('students')
185
+ getStudents(
186
+ @User('id') userId: number,
187
+ @Query('enterpriseId', new ParseIntPipe({ optional: true })) enterpriseId?: number,
188
+ @Query('page', new ParseIntPipe({ optional: true })) page?: number,
189
+ @Query('pageSize', new ParseIntPipe({ optional: true })) pageSize?: number,
190
+ @Query('search') search?: string,
191
+ @Query('status') status?: string,
192
+ ) {
193
+ return this.trainingAdminService.getStudents(userId, { enterpriseId, page, pageSize, search, status });
194
+ }
195
+
196
+ @Get('admins/stats')
197
+ getAdminStats(
198
+ @User('id') userId: number,
199
+ @Query('enterpriseId', new ParseIntPipe({ optional: true })) enterpriseId?: number,
200
+ ) {
201
+ return this.trainingAdminService.getAdminStats(userId, enterpriseId);
202
+ }
203
+
204
+ @Get('admins')
205
+ getAdmins(
206
+ @User('id') userId: number,
207
+ @Query('enterpriseId', new ParseIntPipe({ optional: true })) enterpriseId?: number,
208
+ @Query('page', new ParseIntPipe({ optional: true })) page?: number,
209
+ @Query('pageSize', new ParseIntPipe({ optional: true })) pageSize?: number,
210
+ @Query('search') search?: string,
211
+ @Query('role') role?: string,
212
+ ) {
213
+ return this.trainingAdminService.getAdmins(userId, { enterpriseId, page, pageSize, search, role });
214
+ }
215
+
216
+ @Post('admins')
217
+ addAdmin(
218
+ @User('id') userId: number,
219
+ @Body() dto: { name: string; email?: string; department?: string; role?: string },
220
+ ) {
221
+ return this.trainingAdminService.addAdmin(userId, dto);
222
+ }
223
+
224
+ @Patch('admins/:id')
225
+ updateAdminRole(
226
+ @User('id') userId: number,
227
+ @Param('id', ParseIntPipe) enterpriseUserId: number,
228
+ @Body('role') role: string,
229
+ ) {
230
+ return this.trainingAdminService.updateAdminRole(userId, enterpriseUserId, role);
231
+ }
232
+
233
+ @Delete('admins/:id')
234
+ removeAdmin(
235
+ @User('id') userId: number,
236
+ @Param('id', ParseIntPipe) enterpriseUserId: number,
237
+ ) {
238
+ return this.trainingAdminService.removeAdmin(userId, enterpriseUserId);
239
+ }
240
+
241
+ @Get('relatorios')
242
+ getAdminReports(
243
+ @User('id') userId: number,
244
+ @Query('enterpriseId', new ParseIntPipe({ optional: true })) enterpriseId?: number,
245
+ @Query('dateFrom') dateFrom?: string,
246
+ @Query('dateTo') dateTo?: string,
247
+ ) {
248
+ return this.trainingAdminService.getAdminReports(userId, { enterpriseId, dateFrom, dateTo });
249
+ }
250
+
251
+ @Get('avaliacoes/stats')
252
+ getEvaluationStats(
253
+ @User('id') userId: number,
254
+ @Query('enterpriseId', new ParseIntPipe({ optional: true })) enterpriseId?: number,
255
+ ) {
256
+ return this.trainingAdminService.getEvaluationStats(userId, enterpriseId);
257
+ }
258
+
259
+ @Get('avaliacoes')
260
+ getEvaluationClassGroups(
261
+ @User('id') userId: number,
262
+ @Query('enterpriseId', new ParseIntPipe({ optional: true })) enterpriseId?: number,
263
+ @Query('page', new ParseIntPipe({ optional: true })) page?: number,
264
+ @Query('pageSize', new ParseIntPipe({ optional: true })) pageSize?: number,
265
+ @Query('search') search?: string,
266
+ ) {
267
+ return this.trainingAdminService.getEvaluationClassGroups(userId, { enterpriseId, page, pageSize, search });
268
+ }
269
+
270
+ @Get('avaliacoes/:classGroupId')
271
+ getEvaluationClassGroupDetail(
272
+ @User('id') userId: number,
273
+ @Param('classGroupId', ParseIntPipe) classGroupId: number,
274
+ @Query('enterpriseId', new ParseIntPipe({ optional: true })) enterpriseId?: number,
275
+ ) {
276
+ return this.trainingAdminService.getEvaluationClassGroupDetail(userId, classGroupId, enterpriseId);
277
+ }
278
+ }