@hed-hog/lms 0.0.318 → 0.0.320

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 +65 -2
  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 +67 -2
  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 +27 -47
  91. package/hedhog/frontend/app/classes/[id]/_components/event-summary-popover.tsx.ejs +15 -15
  92. package/hedhog/frontend/app/classes/[id]/_components/quick-create-session-popover.tsx.ejs +5 -5
  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 +21 -8
  96. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +10 -6
  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 +621 -1250
  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 +5 -5
  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 +8 -8
  109. package/src/class-group/dto/material.dto.ts +69 -0
  110. package/src/course/course.service.ts +54 -21
  111. package/src/course/dto/create-course.dto.ts +8 -8
  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
@@ -2372,6 +2372,11 @@
2372
2372
  "label": "Lesson title",
2373
2373
  "placeholder": "e.g. Live class session"
2374
2374
  },
2375
+ "sessionAccess": {
2376
+ "label": "Lesson location / access link",
2377
+ "placeholder": "Room 201 or https://meet.google.com/...",
2378
+ "description": "Optional. When filled, it will be applied to every lesson generated for this class."
2379
+ },
2375
2380
  "schedule": {
2376
2381
  "label": "Schedule",
2377
2382
  "placeholder": "7:00 PM - 10:00 PM"
@@ -2420,13 +2425,13 @@
2420
2425
  "yearly": "year"
2421
2426
  },
2422
2427
  "days": {
2423
- "MO": "M",
2424
- "TU": "T",
2425
- "WE": "W",
2426
- "TH": "T",
2427
- "FR": "F",
2428
- "SA": "S",
2429
- "SU": "S"
2428
+ "MO": "Mon",
2429
+ "TU": "Tue",
2430
+ "WE": "Wed",
2431
+ "TH": "Thu",
2432
+ "FR": "Fri",
2433
+ "SA": "Sat",
2434
+ "SU": "Sun"
2430
2435
  }
2431
2436
  }
2432
2437
  },
@@ -2809,17 +2814,103 @@
2809
2814
  "studentUpdated": "Student updated successfully!",
2810
2815
  "lessonUpdated": "Lesson updated successfully!",
2811
2816
  "lessonCreated": "Lesson created successfully!",
2817
+ "attendanceSaving": "Saving attendance...",
2812
2818
  "attendanceSaved": "Attendance saved successfully!",
2813
2819
  "error": "An error occurred. Please try again."
2814
2820
  }
2815
2821
  }
2816
2822
  },
