@hed-hog/lms 0.0.325 → 0.0.327

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 (69) hide show
  1. package/dist/course/course.service.d.ts +3 -1
  2. package/dist/course/course.service.d.ts.map +1 -1
  3. package/dist/course/course.service.js +35 -5
  4. package/dist/course/course.service.js.map +1 -1
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +2 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/instructor/dto/create-instructor-skill.dto.d.ts +9 -0
  10. package/dist/instructor/dto/create-instructor-skill.dto.d.ts.map +1 -0
  11. package/dist/instructor/dto/create-instructor-skill.dto.js +48 -0
  12. package/dist/instructor/dto/create-instructor-skill.dto.js.map +1 -0
  13. package/dist/instructor/dto/create-instructor.dto.d.ts +2 -0
  14. package/dist/instructor/dto/create-instructor.dto.d.ts.map +1 -1
  15. package/dist/instructor/dto/create-instructor.dto.js +12 -0
  16. package/dist/instructor/dto/create-instructor.dto.js.map +1 -1
  17. package/dist/instructor/dto/update-instructor-skill.dto.d.ts +9 -0
  18. package/dist/instructor/dto/update-instructor-skill.dto.d.ts.map +1 -0
  19. package/dist/instructor/dto/update-instructor-skill.dto.js +50 -0
  20. package/dist/instructor/dto/update-instructor-skill.dto.js.map +1 -0
  21. package/dist/instructor/dto/update-instructor.dto.d.ts +2 -0
  22. package/dist/instructor/dto/update-instructor.dto.d.ts.map +1 -1
  23. package/dist/instructor/dto/update-instructor.dto.js +12 -0
  24. package/dist/instructor/dto/update-instructor.dto.js.map +1 -1
  25. package/dist/instructor/instructor-skill.controller.d.ts +38 -0
  26. package/dist/instructor/instructor-skill.controller.d.ts.map +1 -0
  27. package/dist/instructor/instructor-skill.controller.js +89 -0
  28. package/dist/instructor/instructor-skill.controller.js.map +1 -0
  29. package/dist/instructor/instructor-skill.service.d.ts +48 -0
  30. package/dist/instructor/instructor-skill.service.d.ts.map +1 -0
  31. package/dist/instructor/instructor-skill.service.js +203 -0
  32. package/dist/instructor/instructor-skill.service.js.map +1 -0
  33. package/dist/instructor/instructor.controller.d.ts +26 -0
  34. package/dist/instructor/instructor.controller.d.ts.map +1 -1
  35. package/dist/instructor/instructor.module.d.ts.map +1 -1
  36. package/dist/instructor/instructor.module.js +4 -2
  37. package/dist/instructor/instructor.module.js.map +1 -1
  38. package/dist/instructor/instructor.service.d.ts +35 -0
  39. package/dist/instructor/instructor.service.d.ts.map +1 -1
  40. package/dist/instructor/instructor.service.js +132 -11
  41. package/dist/instructor/instructor.service.js.map +1 -1
  42. package/dist/training/training.service.d.ts +3 -1
  43. package/dist/training/training.service.d.ts.map +1 -1
  44. package/dist/training/training.service.js +34 -4
  45. package/dist/training/training.service.js.map +1 -1
  46. package/hedhog/data/integration_event_catalog.yaml +219 -0
  47. package/hedhog/data/menu.yaml +23 -6
  48. package/hedhog/data/route.yaml +45 -0
  49. package/hedhog/frontend/app/certificates/models/page.tsx.ejs +1 -1
  50. package/hedhog/frontend/app/evaluations/_components/evaluation-topic-form-sheet.tsx.ejs +1 -1
  51. package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +547 -0
  52. package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +845 -239
  53. package/hedhog/frontend/app/instructors/_components/instructor-types.ts.ejs +9 -0
  54. package/hedhog/frontend/app/instructors/page.tsx.ejs +69 -20
  55. package/hedhog/table/instructor.yaml +5 -0
  56. package/hedhog/table/instructor_skill.yaml +26 -0
  57. package/hedhog/table/instructor_skill_assignment.yaml +22 -0
  58. package/package.json +6 -6
  59. package/src/course/course.service.ts +38 -4
  60. package/src/index.ts +2 -0
  61. package/src/instructor/dto/create-instructor-skill.dto.ts +28 -0
  62. package/src/instructor/dto/create-instructor.dto.ts +20 -8
  63. package/src/instructor/dto/update-instructor-skill.dto.ts +30 -0
  64. package/src/instructor/dto/update-instructor.dto.ts +18 -6
  65. package/src/instructor/instructor-skill.controller.ts +60 -0
  66. package/src/instructor/instructor-skill.service.ts +214 -0
  67. package/src/instructor/instructor.module.ts +4 -2
  68. package/src/instructor/instructor.service.ts +148 -0
  69. package/src/training/training.service.ts +38 -4
