@hed-hog/lms 0.0.353 → 0.0.355

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 (135) hide show
  1. package/dist/course/course-audio-transcription.service.d.ts +29 -0
  2. package/dist/course/course-audio-transcription.service.d.ts.map +1 -0
  3. package/dist/course/course-audio-transcription.service.js +291 -0
  4. package/dist/course/course-audio-transcription.service.js.map +1 -0
  5. package/dist/course/course-lesson.controller.d.ts +10 -0
  6. package/dist/course/course-lesson.controller.d.ts.map +1 -0
  7. package/dist/course/course-lesson.controller.js +62 -0
  8. package/dist/course/course-lesson.controller.js.map +1 -0
  9. package/dist/course/course-structure.controller.d.ts +41 -15
  10. package/dist/course/course-structure.controller.d.ts.map +1 -1
  11. package/dist/course/course-structure.controller.js +50 -6
  12. package/dist/course/course-structure.controller.js.map +1 -1
  13. package/dist/course/course-structure.service.d.ts +50 -15
  14. package/dist/course/course-structure.service.d.ts.map +1 -1
  15. package/dist/course/course-structure.service.js +238 -73
  16. package/dist/course/course-structure.service.js.map +1 -1
  17. package/dist/course/course-video-conversion.service.d.ts +20 -2
  18. package/dist/course/course-video-conversion.service.d.ts.map +1 -1
  19. package/dist/course/course-video-conversion.service.js +730 -10
  20. package/dist/course/course-video-conversion.service.js.map +1 -1
  21. package/dist/course/course.controller.d.ts +24 -8
  22. package/dist/course/course.controller.d.ts.map +1 -1
  23. package/dist/course/course.module.d.ts.map +1 -1
  24. package/dist/course/course.module.js +5 -3
  25. package/dist/course/course.module.js.map +1 -1
  26. package/dist/course/course.service.d.ts +24 -8
  27. package/dist/course/course.service.d.ts.map +1 -1
  28. package/dist/course/course.service.js +112 -176
  29. package/dist/course/course.service.js.map +1 -1
  30. package/dist/course/dto/create-course-lesson-frame.dto.d.ts +5 -0
  31. package/dist/course/dto/create-course-lesson-frame.dto.d.ts.map +1 -0
  32. package/dist/course/dto/create-course-lesson-frame.dto.js +30 -0
  33. package/dist/course/dto/create-course-lesson-frame.dto.js.map +1 -0
  34. package/dist/course/dto/create-course-structure-lesson.dto.d.ts +4 -2
  35. package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
  36. package/dist/course/dto/create-course-structure-lesson.dto.js +10 -3
  37. package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
  38. package/dist/course/dto/create-course.dto.d.ts +1 -1
  39. package/dist/course/dto/create-course.dto.d.ts.map +1 -1
  40. package/dist/course/dto/create-course.dto.js +6 -6
  41. package/dist/course/dto/create-course.dto.js.map +1 -1
  42. package/dist/course/dto/update-course-lesson-frame.dto.d.ts +5 -0
  43. package/dist/course/dto/update-course-lesson-frame.dto.d.ts.map +1 -0
  44. package/dist/course/dto/update-course-lesson-frame.dto.js +32 -0
  45. package/dist/course/dto/update-course-lesson-frame.dto.js.map +1 -0
  46. package/dist/course/dto/update-course-resources.dto.d.ts +4 -2
  47. package/dist/course/dto/update-course-resources.dto.d.ts.map +1 -1
  48. package/dist/course/dto/update-course-resources.dto.js +10 -3
  49. package/dist/course/dto/update-course-resources.dto.js.map +1 -1
  50. package/dist/course/dto/update-transcription-segments.dto.d.ts +10 -0
  51. package/dist/course/dto/update-transcription-segments.dto.d.ts.map +1 -0
  52. package/dist/course/dto/update-transcription-segments.dto.js +38 -0
  53. package/dist/course/dto/update-transcription-segments.dto.js.map +1 -0
  54. package/dist/course/lms-setting.controller.d.ts +13 -0
  55. package/dist/course/lms-setting.controller.d.ts.map +1 -0
  56. package/dist/course/lms-setting.controller.js +53 -0
  57. package/dist/course/lms-setting.controller.js.map +1 -0
  58. package/dist/enterprise/training/training-admin.service.d.ts.map +1 -1
  59. package/dist/enterprise/training/training-admin.service.js +74 -33
  60. package/dist/enterprise/training/training-admin.service.js.map +1 -1
  61. package/dist/index.d.ts +2 -0
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +2 -0
  64. package/dist/index.js.map +1 -1
  65. package/dist/lms.module.d.ts.map +1 -1
  66. package/dist/lms.module.js +6 -0
  67. package/dist/lms.module.js.map +1 -1
  68. package/hedhog/data/route.yaml +63 -0
  69. package/hedhog/data/setting_group.yaml +76 -0
  70. package/hedhog/frontend/app/courses/[id]/_components/CourseCertificateCard.tsx.ejs +3 -0
  71. package/hedhog/frontend/app/courses/[id]/_components/CourseClassificationCard.tsx.ejs +10 -1
  72. package/hedhog/frontend/app/courses/[id]/_components/CourseContentCard.tsx.ejs +7 -2
  73. package/hedhog/frontend/app/courses/[id]/_components/CourseDangerZoneCard.tsx.ejs +5 -2
  74. package/hedhog/frontend/app/courses/[id]/_components/CourseFlagsCard.tsx.ejs +7 -1
  75. package/hedhog/frontend/app/courses/[id]/_components/CourseMediaCard.tsx.ejs +3 -0
  76. package/hedhog/frontend/app/courses/[id]/_components/CourseMultiEntityPicker.tsx.ejs +95 -50
  77. package/hedhog/frontend/app/courses/[id]/_components/CourseRelationsCard.tsx.ejs +11 -36
  78. package/hedhog/frontend/app/courses/[id]/_components/CourseSectionCard.tsx.ejs +19 -5
  79. package/hedhog/frontend/app/courses/[id]/_components/course-edit-types.ts.ejs +1 -1
  80. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +30 -10
  81. package/hedhog/frontend/app/courses/[id]/structure/_components/CourseInstructorsSummaryCard.tsx.ejs +95 -0
  82. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +29 -11
  83. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-panel.tsx.ejs +42 -31
  84. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +10 -1
  85. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +51 -9
  86. package/hedhog/frontend/app/courses/[id]/structure/_components/drag-handle.tsx.ejs +25 -22
  87. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +12 -9
  88. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +315 -229
  89. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +3220 -534
  90. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +20 -15
  91. package/hedhog/frontend/app/courses/[id]/structure/_components/icon-action-tooltip.tsx.ejs +35 -0
  92. package/hedhog/frontend/app/courses/[id]/structure/_components/multi-select-bar.tsx.ejs +76 -67
  93. package/hedhog/frontend/app/courses/[id]/structure/_components/search-filter.tsx.ejs +13 -10
  94. package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +18 -16
  95. package/hedhog/frontend/app/courses/[id]/structure/_components/sortable-tree-row.tsx.ejs +6 -0
  96. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-context-menu.tsx.ejs +1 -1
  97. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs +23 -11
  98. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-helpers.ts.ejs +48 -9
  99. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-course.tsx.ejs +11 -2
  100. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +121 -15
  101. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +69 -14
  102. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row.tsx.ejs +11 -0
  103. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +31 -0
  104. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +32 -6
  105. package/hedhog/frontend/app/courses/[id]/structure/_data/services/course-structure.service.ts.ejs +57 -3
  106. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +46 -6
  107. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +14 -0
  108. package/hedhog/frontend/app/courses/[id]/structure/_data/use-lesson-audio-files.ts.ejs +23 -0
  109. package/hedhog/frontend/app/courses/[id]/structure/_data/use-lms-settings-query.ts.ejs +34 -0
  110. package/hedhog/frontend/app/courses/[id]/structure/_data/use-transcription-segments.ts.ejs +76 -0
  111. package/hedhog/frontend/messages/en.json +39 -3
  112. package/hedhog/frontend/messages/pt.json +39 -3
  113. package/hedhog/table/course.yaml +8 -0
  114. package/hedhog/table/course_lesson_file.yaml +12 -4
  115. package/hedhog/table/course_lesson_transcription_segment.yaml +22 -0
  116. package/hedhog/table/course_lesson_video_frame.yaml +25 -0
  117. package/package.json +8 -8
  118. package/src/course/course-audio-transcription.service.ts +393 -0
  119. package/src/course/course-lesson.controller.ts +28 -0
  120. package/src/course/course-structure.controller.ts +49 -3
  121. package/src/course/course-structure.service.ts +294 -32
  122. package/src/course/course-video-conversion.service.ts +972 -6
  123. package/src/course/course.module.ts +5 -3
  124. package/src/course/course.service.ts +87 -139
  125. package/src/course/dto/create-course-lesson-frame.dto.ts +14 -0
  126. package/src/course/dto/create-course-structure-lesson.dto.ts +19 -3
  127. package/src/course/dto/create-course.dto.ts +5 -5
  128. package/src/course/dto/update-course-lesson-frame.dto.ts +16 -0
  129. package/src/course/dto/update-course-resources.dto.ts +18 -3
  130. package/src/course/dto/update-transcription-segments.dto.ts +20 -0
  131. package/src/course/lms-setting.controller.ts +30 -0
  132. package/src/enterprise/training/training-admin.service.ts +77 -24
  133. package/src/index.ts +2 -0
  134. package/src/lms.module.ts +6 -0
  135. package/hedhog/table/course_instructor.yaml +0 -27
