@hed-hog/lms 0.0.355 → 0.0.358

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 (115) hide show
  1. package/dist/course/course-audio-transcription.service.d.ts.map +1 -1
  2. package/dist/course/course-audio-transcription.service.js +15 -7
  3. package/dist/course/course-audio-transcription.service.js.map +1 -1
  4. package/dist/course/course-operations-integration.service.d.ts +31 -0
  5. package/dist/course/course-operations-integration.service.d.ts.map +1 -1
  6. package/dist/course/course-operations-integration.service.js +286 -22
  7. package/dist/course/course-operations-integration.service.js.map +1 -1
  8. package/dist/course/course-operations.controller.d.ts +10 -0
  9. package/dist/course/course-operations.controller.d.ts.map +1 -0
  10. package/dist/course/course-operations.controller.js +67 -0
  11. package/dist/course/course-operations.controller.js.map +1 -0
  12. package/dist/course/course-structure.controller.d.ts +15 -1
  13. package/dist/course/course-structure.controller.d.ts.map +1 -1
  14. package/dist/course/course-structure.service.d.ts +25 -1
  15. package/dist/course/course-structure.service.d.ts.map +1 -1
  16. package/dist/course/course-structure.service.js +160 -24
  17. package/dist/course/course-structure.service.js.map +1 -1
  18. package/dist/course/course.module.d.ts.map +1 -1
  19. package/dist/course/course.module.js +4 -1
  20. package/dist/course/course.module.js.map +1 -1
  21. package/dist/course/course.service.d.ts +4 -2
  22. package/dist/course/course.service.d.ts.map +1 -1
  23. package/dist/course/course.service.js +61 -2
  24. package/dist/course/course.service.js.map +1 -1
  25. package/dist/course/dto/create-course-structure-lesson.dto.d.ts +3 -0
  26. package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
  27. package/dist/course/dto/create-course-structure-lesson.dto.js +15 -0
  28. package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
  29. package/dist/course/dto/create-course-structure-session.dto.d.ts +1 -0
  30. package/dist/course/dto/create-course-structure-session.dto.d.ts.map +1 -1
  31. package/dist/course/dto/create-course-structure-session.dto.js +5 -0
  32. package/dist/course/dto/create-course-structure-session.dto.js.map +1 -1
  33. package/dist/course/dto/update-course-operations-config.dto.d.ts +6 -0
  34. package/dist/course/dto/update-course-operations-config.dto.d.ts.map +1 -0
  35. package/dist/course/dto/update-course-operations-config.dto.js +33 -0
  36. package/dist/course/dto/update-course-operations-config.dto.js.map +1 -0
  37. package/dist/course/lms-operations-task.subscriber.d.ts +13 -0
  38. package/dist/course/lms-operations-task.subscriber.d.ts.map +1 -0
  39. package/dist/course/lms-operations-task.subscriber.js +57 -0
  40. package/dist/course/lms-operations-task.subscriber.js.map +1 -0
  41. package/dist/enterprise/enterprise.service.js +1 -1
  42. package/dist/enterprise/enterprise.service.js.map +1 -1
  43. package/dist/enterprise/training/training-student.controller.d.ts +0 -95
  44. package/dist/enterprise/training/training-student.controller.d.ts.map +1 -1
  45. package/dist/enterprise/training/training-student.controller.js +1 -34
  46. package/dist/enterprise/training/training-student.controller.js.map +1 -1
  47. package/dist/enterprise/training/training-student.service.d.ts +63 -0
  48. package/dist/enterprise/training/training-student.service.d.ts.map +1 -1
  49. package/dist/enterprise/training/training-student.service.js +320 -4
  50. package/dist/enterprise/training/training-student.service.js.map +1 -1
  51. package/dist/instructor/instructor.service.d.ts.map +1 -1
  52. package/dist/instructor/instructor.service.js +12 -3
  53. package/dist/instructor/instructor.service.js.map +1 -1
  54. package/dist/lms.module.d.ts.map +1 -1
  55. package/dist/lms.module.js +2 -0
  56. package/dist/lms.module.js.map +1 -1
  57. package/dist/platforma/platforma.controller.d.ts +287 -0
  58. package/dist/platforma/platforma.controller.d.ts.map +1 -0
  59. package/dist/platforma/platforma.controller.js +147 -0
  60. package/dist/platforma/platforma.controller.js.map +1 -0
  61. package/hedhog/data/route.yaml +102 -9
  62. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +2 -2
  63. package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +10 -13
  64. package/hedhog/frontend/app/_lib/editor/templateSerializer.ts.ejs +26 -4
  65. package/hedhog/frontend/app/_lib/editor/types.ts.ejs +6 -0
  66. package/hedhog/frontend/app/certificates/models/CanvasStage.tsx.ejs +447 -31
  67. package/hedhog/frontend/app/certificates/models/LeftPanel.tsx.ejs +59 -47
  68. package/hedhog/frontend/app/certificates/models/RightPanel.tsx.ejs +201 -35
  69. package/hedhog/frontend/app/certificates/models/TemplateEditorPage.tsx.ejs +36 -5
  70. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +2 -2
  71. package/hedhog/frontend/app/courses/[id]/_components/course-edit-types.ts.ejs +1 -1
  72. package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +17 -6
  73. package/hedhog/frontend/app/courses/[id]/structure/_components/course-operations-tab.tsx.ejs +382 -0
  74. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +45 -8
  75. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +177 -67
  76. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +25 -60
  77. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +1 -0
  78. package/hedhog/frontend/app/courses/[id]/structure/_components/store.ts.ejs +11 -0
  79. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-context-menu.tsx.ejs +22 -0
  80. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +79 -64
  81. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +31 -14
  82. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +4 -4
  83. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +39 -27
  84. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +24 -2
  85. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +41 -6
  86. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-query.ts.ejs +2 -2
  87. package/hedhog/frontend/app/courses/page.tsx.ejs +80 -103
  88. package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +1 -1
  89. package/hedhog/frontend/app/enterprise/_components/enterprise-course-edit-sheet.tsx.ejs +2 -2
  90. package/hedhog/frontend/app/enterprise/page.tsx.ejs +18 -6
  91. package/hedhog/frontend/app/exams/[id]/page.tsx.ejs +5 -4
  92. package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +16 -10
  93. package/hedhog/frontend/app/paths/page.tsx.ejs +1 -1
  94. package/hedhog/frontend/app/training/page.tsx.ejs +1 -1
  95. package/hedhog/frontend/messages/en.json +7 -2
  96. package/hedhog/frontend/messages/pt.json +7 -2
  97. package/hedhog/table/course_lesson.yaml +2 -2
  98. package/hedhog/table/course_module.yaml +3 -0
  99. package/package.json +8 -8
  100. package/src/course/course-audio-transcription.service.ts +21 -8
  101. package/src/course/course-operations-integration.service.ts +460 -22
  102. package/src/course/course-operations.controller.ts +45 -0
  103. package/src/course/course-structure.service.ts +209 -4
  104. package/src/course/course.module.ts +4 -1
  105. package/src/course/course.service.ts +67 -1
  106. package/src/course/dto/create-course-structure-lesson.dto.ts +17 -0
  107. package/src/course/dto/create-course-structure-session.dto.ts +13 -1
  108. package/src/course/dto/update-course-operations-config.dto.ts +16 -0
  109. package/src/course/lms-operations-task.subscriber.ts +44 -0
  110. package/src/enterprise/enterprise.service.ts +1 -1
  111. package/src/enterprise/training/training-student.controller.ts +3 -27
  112. package/src/enterprise/training/training-student.service.ts +350 -2
  113. package/src/instructor/instructor.service.ts +12 -3
  114. package/src/lms.module.ts +2 -0
  115. package/src/platforma/platforma.controller.ts +92 -0