@@ -7,6 +7,8 @@ export type InstructorRow = {
7
7
  phone?: string | null;
8
8
  status: 'active' | 'inactive';
9
9
  qualificationSlugs: string[];
10
+ hourlyRate?: number | null;
11
+ skills: { id: number; slug: string; name: string }[];
10
12
  userId?: number | null;
11
13
  hasTrainingAccess?: boolean;
12
14
  };
@@ -38,3 +40,10 @@ export type InstructorFormValues = {
38
40
  status: 'active' | 'inactive';
39
41
  can_teach_courses: boolean;
40
42
  };
43
+
44
+ export interface InstructorSkill {
45
+ id: number;
46
+ slug: string;
47
+ name: string;
48
+ status: 'active' | 'inactive';
49
+ }
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { CopyButton } from '@/components/copy-button';
3
4
  import {
4
5
  EmptyState,
5
6
  Page,
@@ -407,6 +408,8 @@ export default function InstructorsPage() {
407
408
  <TableHead>E-mail</TableHead>
408
409
  <TableHead>Telefone</TableHead>
409
410
  <TableHead>Qualificações</TableHead>
411
+ <TableHead>Skills</TableHead>
412
+ <TableHead>Valor/hora</TableHead>
410
413
  <TableHead>Training</TableHead>
411
414
  <TableHead>Status</TableHead>
412
415
  <TableHead className="w-10" />
@@ -437,28 +440,30 @@ export default function InstructorsPage() {
437
440
  </div>
438
441
  </TableCell>
439
442
  <TableCell>
440
- <span className="text-sm text-muted-foreground">
441
- {instructor.email ? (
442
- <span className="flex items-center gap-1">
443
- <Mail className="h-3 w-3" />
444
- {instructor.email}
445
- </span>
446
- ) : (
447
- '-'
448
- )}
449
- </span>
443
+ {instructor.email ? (
444
+ <span className="flex items-center gap-1 text-sm text-muted-foreground">
445
+ <Mail className="h-3 w-3 shrink-0" />
446
+ <span>{instructor.email}</span>
447
+ <CopyButton value={instructor.email} />
448
+ </span>
449
+ ) : (
450
+ <span className="text-sm text-muted-foreground">
451
+ -
452
+ </span>
453
+ )}
450
454
  </TableCell>
451
455
  <TableCell>
452
- <span className="text-sm text-muted-foreground">
453
- {instructor.phone ? (
454
- <span className="flex items-center gap-1">
455
- <Phone className="h-3 w-3" />
456
- {instructor.phone}
457
- </span>
458
- ) : (
459
- '-'
460
- )}
461
- </span>
456
+ {instructor.phone ? (
457
+ <span className="flex items-center gap-1 text-sm text-muted-foreground">
458
+ <Phone className="h-3 w-3 shrink-0" />
459
+ <span>{instructor.phone}</span>
460
+ <CopyButton value={instructor.phone} />
461
+ </span>
462
+ ) : (
463
+ <span className="text-sm text-muted-foreground">
464
+ -
465
+ </span>
466
+ )}
462
467
  </TableCell>
463
468
  <TableCell>
464
469
  <div className="flex flex-wrap gap-1">
@@ -475,6 +480,36 @@ export default function InstructorsPage() {
475
480
  )}
476
481
  </div>
477
482
  </TableCell>
483
+ <TableCell>
484
+ <div className="flex flex-wrap gap-1">
485
+ {(instructor.skills ?? []).length > 0 ? (
486
+ (instructor.skills ?? []).map((skill) => (
487
+ <Badge
488
+ key={skill.slug}
489
+ variant="outline"
490
+ className="border-purple-500/20 bg-purple-500/10 px-2 py-0.5 text-[11px] font-medium text-purple-600"
491
+ >
492
+ {skill.name}
493
+ </Badge>
494
+ ))
495
+ ) : (
496
+ <span className="text-xs text-muted-foreground">
497
+
498
+ </span>
499
+ )}
500
+ </div>
501
+ </TableCell>
502
+ <TableCell>
503
+ {instructor.hourlyRate != null ? (
504
+ <span className="text-sm">
505
+ {`R$ ${instructor.hourlyRate.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}`}
506
+ </span>
507
+ ) : (
508
+ <span className="text-xs text-muted-foreground">
509
+
510
+ </span>
511
+ )}
512
+ </TableCell>
478
513
  <TableCell>
479
514
  {instructor.hasTrainingAccess ? (
480
515
  <Badge
@@ -642,7 +677,21 @@ export default function InstructorsPage() {
642
677
  {QUALIFICATION_LABELS[slug] ?? slug}
643
678
  </Badge>
644
679
  ))}
680
+ {(instructor.skills ?? []).map((skill) => (
681
+ <Badge
682
+ key={skill.slug}
683
+ variant="outline"
684
+ className="border-purple-500/20 bg-purple-500/10 px-2 py-0.5 text-[11px] font-medium text-purple-600"
685
+ >
686
+ {skill.name}
687
+ </Badge>
688
+ ))}
645
689
  </div>
690
+ {instructor.hourlyRate != null && (
691
+ <p className="text-xs text-muted-foreground">
692
+ {`R$ ${instructor.hourlyRate.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}`}
693
+ </p>
694
+ )}
646
695
  </CardContent>
647
696
  </Card>
648
697
  ))}
