@hed-hog/lms 0.0.353 → 0.0.354
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.
- package/dist/course/course-audio-transcription.service.d.ts +29 -0
- package/dist/course/course-audio-transcription.service.d.ts.map +1 -0
- package/dist/course/course-audio-transcription.service.js +291 -0
- package/dist/course/course-audio-transcription.service.js.map +1 -0
- package/dist/course/course-lesson.controller.d.ts +10 -0
- package/dist/course/course-lesson.controller.d.ts.map +1 -0
- package/dist/course/course-lesson.controller.js +62 -0
- package/dist/course/course-lesson.controller.js.map +1 -0
- package/dist/course/course-structure.controller.d.ts +41 -15
- package/dist/course/course-structure.controller.d.ts.map +1 -1
- package/dist/course/course-structure.controller.js +50 -6
- package/dist/course/course-structure.controller.js.map +1 -1
- package/dist/course/course-structure.service.d.ts +50 -15
- package/dist/course/course-structure.service.d.ts.map +1 -1
- package/dist/course/course-structure.service.js +238 -73
- package/dist/course/course-structure.service.js.map +1 -1
- package/dist/course/course-video-conversion.service.d.ts +20 -2
- package/dist/course/course-video-conversion.service.d.ts.map +1 -1
- package/dist/course/course-video-conversion.service.js +730 -10
- package/dist/course/course-video-conversion.service.js.map +1 -1
- package/dist/course/course.controller.d.ts +24 -8
- package/dist/course/course.controller.d.ts.map +1 -1
- package/dist/course/course.module.d.ts.map +1 -1
- package/dist/course/course.module.js +5 -3
- package/dist/course/course.module.js.map +1 -1
- package/dist/course/course.service.d.ts +24 -8
- package/dist/course/course.service.d.ts.map +1 -1
- package/dist/course/course.service.js +112 -176
- package/dist/course/course.service.js.map +1 -1
- package/dist/course/dto/create-course-lesson-frame.dto.d.ts +5 -0
- package/dist/course/dto/create-course-lesson-frame.dto.d.ts.map +1 -0
- package/dist/course/dto/create-course-lesson-frame.dto.js +30 -0
- package/dist/course/dto/create-course-lesson-frame.dto.js.map +1 -0
- package/dist/course/dto/create-course-structure-lesson.dto.d.ts +4 -2
- package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
- package/dist/course/dto/create-course-structure-lesson.dto.js +10 -3
- package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
- package/dist/course/dto/create-course.dto.d.ts +1 -1
- package/dist/course/dto/create-course.dto.d.ts.map +1 -1
- package/dist/course/dto/create-course.dto.js +6 -6
- package/dist/course/dto/create-course.dto.js.map +1 -1
- package/dist/course/dto/update-course-lesson-frame.dto.d.ts +5 -0
- package/dist/course/dto/update-course-lesson-frame.dto.d.ts.map +1 -0
- package/dist/course/dto/update-course-lesson-frame.dto.js +32 -0
- package/dist/course/dto/update-course-lesson-frame.dto.js.map +1 -0
- package/dist/course/dto/update-course-resources.dto.d.ts +4 -2
- package/dist/course/dto/update-course-resources.dto.d.ts.map +1 -1
- package/dist/course/dto/update-course-resources.dto.js +10 -3
- package/dist/course/dto/update-course-resources.dto.js.map +1 -1
- package/dist/course/dto/update-transcription-segments.dto.d.ts +10 -0
- package/dist/course/dto/update-transcription-segments.dto.d.ts.map +1 -0
- package/dist/course/dto/update-transcription-segments.dto.js +38 -0
- package/dist/course/dto/update-transcription-segments.dto.js.map +1 -0
- package/dist/course/lms-setting.controller.d.ts +13 -0
- package/dist/course/lms-setting.controller.d.ts.map +1 -0
- package/dist/course/lms-setting.controller.js +53 -0
- package/dist/course/lms-setting.controller.js.map +1 -0
- package/dist/enterprise/training/training-admin.service.d.ts.map +1 -1
- package/dist/enterprise/training/training-admin.service.js +74 -33
- package/dist/enterprise/training/training-admin.service.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lms.module.d.ts.map +1 -1
- package/dist/lms.module.js +6 -0
- package/dist/lms.module.js.map +1 -1
- package/hedhog/data/route.yaml +63 -0
- package/hedhog/data/setting_group.yaml +76 -0
- package/hedhog/frontend/app/courses/[id]/_components/CourseCertificateCard.tsx.ejs +3 -0
- package/hedhog/frontend/app/courses/[id]/_components/CourseClassificationCard.tsx.ejs +10 -1
- package/hedhog/frontend/app/courses/[id]/_components/CourseContentCard.tsx.ejs +7 -2
- package/hedhog/frontend/app/courses/[id]/_components/CourseDangerZoneCard.tsx.ejs +5 -2
- package/hedhog/frontend/app/courses/[id]/_components/CourseFlagsCard.tsx.ejs +7 -1
- package/hedhog/frontend/app/courses/[id]/_components/CourseMediaCard.tsx.ejs +3 -0
- package/hedhog/frontend/app/courses/[id]/_components/CourseMultiEntityPicker.tsx.ejs +95 -50
- package/hedhog/frontend/app/courses/[id]/_components/CourseRelationsCard.tsx.ejs +11 -36
- package/hedhog/frontend/app/courses/[id]/_components/CourseSectionCard.tsx.ejs +19 -5
- package/hedhog/frontend/app/courses/[id]/_components/course-edit-types.ts.ejs +1 -1
- package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +30 -10
- package/hedhog/frontend/app/courses/[id]/structure/_components/CourseInstructorsSummaryCard.tsx.ejs +95 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +29 -11
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-panel.tsx.ejs +42 -31
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +10 -1
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +51 -9
- package/hedhog/frontend/app/courses/[id]/structure/_components/drag-handle.tsx.ejs +25 -22
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +12 -9
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +315 -229
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +3220 -534
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +20 -15
- package/hedhog/frontend/app/courses/[id]/structure/_components/icon-action-tooltip.tsx.ejs +35 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/multi-select-bar.tsx.ejs +76 -67
- package/hedhog/frontend/app/courses/[id]/structure/_components/search-filter.tsx.ejs +13 -10
- package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +18 -16
- package/hedhog/frontend/app/courses/[id]/structure/_components/sortable-tree-row.tsx.ejs +6 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-context-menu.tsx.ejs +1 -1
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs +23 -11
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-helpers.ts.ejs +48 -9
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-course.tsx.ejs +11 -2
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +121 -15
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +69 -14
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row.tsx.ejs +11 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +31 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +32 -6
- package/hedhog/frontend/app/courses/[id]/structure/_data/services/course-structure.service.ts.ejs +57 -3
- package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +46 -6
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +14 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-lesson-audio-files.ts.ejs +23 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-lms-settings-query.ts.ejs +34 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-transcription-segments.ts.ejs +76 -0
- package/hedhog/frontend/messages/en.json +39 -3
- package/hedhog/frontend/messages/pt.json +39 -3
- package/hedhog/table/course.yaml +8 -0
- package/hedhog/table/course_lesson_file.yaml +12 -4
- package/hedhog/table/course_lesson_transcription_segment.yaml +22 -0
- package/hedhog/table/course_lesson_video_frame.yaml +25 -0
- package/package.json +9 -9
- package/src/course/course-audio-transcription.service.ts +393 -0
- package/src/course/course-lesson.controller.ts +28 -0
- package/src/course/course-structure.controller.ts +49 -3
- package/src/course/course-structure.service.ts +294 -32
- package/src/course/course-video-conversion.service.ts +972 -6
- package/src/course/course.module.ts +5 -3
- package/src/course/course.service.ts +87 -139
- package/src/course/dto/create-course-lesson-frame.dto.ts +14 -0
- package/src/course/dto/create-course-structure-lesson.dto.ts +19 -3
- package/src/course/dto/create-course.dto.ts +5 -5
- package/src/course/dto/update-course-lesson-frame.dto.ts +16 -0
- package/src/course/dto/update-course-resources.dto.ts +18 -3
- package/src/course/dto/update-transcription-segments.dto.ts +20 -0
- package/src/course/lms-setting.controller.ts +30 -0
- package/src/enterprise/training/training-admin.service.ts +77 -24
- package/src/index.ts +2 -0
- package/src/lms.module.ts +6 -0
- 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
|
|
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({
|
|
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=
|
|
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=
|
|
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-
|
|
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({
|
|
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
|
-
<
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
<
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
<
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
<
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
<
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
53
|
-
<div
|
|
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
|
-
|
|
41
|
-
|
|
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
|
|
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
|
|
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,
|