@hed-hog/lms 0.0.354 → 0.0.357

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 (73) 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-structure.controller.d.ts +12 -0
  5. package/dist/course/course-structure.controller.d.ts.map +1 -1
  6. package/dist/course/course-structure.service.d.ts +22 -0
  7. package/dist/course/course-structure.service.d.ts.map +1 -1
  8. package/dist/course/course-structure.service.js +147 -18
  9. package/dist/course/course-structure.service.js.map +1 -1
  10. package/dist/course/course.service.d.ts +4 -2
  11. package/dist/course/course.service.d.ts.map +1 -1
  12. package/dist/course/course.service.js +61 -2
  13. package/dist/course/course.service.js.map +1 -1
  14. package/dist/course/dto/create-course-structure-lesson.dto.d.ts +3 -0
  15. package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
  16. package/dist/course/dto/create-course-structure-lesson.dto.js +15 -0
  17. package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
  18. package/dist/course/dto/create-course-structure-session.dto.d.ts +1 -0
  19. package/dist/course/dto/create-course-structure-session.dto.d.ts.map +1 -1
  20. package/dist/course/dto/create-course-structure-session.dto.js +5 -0
  21. package/dist/course/dto/create-course-structure-session.dto.js.map +1 -1
  22. package/dist/enterprise/training/training-student.controller.d.ts +0 -95
  23. package/dist/enterprise/training/training-student.controller.d.ts.map +1 -1
  24. package/dist/enterprise/training/training-student.controller.js +1 -34
  25. package/dist/enterprise/training/training-student.controller.js.map +1 -1
  26. package/dist/enterprise/training/training-student.service.d.ts +63 -0
  27. package/dist/enterprise/training/training-student.service.d.ts.map +1 -1
  28. package/dist/enterprise/training/training-student.service.js +320 -4
  29. package/dist/enterprise/training/training-student.service.js.map +1 -1
  30. package/dist/lms.module.d.ts.map +1 -1
  31. package/dist/lms.module.js +2 -0
  32. package/dist/lms.module.js.map +1 -1
  33. package/dist/platforma/platforma.controller.d.ts +287 -0
  34. package/dist/platforma/platforma.controller.d.ts.map +1 -0
  35. package/dist/platforma/platforma.controller.js +147 -0
  36. package/dist/platforma/platforma.controller.js.map +1 -0
  37. package/hedhog/data/route.yaml +75 -9
  38. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +2 -2
  39. package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +10 -13
  40. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +2 -2
  41. package/hedhog/frontend/app/courses/[id]/_components/course-edit-types.ts.ejs +1 -1
  42. package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +17 -6
  43. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +14 -7
  44. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +92 -53
  45. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +25 -60
  46. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +1 -0
  47. package/hedhog/frontend/app/courses/[id]/structure/_components/store.ts.ejs +11 -0
  48. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-context-menu.tsx.ejs +22 -0
  49. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +79 -64
  50. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +31 -14
  51. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +3 -4
  52. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +28 -24
  53. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +20 -0
  54. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +41 -6
  55. package/hedhog/frontend/app/courses/page.tsx.ejs +59 -15
  56. package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +1 -1
  57. package/hedhog/frontend/app/enterprise/_components/enterprise-course-edit-sheet.tsx.ejs +2 -2
  58. package/hedhog/frontend/app/paths/page.tsx.ejs +1 -1
  59. package/hedhog/frontend/app/training/page.tsx.ejs +1 -1
  60. package/hedhog/frontend/messages/en.json +7 -2
  61. package/hedhog/frontend/messages/pt.json +7 -2
  62. package/hedhog/table/course_lesson.yaml +2 -2
  63. package/hedhog/table/course_module.yaml +3 -0
  64. package/package.json +9 -9
  65. package/src/course/course-audio-transcription.service.ts +21 -8
  66. package/src/course/course-structure.service.ts +204 -3
  67. package/src/course/course.service.ts +67 -1
  68. package/src/course/dto/create-course-structure-lesson.dto.ts +17 -0
  69. package/src/course/dto/create-course-structure-session.dto.ts +13 -1
  70. package/src/enterprise/training/training-student.controller.ts +3 -27
  71. package/src/enterprise/training/training-student.service.ts +350 -2
  72. package/src/lms.module.ts +2 -0
  73. package/src/platforma/platforma.controller.ts +92 -0