2823
+ "EvaluationTopicsPage": {
2824
+ "title": "Evaluation Topics",
2825
+ "description": "Manage evaluation topics grouped by target type.",
2826
+ "breadcrumbs": {
2827
+ "home": "Home",
2828
+ "lms": "LMS",
2829
+ "evaluations": "Evaluation Topics"
2830
+ },
2831
+ "newTopic": "New Topic",
2832
+ "kpi": {
2833
+ "total": "Total Topics",
2834
+ "totalDesc": "All evaluation topics",
2835
+ "active": "Active",
2836
+ "activeDesc": "Currently active topics",
2837
+ "inactive": "Inactive",
2838
+ "inactiveDesc": "Deactivated topics",
2839
+ "withRatings": "With Ratings",
2840
+ "withRatingsDesc": "Topics that have received ratings"
2841
+ },
2842
+ "filters": {
2843
+ "searchPlaceholder": "Search by name or description…",
2844
+ "clear": "Clear"
2845
+ },
2846
+ "tabs": {
2847
+ "all": "All"
2848
+ },
2849
+ "targetType": {
2850
+ "course": "Course",
2851
+ "course_lesson": "Lesson",
2852
+ "course_class_session": "Session",
2853
+ "question": "Question",
2854
+ "exam": "Exam"
2855
+ },
2856
+ "columns": {
2857
+ "name": "Name",
2858
+ "targetType": "Target Type",
2859
+ "order": "Order",
2860
+ "ratings": "Ratings",
2861
+ "status": "Status"
2862
+ },
2863
+ "status": {
2864
+ "active": "Active",
2865
+ "inactive": "Inactive"
2866
+ },
2867
+ "empty": {
2868
+ "title": "No topics found",
2869
+ "description": "Create an evaluation topic to get started."
2870
+ },
2871
+ "delete": {
2872
+ "title": "Delete Topic",
2873
+ "description": "Are you sure you want to delete \"{name}\"? This action cannot be undone.",
2874
+ "cancel": "Cancel",
2875
+ "confirm": "Delete",
2876
+ "deleting": "Deleting…",
2877
+ "success": "Topic deleted.",
2878
+ "error": "Failed to delete topic.",
2879
+ "disabledTooltip": "Cannot delete a topic that already has ratings."
2880
+ },
2881
+ "reorder": {
2882
+ "success": "Order saved.",
2883
+ "error": "Failed to save order."
2884
+ },
2885
+ "form": {
2886
+ "editTitle": "Edit Topic",
2887
+ "createTitle": "New Topic",
2888
+ "name": "Name",
2889
+ "namePlaceholder": "Enter topic name",
2890
+ "description": "Description",
2891
+ "descriptionPlaceholder": "Optional description",
2892
+ "targetType": "Target Type",
2893
+ "targetTypeReadonly": "Target type cannot be changed after creation.",
2894
+ "order": "Display Order",
2895
+ "orderPlaceholder": "e.g. 1",
2896
+ "isActive": "Active",
2897
+ "isActiveDesc": "Visible to evaluators",
2898
+ "cancel": "Cancel",
2899
+ "save": "Save Changes",
2900
+ "create": "Create Topic",
2901
+ "saving": "Saving…",
2902
+ "createdSuccess": "Topic created successfully.",
2903
+ "updatedSuccess": "Topic updated successfully.",
2904
+ "saveError": "Failed to save topic."
2905
+ }
2906
+ },
2817
2907
  "EvaluationsPage": {
2818
2908
  "title": "Evaluations",
2819
2909
  "description": "Browse and filter all evaluation ratings submitted by students.",
2820
2910
  "breadcrumbs": {
2821
2911
  "home": "Home",
2822
2912
  "lms": "LMS",
2913
+ "reports": "Reports",
2823
2914
  "evaluations": "Evaluations"
2824
2915
  },
2825
2916
  "filters": {
@@ -2379,6 +2379,11 @@
2379
2379
  "label": "Título das aulas",
2380
2380
  "placeholder": "Ex.: Aula ao vivo da turma"
2381
2381
  },
2382
+ "sessionAccess": {
2383
+ "label": "Local / link das aulas",
2384
+ "placeholder": "Sala 201 ou https://meet.google.com/...",
2385
+ "description": "Opcional. Quando preenchido, será aplicado a todas as aulas geradas para esta turma."
2386
+ },
2382
2387
  "schedule": {
2383
2388
  "label": "Horário",
2384
2389
  "placeholder": "19:00 - 22:00"
@@ -2427,13 +2432,13 @@
2427
2432
  "yearly": "ano"
2428
2433
  },
2429
2434
  "days": {
2430
- "MO": "S",
2431
- "TU": "T",
2432
- "WE": "Q",
2433
- "TH": "Q",
2434
- "FR": "S",
2435
- "SA": "S",
2436
- "SU": "D"
2435
+ "MO": "Seg",
2436
+ "TU": "Ter",
2437
+ "WE": "Qua",
2438
+ "TH": "Qui",
2439
+ "FR": "Sex",
2440
+ "SA": "Sáb",
2441
+ "SU": "Dom"
2437
2442
  }
2438
2443
  }
2439
2444
  },
@@ -2816,17 +2821,103 @@
2816
2821
  "studentUpdated": "Aluno atualizado com sucesso!",
2817
2822
  "lessonUpdated": "Aula atualizada com sucesso!",
2818
2823
  "lessonCreated": "Aula criada com sucesso!",
2824
+ "attendanceSaving": "Salvando presença...",
2819
2825
  "attendanceSaved": "Presença salva com sucesso!",