@@ -0,0 +1,76 @@
1
+ - slug: lms
2
+ icon: video
3
+ name:
4
+ en: LMS
5
+ pt: LMS
6
+ description:
7
+ en: Learning management module settings.
8
+ pt: Configuracoes do modulo de gestao de aprendizagem.
9
+ relations:
10
+ setting:
11
+ - slug: lms-video-conversion-enabled
12
+ type: boolean
13
+ component: switch
14
+ name:
15
+ en: Enable Video Conversion Queue
16
+ pt: Habilitar fila de conversao de video
17
+ description:
18
+ en: Enables original video upload and background conversion queue processing for lessons.
19
+ pt: Habilita o upload de video original e o processamento em fila para conversao de videos das aulas.
20
+ value: true
21
+ user_override: false
22
+ - slug: lms-video-frame-capture-interval-seconds
23
+ type: number
24
+ component: input-number
25
+ name:
26
+ en: Video Frame Capture Interval (seconds)
27
+ pt: Intervalo de captura de frames do video (segundos)
28
+ description:
29
+ en: Defines how often frame images are extracted from the original video during conversion.
30
+ pt: Define de quantos em quantos segundos os frames sao extraidos do video original durante a conversao.
31
+ value: 10
32
+ user_override: false
33
+ - slug: lms-image-extraction-enabled
34
+ type: boolean
35
+ component: switch
36
+ name:
37
+ en: Enable Video Frame Extraction
38
+ pt: Habilitar extracao de frames do video
39
+ description:
40
+ en: Enables extraction of screenshot images from the original video during conversion.
41
+ pt: Habilita a extracao de imagens (frames) do video original durante a conversao.
42
+ value: true
43
+ user_override: false
44
+ - slug: lms-audio-transcription-enabled
45
+ type: boolean
46
+ component: switch
47
+ name:
48
+ en: Enable Audio Transcription
49
+ pt: Habilitar transcricao de audio
50
+ description:
51
+ en: Enables automatic audio extraction and AI transcription for video lessons.
52
+ pt: Habilita a extracao de audio e a transcricao automatica por IA para aulas em video.
53
+ value: true
54
+ user_override: false
55
+ - slug: lms-youtube-provider-enabled
56
+ type: boolean
57
+ component: switch
58
+ name:
59
+ en: Allow YouTube as Video Provider
60
+ pt: Permitir YouTube como provedor de video
61
+ description:
62
+ en: When enabled, instructors can use YouTube links as the video source for lessons.
63
+ pt: Quando habilitado, instrutores podem usar links do YouTube como fonte de video nas aulas.
64
+ value: true
65
+ user_override: false
66
+ - slug: lms-vimeo-provider-enabled
67
+ type: boolean
68
+ component: switch
69
+ name:
70
+ en: Allow Vimeo as Video Provider
71
+ pt: Permitir Vimeo como provedor de video
72
+ description:
73
+ en: When enabled, instructors can use Vimeo links as the video source for lessons.
74
+ pt: Quando habilitado, instrutores podem usar links do Vimeo como fonte de video nas aulas.
75
+ value: true
76
+ user_override: false
@@ -22,6 +22,7 @@ type CourseCertificateCardProps = {
22
22
  onCreateTemplate: (
23
23
  values: Record<string, string>
24
24
  ) => Promise<PickerOption | null>;
25
+ compact?: boolean;
25
26
  };