@@ -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
 
@@ -200,12 +193,9 @@ export function normalizeLesson(raw: ApiLesson, order = 0): Lesson {
200
193
  resources: (raw.recursos ?? []).map(normalizeResource),
201
194
  frames: (raw.frames ?? []).map(normalizeFrame),
202
195
  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,
196
+ status: raw.statusProducao,
208
197
  visibility: undefined,
198
+ published: raw.published ?? false,
209
199
  };
210
200
  }
211
201
 
@@ -249,17 +239,17 @@ export function normalizeStructureResponse(raw: ApiGetStructureResponse): {
249
239
  * Convert SessionFormValues → ApiCreateSessionPayload.
250
240
  * Used for POST /lms/courses/:id/structure/sessions.
251
241
  *
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
242
  */
257
243
  export function toCreateSessionPayload(
258
- data: Pick<SessionFormValues, 'title' | 'duration' | 'description' | 'code'>
244
+ data: Pick<
245
+ SessionFormValues,
246
+ 'title' | 'duration' | 'description' | 'code' | 'published'
247
+ >
259
248
  ): ApiCreateSessionPayload {
260
249
  return {
261
250
  titulo: data.title,
262
251
  duracao: data.duration,
252
+ published: data.published,
263
253
  ...(data.description?.trim() && { descricao: data.description }),
264
254
  ...(data.code?.trim() && { codigo: data.code }),
265
255
  };
@@ -271,7 +261,10 @@ export function toCreateSessionPayload(
271
261
  */
272
262
  export function toUpdateSessionPayload(
273
263
  data: Partial<
274
- Pick<SessionFormValues, 'title' | 'duration' | 'description' | 'code'>
264
+ Pick<
265
+ SessionFormValues,
266
+ 'title' | 'duration' | 'description' | 'code' | 'published'
267
+ >
275
268
  >
276
269
  ): ApiUpdateSessionPayload {
277
270
  const payload: ApiUpdateSessionPayload = {};
@@ -279,6 +272,7 @@ export function toUpdateSessionPayload(
279
272
  if (data.duration !== undefined) payload.duracao = data.duration;
280
273
  if (data.description !== undefined) payload.descricao = data.description;
281
274
  if (data.code !== undefined) payload.codigo = data.code;
275
+ if (data.published !== undefined) payload.published = data.published;
282
276
  return payload;
283
277
  }
284
278
 
@@ -286,9 +280,7 @@ export function toUpdateSessionPayload(
286
280
  * Convert LessonFormValues → ApiCreateLessonPayload.
287
281
  * Used for POST /lms/courses/:id/structure/sessions/:sessionId/lessons.
288
282
  *
289
- * ⚠️ ADAPTER POINT:
290
- * `status` and `visibility` from LessonFormValues are intentionally omitted
291
- * (not in the DB schema). `linkedExam` (string) is converted to number.
283
+ * `linkedExam` (string) is converted to number.
292
284
  */
293
285
  export function toCreateLessonPayload(
294
286
  data: LessonFormValues & {
@@ -296,6 +288,7 @@ export function toCreateLessonPayload(
296
288
  videoConversionJobId?: number;
297
289
  instructorIds?: number[];
298
290
  resources?: LessonResourceInput[];
291
+ confirmarPublicacaoComStatus?: boolean;
299
292
  }
300
293
  ): ApiCreateLessonPayload {
301
294
  return {
@@ -304,6 +297,11 @@ export function toCreateLessonPayload(
304
297
  // backend accepts it. Cast is safe as long as LessonType ⊆ ApiLesson['tipo'].
305
298
  tipo: data.type as ApiCreateLessonPayload['tipo'],
306
299
  duracao: data.duration,
300
+ published: data.published,
301
+ statusProducao: data.status,
302
+ ...(data.confirmarPublicacaoComStatus !== undefined && {
303
+ confirmarPublicacaoComStatus: data.confirmarPublicacaoComStatus,
304
+ }),
307
305
  ...(data.publicDescription && { descricaoPublica: data.publicDescription }),
308
306
  ...(data.privateDescription && {
309
307
  descricaoPrivada: data.privateDescription,
@@ -349,6 +347,7 @@ export function toUpdateLessonPayload(
349
347
  videoConversionJobId?: number;
350
348
  instructorIds?: number[];
351
349
  resources?: LessonResourceInput[];
350
+ confirmarPublicacaoComStatus?: boolean;
352
351
  }
353
352
  >
354
353
  ): ApiUpdateLessonPayload {
@@ -357,6 +356,11 @@ export function toUpdateLessonPayload(
357
356
  if (data.type !== undefined)
358
357
  payload.tipo = data.type as ApiCreateLessonPayload['tipo'];
359
358
  if (data.duration !== undefined) payload.duracao = data.duration;
359
+ if (data.published !== undefined) payload.published = data.published;
360
+ if (data.status !== undefined) payload.statusProducao = data.status;
361
+ if (data.confirmarPublicacaoComStatus !== undefined) {
362
+ payload.confirmarPublicacaoComStatus = data.confirmarPublicacaoComStatus;
363
+ }
360
364
  if (data.publicDescription !== undefined)
361
365
  payload.descricaoPublica = data.publicDescription;
362
366
  if (data.privateDescription !== undefined)
@@ -81,6 +81,13 @@ export interface ApiLesson {
81
81
  tipo: 'video' | 'questao' | 'post' | 'exercicio';
82
82
  /** Duration in minutes. */
83
83
  duracao: number;
84
+ published?: boolean;
85
+ statusProducao?:
86
+ | 'preparada'
87
+ | 'gravada'
88
+ | 'editada'
89
+ | 'finalizada'
90
+ | 'publicada';
84
91
  /** ID of the parent session (course_module). */
85
92
  sessaoId: string;
86
93
  videoProvedor?: 'youtube' | 'vimeo' | 'file_storage' | 'bunny' | 'custom';
@@ -115,6 +122,7 @@ export interface ApiSession {
115
122
  titulo: string;
116
123
  /** Duration in minutes. */
117
124
  duracao: number;
125
+ published?: boolean;
118
126
  collapsed: boolean;
119
127
  }
120
128
 
@@ -124,6 +132,8 @@ export interface ApiSession {
124
132
  export interface ApiCourse {
125
133
  id: string;
126
134
  name: string;
135
+ codigo?: string | null;
136
+ code?: string | null;
127
137
  slug: string;
128
138
  titulo: string;
129
139
  descricao: string;
@@ -159,6 +169,7 @@ export interface ApiCreateSessionPayload {
159
169
  duracao: number;
160
170
  descricao?: string;
161
171
  codigo?: string;
172
+ published?: boolean;
162
173
  }
163
174
 
164
175
  /**
@@ -185,6 +196,7 @@ export interface ApiUpdateSessionPayload {
185
196
  duracao?: number;
186
197
  descricao?: string;
187
198
  codigo?: string;
199
+ published?: boolean;
188
200
  }
189
201
 
190
202
  /**
@@ -196,6 +208,14 @@ export interface ApiCreateLessonPayload {
196
208
  tipo: 'video' | 'questao' | 'post' | 'exercicio';
197
209
  /** Duration in minutes. */
198
210
  duracao: number;
211
+ published?: boolean;
212
+ statusProducao?:
213
+ | 'preparada'
214
+ | 'gravada'
215
+ | 'editada'
216
+ | 'finalizada'
217
+ | 'publicada';
218
+ confirmarPublicacaoComStatus?: boolean;
199
219
  descricaoPublica?: string;
200
220
  descricaoPrivada?: string;
201
221
  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
  ),
@@ -98,7 +98,7 @@ interface Curso {
98
98
  primaryColor?: string | null;
99
99
  secondaryColor?: string | null;
100
100
  nivel: 'iniciante' | 'intermediario' | 'avancado';
101
- status: 'ativo' | 'rascunho' | 'arquivado';
101
+ status: 'publicado' | 'rascunho' | 'arquivado';
102
102
  tipoCurso: 'on_demand' | 'agendado' | 'hibrido';
103
103
  categorias: string[];
104
104
  destaque: boolean;
@@ -214,11 +214,12 @@ function toPtStatus(status?: string | null): CourseSheetFormValues['status'] {
214
214
  const normalizedStatus = normalizeEnumValue(status);
215
215
 
216
216
  if (
217
+ normalizedStatus === 'publicado' ||
217
218
  normalizedStatus === 'ativo' ||
218
219
  normalizedStatus === 'active' ||
219
220
  normalizedStatus === 'published'
220
221
  ) {
221
- return 'ativo';
222
+ return 'publicado';
222
223
  }
223
224
  if (normalizedStatus === 'rascunho' || normalizedStatus === 'draft') {
224
225
  return 'rascunho';
@@ -264,7 +265,7 @@ function toApiLevel(level: CourseSheetFormValues['nivel']) {
264
265
  }
265
266
 
266
267
  function toApiStatus(status: CourseSheetFormValues['status']) {
267
- if (status === 'ativo') return 'published';
268
+ if (status === 'publicado') return 'published';
268
269
  if (status === 'rascunho') return 'draft';
269
270
  return 'archived';
270
271
  }
@@ -296,7 +297,7 @@ const NIVEL_COLOR: Record<string, string> = {
296
297
  };
297
298
 
298
299
  const STATUS_VARIANT: Record<string, 'default' | 'secondary' | 'outline'> = {
299
- ativo: 'default',
300
+ publicado: 'default',
300
301
  rascunho: 'secondary',
301
302
  arquivado: 'outline',
302
303
  };
@@ -407,7 +408,7 @@ export default function CursosPage() {
407
408
  ...(filtroStatusInput !== 'todos'
408
409
  ? {
409
410
  status:
410
- filtroStatusInput === 'ativo'
411
+ filtroStatusInput === 'publicado'
411
412
  ? 'published'
412
413
  : filtroStatusInput === 'rascunho'
413
414
  ? 'draft'
@@ -579,6 +580,19 @@ export default function CursosPage() {
579
580
  curso.id === previewCurso.id ? previewCurso : curso
580
581
  );
581
582
  }, [cursos, previewCurso]);
583
+ const selectedCourses = useMemo(
584
+ () => cursos.filter((curso) => selectedIds.has(curso.id)),
585
+ [cursos, selectedIds]
586
+ );
587
+ const selectedArchivedIds = useMemo(
588
+ () =>
589
+ selectedCourses
590
+ .filter((curso) => curso.status === 'arquivado')
591
+ .map((curso) => curso.id),
592
+ [selectedCourses]
593
+ );
594
+ const hasSelectedNonArchived =
595
+ selectedCourses.length > selectedArchivedIds.length;
582
596
  const totalPages = Math.max(1, effectiveCourseList?.lastPage ?? 1);
583
597
  const safePage = Math.min(currentPage, totalPages);
584
598
  function clearFilters() {
@@ -604,12 +618,18 @@ export default function CursosPage() {
604
618
 
605
619
  function getStatusLabel(curso: Curso) {
606
620
  return t(
607
- `status.${curso.status === 'ativo' ? 'active' : curso.status === 'rascunho' ? 'draft' : 'archived'}`
621
+ `status.${curso.status === 'publicado' ? 'published' : curso.status === 'rascunho' ? 'draft' : 'archived'}`
608
622
  );
609
623
  }
610
624
 
611
625
  function openDeleteDialog(curso: Curso, e: React.MouseEvent) {
612
626
  e.stopPropagation();
627
+
628
+ if (curso.status !== 'arquivado') {
629
+ toast.error(t('toasts.deleteOnlyArchived'));
630
+ return;
631
+ }
632
+
613
633
  setCursoToDelete(curso);
614
634
  setDeleteDialogOpen(true);
615
635
  }
@@ -715,6 +735,11 @@ export default function CursosPage() {
715
735
 
716
736
  async function confirmDelete() {
717
737
  if (!cursoToDelete) return;
738
+ if (cursoToDelete.status !== 'arquivado') {
739
+ toast.error(t('toasts.deleteOnlyArchived'));
740
+ return;
741
+ }
742
+
718
743
  await request({
719
744
  url: `/lms/courses/${cursoToDelete.id}`,
720
745
  method: 'DELETE',
@@ -935,7 +960,7 @@ export default function CursosPage() {
935
960
  placeholder: t('table.headers.status'),
936
961
  options: [
937
962
  { value: 'todos', label: t('filters.allStatuses') },
938
- { value: 'ativo', label: t('status.active') },
963
+ { value: 'publicado', label: t('status.published') },
939
964
  { value: 'rascunho', label: t('status.draft') },
940
965
  { value: 'arquivado', label: t('status.archived') },
941
966
  ],
@@ -1043,13 +1068,24 @@ export default function CursosPage() {
1043
1068
  variant="secondary"
1044
1069
  className="gap-1.5 text-destructive hover:text-destructive"
1045
1070
  onClick={() => {
1071
+ if (selectedArchivedIds.length === 0) {
1072
+ toast.error(t('toasts.deleteOnlyArchived'));
1073
+ return;
1074
+ }
1075
+
1076
+ if (hasSelectedNonArchived) {
1077
+ toast.warning(t('toasts.bulkDeleteArchivedOnly'));
1078
+ }
1079
+
1046
1080
  Promise.all(
1047
- Array.from(selectedIds).map((id) =>
1081
+ selectedArchivedIds.map((id) =>
1048
1082
  request({ url: `/lms/courses/${id}`, method: 'DELETE' })
1049
1083
  )
1050
1084
  ).then(async () => {
1051
1085
  toast.success(
1052
- t('toasts.coursesRemoved', { count: selectedIds.size })
1086
+ t('toasts.coursesRemoved', {
1087
+ count: selectedArchivedIds.length,
1088
+ })
1053
1089
  );
1054
1090
  setSelectedIds(new Set());
1055
1091
  await refetchCourses();
@@ -1254,6 +1290,7 @@ export default function CursosPage() {
1254
1290
  </DropdownMenuItem>
1255
1291
  <DropdownMenuSeparator />
1256
1292
  <DropdownMenuItem
1293
+ disabled={curso.status !== 'arquivado'}
1257
1294
  className="text-destructive focus:text-destructive"
1258
1295
  onClick={(e) => openDeleteDialog(curso, e)}
1259
1296
  >
@@ -1283,18 +1320,24 @@ export default function CursosPage() {
1283
1320
  <div className="mt-3 flex items-center gap-3 border-t border-border/50 pt-2.5 text-xs text-muted-foreground">
1284
1321
  <span className="flex items-center gap-1">
1285
1322
  <Layers className="size-3.5 shrink-0" />
1286
- <span className="font-medium text-foreground">{curso.quantidadeSessoes}</span>
1287
- {' '}sess.
1323
+ <span className="font-medium text-foreground">
1324
+ {curso.quantidadeSessoes}
1325
+ </span>{' '}
1326
+ sess.
1288
1327
  </span>
1289
1328
  <span className="flex items-center gap-1">
1290
1329
  <BookOpen className="size-3.5 shrink-0" />
1291
- <span className="font-medium text-foreground">{curso.quantidadeAulas}</span>
1292
- {' '}aulas
1330
+ <span className="font-medium text-foreground">
1331
+ {curso.quantidadeAulas}
1332
+ </span>{' '}
1333
+ aulas
1293
1334
  </span>
1294
1335
  <span className="flex items-center gap-1">
1295
1336
  <Paperclip className="size-3.5 shrink-0" />
1296
- <span className="font-medium text-foreground">{curso.quantidadeRecursos}</span>
1297
- {' '}rec.
1337
+ <span className="font-medium text-foreground">
1338
+ {curso.quantidadeRecursos}
1339
+ </span>{' '}
1340
+ rec.
1298
1341
  </span>
1299
1342
  </div>
1300
1343
  </CardContent>
@@ -1448,6 +1491,7 @@ export default function CursosPage() {
1448
1491
  </DropdownMenuItem>
1449
1492
  <DropdownMenuSeparator />
1450
1493
  <DropdownMenuItem
1494
+ disabled={curso.status !== 'arquivado'}
1451
1495
  className="text-destructive focus:text-destructive"
1452
1496
  onClick={(e) => openDeleteDialog(curso, e)}
1453
1497
  >
@@ -23,7 +23,7 @@ function toApiLevel(level: CourseSheetFormValues['nivel']) {
23
23
  }
24
24
 
25
25
  function toApiStatus(status: CourseSheetFormValues['status']) {
26
- if (status === 'ativo') return 'published';
26
+ if (status === 'publicado') return 'published';
27
27
  if (status === 'rascunho') return 'draft';
28
28
  return 'archived';
29
29
  }
@@ -54,7 +54,7 @@ function toPtLevel(level?: string | null): CourseSheetFormValues['nivel'] {
54
54
  }
55
55
 
56
56
  function toPtStatus(status?: string | null): CourseSheetFormValues['status'] {
57
- if (status === 'published' || status === 'active') return 'ativo';
57
+ if (status === 'published' || status === 'active') return 'publicado';
58
58
  if (status === 'draft') return 'rascunho';
59
59
  if (status === 'archived') return 'arquivado';
60
60
  return 'rascunho';
@@ -67,7 +67,7 @@ function toApiLevel(level: CourseSheetFormValues['nivel']) {
67
67
  }
68
68
 
69
69
  function toApiStatus(status: CourseSheetFormValues['status']) {
70
- if (status === 'ativo') return 'published';
70
+ if (status === 'publicado') return 'published';
71
71
  if (status === 'rascunho') return 'draft';
72
72
  return 'archived';
73
73
  }
@@ -1146,7 +1146,7 @@ export default function TrainingPage() {
1146
1146
  }
1147
1147
 
1148
1148
  function courseStatusToApi(value: CourseSheetFormValues['status']) {
1149
- if (value === 'ativo') return 'published';
1149
+ if (value === 'publicado') return 'published';
1150
1150
  if (value === 'arquivado') return 'archived';
1151
1151
  return 'draft';
1152
1152
  }
@@ -1132,7 +1132,7 @@ export default function TrainingPage() {
1132
1132
  }
1133
1133
 
1134
1134
  function courseStatusToApi(value: CourseSheetFormValues['status']) {
1135
- if (value === 'ativo') return 'published';
1135
+ if (value === 'publicado') return 'published';
1136
1136
  if (value === 'arquivado') return 'archived';
1137
1137
  return 'draft';
1138
1138
  }
@@ -400,7 +400,7 @@
400
400
  "advanced": "Advanced"
401
401
  },
402
402
  "status": {
403
- "active": "Active",
403
+ "published": "Published",
404
404
  "draft": "Draft",
405
405
  "archived": "Archived"
406
406
  },
@@ -574,7 +574,7 @@
574
574
  "sub": "registered"
575
575
  },
576
576
  "activeCourses": {
577
- "label": "Active Courses",
577
+ "label": "Published Courses",
578
578
  "sub": "published"
579
579
  },
580
580
  "totalStudents": {
@@ -610,6 +610,7 @@
610
610
  "advanced": "Advanced"
611
611
  },
612
612
  "status": {
613
+ "published": "Published",
613
614
  "active": "Active",
614
615
  "draft": "Draft",
615
616
  "archived": "Archived"
@@ -771,6 +772,8 @@
771
772
  "courseRemoved": "Course \"{title}\" removed.",
772
773
  "coursesRemoved": "{count} course(s) removed.",
773
774
  "coursesArchived": "{count} course(s) archived.",
775
+ "deleteOnlyArchived": "Only archived courses can be deleted.",
776
+ "bulkDeleteArchivedOnly": "Only selected archived courses will be deleted.",
774
777
  "codeCopied": "Code \"{code}\" copied."
775
778
  },
776
779
  "StructurePage": {
@@ -942,6 +945,8 @@
942
945
  },
943
946
  "lessonRow": {
944
947
  "rename": "Rename lesson",
948
+ "draft": "Draft",
949
+ "published": "Published",
945
950
  "visibility": "Visibility: {value}",
946
951
  "status": "Status: {value}",
947
952
  "videoLinked": "Linked video",
@@ -409,7 +409,7 @@
409
409
  "advanced": "Avançado"
410
410
  },
411
411
  "status": {
412
- "active": "Ativo",
412
+ "published": "Publicado",
413
413
  "draft": "Rascunho",
414
414
  "archived": "Arquivado"
415
415
  },
@@ -583,7 +583,7 @@
583
583
  "sub": "cadastrados"
584
584
  },
585
585
  "activeCourses": {
586
- "label": "Cursos Ativos",
586
+ "label": "Cursos Publicados",
587
587
  "sub": "publicados"
588
588
  },
589
589
  "totalStudents": {
@@ -619,6 +619,7 @@
619
619
  "advanced": "Avançado"
620
620
  },
621
621
  "status": {
622
+ "published": "Publicado",
622
623
  "active": "Ativo",
623
624
  "draft": "Rascunho",
624
625
  "archived": "Arquivado"
@@ -780,6 +781,8 @@
780
781
  "courseRemoved": "Curso \"{title}\" removido.",
781
782
  "coursesRemoved": "{count} curso(s) removido(s).",
782
783
  "coursesArchived": "{count} curso(s) arquivado(s).",
784
+ "deleteOnlyArchived": "Apenas cursos arquivados podem ser excluídos.",
785
+ "bulkDeleteArchivedOnly": "Somente cursos arquivados selecionados serão excluídos.",
783
786
  "codeCopied": "Código \"{code}\" copiado."
784
787
  },
785
788
  "StructurePage": {
@@ -951,6 +954,8 @@
951
954
  },
952
955
  "lessonRow": {
953
956
  "rename": "Renomear aula",
957
+ "draft": "Rascunho",
958
+ "published": "Publicada",
954
959
  "visibility": "Visibilidade: {value}",
955
960
  "status": "Status: {value}",
956
961
  "videoLinked": "Vídeo vinculado",
@@ -26,9 +26,9 @@ columns:
26
26
  type: int
27
27
  isNullable: true
28
28
  - type: order
29
- - name: is_released
29
+ - name: published
30
30
  type: boolean
31
- default: true
31
+ default: false
32
32
  - type: created_at
33
33
  - type: updated_at
34
34
 
@@ -16,6 +16,9 @@ columns:
16
16
  - name: duration_minutes
17
17
  type: int
18
18
  default: 0
19
+ - name: published
20
+ type: boolean
21
+ default: false
19
22
  - type: created_at
20
23
  - type: updated_at
21
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/lms",
3
- "version": "0.0.354",
3
+ "version": "0.0.357",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -9,16 +9,16 @@
9
9
  "@nestjs/core": "^11",
10
10
  "@nestjs/jwt": "^11",
11
11
  "@nestjs/mapped-types": "*",
12
- "@hed-hog/api": "0.0.8",
13
- "@hed-hog/api-types": "0.0.1",
14
12
  "@hed-hog/api-prisma": "0.0.6",
15
- "@hed-hog/crm": "0.0.354",
16
- "@hed-hog/api-pagination": "0.0.7",
13
+ "@hed-hog/api-types": "0.0.1",
14
+ "@hed-hog/api": "0.0.8",
15
+ "@hed-hog/category": "0.0.356",
17
16
  "@hed-hog/api-locale": "0.0.14",
18
- "@hed-hog/category": "0.0.354",
19
- "@hed-hog/core": "0.0.354",
20
- "@hed-hog/finance": "0.0.354",
21
- "@hed-hog/queue": "0.0.354"
17
+ "@hed-hog/api-pagination": "0.0.7",
18
+ "@hed-hog/crm": "0.0.356",
19
+ "@hed-hog/finance": "0.0.356",
20
+ "@hed-hog/core": "0.0.356",
21
+ "@hed-hog/queue": "0.0.356"
22
22
  },
23
23
  "exports": {
24
24
  ".": {