@hed-hog/lms 0.0.366 → 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/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 +26 -5
- package/dist/course/course-structure.controller.d.ts.map +1 -1
- package/dist/course/course-structure.controller.js +31 -1
- 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.js +7 -7
- package/dist/course/course-video-agent-pipeline.service.js.map +1 -1
- package/dist/course/course.module.d.ts.map +1 -1
- package/dist/course/course.module.js +4 -0
- package/dist/course/course.module.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/lms-bulk-upload-automation.service.d.ts.map +1 -1
- package/dist/course/lms-bulk-upload-automation.service.js +26 -13
- 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-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 +4 -0
- package/dist/lms.module.js.map +1 -1
- package/dist/platforma/platforma-performance.service.js +121 -121
- 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 +99 -1
- package/dist/platforma/platforma.controller.d.ts.map +1 -1
- package/dist/platforma/platforma.controller.js +111 -2
- package/dist/platforma/platforma.controller.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/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/detail-course.tsx.ejs +69 -1
- 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/_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-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 +26 -4
- package/hedhog/frontend/app/paths/page.tsx.ejs +612 -164
- package/hedhog/frontend/messages/en.json +23 -12
- package/hedhog/frontend/messages/pt.json +23 -12
- package/hedhog/query/triggers.sql +33 -0
- package/hedhog/table/course_ai_usage.yaml +46 -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 -66
- package/package.json +9 -9
- 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 +16 -0
- 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 -471
- package/src/course/course.module.ts +4 -0
- 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 -65
- package/src/course/lms-bulk-upload-automation.service.ts +29 -7
- 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 +4 -0
- package/src/platforma/dto/heartbeat.dto.ts +30 -30
- package/src/platforma/handlers/emit-certificate.handler.ts +117 -117
- package/src/platforma/handlers/lesson-heartbeat.handler.ts +343 -343
- package/src/platforma/platforma-heartbeat.service.ts +33 -33
- package/src/platforma/platforma-performance.service.ts +606 -606
- package/src/platforma/platforma-search.service.ts +48 -48
- package/src/platforma/platforma-video.service.ts +59 -3
- package/src/platforma/platforma.controller.ts +88 -0
- package/src/training/dto/create-training.dto.ts +36 -0
- package/src/training/training.service.ts +360 -163
|
@@ -83,6 +83,7 @@ type UploadStatus =
|
|
|
83
83
|
| 'queued'
|
|
84
84
|
| 'uploading'
|
|
85
85
|
| 'cancelling'
|
|
86
|
+
| 'processing'
|
|
86
87
|
| 'received'
|
|
87
88
|
| 'done'
|
|
88
89
|
| 'error'
|
|
@@ -116,6 +117,9 @@ type UploadItemRow = {
|
|
|
116
117
|
matchedSessionTitle: string | null;
|
|
117
118
|
matchedLessonId: number | null;
|
|
118
119
|
matchedLessonTitle: string | null;
|
|
120
|
+
jobStatus: string | null;
|
|
121
|
+
jobAttempts: number | null;
|
|
122
|
+
jobMaxAttempts: number | null;
|
|
119
123
|
};
|
|
120
124
|
|
|
121
125
|
type UploadListResponse = {
|
|
@@ -271,6 +275,15 @@ function formatDate(value: string | null) {
|
|
|
271
275
|
|
|
272
276
|
function getStatusMeta(status: UploadStatus) {
|
|
273
277
|
switch (status) {
|
|
278
|
+
case 'processing':
|
|
279
|
+
return {
|
|
280
|
+
label: 'Aula vinculada',
|
|
281
|
+
variant: 'secondary' as const,
|
|
282
|
+
icon: Loader2,
|
|
283
|
+
iconClass: 'text-violet-500 animate-spin',
|
|
284
|
+
animated: true,
|
|
285
|
+
progressClass: 'bg-violet-500',
|
|
286
|
+
};
|
|
274
287
|
case 'received':
|
|
275
288
|
return {
|
|
276
289
|
label: 'Concluido',
|
|
@@ -394,6 +407,7 @@ export default function LmsBulkUploadSessionsPage() {
|
|
|
394
407
|
} | null>(null);
|
|
395
408
|
const [manualLinkStructureLoading, setManualLinkStructureLoading] = useState(false);
|
|
396
409
|
const [isLinkingLesson, setIsLinkingLesson] = useState(false);
|
|
410
|
+
const [manualLinkIsReassign, setManualLinkIsReassign] = useState(false);
|
|
397
411
|
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
398
412
|
storageKey: 'pagination:global:pageSize',
|
|
399
413
|
defaultValue: PAGE_SIZE_OPTIONS[1],
|
|
@@ -446,7 +460,7 @@ export default function LmsBulkUploadSessionsPage() {
|
|
|
446
460
|
},
|
|
447
461
|
});
|
|
448
462
|
|
|
449
|
-
const ACTIVE_STATUSES: UploadStatus[] = ['queued', 'uploading', 'cancelling', 'done'];
|
|
463
|
+
const ACTIVE_STATUSES: UploadStatus[] = ['queued', 'uploading', 'cancelling', 'processing', 'done'];
|
|
450
464
|
|
|
451
465
|
const {
|
|
452
466
|
data,
|
|
@@ -485,7 +499,7 @@ export default function LmsBulkUploadSessionsPage() {
|
|
|
485
499
|
enabled: manualLinkOpen,
|
|
486
500
|
queryFn: async () => {
|
|
487
501
|
const response = await request<{ data: Array<{ id: number; title: string; code: string | null }> }>({
|
|
488
|
-
url: '/
|
|
502
|
+
url: '/lms/courses?pageSize=500',
|
|
489
503
|
method: 'GET',
|
|
490
504
|
});
|
|
491
505
|
return response.data;
|
|
@@ -498,6 +512,7 @@ export default function LmsBulkUploadSessionsPage() {
|
|
|
498
512
|
() => ({
|
|
499
513
|
sending: rows.filter((row) => row.status === 'uploading').length,
|
|
500
514
|
done: rows.filter((row) => row.status === 'received').length,
|
|
515
|
+
pending: rows.filter((row) => row.status === 'done').length,
|
|
501
516
|
error: rows.filter((row) => row.status === 'error').length,
|
|
502
517
|
}),
|
|
503
518
|
[rows]
|
|
@@ -523,6 +538,15 @@ export default function LmsBulkUploadSessionsPage() {
|
|
|
523
538
|
iconContainerClassName: 'bg-green-500/10 text-green-700',
|
|
524
539
|
layout: 'compact' as const,
|
|
525
540
|
},
|
|
541
|
+
{
|
|
542
|
+
key: 'pending',
|
|
543
|
+
title: 'Pendentes',
|
|
544
|
+
value: isLoading ? '-' : stats.pending,
|
|
545
|
+
icon: Clock,
|
|
546
|
+
accentClassName: 'from-blue-500/20 via-blue-500/10 to-transparent',
|
|
547
|
+
iconContainerClassName: 'bg-blue-500/10 text-blue-700',
|
|
548
|
+
layout: 'compact' as const,
|
|
549
|
+
},
|
|
526
550
|
{
|
|
527
551
|
key: 'error',
|
|
528
552
|
title: 'Erros',
|
|
@@ -666,15 +690,29 @@ export default function LmsBulkUploadSessionsPage() {
|
|
|
666
690
|
}
|
|
667
691
|
};
|
|
668
692
|
|
|
669
|
-
const openManualLink = async (
|
|
693
|
+
const openManualLink = async (
|
|
694
|
+
itemId: number,
|
|
695
|
+
prefill?: {
|
|
696
|
+
courseId: number | null;
|
|
697
|
+
sessionId: number | null;
|
|
698
|
+
lessonId: number | null;
|
|
699
|
+
}
|
|
700
|
+
) => {
|
|
670
701
|
setManualLinkItemId(itemId);
|
|
671
|
-
setManualLinkCourseId(null);
|
|
672
|
-
setManualLinkSessionId(null);
|
|
673
|
-
setManualLinkLessonId(null);
|
|
702
|
+
setManualLinkCourseId(prefill?.courseId ?? null);
|
|
703
|
+
setManualLinkSessionId(prefill?.sessionId ?? null);
|
|
704
|
+
setManualLinkLessonId(prefill?.lessonId ?? null);
|
|
674
705
|
setManualLinkStructure(null);
|
|
675
706
|
setManualLinkVideoUrl(null);
|
|
707
|
+
setManualLinkIsReassign(Boolean(prefill?.courseId && prefill?.lessonId));
|
|
676
708
|
setManualLinkOpen(true);
|
|
677
709
|
setManualLinkVideoLoading(true);
|
|
710
|
+
if (prefill?.courseId) {
|
|
711
|
+
void loadCourseStructure(prefill.courseId, {
|
|
712
|
+
sessionId: prefill.sessionId,
|
|
713
|
+
lessonId: prefill.lessonId,
|
|
714
|
+
});
|
|
715
|
+
}
|
|
678
716
|
try {
|
|
679
717
|
const res = await request<{ url: string }>({
|
|
680
718
|
url: `/lms/bulk-upload/item/${itemId}/video-url`,
|
|
@@ -688,16 +726,35 @@ export default function LmsBulkUploadSessionsPage() {
|
|
|
688
726
|
}
|
|
689
727
|
};
|
|
690
728
|
|
|
691
|
-
const loadCourseStructure = async (
|
|
729
|
+
const loadCourseStructure = async (
|
|
730
|
+
courseId: number,
|
|
731
|
+
keep?: { sessionId: number | null; lessonId: number | null }
|
|
732
|
+
) => {
|
|
692
733
|
setManualLinkStructureLoading(true);
|
|
693
|
-
|
|
694
|
-
|
|
734
|
+
if (!keep) {
|
|
735
|
+
setManualLinkSessionId(null);
|
|
736
|
+
setManualLinkLessonId(null);
|
|
737
|
+
}
|
|
695
738
|
try {
|
|
696
|
-
const res = await request<{
|
|
697
|
-
|
|
739
|
+
const res = await request<{
|
|
740
|
+
sessoes: Array<{ id: string; titulo: string }>;
|
|
741
|
+
aulas: Array<{ id: string; titulo: string; sessaoId: string }>;
|
|
742
|
+
}>({
|
|
743
|
+
url: `/lms/courses/${courseId}/structure`,
|
|
698
744
|
method: 'GET',
|
|
699
745
|
});
|
|
700
|
-
|
|
746
|
+
const sessions = (res.data.sessoes ?? []).map((sessao) => ({
|
|
747
|
+
id: Number(sessao.id),
|
|
748
|
+
title: sessao.titulo,
|
|
749
|
+
lessons: (res.data.aulas ?? [])
|
|
750
|
+
.filter((aula) => aula.sessaoId === sessao.id)
|
|
751
|
+
.map((aula) => ({ id: Number(aula.id), title: aula.titulo })),
|
|
752
|
+
}));
|
|
753
|
+
setManualLinkStructure({ sessions });
|
|
754
|
+
if (keep) {
|
|
755
|
+
setManualLinkSessionId(keep.sessionId);
|
|
756
|
+
setManualLinkLessonId(keep.lessonId);
|
|
757
|
+
}
|
|
701
758
|
} catch {
|
|
702
759
|
setManualLinkStructure(null);
|
|
703
760
|
} finally {
|
|
@@ -1384,7 +1441,7 @@ export default function LmsBulkUploadSessionsPage() {
|
|
|
1384
1441
|
onSaved={handleProfileSaved}
|
|
1385
1442
|
/>
|
|
1386
1443
|
|
|
1387
|
-
<KpiCardsGrid items={kpiItems} columns={
|
|
1444
|
+
<KpiCardsGrid items={kpiItems} columns={4} />
|
|
1388
1445
|
|
|
1389
1446
|
<div className="flex items-center justify-between gap-3">
|
|
1390
1447
|
<SearchBar
|
|
@@ -1465,9 +1522,21 @@ export default function LmsBulkUploadSessionsPage() {
|
|
|
1465
1522
|
<FileTypeIcon filename={row.fileName} size={20} />
|
|
1466
1523
|
</span>
|
|
1467
1524
|
<div className="min-w-0 space-y-0.5">
|
|
1468
|
-
<
|
|
1469
|
-
|
|
1470
|
-
|
|
1525
|
+
<TooltipProvider>
|
|
1526
|
+
<Tooltip>
|
|
1527
|
+
<TooltipTrigger asChild>
|
|
1528
|
+
<p className="truncate text-sm font-medium cursor-default">
|
|
1529
|
+
{row.fileName}
|
|
1530
|
+
</p>
|
|
1531
|
+
</TooltipTrigger>
|
|
1532
|
+
<TooltipContent
|
|
1533
|
+
side="top"
|
|
1534
|
+
className="max-w-md break-all"
|
|
1535
|
+
>
|
|
1536
|
+
{row.fileName}
|
|
1537
|
+
</TooltipContent>
|
|
1538
|
+
</Tooltip>
|
|
1539
|
+
</TooltipProvider>
|
|
1471
1540
|
<p className="truncate text-xs text-muted-foreground">
|
|
1472
1541
|
Sessao #{row.sessionId} · {row.appName}
|
|
1473
1542
|
</p>
|
|
@@ -1512,12 +1581,13 @@ export default function LmsBulkUploadSessionsPage() {
|
|
|
1512
1581
|
|
|
1513
1582
|
<TableCell className="max-w-[22rem]">
|
|
1514
1583
|
{row.matchedCourseId && row.matchedLessonId ? (
|
|
1584
|
+
<div className="flex items-center gap-1">
|
|
1515
1585
|
<button
|
|
1516
1586
|
type="button"
|
|
1517
|
-
className="flex w-
|
|
1587
|
+
className="flex min-w-0 flex-1 items-center gap-2 text-left"
|
|
1518
1588
|
onClick={() =>
|
|
1519
1589
|
window.open(
|
|
1520
|
-
`/lms/courses/${row.matchedCourseId}`,
|
|
1590
|
+
`/lms/courses/${row.matchedCourseId}?sel=${row.matchedLessonId}&type=lesson&tab=videos`,
|
|
1521
1591
|
'_blank',
|
|
1522
1592
|
'noopener,noreferrer'
|
|
1523
1593
|
)
|
|
@@ -1553,7 +1623,33 @@ export default function LmsBulkUploadSessionsPage() {
|
|
|
1553
1623
|
</p>
|
|
1554
1624
|
</div>
|
|
1555
1625
|
</button>
|
|
1556
|
-
|
|
1626
|
+
<TooltipProvider>
|
|
1627
|
+
<Tooltip>
|
|
1628
|
+
<TooltipTrigger asChild>
|
|
1629
|
+
<Button
|
|
1630
|
+
variant="ghost"
|
|
1631
|
+
size="icon"
|
|
1632
|
+
className="h-7 w-7 shrink-0 text-muted-foreground"
|
|
1633
|
+
onClick={(event) => {
|
|
1634
|
+
event.stopPropagation();
|
|
1635
|
+
openManualLink(row.id, {
|
|
1636
|
+
courseId: row.matchedCourseId,
|
|
1637
|
+
sessionId: row.matchedSessionId,
|
|
1638
|
+
lessonId: row.matchedLessonId,
|
|
1639
|
+
});
|
|
1640
|
+
}}
|
|
1641
|
+
aria-label="Alterar aula vinculada"
|
|
1642
|
+
>
|
|
1643
|
+
<Pencil className="h-3.5 w-3.5" />
|
|
1644
|
+
</Button>
|
|
1645
|
+
</TooltipTrigger>
|
|
1646
|
+
<TooltipContent>
|
|
1647
|
+
Alterar aula vinculada
|
|
1648
|
+
</TooltipContent>
|
|
1649
|
+
</Tooltip>
|
|
1650
|
+
</TooltipProvider>
|
|
1651
|
+
</div>
|
|
1652
|
+
) : (row.status === 'lesson_not_found' || row.status === 'done') ? (
|
|
1557
1653
|
<Button
|
|
1558
1654
|
variant="outline"
|
|
1559
1655
|
size="sm"
|
|
@@ -1569,11 +1665,21 @@ export default function LmsBulkUploadSessionsPage() {
|
|
|
1569
1665
|
</TableCell>
|
|
1570
1666
|
|
|
1571
1667
|
<TableCell>
|
|
1572
|
-
<div className="flex
|
|
1573
|
-
<
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1668
|
+
<div className="flex flex-col gap-1">
|
|
1669
|
+
<div className="flex items-center gap-1.5">
|
|
1670
|
+
<StatusIcon
|
|
1671
|
+
className={cn('h-3.5 w-3.5 shrink-0', meta.iconClass)}
|
|
1672
|
+
/>
|
|
1673
|
+
<Badge variant={meta.variant}>{meta.label}</Badge>
|
|
1674
|
+
</div>
|
|
1675
|
+
{row.status === 'processing' && row.jobStatus ? (
|
|
1676
|
+
<p className="text-[11px] text-muted-foreground">
|
|
1677
|
+
Job: {row.jobStatus}
|
|
1678
|
+
{row.jobAttempts != null && row.jobMaxAttempts != null
|
|
1679
|
+
? ` (${row.jobAttempts}/${row.jobMaxAttempts})`
|
|
1680
|
+
: ''}
|
|
1681
|
+
</p>
|
|
1682
|
+
) : null}
|
|
1577
1683
|
</div>
|
|
1578
1684
|
</TableCell>
|
|
1579
1685
|
|
|
@@ -1638,10 +1744,14 @@ export default function LmsBulkUploadSessionsPage() {
|
|
|
1638
1744
|
<SheetHeader className="border-b px-6 py-4">
|
|
1639
1745
|
<SheetTitle className="flex items-center gap-2">
|
|
1640
1746
|
<Link2 className="h-4 w-4" />
|
|
1641
|
-
|
|
1747
|
+
{manualLinkIsReassign
|
|
1748
|
+
? 'Alterar aula vinculada'
|
|
1749
|
+
: 'Vincular aula manualmente'}
|
|
1642
1750
|
</SheetTitle>
|
|
1643
1751
|
<SheetDescription>
|
|
1644
|
-
|
|
1752
|
+
{manualLinkIsReassign
|
|
1753
|
+
? 'Selecione o novo curso, módulo e aula para revincular este vídeo e reiniciar o processamento.'
|
|
1754
|
+
: 'Selecione o curso, módulo e aula para associar este vídeo e iniciar o processamento.'}
|
|
1645
1755
|
</SheetDescription>
|
|
1646
1756
|
</SheetHeader>
|
|
1647
1757
|
|
|
@@ -1739,7 +1849,9 @@ export default function LmsBulkUploadSessionsPage() {
|
|
|
1739
1849
|
) : (
|
|
1740
1850
|
<Link2 className="h-4 w-4" />
|
|
1741
1851
|
)}
|
|
1742
|
-
|
|
1852
|
+
{manualLinkIsReassign
|
|
1853
|
+
? 'Confirmar novo vínculo e reprocessar'
|
|
1854
|
+
: 'Confirmar vínculo e iniciar processamento'}
|
|
1743
1855
|
</Button>
|
|
1744
1856
|
</div>
|
|
1745
1857
|
</ResizableSheetContent>
|
|
@@ -17,10 +17,13 @@ import type { UseFormReturn } from 'react-hook-form';
|
|
|
17
17
|
import { CourseSectionCard } from './CourseSectionCard';
|
|
18
18
|
import type { CourseEditFormValues, TranslationFn } from './course-edit-types';
|
|
19
19
|
|
|
20
|
+
type CourseFlag = 'certificado' | 'destaque' | 'listado';
|
|
21
|
+
|
|
20
22
|
type CourseFlagsCardProps = {
|
|
21
23
|
form: UseFormReturn<CourseEditFormValues>;
|
|
22
24
|
t: TranslationFn;
|
|
23
25
|
compact?: boolean;
|
|
26
|
+
flags?: CourseFlag[];
|
|
24
27
|
};
|
|
25
28
|
|
|
26
29
|
function FlagRow({
|
|
@@ -53,10 +56,13 @@ function FlagRow({
|
|
|
53
56
|
);
|
|
54
57
|
}
|
|
55
58
|
|
|
59
|
+
const DEFAULT_FLAGS: CourseFlag[] = ['certificado', 'destaque', 'listado'];
|
|
60
|
+
|
|
56
61
|
export function CourseFlagsCard({
|
|
57
62
|
form,
|
|
58
63
|
t,
|
|
59
64
|
compact = false,
|
|
65
|
+
flags = DEFAULT_FLAGS,
|
|
60
66
|
}: CourseFlagsCardProps) {
|
|
61
67
|
return (
|
|
62
68
|
<CourseSectionCard
|
|
@@ -66,65 +72,71 @@ export function CourseFlagsCard({
|
|
|
66
72
|
compact={compact}
|
|
67
73
|
>
|
|
68
74
|
<div className="space-y-3">
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
75
|
+
{flags.includes('certificado') && (
|
|
76
|
+
<FormField
|
|
77
|
+
control={form.control}
|
|
78
|
+
name="certificado"
|
|
79
|
+
render={({ field }) => (
|
|
80
|
+
<FormItem>
|
|
81
|
+
<FormLabel className="sr-only">
|
|
82
|
+
{t('form.flags.certificate.label')}
|
|
83
|
+
</FormLabel>
|
|
84
|
+
<FormControl>
|
|
85
|
+
<FlagRow
|
|
86
|
+
title={t('form.flags.certificate.label')}
|
|
87
|
+
description={t('form.flags.certificate.description')}
|
|
88
|
+
checked={field.value}
|
|
89
|
+
onCheckedChange={field.onChange}
|
|
90
|
+
/>
|
|
91
|
+
</FormControl>
|
|
92
|
+
</FormItem>
|
|
93
|
+
)}
|
|
94
|
+
/>
|
|
95
|
+
)}
|
|
88
96
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
97
|
+
{flags.includes('destaque') && (
|
|
98
|
+
<FormField
|
|
99
|
+
control={form.control}
|
|
100
|
+
name="destaque"
|
|
101
|
+
render={({ field }) => (
|
|
102
|
+
<FormItem>
|
|
103
|
+
<FormLabel className="sr-only">
|
|
104
|
+
{t('form.flags.featured.label')}
|
|
105
|
+
</FormLabel>
|
|
106
|
+
<FormControl>
|
|
107
|
+
<FlagRow
|
|
108
|
+
title={t('form.flags.featured.label')}
|
|
109
|
+
description={t('form.flags.featured.description')}
|
|
110
|
+
checked={field.value}
|
|
111
|
+
onCheckedChange={field.onChange}
|
|
112
|
+
/>
|
|
113
|
+
</FormControl>
|
|
114
|
+
</FormItem>
|
|
115
|
+
)}
|
|
116
|
+
/>
|
|
117
|
+
)}
|
|
108
118
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
<
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
119
|
+
{flags.includes('listado') && (
|
|
120
|
+
<FormField
|
|
121
|
+
control={form.control}
|
|
122
|
+
name="listado"
|
|
123
|
+
render={({ field }) => (
|
|
124
|
+
<FormItem>
|
|
125
|
+
<FormLabel className="sr-only">
|
|
126
|
+
{t('form.flags.listed.label')}
|
|
127
|
+
</FormLabel>
|
|
128
|
+
<FormControl>
|
|
129
|
+
<FlagRow
|
|
130
|
+
title={t('form.flags.listed.label')}
|
|
131
|
+
description={t('form.flags.listed.description')}
|
|
132
|
+
checked={field.value}
|
|
133
|
+
onCheckedChange={field.onChange}
|
|
134
|
+
/>
|
|
135
|
+
</FormControl>
|
|
136
|
+
</FormItem>
|
|
137
|
+
)}
|
|
138
|
+
/>
|
|
139
|
+
)}
|
|
128
140
|
</div>
|
|
129
141
|
</CourseSectionCard>
|
|
130
142
|
);
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { PaginationFooter } from '@/components/entity-list';
|
|
4
|
+
import { Badge } from '@/components/ui/badge';
|
|
5
|
+
import { Button } from '@/components/ui/button';
|
|
6
|
+
import { Skeleton } from '@/components/ui/skeleton';
|
|
7
|
+
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
8
|
+
import { Award, BadgeCheck } from 'lucide-react';
|
|
9
|
+
import { useLocale, useTranslations } from 'next-intl';
|
|
10
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
11
|
+
|
|
12
|
+
import { CourseSectionCard } from './CourseSectionCard';
|
|
13
|
+
|
|
14
|
+
type IssuedCertificate = {
|
|
15
|
+
id: number;
|
|
16
|
+
studentId: number;
|
|
17
|
+
studentName: string;
|
|
18
|
+
courseName: string;
|
|
19
|
+
templateId: number;
|
|
20
|
+
templateName: string;
|
|
21
|
+
templateStatus: string | null;
|
|
22
|
+
certificateType: 'course' | 'exam' | 'course_class_group' | 'learning_path';
|
|
23
|
+
workloadHours: number;
|
|
24
|
+
issuedAt: string;
|
|
25
|
+
completedAt: string;
|
|
26
|
+
finalScore: number | null;
|
|
27
|
+
verificationCode: string;
|
|
28
|
+
verificationUrl: string | null;
|
|
29
|
+
publicAccess: boolean;
|
|
30
|
+
pdfUrl: string | null;
|
|
31
|
+
sourceTitle: string;
|
|
32
|
+
primaryColor: string | null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type ApiIssuedCertificatesResponse = {
|
|
36
|
+
total: number;
|
|
37
|
+
page: number;
|
|
38
|
+
pageSize: number;
|
|
39
|
+
lastPage: number;
|
|
40
|
+
data: IssuedCertificate[];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const PAGE_SIZES = [6, 12, 24] as const;
|
|
44
|
+
|
|
45
|
+
type CourseIssuedCertificatesCardProps = {
|
|
46
|
+
courseId: string;
|
|
47
|
+
compact?: boolean;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export function CourseIssuedCertificatesCard({
|
|
51
|
+
courseId,
|
|
52
|
+
compact = false,
|
|
53
|
+
}: CourseIssuedCertificatesCardProps) {
|
|
54
|
+
const { request } = useApp();
|
|
55
|
+
const t = useTranslations('lms.CursoEditPage.structureEditor.issuedCertificates');
|
|
56
|
+
const locale = useLocale();
|
|
57
|
+
|
|
58
|
+
const [page, setPage] = useState(1);
|
|
59
|
+
const [pageSize, setPageSize] = useState<number>(6);
|
|
60
|
+
|
|
61
|
+
const { data, isLoading } = useQuery<ApiIssuedCertificatesResponse>({
|
|
62
|
+
queryKey: ['lms-course-issued-certificates', courseId, page, pageSize],
|
|
63
|
+
enabled: Boolean(courseId),
|
|
64
|
+
queryFn: async () => {
|
|
65
|
+
const response = await request<ApiIssuedCertificatesResponse>({
|
|
66
|
+
url: '/lms/certificates/issued',
|
|
67
|
+
method: 'GET',
|
|
68
|
+
params: { courseId, page, pageSize },
|
|
69
|
+
});
|
|
70
|
+
return response.data;
|
|
71
|
+
},
|
|
72
|
+
placeholderData: (old) => old,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const certificates = data?.data ?? [];
|
|
76
|
+
const totalItems = data?.total ?? 0;
|
|
77
|
+
const totalPages = Math.max(data?.lastPage ?? 1, 1);
|
|
78
|
+
const loading = isLoading && !data;
|
|
79
|
+
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (page > totalPages) setPage(totalPages);
|
|
82
|
+
}, [page, totalPages]);
|
|
83
|
+
|
|
84
|
+
const dateFormatter = useMemo(
|
|
85
|
+
() =>
|
|
86
|
+
new Intl.DateTimeFormat(locale.startsWith('pt') ? 'pt-BR' : 'en-US', {
|
|
87
|
+
dateStyle: 'medium',
|
|
88
|
+
}),
|
|
89
|
+
[locale],
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
function formatDate(value: string) {
|
|
93
|
+
const parsed = new Date(value);
|
|
94
|
+
if (Number.isNaN(parsed.getTime())) return '-';
|
|
95
|
+
return dateFormatter.format(parsed);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<CourseSectionCard
|
|
100
|
+
title={t('title')}
|
|
101
|
+
description={t('description')}
|
|
102
|
+
icon={Award}
|
|
103
|
+
compact={compact}
|
|
104
|
+
>
|
|
105
|
+
{loading ? (
|
|
106
|
+
<div className="flex flex-col gap-2">
|
|
107
|
+
{Array.from({ length: 3 }).map((_, index) => (
|
|
108
|
+
<Skeleton key={index} className="h-14 w-full rounded-md" />
|
|
109
|
+
))}
|
|
110
|
+
</div>
|
|
111
|
+
) : totalItems === 0 ? (
|
|
112
|
+
<p className="py-3 text-center text-xs text-muted-foreground">
|
|
113
|
+
{t('empty')}
|
|
114
|
+
</p>
|
|
115
|
+
) : (
|
|
116
|
+
<div className="flex flex-col gap-2">
|
|
117
|
+
{certificates.map((certificate) => (
|
|
118
|
+
<div
|
|
119
|
+
key={certificate.id}
|
|
120
|
+
className="flex items-center gap-3 rounded-md border bg-background/80 px-2.5 py-2"
|
|
121
|
+
>
|
|
122
|
+
<div className="min-w-0 flex-1">
|
|
123
|
+
<p className="truncate text-xs font-medium">
|
|
124
|
+
{certificate.studentName}
|
|
125
|
+
</p>
|
|
126
|
+
<div className="mt-0.5 flex flex-wrap items-center gap-1.5 text-[0.65rem] text-muted-foreground">
|
|
127
|
+
<span>
|
|
128
|
+
{t('issuedAt')}: {formatDate(certificate.issuedAt)}
|
|
129
|
+
</span>
|
|
130
|
+
<span aria-hidden>·</span>
|
|
131
|
+
<span className="font-mono">
|
|
132
|
+
{certificate.verificationCode}
|
|
133
|
+
</span>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
{certificate.pdfUrl ? (
|
|
137
|
+
<Button
|
|
138
|
+
type="button"
|
|
139
|
+
variant="ghost"
|
|
140
|
+
size="icon"
|
|
141
|
+
className="size-7 shrink-0 text-muted-foreground hover:text-foreground"
|
|
142
|
+
aria-label={t('openPdf')}
|
|
143
|
+
onClick={() => window.open(certificate.pdfUrl ?? '', '_blank')}
|
|
144
|
+
>
|
|
145
|
+
<BadgeCheck className="size-4" />
|
|
146
|
+
</Button>
|
|
147
|
+
) : null}
|
|
148
|
+
</div>
|
|
149
|
+
))}
|
|
150
|
+
|
|
151
|
+
<div className="pt-1">
|
|
152
|
+
<PaginationFooter
|
|
153
|
+
currentPage={page}
|
|
154
|
+
pageSize={pageSize}
|
|
155
|
+
totalItems={totalItems}
|
|
156
|
+
onPageChange={setPage}
|
|
157
|
+
onPageSizeChange={(value) => {
|
|
158
|
+
setPageSize(value);
|
|
159
|
+
setPage(1);
|
|
160
|
+
}}
|
|
161
|
+
pageSizeOptions={PAGE_SIZES}
|
|
162
|
+
/>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
)}
|
|
166
|
+
</CourseSectionCard>
|
|
167
|
+
);
|
|
168
|
+
}
|