@hed-hog/lms 0.0.331 → 0.0.338

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 (110) hide show
  1. package/dist/class-group/class-group.controller.d.ts +3 -3
  2. package/dist/class-group/class-group.service.d.ts +3 -3
  3. package/dist/course/course.service.d.ts.map +1 -1
  4. package/dist/course/course.service.js +12 -20
  5. package/dist/course/course.service.js.map +1 -1
  6. package/dist/enterprise/enterprise.controller.d.ts +72 -0
  7. package/dist/enterprise/enterprise.controller.d.ts.map +1 -1
  8. package/dist/enterprise/enterprise.controller.js +10 -0
  9. package/dist/enterprise/enterprise.controller.js.map +1 -1
  10. package/dist/enterprise/enterprise.service.d.ts +78 -0
  11. package/dist/enterprise/enterprise.service.d.ts.map +1 -1
  12. package/dist/enterprise/enterprise.service.js +413 -40
  13. package/dist/enterprise/enterprise.service.js.map +1 -1
  14. package/dist/enterprise/training/training-admin.controller.d.ts +6 -3
  15. package/dist/enterprise/training/training-admin.controller.d.ts.map +1 -1
  16. package/dist/enterprise/training/training-admin.controller.js +10 -6
  17. package/dist/enterprise/training/training-admin.controller.js.map +1 -1
  18. package/dist/enterprise/training/training-admin.service.d.ts +8 -2
  19. package/dist/enterprise/training/training-admin.service.d.ts.map +1 -1
  20. package/dist/enterprise/training/training-admin.service.js +108 -52
  21. package/dist/enterprise/training/training-admin.service.js.map +1 -1
  22. package/dist/enterprise/training/training-viewer.controller.d.ts +3 -0
  23. package/dist/enterprise/training/training-viewer.controller.d.ts.map +1 -1
  24. package/dist/evaluation/evaluation.controller.d.ts +4 -4
  25. package/dist/evaluation/evaluation.service.d.ts +4 -4
  26. package/dist/instructor/dto/create-instructor-skill.dto.d.ts +0 -4
  27. package/dist/instructor/dto/create-instructor-skill.dto.d.ts.map +1 -1
  28. package/dist/instructor/dto/create-instructor-skill.dto.js +0 -21
  29. package/dist/instructor/dto/create-instructor-skill.dto.js.map +1 -1
  30. package/dist/instructor/dto/update-instructor-skill.dto.d.ts +0 -4
  31. package/dist/instructor/dto/update-instructor-skill.dto.d.ts.map +1 -1
  32. package/dist/instructor/dto/update-instructor-skill.dto.js +0 -22
  33. package/dist/instructor/dto/update-instructor-skill.dto.js.map +1 -1
  34. package/dist/instructor/instructor-skill.controller.d.ts +4 -4
  35. package/dist/instructor/instructor-skill.service.d.ts +4 -7
  36. package/dist/instructor/instructor-skill.service.d.ts.map +1 -1
  37. package/dist/instructor/instructor-skill.service.js +2 -89
  38. package/dist/instructor/instructor-skill.service.js.map +1 -1
  39. package/dist/instructor/instructor.controller.d.ts +20 -0
  40. package/dist/instructor/instructor.controller.d.ts.map +1 -1
  41. package/dist/instructor/instructor.controller.js +19 -0
  42. package/dist/instructor/instructor.controller.js.map +1 -1
  43. package/dist/instructor/instructor.service.d.ts +25 -0
  44. package/dist/instructor/instructor.service.d.ts.map +1 -1
  45. package/dist/instructor/instructor.service.js +70 -18
  46. package/dist/instructor/instructor.service.js.map +1 -1
  47. package/dist/lms.module.d.ts.map +1 -1
  48. package/dist/lms.module.js.map +1 -1
  49. package/hedhog/data/route.yaml +23 -1
  50. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +42 -24
  51. package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +3 -3
  52. package/hedhog/frontend/app/_components/create-lms-instructor-sheet.tsx.ejs +591 -0
  53. package/hedhog/frontend/app/certificates/issued/page.tsx.ejs +6 -1
  54. package/hedhog/frontend/app/certificates/models/page.tsx.ejs +6 -1
  55. package/hedhog/frontend/app/classes/page.tsx.ejs +6 -1
  56. package/hedhog/frontend/app/courses/[id]/_components/CourseClassificationCard.tsx.ejs +3 -33
  57. package/hedhog/frontend/app/courses/[id]/_components/CourseContentCard.tsx.ejs +9 -9
  58. package/hedhog/frontend/app/courses/[id]/_components/CourseMainInfoCard.tsx.ejs +109 -0
  59. package/hedhog/frontend/app/courses/[id]/_components/CourseMultiEntityPicker.tsx.ejs +40 -13
  60. package/hedhog/frontend/app/courses/[id]/_components/CourseRelationsCard.tsx.ejs +76 -81
  61. package/hedhog/frontend/app/courses/[id]/_components/CourseSummaryCard.tsx.ejs +60 -0
  62. package/hedhog/frontend/app/courses/[id]/structure/_components/course-scheduled-classes-tab.tsx.ejs +406 -0
  63. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +134 -0
  64. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-course.tsx.ejs +113 -0
  65. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +314 -0
  66. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +174 -0
  67. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +242 -33
  68. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +185 -0
  69. package/hedhog/frontend/app/courses/page.tsx.ejs +6 -1
  70. package/hedhog/frontend/app/enterprise/_components/enterprise-activity-timeline.tsx.ejs +87 -0
  71. package/hedhog/frontend/app/enterprise/_components/enterprise-admin-create-sheet.tsx.ejs +4 -0
  72. package/hedhog/frontend/app/enterprise/_components/enterprise-administrators-tab.tsx.ejs +31 -5
  73. package/hedhog/frontend/app/enterprise/_components/enterprise-classes-tab.tsx.ejs +79 -20
  74. package/hedhog/frontend/app/enterprise/_components/enterprise-company-identity-card.tsx.ejs +11 -2
  75. package/hedhog/frontend/app/enterprise/_components/enterprise-course-edit-sheet.tsx.ejs +201 -0
  76. package/hedhog/frontend/app/enterprise/_components/enterprise-courses-tab.tsx.ejs +55 -24
  77. package/hedhog/frontend/app/enterprise/_components/enterprise-detail-sheet.tsx.ejs +430 -296
  78. package/hedhog/frontend/app/enterprise/_components/enterprise-mocks.ts.ejs +277 -0
  79. package/hedhog/frontend/app/enterprise/_components/enterprise-overview-analytics.tsx.ejs +205 -0
  80. package/hedhog/frontend/app/enterprise/_components/enterprise-person-edit-sheet.tsx.ejs +97 -0
  81. package/hedhog/frontend/app/enterprise/_components/enterprise-sheet.tsx.ejs +82 -57
  82. package/hedhog/frontend/app/enterprise/_components/enterprise-student-create-sheet.tsx.ejs +4 -0
  83. package/hedhog/frontend/app/enterprise/_components/enterprise-students-tab.tsx.ejs +60 -22
  84. package/hedhog/frontend/app/enterprise/_components/enterprise-types.ts.ejs +54 -0
  85. package/hedhog/frontend/app/enterprise/_components/enterprise-user-create-sheet.tsx.ejs +211 -0
  86. package/hedhog/frontend/app/enterprise/page.tsx.ejs +39 -7
  87. package/hedhog/frontend/app/exams/[id]/questions/page.tsx.ejs +6 -1
  88. package/hedhog/frontend/app/exams/page.tsx.ejs +6 -1
  89. package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +51 -104
  90. package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +625 -366
  91. package/hedhog/frontend/app/instructors/page.tsx.ejs +6 -1
  92. package/hedhog/frontend/app/paths/page.tsx.ejs +9 -4
  93. package/hedhog/frontend/app/reports/evaluations/page.tsx.ejs +6 -1
  94. package/hedhog/frontend/app/training/page.tsx.ejs +9 -4
  95. package/hedhog/frontend/messages/en.json +101 -10
  96. package/hedhog/frontend/messages/pt.json +101 -10
  97. package/hedhog/table/enterprise_student_license_event.yaml +30 -0
  98. package/hedhog/table/instructor_skill.yaml +0 -11
  99. package/package.json +7 -7
  100. package/src/course/course.service.ts +12 -24
  101. package/src/enterprise/enterprise.controller.ts +5 -0
  102. package/src/enterprise/enterprise.service.ts +507 -29
  103. package/src/enterprise/training/training-admin.controller.ts +4 -0
  104. package/src/enterprise/training/training-admin.service.ts +115 -51
  105. package/src/instructor/dto/create-instructor-skill.dto.ts +0 -17
  106. package/src/instructor/dto/update-instructor-skill.dto.ts +0 -18
  107. package/src/instructor/instructor-skill.service.ts +2 -97
  108. package/src/instructor/instructor.controller.ts +16 -0
  109. package/src/instructor/instructor.service.ts +85 -10
  110. package/src/lms.module.ts +1 -0
