@hed-hog/lms 0.0.364 → 0.0.366
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/bitcode-wallet/bitcode-wallet.service.d.ts +1 -0
- package/dist/bitcode-wallet/bitcode-wallet.service.d.ts.map +1 -1
- package/dist/bitcode-wallet/bitcode-wallet.service.js +22 -3
- package/dist/bitcode-wallet/bitcode-wallet.service.js.map +1 -1
- package/dist/class-group/class-group.controller.d.ts +1 -0
- package/dist/class-group/class-group.controller.d.ts.map +1 -1
- package/dist/class-group/class-group.service.d.ts +1 -0
- package/dist/class-group/class-group.service.d.ts.map +1 -1
- package/dist/course/course-export-scorm12-worker.service.d.ts +21 -0
- package/dist/course/course-export-scorm12-worker.service.d.ts.map +1 -0
- package/dist/course/course-export-scorm12-worker.service.js +109 -0
- package/dist/course/course-export-scorm12-worker.service.js.map +1 -0
- package/dist/course/course-export-scorm12.service.d.ts +42 -0
- package/dist/course/course-export-scorm12.service.d.ts.map +1 -0
- package/dist/course/course-export-scorm12.service.js +628 -0
- package/dist/course/course-export-scorm12.service.js.map +1 -0
- package/dist/course/course-export.service.d.ts +84 -0
- package/dist/course/course-export.service.d.ts.map +1 -0
- package/dist/course/course-export.service.js +237 -0
- package/dist/course/course-export.service.js.map +1 -0
- package/dist/course/course-structure.controller.d.ts +20 -10
- package/dist/course/course-structure.controller.d.ts.map +1 -1
- package/dist/course/course-structure.controller.js +20 -4
- package/dist/course/course-structure.controller.js.map +1 -1
- package/dist/course/course-structure.service.d.ts +12 -4
- package/dist/course/course-structure.service.d.ts.map +1 -1
- package/dist/course/course-structure.service.js +98 -23
- package/dist/course/course-structure.service.js.map +1 -1
- package/dist/course/course-video-agent-pipeline.service.d.ts +70 -0
- package/dist/course/course-video-agent-pipeline.service.d.ts.map +1 -0
- package/dist/course/course-video-agent-pipeline.service.js +398 -0
- package/dist/course/course-video-agent-pipeline.service.js.map +1 -0
- package/dist/course/course-video-hls.service.d.ts +71 -0
- package/dist/course/course-video-hls.service.d.ts.map +1 -0
- package/dist/course/course-video-hls.service.js +784 -0
- package/dist/course/course-video-hls.service.js.map +1 -0
- package/dist/course/course.controller.d.ts +47 -13
- package/dist/course/course.controller.d.ts.map +1 -1
- package/dist/course/course.controller.js +40 -26
- package/dist/course/course.controller.js.map +1 -1
- package/dist/course/course.mcp-tools.js +1 -1
- package/dist/course/course.mcp-tools.js.map +1 -1
- package/dist/course/course.module.d.ts.map +1 -1
- package/dist/course/course.module.js +16 -0
- package/dist/course/course.module.js.map +1 -1
- package/dist/course/course.service.d.ts +8 -9
- package/dist/course/course.service.d.ts.map +1 -1
- package/dist/course/course.service.js +93 -50
- package/dist/course/course.service.js.map +1 -1
- package/dist/course/dto/cleanup-course-storage.dto.d.ts +1 -1
- package/dist/course/dto/cleanup-course-storage.dto.d.ts.map +1 -1
- package/dist/course/dto/cleanup-course-storage.dto.js +1 -0
- package/dist/course/dto/cleanup-course-storage.dto.js.map +1 -1
- package/dist/course/dto/cleanup-upload-history.dto.d.ts +1 -1
- package/dist/course/dto/cleanup-upload-history.dto.d.ts.map +1 -1
- package/dist/course/dto/cleanup-upload-history.dto.js +1 -1
- package/dist/course/dto/cleanup-upload-history.dto.js.map +1 -1
- package/dist/course/dto/create-course-bulk-job.dto.d.ts +2 -1
- package/dist/course/dto/create-course-bulk-job.dto.d.ts.map +1 -1
- package/dist/course/dto/create-course-bulk-job.dto.js +6 -1
- package/dist/course/dto/create-course-bulk-job.dto.js.map +1 -1
- package/dist/course/dto/create-course-export.dto.d.ts +14 -0
- package/dist/course/dto/create-course-export.dto.d.ts.map +1 -0
- package/dist/course/dto/create-course-export.dto.js +71 -0
- package/dist/course/dto/create-course-export.dto.js.map +1 -0
- package/dist/course/dto/create-course-structure-lesson.dto.d.ts +2 -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 +3 -2
- package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
- package/dist/course/ffmpeg.util.d.ts +10 -0
- package/dist/course/ffmpeg.util.d.ts.map +1 -0
- package/dist/course/ffmpeg.util.js +79 -0
- package/dist/course/ffmpeg.util.js.map +1 -0
- package/dist/course/lms-bulk-upload-automation.service.d.ts +18 -1
- package/dist/course/lms-bulk-upload-automation.service.d.ts.map +1 -1
- package/dist/course/lms-bulk-upload-automation.service.js +106 -8
- package/dist/course/lms-bulk-upload-automation.service.js.map +1 -1
- package/dist/course/lms-bulk-upload-infra.service.d.ts +1 -0
- package/dist/course/lms-bulk-upload-infra.service.d.ts.map +1 -1
- package/dist/course/lms-bulk-upload-infra.service.js +32 -8
- package/dist/course/lms-bulk-upload-infra.service.js.map +1 -1
- package/dist/course/lms-bulk-upload.controller.d.ts +30 -3
- package/dist/course/lms-bulk-upload.controller.d.ts.map +1 -1
- package/dist/course/lms-bulk-upload.controller.js +43 -2
- package/dist/course/lms-bulk-upload.controller.js.map +1 -1
- package/dist/course/lms-bulk-upload.service.d.ts +11 -0
- package/dist/course/lms-bulk-upload.service.d.ts.map +1 -1
- package/dist/course/lms-bulk-upload.service.js +59 -6
- package/dist/course/lms-bulk-upload.service.js.map +1 -1
- package/dist/course/lms-setting.controller.d.ts +2 -1
- package/dist/course/lms-setting.controller.d.ts.map +1 -1
- package/dist/course/lms-setting.controller.js +4 -2
- package/dist/course/lms-setting.controller.js.map +1 -1
- package/dist/course/scorm12-schemas.d.ts +4 -0
- package/dist/course/scorm12-schemas.d.ts.map +1 -0
- package/dist/course/scorm12-schemas.js +9 -0
- package/dist/course/scorm12-schemas.js.map +1 -0
- package/dist/enterprise/training/training-admin.controller.d.ts +2 -0
- package/dist/enterprise/training/training-admin.controller.d.ts.map +1 -1
- package/dist/enterprise/training/training-admin.service.d.ts +2 -0
- package/dist/enterprise/training/training-admin.service.d.ts.map +1 -1
- package/dist/enterprise/training/training-student.service.d.ts +51 -0
- package/dist/enterprise/training/training-student.service.d.ts.map +1 -1
- package/dist/enterprise/training/training-student.service.js +217 -4
- package/dist/enterprise/training/training-student.service.js.map +1 -1
- package/dist/evaluation/evaluation.service.d.ts +18 -0
- package/dist/evaluation/evaluation.service.d.ts.map +1 -1
- package/dist/evaluation/evaluation.service.js +125 -0
- package/dist/evaluation/evaluation.service.js.map +1 -1
- package/dist/exam/dto/create-standalone-question.dto.d.ts +12 -0
- package/dist/exam/dto/create-standalone-question.dto.d.ts.map +1 -0
- package/dist/exam/dto/create-standalone-question.dto.js +70 -0
- package/dist/exam/dto/create-standalone-question.dto.js.map +1 -0
- package/dist/exam/exam.module.d.ts.map +1 -1
- package/dist/exam/exam.module.js +2 -1
- package/dist/exam/exam.module.js.map +1 -1
- package/dist/exam/exam.service.d.ts +21 -0
- package/dist/exam/exam.service.d.ts.map +1 -1
- package/dist/exam/exam.service.js +80 -0
- package/dist/exam/exam.service.js.map +1 -1
- package/dist/exam/question.controller.d.ts +27 -0
- package/dist/exam/question.controller.d.ts.map +1 -0
- package/dist/exam/question.controller.js +53 -0
- package/dist/exam/question.controller.js.map +1 -0
- package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts +4 -0
- package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts.map +1 -1
- package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js +161 -25
- package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js.map +1 -1
- package/dist/libraries/lms/tsconfig.tsbuildinfo +1 -1
- package/dist/lms-commerce-access.subscriber.d.ts +11 -0
- package/dist/lms-commerce-access.subscriber.d.ts.map +1 -0
- package/dist/lms-commerce-access.subscriber.js +74 -0
- package/dist/lms-commerce-access.subscriber.js.map +1 -0
- package/dist/lms.module.d.ts.map +1 -1
- package/dist/lms.module.js +16 -5
- package/dist/lms.module.js.map +1 -1
- package/dist/platforma/dto/heartbeat.dto.d.ts +9 -0
- package/dist/platforma/dto/heartbeat.dto.d.ts.map +1 -0
- package/dist/platforma/dto/heartbeat.dto.js +50 -0
- package/dist/platforma/dto/heartbeat.dto.js.map +1 -0
- package/dist/platforma/handlers/emit-certificate.handler.d.ts +27 -0
- package/dist/platforma/handlers/emit-certificate.handler.d.ts.map +1 -0
- package/dist/platforma/handlers/emit-certificate.handler.js +117 -0
- package/dist/platforma/handlers/emit-certificate.handler.js.map +1 -0
- package/dist/platforma/handlers/lesson-heartbeat.handler.d.ts +31 -0
- package/dist/platforma/handlers/lesson-heartbeat.handler.d.ts.map +1 -0
- package/dist/platforma/handlers/lesson-heartbeat.handler.js +281 -0
- package/dist/platforma/handlers/lesson-heartbeat.handler.js.map +1 -0
- package/dist/platforma/platforma-heartbeat.service.d.ts +10 -0
- package/dist/platforma/platforma-heartbeat.service.d.ts.map +1 -0
- package/dist/platforma/platforma-heartbeat.service.js +50 -0
- package/dist/platforma/platforma-heartbeat.service.js.map +1 -0
- package/dist/platforma/platforma-performance.service.d.ts +121 -0
- package/dist/platforma/platforma-performance.service.d.ts.map +1 -0
- package/dist/platforma/platforma-performance.service.js +500 -0
- package/dist/platforma/platforma-performance.service.js.map +1 -0
- package/dist/platforma/platforma-search.service.d.ts +21 -0
- package/dist/platforma/platforma-search.service.d.ts.map +1 -0
- package/dist/platforma/platforma-search.service.js +64 -0
- package/dist/platforma/platforma-search.service.js.map +1 -0
- package/dist/platforma/platforma-video.service.d.ts +39 -0
- package/dist/platforma/platforma-video.service.d.ts.map +1 -0
- package/dist/platforma/platforma-video.service.js +301 -0
- package/dist/platforma/platforma-video.service.js.map +1 -0
- package/dist/platforma/platforma.controller.d.ts +209 -1
- package/dist/platforma/platforma.controller.d.ts.map +1 -1
- package/dist/platforma/platforma.controller.js +208 -2
- package/dist/platforma/platforma.controller.js.map +1 -1
- package/dist/realtime/lms-realtime.controller.d.ts +2 -0
- package/dist/realtime/lms-realtime.controller.d.ts.map +1 -1
- package/dist/realtime/lms-realtime.controller.js +31 -0
- package/dist/realtime/lms-realtime.controller.js.map +1 -1
- package/dist/realtime/lms-realtime.service.d.ts +1 -1
- package/dist/realtime/lms-realtime.service.d.ts.map +1 -1
- package/dist/realtime/lms-realtime.service.js.map +1 -1
- package/dist/student-xp/dto/grant-skill-card-xp.dto.d.ts +5 -0
- package/dist/student-xp/dto/grant-skill-card-xp.dto.d.ts.map +1 -0
- package/dist/student-xp/dto/grant-skill-card-xp.dto.js +26 -0
- package/dist/student-xp/dto/grant-skill-card-xp.dto.js.map +1 -0
- package/dist/student-xp/student-xp.controller.d.ts +15 -0
- package/dist/student-xp/student-xp.controller.d.ts.map +1 -1
- package/dist/student-xp/student-xp.controller.js +24 -0
- package/dist/student-xp/student-xp.controller.js.map +1 -1
- package/dist/student-xp/student-xp.service.d.ts +16 -0
- package/dist/student-xp/student-xp.service.d.ts.map +1 -1
- package/dist/student-xp/student-xp.service.js +51 -1
- package/dist/student-xp/student-xp.service.js.map +1 -1
- package/hedhog/data/evaluation_topic.yaml +17 -0
- package/hedhog/data/menu.yaml +0 -17
- package/hedhog/data/queue_definition.yaml +48 -0
- package/hedhog/data/route.yaml +94 -124
- package/hedhog/data/setting_group.yaml +19 -19
- package/hedhog/frontend/app/bulk-upload-sessions/page.tsx.ejs +337 -41
- package/hedhog/frontend/app/certificates/models/page.tsx.ejs +182 -29
- package/hedhog/frontend/app/classes/_components/classes-calendar-view.tsx.ejs +277 -0
- package/hedhog/frontend/app/classes/page.tsx.ejs +127 -20
- package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +69 -4
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-export-sheet.tsx.ejs +420 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-exports-tab.tsx.ejs +308 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-overview-tab.tsx.ejs +158 -45
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-xp-overview-tab.tsx.ejs +13 -13
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson-xp-tab.tsx.ejs +11 -23
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +51 -63
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-panel.tsx.ejs +8 -3
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +31 -8
- package/hedhog/frontend/app/courses/[id]/structure/_components/drag-overlay.tsx.ejs +16 -9
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +201 -401
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +378 -690
- package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +1 -2
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +3 -9
- package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +1 -1
- package/hedhog/frontend/app/courses/[id]/structure/_components/xp-premium-pills.tsx.ejs +1 -8
- package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +6 -10
- package/hedhog/frontend/app/courses/[id]/structure/_data/services/course-structure.service.ts.ejs +49 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +4 -3
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-content-overview.ts.ejs +2 -1
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-exports.ts.ejs +106 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +28 -1
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-lms-settings-query.ts.ejs +0 -2
- package/hedhog/frontend/app/courses/page.tsx.ejs +85 -9
- package/hedhog/frontend/app/enterprise/[id]/page.tsx.ejs +6 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-classes-calendar-tab.tsx.ejs +264 -0
- package/hedhog/frontend/app/enterprise/page.tsx.ejs +104 -47
- package/hedhog/frontend/app/exams/page.tsx.ejs +38 -4
- package/hedhog/frontend/app/instructors/page.tsx.ejs +87 -46
- package/hedhog/frontend/app/paths/page.tsx.ejs +38 -4
- package/hedhog/frontend/app/training/page.tsx.ejs +38 -4
- package/hedhog/frontend/messages/en.json +44 -28
- package/hedhog/frontend/messages/pt.json +47 -29
- package/hedhog/table/course_enrollment.yaml +3 -0
- package/hedhog/table/course_export.yaml +62 -0
- package/hedhog/table/lesson_view_event.yaml +66 -0
- package/package.json +14 -9
- package/src/bitcode-wallet/bitcode-wallet.service.ts +43 -4
- package/src/course/course-export-scorm12-worker.service.ts +124 -0
- package/src/course/course-export-scorm12.service.ts +668 -0
- package/src/course/course-export.service.ts +280 -0
- package/src/course/course-structure.controller.ts +16 -2
- package/src/course/course-structure.service.ts +100 -7
- package/src/course/course-video-agent-pipeline.service.ts +471 -0
- package/src/course/course-video-hls.service.ts +966 -0
- package/src/course/course.controller.ts +33 -19
- package/src/course/course.mcp-tools.ts +1 -1
- package/src/course/course.module.ts +16 -0
- package/src/course/course.service.ts +119 -61
- package/src/course/dto/cleanup-course-storage.dto.ts +1 -0
- package/src/course/dto/cleanup-upload-history.dto.ts +1 -1
- package/src/course/dto/create-course-bulk-job.dto.ts +7 -3
- package/src/course/dto/create-course-export.dto.ts +56 -0
- package/src/course/dto/create-course-structure-lesson.dto.ts +4 -3
- package/src/course/ffmpeg.util.ts +65 -0
- package/src/course/lms-bulk-upload-automation.service.ts +156 -6
- package/src/course/lms-bulk-upload-infra.service.ts +39 -6
- package/src/course/lms-bulk-upload.controller.ts +32 -2
- package/src/course/lms-bulk-upload.service.ts +70 -7
- package/src/course/lms-setting.controller.ts +4 -2
- package/src/course/scorm12-schemas.ts +9 -0
- package/src/enterprise/training/training-student.service.ts +221 -2
- package/src/evaluation/evaluation.service.ts +123 -0
- package/src/exam/dto/create-standalone-question.dto.ts +66 -0
- package/src/exam/exam.module.ts +2 -1
- package/src/exam/exam.service.ts +86 -0
- package/src/exam/question.controller.ts +28 -0
- package/src/lesson-xp-map/lesson-xp-ai-calculation.service.ts +205 -31
- package/src/lms-commerce-access.subscriber.ts +88 -0
- package/src/lms.module.ts +16 -5
- package/src/platforma/dto/heartbeat.dto.ts +30 -0
- package/src/platforma/handlers/emit-certificate.handler.ts +117 -0
- package/src/platforma/handlers/lesson-heartbeat.handler.ts +343 -0
- package/src/platforma/platforma-heartbeat.service.ts +33 -0
- package/src/platforma/platforma-performance.service.ts +606 -0
- package/src/platforma/platforma-search.service.ts +48 -0
- package/src/platforma/platforma-video.service.ts +346 -0
- package/src/platforma/platforma.controller.ts +137 -1
- package/src/platforma/platforma.service.ts +268 -268
- package/src/realtime/lms-realtime.controller.ts +27 -1
- package/src/realtime/lms-realtime.service.ts +2 -1
- package/src/student-xp/dto/grant-skill-card-xp.dto.ts +10 -0
- package/src/student-xp/student-xp.controller.ts +18 -2
- package/src/student-xp/student-xp.service.ts +84 -2
- package/hedhog/data/video_resolution_profile.yaml +0 -7
- package/hedhog/frontend/app/video-resolution-profiles/page.tsx.ejs +0 -607
- package/hedhog/table/course_video_resolution_profile.yaml +0 -22
- package/hedhog/table/video_resolution_profile.yaml +0 -18
- package/src/video-resolution-profile/dto/create-video-resolution-profile.dto.ts +0 -16
- package/src/video-resolution-profile/dto/update-video-resolution-profile.dto.ts +0 -16
- package/src/video-resolution-profile/video-resolution-profile.controller.ts +0 -62
- package/src/video-resolution-profile/video-resolution-profile.mcp-tools.ts +0 -128
- package/src/video-resolution-profile/video-resolution-profile.module.ts +0 -13
- package/src/video-resolution-profile/video-resolution-profile.service.ts +0 -117
|
@@ -31,7 +31,6 @@ import { toast } from 'sonner';
|
|
|
31
31
|
import { z } from 'zod';
|
|
32
32
|
|
|
33
33
|
import { createDefaultTemplate } from '@/app/(app)/(libraries)/lms/_lib/editor/types';
|
|
34
|
-
import { FfmpegParamsEditor } from '@/components/ffmpeg-params-editor';
|
|
35
34
|
import { FileTypeIcon } from '@/components/file-type-icon';
|
|
36
35
|
import { RichTextEditor } from '@/components/rich-text-editor';
|
|
37
36
|
import {
|
|
@@ -46,6 +45,15 @@ import {
|
|
|
46
45
|
} from '@/components/ui/alert-dialog';
|
|
47
46
|
import { Badge } from '@/components/ui/badge';
|
|
48
47
|
import { Button } from '@/components/ui/button';
|
|
48
|
+
import { Checkbox } from '@/components/ui/checkbox';
|
|
49
|
+
import {
|
|
50
|
+
Dialog,
|
|
51
|
+
DialogContent,
|
|
52
|
+
DialogDescription,
|
|
53
|
+
DialogFooter,
|
|
54
|
+
DialogHeader,
|
|
55
|
+
DialogTitle,
|
|
56
|
+
} from '@/components/ui/dialog';
|
|
49
57
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
50
58
|
import {
|
|
51
59
|
DropdownMenu,
|
|
@@ -108,6 +116,8 @@ import {
|
|
|
108
116
|
import { useCreateSessionMutation } from '../_data/use-course-structure-mutations';
|
|
109
117
|
import { courseStructureQueryKey } from '../_data/use-course-structure-query';
|
|
110
118
|
import { useLmsSettingsQuery } from '../_data/use-lms-settings-query';
|
|
119
|
+
import { CourseExportSheet } from './course-export-sheet';
|
|
120
|
+
import { CourseExportsTab } from './course-exports-tab';
|
|
111
121
|
import { CourseOperationsTab } from './course-operations-tab';
|
|
112
122
|
import { CourseOverviewTab } from './course-overview-tab';
|
|
113
123
|
import { CourseXpOverviewTab } from './course-xp-overview-tab';
|
|
@@ -190,13 +200,6 @@ type ApiQueuedDeleteResponse = {
|
|
|
190
200
|
>;
|
|
191
201
|
};
|
|
192
202
|
|
|
193
|
-
type VideoProfileOption = {
|
|
194
|
-
id: number;
|
|
195
|
-
name: string;
|
|
196
|
-
ffmpeg_params: string;
|
|
197
|
-
status: string;
|
|
198
|
-
};
|
|
199
|
-
|
|
200
203
|
type ApiCategory = { id: number; slug: string; name: string };
|
|
201
204
|
type ApiCategoryDetail = {
|
|
202
205
|
id: number;
|
|
@@ -498,7 +501,25 @@ function EditorCourseSkeleton() {
|
|
|
498
501
|
|
|
499
502
|
// ── Component ─────────────────────────────────────────────────────────────────
|
|
500
503
|
|
|
501
|
-
|
|
504
|
+
const COURSE_TABS = [
|
|
505
|
+
'visao-geral',
|
|
506
|
+
'detalhes',
|
|
507
|
+
'xp',
|
|
508
|
+
'midia',
|
|
509
|
+
'recursos',
|
|
510
|
+
'operacoes',
|
|
511
|
+
'publicacao',
|
|
512
|
+
'extra',
|
|
513
|
+
'exportacoes',
|
|
514
|
+
] as const;
|
|
515
|
+
type CourseTab = (typeof COURSE_TABS)[number];
|
|
516
|
+
|
|
517
|
+
interface EditorCourseProps {
|
|
518
|
+
defaultTab?: string;
|
|
519
|
+
onTabChange?: (tab: string) => void;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
export function EditorCourse({ defaultTab, onTabChange }: EditorCourseProps) {
|
|
502
523
|
const courseId = useStructureStore((s) => s.courseId);
|
|
503
524
|
const course = useStructureStore((s) => s.course);
|
|
504
525
|
const sessions = useStructureStore((s) => s.sessions);
|
|
@@ -518,9 +539,21 @@ export function EditorCourse() {
|
|
|
518
539
|
const lmsSettings = useLmsSettingsQuery();
|
|
519
540
|
|
|
520
541
|
// ── UI state ────────────────────────────────────────────────────────────────
|
|
521
|
-
const [activeTab, setActiveTab] = useState(
|
|
522
|
-
|
|
542
|
+
const [activeTab, setActiveTab] = useState<CourseTab>(() =>
|
|
543
|
+
defaultTab && (COURSE_TABS as readonly string[]).includes(defaultTab)
|
|
544
|
+
? (defaultTab as CourseTab)
|
|
545
|
+
: 'visao-geral'
|
|
546
|
+
);
|
|
547
|
+
const [exportSheetOpen, setExportSheetOpen] = useState(false);
|
|
548
|
+
const [pendingBulkJob, setPendingBulkJob] = useState<'transcription' | 'xp_recalculation' | 'video_processing' | null>(null);
|
|
523
549
|
const [isBulkJobPending, setIsBulkJobPending] = useState(false);
|
|
550
|
+
const [videoProcessingStats, setVideoProcessingStats] = useState<{
|
|
551
|
+
withOriginal: number;
|
|
552
|
+
withoutOriginal: number;
|
|
553
|
+
alreadyProcessed: number;
|
|
554
|
+
} | null>(null);
|
|
555
|
+
const [isFetchingVideoStats, setIsFetchingVideoStats] = useState(false);
|
|
556
|
+
const [reprocessAlreadyProcessed, setReprocessAlreadyProcessed] = useState(false);
|
|
524
557
|
const [categoryEditSheetOpen, setCategoryEditSheetOpen] = useState(false);
|
|
525
558
|
const [editingCategoryId, setEditingCategoryId] = useState<number | null>(
|
|
526
559
|
null
|
|
@@ -569,18 +602,6 @@ export function EditorCourse() {
|
|
|
569
602
|
const [isUploadingResources, setIsUploadingResources] = useState(false);
|
|
570
603
|
const [isSavingResources, setIsSavingResources] = useState(false);
|
|
571
604
|
const resourcesInputRef = useRef<HTMLInputElement>(null);
|
|
572
|
-
const [linkedProfileIds, setLinkedProfileIds] = useState<number[]>([]);
|
|
573
|
-
const [videoProfilePickerResetKey, setVideoProfilePickerResetKey] =
|
|
574
|
-
useState(0);
|
|
575
|
-
const [videoProfileEditSheetOpen, setVideoProfileEditSheetOpen] =
|
|
576
|
-
useState(false);
|
|
577
|
-
const [editingVideoProfileId, setEditingVideoProfileId] = useState<
|
|
578
|
-
number | null
|
|
579
|
-
>(null);
|
|
580
|
-
const [editingVideoProfileName, setEditingVideoProfileName] = useState('');
|
|
581
|
-
const [editingVideoProfileParams, setEditingVideoProfileParams] =
|
|
582
|
-
useState('');
|
|
583
|
-
const [savingVideoProfileEdit, setSavingVideoProfileEdit] = useState(false);
|
|
584
605
|
|
|
585
606
|
// ── Queries ─────────────────────────────────────────────────────────────────
|
|
586
607
|
const {
|
|
@@ -668,22 +689,6 @@ export function EditorCourse() {
|
|
|
668
689
|
initialData: { data: [] },
|
|
669
690
|
});
|
|
670
691
|
|
|
671
|
-
const {
|
|
672
|
-
data: allVideoProfiles = [],
|
|
673
|
-
refetch: refetchVideoProfiles,
|
|
674
|
-
isFetching: isFetchingVideoProfiles,
|
|
675
|
-
} = useQuery<VideoProfileOption[]>({
|
|
676
|
-
queryKey: ['lms-video-resolution-profiles-all'],
|
|
677
|
-
queryFn: async () => {
|
|
678
|
-
const response = await request<VideoProfileOption[]>({
|
|
679
|
-
url: '/lms/video-resolution-profiles/all',
|
|
680
|
-
method: 'GET',
|
|
681
|
-
});
|
|
682
|
-
return response.data;
|
|
683
|
-
},
|
|
684
|
-
initialData: [],
|
|
685
|
-
});
|
|
686
|
-
|
|
687
692
|
const { data: operationsConfig } = useQuery<{ isAvailable: boolean }>({
|
|
688
693
|
queryKey: ['lms-course-operations-config', courseId],
|
|
689
694
|
enabled: Boolean(courseId),
|
|
@@ -745,15 +750,6 @@ export function EditorCourse() {
|
|
|
745
750
|
return data;
|
|
746
751
|
},
|
|
747
752
|
onSuccess: (data) => {
|
|
748
|
-
if (courseId) {
|
|
749
|
-
void request({
|
|
750
|
-
url: `/lms/courses/${courseId}/video-resolution-profiles/sync`,
|
|
751
|
-
method: 'POST',
|
|
752
|
-
data: { profileIds: linkedProfileIds },
|
|
753
|
-
}).catch(() => {
|
|
754
|
-
toast.error('Erro ao sincronizar perfis de vídeo.');
|
|
755
|
-
});
|
|
756
|
-
}
|
|
757
753
|
setPersistedCertificateModel(data.modeloCertificado || '');
|
|
758
754
|
updateCourseInStore({
|
|
759
755
|
code: data.code,
|
|
@@ -897,14 +893,6 @@ export function EditorCourse() {
|
|
|
897
893
|
);
|
|
898
894
|
}, [certificateTemplateData, createdTemplateOptions]);
|
|
899
895
|
|
|
900
|
-
const availableVideoProfiles = useMemo(
|
|
901
|
-
() =>
|
|
902
|
-
allVideoProfiles.filter(
|
|
903
|
-
(profile) => !linkedProfileIds.includes(profile.id)
|
|
904
|
-
),
|
|
905
|
-
[allVideoProfiles, linkedProfileIds]
|
|
906
|
-
);
|
|
907
|
-
|
|
908
896
|
// ── Structural stats ─────────────────────────────────────────────────────────
|
|
909
897
|
const totalMinutes = lessons.reduce((sum, l) => sum + l.duration, 0);
|
|
910
898
|
const hours = Math.floor(totalMinutes / 60);
|
|
@@ -915,9 +903,6 @@ export function EditorCourse() {
|
|
|
915
903
|
).length;
|
|
916
904
|
|
|
917
905
|
const formValues = form.watch();
|
|
918
|
-
const showVideoTab =
|
|
919
|
-
formValues.tipoOferta === 'sob_demanda' ||
|
|
920
|
-
formValues.tipoOferta === 'hibrido';
|
|
921
906
|
const descriptionText = String(formValues.descricaoPublica ?? '')
|
|
922
907
|
.replace(/<[^>]+>/g, '')
|
|
923
908
|
.trim();
|
|
@@ -1049,32 +1034,6 @@ export function EditorCourse() {
|
|
|
1049
1034
|
};
|
|
1050
1035
|
}, [courseId, request]);
|
|
1051
1036
|
|
|
1052
|
-
useEffect(() => {
|
|
1053
|
-
if (!showVideoTab && activeTab === 'videos') {
|
|
1054
|
-
setActiveTab('detalhes');
|
|
1055
|
-
}
|
|
1056
|
-
}, [showVideoTab, activeTab]);
|
|
1057
|
-
|
|
1058
|
-
useEffect(() => {
|
|
1059
|
-
if (!courseId) return;
|
|
1060
|
-
let active = true;
|
|
1061
|
-
void request<VideoProfileOption[]>({
|
|
1062
|
-
url: `/lms/courses/${courseId}/video-resolution-profiles`,
|
|
1063
|
-
method: 'GET',
|
|
1064
|
-
})
|
|
1065
|
-
.then((res) => {
|
|
1066
|
-
if (!active) return;
|
|
1067
|
-
setLinkedProfileIds((res.data ?? []).map((p) => p.id));
|
|
1068
|
-
})
|
|
1069
|
-
.catch(() => {
|
|
1070
|
-
if (!active) return;
|
|
1071
|
-
setLinkedProfileIds([]);
|
|
1072
|
-
});
|
|
1073
|
-
return () => {
|
|
1074
|
-
active = false;
|
|
1075
|
-
};
|
|
1076
|
-
}, [courseId, request]);
|
|
1077
|
-
|
|
1078
1037
|
async function syncCourseResources(nextResources: CourseResourceItem[]) {
|
|
1079
1038
|
setIsSavingResources(true);
|
|
1080
1039
|
try {
|
|
@@ -1394,38 +1353,6 @@ export function EditorCourse() {
|
|
|
1394
1353
|
}
|
|
1395
1354
|
}
|
|
1396
1355
|
|
|
1397
|
-
function handleEditVideoProfile(profileId: number) {
|
|
1398
|
-
const profile = allVideoProfiles.find((p) => p.id === profileId);
|
|
1399
|
-
if (!profile) return;
|
|
1400
|
-
setEditingVideoProfileId(profileId);
|
|
1401
|
-
setEditingVideoProfileName(profile.name);
|
|
1402
|
-
setEditingVideoProfileParams(profile.ffmpeg_params);
|
|
1403
|
-
setVideoProfileEditSheetOpen(true);
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
async function handleSaveVideoProfileEdit() {
|
|
1407
|
-
if (!editingVideoProfileId) return;
|
|
1408
|
-
try {
|
|
1409
|
-
setSavingVideoProfileEdit(true);
|
|
1410
|
-
await request({
|
|
1411
|
-
url: `/lms/video-resolution-profiles/${editingVideoProfileId}`,
|
|
1412
|
-
method: 'PATCH',
|
|
1413
|
-
data: {
|
|
1414
|
-
name: editingVideoProfileName,
|
|
1415
|
-
ffmpeg_params: editingVideoProfileParams,
|
|
1416
|
-
},
|
|
1417
|
-
});
|
|
1418
|
-
await refetchVideoProfiles();
|
|
1419
|
-
setVideoProfileEditSheetOpen(false);
|
|
1420
|
-
setEditingVideoProfileId(null);
|
|
1421
|
-
toast.success('Perfil de vídeo atualizado com sucesso.');
|
|
1422
|
-
} catch {
|
|
1423
|
-
toast.error('Erro ao atualizar o perfil de vídeo.');
|
|
1424
|
-
} finally {
|
|
1425
|
-
setSavingVideoProfileEdit(false);
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
1356
|
async function handleEditCategory(categorySlug: string) {
|
|
1430
1357
|
const category = (categoryListData?.data ?? []).find(
|
|
1431
1358
|
(item) => item.slug === categorySlug
|
|
@@ -1648,52 +1575,30 @@ export function EditorCourse() {
|
|
|
1648
1575
|
}
|
|
1649
1576
|
|
|
1650
1577
|
function onSubmit(data: CourseEditFormValues) {
|
|
1651
|
-
if (data.status === 'publicado') {
|
|
1652
|
-
const requiredProfileIds = new Set(linkedProfileIds);
|
|
1653
|
-
const invalidLesson = lessons.find((lesson) => {
|
|
1654
|
-
if (
|
|
1655
|
-
lesson.type !== 'video' ||
|
|
1656
|
-
lesson.videoProvider !== 'file_storage'
|
|
1657
|
-
) {
|
|
1658
|
-
return false;
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
|
-
if (requiredProfileIds.size === 0) {
|
|
1662
|
-
return true;
|
|
1663
|
-
}
|
|
1664
|
-
|
|
1665
|
-
const resourceTypes = new Set(lesson.resources.map((res) => res.type));
|
|
1666
|
-
return [...requiredProfileIds].some(
|
|
1667
|
-
(profileId) => !resourceTypes.has(`video_profile:${profileId}`)
|
|
1668
|
-
);
|
|
1669
|
-
});
|
|
1670
|
-
|
|
1671
|
-
if (invalidLesson) {
|
|
1672
|
-
toast.error(
|
|
1673
|
-
linkedProfileIds.length === 0
|
|
1674
|
-
? 'Configure ao menos um perfil de vídeo antes de publicar o curso.'
|
|
1675
|
-
: `A aula "${invalidLesson.title}" precisa de um vídeo para cada perfil antes da publicação.`
|
|
1676
|
-
);
|
|
1677
|
-
return;
|
|
1678
|
-
}
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
1578
|
saveCourse(data);
|
|
1682
1579
|
}
|
|
1683
1580
|
|
|
1581
|
+
function handlePublish() {
|
|
1582
|
+
const values = form.getValues();
|
|
1583
|
+
saveCourse({ ...values, status: 'publicado' });
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1684
1586
|
function onFormError(errors: Record<string, unknown>) {
|
|
1685
1587
|
const first = Object.values(errors)[0] as { message?: string } | undefined;
|
|
1686
1588
|
toast.error(first?.message ?? 'Verifique os campos obrigatórios');
|
|
1687
1589
|
}
|
|
1688
1590
|
|
|
1689
1591
|
// ── Bulk jobs ─────────────────────────────────────────────────────────────────
|
|
1690
|
-
const handleBulkJob = async (
|
|
1592
|
+
const handleBulkJob = async (
|
|
1593
|
+
jobType: 'transcription' | 'xp_recalculation' | 'video_processing',
|
|
1594
|
+
opts?: { reprocessAlreadyProcessed?: boolean },
|
|
1595
|
+
) => {
|
|
1691
1596
|
setIsBulkJobPending(true);
|
|
1692
1597
|
try {
|
|
1693
1598
|
const res = await request<{ queued: number; skipped: number }>({
|
|
1694
1599
|
url: `/lms/courses/${courseId}/structure/bulk-jobs`,
|
|
1695
1600
|
method: 'POST',
|
|
1696
|
-
data: { jobType },
|
|
1601
|
+
data: { jobType, ...opts },
|
|
1697
1602
|
});
|
|
1698
1603
|
toast.success(`${res.data?.queued ?? 0} job(s) enfileirado(s), ${res.data?.skipped ?? 0} ignorado(s).`);
|
|
1699
1604
|
} catch {
|
|
@@ -1701,6 +1606,28 @@ export function EditorCourse() {
|
|
|
1701
1606
|
} finally {
|
|
1702
1607
|
setIsBulkJobPending(false);
|
|
1703
1608
|
setPendingBulkJob(null);
|
|
1609
|
+
setVideoProcessingStats(null);
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1612
|
+
|
|
1613
|
+
const handleOpenVideoProcessingDialog = async () => {
|
|
1614
|
+
setIsFetchingVideoStats(true);
|
|
1615
|
+
setReprocessAlreadyProcessed(false);
|
|
1616
|
+
try {
|
|
1617
|
+
const res = await request<{
|
|
1618
|
+
withOriginal: number;
|
|
1619
|
+
withoutOriginal: number;
|
|
1620
|
+
alreadyProcessed: number;
|
|
1621
|
+
}>({
|
|
1622
|
+
url: `/lms/courses/${courseId}/structure/video-processing-stats`,
|
|
1623
|
+
method: 'GET',
|
|
1624
|
+
});
|
|
1625
|
+
setVideoProcessingStats(res.data ?? null);
|
|
1626
|
+
setPendingBulkJob('video_processing');
|
|
1627
|
+
} catch {
|
|
1628
|
+
toast.error('Erro ao buscar estatísticas de processamento de vídeo.');
|
|
1629
|
+
} finally {
|
|
1630
|
+
setIsFetchingVideoStats(false);
|
|
1704
1631
|
}
|
|
1705
1632
|
};
|
|
1706
1633
|
|
|
@@ -1839,9 +1766,9 @@ export function EditorCourse() {
|
|
|
1839
1766
|
variant="ghost"
|
|
1840
1767
|
size="icon"
|
|
1841
1768
|
className="size-6 shrink-0"
|
|
1842
|
-
disabled={isBulkJobPending}
|
|
1769
|
+
disabled={isBulkJobPending || isFetchingVideoStats}
|
|
1843
1770
|
>
|
|
1844
|
-
{isBulkJobPending ? (
|
|
1771
|
+
{isBulkJobPending || isFetchingVideoStats ? (
|
|
1845
1772
|
<Loader2 className="size-3.5 animate-spin" />
|
|
1846
1773
|
) : (
|
|
1847
1774
|
<Briefcase className="size-3.5" />
|
|
@@ -1867,11 +1794,27 @@ export function EditorCourse() {
|
|
|
1867
1794
|
<Zap className="size-4 text-muted-foreground" />
|
|
1868
1795
|
Recalcular XP de todas as aulas
|
|
1869
1796
|
</DropdownMenuItem>
|
|
1797
|
+
<DropdownMenuItem
|
|
1798
|
+
onSelect={handleOpenVideoProcessingDialog}
|
|
1799
|
+
className="gap-2"
|
|
1800
|
+
>
|
|
1801
|
+
<Video className="size-4 text-muted-foreground" />
|
|
1802
|
+
Processar todos os vídeos
|
|
1803
|
+
</DropdownMenuItem>
|
|
1804
|
+
<DropdownMenuSeparator />
|
|
1805
|
+
<DropdownMenuLabel className="text-xs">Exportar</DropdownMenuLabel>
|
|
1806
|
+
<DropdownMenuItem
|
|
1807
|
+
onSelect={() => setExportSheetOpen(true)}
|
|
1808
|
+
className="gap-2"
|
|
1809
|
+
>
|
|
1810
|
+
<Download className="size-4 text-muted-foreground" />
|
|
1811
|
+
SCORM 1.2
|
|
1812
|
+
</DropdownMenuItem>
|
|
1870
1813
|
</DropdownMenuContent>
|
|
1871
1814
|
</DropdownMenu>
|
|
1872
1815
|
|
|
1873
1816
|
<AlertDialog
|
|
1874
|
-
open={pendingBulkJob
|
|
1817
|
+
open={pendingBulkJob === 'transcription' || pendingBulkJob === 'xp_recalculation'}
|
|
1875
1818
|
onOpenChange={(open) => { if (!open) setPendingBulkJob(null); }}
|
|
1876
1819
|
>
|
|
1877
1820
|
<AlertDialogContent>
|
|
@@ -1890,20 +1833,81 @@ export function EditorCourse() {
|
|
|
1890
1833
|
<AlertDialogFooter>
|
|
1891
1834
|
<AlertDialogCancel>Cancelar</AlertDialogCancel>
|
|
1892
1835
|
<AlertDialogAction
|
|
1893
|
-
onClick={() => pendingBulkJob && handleBulkJob(pendingBulkJob)}
|
|
1836
|
+
onClick={() => pendingBulkJob && handleBulkJob(pendingBulkJob as 'transcription' | 'xp_recalculation')}
|
|
1894
1837
|
>
|
|
1895
1838
|
Confirmar
|
|
1896
1839
|
</AlertDialogAction>
|
|
1897
1840
|
</AlertDialogFooter>
|
|
1898
1841
|
</AlertDialogContent>
|
|
1899
1842
|
</AlertDialog>
|
|
1843
|
+
|
|
1844
|
+
<Dialog
|
|
1845
|
+
open={pendingBulkJob === 'video_processing'}
|
|
1846
|
+
onOpenChange={(open) => {
|
|
1847
|
+
if (!open) {
|
|
1848
|
+
setPendingBulkJob(null);
|
|
1849
|
+
setVideoProcessingStats(null);
|
|
1850
|
+
}
|
|
1851
|
+
}}
|
|
1852
|
+
>
|
|
1853
|
+
<DialogContent className="sm:max-w-md">
|
|
1854
|
+
<DialogHeader>
|
|
1855
|
+
<DialogTitle>Processar todos os vídeos?</DialogTitle>
|
|
1856
|
+
<DialogDescription>
|
|
1857
|
+
{videoProcessingStats ? (
|
|
1858
|
+
<>
|
|
1859
|
+
<span className="font-medium text-foreground">{videoProcessingStats.withOriginal}</span> aula(s) com vídeo original elegíveis para processamento.{' '}
|
|
1860
|
+
<span className="font-medium text-foreground">{videoProcessingStats.withoutOriginal}</span> aula(s) sem vídeo original serão ignoradas.
|
|
1861
|
+
</>
|
|
1862
|
+
) : (
|
|
1863
|
+
'Carregando estatísticas...'
|
|
1864
|
+
)}
|
|
1865
|
+
</DialogDescription>
|
|
1866
|
+
</DialogHeader>
|
|
1867
|
+
{videoProcessingStats && videoProcessingStats.alreadyProcessed > 0 && (
|
|
1868
|
+
<div className="flex items-center gap-2 py-1">
|
|
1869
|
+
<Checkbox
|
|
1870
|
+
id="reprocess-checkbox"
|
|
1871
|
+
checked={reprocessAlreadyProcessed}
|
|
1872
|
+
onCheckedChange={(checked) => setReprocessAlreadyProcessed(checked === true)}
|
|
1873
|
+
/>
|
|
1874
|
+
<label htmlFor="reprocess-checkbox" className="text-sm leading-none cursor-pointer">
|
|
1875
|
+
Reprocessar aulas que já foram processadas{' '}
|
|
1876
|
+
<span className="text-muted-foreground">({videoProcessingStats.alreadyProcessed} aula(s))</span>
|
|
1877
|
+
</label>
|
|
1878
|
+
</div>
|
|
1879
|
+
)}
|
|
1880
|
+
<DialogFooter>
|
|
1881
|
+
<Button
|
|
1882
|
+
variant="outline"
|
|
1883
|
+
onClick={() => {
|
|
1884
|
+
setPendingBulkJob(null);
|
|
1885
|
+
setVideoProcessingStats(null);
|
|
1886
|
+
}}
|
|
1887
|
+
>
|
|
1888
|
+
Cancelar
|
|
1889
|
+
</Button>
|
|
1890
|
+
<Button
|
|
1891
|
+
disabled={isBulkJobPending || !videoProcessingStats}
|
|
1892
|
+
onClick={() => handleBulkJob('video_processing', { reprocessAlreadyProcessed })}
|
|
1893
|
+
>
|
|
1894
|
+
{isBulkJobPending && <Loader2 className="size-4 animate-spin mr-2" />}
|
|
1895
|
+
Confirmar
|
|
1896
|
+
</Button>
|
|
1897
|
+
</DialogFooter>
|
|
1898
|
+
</DialogContent>
|
|
1899
|
+
</Dialog>
|
|
1900
1900
|
</div>
|
|
1901
1901
|
</div>
|
|
1902
1902
|
|
|
1903
1903
|
{/* ── Tabs ─────────────────────────────────────────────────────────── */}
|
|
1904
1904
|
<Tabs
|
|
1905
1905
|
value={activeTab}
|
|
1906
|
-
onValueChange={
|
|
1906
|
+
onValueChange={(v) => {
|
|
1907
|
+
const tab = v as CourseTab;
|
|
1908
|
+
setActiveTab(tab);
|
|
1909
|
+
onTabChange?.(tab);
|
|
1910
|
+
}}
|
|
1907
1911
|
className="flex flex-1 min-h-0 min-w-0 flex-col"
|
|
1908
1912
|
>
|
|
1909
1913
|
<TabsList
|
|
@@ -1922,10 +1926,10 @@ export function EditorCourse() {
|
|
|
1922
1926
|
'xp',
|
|
1923
1927
|
'midia',
|
|
1924
1928
|
'recursos',
|
|
1925
|
-
...(showVideoTab ? ['videos'] : []),
|
|
1926
1929
|
...(showOperationsTab ? ['operacoes'] : []),
|
|
1927
1930
|
'publicacao',
|
|
1928
1931
|
'extra',
|
|
1932
|
+
'exportacoes',
|
|
1929
1933
|
] as const
|
|
1930
1934
|
).map((tab) => (
|
|
1931
1935
|
<TabsTrigger
|
|
@@ -1950,11 +1954,11 @@ export function EditorCourse() {
|
|
|
1950
1954
|
? t('structureEditor.tabs.resources')
|
|
1951
1955
|
: tab === 'extra'
|
|
1952
1956
|
? t('structureEditor.tabs.extra')
|
|
1953
|
-
: tab === '
|
|
1954
|
-
? t('structureEditor.tabs.videoProfiles')
|
|
1955
|
-
: tab === 'operacoes'
|
|
1957
|
+
: tab === 'operacoes'
|
|
1956
1958
|
? 'Operações'
|
|
1957
|
-
:
|
|
1959
|
+
: tab === 'exportacoes'
|
|
1960
|
+
? 'Exportações'
|
|
1961
|
+
: t('structureEditor.tabs.publish')}
|
|
1958
1962
|
</TabsTrigger>
|
|
1959
1963
|
))}
|
|
1960
1964
|
</TabsList>
|
|
@@ -2485,174 +2489,6 @@ export function EditorCourse() {
|
|
|
2485
2489
|
/>
|
|
2486
2490
|
</TabsContent>
|
|
2487
2491
|
|
|
2488
|
-
{/* ── Tab: Vídeos (on_demand / blended only) ─────────────── */}
|
|
2489
|
-
{showVideoTab && (
|
|
2490
|
-
<TabsContent
|
|
2491
|
-
value="videos"
|
|
2492
|
-
className="mt-0 flex min-w-0 flex-col gap-3"
|
|
2493
|
-
>
|
|
2494
|
-
<Card className="bg-muted/20 py-2 gap-2">
|
|
2495
|
-
<CardHeader className="px-3 pt-2 pb-0">
|
|
2496
|
-
<CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
|
2497
|
-
{t('structureEditor.videoProfiles.title')}
|
|
2498
|
-
</CardTitle>
|
|
2499
|
-
</CardHeader>
|
|
2500
|
-
<CardContent className="px-3 pb-2 flex flex-col gap-2">
|
|
2501
|
-
<EntityPicker
|
|
2502
|
-
key={videoProfilePickerResetKey}
|
|
2503
|
-
placeholder={t(
|
|
2504
|
-
'structureEditor.videoProfiles.placeholder'
|
|
2505
|
-
)}
|
|
2506
|
-
options={availableVideoProfiles}
|
|
2507
|
-
getOptionValue={(opt) => opt.id}
|
|
2508
|
-
getOptionLabel={(opt) => opt.name}
|
|
2509
|
-
onChange={(_val, opt) => {
|
|
2510
|
-
if (opt && !linkedProfileIds.includes(opt.id)) {
|
|
2511
|
-
setLinkedProfileIds((prev) => [...prev, opt.id]);
|
|
2512
|
-
setVideoProfilePickerResetKey(
|
|
2513
|
-
(current) => current + 1
|
|
2514
|
-
);
|
|
2515
|
-
}
|
|
2516
|
-
}}
|
|
2517
|
-
showCreateButton
|
|
2518
|
-
createTitle={t(
|
|
2519
|
-
'structureEditor.videoProfiles.createTitle'
|
|
2520
|
-
)}
|
|
2521
|
-
createFields={[
|
|
2522
|
-
{
|
|
2523
|
-
name: 'name',
|
|
2524
|
-
label: t(
|
|
2525
|
-
'structureEditor.videoProfiles.createFields.name.label'
|
|
2526
|
-
),
|
|
2527
|
-
placeholder: t(
|
|
2528
|
-
'structureEditor.videoProfiles.createFields.name.placeholder'
|
|
2529
|
-
),
|
|
2530
|
-
required: true,
|
|
2531
|
-
},
|
|
2532
|
-
{
|
|
2533
|
-
name: 'ffmpeg_params',
|
|
2534
|
-
label: t(
|
|
2535
|
-
'structureEditor.videoProfiles.createFields.ffmpegParams.label'
|
|
2536
|
-
),
|
|
2537
|
-
placeholder: t(
|
|
2538
|
-
'structureEditor.videoProfiles.createFields.ffmpegParams.placeholder'
|
|
2539
|
-
),
|
|
2540
|
-
required: true,
|
|
2541
|
-
},
|
|
2542
|
-
]}
|
|
2543
|
-
onCreate={async (values) => {
|
|
2544
|
-
try {
|
|
2545
|
-
const response = await request<VideoProfileOption>({
|
|
2546
|
-
url: '/lms/video-resolution-profiles',
|
|
2547
|
-
method: 'POST',
|
|
2548
|
-
data: {
|
|
2549
|
-
name: values.name,
|
|
2550
|
-
ffmpeg_params: values.ffmpeg_params,
|
|
2551
|
-
},
|
|
2552
|
-
});
|
|
2553
|
-
await refetchVideoProfiles();
|
|
2554
|
-
return response.data;
|
|
2555
|
-
} catch {
|
|
2556
|
-
toast.error(
|
|
2557
|
-
t('structureEditor.videoProfiles.createError')
|
|
2558
|
-
);
|
|
2559
|
-
return null;
|
|
2560
|
-
}
|
|
2561
|
-
}}
|
|
2562
|
-
/>
|
|
2563
|
-
|
|
2564
|
-
{isFetchingVideoProfiles ? (
|
|
2565
|
-
<div className="flex flex-col gap-1">
|
|
2566
|
-
{Array.from({ length: 3 }).map((_, index) => (
|
|
2567
|
-
<div
|
|
2568
|
-
key={`video-profile-skeleton-${index}`}
|
|
2569
|
-
className="flex items-center gap-2 rounded-md border bg-muted/20 px-2.5 py-2"
|
|
2570
|
-
>
|
|
2571
|
-
<Skeleton className="size-3.5 rounded-full shrink-0" />
|
|
2572
|
-
<Skeleton className="h-3 w-full max-w-56" />
|
|
2573
|
-
</div>
|
|
2574
|
-
))}
|
|
2575
|
-
</div>
|
|
2576
|
-
) : linkedProfileIds.length === 0 ? (
|
|
2577
|
-
<p className="text-center text-xs text-muted-foreground py-1">
|
|
2578
|
-
{t('structureEditor.videoProfiles.empty')}
|
|
2579
|
-
</p>
|
|
2580
|
-
) : (
|
|
2581
|
-
<div className="flex flex-col gap-1">
|
|
2582
|
-
{linkedProfileIds.map((profileId) => {
|
|
2583
|
-
const profile = allVideoProfiles.find(
|
|
2584
|
-
(p) => p.id === profileId
|
|
2585
|
-
);
|
|
2586
|
-
return (
|
|
2587
|
-
<div
|
|
2588
|
-
key={profileId}
|
|
2589
|
-
className="flex items-center gap-2 rounded-md border bg-muted/20 px-2.5 py-2"
|
|
2590
|
-
>
|
|
2591
|
-
<Video className="size-3.5 shrink-0 text-violet-500" />
|
|
2592
|
-
<div className="flex-1 min-w-0">
|
|
2593
|
-
<p className="text-xs font-medium truncate">
|
|
2594
|
-
{profile?.name?.trim()
|
|
2595
|
-
? profile.name
|
|
2596
|
-
: t(
|
|
2597
|
-
'structureEditor.videoProfiles.fallbackName',
|
|
2598
|
-
{
|
|
2599
|
-
id: profileId,
|
|
2600
|
-
}
|
|
2601
|
-
)}
|
|
2602
|
-
</p>
|
|
2603
|
-
</div>
|
|
2604
|
-
<IconActionTooltip
|
|
2605
|
-
label={t(
|
|
2606
|
-
'structureEditor.videoProfiles.editAria'
|
|
2607
|
-
)}
|
|
2608
|
-
>
|
|
2609
|
-
<Button
|
|
2610
|
-
type="button"
|
|
2611
|
-
variant="ghost"
|
|
2612
|
-
size="icon"
|
|
2613
|
-
className="size-6 shrink-0 text-muted-foreground hover:text-foreground"
|
|
2614
|
-
onClick={() =>
|
|
2615
|
-
handleEditVideoProfile(profileId)
|
|
2616
|
-
}
|
|
2617
|
-
aria-label={t(
|
|
2618
|
-
'structureEditor.videoProfiles.editAria'
|
|
2619
|
-
)}
|
|
2620
|
-
>
|
|
2621
|
-
<Pencil className="size-3" />
|
|
2622
|
-
</Button>
|
|
2623
|
-
</IconActionTooltip>
|
|
2624
|
-
<IconActionTooltip
|
|
2625
|
-
label={t(
|
|
2626
|
-
'structureEditor.videoProfiles.removeAria'
|
|
2627
|
-
)}
|
|
2628
|
-
>
|
|
2629
|
-
<Button
|
|
2630
|
-
type="button"
|
|
2631
|
-
variant="ghost"
|
|
2632
|
-
size="icon"
|
|
2633
|
-
className="size-6 shrink-0 text-muted-foreground hover:text-destructive"
|
|
2634
|
-
onClick={() =>
|
|
2635
|
-
setLinkedProfileIds((prev) =>
|
|
2636
|
-
prev.filter((id) => id !== profileId)
|
|
2637
|
-
)
|
|
2638
|
-
}
|
|
2639
|
-
aria-label={t(
|
|
2640
|
-
'structureEditor.videoProfiles.removeAria'
|
|
2641
|
-
)}
|
|
2642
|
-
>
|
|
2643
|
-
<X className="size-3" />
|
|
2644
|
-
</Button>
|
|
2645
|
-
</IconActionTooltip>
|
|
2646
|
-
</div>
|
|
2647
|
-
);
|
|
2648
|
-
})}
|
|
2649
|
-
</div>
|
|
2650
|
-
)}
|
|
2651
|
-
</CardContent>
|
|
2652
|
-
</Card>
|
|
2653
|
-
</TabsContent>
|
|
2654
|
-
)}
|
|
2655
|
-
|
|
2656
2492
|
{/* ── Tab: Operações ──────────────────────────────────────── */}
|
|
2657
2493
|
{showOperationsTab && (
|
|
2658
2494
|
<TabsContent value="operacoes" className="mt-0 min-w-0">
|
|
@@ -2715,21 +2551,39 @@ export function EditorCourse() {
|
|
|
2715
2551
|
{t('structureEditor.publishChecklist.statusTitle')}
|
|
2716
2552
|
</CardTitle>
|
|
2717
2553
|
</CardHeader>
|
|
2718
|
-
<CardContent className="px-3 pb-2 flex
|
|
2719
|
-
<
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
{isReadyToPublish
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2554
|
+
<CardContent className="px-3 pb-2 flex flex-col gap-3">
|
|
2555
|
+
<div className="flex items-center justify-between gap-2">
|
|
2556
|
+
<p className="text-xs text-muted-foreground">
|
|
2557
|
+
{t('structureEditor.publishChecklist.statusProgress', {
|
|
2558
|
+
completed: completedRequired,
|
|
2559
|
+
total: requiredChecklist.length,
|
|
2560
|
+
})}
|
|
2561
|
+
</p>
|
|
2562
|
+
<Badge variant={isReadyToPublish ? 'default' : 'secondary'}>
|
|
2563
|
+
{isReadyToPublish
|
|
2564
|
+
? t('structureEditor.publishChecklist.ready')
|
|
2565
|
+
: t('structureEditor.publishChecklist.notReady')}
|
|
2566
|
+
</Badge>
|
|
2567
|
+
</div>
|
|
2568
|
+
<Button
|
|
2569
|
+
type="button"
|
|
2570
|
+
size="sm"
|
|
2571
|
+
className="w-full h-8 text-xs"
|
|
2572
|
+
disabled={!isReadyToPublish || formValues.status === 'publicado' || saving}
|
|
2573
|
+
onClick={handlePublish}
|
|
2574
|
+
>
|
|
2575
|
+
{formValues.status === 'publicado'
|
|
2576
|
+
? t('structureEditor.publishChecklist.alreadyPublished')
|
|
2577
|
+
: t('structureEditor.publishChecklist.publishButton')}
|
|
2578
|
+
</Button>
|
|
2730
2579
|
</CardContent>
|
|
2731
2580
|
</Card>
|
|
2732
2581
|
</TabsContent>
|
|
2582
|
+
|
|
2583
|
+
{/* ── Tab: Exportações ────────────────────────────────────────── */}
|
|
2584
|
+
<TabsContent value="exportacoes" className="mt-0 min-w-0">
|
|
2585
|
+
<CourseExportsTab courseId={courseId} />
|
|
2586
|
+
</TabsContent>
|
|
2733
2587
|
</div>
|
|
2734
2588
|
</div>
|
|
2735
2589
|
</Tabs>
|
|
@@ -2778,68 +2632,6 @@ export function EditorCourse() {
|
|
|
2778
2632
|
</div>
|
|
2779
2633
|
</form>
|
|
2780
2634
|
|
|
2781
|
-
<Sheet
|
|
2782
|
-
open={videoProfileEditSheetOpen}
|
|
2783
|
-
onOpenChange={(open) => {
|
|
2784
|
-
setVideoProfileEditSheetOpen(open);
|
|
2785
|
-
if (!open) setEditingVideoProfileId(null);
|
|
2786
|
-
}}
|
|
2787
|
-
>
|
|
2788
|
-
<ResizableSheetContent
|
|
2789
|
-
sheetId="lms-course-structure-video-profile-edit-sheet"
|
|
2790
|
-
defaultWidth={560}
|
|
2791
|
-
minWidth={420}
|
|
2792
|
-
maxWidth={920}
|
|
2793
|
-
className="sm:max-w-lg"
|
|
2794
|
-
>
|
|
2795
|
-
<SheetHeader>
|
|
2796
|
-
<SheetTitle>
|
|
2797
|
-
{t('structureEditor.videoProfiles.sheet.title')}
|
|
2798
|
-
</SheetTitle>
|
|
2799
|
-
<SheetDescription>
|
|
2800
|
-
{t('structureEditor.videoProfiles.sheet.description')}
|
|
2801
|
-
</SheetDescription>
|
|
2802
|
-
</SheetHeader>
|
|
2803
|
-
<div className="space-y-3 px-4 pb-4 pt-2">
|
|
2804
|
-
<div className="space-y-1.5">
|
|
2805
|
-
<FormLabel>
|
|
2806
|
-
{t('structureEditor.videoProfiles.createFields.name.label')}
|
|
2807
|
-
</FormLabel>
|
|
2808
|
-
<Input
|
|
2809
|
-
value={editingVideoProfileName}
|
|
2810
|
-
onChange={(e) => setEditingVideoProfileName(e.target.value)}
|
|
2811
|
-
placeholder={t(
|
|
2812
|
-
'structureEditor.videoProfiles.createFields.name.placeholder'
|
|
2813
|
-
)}
|
|
2814
|
-
/>
|
|
2815
|
-
</div>
|
|
2816
|
-
<FfmpegParamsEditor
|
|
2817
|
-
value={editingVideoProfileParams}
|
|
2818
|
-
onChange={setEditingVideoProfileParams}
|
|
2819
|
-
/>
|
|
2820
|
-
<div className="flex justify-end gap-2 pt-2">
|
|
2821
|
-
<Button
|
|
2822
|
-
type="button"
|
|
2823
|
-
variant="outline"
|
|
2824
|
-
onClick={() => setVideoProfileEditSheetOpen(false)}
|
|
2825
|
-
disabled={savingVideoProfileEdit}
|
|
2826
|
-
>
|
|
2827
|
-
{t('structureEditor.videoProfiles.sheet.actions.cancel')}
|
|
2828
|
-
</Button>
|
|
2829
|
-
<Button
|
|
2830
|
-
type="button"
|
|
2831
|
-
disabled={savingVideoProfileEdit}
|
|
2832
|
-
onClick={() => void handleSaveVideoProfileEdit()}
|
|
2833
|
-
>
|
|
2834
|
-
{savingVideoProfileEdit
|
|
2835
|
-
? t('structureEditor.videoProfiles.sheet.actions.saving')
|
|
2836
|
-
: t('structureEditor.videoProfiles.sheet.actions.save')}
|
|
2837
|
-
</Button>
|
|
2838
|
-
</div>
|
|
2839
|
-
</div>
|
|
2840
|
-
</ResizableSheetContent>
|
|
2841
|
-
</Sheet>
|
|
2842
|
-
|
|
2843
2635
|
<Sheet
|
|
2844
2636
|
open={categoryEditSheetOpen}
|
|
2845
2637
|
onOpenChange={(open) => {
|
|
@@ -2915,6 +2707,14 @@ export function EditorCourse() {
|
|
|
2915
2707
|
</ResizableSheetContent>
|
|
2916
2708
|
</Sheet>
|
|
2917
2709
|
|
|
2710
|
+
<CourseExportSheet
|
|
2711
|
+
open={exportSheetOpen}
|
|
2712
|
+
onOpenChange={setExportSheetOpen}
|
|
2713
|
+
courseId={courseId}
|
|
2714
|
+
sessionCount={sessions.length}
|
|
2715
|
+
videoLessonCount={lessons.filter((l) => l.type === 'video').length}
|
|
2716
|
+
/>
|
|
2717
|
+
|
|
2918
2718
|
<CourseDeleteDialog
|
|
2919
2719
|
open={deleteDialogOpen}
|
|
2920
2720
|
onOpenChange={(open) => {
|