@hed-hog/lms 0.0.364 → 0.0.365

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 (218) hide show
  1. package/dist/bitcode-wallet/bitcode-wallet.service.d.ts +1 -0
  2. package/dist/bitcode-wallet/bitcode-wallet.service.d.ts.map +1 -1
  3. package/dist/bitcode-wallet/bitcode-wallet.service.js +22 -3
  4. package/dist/bitcode-wallet/bitcode-wallet.service.js.map +1 -1
  5. package/dist/course/course-export-scorm12-worker.service.d.ts +21 -0
  6. package/dist/course/course-export-scorm12-worker.service.d.ts.map +1 -0
  7. package/dist/course/course-export-scorm12-worker.service.js +109 -0
  8. package/dist/course/course-export-scorm12-worker.service.js.map +1 -0
  9. package/dist/course/course-export-scorm12.service.d.ts +42 -0
  10. package/dist/course/course-export-scorm12.service.d.ts.map +1 -0
  11. package/dist/course/course-export-scorm12.service.js +628 -0
  12. package/dist/course/course-export-scorm12.service.js.map +1 -0
  13. package/dist/course/course-export.service.d.ts +84 -0
  14. package/dist/course/course-export.service.d.ts.map +1 -0
  15. package/dist/course/course-export.service.js +237 -0
  16. package/dist/course/course-export.service.js.map +1 -0
  17. package/dist/course/course-structure.controller.d.ts +17 -9
  18. package/dist/course/course-structure.controller.d.ts.map +1 -1
  19. package/dist/course/course-structure.controller.js +17 -4
  20. package/dist/course/course-structure.controller.js.map +1 -1
  21. package/dist/course/course-structure.service.d.ts +12 -4
  22. package/dist/course/course-structure.service.d.ts.map +1 -1
  23. package/dist/course/course-structure.service.js +98 -23
  24. package/dist/course/course-structure.service.js.map +1 -1
  25. package/dist/course/course-video-hls.service.d.ts +57 -0
  26. package/dist/course/course-video-hls.service.d.ts.map +1 -0
  27. package/dist/course/course-video-hls.service.js +767 -0
  28. package/dist/course/course-video-hls.service.js.map +1 -0
  29. package/dist/course/course.controller.d.ts +45 -13
  30. package/dist/course/course.controller.d.ts.map +1 -1
  31. package/dist/course/course.controller.js +40 -26
  32. package/dist/course/course.controller.js.map +1 -1
  33. package/dist/course/course.mcp-tools.js +1 -1
  34. package/dist/course/course.mcp-tools.js.map +1 -1
  35. package/dist/course/course.module.d.ts.map +1 -1
  36. package/dist/course/course.module.js +11 -0
  37. package/dist/course/course.module.js.map +1 -1
  38. package/dist/course/course.service.d.ts +6 -9
  39. package/dist/course/course.service.d.ts.map +1 -1
  40. package/dist/course/course.service.js +57 -48
  41. package/dist/course/course.service.js.map +1 -1
  42. package/dist/course/dto/cleanup-course-storage.dto.d.ts +1 -1
  43. package/dist/course/dto/cleanup-course-storage.dto.d.ts.map +1 -1
  44. package/dist/course/dto/cleanup-course-storage.dto.js +1 -0
  45. package/dist/course/dto/cleanup-course-storage.dto.js.map +1 -1
  46. package/dist/course/dto/cleanup-upload-history.dto.d.ts +1 -1
  47. package/dist/course/dto/cleanup-upload-history.dto.d.ts.map +1 -1
  48. package/dist/course/dto/cleanup-upload-history.dto.js +1 -1
  49. package/dist/course/dto/cleanup-upload-history.dto.js.map +1 -1
  50. package/dist/course/dto/create-course-bulk-job.dto.d.ts +2 -1
  51. package/dist/course/dto/create-course-bulk-job.dto.d.ts.map +1 -1
  52. package/dist/course/dto/create-course-bulk-job.dto.js +6 -1
  53. package/dist/course/dto/create-course-bulk-job.dto.js.map +1 -1
  54. package/dist/course/dto/create-course-export.dto.d.ts +14 -0
  55. package/dist/course/dto/create-course-export.dto.d.ts.map +1 -0
  56. package/dist/course/dto/create-course-export.dto.js +71 -0
  57. package/dist/course/dto/create-course-export.dto.js.map +1 -0
  58. package/dist/course/dto/create-course-structure-lesson.dto.d.ts +2 -2
  59. package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
  60. package/dist/course/dto/create-course-structure-lesson.dto.js +3 -2
  61. package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
  62. package/dist/course/lms-bulk-upload-automation.service.d.ts +16 -1
  63. package/dist/course/lms-bulk-upload-automation.service.d.ts.map +1 -1
  64. package/dist/course/lms-bulk-upload-automation.service.js +102 -8
  65. package/dist/course/lms-bulk-upload-automation.service.js.map +1 -1
  66. package/dist/course/lms-bulk-upload-infra.service.d.ts +1 -0
  67. package/dist/course/lms-bulk-upload-infra.service.d.ts.map +1 -1
  68. package/dist/course/lms-bulk-upload-infra.service.js +32 -8
  69. package/dist/course/lms-bulk-upload-infra.service.js.map +1 -1
  70. package/dist/course/lms-bulk-upload.controller.d.ts +30 -3
  71. package/dist/course/lms-bulk-upload.controller.d.ts.map +1 -1
  72. package/dist/course/lms-bulk-upload.controller.js +43 -2
  73. package/dist/course/lms-bulk-upload.controller.js.map +1 -1
  74. package/dist/course/lms-bulk-upload.service.d.ts +11 -0
  75. package/dist/course/lms-bulk-upload.service.d.ts.map +1 -1
  76. package/dist/course/lms-bulk-upload.service.js +59 -6
  77. package/dist/course/lms-bulk-upload.service.js.map +1 -1
  78. package/dist/course/lms-setting.controller.d.ts +2 -1
  79. package/dist/course/lms-setting.controller.d.ts.map +1 -1
  80. package/dist/course/lms-setting.controller.js +4 -2
  81. package/dist/course/lms-setting.controller.js.map +1 -1
  82. package/dist/course/scorm12-schemas.d.ts +4 -0
  83. package/dist/course/scorm12-schemas.d.ts.map +1 -0
  84. package/dist/course/scorm12-schemas.js +9 -0
  85. package/dist/course/scorm12-schemas.js.map +1 -0
  86. package/dist/enterprise/training/training-student.service.d.ts +51 -0
  87. package/dist/enterprise/training/training-student.service.d.ts.map +1 -1
  88. package/dist/enterprise/training/training-student.service.js +217 -4
  89. package/dist/enterprise/training/training-student.service.js.map +1 -1
  90. package/dist/evaluation/evaluation.service.d.ts +18 -0
  91. package/dist/evaluation/evaluation.service.d.ts.map +1 -1
  92. package/dist/evaluation/evaluation.service.js +125 -0
  93. package/dist/evaluation/evaluation.service.js.map +1 -1
  94. package/dist/exam/dto/create-standalone-question.dto.d.ts +12 -0
  95. package/dist/exam/dto/create-standalone-question.dto.d.ts.map +1 -0
  96. package/dist/exam/dto/create-standalone-question.dto.js +70 -0
  97. package/dist/exam/dto/create-standalone-question.dto.js.map +1 -0
  98. package/dist/exam/exam.module.d.ts.map +1 -1
  99. package/dist/exam/exam.module.js +2 -1
  100. package/dist/exam/exam.module.js.map +1 -1
  101. package/dist/exam/exam.service.d.ts +21 -0
  102. package/dist/exam/exam.service.d.ts.map +1 -1
  103. package/dist/exam/exam.service.js +80 -0
  104. package/dist/exam/exam.service.js.map +1 -1
  105. package/dist/exam/question.controller.d.ts +27 -0
  106. package/dist/exam/question.controller.d.ts.map +1 -0
  107. package/dist/exam/question.controller.js +53 -0
  108. package/dist/exam/question.controller.js.map +1 -0
  109. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts +4 -0
  110. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts.map +1 -1
  111. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js +161 -25
  112. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js.map +1 -1
  113. package/dist/libraries/lms/tsconfig.tsbuildinfo +1 -1
  114. package/dist/lms-commerce-access.subscriber.d.ts +11 -0
  115. package/dist/lms-commerce-access.subscriber.d.ts.map +1 -0
  116. package/dist/lms-commerce-access.subscriber.js +74 -0
  117. package/dist/lms-commerce-access.subscriber.js.map +1 -0
  118. package/dist/lms.module.d.ts.map +1 -1
  119. package/dist/lms.module.js +6 -5
  120. package/dist/lms.module.js.map +1 -1
  121. package/dist/platforma/platforma-video.service.d.ts +39 -0
  122. package/dist/platforma/platforma-video.service.d.ts.map +1 -0
  123. package/dist/platforma/platforma-video.service.js +301 -0
  124. package/dist/platforma/platforma-video.service.js.map +1 -0
  125. package/dist/platforma/platforma.controller.d.ts +95 -1
  126. package/dist/platforma/platforma.controller.d.ts.map +1 -1
  127. package/dist/platforma/platforma.controller.js +160 -2
  128. package/dist/platforma/platforma.controller.js.map +1 -1
  129. package/dist/student-xp/dto/grant-skill-card-xp.dto.d.ts +5 -0
  130. package/dist/student-xp/dto/grant-skill-card-xp.dto.d.ts.map +1 -0
  131. package/dist/student-xp/dto/grant-skill-card-xp.dto.js +26 -0
  132. package/dist/student-xp/dto/grant-skill-card-xp.dto.js.map +1 -0
  133. package/dist/student-xp/student-xp.controller.d.ts +15 -0
  134. package/dist/student-xp/student-xp.controller.d.ts.map +1 -1
  135. package/dist/student-xp/student-xp.controller.js +24 -0
  136. package/dist/student-xp/student-xp.controller.js.map +1 -1
  137. package/dist/student-xp/student-xp.service.d.ts +16 -0
  138. package/dist/student-xp/student-xp.service.d.ts.map +1 -1
  139. package/dist/student-xp/student-xp.service.js +51 -1
  140. package/dist/student-xp/student-xp.service.js.map +1 -1
  141. package/hedhog/data/evaluation_topic.yaml +17 -0
  142. package/hedhog/data/menu.yaml +0 -17
  143. package/hedhog/data/queue_definition.yaml +48 -0
  144. package/hedhog/data/route.yaml +94 -124
  145. package/hedhog/data/setting_group.yaml +19 -19
  146. package/hedhog/frontend/app/bulk-upload-sessions/page.tsx.ejs +337 -41
  147. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +69 -4
  148. package/hedhog/frontend/app/courses/[id]/structure/_components/course-export-sheet.tsx.ejs +420 -0
  149. package/hedhog/frontend/app/courses/[id]/structure/_components/course-exports-tab.tsx.ejs +308 -0
  150. package/hedhog/frontend/app/courses/[id]/structure/_components/course-overview-tab.tsx.ejs +17 -15
  151. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +51 -63
  152. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-panel.tsx.ejs +8 -3
  153. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +31 -8
  154. package/hedhog/frontend/app/courses/[id]/structure/_components/drag-overlay.tsx.ejs +16 -9
  155. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +201 -401
  156. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +378 -690
  157. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +1 -2
  158. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +3 -9
  159. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +1 -1
  160. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +6 -10
  161. package/hedhog/frontend/app/courses/[id]/structure/_data/services/course-structure.service.ts.ejs +49 -0
  162. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +4 -3
  163. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-content-overview.ts.ejs +0 -1
  164. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-exports.ts.ejs +106 -0
  165. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +28 -1
  166. package/hedhog/frontend/app/courses/[id]/structure/_data/use-lms-settings-query.ts.ejs +0 -2
  167. package/hedhog/frontend/app/courses/page.tsx.ejs +45 -0
  168. package/hedhog/frontend/messages/en.json +26 -28
  169. package/hedhog/frontend/messages/pt.json +26 -28
  170. package/hedhog/table/course_export.yaml +62 -0
  171. package/package.json +13 -9
  172. package/src/bitcode-wallet/bitcode-wallet.service.ts +43 -4
  173. package/src/course/course-export-scorm12-worker.service.ts +124 -0
  174. package/src/course/course-export-scorm12.service.ts +668 -0
  175. package/src/course/course-export.service.ts +280 -0
  176. package/src/course/course-structure.controller.ts +14 -2
  177. package/src/course/course-structure.service.ts +100 -7
  178. package/src/course/course-video-hls.service.ts +946 -0
  179. package/src/course/course.controller.ts +33 -19
  180. package/src/course/course.mcp-tools.ts +1 -1
  181. package/src/course/course.module.ts +11 -0
  182. package/src/course/course.service.ts +73 -60
  183. package/src/course/dto/cleanup-course-storage.dto.ts +1 -0
  184. package/src/course/dto/cleanup-upload-history.dto.ts +1 -1
  185. package/src/course/dto/create-course-bulk-job.dto.ts +7 -3
  186. package/src/course/dto/create-course-export.dto.ts +56 -0
  187. package/src/course/dto/create-course-structure-lesson.dto.ts +4 -3
  188. package/src/course/lms-bulk-upload-automation.service.ts +153 -6
  189. package/src/course/lms-bulk-upload-infra.service.ts +39 -6
  190. package/src/course/lms-bulk-upload.controller.ts +32 -2
  191. package/src/course/lms-bulk-upload.service.ts +70 -7
  192. package/src/course/lms-setting.controller.ts +4 -2
  193. package/src/course/scorm12-schemas.ts +9 -0
  194. package/src/enterprise/training/training-student.service.ts +221 -2
  195. package/src/evaluation/evaluation.service.ts +123 -0
  196. package/src/exam/dto/create-standalone-question.dto.ts +66 -0
  197. package/src/exam/exam.module.ts +2 -1
  198. package/src/exam/exam.service.ts +86 -0
  199. package/src/exam/question.controller.ts +28 -0
  200. package/src/lesson-xp-map/lesson-xp-ai-calculation.service.ts +205 -31
  201. package/src/lms-commerce-access.subscriber.ts +88 -0
  202. package/src/lms.module.ts +6 -5
  203. package/src/platforma/platforma-video.service.ts +346 -0
  204. package/src/platforma/platforma.controller.ts +95 -1
  205. package/src/platforma/platforma.service.ts +268 -268
  206. package/src/student-xp/dto/grant-skill-card-xp.dto.ts +10 -0
  207. package/src/student-xp/student-xp.controller.ts +18 -2
  208. package/src/student-xp/student-xp.service.ts +84 -2
  209. package/hedhog/data/video_resolution_profile.yaml +0 -7
  210. package/hedhog/frontend/app/video-resolution-profiles/page.tsx.ejs +0 -607
  211. package/hedhog/table/course_video_resolution_profile.yaml +0 -22
  212. package/hedhog/table/video_resolution_profile.yaml +0 -18
  213. package/src/video-resolution-profile/dto/create-video-resolution-profile.dto.ts +0 -16
  214. package/src/video-resolution-profile/dto/update-video-resolution-profile.dto.ts +0 -16
  215. package/src/video-resolution-profile/video-resolution-profile.controller.ts +0 -62
  216. package/src/video-resolution-profile/video-resolution-profile.mcp-tools.ts +0 -128
  217. package/src/video-resolution-profile/video-resolution-profile.module.ts +0 -13
  218. package/src/video-resolution-profile/video-resolution-profile.service.ts +0 -117