26
27
 
27
28
  export function CourseCertificateCard({
@@ -29,12 +30,14 @@ export function CourseCertificateCard({
29
30
  t,
30
31
  options,
31
32
  onCreateTemplate,
33
+ compact = false,
32
34
  }: CourseCertificateCardProps) {
33
35
  return (
34
36
  <CourseSectionCard
35
37
  title={t('form.sections.certification')}
36
38
  description={t('form.sectionDescriptions.certification')}
37
39
  icon={Award}
40
+ compact={compact}
38
41
  >
39
42
  <FormField
40
43
  control={form.control}
@@ -31,6 +31,7 @@ type CourseClassificationCardProps = {
31
31
  levels: readonly SelectOption[];
32
32
  statuses: readonly SelectOption[];
33
33
  offeringTypes: readonly SelectOption[];
34
+ compact?: boolean;
34
35
  };
35
36
 
36
37
  export function CourseClassificationCard({
@@ -39,14 +40,22 @@ export function CourseClassificationCard({
39
40
  levels,
40
41
  statuses,
41
42
  offeringTypes,
43
+ compact = false,
42
44
  }: CourseClassificationCardProps) {
43
45
  return (
44
46
  <CourseSectionCard
45
47
  title="Classificação e configuração"
46
48
  description="Defina estágio, formato e identidade visual do curso."
47
49
  icon={Settings2}
50
+ compact={compact}
48
51
  >
49
- <div className="grid gap-2 sm:gap-3 md:grid-cols-2 xl:grid-cols-3">
52
+ <div
53
+ className={
54
+ compact
55
+ ? 'grid gap-2 md:grid-cols-2 xl:grid-cols-3'
56
+ : 'grid gap-2 sm:gap-3 md:grid-cols-2 xl:grid-cols-3'
57
+ }
58
+ >
50
59
  <FormField
51
60
  control={form.control}
52
61
  name="nivel"
@@ -14,16 +14,21 @@ import type { CourseEditFormValues } from './course-edit-types';
14
14
 
15
15
  type CourseContentCardProps = {
16
16
  form: UseFormReturn<CourseEditFormValues>;
17
+ compact?: boolean;
17
18
  };
18
19
 
19
- export function CourseContentCard({ form }: CourseContentCardProps) {
20
+ export function CourseContentCard({
21
+ form,
22
+ compact = false,
23
+ }: CourseContentCardProps) {
20
24
  return (
21
25
  <CourseSectionCard
22
26
  title="Conteúdo e regras"
23
27
  description="Concentre os textos longos que apoiam venda, orientação pedagógica e publicação."
24
28
  icon={FileText}
29
+ compact={compact}
25
30
  >
26
- <div className="grid gap-2 sm:gap-3">
31
+ <div className={compact ? 'grid gap-2' : 'grid gap-2 sm:gap-3'}>
27
32
  <FormField
28
33
  control={form.control}
29
34
  name="objetivos"
@@ -13,19 +13,22 @@ import type { TranslationFn } from './course-edit-types';
13
13
  type CourseDangerZoneCardProps = {
14
14
  t: TranslationFn;
15
15
  onDelete: () => void;
16
+ compact?: boolean;
16
17
  };
17
18
 
18
19
  export function CourseDangerZoneCard({
19
20
  t,
20
21
  onDelete,
22
+ compact = false,
21
23
  }: CourseDangerZoneCardProps) {
22
24
  return (
23
25
  <CourseSectionCard
24
26
  title="Zona de risco"
25
27
  description="Ações destrutivas ficam isoladas para evitar cliques acidentais."
26
28
  icon={AlertTriangle}
29
+ compact={compact}
27
30
  className="border-destructive/20"
28
- contentClassName="pt-0"
31
+ contentClassName={compact ? undefined : 'pt-0'}
29
32
  >
30
33
  <TooltipProvider>
31
34
  <Tooltip>
@@ -41,7 +44,7 @@ export function CourseDangerZoneCard({
41
44
  {t('form.actions.deleteCourse')}
42
45
  </Button>
43
46
  </TooltipTrigger>
44
- <TooltipContent side="top" className="max-w-[240px] text-xs">
47
+ <TooltipContent side="top" className="max-w-60 text-xs">
45
48
  Remove o curso, turmas, matrículas e certificados vinculados
46
49
  permanentemente.
47
50
  </TooltipContent>
@@ -20,6 +20,7 @@ import type { CourseEditFormValues, TranslationFn } from './course-edit-types';
20
20
  type CourseFlagsCardProps = {
21
21
  form: UseFormReturn<CourseEditFormValues>;
22
22
  t: TranslationFn;
23
+ compact?: boolean;
23
24
  };
24
25
 
25
26
  function FlagRow({
@@ -52,12 +53,17 @@ function FlagRow({
52
53
  );
53
54
  }
54
55
 
55
- export function CourseFlagsCard({ form, t }: CourseFlagsCardProps) {
56
+ export function CourseFlagsCard({
57
+ form,
58
+ t,
59
+ compact = false,
60
+ }: CourseFlagsCardProps) {
56
61
  return (
57
62
  <CourseSectionCard
58
63
  title={t('form.sections.flags')}
59
64
  description="Controles rápidos para disponibilidade e destaque do curso."
60
65
  icon={BadgeCheck}
66
+ compact={compact}
61
67
  >
62
68
  <div className="space-y-3">
63
69
  <FormField
@@ -186,6 +186,7 @@ type CourseMediaCardProps = {
186
186
  logoImageType?: ImageTypeSpec | null;
187
187
  bannerImageType?: ImageTypeSpec | null;
188
188
  t: TranslationFn;
189
+ compact?: boolean;
189
190
  };
190
191
 
191
192
  export function CourseMediaCard({
@@ -204,12 +205,14 @@ export function CourseMediaCard({
204
205
  logoImageType,
205
206
  bannerImageType,
206
207
  t,
208
+ compact = false,
207
209
  }: CourseMediaCardProps) {
208
210
  return (
209
211
  <CourseSectionCard
210
212
  title={t('form.media.title')}
211
213
  description="Gerencie os ativos visuais do curso com preview e ações rápidas."
212
214
  icon={FileImage}
215
+ compact={compact}
213
216
  >
214
217
  <div className="space-y-4">
215
218
  <MediaRow
@@ -2,6 +2,12 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
2
2
  import { Badge } from '@/components/ui/badge';
3
3
  import { Button } from '@/components/ui/button';
4
4
  import { EntityPicker } from '@/components/ui/entity-picker';
5
+ import {
6
+ Tooltip,
7
+ TooltipContent,
8
+ TooltipProvider,
9
+ TooltipTrigger,
10
+ } from '@/components/ui/tooltip';
5
11
  import { cn } from '@/lib/utils';
6
12
  import { Grip, Pencil, Plus, X } from 'lucide-react';
7
13
  import { useMemo, useState } from 'react';
@@ -148,16 +154,25 @@ export function CourseMultiEntityPicker({
148
154
  />
149
155
 
150
156
  {onCreateClick ? (
151
- <Button
152
- type="button"
153
- variant="outline"
154
- size="icon"
155
- className="h-10 w-10 shrink-0 rounded-xl"
156
- onClick={onCreateClick}
157
- aria-label={createActionLabel ?? `Criar ${entityLabel}`}
158
- >
159
- <Plus className="h-4 w-4" />
160
- </Button>
157
+ <TooltipProvider>
158
+ <Tooltip>
159
+ <TooltipTrigger asChild>
160
+ <Button
161
+ type="button"
162
+ variant="outline"
163
+ size="icon"
164
+ className="h-10 w-10 shrink-0 rounded-xl"
165
+ onClick={onCreateClick}
166
+ aria-label={createActionLabel ?? `Criar ${entityLabel}`}
167
+ >
168
+ <Plus className="h-4 w-4" />
169
+ </Button>
170
+ </TooltipTrigger>
171
+ <TooltipContent>
172
+ {createActionLabel ?? `Criar ${entityLabel}`}
173
+ </TooltipContent>
174
+ </Tooltip>
175
+ </TooltipProvider>
161
176
  ) : null}
162
177
  </div>
163
178
 
@@ -196,30 +211,46 @@ export function CourseMultiEntityPicker({
196
211
 
197
212
  <div className="flex items-center gap-1">
198
213
  {onEditSelected ? (
199
- <Button
200
- type="button"
201
- variant="ghost"
202
- size="icon"
203
- className="h-8 w-8 rounded-lg text-muted-foreground hover:text-foreground"
204
- onClick={() => onEditSelected(option)}
205
- aria-label={`Editar ${option.label}`}
206
- >
207
- <Pencil className="h-4 w-4" />
208
- </Button>
214
+ <TooltipProvider>
215
+ <Tooltip>
216
+ <TooltipTrigger asChild>
217
+ <Button
218
+ type="button"
219
+ variant="ghost"
220
+ size="icon"
221
+ className="h-8 w-8 rounded-lg text-muted-foreground hover:text-foreground"
222
+ onClick={() => onEditSelected(option)}
223
+ aria-label={`Editar ${option.label}`}
224
+ >
225
+ <Pencil className="h-4 w-4" />
226
+ </Button>
227
+ </TooltipTrigger>
228
+ <TooltipContent>{`Editar ${option.label}`}</TooltipContent>
229
+ </Tooltip>
230
+ </TooltipProvider>
209
231
  ) : null}
210
232
 
211
- <Button
212
- type="button"
213
- variant="ghost"
214
- size="icon"
215
- className="h-8 w-8 rounded-lg text-muted-foreground hover:text-foreground"
216
- onClick={() =>
217
- onChange(value.filter((item) => item !== option.value))
218
- }
219
- aria-label={`Remover ${option.label}`}
220
- >
221
- <X className="h-4 w-4" />
222
- </Button>
233
+ <TooltipProvider>
234
+ <Tooltip>
235
+ <TooltipTrigger asChild>
236
+ <Button
237
+ type="button"
238
+ variant="ghost"
239
+ size="icon"
240
+ className="h-8 w-8 rounded-lg text-muted-foreground hover:text-foreground"
241
+ onClick={() =>
242
+ onChange(
243
+ value.filter((item) => item !== option.value)
244
+ )
245
+ }
246
+ aria-label={`Remover ${option.label}`}
247
+ >
248
+ <X className="h-4 w-4" />
249
+ </Button>
250
+ </TooltipTrigger>
251
+ <TooltipContent>{`Remover ${option.label}`}</TooltipContent>
252
+ </Tooltip>
253
+ </TooltipProvider>
223
254
  </div>
224
255
  </div>
225
256
  ) : (
@@ -230,25 +261,39 @@ export function CourseMultiEntityPicker({
230
261
  >
231
262
  <span className="max-w-45 truncate">{option.label}</span>
232
263
  {onEditSelected ? (
233
- <button
234
- type="button"
235
- className="cursor-pointer text-muted-foreground transition-colors hover:text-foreground"
236
- onClick={() => onEditSelected(option)}
237
- aria-label={`Editar ${option.label}`}
238
- >
239
- <Pencil className="h-3.5 w-3.5" />
240
- </button>
264
+ <TooltipProvider>
265
+ <Tooltip>
266
+ <TooltipTrigger asChild>
267
+ <button
268
+ type="button"
269
+ className="cursor-pointer text-muted-foreground transition-colors hover:text-foreground"
270
+ onClick={() => onEditSelected(option)}
271
+ aria-label={`Editar ${option.label}`}
272
+ >
273
+ <Pencil className="h-3.5 w-3.5" />
274
+ </button>
275
+ </TooltipTrigger>
276
+ <TooltipContent>{`Editar ${option.label}`}</TooltipContent>
277
+ </Tooltip>
278
+ </TooltipProvider>
241
279
  ) : null}
242
- <button
243
- type="button"
244
- className="cursor-pointer text-muted-foreground transition-colors hover:text-foreground"
245
- onClick={() =>
246
- onChange(value.filter((item) => item !== option.value))
247
- }
248
- aria-label={`Remover ${option.label}`}
249
- >
250
- <X className="h-3.5 w-3.5" />
251
- </button>
280
+ <TooltipProvider>
281
+ <Tooltip>
282
+ <TooltipTrigger asChild>
283
+ <button
284
+ type="button"
285
+ className="cursor-pointer text-muted-foreground transition-colors hover:text-foreground"
286
+ onClick={() =>
287
+ onChange(value.filter((item) => item !== option.value))
288
+ }
289
+ aria-label={`Remover ${option.label}`}
290
+ >
291
+ <X className="h-3.5 w-3.5" />
292
+ </button>
293
+ </TooltipTrigger>
294
+ <TooltipContent>{`Remover ${option.label}`}</TooltipContent>
295
+ </Tooltip>
296
+ </TooltipProvider>
252
297
  </Badge>
253
298
  )
254
299
  )}
@@ -23,13 +23,11 @@ type CourseRelationsCardProps = {
23
23
  t: TranslationFn;
24
24
  categoryOptions: PickerOption[];
25
25
  projectOptions: PickerOption[];
26
- instructorOptions: PickerOption[];
27
26
  onCreateCategory: (
28
27
  values: Record<string, string>
29
28
  ) => Promise<PickerOption | null>;
30
- onCreateInstructor: () => void;
31
29
  onEditCategory: (categorySlug: string) => void;
32
- onEditInstructor: (instructorId: string) => void;
30
+ compact?: boolean;
33
31
  };
34
32
 
35
33
  export function CourseRelationsCard({
@@ -37,20 +35,25 @@ export function CourseRelationsCard({
37
35
  t,
38
36
  categoryOptions,
39
37
  projectOptions,
40
- instructorOptions,
41
38
  onCreateCategory,
42
- onCreateInstructor,
43
39
  onEditCategory,
44
- onEditInstructor,
40
+ compact = false,
45
41
  }: CourseRelationsCardProps) {
46
42
  return (
47
43
  <CourseSectionCard
48
44
  title={t('form.sections.relations')}
49
45
  description={t('form.sectionDescriptions.relations')}
50
46
  icon={Network}
47
+ compact={compact}
51
48
  >
52
- <div className="space-y-2 sm:space-y-3">
53
- <div className="grid items-start gap-2 sm:gap-3 lg:grid-cols-2">
49
+ <div className={compact ? 'space-y-2' : 'space-y-2 sm:space-y-3'}>
50
+ <div
51
+ className={
52
+ compact
53
+ ? 'grid items-start gap-2 lg:grid-cols-2'
54
+ : 'grid items-start gap-2 sm:gap-3 lg:grid-cols-2'
55
+ }
56
+ >
54
57
  <FormField
55
58
  control={form.control}
56
59
  name="operationsProjectId"
@@ -126,34 +129,6 @@ export function CourseRelationsCard({
126
129
  </FormItem>
127
130
  )}
128
131
  />
129
-
130
- <FormField
131
- control={form.control}
132
- name="instrutores"
133
- render={({ field }) => (
134
- <FormItem className="self-start">
135
- <FormLabel>{t('form.fields.instructors.label')}</FormLabel>
136
- <CourseMultiEntityPicker
137
- value={field.value}
138
- onChange={field.onChange}
139
- onEditSelected={(option) => onEditInstructor(option.value)}
140
- options={instructorOptions}
141
- placeholder={t('form.fields.instructors.selectPlaceholder')}
142
- searchPlaceholder={t(
143
- 'form.fields.instructors.searchPlaceholder'
144
- )}
145
- entityLabel="instrutor"
146
- emptyStateDescription={t('form.fields.instructors.noResults')}
147
- emptyHint={t('form.fields.instructors.emptyHint')}
148
- createActionLabel={t('form.fields.instructors.createAction')}
149
- onCreateClick={onCreateInstructor}
150
- variant="instructor"
151
- renderOptionMeta
152
- />
153
- <FormMessage />
154
- </FormItem>
155
- )}
156
- />
157
132
  </div>
158
133
 
159
134
  <FormField
@@ -17,6 +17,7 @@ type CourseSectionCardProps = {
17
17
  className?: string;
18
18
  contentClassName?: string;
19
19
  action?: ReactNode;
20
+ compact?: boolean;
20
21
  };
21
22
 
22
23
  export function CourseSectionCard({
@@ -27,6 +28,7 @@ export function CourseSectionCard({
27
28
  className,
28
29
  contentClassName,
29
30
  action,
31
+ compact = false,
30
32
  }: CourseSectionCardProps) {
31
33
  const iconEl = Icon ? (
32
34
  <div className="flex h-6 w-6 shrink-0 items-center justify-center rounded-md border border-border/60 bg-muted/30 text-foreground sm:h-7 sm:w-7 sm:rounded-lg">
@@ -37,11 +39,19 @@ export function CourseSectionCard({
37
39
  return (
38
40
  <Card
39
41
  className={cn(
40
- 'border-border/70 gap-3 py-3 shadow-sm sm:gap-6 sm:py-6',
41
- className
42
+ compact
43
+ ? 'bg-muted/20 py-2 gap-2'
44
+ : 'border-border/70 gap-3 py-3 shadow-sm sm:gap-6 sm:py-6',
45
+ className,
42
46
  )}
43
47
  >
44
- <CardHeader className="space-y-1 pb-2 px-3 sm:space-y-1.5 sm:pb-3 sm:px-6">
48
+ <CardHeader
49
+ className={cn(
50
+ compact
51
+ ? 'space-y-1 px-3 pt-2 pb-0'
52
+ : 'space-y-1 pb-2 px-3 sm:space-y-1.5 sm:pb-3 sm:px-6',
53
+ )}
54
+ >
45
55
  <div className="flex items-center justify-between gap-3">
46
56
  <div className="flex min-w-0 items-center gap-2">
47
57
  {Icon && description ? (
@@ -50,7 +60,6 @@ export function CourseSectionCard({
50
60
  <TooltipTrigger asChild>
51
61
  <button
52
62
  type="button"
53
- tabIndex={-1}
54
63
  className="cursor-default focus:outline-none"
55
64
  >
56
65
  {iconEl}
@@ -73,7 +82,12 @@ export function CourseSectionCard({
73
82
  </div>
74
83
  </CardHeader>
75
84
 
76
- <CardContent className={cn('px-3 pt-0 sm:px-6', contentClassName)}>
85
+ <CardContent
86
+ className={cn(
87
+ compact ? 'px-3 pb-2 pt-0' : 'px-3 pt-0 sm:px-6',
88
+ contentClassName,
89
+ )}
90
+ >
77
91
  {children}
78
92
  </CardContent>
79
93
  </Card>
@@ -9,11 +9,11 @@ export type CourseEditFormValues = {
9
9
  nivel: 'iniciante' | 'intermediario' | 'avancado';
10
10
  status: 'ativo' | 'rascunho' | 'arquivado';
11
11
  tipoOferta: 'agendado' | 'sob_demanda' | 'hibrido';
12
+ localeId: string;
12
13
  categorias: string[];
13
14
  operationsProjectId: string;
14
15
  primaryColor: string;
15
16
  secondaryColor: string;
16
- instrutores: string[];
17
17
  preRequisitos: string;
18
18
  modeloCertificado: string;
19
19
  certificado: boolean;
@@ -9,15 +9,11 @@ import {
9
9
  ResizablePanel,
10
10
  ResizablePanelGroup,
11
11
  } from '@/components/ui/resizable';
12
- import {
13
- Sheet,
14
- SheetHeader,
15
- SheetTitle,
16
- } from '@/components/ui/sheet';
12
+ import { Sheet, SheetHeader, SheetTitle } from '@/components/ui/sheet';
17
13
  import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
18
14
  import { useIsMobile } from '@/hooks/use-mobile';
19
15
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
20
- import { useIsMutating } from '@tanstack/react-query';
16
+ import { useIsMutating, useQueryClient } from '@tanstack/react-query';
21
17
  import { AlertCircle, Menu, RefreshCw } from 'lucide-react';
22
18
  import { useTranslations } from 'next-intl';
23
19
 
@@ -41,12 +37,14 @@ import {
41
37
  usePasteSessionsMutation,
42
38
  } from './structure/_data/use-course-structure-mutations';
43
39
  import { useCourseStructureQuery } from './structure/_data/use-course-structure-query';
40
+ import { useLmsRealtimeRefresh } from '../../_lib/hooks/use-lms-realtime-refresh';
44
41
 
45
42
  interface Props {
46
43
  params: Promise<{ id: string }>;
47
44
  }
48
45
 
49
46
  type ApiCourseSummary = {
47
+ code?: string | null;
50
48
  name?: string;
51
49
  title?: string;
52
50
  logoFileId?: number | null;
@@ -61,6 +59,7 @@ export default function CourseStructurePage({ params }: Props) {
61
59
  const { id } = use(params);
62
60
  const isMobile = useIsMobile();
63
61
  const { request } = useApp();
62
+ const queryClient = useQueryClient();
64
63
 
65
64
  const { data: courseSummary } = useQuery<ApiCourseSummary>({
66
65
  queryKey: ['lms-course-detail', id],
@@ -93,6 +92,20 @@ export default function CourseStructurePage({ params }: Props) {
93
92
 
94
93
  const isMutating = useIsMutating();
95
94
 
95
+ const handleRealtimeRefresh = useCallback(async () => {
96
+ await refetch();
97
+ await queryClient.invalidateQueries({
98
+ queryKey: ['lesson-conversion-job-status'],
99
+ });
100
+ }, [queryClient, refetch]);
101
+
102
+ useLmsRealtimeRefresh({
103
+ request,
104
+ enabled: Boolean(id),
105
+ pollIntervalMs: 3000,
106
+ onRefresh: handleRealtimeRefresh,
107
+ });
108
+
96
109
  useEffect(() => {
97
110
  if (isMutating > 0) return;
98
111
  if (
@@ -100,11 +113,16 @@ export default function CourseStructurePage({ params }: Props) {
100
113
  !isError &&
101
114
  (apiSessions.length > 0 || apiLessons.length > 0 || apiCourse)
102
115
  ) {
116
+ const mergedCourse = apiCourse
117
+ ? {
118
+ ...apiCourse,
119
+ ...(courseSummary?.name ? { name: courseSummary.name } : {}),
120
+ ...(courseSummary?.code ? { code: courseSummary.code } : {}),
121
+ }
122
+ : apiCourse;
123
+
103
124
  setStructureFromApi({
104
- course:
105
- apiCourse && courseSummary?.name
106
- ? { ...apiCourse, name: courseSummary.name }
107
- : apiCourse,
125
+ course: mergedCourse,
108
126
  sessions: apiSessions,
109
127
  lessons: apiLessons,
110
128
  });
@@ -113,6 +131,8 @@ export default function CourseStructurePage({ params }: Props) {
113
131
  apiCourse,
114
132
  apiSessions,
115
133
  apiLessons,
134
+ courseSummary?.code,
135
+ courseSummary?.name,
116
136
  isLoading,
117
137
  isError,
118
138
  isMutating,