@hed-hog/lms 0.0.365 → 0.0.370
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/certificate/certificate.controller.d.ts +1 -1
- package/dist/certificate/certificate.controller.d.ts.map +1 -1
- package/dist/certificate/certificate.controller.js +4 -2
- package/dist/certificate/certificate.controller.js.map +1 -1
- package/dist/certificate/certificate.service.d.ts +50 -0
- package/dist/certificate/certificate.service.d.ts.map +1 -1
- package/dist/certificate/certificate.service.js +73 -0
- package/dist/certificate/certificate.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-ai-usage.service.d.ts +58 -0
- package/dist/course/course-ai-usage.service.d.ts.map +1 -0
- package/dist/course/course-ai-usage.service.js +176 -0
- package/dist/course/course-ai-usage.service.js.map +1 -0
- package/dist/course/course-audio-transcription.service.d.ts +65 -1
- package/dist/course/course-audio-transcription.service.d.ts.map +1 -1
- package/dist/course/course-audio-transcription.service.js +381 -29
- package/dist/course/course-audio-transcription.service.js.map +1 -1
- package/dist/course/course-export-scorm12.service.d.ts +3 -0
- package/dist/course/course-export-scorm12.service.d.ts.map +1 -1
- package/dist/course/course-export-scorm12.service.js +141 -6
- package/dist/course/course-export-scorm12.service.js.map +1 -1
- package/dist/course/course-export.service.d.ts.map +1 -1
- package/dist/course/course-export.service.js +2 -1
- package/dist/course/course-export.service.js.map +1 -1
- package/dist/course/course-lesson.controller.d.ts +25 -3
- package/dist/course/course-lesson.controller.d.ts.map +1 -1
- package/dist/course/course-lesson.controller.js +71 -8
- package/dist/course/course-lesson.controller.js.map +1 -1
- package/dist/course/course-structure.controller.d.ts +30 -7
- package/dist/course/course-structure.controller.d.ts.map +1 -1
- package/dist/course/course-structure.controller.js +37 -4
- package/dist/course/course-structure.controller.js.map +1 -1
- package/dist/course/course-structure.service.d.ts +37 -5
- package/dist/course/course-structure.service.d.ts.map +1 -1
- package/dist/course/course-structure.service.js +165 -20
- package/dist/course/course-structure.service.js.map +1 -1
- package/dist/course/course-transcription-translation.service.d.ts +31 -0
- package/dist/course/course-transcription-translation.service.d.ts.map +1 -0
- package/dist/course/course-transcription-translation.service.js +227 -0
- package/dist/course/course-transcription-translation.service.js.map +1 -0
- 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 +14 -0
- package/dist/course/course-video-hls.service.d.ts.map +1 -1
- package/dist/course/course-video-hls.service.js +25 -8
- package/dist/course/course-video-hls.service.js.map +1 -1
- package/dist/course/course.controller.d.ts +2 -0
- 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 +9 -0
- package/dist/course/course.module.js.map +1 -1
- package/dist/course/course.service.d.ts +2 -0
- package/dist/course/course.service.d.ts.map +1 -1
- package/dist/course/course.service.js +36 -2
- package/dist/course/course.service.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 +1 -0
- package/dist/course/dto/create-course-export.dto.d.ts.map +1 -1
- package/dist/course/dto/create-course-export.dto.js +6 -0
- package/dist/course/dto/create-course-export.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 +3 -1
- package/dist/course/lms-bulk-upload-automation.service.d.ts.map +1 -1
- package/dist/course/lms-bulk-upload-automation.service.js +33 -16
- package/dist/course/lms-bulk-upload-automation.service.js.map +1 -1
- package/dist/course/lms-bulk-upload.controller.d.ts +3 -0
- package/dist/course/lms-bulk-upload.controller.d.ts.map +1 -1
- package/dist/course/lms-bulk-upload.service.d.ts +3 -0
- package/dist/course/lms-bulk-upload.service.d.ts.map +1 -1
- package/dist/course/lms-bulk-upload.service.js +48 -29
- package/dist/course/lms-bulk-upload.service.js.map +1 -1
- package/dist/course/subtitle.util.d.ts +46 -0
- package/dist/course/subtitle.util.d.ts.map +1 -0
- package/dist/course/subtitle.util.js +206 -0
- package/dist/course/subtitle.util.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 +27 -0
- package/dist/enterprise/training/training-student.service.d.ts.map +1 -1
- package/dist/enterprise/training/training-student.service.js +197 -10
- package/dist/enterprise/training/training-student.service.js.map +1 -1
- package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts +3 -1
- 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 +19 -5
- package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js.map +1 -1
- package/dist/lesson-xp-map/lesson-xp-map.module.d.ts.map +1 -1
- package/dist/lesson-xp-map/lesson-xp-map.module.js +2 -1
- package/dist/lesson-xp-map/lesson-xp-map.module.js.map +1 -1
- package/dist/libraries/lms/tsconfig.tsbuildinfo +1 -1
- package/dist/lms.module.d.ts.map +1 -1
- package/dist/lms.module.js +14 -0
- 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 +8 -0
- package/dist/platforma/platforma-video.service.d.ts.map +1 -1
- package/dist/platforma/platforma-video.service.js +45 -2
- package/dist/platforma/platforma-video.service.js.map +1 -1
- package/dist/platforma/platforma.controller.d.ts +213 -1
- package/dist/platforma/platforma.controller.d.ts.map +1 -1
- package/dist/platforma/platforma.controller.js +159 -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/training/dto/create-training.dto.d.ts +9 -0
- package/dist/training/dto/create-training.dto.d.ts.map +1 -1
- package/dist/training/dto/create-training.dto.js +45 -1
- package/dist/training/dto/create-training.dto.js.map +1 -1
- package/dist/training/training.controller.d.ts +144 -0
- package/dist/training/training.controller.d.ts.map +1 -1
- package/dist/training/training.service.d.ts +149 -0
- package/dist/training/training.service.d.ts.map +1 -1
- package/dist/training/training.service.js +332 -167
- package/dist/training/training.service.js.map +1 -1
- package/hedhog/data/image_type.yaml +10 -0
- package/hedhog/data/route.yaml +251 -0
- package/hedhog/data/setting_group.yaml +97 -0
- package/hedhog/frontend/app/bulk-upload-sessions/page.tsx.ejs +139 -27
- 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]/_components/CourseFlagsCard.tsx.ejs +69 -57
- package/hedhog/frontend/app/courses/[id]/_components/CourseIssuedCertificatesCard.tsx.ejs +168 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-ai-costs-tab.tsx.ejs +191 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-export-sheet.tsx.ejs +81 -1
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-exports-tab.tsx.ejs +12 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-overview-tab.tsx.ejs +141 -30
- 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-course.tsx.ejs +69 -1
- 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 +267 -19
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +114 -86
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +239 -31
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +344 -59
- package/hedhog/frontend/app/courses/[id]/structure/_components/lesson-video-preview.tsx.ejs +200 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +1 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +3 -0
- 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 +19 -7
- package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +4 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-ai-costs.ts.ejs +40 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-content-overview.ts.ejs +2 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-exports.ts.ejs +25 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +148 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-transcription-segments.ts.ejs +157 -8
- package/hedhog/frontend/app/courses/_components/CourseRowActions.tsx.ejs +1 -22
- package/hedhog/frontend/app/courses/page.tsx.ejs +66 -13
- 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 +650 -168
- package/hedhog/frontend/app/training/page.tsx.ejs +38 -4
- package/hedhog/frontend/messages/en.json +41 -12
- package/hedhog/frontend/messages/pt.json +44 -13
- package/hedhog/query/triggers.sql +33 -0
- package/hedhog/table/course_ai_usage.yaml +46 -0
- package/hedhog/table/course_enrollment.yaml +3 -0
- package/hedhog/table/course_lesson.yaml +3 -0
- package/hedhog/table/course_lesson_answer.yaml +37 -0
- package/hedhog/table/course_lesson_transcription_segment.yaml +8 -0
- package/hedhog/table/learning_path.yaml +6 -0
- package/hedhog/table/learning_path_module.yaml +22 -0
- package/hedhog/table/learning_path_step.yaml +9 -6
- package/hedhog/table/lesson_view_event.yaml +66 -0
- package/package.json +8 -7
- package/src/certificate/certificate.controller.ts +2 -0
- package/src/certificate/certificate.service.ts +99 -0
- package/src/course/course-ai-usage.service.ts +221 -0
- package/src/course/course-audio-transcription.service.ts +471 -43
- package/src/course/course-export-scorm12.service.ts +149 -5
- package/src/course/course-export.service.ts +1 -0
- package/src/course/course-lesson.controller.ts +59 -6
- package/src/course/course-structure.controller.ts +19 -1
- package/src/course/course-structure.service.ts +184 -10
- package/src/course/course-transcription-translation.service.ts +293 -0
- package/src/course/course-video-agent-pipeline.service.ts +471 -0
- package/src/course/course-video-hls.service.ts +30 -10
- package/src/course/course.module.ts +9 -0
- package/src/course/course.service.ts +46 -1
- package/src/course/dto/create-course-bulk-job.dto.ts +7 -3
- package/src/course/dto/create-course-export.dto.ts +6 -0
- package/src/course/ffmpeg.util.ts +65 -0
- package/src/course/lms-bulk-upload-automation.service.ts +33 -8
- package/src/course/lms-bulk-upload.service.ts +20 -1
- package/src/course/subtitle.util.ts +220 -0
- package/src/enterprise/training/training-student.service.ts +224 -4
- package/src/lesson-xp-map/lesson-xp-ai-calculation.service.ts +14 -0
- package/src/lesson-xp-map/lesson-xp-map.module.ts +2 -1
- package/src/lms.module.ts +14 -0
- 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 +59 -3
- package/src/platforma/platforma.controller.ts +130 -0
- package/src/realtime/lms-realtime.controller.ts +27 -1
- package/src/realtime/lms-realtime.service.ts +2 -1
- package/src/training/dto/create-training.dto.ts +36 -0
- package/src/training/training.service.ts +360 -163
|
@@ -435,7 +435,7 @@ export function LessonXpTab({
|
|
|
435
435
|
icon={<Cpu className="size-4 text-sky-600" />}
|
|
436
436
|
total={xpMap.totalXp}
|
|
437
437
|
items={areaDistribution}
|
|
438
|
-
toneClassName="
|
|
438
|
+
toneClassName=""
|
|
439
439
|
/>
|
|
440
440
|
<DistributionCard
|
|
441
441
|
title="Skills"
|
|
@@ -443,7 +443,7 @@ export function LessonXpTab({
|
|
|
443
443
|
icon={<Zap className="size-4 text-emerald-600" />}
|
|
444
444
|
total={xpMap.totalXp}
|
|
445
445
|
items={skillDistribution}
|
|
446
|
-
toneClassName="
|
|
446
|
+
toneClassName=""
|
|
447
447
|
/>
|
|
448
448
|
<TimelineCard
|
|
449
449
|
segmentViews={segmentViews}
|
|
@@ -670,7 +670,7 @@ function TimelineCard({
|
|
|
670
670
|
|
|
671
671
|
return (
|
|
672
672
|
<Card className="min-w-0 overflow-hidden border-border/70 bg-card/95 py-0 gap-0! shadow-[0_18px_40px_-32px_rgba(15,23,42,0.45)] xl:col-span-2">
|
|
673
|
-
<CardHeader className="border-b border-border/60
|
|
673
|
+
<CardHeader className="border-b border-border/60 pt-3 pb-1.5!">
|
|
674
674
|
<div className="flex items-center justify-between gap-3">
|
|
675
675
|
<div>
|
|
676
676
|
<CardTitle className="text-sm font-semibold">
|
|
@@ -761,7 +761,7 @@ function LearningTypesCard({ items }: { items: DistributionItem[] }) {
|
|
|
761
761
|
|
|
762
762
|
return (
|
|
763
763
|
<Card className="min-w-0 overflow-hidden border-border/70 bg-card/95 py-0 gap-0! shadow-[0_18px_40px_-32px_rgba(15,23,42,0.45)] xl:col-span-2">
|
|
764
|
-
<CardHeader className="border-b border-border/60
|
|
764
|
+
<CardHeader className="border-b border-border/60 pt-3 pb-1.5!">
|
|
765
765
|
<div className="flex items-center justify-between gap-3">
|
|
766
766
|
<CardTitle className="text-sm font-semibold">
|
|
767
767
|
Tipos de aprendizado
|
|
@@ -775,7 +775,7 @@ function LearningTypesCard({ items }: { items: DistributionItem[] }) {
|
|
|
775
775
|
{items.map((item) => (
|
|
776
776
|
<div
|
|
777
777
|
key={item.id}
|
|
778
|
-
className="flex h-full flex-col justify-start gap-1.5 rounded-xl border border-border/60 bg-muted/20 px-2.5 py-2 text-xs shadow-sm transition-all duration-200 hover:-translate-y-0.5 hover:border-
|
|
778
|
+
className="flex h-full flex-col justify-start gap-1.5 rounded-xl border border-border/60 bg-muted/20 px-2.5 py-2 text-xs shadow-sm transition-all duration-200 hover:-translate-y-0.5 hover:border-border/80 hover:bg-background/95 hover:shadow-md"
|
|
779
779
|
>
|
|
780
780
|
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
781
781
|
<span className="min-w-0 flex-1 font-medium">{item.label}</span>
|
|
@@ -813,7 +813,7 @@ function SegmentsSection({
|
|
|
813
813
|
}) {
|
|
814
814
|
return (
|
|
815
815
|
<Card className="min-w-0 overflow-hidden border-border/70 bg-card/95 py-0 gap-0! shadow-[0_18px_40px_-32px_rgba(15,23,42,0.45)]">
|
|
816
|
-
<CardHeader className="border-b border-border/60
|
|
816
|
+
<CardHeader className="border-b border-border/60 pt-3 pb-1.5!">
|
|
817
817
|
<div className="flex items-center justify-between gap-3">
|
|
818
818
|
<CardTitle className="text-sm font-semibold">
|
|
819
819
|
Segmentos ({segments.length})
|
|
@@ -829,14 +829,14 @@ function SegmentsSection({
|
|
|
829
829
|
<div
|
|
830
830
|
key={segment.id}
|
|
831
831
|
className={cn(
|
|
832
|
-
'flex flex-col gap-2 rounded-2xl border border-border/60 bg-
|
|
832
|
+
'flex flex-col gap-2 rounded-2xl border border-border/60 bg-card/90 px-3 py-2.5 text-xs shadow-[0_16px_30px_-24px_rgba(15,23,42,0.45)] transition-all duration-200 hover:-translate-y-0.5 hover:border-border/80 hover:shadow-[0_20px_36px_-24px_rgba(15,23,42,0.52)]',
|
|
833
833
|
!segment.shouldGrantXp && 'opacity-50'
|
|
834
834
|
)}
|
|
835
835
|
>
|
|
836
836
|
<div className="flex flex-wrap items-start justify-between gap-2">
|
|
837
837
|
<div className="flex min-w-0 flex-col gap-1">
|
|
838
838
|
<div className="flex flex-wrap items-center gap-1.5">
|
|
839
|
-
<span className="rounded-full border border-
|
|
839
|
+
<span className="rounded-full border border-border/60 bg-muted/30 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground">
|
|
840
840
|
{segment.label}
|
|
841
841
|
</span>
|
|
842
842
|
<span className="rounded-full border border-border/60 bg-background/90 px-2 py-0.5 font-mono text-[10px] text-muted-foreground shadow-sm transition-all duration-200 hover:border-border hover:bg-background hover:text-foreground hover:shadow-md">
|
|
@@ -856,7 +856,7 @@ function SegmentsSection({
|
|
|
856
856
|
</div>
|
|
857
857
|
|
|
858
858
|
<div className="flex flex-wrap items-center justify-end gap-1.5">
|
|
859
|
-
<span className="rounded-full border border-
|
|
859
|
+
<span className="rounded-full border border-border/60 bg-muted/30 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground">
|
|
860
860
|
{segment.xpValue} XP
|
|
861
861
|
</span>
|
|
862
862
|
<DifficultyBadge difficulty={segment.difficulty} />
|
|
@@ -1031,14 +1031,7 @@ function MetricPill({
|
|
|
1031
1031
|
value: string;
|
|
1032
1032
|
tone?: 'default' | 'sky' | 'emerald' | 'violet';
|
|
1033
1033
|
}) {
|
|
1034
|
-
const toneClassName =
|
|
1035
|
-
tone === 'sky'
|
|
1036
|
-
? 'border-sky-500/15 bg-sky-500/10 text-sky-700 dark:text-sky-300'
|
|
1037
|
-
: tone === 'emerald'
|
|
1038
|
-
? 'border-emerald-500/15 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300'
|
|
1039
|
-
: tone === 'violet'
|
|
1040
|
-
? 'border-violet-500/15 bg-violet-500/10 text-violet-700 dark:text-violet-300'
|
|
1041
|
-
: 'border-border/60 bg-muted/40 text-foreground';
|
|
1034
|
+
const toneClassName = 'border-border/60 bg-muted/40 text-foreground';
|
|
1042
1035
|
|
|
1043
1036
|
return (
|
|
1044
1037
|
<div
|
|
@@ -1069,12 +1062,7 @@ function SegmentBreakdownGroup({
|
|
|
1069
1062
|
color: string;
|
|
1070
1063
|
}>;
|
|
1071
1064
|
}) {
|
|
1072
|
-
const toneClassName =
|
|
1073
|
-
tone === 'sky'
|
|
1074
|
-
? 'border-sky-500/15 bg-sky-500/8'
|
|
1075
|
-
: tone === 'emerald'
|
|
1076
|
-
? 'border-emerald-500/15 bg-emerald-500/8'
|
|
1077
|
-
: 'border-violet-500/15 bg-violet-500/8';
|
|
1065
|
+
const toneClassName = 'border-border/60 bg-muted/20';
|
|
1078
1066
|
|
|
1079
1067
|
if (!items.length) {
|
|
1080
1068
|
return null;
|
|
@@ -2,28 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
import { Badge } from '@/components/ui/badge';
|
|
4
4
|
import { Button } from '@/components/ui/button';
|
|
5
|
+
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
|
5
6
|
import { Separator } from '@/components/ui/separator';
|
|
6
7
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
7
8
|
import { cn } from '@/lib/utils';
|
|
9
|
+
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
8
10
|
import { useQueryClient } from '@tanstack/react-query';
|
|
9
11
|
import {
|
|
10
12
|
Clock,
|
|
11
13
|
ExternalLink,
|
|
12
14
|
FileText,
|
|
13
15
|
HelpCircle,
|
|
16
|
+
Languages,
|
|
14
17
|
Loader2,
|
|
15
18
|
Mic,
|
|
16
19
|
Paperclip,
|
|
20
|
+
Plus,
|
|
21
|
+
Trash2,
|
|
17
22
|
Video,
|
|
23
|
+
X,
|
|
18
24
|
type LucideIcon,
|
|
19
25
|
} from 'lucide-react';
|
|
26
|
+
import Link from 'next/link';
|
|
20
27
|
import { useEffect, useState } from 'react';
|
|
21
28
|
import { toast } from 'sonner';
|
|
22
29
|
import { useLmsSettingsQuery } from '../_data/use-lms-settings-query';
|
|
23
30
|
import {
|
|
31
|
+
useDeleteTranscriptionLocaleMutation,
|
|
24
32
|
useStartTranscriptionMutation,
|
|
33
|
+
useTranscriptionLocalesQuery,
|
|
25
34
|
useTranscriptionSegmentsQuery,
|
|
35
|
+
useTranslateTranscriptionMutation,
|
|
36
|
+
type TranscriptionLocale,
|
|
26
37
|
} from '../_data/use-transcription-segments';
|
|
38
|
+
import { LessonVideoPreview } from './lesson-video-preview';
|
|
27
39
|
import { LessonXpTab } from './detail-lesson-xp-tab';
|
|
28
40
|
import { useStructureStore } from './store';
|
|
29
41
|
import type { LessonType } from './types';
|
|
@@ -69,7 +81,15 @@ export function DetailLesson({ lessonId }: DetailLessonProps) {
|
|
|
69
81
|
s.lessons.find((l) => l.id === lessonId)
|
|
70
82
|
);
|
|
71
83
|
const [activeTab, setActiveTab] = useState('dados');
|
|
84
|
+
const [selectedLocaleId, setSelectedLocaleId] = useState<number | null | undefined>(undefined);
|
|
85
|
+
const [translateOpen, setTranslateOpen] = useState(false);
|
|
86
|
+
const [targetLocaleId, setTargetLocaleId] = useState<number | null>(null);
|
|
87
|
+
const [activeTranslationJob, setActiveTranslationJob] = useState<{
|
|
88
|
+
queueJobId: number;
|
|
89
|
+
targetLocaleName: string;
|
|
90
|
+
} | null>(null);
|
|
72
91
|
const lmsSettings = useLmsSettingsQuery();
|
|
92
|
+
const { request } = useApp();
|
|
73
93
|
|
|
74
94
|
useEffect(() => {
|
|
75
95
|
if (!lmsSettings.transcriptionEnabled && activeTab === 'transcription') {
|
|
@@ -77,16 +97,77 @@ export function DetailLesson({ lessonId }: DetailLessonProps) {
|
|
|
77
97
|
}
|
|
78
98
|
}, [lmsSettings.transcriptionEnabled, activeTab]);
|
|
79
99
|
|
|
100
|
+
const isTranscriptionActive = activeTab === 'transcription' || activeTab === 'xp';
|
|
101
|
+
const activeLessonId = isTranscriptionActive ? (lesson?.id ?? null) : null;
|
|
102
|
+
|
|
103
|
+
const { data: transcriptionLocales = [], refetch: refetchLocales } =
|
|
104
|
+
useTranscriptionLocalesQuery(activeLessonId);
|
|
105
|
+
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (transcriptionLocales.length > 0 && selectedLocaleId === undefined) {
|
|
108
|
+
setSelectedLocaleId(transcriptionLocales[0]?.id ?? null);
|
|
109
|
+
}
|
|
110
|
+
if (transcriptionLocales.length === 0 && selectedLocaleId !== undefined) {
|
|
111
|
+
setSelectedLocaleId(undefined);
|
|
112
|
+
}
|
|
113
|
+
}, [transcriptionLocales, selectedLocaleId]);
|
|
114
|
+
|
|
80
115
|
const { data: segments = [], isLoading: segmentsLoading } =
|
|
81
|
-
useTranscriptionSegmentsQuery(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
)
|
|
116
|
+
useTranscriptionSegmentsQuery(activeLessonId, selectedLocaleId);
|
|
117
|
+
|
|
118
|
+
const { data: allLocales = [] } = useQuery<TranscriptionLocale[]>({
|
|
119
|
+
queryKey: ['all-locales'],
|
|
120
|
+
queryFn: async () => {
|
|
121
|
+
const res = await request<{ data: TranscriptionLocale[] }>({
|
|
122
|
+
url: '/core/locales?limit=200',
|
|
123
|
+
method: 'GET',
|
|
124
|
+
});
|
|
125
|
+
return (res.data?.data ?? []) as TranscriptionLocale[];
|
|
126
|
+
},
|
|
127
|
+
enabled: translateOpen,
|
|
128
|
+
initialData: [],
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const availableTargetLocales = allLocales.filter(
|
|
132
|
+
(l) => l.id !== null && !transcriptionLocales.some((tl) => tl.id === l.id),
|
|
133
|
+
);
|
|
86
134
|
|
|
87
135
|
const queryClient = useQueryClient();
|
|
136
|
+
const showConfirm = useStructureStore((s) => s.showConfirm);
|
|
88
137
|
const { mutate: startTranscription, isPending: isStarting } =
|
|
89
138
|
useStartTranscriptionMutation(lesson?.id ?? null);
|
|
139
|
+
const { mutate: translateTranscription, isPending: isTranslating } =
|
|
140
|
+
useTranslateTranscriptionMutation(lesson?.id ?? null);
|
|
141
|
+
const { mutate: deleteTranscriptionLocale } =
|
|
142
|
+
useDeleteTranscriptionLocaleMutation(lesson?.id ?? null);
|
|
143
|
+
|
|
144
|
+
const handleDeleteLocale = (loc: TranscriptionLocale) => {
|
|
145
|
+
const name = loc.name ?? loc.code ?? 'este idioma';
|
|
146
|
+
showConfirm({
|
|
147
|
+
title: 'Excluir transcrição',
|
|
148
|
+
description: `Excluir a transcrição/tradução em "${name}" desta aula? Esta ação não pode ser desfeita.`,
|
|
149
|
+
confirmText: 'Excluir',
|
|
150
|
+
destructive: true,
|
|
151
|
+
onConfirm: () => {
|
|
152
|
+
deleteTranscriptionLocale(loc.id, {
|
|
153
|
+
onSuccess: () => {
|
|
154
|
+
toast.success('Transcrição excluída.');
|
|
155
|
+
if (selectedLocaleId === loc.id) setSelectedLocaleId(undefined);
|
|
156
|
+
refetchLocales();
|
|
157
|
+
queryClient.invalidateQueries({
|
|
158
|
+
queryKey: ['lesson-transcription-locales', lesson?.id],
|
|
159
|
+
});
|
|
160
|
+
queryClient.invalidateQueries({
|
|
161
|
+
queryKey: ['lesson-transcription-segments', lesson?.id],
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
onError: (err) => {
|
|
165
|
+
toast.error(err.message || 'Erro ao excluir a transcrição.');
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
};
|
|
90
171
|
|
|
91
172
|
const handleStartTranscription = () => {
|
|
92
173
|
startTranscription({
|
|
@@ -95,6 +176,9 @@ export function DetailLesson({ lessonId }: DetailLessonProps) {
|
|
|
95
176
|
queryClient.invalidateQueries({
|
|
96
177
|
queryKey: ['lesson-transcription-segments', lesson?.id],
|
|
97
178
|
});
|
|
179
|
+
queryClient.invalidateQueries({
|
|
180
|
+
queryKey: ['lesson-transcription-locales', lesson?.id],
|
|
181
|
+
});
|
|
98
182
|
},
|
|
99
183
|
onError: (err) => {
|
|
100
184
|
toast.error(err.message || 'Erro ao iniciar a transcrição.');
|
|
@@ -102,6 +186,32 @@ export function DetailLesson({ lessonId }: DetailLessonProps) {
|
|
|
102
186
|
});
|
|
103
187
|
};
|
|
104
188
|
|
|
189
|
+
const handleTranslate = () => {
|
|
190
|
+
if (!targetLocaleId) return;
|
|
191
|
+
const targetName =
|
|
192
|
+
availableTargetLocales.find((l) => l.id === targetLocaleId)?.name ?? String(targetLocaleId);
|
|
193
|
+
translateTranscription(targetLocaleId, selectedLocaleId ?? null, {
|
|
194
|
+
onSuccess: (result) => {
|
|
195
|
+
toast.success('Tradução iniciada! Acompanhe o progresso na fila.');
|
|
196
|
+
setTranslateOpen(false);
|
|
197
|
+
setTargetLocaleId(null);
|
|
198
|
+
setActiveTranslationJob({ queueJobId: result.queueJobId, targetLocaleName: targetName });
|
|
199
|
+
setTimeout(() => {
|
|
200
|
+
refetchLocales();
|
|
201
|
+
queryClient.invalidateQueries({
|
|
202
|
+
queryKey: ['lesson-transcription-segments', lesson?.id],
|
|
203
|
+
});
|
|
204
|
+
queryClient.invalidateQueries({
|
|
205
|
+
queryKey: ['lesson-transcription-locales', lesson?.id],
|
|
206
|
+
});
|
|
207
|
+
}, 2000);
|
|
208
|
+
},
|
|
209
|
+
onError: (err) => {
|
|
210
|
+
toast.error(err.message || 'Erro ao iniciar a tradução.');
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
};
|
|
214
|
+
|
|
105
215
|
if (!lesson) return null;
|
|
106
216
|
|
|
107
217
|
const cfg = LESSON_TYPE_CONFIG[lesson.type];
|
|
@@ -142,6 +252,9 @@ export function DetailLesson({ lessonId }: DetailLessonProps) {
|
|
|
142
252
|
>
|
|
143
253
|
<TabsList className="mx-4 mt-3 w-auto justify-start shrink-0">
|
|
144
254
|
<TabsTrigger value="dados">Dados</TabsTrigger>
|
|
255
|
+
{lesson.type === 'video' && (
|
|
256
|
+
<TabsTrigger value="conteudo">Conteúdo</TabsTrigger>
|
|
257
|
+
)}
|
|
145
258
|
{lmsSettings.transcriptionEnabled && (
|
|
146
259
|
<TabsTrigger value="transcription">Transcrição</TabsTrigger>
|
|
147
260
|
)}
|
|
@@ -243,27 +356,160 @@ export function DetailLesson({ lessonId }: DetailLessonProps) {
|
|
|
243
356
|
</div>
|
|
244
357
|
</TabsContent>
|
|
245
358
|
|
|
359
|
+
{/* ── Conteúdo (player de vídeo) ───────────────────────────────────── */}
|
|
360
|
+
{lesson.type === 'video' && (
|
|
361
|
+
<TabsContent
|
|
362
|
+
value="conteudo"
|
|
363
|
+
className="flex-1 overflow-y-auto px-4 pb-4 mt-3"
|
|
364
|
+
>
|
|
365
|
+
{activeTab === 'conteudo' && (
|
|
366
|
+
<LessonVideoPreview lessonId={lesson.id} />
|
|
367
|
+
)}
|
|
368
|
+
</TabsContent>
|
|
369
|
+
)}
|
|
370
|
+
|
|
246
371
|
{/* ── Transcrição ───────────────────────────────────────────────────── */}
|
|
247
372
|
{lmsSettings.transcriptionEnabled && (
|
|
248
373
|
<TabsContent
|
|
249
374
|
value="transcription"
|
|
250
375
|
className="flex-1 overflow-y-auto px-4 pb-4 mt-3"
|
|
251
376
|
>
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
377
|
+
{/* Banner de job de tradução ativo */}
|
|
378
|
+
{activeTranslationJob && (
|
|
379
|
+
<div className="flex items-center justify-between gap-2 mb-3 rounded-md border border-amber-200 bg-amber-50 dark:border-amber-800 dark:bg-amber-950/30 px-3 py-2 text-sm">
|
|
380
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
381
|
+
<Loader2 className="size-4 shrink-0 animate-spin text-amber-600 dark:text-amber-400" />
|
|
382
|
+
<span className="text-amber-900 dark:text-amber-200 truncate">
|
|
383
|
+
Tradução para{' '}
|
|
384
|
+
<strong>{activeTranslationJob.targetLocaleName}</strong> em andamento
|
|
385
|
+
{' '}· Job #{activeTranslationJob.queueJobId}
|
|
386
|
+
</span>
|
|
387
|
+
</div>
|
|
388
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
389
|
+
<Link
|
|
390
|
+
href="/queue/jobs"
|
|
391
|
+
className="text-xs text-amber-700 dark:text-amber-300 underline underline-offset-2 hover:text-amber-900 dark:hover:text-amber-100"
|
|
392
|
+
>
|
|
393
|
+
Ver na fila →
|
|
394
|
+
</Link>
|
|
395
|
+
<button
|
|
396
|
+
onClick={() => setActiveTranslationJob(null)}
|
|
397
|
+
className="text-amber-600 dark:text-amber-400 hover:text-amber-900 dark:hover:text-amber-100"
|
|
398
|
+
>
|
|
399
|
+
<X className="size-3.5" />
|
|
400
|
+
</button>
|
|
401
|
+
</div>
|
|
402
|
+
</div>
|
|
403
|
+
)}
|
|
404
|
+
|
|
405
|
+
{/* Toolbar */}
|
|
406
|
+
<div className="flex items-center justify-between gap-2 mb-3 flex-wrap">
|
|
407
|
+
{/* Locale pills */}
|
|
408
|
+
<div className="flex items-center gap-1.5 flex-wrap">
|
|
409
|
+
{transcriptionLocales.map((loc) => (
|
|
410
|
+
<div
|
|
411
|
+
key={loc.id ?? 'null'}
|
|
412
|
+
className={cn(
|
|
413
|
+
'flex items-center gap-1 rounded-full pl-2.5 pr-1 py-0.5 text-xs font-medium border transition-colors',
|
|
414
|
+
selectedLocaleId === loc.id
|
|
415
|
+
? 'bg-primary text-primary-foreground border-primary'
|
|
416
|
+
: 'bg-muted text-muted-foreground border-border hover:border-primary/50',
|
|
417
|
+
)}
|
|
418
|
+
>
|
|
419
|
+
<button
|
|
420
|
+
onClick={() => setSelectedLocaleId(loc.id)}
|
|
421
|
+
className="flex items-center gap-1"
|
|
422
|
+
>
|
|
423
|
+
<Languages className="size-3" />
|
|
424
|
+
{loc.name ?? loc.code ?? 'Desconhecido'}
|
|
425
|
+
</button>
|
|
426
|
+
<button
|
|
427
|
+
onClick={() => handleDeleteLocale(loc)}
|
|
428
|
+
title="Excluir transcrição deste idioma"
|
|
429
|
+
className={cn(
|
|
430
|
+
'rounded-full p-0.5 transition-colors',
|
|
431
|
+
selectedLocaleId === loc.id
|
|
432
|
+
? 'hover:bg-primary-foreground/20'
|
|
433
|
+
: 'hover:bg-foreground/10',
|
|
434
|
+
)}
|
|
435
|
+
>
|
|
436
|
+
<Trash2 className="size-3" />
|
|
437
|
+
</button>
|
|
438
|
+
</div>
|
|
439
|
+
))}
|
|
440
|
+
</div>
|
|
441
|
+
|
|
442
|
+
{/* Action buttons */}
|
|
443
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
444
|
+
{transcriptionLocales.length > 0 && (
|
|
445
|
+
<Popover open={translateOpen} onOpenChange={setTranslateOpen}>
|
|
446
|
+
<PopoverTrigger asChild>
|
|
447
|
+
<Button size="sm" variant="outline" disabled={isTranslating}>
|
|
448
|
+
{isTranslating ? (
|
|
449
|
+
<Loader2 className="size-4 mr-2 animate-spin" />
|
|
450
|
+
) : (
|
|
451
|
+
<Plus className="size-4 mr-2" />
|
|
452
|
+
)}
|
|
453
|
+
Traduzir
|
|
454
|
+
</Button>
|
|
455
|
+
</PopoverTrigger>
|
|
456
|
+
<PopoverContent className="w-64 p-3" align="end">
|
|
457
|
+
<p className="text-xs font-medium mb-2 text-muted-foreground">
|
|
458
|
+
Traduzir para:
|
|
459
|
+
</p>
|
|
460
|
+
{availableTargetLocales.length === 0 ? (
|
|
461
|
+
<p className="text-xs text-muted-foreground">
|
|
462
|
+
Todos os idiomas disponíveis já foram traduzidos.
|
|
463
|
+
</p>
|
|
464
|
+
) : (
|
|
465
|
+
<div className="flex flex-col gap-1">
|
|
466
|
+
{availableTargetLocales.map((loc) => (
|
|
467
|
+
<button
|
|
468
|
+
key={loc.id}
|
|
469
|
+
onClick={() => setTargetLocaleId(loc.id)}
|
|
470
|
+
className={cn(
|
|
471
|
+
'flex items-center gap-2 rounded-md px-2 py-1.5 text-sm text-left transition-colors',
|
|
472
|
+
targetLocaleId === loc.id
|
|
473
|
+
? 'bg-primary text-primary-foreground'
|
|
474
|
+
: 'hover:bg-muted',
|
|
475
|
+
)}
|
|
476
|
+
>
|
|
477
|
+
{loc.name ?? loc.code}
|
|
478
|
+
</button>
|
|
479
|
+
))}
|
|
480
|
+
<Button
|
|
481
|
+
size="sm"
|
|
482
|
+
className="mt-2 w-full"
|
|
483
|
+
disabled={!targetLocaleId || isTranslating}
|
|
484
|
+
onClick={handleTranslate}
|
|
485
|
+
>
|
|
486
|
+
{isTranslating && (
|
|
487
|
+
<Loader2 className="size-4 mr-2 animate-spin" />
|
|
488
|
+
)}
|
|
489
|
+
Confirmar tradução
|
|
490
|
+
</Button>
|
|
491
|
+
</div>
|
|
492
|
+
)}
|
|
493
|
+
</PopoverContent>
|
|
494
|
+
</Popover>
|
|
263
495
|
)}
|
|
264
|
-
|
|
265
|
-
|
|
496
|
+
<Button
|
|
497
|
+
size="sm"
|
|
498
|
+
variant="outline"
|
|
499
|
+
onClick={handleStartTranscription}
|
|
500
|
+
disabled={isStarting}
|
|
501
|
+
>
|
|
502
|
+
{isStarting ? (
|
|
503
|
+
<Loader2 className="size-4 mr-2 animate-spin" />
|
|
504
|
+
) : (
|
|
505
|
+
<Mic className="size-4 mr-2" />
|
|
506
|
+
)}
|
|
507
|
+
Iniciar transcrição
|
|
508
|
+
</Button>
|
|
509
|
+
</div>
|
|
266
510
|
</div>
|
|
511
|
+
|
|
512
|
+
{/* Segment list */}
|
|
267
513
|
{segmentsLoading ? (
|
|
268
514
|
<div className="p-4 text-sm text-muted-foreground">
|
|
269
515
|
Carregando transcrição...
|
|
@@ -284,7 +530,9 @@ export function DetailLesson({ lessonId }: DetailLessonProps) {
|
|
|
284
530
|
<EmptyState
|
|
285
531
|
icon={<FileText className="size-8 text-muted-foreground/40" />}
|
|
286
532
|
>
|
|
287
|
-
|
|
533
|
+
{transcriptionLocales.length === 0
|
|
534
|
+
? 'Transcrição não disponível para esta aula.'
|
|
535
|
+
: 'Nenhum segmento encontrado para este idioma.'}
|
|
288
536
|
</EmptyState>
|
|
289
537
|
)}
|
|
290
538
|
</TabsContent>
|