@@ -72,7 +72,7 @@ const TITLE_PATTERNS = [
72
72
  'Conceitos fundamentais',
73
73
  'Configuracao do ambiente',
74
74
  'Pratica guiada passo a passo',
75
- 'Exercicio: implementacao',
75
+ 'Pratica: implementacao',
76
76
  'Quiz de revisao',
77
77
  'Caso de uso real',
78
78
  'Implementacao completa',
@@ -107,7 +107,6 @@ const LESSON_TYPES: LessonType[] = [
107
107
  'video',
108
108
  'video',
109
109
  'questao',
110
- 'exercicio',
111
110
  ];
112
111
  const PROVIDERS: VideoProvider[] = [
113
112
  'youtube',
@@ -7,8 +7,8 @@ import {
7
7
  TooltipTrigger,
8
8
  } from '@/components/ui/tooltip';
9
9
  import { cn } from '@/lib/utils';
10
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
10
11
  import {
11
- ClipboardList,
12
12
  Clock3,
13
13
  Eye,
14
14
  EyeOff,
@@ -24,19 +24,18 @@ import {
24
24
  } from 'lucide-react';
25
25
  import { useTranslations } from 'next-intl';
26
26
  import { useEffect, useRef } from 'react';
27
- import { useApp, useQuery } from '@hed-hog/next-app-provider';
28
- import { useUpdateLessonMutation } from '../_data/use-course-structure-mutations';
29
27
  import {
30
28
  getQueueJob,
31
29
  type QueueJobStatus,
32
30
  } from '../_data/services/course-structure.service';
31
+ import { useUpdateLessonMutation } from '../_data/use-course-structure-mutations';
33
32
  import { HighlightedText } from './highlighted-text';
34
33
  import { useStructureStore } from './store';
35
- import type { Lesson, LessonStatus, LessonType } from './types';
36
34
  import {
37
35
  getLessonAttachedResourceCount,
38
36
  getLessonUploadedVideoCount,
39
37
  } from './tree-helpers';
38
+ import type { Lesson, LessonStatus, LessonType } from './types';
40
39
  import { useTreeDisplaySettings } from './use-tree-display-settings';
41
40
 
42
41
  // ── Type configs ──────────────────────────────────────────────────────────────
@@ -48,11 +47,6 @@ const LESSON_TYPE_CONFIG: Record<
48
47
  video: { icon: Video, color: 'text-violet-500', label: 'Vídeo' },
49
48
  post: { icon: FileText, color: 'text-emerald-500', label: 'Post' },
50
49
  questao: { icon: HelpCircle, color: 'text-amber-500', label: 'Quiz' },
51
- exercicio: {
52
- icon: ClipboardList,
53
- color: 'text-rose-500',
54
- label: 'Exercício',
55
- },
56
50
  };
57
51
 
58
52
  const STATUS_CONFIG: Record<LessonStatus, { label: string; dot: string }> = {
@@ -2,7 +2,7 @@
2
2
  // Types — LMS Course Structure TreeView
3
3
  // ─────────────────────────────────────────────────────────────────────────────
4
4
 
5
- export type LessonType = 'video' | 'questao' | 'post' | 'exercicio';
5
+ export type LessonType = 'video' | 'questao' | 'post';
6
6
  export type VideoProvider = 'youtube' | 'vimeo' | 'file_storage';
7
7
  export type ItemType = 'course' | 'session' | 'lesson';
8
8
 
@@ -14,9 +14,8 @@
14
14
  *
15
15
  * ⚠️ KNOWN MISMATCHES (documented with inline comments):
16
16
  * 1. exameVinculado (number) ↔ linkedExam (string) — converted with String()/Number()
17
- * 2. 'exercicio' tipo exists in backend and is now part of frontend LessonType.
18
- * 3. Session.order — not returned by API; inferred from sorted array position.
19
- * 4. Resource.size / url — not returned by API; left as empty string / undefined.
17
+ * 2. Session.ordernot returned by API; inferred from sorted array position.
18
+ * 3. Resource.size / url — not returned by API; left as empty string / undefined.
20
19
  */
21
20
 
22
21
  import type {
@@ -55,7 +54,7 @@ import type {
55
54
  * Map backend lesson type to the frontend LessonType union.
56
55
  *
57
56
  * ⚠️ ADAPTER POINT:
58
- * 'exercicio' is now part of the frontend LessonType union.
57
+ * Keep this helper updated if backend introduces new lesson type aliases.
59
58
  */
60
59
  function normalizeLessonType(tipo: ApiLesson['tipo']): LessonType {
61
60
  return tipo as LessonType;
@@ -129,7 +128,7 @@ export function normalizeCourse(raw: ApiCourse): Course {
129
128
  description: raw.descricao,
130
129
  slug: raw.slug,
131
130
  offeringType: raw.offeringType,
132
- published: false,
131
+ published: raw.status === 'published',
133
132
  };
134
133
  }
135
134
 
@@ -301,9 +300,7 @@ export function toCreateLessonPayload(
301
300
  ): ApiCreateLessonPayload {
302
301
  return {
303
302
  titulo: data.title,
304
- // ⚠️ TYPE NOTE: 'exercicio' is not in the frontend LessonType but the
305
- // backend accepts it. Cast is safe as long as LessonType ⊆ ApiLesson['tipo'].
306
- tipo: data.type as ApiCreateLessonPayload['tipo'],
303
+ tipo: data.type,
307
304
  duracao: data.duration,
308
305
  published: data.published,
309
306
  statusProducao: data.status,
@@ -361,8 +358,7 @@ export function toUpdateLessonPayload(
361
358
  ): ApiUpdateLessonPayload {
362
359
  const payload: ApiUpdateLessonPayload = {};
363
360
  if (data.title !== undefined) payload.titulo = data.title;
364
- if (data.type !== undefined)
365
- payload.tipo = data.type as ApiCreateLessonPayload['tipo'];
361
+ if (data.type !== undefined) payload.tipo = data.type;
366
362
  if (data.duration !== undefined) payload.duracao = data.duration;
367
363
  if (data.published !== undefined) payload.published = data.published;
368
364
  if (data.status !== undefined) payload.statusProducao = data.status;
@@ -54,6 +54,7 @@ type RequestFn = (input: {
54
54
  data?: unknown;
55
55
  headers?: Record<string, string>;
56
56
  onUploadProgress?: (event: { loaded: number; total?: number }) => void;
57
+ showErrors?: boolean;
57
58
  }) => Promise<{ data: unknown }>;
58
59
 
59
60
  export type QueueJobStatus =
@@ -584,6 +585,7 @@ export async function getQueueJob(
584
585
  const response = await request({
585
586
  url: `/queue/jobs/${jobId}`,
586
587
  method: 'GET',
588
+ showErrors: false,
587
589
  });
588
590
  return extractData<QueueJobResponse>(response);
589
591
  }
@@ -604,3 +606,50 @@ export async function deleteFile(
604
606
  data: { ids: [fileId] },
605
607
  });
606
608
  }
609
+
610
+ // ── Questions ─────────────────────────────────────────────────────────────────
611
+
612
+ export interface ApiQuestion {
613
+ id: number;
614
+ questionType: string;
615
+ statement: string;
616
+ points: number;
617
+ subjectId: number | null;
618
+ subjectName: string | null;
619
+ }
620
+
621
+ export interface ApiQuestionListResponse {
622
+ total: number;
623
+ page: number;
624
+ limit: number;
625
+ data: ApiQuestion[];
626
+ }
627
+
628
+ export interface CreateQuestionPayload {
629
+ statement: string;
630
+ questionType?: string;
631
+ points?: number;
632
+ subjectId?: number;
633
+ explanation?: string;
634
+ alternatives?: { text: string; isCorrect: boolean }[];
635
+ fillBlankAnswers?: { answer: string; alternatives?: string[] }[];
636
+ matchingPairs?: { id: string; leftText: string; rightText: string }[];
637
+ }
638
+
639
+ export async function listQuestions(
640
+ request: RequestFn,
641
+ search?: string
642
+ ): Promise<ApiQuestionListResponse> {
643
+ const params = new URLSearchParams({ limit: '100' });
644
+ if (search?.trim()) params.set('search', search.trim());
645
+ const response = await request({ url: `/lms/questions?${params}`, method: 'GET' });
646
+ return response.data as ApiQuestionListResponse;
647
+ }
648
+
649
+ export async function createQuestion(
650
+ request: RequestFn,
651
+ dto: CreateQuestionPayload
652
+ ): Promise<ApiQuestion> {
653
+ const response = await request({ url: '/lms/questions', method: 'POST', data: dto });
654
+ return response.data as ApiQuestion;
655
+ }
@@ -65,7 +65,7 @@ export interface ApiLessonInstructor {
65
65
  * The backend maps DB lesson types to UI types in mapDbTypeToUiType():
66
66
  * DB 'video' → UI 'video'
67
67
  * DB 'text' → UI 'post'
68
- * DB 'quiz' → UI 'questao' or 'exercicio' (based on content.sourceType)
68
+ * DB 'quiz' → UI 'questao' (based on content.sourceType)
69
69
  *
70
70
  * ⚠️ BACKEND NOTE — missing fields:
71
71
  * `order` and `visibility` are NOT included in the API response.
@@ -80,7 +80,7 @@ export interface ApiLesson {
80
80
  descricaoPrivada: string;
81
81
  locale_id?: number | null;
82
82
  /** UI-level lesson type resolved by the backend service. */
83
- tipo: 'video' | 'questao' | 'post' | 'exercicio';
83
+ tipo: 'video' | 'questao' | 'post';
84
84
  /** Duration in minutes. */
85
85
  duracao: number;
86
86
  published?: boolean;
@@ -140,6 +140,7 @@ export interface ApiCourse {
140
140
  titulo: string;
141
141
  descricao: string;
142
142
  offeringType?: 'scheduled' | 'on_demand' | 'blended';
143
+ status?: 'draft' | 'published' | 'archived';
143
144
  recursos?: ApiCourseResource[];
144
145
  }
145
146
 
@@ -207,7 +208,7 @@ export interface ApiUpdateSessionPayload {
207
208
  */
208
209
  export interface ApiCreateLessonPayload {
209
210
  titulo: string;
210
- tipo: 'video' | 'questao' | 'post' | 'exercicio';
211
+ tipo: 'video' | 'questao' | 'post';
211
212
  /** Duration in minutes. */
212
213
  duracao: number;
213
214
  published?: boolean;
@@ -11,7 +11,6 @@ export type CourseContentOverview = {
11
11
  video: number;
12
12
  questao: number;
13
13
  post: number;
14
- exercicio: number;
15
14
  };
16
15
  };
17
16
  videos: {
@@ -0,0 +1,106 @@
1
+ 'use client';
2
+
3
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
4
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
5
+
6
+ export type ScormVisualSettings = {
7
+ primaryColor?: string;
8
+ fontFamily?: string;
9
+ fontSize?: string;
10
+ sidebarWidth?: number;
11
+ sidebarPosition?: string;
12
+ progressStyle?: string;
13
+ sidebarTheme?: string;
14
+ };
15
+
16
+ export type CourseExportRecord = {
17
+ id: number;
18
+ format: string;
19
+ status: 'pending' | 'processing' | 'completed' | 'failed';
20
+ settings: Record<string, unknown> | null;
21
+ duration_seconds: number | null;
22
+ error_message: string | null;
23
+ created_at: string;
24
+ finished_at: string | null;
25
+ file_id: number | null;
26
+ notification_id: number | null;
27
+ file: {
28
+ id: number;
29
+ path: string;
30
+ filename: string;
31
+ size: number | null;
32
+ } | null;
33
+ user: {
34
+ id: number;
35
+ name: string;
36
+ photo_id: number | null;
37
+ };
38
+ };
39
+
40
+ export function courseExportsQueryKey(courseId: string) {
41
+ return ['course-exports', courseId] as const;
42
+ }
43
+
44
+ export function useCourseExportsQuery(courseId: string | null) {
45
+ const { request } = useApp();
46
+
47
+ return useQuery<CourseExportRecord[]>({
48
+ queryKey: courseExportsQueryKey(courseId ?? ''),
49
+ enabled: Boolean(courseId),
50
+ queryFn: async () => {
51
+ const res = await request<CourseExportRecord[]>({
52
+ url: `/lms/courses/${courseId}/exports`,
53
+ method: 'GET',
54
+ });
55
+ return res.data ?? [];
56
+ },
57
+ initialData: [],
58
+ });
59
+ }
60
+
61
+ export function useCreateCourseExportMutation(courseId: string | null) {
62
+ const { request } = useApp();
63
+ const queryClient = useQueryClient();
64
+
65
+ return useMutation({
66
+ mutationFn: async (payload: {
67
+ format: 'scorm_1_2';
68
+ visualSettings?: ScormVisualSettings;
69
+ }) => {
70
+ const res = await request<{
71
+ exportId: number;
72
+ queueJobId: number;
73
+ notificationId: number;
74
+ }>({
75
+ url: `/lms/courses/${courseId}/exports`,
76
+ method: 'POST',
77
+ data: payload,
78
+ });
79
+ return res.data;
80
+ },
81
+ onSuccess: () => {
82
+ queryClient.invalidateQueries({
83
+ queryKey: courseExportsQueryKey(courseId ?? ''),
84
+ });
85
+ },
86
+ });
87
+ }
88
+
89
+ export function useDeleteCourseExportMutation(courseId: string | null) {
90
+ const { request } = useApp();
91
+ const queryClient = useQueryClient();
92
+
93
+ return useMutation({
94
+ mutationFn: async (exportId: number) => {
95
+ await request({
96
+ url: `/lms/courses/${courseId}/exports/${exportId}`,
97
+ method: 'DELETE',
98
+ });
99
+ },
100
+ onSuccess: () => {
101
+ queryClient.invalidateQueries({
102
+ queryKey: courseExportsQueryKey(courseId ?? ''),
103
+ });
104
+ },
105
+ });
106
+ }
@@ -20,7 +20,7 @@
20
20
  * onError: restore cache snapshot + Zustand rollback
21
21
  */
22
22
 
23
- import { useMutation, useQueryClient } from '@tanstack/react-query';
23
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
24
24
  import { useTranslations } from 'next-intl';
25
25
  import { toast } from 'sonner';
26
26
 
@@ -54,6 +54,9 @@ import {
54
54
  updateCourse as apiUpdateCourse,
55
55
  updateLesson as apiUpdateLesson,
56
56
  updateSession as apiUpdateSession,
57
+ listQuestions as apiListQuestions,
58
+ createQuestion as apiCreateQuestion,
59
+ type CreateQuestionPayload,
57
60
  } from './services/course-structure.service';
58
61
  import type { CourseStructureCacheData } from './use-course-structure-query';
59
62
  import { courseStructureQueryKey } from './use-course-structure-query';
@@ -1104,3 +1107,27 @@ export function useUpdateResourceTypeMutation() {
1104
1107
  },
1105
1108
  });
1106
1109
  }
1110
+
1111
+ // ─────────────────────────────────────────────────────────────────────────────
1112
+ // useQuestionsQuery / useCreateQuestionMutation
1113
+ // ─────────────────────────────────────────────────────────────────────────────
1114
+
1115
+ export function useQuestionsQuery(search?: string) {
1116
+ const { request } = useApp();
1117
+ return useQuery({
1118
+ queryKey: ['lms-questions', search ?? ''],
1119
+ queryFn: () => apiListQuestions(request, search),
1120
+ staleTime: 30_000,
1121
+ });
1122
+ }
1123
+
1124
+ export function useCreateQuestionMutation() {
1125
+ const { request } = useApp();
1126
+ const queryClient = useQueryClient();
1127
+ return useMutation({
1128
+ mutationFn: (dto: CreateQuestionPayload) => apiCreateQuestion(request, dto),
1129
+ onSuccess: () => {
1130
+ void queryClient.invalidateQueries({ queryKey: ['lms-questions'] });
1131
+ },
1132
+ });
1133
+ }
@@ -3,7 +3,6 @@
3
3
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
4
4
 
5
5
  export interface LmsFeatureFlags {
6
- videoConversionEnabled: boolean;
7
6
  imageExtractionEnabled: boolean;
8
7
  transcriptionEnabled: boolean;
9
8
  youtubeEnabled: boolean;
@@ -11,7 +10,6 @@ export interface LmsFeatureFlags {
11
10
  }
12
11
 
13
12
  const DEFAULT_FLAGS: LmsFeatureFlags = {
14
- videoConversionEnabled: true,
15
13
  imageExtractionEnabled: true,
16
14
  transcriptionEnabled: true,
17
15
  youtubeEnabled: true,
@@ -95,6 +95,7 @@ interface Curso {
95
95
  criadoEm: string;
96
96
  logoFileId?: number | null;
97
97
  operationsProjectId?: number | null;
98
+ processando?: boolean;
98
99
  }
99
100
 
100
101
  type ApiCourse = {
@@ -122,6 +123,7 @@ type ApiCourse = {
122
123
  createdAt: string;
123
124
  logoFileId?: number | null;
124
125
  operationsProjectId?: number | null;
126
+ hasRunningJobs?: boolean;
125
127
  };
126
128
 
127
129
  type ApiCourseList = {
@@ -238,6 +240,7 @@ function mapApiCourse(course: ApiCourse): Curso {
238
240
  criadoEm: course.createdAt ?? '',
239
241
  logoFileId: course.logoFileId ?? null,
240
242
  operationsProjectId: course.operationsProjectId ?? null,
243
+ processando: course.hasRunningJobs ?? false,
241
244
  };
242
245
  }
243
246
 
@@ -1405,6 +1408,27 @@ export default function CursosPage() {
1405
1408
  </Badge>
1406
1409
  </motion.div>
1407
1410
  )}
1411
+ {curso.processando &&
1412
+ !deletingCourseIds.has(curso.id) && (
1413
+ <motion.div
1414
+ initial={{ opacity: 0, scale: 0.8 }}
1415
+ animate={{ opacity: 1, scale: 1 }}
1416
+ exit={{ opacity: 0, scale: 0.8 }}
1417
+ transition={{
1418
+ type: 'spring',
1419
+ stiffness: 300,
1420
+ damping: 30,
1421
+ }}
1422
+ >
1423
+ <Badge
1424
+ variant="secondary"
1425
+ className="gap-1 rounded-full px-2 py-0.5 text-[10px]"
1426
+ >
1427
+ <Loader2 className="size-2.5 animate-spin" />
1428
+ Processando...
1429
+ </Badge>
1430
+ </motion.div>
1431
+ )}
1408
1432
  </div>
1409
1433
  </div>
1410
1434
  </div>
@@ -1554,6 +1578,27 @@ export default function CursosPage() {
1554
1578
  </Badge>
1555
1579
  </motion.div>
1556
1580
  )}
1581
+ {curso.processando &&
1582
+ !deletingCourseIds.has(curso.id) && (
1583
+ <motion.div
1584
+ initial={{ opacity: 0, scale: 0.8 }}
1585
+ animate={{ opacity: 1, scale: 1 }}
1586
+ exit={{ opacity: 0, scale: 0.8 }}
1587
+ transition={{
1588
+ type: 'spring',
1589
+ stiffness: 300,
1590
+ damping: 30,
1591
+ }}
1592
+ >
1593
+ <Badge
1594
+ variant="secondary"
1595
+ className="gap-1 text-[11px]"
1596
+ >
1597
+ <Loader2 className="size-2.5 animate-spin" />
1598
+ Processando...
1599
+ </Badge>
1600
+ </motion.div>
1601
+ )}
1557
1602
  {curso.destaque && (
1558
1603
  <span className="inline-flex items-center gap-1 rounded-full border border-amber-200 bg-amber-50 px-2 py-0.5 text-[11px] font-medium text-amber-700">
1559
1604
  <Star className="size-3 fill-amber-400 text-amber-400" />
@@ -579,6 +579,8 @@
579
579
  "statusProgress": "{completed}/{total} required items completed",
580
580
  "ready": "Ready to publish",
581
581
  "notReady": "Not ready yet",
582
+ "publishButton": "Publish course",
583
+ "alreadyPublished": "Course already published",
582
584
  "items": {
583
585
  "title": "Commercial title filled",
584
586
  "description": "Public description filled",
@@ -1197,36 +1199,32 @@
1197
1199
  },
1198
1200
  "addResource": "Add resource",
1199
1201
  "noResources": "No resources added",
1200
- "fileStorageVideosByResolution": "Videos by resolution (File Storage)",
1201
- "videoResolutionPlaceholder": "Eg.: 1080p",
1202
- "uploadVideoForResolution": "Upload video for this resolution",
1203
- "noResolutionVideosYet": "No resolution-based videos uploaded yet.",
1204
- "loadingVideoProfiles": "Loading course video profiles...",
1205
- "videoProfilesLoadError": "Could not load the course video profiles.",
1206
- "retryLoadVideoProfiles": "Try loading again",
1207
- "noVideoProfilesConfigured": "Configure video profiles in the course Resolutions tab before attaching File Storage videos.",
1202
+ "hlsStatusTitle": "HLS Stream",
1203
+ "hlsStatusReady": "Stream available for students.",
1204
+ "hlsStatusProcessing": "Processing video...",
1205
+ "hlsStatusPending": "No video processed yet.",
1208
1206
  "originalVideoTitle": "Original high-resolution video",
1209
- "originalVideoHint": "Upload an original video to automatically generate the course profile videos.",
1210
- "originalVideoPurpose": "This original file is only used to generate the course video variants.",
1211
- "fileStorageVideoHint": "Use the original video below to automatically generate the course profile variants.",
1212
- "uploadOriginalForConversion": "Upload original for conversion",
1207
+ "originalVideoHint": "Upload an original video to automatically generate the HLS stream for this course.",
1208
+ "originalVideoPurpose": "This original file is only used to generate the adaptive streaming for this course.",
1209
+ "fileStorageVideoHint": "Use the original video below to automatically generate the HLS stream for this course.",
1210
+ "uploadOriginalForConversion": "Upload video for processing",
1213
1211
  "replaceOriginalForConversion": "Replace original video",
1214
1212
  "retryConversionWithSavedOriginal": "Start a new job with the saved original",
1215
- "videoUploadBlockedWhileProcessing": "Original uploads stay blocked while the current conversion is still running.",
1213
+ "videoUploadBlockedWhileProcessing": "Original uploads stay blocked while the current processing job is still running.",
1216
1214
  "videoJobStateLoading": "Waiting for the latest job status before allowing new actions.",
1217
1215
  "videoProviderSavePending": "Wait for the provider to be saved before uploading the original video.",
1218
1216
  "videoUploadMaxSizeError": "The video exceeds the {size} limit.",
1219
- "videoConversionQueued": "Video conversion queued as job #{id}.",
1220
- "videoConversionFailed": "Could not queue the video conversion.",
1217
+ "videoConversionQueued": "Video processing queued as job #{id}.",
1218
+ "videoConversionFailed": "Could not queue the video for processing.",
1221
1219
  "videoConversionRetryFailed": "Could not start a new job with the saved original video.",
1222
- "videoConversionJob": "Conversion queued as job #{id}",
1223
- "videoJobFeedbackTitle": "Conversion tracking",
1224
- "videoJobToggleDetails": "Toggle conversion details",
1225
- "videoJobCollapsedSummary": "Conversion completed successfully. Processed videos are listed in the resolution section above.",
1226
- "videoJobLoading": "Loading conversion progress...",
1227
- "videoJobPendingLoad": "Preparing conversion job tracking...",
1228
- "awaitingConversion": "Awaiting conversion...",
1229
- "videoJobLoadError": "Could not load the conversion progress.",
1220
+ "videoConversionJob": "Processing queued as job #{id}",
1221
+ "videoJobFeedbackTitle": "Processing status",
1222
+ "videoJobToggleDetails": "Toggle processing details",
1223
+ "videoJobCollapsedSummary": "Processing completed successfully. HLS streaming is now available for students.",
1224
+ "videoJobLoading": "Loading processing progress...",
1225
+ "videoJobPendingLoad": "Preparing processing job tracking...",
1226
+ "awaitingConversion": "Awaiting processing...",
1227
+ "videoJobLoadError": "Could not load the processing progress.",
1230
1228
  "retryLoadVideoJob": "Try again",
1231
1229
  "videoJobIdLabel": "Job",
1232
1230
  "videoJobAttemptsLabel": "Attempts",
@@ -1242,7 +1240,6 @@
1242
1240
  "videoJobRecentEvents": "Recent events",
1243
1241
  "videoJobTranscriptionEvents": "Recent transcription events",
1244
1242
  "videoJobNoEvents": "No events recorded yet.",
1245
- "videoProfilesLockedWhileProcessing": "Profile video uploads stay locked while the original conversion job is active.",
1246
1243
  "videoJobStatuses": {
1247
1244
  "pending": "Queued",
1248
1245
  "scheduled": "Scheduled",
@@ -1274,8 +1271,9 @@
1274
1271
  },
1275
1272
  "videoJobProgress": {
1276
1273
  "download_original": "Downloading original video...",
1277
- "probe_duration": "Reading video duration...",
1278
- "convert_profile": "Converting video for the {profileName} profile...",
1274
+ "probe_duration": "Reading video dimensions and duration...",
1275
+ "hls_encode": "Generating HLS stream...",
1276
+ "hls_upload": "Uploading HLS segments to storage...",
1279
1277
  "extract_frames": "Extracting video frames...",
1280
1278
  "extract_frames_done": "Frames extracted: {count}.",
1281
1279
  "extract_audio": "Extracting video audio...",
@@ -1342,8 +1340,7 @@
1342
1340
  "types": {
1343
1341
  "video": "Video",
1344
1342
  "post": "Post",
1345
- "questao": "Question",
1346
- "exercicio": "Exercise"
1343
+ "questao": "Question"
1347
1344
  },
1348
1345
  "statuses": {
1349
1346
  "preparada": "Prepared",
@@ -1382,6 +1379,7 @@
1382
1379
  "videoUploadFailed": "Failed to upload {count} video file(s).",
1383
1380
  "newQuestion": "New question",
1384
1381
  "editQuestion": "Edit question",
1382
+ "loadingQuestions": "Loading questions...",
1385
1383
  "selectQuestion": "Select question...",
1386
1384
  "searchQuestion": "Search question...",
1387
1385
  "noQuestionsFound": "No questions found",