@@ -16,7 +16,6 @@ import {
16
16
  Film,
17
17
  HelpCircle,
18
18
  Loader2,
19
- Lock,
20
19
  Paperclip,
21
20
  ScrollText,
22
21
  Video,
@@ -32,7 +31,7 @@ import {
32
31
  } from '../_data/services/course-structure.service';
33
32
  import { HighlightedText } from './highlighted-text';
34
33
  import { useStructureStore } from './store';
35
- import type { Lesson, LessonStatus, LessonType, Visibility } from './types';
34
+ import type { Lesson, LessonStatus, LessonType } from './types';
36
35
  import {
37
36
  getLessonAttachedResourceCount,
38
37
  getLessonUploadedVideoCount,
@@ -63,15 +62,6 @@ const STATUS_CONFIG: Record<LessonStatus, { label: string; dot: string }> = {
63
62
  publicada: { label: 'Publicada', dot: 'bg-emerald-500' },
64
63
  };
65
64
 
66
- const VISIBILITY_CONFIG: Record<
67
- Visibility,
68
- { icon: LucideIcon; color: string; label: string }
69
- > = {
70
- publico: { icon: Eye, color: 'text-emerald-500', label: 'Público' },
71
- privado: { icon: EyeOff, color: 'text-muted-foreground', label: 'Privado' },
72
- restrito: { icon: Lock, color: 'text-amber-500', label: 'Restrito' },
73
- };
74
-
75
65
  // ── Props ─────────────────────────────────────────────────────────────────────
76
66
 
77
67
  interface TreeRowLessonProps {
@@ -98,9 +88,7 @@ export function TreeRowLesson({
98
88
  const { request } = useApp();
99
89
 
100
90
  const statusCfg = data.status ? STATUS_CONFIG[data.status] : null;
101
- const visibilityCfg = data.visibility
102
- ? VISIBILITY_CONFIG[data.visibility]
103
- : null;
91
+ const isPublished = data.published === true;
104
92
 
105
93
  const {
106
94
  showStatusDot,
@@ -171,7 +159,6 @@ export function TreeRowLesson({
171
159
  publicDescription: data.publicDescription,
172
160
  privateDescription: data.privateDescription,
173
161
  status: data.status,
174
- visibility: data.visibility,
175
162
  videoProvider: data.videoProvider,
176
163
  videoUrl: data.videoUrl,
177
164
  autoDuration: data.autoDuration,
@@ -215,24 +202,32 @@ export function TreeRowLesson({
215
202
  data.type !== 'video' ? (
216
203
  <Icon className={cn('size-3.5 shrink-0', cfg.color)} aria-hidden />
217
204
  ) : videoJobVisualState === 'waiting' ? (
218
- <span
219
- title="Conversão aguardando processamento"
220
- aria-label="Conversão aguardando processamento"
221
- className="inline-flex items-center"
222
- >
223
- <Clock3 className="size-3.5 shrink-0 text-amber-500" aria-hidden />
224
- </span>
205
+ <Tooltip>
206
+ <TooltipTrigger asChild>
207
+ <span
208
+ aria-label="Conversão aguardando processamento"
209
+ className="inline-flex items-center"
210
+ >
211
+ <Clock3 className="size-3.5 shrink-0 text-amber-500" aria-hidden />
212
+ </span>
213
+ </TooltipTrigger>
214
+ <TooltipContent>Conversão aguardando processamento</TooltipContent>
215
+ </Tooltip>
225
216
  ) : videoJobVisualState === 'processing' ? (
226
- <span
227
- title="Conversão em processamento"
228
- aria-label="Conversão em processamento"
229
- className="inline-flex items-center"
230
- >
231
- <Loader2
232
- className="size-3.5 shrink-0 animate-spin text-blue-500"
233
- aria-hidden
234
- />
235
- </span>
217
+ <Tooltip>
218
+ <TooltipTrigger asChild>
219
+ <span
220
+ aria-label="Conversão em processamento"
221
+ className="inline-flex items-center"
222
+ >
223
+ <Loader2
224
+ className="size-3.5 shrink-0 animate-spin text-blue-500"
225
+ aria-hidden
226
+ />
227
+ </span>
228
+ </TooltipTrigger>
229
+ <TooltipContent>Conversão em processamento</TooltipContent>
230
+ </Tooltip>
236
231
  ) : (
237
232
  <Video className={cn('size-3.5 shrink-0', cfg.color)} aria-hidden />
238
233
  );
@@ -246,7 +241,7 @@ export function TreeRowLesson({
246
241
  }}
247
242
  role="treeitem"
248
243
  aria-selected={isActive}
249
- title={`${cfg.label} · ${data.duration}min`}
244
+ aria-label={`${cfg.label} · ${data.duration}min`}
250
245
  className={cn(
251
246
  'flex items-center gap-1.5 pl-7 pr-2 rounded-md cursor-pointer select-none text-sm h-full group',
252
247
  'transition-colors duration-100',
@@ -290,39 +285,55 @@ export function TreeRowLesson({
290
285
  {durationLabel}
291
286
  </span>
292
287
  {/* Visibility icon */}
293
- {showVisibility &&
294
- visibilityCfg &&
295
- (() => {
296
- const VisIcon = visibilityCfg.icon;
297
- return (
288
+ {showVisibility && (
289
+ <Tooltip>
290
+ <TooltipTrigger asChild>
298
291
  <span
299
- title={t('visibility', { value: visibilityCfg.label })}
300
- aria-label={t('visibility', { value: visibilityCfg.label })}
292
+ aria-label={isPublished ? t('published') : t('draft')}
301
293
  className="inline-flex items-center"
302
294
  >
303
- <VisIcon
304
- className={cn('size-3 shrink-0', visibilityCfg.color)}
305
- />
295
+ {isPublished ? (
296
+ <Eye className="size-3 shrink-0 text-emerald-600 dark:text-emerald-400" />
297
+ ) : (
298
+ <EyeOff className="size-3 shrink-0 text-muted-foreground" />
299
+ )}
306
300
  </span>
307
- );
308
- })()}
301
+ </TooltipTrigger>
302
+ <TooltipContent>
303
+ {isPublished ? t('published') : t('draft')}
304
+ </TooltipContent>
305
+ </Tooltip>
306
+ )}
309
307
  {/* Status dot */}
310
308
  {showStatusDot && statusCfg && (
311
- <span
312
- className={cn('size-1.5 rounded-full shrink-0', statusCfg.dot)}
313
- title={t('status', { value: statusCfg.label })}
314
- aria-label={t('status', { value: statusCfg.label })}
315
- />
309
+ <Tooltip>
310
+ <TooltipTrigger asChild>
311
+ <span
312
+ className={cn(
313
+ 'size-1.5 rounded-full shrink-0',
314
+ statusCfg.dot
315
+ )}
316
+ aria-label={t('status', { value: statusCfg.label })}
317
+ />
318
+ </TooltipTrigger>
319
+ <TooltipContent>
320
+ {t('status', { value: statusCfg.label })}
321
+ </TooltipContent>
322
+ </Tooltip>
316
323
  )}
317
324
  {/* Video linked indicator */}
318
325
  {showVideoIndicator && data.type === 'video' && data.videoUrl && (
319
- <span
320
- title={t('videoLinked')}
321
- aria-label={t('videoLinked')}
322
- className="inline-flex items-center"
323
- >
324
- <Film className="size-3 shrink-0 text-violet-500" />
325
- </span>
326
+ <Tooltip>
327
+ <TooltipTrigger asChild>
328
+ <span
329
+ aria-label={t('videoLinked')}
330
+ className="inline-flex items-center"
331
+ >
332
+ <Film className="size-3 shrink-0 text-violet-500" />
333
+ </span>
334
+ </TooltipTrigger>
335
+ <TooltipContent>{t('videoLinked')}</TooltipContent>
336
+ </Tooltip>
326
337
  )}
327
338
  {/* Uploaded videos indicator */}
328
339
  {showVideoIndicator && uploadedVideoCount > 0 && (
@@ -364,13 +375,17 @@ export function TreeRowLesson({
364
375
  {showTranscriptionIndicator &&
365
376
  data.type === 'video' &&
366
377
  data.transcription && (
367
- <span
368
- title={t('hasTranscription')}
369
- aria-label={t('hasTranscription')}
370
- className="inline-flex items-center"
371
- >
372
- <ScrollText className="size-3 shrink-0 text-emerald-500" />
373
- </span>
378
+ <Tooltip>
379
+ <TooltipTrigger asChild>
380
+ <span
381
+ aria-label={t('hasTranscription')}
382
+ className="inline-flex items-center"
383
+ >
384
+ <ScrollText className="size-3 shrink-0 text-emerald-500" />
385
+ </span>
386
+ </TooltipTrigger>
387
+ <TooltipContent>{t('hasTranscription')}</TooltipContent>
388
+ </Tooltip>
374
389
  )}
375
390
  </div>
376
391
  )}
@@ -10,6 +10,8 @@ import { cn } from '@/lib/utils';
10
10
  import {
11
11
  ChevronDown,
12
12
  ChevronRight,
13
+ Eye,
14
+ EyeOff,
13
15
  Layers,
14
16
  Paperclip,
15
17
  Video,
@@ -54,7 +56,7 @@ export function TreeRowSession({
54
56
  const cancelRename = useStructureStore((s) => s.cancelRename);
55
57
  const startRename = useStructureStore((s) => s.startRename);
56
58
  const renameItem = useStructureStore((s) => s.renameItem);
57
- const { showCode } = useTreeDisplaySettings();
59
+ const { showCode, showVisibility } = useTreeDisplaySettings();
58
60
 
59
61
  const updateSession = useUpdateSessionMutation();
60
62
 
@@ -79,8 +81,7 @@ export function TreeRowSession({
79
81
  duration: data.duration,
80
82
  code: data.code,
81
83
  description: '',
82
- visibility: data.visibility ?? 'publico',
83
- published: data.published ?? false,
84
+ published: data.published,
84
85
  },
85
86
  });
86
87
  }
@@ -96,6 +97,7 @@ export function TreeRowSession({
96
97
 
97
98
  const durationH = Math.floor((data.duration ?? 0) / 60);
98
99
  const durationM = (data.duration ?? 0) % 60;
100
+ const isPublished = data.published === true;
99
101
  const durationLabel =
100
102
  durationH > 0
101
103
  ? `${durationH}h${durationM > 0 ? ` ${durationM}m` : ''}`
@@ -138,7 +140,12 @@ export function TreeRowSession({
138
140
 
139
141
  {/* Session icon */}
140
142
  <Layers
141
- className="size-3.5 shrink-0 text-blue-500 dark:text-blue-400"
143
+ className={cn(
144
+ 'size-3.5 shrink-0 transition-colors',
145
+ isPublished
146
+ ? 'text-emerald-600 dark:text-emerald-400'
147
+ : 'text-muted-foreground/70'
148
+ )}
142
149
  aria-hidden
143
150
  />
144
151
 
@@ -162,6 +169,26 @@ export function TreeRowSession({
162
169
  {/* Meta badges (hidden during rename) */}
163
170
  {!isRenaming && (
164
171
  <div className="flex items-center gap-1 shrink-0">
172
+ {showVisibility && (
173
+ <Tooltip>
174
+ <TooltipTrigger asChild>
175
+ <span
176
+ className="inline-flex items-center"
177
+ aria-label={isPublished ? t('published') : t('draft')}
178
+ >
179
+ {isPublished ? (
180
+ <Eye className="size-3 shrink-0 text-emerald-600 dark:text-emerald-400" />
181
+ ) : (
182
+ <EyeOff className="size-3 shrink-0 text-muted-foreground" />
183
+ )}
184
+ </span>
185
+ </TooltipTrigger>
186
+ <TooltipContent>
187
+ {isPublished ? t('published') : t('draft')}
188
+ </TooltipContent>
189
+ </Tooltip>
190
+ )}
191
+
165
192
  {showCode && (
166
193
  <span className="inline-flex items-center rounded border border-border/60 bg-muted/60 px-1 text-[0.6rem] font-mono text-muted-foreground leading-4">
167
194
  {data.code}
@@ -207,16 +234,6 @@ export function TreeRowSession({
207
234
  </TooltipContent>
208
235
  </Tooltip>
209
236
  )}
210
- {/* Published status dot */}
211
- <span
212
- className={cn(
213
- 'size-1.5 rounded-full shrink-0',
214
- data.published === false
215
- ? 'bg-muted-foreground/30'
216
- : 'bg-emerald-500'
217
- )}
218
- title={data.published === false ? t('draft') : t('published')}
219
- />
220
237
  </div>
221
238
  )}
222
239
  </div>
@@ -35,8 +35,7 @@ export interface Session {
35
35
  description?: string;
36
36
  duration: number;
37
37
  order: number;
38
- visibility?: Visibility;
39
- published?: boolean;
38
+ published: boolean;
40
39
  }
41
40
 
42
41
  export interface Resource {
@@ -63,6 +62,7 @@ export interface LessonInstructor {
63
62
  id: string;
64
63
  role?: 'lead' | 'assistant';
65
64
  name?: string;
65
+ avatarId?: number | null;
66
66
  }
67
67
 
68
68
  export interface TranscriptionSegment {
@@ -95,6 +95,7 @@ export interface Lesson {
95
95
  order: number;
96
96
  status?: LessonStatus;
97
97
  visibility?: Visibility;
98
+ published?: boolean;
98
99
  // Video
99
100
  videoProvider?: VideoProvider;
100
101
  videoUrl?: string;
@@ -127,7 +128,6 @@ export type SessionFormValues = {
127
128
  title: string;
128
129
  description: string;
129
130
  duration: number;
130
- visibility: Visibility;
131
131
  published: boolean;
132
132
  };
133
133
 
@@ -139,7 +139,7 @@ export type LessonFormValues = {
139
139
  type: LessonType;
140
140
  duration: number;
141
141
  status: LessonStatus;
142
- visibility: Visibility;
142
+ published: boolean;
143
143
  videoProvider?: VideoProvider;
144
144
  videoUrl?: string;
145
145
  autoDuration?: boolean;
@@ -16,9 +16,7 @@
16
16
  * 1. exameVinculado (number) ↔ linkedExam (string) — converted with String()/Number()
17
17
  * 2. 'exercicio' tipo — exists in backend and is now part of frontend LessonType.
18
18
  * 3. Session.order — not returned by API; inferred from sorted array position.
19
- * 4. Session.visibility / published — not in DB schema; defaulted to safe values.
20
- * 5. Lesson.status / visibility — not in DB schema; left undefined.
21
- * 6. Resource.size / url — not returned by API; left as empty string / undefined.
19
+ * 4. Resource.size / url — not returned by API; left as empty string / undefined.
22
20
  */
23
21
 
24
22
  import type {
@@ -125,7 +123,7 @@ function normalizeFrame(raw: ApiLessonFrame): VideoFrame {
125
123
  export function normalizeCourse(raw: ApiCourse): Course {
126
124
  return {
127
125
  id: raw.id,
128
- code: raw.slug || raw.id,
126
+ code: raw.codigo ?? raw.code ?? raw.slug ?? raw.id,
129
127
  name: raw.name ?? raw.slug,
130
128
  title: raw.titulo,
131
129
  description: raw.descricao,
@@ -154,12 +152,7 @@ export function normalizeSession(raw: ApiSession, index: number): Session {
154
152
  * TODO[BACKEND]: Include `order` in the ApiSession response.
155
153
  */
156
154
  order: index + 1,
157
- /**
158
- * ⚠️ BACKEND NOTE: visibility and published are not in the backend schema.
159
- * Defaulted here. If these fields are added to the DB, read them from `raw`.
160
- */
161
- visibility: 'publico',
162
- published: false,
155
+ published: raw.published ?? false,
163
156
  };
164
157
  }
165
158
 
@@ -171,7 +164,11 @@ export function normalizeSession(raw: ApiSession, index: number): Session {
171
164
  */
172
165
  export function normalizeLesson(raw: ApiLesson, order = 0): Lesson {
173
166
  const instructors: LessonInstructor[] = (raw.instrutores ?? []).map(
174
- (inst) => ({ id: String(inst.id), name: inst.name })
167
+ (inst) => ({
168
+ id: String(inst.id),
169
+ name: inst.name ?? inst.nome,
170
+ avatarId: inst.avatarId ?? null,
171
+ })
175
172
  );
176
173
 
177
174
  return {
@@ -200,12 +197,9 @@ export function normalizeLesson(raw: ApiLesson, order = 0): Lesson {
200
197
  resources: (raw.recursos ?? []).map(normalizeResource),
201
198
  frames: (raw.frames ?? []).map(normalizeFrame),
202
199
  instructors,
203
- /**
204
- * ⚠️ BACKEND NOTE: status and visibility are not in the DB schema.
205
- * Left undefined. If added to the schema, read them from `raw`.
206
- */
207
- status: undefined,
200
+ status: raw.statusProducao,
208
201
  visibility: undefined,
202
+ published: raw.published ?? false,
209
203
  };
210
204
  }
211
205
 
@@ -220,7 +214,7 @@ export function normalizeStructureResponse(raw: ApiGetStructureResponse): {
220
214
  course: Course | null;
221
215
  sessions: Session[];
222
216
  lessons: Lesson[];
223
- instructors: { id: number; name: string }[];
217
+ instructors: { id: number; name: string; avatarId?: number | null }[];
224
218
  } {
225
219
  const sessions = raw.sessoes.map((s, i) => normalizeSession(s, i));
226
220
 
@@ -237,7 +231,11 @@ export function normalizeStructureResponse(raw: ApiGetStructureResponse): {
237
231
  course: raw.curso ? normalizeCourse(raw.curso) : null,
238
232
  sessions,
239
233
  lessons,
240
- instructors: raw.instructors ?? [],
234
+ instructors: (raw.instructors ?? []).map((instructor) => ({
235
+ id: Number(instructor.id),
236
+ name: instructor.name ?? instructor.nome ?? '',
237
+ avatarId: instructor.avatarId ?? null,
238
+ })),
241
239
  };
242
240
  }
243
241
 
@@ -249,17 +247,17 @@ export function normalizeStructureResponse(raw: ApiGetStructureResponse): {
249
247
  * Convert SessionFormValues → ApiCreateSessionPayload.
250
248
  * Used for POST /lms/courses/:id/structure/sessions.
251
249
  *
252
- * ⚠️ ADAPTER POINT:
253
- * `visibility` and `published` from SessionFormValues are intentionally
254
- * omitted — the backend schema does not have these fields yet.
255
- * TODO[BACKEND]: Add visibility/published to course_module if required.
256
250
  */
257
251
  export function toCreateSessionPayload(
258
- data: Pick<SessionFormValues, 'title' | 'duration' | 'description' | 'code'>
252
+ data: Pick<
253
+ SessionFormValues,
254
+ 'title' | 'duration' | 'description' | 'code' | 'published'
255
+ >
259
256
  ): ApiCreateSessionPayload {
260
257
  return {
261
258
  titulo: data.title,
262
259
  duracao: data.duration,
260
+ published: data.published,
263
261
  ...(data.description?.trim() && { descricao: data.description }),
264
262
  ...(data.code?.trim() && { codigo: data.code }),
265
263
  };
@@ -271,7 +269,10 @@ export function toCreateSessionPayload(
271
269
  */
272
270
  export function toUpdateSessionPayload(
273
271
  data: Partial<
274
- Pick<SessionFormValues, 'title' | 'duration' | 'description' | 'code'>
272
+ Pick<
273
+ SessionFormValues,
274
+ 'title' | 'duration' | 'description' | 'code' | 'published'
275
+ >
275
276
  >
276
277
  ): ApiUpdateSessionPayload {
277
278
  const payload: ApiUpdateSessionPayload = {};
@@ -279,6 +280,7 @@ export function toUpdateSessionPayload(
279
280
  if (data.duration !== undefined) payload.duracao = data.duration;
280
281
  if (data.description !== undefined) payload.descricao = data.description;
281
282
  if (data.code !== undefined) payload.codigo = data.code;
283
+ if (data.published !== undefined) payload.published = data.published;
282
284
  return payload;
283
285
  }
284
286
 
@@ -286,9 +288,7 @@ export function toUpdateSessionPayload(
286
288
  * Convert LessonFormValues → ApiCreateLessonPayload.
287
289
  * Used for POST /lms/courses/:id/structure/sessions/:sessionId/lessons.
288
290
  *
289
- * ⚠️ ADAPTER POINT:
290
- * `status` and `visibility` from LessonFormValues are intentionally omitted
291
- * (not in the DB schema). `linkedExam` (string) is converted to number.
291
+ * `linkedExam` (string) is converted to number.
292
292
  */
293
293
  export function toCreateLessonPayload(
294
294
  data: LessonFormValues & {
@@ -296,6 +296,7 @@ export function toCreateLessonPayload(
296
296
  videoConversionJobId?: number;
297
297
  instructorIds?: number[];
298
298
  resources?: LessonResourceInput[];
299
+ confirmarPublicacaoComStatus?: boolean;
299
300
  }
300
301
  ): ApiCreateLessonPayload {
301
302
  return {
@@ -304,6 +305,11 @@ export function toCreateLessonPayload(
304
305
  // backend accepts it. Cast is safe as long as LessonType ⊆ ApiLesson['tipo'].
305
306
  tipo: data.type as ApiCreateLessonPayload['tipo'],
306
307
  duracao: data.duration,
308
+ published: data.published,
309
+ statusProducao: data.status,
310
+ ...(data.confirmarPublicacaoComStatus !== undefined && {
311
+ confirmarPublicacaoComStatus: data.confirmarPublicacaoComStatus,
312
+ }),
307
313
  ...(data.publicDescription && { descricaoPublica: data.publicDescription }),
308
314
  ...(data.privateDescription && {
309
315
  descricaoPrivada: data.privateDescription,
@@ -349,6 +355,7 @@ export function toUpdateLessonPayload(
349
355
  videoConversionJobId?: number;
350
356
  instructorIds?: number[];
351
357
  resources?: LessonResourceInput[];
358
+ confirmarPublicacaoComStatus?: boolean;
352
359
  }
353
360
  >
354
361
  ): ApiUpdateLessonPayload {
@@ -357,6 +364,11 @@ export function toUpdateLessonPayload(
357
364
  if (data.type !== undefined)
358
365
  payload.tipo = data.type as ApiCreateLessonPayload['tipo'];
359
366
  if (data.duration !== undefined) payload.duracao = data.duration;
367
+ if (data.published !== undefined) payload.published = data.published;
368
+ if (data.status !== undefined) payload.statusProducao = data.status;
369
+ if (data.confirmarPublicacaoComStatus !== undefined) {
370
+ payload.confirmarPublicacaoComStatus = data.confirmarPublicacaoComStatus;
371
+ }
360
372
  if (data.publicDescription !== undefined)
361
373
  payload.descricaoPublica = data.publicDescription;
362
374
  if (data.privateDescription !== undefined)
@@ -52,8 +52,10 @@ export interface ApiCourseResource {
52
52
 
53
53
  /** Instructor linked to a lesson or available in the course pool. */
54
54
  export interface ApiLessonInstructor {
55
- id: number;
56
- name: string;
55
+ id: number | string;
56
+ name?: string;
57
+ nome?: string;
58
+ avatarId?: number | null;
57
59
  }
58
60
 
59
61
  /**
@@ -81,6 +83,13 @@ export interface ApiLesson {
81
83
  tipo: 'video' | 'questao' | 'post' | 'exercicio';
82
84
  /** Duration in minutes. */
83
85
  duracao: number;
86
+ published?: boolean;
87
+ statusProducao?:
88
+ | 'preparada'
89
+ | 'gravada'
90
+ | 'editada'
91
+ | 'finalizada'
92
+ | 'publicada';
84
93
  /** ID of the parent session (course_module). */
85
94
  sessaoId: string;
86
95
  videoProvedor?: 'youtube' | 'vimeo' | 'file_storage' | 'bunny' | 'custom';
@@ -115,6 +124,7 @@ export interface ApiSession {
115
124
  titulo: string;
116
125
  /** Duration in minutes. */
117
126
  duracao: number;
127
+ published?: boolean;
118
128
  collapsed: boolean;
119
129
  }
120
130
 
@@ -124,6 +134,8 @@ export interface ApiSession {
124
134
  export interface ApiCourse {
125
135
  id: string;
126
136
  name: string;
137
+ codigo?: string | null;
138
+ code?: string | null;
127
139
  slug: string;
128
140
  titulo: string;
129
141
  descricao: string;
@@ -159,6 +171,7 @@ export interface ApiCreateSessionPayload {
159
171
  duracao: number;
160
172
  descricao?: string;
161
173
  codigo?: string;
174
+ published?: boolean;
162
175
  }
163
176
 
164
177
  /**
@@ -185,6 +198,7 @@ export interface ApiUpdateSessionPayload {
185
198
  duracao?: number;
186
199
  descricao?: string;
187
200
  codigo?: string;
201
+ published?: boolean;
188
202
  }
189
203
 
190
204
  /**
@@ -196,6 +210,14 @@ export interface ApiCreateLessonPayload {
196
210
  tipo: 'video' | 'questao' | 'post' | 'exercicio';
197
211
  /** Duration in minutes. */
198
212
  duracao: number;
213
+ published?: boolean;
214
+ statusProducao?:
215
+ | 'preparada'
216
+ | 'gravada'
217
+ | 'editada'
218
+ | 'finalizada'
219
+ | 'publicada';
220
+ confirmarPublicacaoComStatus?: boolean;
199
221
  descricaoPublica?: string;
200
222
  descricaoPrivada?: string;
201
223
  videoProvedor?: 'youtube' | 'vimeo' | 'file_storage' | 'bunny' | 'custom';
@@ -153,6 +153,7 @@ export function useCreateSessionMutation() {
153
153
  duration: 0,
154
154
  code: `S${String(nextIndex).padStart(2, '0')}`,
155
155
  description: '',
156
+ published: false,
156
157
  });
157
158
  return apiCreateSession(request, courseId, payload);
158
159
  },
@@ -220,6 +221,8 @@ export function useUpdateSessionMutation() {
220
221
  title: formValues.title ?? s.title,
221
222
  code: formValues.code ?? s.code,
222
223
  duration: formValues.duration ?? s.duration,
224
+ description: formValues.description ?? s.description,
225
+ published: formValues.published ?? s.published,
223
226
  }
224
227
  : s
225
228
  ),
@@ -277,7 +280,7 @@ export function useCreateLessonMutation() {
277
280
  publicDescription: '',
278
281
  privateDescription: '',
279
282
  status: 'preparada',
280
- visibility: 'publico',
283
+ published: false,
281
284
  });
282
285
  return apiCreateLesson(request, courseId, sessionId, payload);
283
286
  },
@@ -311,6 +314,7 @@ interface UpdateLessonVars {
311
314
  videoConversionJobId?: number;
312
315
  resources?: Resource[];
313
316
  instructorIds?: number[];
317
+ confirmarPublicacaoComStatus?: boolean;
314
318
  }
315
319
  >;
316
320
  }
@@ -336,8 +340,27 @@ export function useUpdateLessonMutation() {
336
340
  lessonId,
337
341
  toUpdateLessonPayload(formValues)
338
342
  ),
339
- onSuccess: (_apiLesson, { lessonId, formValues }) => {
340
- updateLessonInStore(String(lessonId), formValues);
343
+ onSuccess: (apiLesson, { lessonId, formValues }) => {
344
+ const normalizedLesson = normalizeLesson(apiLesson);
345
+
346
+ updateLessonInStore(String(lessonId), {
347
+ ...formValues,
348
+ code: normalizedLesson.code,
349
+ title: normalizedLesson.title,
350
+ publicDescription: normalizedLesson.publicDescription,
351
+ privateDescription: normalizedLesson.privateDescription,
352
+ duration: normalizedLesson.duration,
353
+ type: normalizedLesson.type,
354
+ status: normalizedLesson.status,
355
+ published: normalizedLesson.published,
356
+ videoProvider: normalizedLesson.videoProvider,
357
+ videoUrl: normalizedLesson.videoUrl,
358
+ linkedExam: normalizedLesson.linkedExam,
359
+ postContent: normalizedLesson.postContent,
360
+ transcription: normalizedLesson.transcription,
361
+ videoConversionJobId: normalizedLesson.videoConversionJobId,
362
+ resources: normalizedLesson.resources,
363
+ });
341
364
  // Manual cache update for in-place field changes.
342
365
  queryClient.setQueryData<CourseStructureCacheData>(
343
366
  courseStructureQueryKey(courseId),
@@ -349,9 +372,21 @@ export function useUpdateLessonMutation() {
349
372
  l.id === String(lessonId)
350
373
  ? {
351
374
  ...l,
352
- title: formValues.title ?? l.title,
353
- duration: formValues.duration ?? l.duration,
354
- type: formValues.type ?? l.type,
375
+ code: normalizedLesson.code,
376
+ title: normalizedLesson.title,
377
+ publicDescription: normalizedLesson.publicDescription,
378
+ privateDescription: normalizedLesson.privateDescription,
379
+ duration: normalizedLesson.duration,
380
+ type: normalizedLesson.type,
381
+ status: normalizedLesson.status,
382
+ published: normalizedLesson.published,
383
+ videoProvider: normalizedLesson.videoProvider,
384
+ videoUrl: normalizedLesson.videoUrl,
385
+ linkedExam: normalizedLesson.linkedExam,
386
+ postContent: normalizedLesson.postContent,
387
+ transcription: normalizedLesson.transcription,
388
+ videoConversionJobId: normalizedLesson.videoConversionJobId,
389
+ resources: normalizedLesson.resources,
355
390
  }
356
391
  : l
357
392
  ),