2820
2826
  "error": "Ocorreu um erro. Tente novamente."
2821
2827
  }
2822
2828
  }
2823
2829
  },
2830
+ "EvaluationTopicsPage": {
2831
+ "title": "Tópicos de Avaliação",
2832
+ "description": "Gerencie os tópicos de avaliação agrupados por tipo.",
2833
+ "breadcrumbs": {
2834
+ "home": "Início",
2835
+ "lms": "LMS",
2836
+ "evaluations": "Tópicos de Avaliação"
2837
+ },
2838
+ "newTopic": "Novo Tópico",
2839
+ "kpi": {
2840
+ "total": "Total de Tópicos",
2841
+ "totalDesc": "Todos os tópicos de avaliação",
2842
+ "active": "Ativos",
2843
+ "activeDesc": "Tópicos ativos no momento",
2844
+ "inactive": "Inativos",
2845
+ "inactiveDesc": "Tópicos desativados",
2846
+ "withRatings": "Com Avaliações",
2847
+ "withRatingsDesc": "Tópicos que já receberam avaliações"
2848
+ },
2849
+ "filters": {
2850
+ "searchPlaceholder": "Buscar por nome ou descrição…",
2851
+ "clear": "Limpar"
2852
+ },
2853
+ "tabs": {
2854
+ "all": "Todos"
2855
+ },
2856
+ "targetType": {
2857
+ "course": "Curso",
2858
+ "course_lesson": "Aula",
2859
+ "course_class_session": "Sessão",
2860
+ "question": "Questão",
2861
+ "exam": "Exame"
2862
+ },
2863
+ "columns": {
2864
+ "name": "Nome",
2865
+ "targetType": "Tipo",
2866
+ "order": "Ordem",
2867
+ "ratings": "Avaliações",
2868
+ "status": "Status"
2869
+ },
2870
+ "status": {
2871
+ "active": "Ativo",
2872
+ "inactive": "Inativo"
2873
+ },
2874
+ "empty": {
2875
+ "title": "Nenhum tópico encontrado",
2876
+ "description": "Crie um tópico de avaliação para começar."
2877
+ },
2878
+ "delete": {
2879
+ "title": "Excluir Tópico",
2880
+ "description": "Tem certeza que deseja excluir \"{name}\"? Esta ação não pode ser desfeita.",
2881
+ "cancel": "Cancelar",
2882
+ "confirm": "Excluir",
2883
+ "deleting": "Excluindo…",
2884
+ "success": "Tópico excluído.",
2885
+ "error": "Falha ao excluir o tópico.",
2886
+ "disabledTooltip": "Não é possível excluir um tópico que já possui avaliações."
2887
+ },
2888
+ "reorder": {
2889
+ "success": "Ordem salva.",
2890
+ "error": "Falha ao salvar a ordem."
2891
+ },
2892
+ "form": {
2893
+ "editTitle": "Editar Tópico",
2894
+ "createTitle": "Novo Tópico",
2895
+ "name": "Nome",
2896
+ "namePlaceholder": "Informe o nome do tópico",
2897
+ "description": "Descrição",
2898
+ "descriptionPlaceholder": "Descrição opcional",
2899
+ "targetType": "Tipo de Alvo",
2900
+ "targetTypeReadonly": "O tipo de alvo não pode ser alterado após a criação.",
2901
+ "order": "Ordem de Exibição",
2902
+ "orderPlaceholder": "ex.: 1",
2903
+ "isActive": "Ativo",
2904
+ "isActiveDesc": "Visível para avaliadores",
2905
+ "cancel": "Cancelar",
2906
+ "save": "Salvar Alterações",
2907
+ "create": "Criar Tópico",
2908
+ "saving": "Salvando…",
2909
+ "createdSuccess": "Tópico criado com sucesso.",
2910
+ "updatedSuccess": "Tópico atualizado com sucesso.",
2911
+ "saveError": "Falha ao salvar o tópico."
2912
+ }
2913
+ },
2824
2914
  "EvaluationsPage": {
2825
2915
  "title": "Avaliações",
2826
2916
  "description": "Visualize e filtre todas as avaliações enviadas pelos alunos.",
2827
2917
  "breadcrumbs": {
2828
2918
  "home": "Início",
2829
2919
  "lms": "LMS",
2920
+ "reports": "Relatórios",
2830
2921
  "evaluations": "Avaliações"
2831
2922
  },
2832
2923
  "filters": {
@@ -0,0 +1,45 @@
1
+ columns:
2
+ - type: pk
3
+ - name: course_class_group_id
4
+ type: fk
5
+ references:
6
+ table: course_class_group
7
+ column: id
8
+ onDelete: CASCADE
9
+ - name: course_class_session_id
10
+ type: fk
11
+ references:
12
+ table: course_class_session
13
+ column: id
14
+ onDelete: SET NULL
15
+ isNullable: true
16
+ - name: title
17
+ type: varchar
18
+ length: 255
19
+ - name: description
20
+ type: text
21
+ isNullable: true
22
+ - name: material_type
23
+ type: enum
24
+ values: [file, link]
25
+ - name: file_id
26
+ type: fk
27
+ isNullable: true
28
+ references:
29
+ table: file
30
+ column: id
31
+ onDelete: SET NULL
32
+ - name: url
33
+ type: varchar
34
+ length: 500
35
+ isNullable: true
36
+ - name: sort_order
37
+ type: int
38
+ default: 0
39
+ - type: created_at
40
+ - type: updated_at
41
+
42
+ indices:
43
+ - columns: [course_class_group_id]
44
+ - columns: [course_class_session_id]
45
+ - columns: [material_type]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/lms",
3
- "version": "0.0.318",
3
+ "version": "0.0.320",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -11,13 +11,13 @@
11
11
  "@nestjs/mapped-types": "*",
12
12
  "@hed-hog/api-types": "0.0.1",
13
13
  "@hed-hog/contact": "0.0.318",
14
- "@hed-hog/api": "0.0.6",
15
- "@hed-hog/api-locale": "0.0.14",
16
14
  "@hed-hog/api-prisma": "0.0.6",
17
15
  "@hed-hog/core": "0.0.318",
18
- "@hed-hog/category": "0.0.318",
19
16
  "@hed-hog/api-pagination": "0.0.7",
20
- "@hed-hog/finance": "0.0.318"
17
+ "@hed-hog/api-locale": "0.0.14",
18
+ "@hed-hog/api": "0.0.8",
19
+ "@hed-hog/finance": "0.0.318",
20
+ "@hed-hog/category": "0.0.318"
21
21
  },
22
22
  "exports": {
23
23
  ".": {
@@ -19,6 +19,7 @@ import {
19
19
  SaveAttendanceDto,
20
20
  UpdateStudentProfileDto,
21
21
  } from './dto/enrollment.dto';
22
+ import { CreateMaterialDto } from './dto/material.dto';
22
23
  import { UpdateClassGroupDto } from './dto/update-class-group.dto';
23
24
  import { UpdateSessionDto } from './dto/update-session.dto';
24
25
 
@@ -186,4 +187,33 @@ export class ClassGroupController {
186
187
  ) {
187
188
  return this.classGroupService.saveAttendance(id, sessionId, dto.attendance);
188
189
  }
190
+
191
+ // ── Materials ───────────────────────────────────────────────────────────────
192
+
193
+ @Get(':id/materials')
194
+ listMaterials(
195
+ @Param('id', ParseIntPipe) id: number,
196
+ @Query('sessionId') sessionId?: string,
197
+ ) {
198
+ return this.classGroupService.listMaterials(
199
+ id,
200
+ sessionId ? Number(sessionId) : undefined,
201
+ );
202
+ }
203
+
204
+ @Post(':id/materials')
205
+ createMaterial(
206
+ @Param('id', ParseIntPipe) id: number,
207
+ @Body() dto: CreateMaterialDto,
208
+ ) {
209
+ return this.classGroupService.createMaterial(id, dto);
210
+ }
211
+
212
+ @Delete(':id/materials/:materialId')
213
+ deleteMaterial(
214
+ @Param('id', ParseIntPipe) id: number,
215
+ @Param('materialId', ParseIntPipe) materialId: number,
216
+ ) {
217
+ return this.classGroupService.deleteMaterial(id, materialId);
218
+ }
189
219
  }
@@ -9,9 +9,9 @@ import { Prisma } from '@prisma/client';
9
9
  import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
10
10
  import { randomUUID } from 'node:crypto';
11
11
  import {
12
- CreateClassGroupDto,
13
- type ClassGroupSessionRecurrenceDto,
14
- type ClassGroupSessionTemplateDto,
12
+ CreateClassGroupDto,
13
+ type ClassGroupSessionRecurrenceDto,
14
+ type ClassGroupSessionTemplateDto,
15
15
  } from './dto/create-class-group.dto';
16
16
  import { CreateSessionDto, type SessionRecurrenceDto } from './dto/create-session.dto';
17
17
  import { UpdateClassGroupDto } from './dto/update-class-group.dto';
@@ -73,6 +73,19 @@ export class ClassGroupService {
73
73
  id: true,
74
74
  title: true,
75
75
  primary_color: true,
76
+ course_image: {
77
+ where: { image_type: { slug: 'course-logo' } },
78
+ orderBy: { order: 'asc' as const },
79
+ take: 1,
80
+ select: {
81
+ file_id: true,
82
+ file: {
83
+ select: {
84
+ id: true,
85
+ },
86
+ },
87
+ },
88
+ },
76
89
  },
77
90
  },
78
91
  _count: {
@@ -91,6 +104,7 @@ export class ClassGroupService {
91
104
  person: {
92
105
  select: {
93
106
  name: true,
107
+ avatar_id: true,
94
108
  },
95
109
  },
96
110
  },
@@ -112,6 +126,7 @@ export class ClassGroupService {
112
126
  person: {
113
127
  select: {
114
128
  name: true,
129
+ avatar_id: true,
115
130
  },
116
131
  },
117
132
  },
@@ -550,6 +565,8 @@ export class ClassGroupService {
550
565
  item.instructorName?.trim?.() ??
551
566
  item.professorName?.trim?.() ??
552
567
  '';
568
+ const instructorAvatarId = item.instructor?.person?.avatar_id ?? null;
569
+ const courseLogo = item.course?.course_image?.[0] ?? null;
553
570
 
554
571
  return {
555
572
  id: item.id,
@@ -568,11 +585,13 @@ export class ClassGroupService {
568
585
  virtualRoomUrl: item.virtual_room_url,
569
586
  courseId: item.course_id,
570
587
  instructorId: item.instructor_id ?? item.instructor?.id ?? null,
588
+ instructorAvatarId,
571
589
  instructorName,
572
590
  instructor: instructorName,
573
591
  professorName: instructorName,
574
592
  professor: instructorName,
575
593
  courseTitle: item.course?.title ?? '',
594
+ logoFileId: courseLogo?.file?.id ?? courseLogo?.file_id ?? null,
576
595
  primaryColor: item.course?.primary_color ?? null,
577
596
  enrolledCount: item._count?.course_enrollment ?? 0,
578
597
  sessionCount: item._count?.course_class_session ?? 0,
@@ -658,6 +677,17 @@ export class ClassGroupService {
658
677
  }
659
678
  }
660
679
 
680
+ private async assertClassGroupExists(classGroupId: number) {
681
+ const exists = await this.prisma.course_class_group.findUnique({
682
+ where: { id: classGroupId },
683
+ select: { id: true },
684
+ });
685
+
686
+ if (!exists) {
687
+ throw new NotFoundException('Class group not found');
688
+ }
689
+ }
690
+
661
691
  // ── Students ────────────────────────────────────────────────────────────────
662
692
 
663
693
  async listStudents(classGroupId: number, search?: string) {
@@ -677,6 +707,7 @@ export class ClassGroupService {
677
707
  select: {
678
708
  id: true,
679
709
  name: true,
710
+ avatar_id: true,
680
711
  contact: {
681
712
  select: { value: true, contact_type: { select: { code: true } } },
682
713
  },
@@ -698,6 +729,7 @@ export class ClassGroupService {
698
729
  nome: e.person.name,
699
730
  email,
700
731
  telefone: phone,
732
+ avatarId: e.person.avatar_id,
701
733
  matriculadoEm: e.enrolled_at,
702
734
  progresso: e.progress_percent,
703
735
  status: e.status,
@@ -728,15 +760,17 @@ export class ClassGroupService {
728
760
  });
729
761
  if (existing) {
730
762
  if (existing.status === 'cancelled') {
731
- return this.prisma.course_enrollment.update({
763
+ const updated = await this.prisma.course_enrollment.update({
732
764
  where: { id: existing.id },
733
765
  data: { status: 'active', enrolled_at: new Date() },
734
766
  });
767
+ await this.ensureEnterpriseStudent(classGroupId, personId);
768
+ return updated;
735
769
  }
736
770
  throw new ConflictException('Student already enrolled');
737
771
  }
738
772
 
739
- return this.prisma.course_enrollment.create({
773
+ const enrollment = await this.prisma.course_enrollment.create({
740
774
  data: {
741
775
  person_id: personId,
742
776
  course_class_group_id: classGroupId,
@@ -745,6 +779,28 @@ export class ClassGroupService {
745
779
  enrolled_at: new Date(),
746
780
  },
747
781
  });
782
+ await this.ensureEnterpriseStudent(classGroupId, personId);
783
+ return enrollment;
784
+ }
785
+
786
+ private async ensureEnterpriseStudent(classGroupId: number, personId: number) {
787
+ const enterpriseLinks = await this.prisma.enterprise_class_group.findMany({
788
+ where: { course_class_group_id: classGroupId },
789
+ select: { enterprise_id: true },
790
+ });
791
+ if (enterpriseLinks.length === 0) return;
792
+
793
+ for (const { enterprise_id } of enterpriseLinks) {
794
+ const exists = await this.prisma.enterprise_student.findFirst({
795
+ where: { enterprise_id, person_id: personId },
796
+ select: { id: true },
797
+ });
798
+ if (!exists) {
799
+ await this.prisma.enterprise_student.create({
800
+ data: { enterprise_id, person_id: personId, status: 'active' },
801
+ });
802
+ }
803
+ }
748
804
  }
749
805
 
750
806
  async createAndEnrollStudent(
@@ -894,6 +950,7 @@ export class ClassGroupService {
894
950
  select: {
895
951
  id: true,
896
952
  name: true,
953
+ avatar_id: true,
897
954
  contact: {
898
955
  select: {
899
956
  id: true,
@@ -923,6 +980,7 @@ export class ClassGroupService {
923
980
  nome: enrollment.person.name,
924
981
  email,
925
982
  telefone: phone,
983
+ avatarId: enrollment.person.avatar_id,
926
984
  matriculadoEm: enrollment.enrolled_at,
927
985
  progresso: enrollment.progress_percent,
928
986
  status: enrollment.status,
@@ -1120,6 +1178,7 @@ export class ClassGroupService {
1120
1178
  return {
1121
1179
  id: p.id,
1122
1180
  nome: p.name,
1181
+ avatarId: p.avatar_id,
1123
1182
  email:
1124
1183
  p.contact.find((c) => this.isEmailCode(c.contact_type.code))
1125
1184
  ?.value ?? '',
@@ -1526,6 +1585,8 @@ export class ClassGroupService {
1526
1585
  instructorId: sessionInstructor?.instructor_id ?? null,
1527
1586
  instructorName:
1528
1587
  sessionInstructor?.instructor?.person?.name ?? '',
1588
+ instructorAvatarId:
1589
+ sessionInstructor?.instructor?.person?.avatar_id ?? null,
1529
1590
  recurrenceId: s.recurrence_id ?? null,
1530
1591
  occurrenceIndex: s.occurrence_index ?? null,
1531
1592
  isException: Boolean(s.is_exception),
@@ -1799,4 +1860,114 @@ export class ClassGroupService {
1799
1860
 
1800
1861
  throw error;
1801
1862
  }
1863
+
1864
+ // ── Materials ─────────────────────────────────────────────────────────────
1865
+
1866
+ async listMaterials(classGroupId: number, sessionId?: number) {
1867
+ await this.assertClassGroupExists(classGroupId);
1868
+
1869
+ const where: any = { course_class_group_id: classGroupId };
1870
+ if (sessionId !== undefined) {
1871
+ where.course_class_session_id = sessionId;
1872
+ }
1873
+
1874
+ return this.prisma.course_class_group_material.findMany({
1875
+ where,
1876
+ include: {
1877
+ file: {
1878
+ select: {
1879
+ id: true,
1880
+ filename: true,
1881
+ path: true,
1882
+ size: true,
1883
+ },
1884
+ },
1885
+ course_class_session: {
1886
+ select: {
1887
+ id: true,
1888
+ title: true,
1889
+ },
1890
+ },
1891
+ },
1892
+ orderBy: [{ sort_order: 'asc' }, { created_at: 'asc' }],
1893
+ });
1894
+ }
1895
+
1896
+ async createMaterial(
1897
+ classGroupId: number,
1898
+ dto: import('./dto/material.dto').CreateMaterialDto,
1899
+ ) {
1900
+ await this.assertClassGroupExists(classGroupId);
1901
+
1902
+ if (dto.sessionId) {
1903
+ const session = await this.prisma.course_class_session.findFirst({
1904
+ where: { id: dto.sessionId, course_class_group_id: classGroupId },
1905
+ select: { id: true },
1906
+ });
1907
+ if (!session) {
1908
+ throw new NotFoundException('Session not found for this class group');
1909
+ }
1910
+ }
1911
+
1912
+ return this.prisma.course_class_group_material.create({
1913
+ data: {
1914
+ course_class_group_id: classGroupId,
1915
+ course_class_session_id: dto.sessionId ?? null,
1916
+ title: dto.title,
1917
+ description: dto.description ?? null,
1918
+ material_type: dto.materialType as any,
1919
+ file_id: dto.fileId ?? null,
1920
+ url: dto.url ?? null,
1921
+ sort_order: dto.sortOrder ?? 0,
1922
+ },
1923
+ include: {
1924
+ file: {
1925
+ select: {
1926
+ id: true,
1927
+ filename: true,
1928
+ path: true,
1929
+ size: true,
1930
+ },
1931
+ },
1932
+ },
1933
+ });
1934
+ }
1935
+
1936
+ async deleteMaterial(classGroupId: number, materialId: number) {
1937
+ await this.assertClassGroupExists(classGroupId);
1938
+
1939
+ const material = await this.prisma.course_class_group_material.findFirst({
1940
+ where: { id: materialId, course_class_group_id: classGroupId },
1941
+ select: { id: true },
1942
+ });
1943
+
1944
+ if (!material) {
1945
+ throw new NotFoundException('Material not found');
1946
+ }
1947
+
1948
+ await this.prisma.course_class_group_material.delete({
1949
+ where: { id: materialId },
1950
+ });
1951
+
1952
+ return { success: true };
1953
+ }
1954
+
1955
+ async countMaterialsBySession(classGroupId: number): Promise<Record<number, number>> {
1956
+ const counts = await this.prisma.course_class_group_material.groupBy({
1957
+ by: ['course_class_session_id'],
1958
+ where: {
1959
+ course_class_group_id: classGroupId,
1960
+ course_class_session_id: { not: null },
1961
+ },
1962
+ _count: { id: true },
1963
+ });
1964
+
1965
+ const result: Record<number, number> = {};
1966
+ for (const row of counts) {
1967
+ if (row.course_class_session_id !== null) {
1968
+ result[row.course_class_session_id] = row._count.id;
1969
+ }
1970
+ }
1971
+ return result;
1972
+ }
1802
1973
  }