@@ -13,6 +13,11 @@ columns:
13
13
  - name: can_teach_courses
14
14
  type: boolean
15
15
  default: true
16
+ - name: hourly_rate
17
+ type: decimal
18
+ precision: 10
19
+ scale: 2
20
+ isNullable: true
16
21
  - type: created_at
17
22
  - type: updated_at
18
23
 
@@ -0,0 +1,26 @@
1
+ columns:
2
+ - type: pk
3
+ - name: slug
4
+ isUnique: true
5
+ - name: name
6
+ type: locale_varchar
7
+ locale:
8
+ pt: Nome
9
+ en: Name
10
+ - name: description
11
+ type: locale_text
12
+ isNullable: true
13
+ locale:
14
+ pt: Descricao
15
+ en: Description
16
+ - name: status
17
+ type: enum
18
+ values: [active, inactive]
19
+ default: active
20
+ - type: created_at
21
+ - type: updated_at
22
+
23
+ indices:
24
+ - columns: [slug]
25
+ isUnique: true
26
+ - columns: [status]
@@ -0,0 +1,22 @@
1
+ columns:
2
+ - type: pk
3
+ - name: instructor_id
4
+ type: fk
5
+ references:
6
+ table: instructor
7
+ column: id
8
+ onDelete: CASCADE
9
+ - name: skill_id
10
+ type: fk
11
+ references:
12
+ table: instructor_skill
13
+ column: id
14
+ onDelete: CASCADE
15
+ - type: created_at
16
+ - type: updated_at
17
+
18
+ indices:
19
+ - columns: [instructor_id, skill_id]
20
+ isUnique: true
21
+ - columns: [instructor_id]
22
+ - columns: [skill_id]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/lms",
3
- "version": "0.0.325",
3
+ "version": "0.0.327",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -10,14 +10,14 @@
10
10
  "@nestjs/jwt": "^11",
11
11
  "@nestjs/mapped-types": "*",
12
12
  "@hed-hog/api": "0.0.8",
13
+ "@hed-hog/api-types": "0.0.1",
13
14
  "@hed-hog/api-locale": "0.0.14",
14
15
  "@hed-hog/api-prisma": "0.0.6",
15
- "@hed-hog/contact": "0.0.325",
16
- "@hed-hog/api-types": "0.0.1",
16
+ "@hed-hog/contact": "0.0.327",
17
17
  "@hed-hog/api-pagination": "0.0.7",
18
- "@hed-hog/core": "0.0.325",
19
- "@hed-hog/category": "0.0.325",
20
- "@hed-hog/finance": "0.0.325"
18
+ "@hed-hog/finance": "0.0.327",
19
+ "@hed-hog/category": "0.0.327",
20
+ "@hed-hog/core": "0.0.327"
21
21
  },