@@ -1,4 +1,4 @@
1
- import { PrismaService } from '@hed-hog/api-prisma';
1
+ import { Prisma, PrismaService } from '@hed-hog/api-prisma';
2
2
  import {
3
3
  BadRequestException,
4
4
  ConflictException,
@@ -11,6 +11,30 @@ import {
11
11
  export class TrainingAdminService {
12
12
  constructor(private readonly prisma: PrismaService) {}
13
13
 
14
+ private async recordLicenseEvent(params: {
15
+ enterpriseId: number;
16
+ personId: number;
17
+ eventType: 'assigned' | 'revoked' | 'status_changed';
18
+ previousStatus?: string | null;
19
+ nextStatus?: string | null;
20
+ }) {
21
+ const eventModel = (this.prisma as any).enterprise_student_license_event;
22
+ if (!eventModel) return;
23
+ try {
24
+ await eventModel.create({
25
+ data: {
26
+ enterprise_id: params.enterpriseId,
27
+ person_id: params.personId,
28
+ event_type: params.eventType,
29
+ previous_status: params.previousStatus ?? null,
30
+ next_status: params.nextStatus ?? null,
31
+ },
32
+ });
33
+ } catch {
34
+ // Keep license operations working until the YAML table is applied.
35
+ }
36
+ }
37
+
14
38
  async getDashboard(userId: number, requestedEnterpriseId?: number) {
15
39
  if (requestedEnterpriseId !== undefined) {
16
40
  const allowed = await this.prisma.enterprise_user.findFirst({
@@ -311,6 +335,8 @@ export class TrainingAdminService {
311
335
  userId: number,
312
336
  options: {
313
337
  enterpriseId?: number;
338
+ page?: number;
339
+ pageSize?: number;
314
340
  search?: string;
315
341
  status?: string;
316
342
  deliveryMode?: string;
@@ -340,62 +366,71 @@ export class TrainingAdminService {
340
366
  }
341
367
 
342
368
  const enterpriseId = enterpriseUser.enterprise_id;
343
- const { search, status, deliveryMode, instructorId } = options;
369
+ const { page = 1, pageSize = 10, search, status, deliveryMode, instructorId } = options;
344
370
  const now = new Date();
345
371
 
346
- const classGroups = await this.prisma.course_class_group.findMany({
347
- where: {
348
- AND: [
349
- { enterprise_class_group: { some: { enterprise_id: enterpriseId } } },
350
- ...(status ? [{ status: status as any }] : []),
351
- ...(deliveryMode ? [{ delivery_mode: deliveryMode as any }] : []),
352
- ...(instructorId !== undefined ? [{ instructor_id: instructorId }] : []),
353
- ...(search
354
- ? [
355
- {
356
- OR: [
357
- { title: { contains: search, mode: 'insensitive' as const } },
358
- { code: { contains: search, mode: 'insensitive' as const } },
359
- { course: { title: { contains: search, mode: 'insensitive' as const } } },
360
- ],
361
- },
362
- ]
363
- : []),
364
- ],
365
- },
366
- orderBy: { start_date: 'desc' },
367
- include: {
368
- course: {
369
- select: {
370
- id: true,
371
- title: true,
372
- course_image: {
373
- where: { image_type: { slug: { in: ['course-logo', 'course-banner'] } } },
374
- orderBy: { is_primary: 'desc' as const },
375
- select: {
376
- file: { select: { location: true } },
377
- image_type: { select: { slug: true } },
372
+ const where = {
373
+ AND: [
374
+ { enterprise_class_group: { some: { enterprise_id: enterpriseId } } },
375
+ ...(status ? [{ status: status as any }] : []),
376
+ ...(deliveryMode ? [{ delivery_mode: deliveryMode as any }] : []),
377
+ ...(instructorId !== undefined ? [{ instructor_id: instructorId }] : []),
378
+ ...(search
379
+ ? [
380
+ {
381
+ OR: [
382
+ { title: { contains: search, mode: 'insensitive' as const } },
383
+ { code: { contains: search, mode: 'insensitive' as const } },
384
+ { course: { title: { contains: search, mode: 'insensitive' as const } } },
385
+ ],
378
386
  },
387
+ ]
388
+ : []),
389
+ ],
390
+ };
391
+
392
+ const include = Prisma.validator<Prisma.course_class_groupInclude>()({
393
+ course: {
394
+ select: {
395
+ id: true,
396
+ title: true,
397
+ course_image: {
398
+ where: { image_type: { slug: { in: ['course-logo', 'course-banner'] } } },
399
+ orderBy: { is_primary: 'desc' as const },
400
+ select: {
401
+ file: { select: { location: true } },
402
+ image_type: { select: { slug: true } },
379
403
  },
380
404
  },
381
405
  },
382
- instructor: {
383
- include: { person: { select: { name: true } } },
384
- },
385
- _count: {
386
- select: {
387
- course_enrollment: { where: { status: { not: 'cancelled' } } },
388
- },
406
+ },
407
+ instructor: {
408
+ include: { person: { select: { name: true } } },
409
+ },
410
+ _count: {
411
+ select: {
412
+ course_enrollment: { where: { status: { not: 'cancelled' } } },
389
413
  },
390
- course_class_session: {
391
- where: { session_date: { gte: now } },
392
- orderBy: { session_date: 'asc' },
393
- take: 1,
414
+ },
415
+ course_class_session: {
416
+ where: { session_date: { gte: now } },
417
+ orderBy: { session_date: 'asc' as const },
418
+ take: 1,
394
419
  select: { session_date: true, start_time: true, location: true },
395
- },
396
420
  },
397
421
  });
398
422
 
423
+ const [classGroups, total] = await this.prisma.$transaction([
424
+ this.prisma.course_class_group.findMany({
425
+ where,
426
+ orderBy: { start_date: 'desc' },
427
+ include,
428
+ skip: (page - 1) * pageSize,
429
+ take: pageSize,
430
+ }),
431
+ this.prisma.course_class_group.count({ where }),
432
+ ]);
433
+
399
434
  const cgIds = classGroups.map((cg) => cg.id);
400
435
 
401
436
  const completedCounts =
@@ -453,7 +488,7 @@ export class TrainingAdminService {
453
488
  };
454
489
  });
455
490
 
456
- return { data, total: data.length };
491
+ return { data, total, page, pageSize, lastPage: Math.ceil(total / pageSize) };
457
492
  }
458
493
 
459
494
  // ── Class-group detail endpoints ─────────────────────────────────────────────
@@ -830,17 +865,32 @@ export class TrainingAdminService {
830
865
  if (existingStudent.status !== 'inactive') {
831
866
  throw new ConflictException('This person already has an active license');
832
867
  }
833
- return this.prisma.enterprise_student.update({
868
+ const updated = await this.prisma.enterprise_student.update({
834
869
  where: { id: existingStudent.id },
835
870
  data: { status: 'active' },
836
871
  select: { id: true, person_id: true, status: true, created_at: true },
837
872
  });
873
+ await this.recordLicenseEvent({
874
+ enterpriseId,
875
+ personId,
876
+ eventType: 'status_changed',
877
+ previousStatus: existingStudent.status,
878
+ nextStatus: 'active',
879
+ });
880
+ return updated;
838
881
  }
839
882
 
840
- return this.prisma.enterprise_student.create({
883
+ const createdStudent = await this.prisma.enterprise_student.create({
841
884
  data: { enterprise_id: enterpriseId, person_id: personId, status: 'pending' },
842
885
  select: { id: true, person_id: true, status: true, created_at: true },
843
886
  });
887
+ await this.recordLicenseEvent({
888
+ enterpriseId,
889
+ personId,
890
+ eventType: 'assigned',
891
+ nextStatus: 'pending',
892
+ });
893
+ return createdStudent;
844
894
  }
845
895
 
846
896
  async revokeLicense(userId: number, personId: number) {
@@ -852,15 +902,23 @@ export class TrainingAdminService {
852
902
 
853
903
  const existing = await this.prisma.enterprise_student.findFirst({
854
904
  where: { enterprise_id: enterpriseUser.enterprise_id, person_id: personId },
855
- select: { id: true },
905
+ select: { id: true, status: true },
856
906
  });
857
907
  if (!existing) throw new NotFoundException('Student not found in your enterprise');
858
908
 
859
- return this.prisma.enterprise_student.update({
909
+ const updated = await this.prisma.enterprise_student.update({
860
910
  where: { id: existing.id },
861
911
  data: { status: 'inactive' },
862
912
  select: { id: true },
863
913
  });
914
+ await this.recordLicenseEvent({
915
+ enterpriseId: enterpriseUser.enterprise_id,
916
+ personId,
917
+ eventType: 'revoked',
918
+ previousStatus: existing.status,
919
+ nextStatus: 'inactive',
920
+ });
921
+ return updated;
864
922
  }
865
923
 
866
924
  // ── Enrollment methods ────────────────────────────────────────────────────────
@@ -1078,6 +1136,12 @@ export class TrainingAdminService {
1078
1136
  },
1079
1137
  select: { id: true },
1080
1138
  });
1139
+ await this.recordLicenseEvent({
1140
+ enterpriseId,
1141
+ personId,
1142
+ eventType: 'assigned',
1143
+ nextStatus: 'active',
1144
+ });
1081
1145
  }
1082
1146
 
1083
1147
  const existingEnrollment = await this.prisma.course_enrollment.findFirst({
@@ -5,23 +5,6 @@ export class CreateInstructorSkillDto {
5
5
  @MaxLength(255)
6
6
  slug: string;
7
7
 
8
- @IsString()
9
- @MaxLength(255)
10
- namePt: string;
11
-
12
- @IsOptional()
13
- @IsString()
14
- @MaxLength(255)
15
- nameEn?: string;
16
-
17
- @IsOptional()
18
- @IsString()
19
- descriptionPt?: string;
20
-
21
- @IsOptional()
22
- @IsString()
23
- descriptionEn?: string;
24
-
25
8
  @IsOptional()
26
9
  @IsEnum(['active', 'inactive'])
27
10
  status?: 'active' | 'inactive';
@@ -6,24 +6,6 @@ export class UpdateInstructorSkillDto {
6
6
  @MaxLength(255)
7
7
  slug?: string;
8
8
 
9
- @IsOptional()
10
- @IsString()
11
- @MaxLength(255)
12
- namePt?: string;
13
-
14
- @IsOptional()
15
- @IsString()
16
- @MaxLength(255)
17
- nameEn?: string;
18
-
19
- @IsOptional()
20
- @IsString()
21
- descriptionPt?: string;
22
-
23
- @IsOptional()
24
- @IsString()
25
- descriptionEn?: string;
26
-
27
9
  @IsOptional()
28
10
  @IsEnum(['active', 'inactive'])
29
11
  status?: 'active' | 'inactive';
@@ -11,10 +11,6 @@ export class InstructorSkillService {
11
11
  return (this.prisma as any).instructor_skill;
12
12
  }
13
13
 
14
- private get localeClient() {
15
- return (this.prisma as any).instructor_skill_locale;
16
- }
17
-
18
14
  async list(params: { page?: number; pageSize?: number; search?: string }) {
19
15
  const page = Math.max(Number(params.page) || 1, 1);
20
16
  const pageSize = Math.max(Number(params.pageSize) || 20, 1);
@@ -24,14 +20,7 @@ export class InstructorSkillService {
24
20
  const where: any = {};
25
21
 
26
22
  if (search) {
27
- where.OR = [
28
- { slug: { contains: search, mode: 'insensitive' } },
29
- {
30
- instructor_skill_locale: {
31
- some: { name: { contains: search, mode: 'insensitive' } },
32
- },
33
- },
34
- ];
23
+ where.slug = { contains: search, mode: 'insensitive' };
35
24
  }
36
25
 
37
26
  const [rows, total] = await Promise.all([
@@ -39,11 +28,6 @@ export class InstructorSkillService {
39
28
  where,
40
29
  skip,
41
30
  take: pageSize,
42
- include: {
43
- instructor_skill_locale: {
44
- select: { locale_id: true, name: true, description: true },
45
- },
46
- },
47
31
  orderBy: { slug: 'asc' },
48
32
  }),
49
33
  this.skillClient.count({ where }),
@@ -60,11 +44,6 @@ export class InstructorSkillService {
60
44
  async getAll() {
61
45
  const rows = await this.skillClient.findMany({
62
46
  where: { status: 'active' },
63
- include: {
64
- instructor_skill_locale: {
65
- select: { locale_id: true, name: true },
66
- },
67
- },
68
47
  orderBy: { slug: 'asc' },
69
48
  });
70
49
 
@@ -79,8 +58,6 @@ export class InstructorSkillService {
79
58
  },
80
59
  });
81
60
 
82
- await this.syncLocales(skill.id, dto);
83
-
84
61
  return this.getSkillById(skill.id);
85
62
  }
86
63
 
@@ -103,8 +80,6 @@ export class InstructorSkillService {
103
80
  await this.skillClient.update({ where: { id }, data });
104
81
  }
105
82
 
106
- await this.syncLocales(id, dto);
107
-
108
83
  return this.getSkillById(id);
109
84
  }
110
85
 
@@ -124,11 +99,6 @@ export class InstructorSkillService {
124
99
  private async getSkillById(id: number) {
125
100
  const row = await this.skillClient.findUnique({
126
101
  where: { id },
127
- include: {
128
- instructor_skill_locale: {
129
- select: { locale_id: true, name: true, description: true },
130
- },
131
- },
132
102
  });
133
103
 
134
104
  if (!row) {
@@ -143,72 +113,7 @@ export class InstructorSkillService {
143
113
  id: row.id,
144
114
  slug: row.slug,
145
115
  status: row.status,
146
- locales: row.instructor_skill_locale ?? [],
147
- };
148
- }
149
-
150
- private async syncLocales(
151
- skillId: number,
152
- dto: CreateInstructorSkillDto | UpdateInstructorSkillDto,
153
- ) {
154
- const localeIds = await this.resolveLocaleIds();
155
-
156
- if (dto.namePt !== undefined && localeIds.pt !== null) {
157
- const existing = await this.localeClient.findFirst({
158
- where: { instructor_skill_id: skillId, locale_id: localeIds.pt },
159
- select: { id: true },
160
- });
161
-
162
- if (existing) {
163
- await this.localeClient.update({
164
- where: { id: existing.id },
165
- data: { name: dto.namePt, description: dto.descriptionPt ?? null },
166
- });
167
- } else {
168
- await this.localeClient.create({
169
- data: {
170
- instructor_skill_id: skillId,
171
- locale_id: localeIds.pt,
172
- name: dto.namePt,
173
- description: dto.descriptionPt ?? null,
174
- },
175
- });
176
- }
177
- }
178
-
179
- if (dto.nameEn !== undefined && localeIds.en !== null) {
180
- const existing = await this.localeClient.findFirst({
181
- where: { instructor_skill_id: skillId, locale_id: localeIds.en },
182
- select: { id: true },
183
- });
184
-
185
- if (existing) {
186
- await this.localeClient.update({
187
- where: { id: existing.id },
188
- data: { name: dto.nameEn, description: dto.descriptionEn ?? null },
189
- });
190
- } else {
191
- await this.localeClient.create({
192
- data: {
193
- instructor_skill_id: skillId,
194
- locale_id: localeIds.en,
195
- name: dto.nameEn,
196
- description: dto.descriptionEn ?? null,
197
- },
198
- });
199
- }
200
- }
201
- }
202
-
203
- private async resolveLocaleIds() {
204
- const locales = await this.prisma.locale.findMany({
205
- where: { code: { in: ['pt', 'en'] } },
206
- select: { id: true, code: true },
207
- });
208
-
209
- return {
210
- pt: locales.find((l) => l.code === 'pt')?.id ?? null,
211
- en: locales.find((l) => l.code === 'en')?.id ?? null,
116
+ name: row.slug,
212
117
  };
213
118
  }
214
119
  }
@@ -53,6 +53,22 @@ export class InstructorController {
53
53
  return this.instructorService.create(dto);
54
54
  }
55
55
 
56
+ @Get(':id/class-groups')
57
+ getInstructorClassGroups(
58
+ @Param('id', ParseIntPipe) id: number,
59
+ @Query('page', new ParseIntPipe({ optional: true })) page?: number,
60
+ @Query('pageSize', new ParseIntPipe({ optional: true })) pageSize?: number,
61
+ @Query('search') search?: string,
62
+ @Query('status') status?: string,
63
+ ) {
64
+ return this.instructorService.getInstructorClassGroups(id, {
65
+ page,
66
+ pageSize,
67
+ search,
68
+ status,
69
+ });
70
+ }
71
+
56
72
  @Get(':id')
57
73
  getById(@Param('id', ParseIntPipe) id: number) {
58
74
  return this.instructorService.getById(id);
@@ -146,10 +146,6 @@ export class InstructorService {
146
146
  select: {
147
147
  id: true,
148
148
  slug: true,
149
- instructor_skill_locale: {
150
- take: 1,
151
- select: { name: true },
152
- },
153
149
  },
154
150
  },
155
151
  },
@@ -170,7 +166,7 @@ export class InstructorService {
170
166
  existing.push({
171
167
  id: a.instructor_skill.id,
172
168
  slug: a.instructor_skill.slug,
173
- name: a.instructor_skill.instructor_skill_locale?.[0]?.name ?? a.instructor_skill.slug,
169
+ name: a.instructor_skill.slug,
174
170
  });
175
171
  skillsMap.set(a.instructor_id, existing);
176
172
  }
@@ -470,10 +466,6 @@ export class InstructorService {
470
466
  select: {
471
467
  id: true,
472
468
  slug: true,
473
- instructor_skill_locale: {
474
- take: 1,
475
- select: { name: true },
476
- },
477
469
  },
478
470
  },
479
471
  },
@@ -495,7 +487,7 @@ export class InstructorService {
495
487
  .map((a) => ({
496
488
  id: a.instructor_skill.id,
497
489
  slug: a.instructor_skill.slug,
498
- name: a.instructor_skill.instructor_skill_locale?.[0]?.name ?? a.instructor_skill.slug,
490
+ name: a.instructor_skill.slug,
499
491
  }));
500
492
 
501
493
  return {
@@ -924,4 +916,87 @@ export class InstructorService {
924
916
  });
925
917
  }
926
918
  }
919
+
920
+ async getInstructorClassGroups(
921
+ instructorId: number,
922
+ options: {
923
+ page?: number;
924
+ pageSize?: number;
925
+ search?: string;
926
+ status?: string;
927
+ } = {},
928
+ ) {
929
+ const { page = 1, pageSize = 6, search, status } = options;
930
+
931
+ const where: Prisma.course_class_groupWhereInput = {
932
+ instructor_id: instructorId,
933
+ ...(status ? { status: status as any } : {}),
934
+ ...(search
935
+ ? {
936
+ OR: [
937
+ { title: { contains: search, mode: 'insensitive' } },
938
+ { code: { contains: search, mode: 'insensitive' } },
939
+ { course: { title: { contains: search, mode: 'insensitive' } } },
940
+ ],
941
+ }
942
+ : {}),
943
+ };
944
+
945
+ const [classGroups, total] = await this.prisma.$transaction([
946
+ this.prisma.course_class_group.findMany({
947
+ where,
948
+ orderBy: { start_date: 'desc' },
949
+ include: {
950
+ course: {
951
+ select: {
952
+ id: true,
953
+ title: true,
954
+ course_image: {
955
+ where: {
956
+ image_type: { slug: { in: ['course-logo', 'course-banner'] } },
957
+ },
958
+ orderBy: { is_primary: 'desc' },
959
+ select: {
960
+ file: { select: { id: true, location: true } },
961
+ image_type: { select: { slug: true } },
962
+ },
963
+ },
964
+ },
965
+ },
966
+ _count: {
967
+ select: {
968
+ course_enrollment: { where: { status: { not: 'cancelled' } } },
969
+ },
970
+ },
971
+ },
972
+ skip: (page - 1) * pageSize,
973
+ take: pageSize,
974
+ }),
975
+ this.prisma.course_class_group.count({ where }),
976
+ ]);
977
+
978
+ const data = classGroups.map((cg) => {
979
+ const preferredImage =
980
+ cg.course?.course_image.find(
981
+ (ci) => ci.image_type?.slug === 'course-logo',
982
+ ) ?? cg.course?.course_image[0];
983
+
984
+ return {
985
+ id: cg.id,
986
+ name: cg.title,
987
+ code: cg.code ?? '',
988
+ courseId: cg.course?.id ?? null,
989
+ courseName: cg.course?.title ?? null,
990
+ courseLogoFileId: preferredImage?.file?.id ?? null,
991
+ courseLogoUrl: preferredImage?.file?.location ?? null,
992
+ startDate: cg.start_date,
993
+ endDate: cg.end_date ?? null,
994
+ slots: cg._count.course_enrollment,
995
+ totalSlots: cg.capacity ?? null,
996
+ status: cg.status,
997
+ };
998
+ });
999
+
1000
+ return { data, total, page, pageSize, lastPage: Math.ceil(total / pageSize) };
1001
+ }
927
1002
  }
package/src/lms.module.ts CHANGED
@@ -32,6 +32,7 @@ import { TrainingModule } from './training/training.module';
32
32
  forwardRef(() => InstructorModule),
33
33
  forwardRef(() => LmsReportsModule),
34
34
  forwardRef(() => TrainingModule),
35
+
35
36
  ],
36
37
  exports: [
37
38
  forwardRef(() => ClassGroupModule),