22
22
  "exports": {
23
23
  ".": {
@@ -1,5 +1,6 @@
1
1
  import { PrismaService } from '@hed-hog/api-prisma';
2
- import { BadRequestException, Injectable } from '@nestjs/common';
2
+ import { IntegrationDeveloperApiService } from '@hed-hog/core';
3
+ import { BadRequestException, Inject, Injectable, forwardRef } from '@nestjs/common';
3
4
  import { CreateCourseDto } from './dto/create-course.dto';
4
5
  import { UpdateCourseDto } from './dto/update-course.dto';
5
6
 
@@ -23,7 +24,11 @@ type PersistCourseExtrasInput = {
23
24
 
24
25
  @Injectable()
25
26
  export class CourseService {
26
- constructor(private readonly prisma: PrismaService) {}
27
+ constructor(
28
+ private readonly prisma: PrismaService,
29
+ @Inject(forwardRef(() => IntegrationDeveloperApiService))
30
+ private readonly integrationApi: IntegrationDeveloperApiService,
31
+ ) {}
27
32
 
28
33
  private normalizeLevel(value?: string | null) {
29
34
  if (!value) return undefined;
@@ -438,7 +443,17 @@ export class CourseService {
438
443
 
439
444
  const extrasById = await this.getCourseExtras([c.id]);
440
445
 
441
- return this.mapCourse(c, undefined, extrasById.get(c.id));
446
+ const result = this.mapCourse(c, undefined, extrasById.get(c.id));
447
+
448
+ await this.integrationApi.publishEvent({
449
+ eventName: 'lms.course.created',
450
+ sourceModule: 'lms',
451
+ aggregateType: 'course',
452
+ aggregateId: String(c.id),
453
+ payload: { id: c.id, name: data.name, slug: normalizedSlug, status: data.status ?? 'draft' },
454
+ }).catch(() => null);
455
+
456
+ return result;
442
457
  }
443
458
 
444
459
  async update(id: number, dto: UpdateCourseDto) {
@@ -604,11 +619,30 @@ export class CourseService {
604
619
 
605
620
  const extrasById = await this.getCourseExtras([id]);
606
621
 
607
- return this.mapCourse(c, undefined, extrasById.get(id));
622
+ const updateResult = this.mapCourse(c, undefined, extrasById.get(id));
623
+
624
+ await this.integrationApi.publishEvent({
625
+ eventName: 'lms.course.updated',
626
+ sourceModule: 'lms',
627
+ aggregateType: 'course',
628
+ aggregateId: String(id),
629
+ payload: { id, name: data.name, status: data.status },
630
+ }).catch(() => null);
631
+
632
+ return updateResult;
608
633
  }
609
634
 
610
635
  async remove(id: number) {
611
636
  await this.prisma.course.delete({ where: { id } });
637
+
638
+ await this.integrationApi.publishEvent({
639
+ eventName: 'lms.course.deleted',
640
+ sourceModule: 'lms',
641
+ aggregateType: 'course',
642
+ aggregateId: String(id),
643
+ payload: { id },
644
+ }).catch(() => null);
645
+
612
646
  return { success: true };
613
647
  }
614
648
 
package/src/index.ts CHANGED
@@ -17,6 +17,8 @@ export * from './exam/exam-attempt.service';
17
17
  export * from './exam/exam.controller';
18
18
  export * from './exam/exam.module';
19
19
  export * from './exam/exam.service';
20
+ export * from './instructor/instructor-skill.controller';
21
+ export * from './instructor/instructor-skill.service';
20
22
  export * from './instructor/instructor.controller';
21
23
  export * from './instructor/instructor.module';
22
24
  export * from './instructor/instructor.service';
@@ -0,0 +1,28 @@
1
+ import { IsEnum, IsOptional, IsString, MaxLength } from 'class-validator';
2
+
3
+ export class CreateInstructorSkillDto {
4
+ @IsString()
5
+ @MaxLength(255)
6
+ slug: string;
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
+ @IsOptional()
26
+ @IsEnum(['active', 'inactive'])
27
+ status?: 'active' | 'inactive';
28
+ }
@@ -1,12 +1,14 @@
1
1
  import {
2
- ArrayMinSize,
3
- IsArray,
4
- IsEmail,
5
- IsEnum,
6
- IsInt,
7
- IsOptional,
8
- IsString,
9
- MaxLength,
2
+ ArrayMinSize,
3
+ IsArray,
4
+ IsEmail,
5
+ IsEnum,
6
+ IsInt,
7
+ IsNumber,
8
+ IsOptional,
9
+ IsString,
10
+ MaxLength,
11
+ Min,
10
12
  } from 'class-validator';
11
13
 
12
14
  export class CreateInstructorDto {
@@ -37,6 +39,16 @@ export class CreateInstructorDto {
37
39
  @IsString({ each: true })
38
40
  qualificationSlugs: string[];
39
41
 
42
+ @IsOptional()
43
+ @IsNumber()
44
+ @Min(0)
45
+ hourlyRate?: number;
46
+
47
+ @IsOptional()
48
+ @IsArray()
49
+ @IsString({ each: true })
50
+ skillSlugs?: string[];
51
+
40
52
  @IsOptional()
41
53
  @IsEnum(['active', 'inactive'])
42
54
  status?: 'active' | 'inactive';
@@ -0,0 +1,30 @@
1
+ import { IsEnum, IsOptional, IsString, MaxLength } from 'class-validator';
2
+
3
+ export class UpdateInstructorSkillDto {
4
+ @IsOptional()
5
+ @IsString()
6
+ @MaxLength(255)
7
+ slug?: string;
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
+ @IsOptional()
28
+ @IsEnum(['active', 'inactive'])
29
+ status?: 'active' | 'inactive';
30
+ }
@@ -1,10 +1,12 @@
1
1
  import {
2
- IsArray,
3
- IsEnum,
4
- IsInt,
5
- IsOptional,
6
- IsString,
7
- MaxLength,
2
+ IsArray,
3
+ IsEnum,
4
+ IsInt,
5
+ IsNumber,
6
+ IsOptional,
7
+ IsString,
8
+ MaxLength,
9
+ Min,
8
10
  } from 'class-validator';
9
11
 
10
12
  export class UpdateInstructorDto {
@@ -32,6 +34,16 @@ export class UpdateInstructorDto {
32
34
  @IsString({ each: true })
33
35
  qualificationSlugs?: string[];
34
36
 
37
+ @IsOptional()
38
+ @IsNumber()
39
+ @Min(0)
40
+ hourlyRate?: number;
41
+
42
+ @IsOptional()
43
+ @IsArray()
44
+ @IsString({ each: true })
45
+ skillSlugs?: string[];
46
+
35
47
  @IsOptional()
36
48
  @IsEnum(['active', 'inactive'])
37
49
  status?: 'active' | 'inactive';
@@ -0,0 +1,60 @@
1
+ import { Role } from '@hed-hog/api';
2
+ import {
3
+ Body,
4
+ Controller,
5
+ Delete,
6
+ Get,
7
+ HttpCode,
8
+ HttpStatus,
9
+ Param,
10
+ ParseIntPipe,
11
+ Patch,
12
+ Post,
13
+ Query,
14
+ } from '@nestjs/common';
15
+ import { CreateInstructorSkillDto } from './dto/create-instructor-skill.dto';
16
+ import { UpdateInstructorSkillDto } from './dto/update-instructor-skill.dto';
17
+ import { InstructorSkillService } from './instructor-skill.service';
18
+
19
+ @Role()
20
+ @Controller('lms/instructor-skills')
21
+ export class InstructorSkillController {
22
+ constructor(private readonly instructorSkillService: InstructorSkillService) {}
23
+
24
+ @Get()
25
+ list(
26
+ @Query('page') page?: string,
27
+ @Query('pageSize') pageSize?: string,
28
+ @Query('search') search?: string,
29
+ ) {
30
+ return this.instructorSkillService.list({
31
+ page: page ? Number(page) : 1,
32
+ pageSize: pageSize ? Number(pageSize) : 20,
33
+ search,
34
+ });
35
+ }
36
+
37
+ @Get('all')
38
+ getAll() {
39
+ return this.instructorSkillService.getAll();
40
+ }
41
+
42
+ @Post()
43
+ create(@Body() dto: CreateInstructorSkillDto) {
44
+ return this.instructorSkillService.create(dto);
45
+ }
46
+
47
+ @Patch(':id')
48
+ update(
49
+ @Param('id', ParseIntPipe) id: number,
50
+ @Body() dto: UpdateInstructorSkillDto,
51
+ ) {
52
+ return this.instructorSkillService.update(id, dto);
53
+ }
54
+
55
+ @Delete(':id')
56
+ @HttpCode(HttpStatus.NO_CONTENT)
57
+ remove(@Param('id', ParseIntPipe) id: number) {
58
+ return this.instructorSkillService.delete(id);
59
+ }
60
+ }