@hed-hog/lms 0.0.354 → 0.0.357
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/course/course-audio-transcription.service.d.ts.map +1 -1
- package/dist/course/course-audio-transcription.service.js +15 -7
- package/dist/course/course-audio-transcription.service.js.map +1 -1
- package/dist/course/course-structure.controller.d.ts +12 -0
- package/dist/course/course-structure.controller.d.ts.map +1 -1
- package/dist/course/course-structure.service.d.ts +22 -0
- package/dist/course/course-structure.service.d.ts.map +1 -1
- package/dist/course/course-structure.service.js +147 -18
- package/dist/course/course-structure.service.js.map +1 -1
- package/dist/course/course.service.d.ts +4 -2
- package/dist/course/course.service.d.ts.map +1 -1
- package/dist/course/course.service.js +61 -2
- package/dist/course/course.service.js.map +1 -1
- package/dist/course/dto/create-course-structure-lesson.dto.d.ts +3 -0
- package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
- package/dist/course/dto/create-course-structure-lesson.dto.js +15 -0
- package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
- package/dist/course/dto/create-course-structure-session.dto.d.ts +1 -0
- package/dist/course/dto/create-course-structure-session.dto.d.ts.map +1 -1
- package/dist/course/dto/create-course-structure-session.dto.js +5 -0
- package/dist/course/dto/create-course-structure-session.dto.js.map +1 -1
- package/dist/enterprise/training/training-student.controller.d.ts +0 -95
- package/dist/enterprise/training/training-student.controller.d.ts.map +1 -1
- package/dist/enterprise/training/training-student.controller.js +1 -34
- package/dist/enterprise/training/training-student.controller.js.map +1 -1
- package/dist/enterprise/training/training-student.service.d.ts +63 -0
- package/dist/enterprise/training/training-student.service.d.ts.map +1 -1
- package/dist/enterprise/training/training-student.service.js +320 -4
- package/dist/enterprise/training/training-student.service.js.map +1 -1
- package/dist/lms.module.d.ts.map +1 -1
- package/dist/lms.module.js +2 -0
- package/dist/lms.module.js.map +1 -1
- package/dist/platforma/platforma.controller.d.ts +287 -0
- package/dist/platforma/platforma.controller.d.ts.map +1 -0
- package/dist/platforma/platforma.controller.js +147 -0
- package/dist/platforma/platforma.controller.js.map +1 -0
- package/hedhog/data/route.yaml +75 -9
- package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +2 -2
- package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +10 -13
- package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/courses/[id]/_components/course-edit-types.ts.ejs +1 -1
- package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +17 -6
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +14 -7
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +92 -53
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +25 -60
- package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +1 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/store.ts.ejs +11 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-context-menu.tsx.ejs +22 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +79 -64
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +31 -14
- package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +3 -4
- package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +28 -24
- package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +20 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +41 -6
- package/hedhog/frontend/app/courses/page.tsx.ejs +59 -15
- package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +1 -1
- package/hedhog/frontend/app/enterprise/_components/enterprise-course-edit-sheet.tsx.ejs +2 -2
- package/hedhog/frontend/app/paths/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/training/page.tsx.ejs +1 -1
- package/hedhog/frontend/messages/en.json +7 -2
- package/hedhog/frontend/messages/pt.json +7 -2
- package/hedhog/table/course_lesson.yaml +2 -2
- package/hedhog/table/course_module.yaml +3 -0
- package/package.json +9 -9
- package/src/course/course-audio-transcription.service.ts +21 -8
- package/src/course/course-structure.service.ts +204 -3
- package/src/course/course.service.ts +67 -1
- package/src/course/dto/create-course-structure-lesson.dto.ts +17 -0
- package/src/course/dto/create-course-structure-session.dto.ts +13 -1
- package/src/enterprise/training/training-student.controller.ts +3 -27
- package/src/enterprise/training/training-student.service.ts +350 -2
- package/src/lms.module.ts +2 -0
- package/src/platforma/platforma.controller.ts +92 -0
package/hedhog/data/route.yaml
CHANGED
|
@@ -1867,7 +1867,7 @@
|
|
|
1867
1867
|
- where:
|
|
1868
1868
|
slug: lms-instructor
|
|
1869
1869
|
|
|
1870
|
-
- url: /lms/
|
|
1870
|
+
- url: /lms/platforma
|
|
1871
1871
|
method: GET
|
|
1872
1872
|
relations:
|
|
1873
1873
|
role:
|
|
@@ -1878,7 +1878,7 @@
|
|
|
1878
1878
|
- where:
|
|
1879
1879
|
slug: lms-student
|
|
1880
1880
|
|
|
1881
|
-
- url: /lms/
|
|
1881
|
+
- url: /lms/platforma/dashboard
|
|
1882
1882
|
method: GET
|
|
1883
1883
|
relations:
|
|
1884
1884
|
role:
|
|
@@ -1889,7 +1889,7 @@
|
|
|
1889
1889
|
- where:
|
|
1890
1890
|
slug: lms-student
|
|
1891
1891
|
|
|
1892
|
-
- url: /lms/
|
|
1892
|
+
- url: /lms/platforma/courses
|
|
1893
1893
|
method: GET
|
|
1894
1894
|
relations:
|
|
1895
1895
|
role:
|
|
@@ -1900,7 +1900,7 @@
|
|
|
1900
1900
|
- where:
|
|
1901
1901
|
slug: lms-student
|
|
1902
1902
|
|
|
1903
|
-
- url: /lms/
|
|
1903
|
+
- url: /lms/platforma/notifications
|
|
1904
1904
|
method: GET
|
|
1905
1905
|
relations:
|
|
1906
1906
|
role:
|
|
@@ -1911,7 +1911,7 @@
|
|
|
1911
1911
|
- where:
|
|
1912
1912
|
slug: lms-student
|
|
1913
1913
|
|
|
1914
|
-
- url: /lms/
|
|
1914
|
+
- url: /lms/platforma/notifications/unread-count
|
|
1915
1915
|
method: GET
|
|
1916
1916
|
relations:
|
|
1917
1917
|
role:
|
|
@@ -1922,8 +1922,19 @@
|
|
|
1922
1922
|
- where:
|
|
1923
1923
|
slug: lms-student
|
|
1924
1924
|
|
|
1925
|
-
- url: /lms/
|
|
1926
|
-
method:
|
|
1925
|
+
- url: /lms/platforma/notifications/read-all
|
|
1926
|
+
method: PATCH
|
|
1927
|
+
relations:
|
|
1928
|
+
role:
|
|
1929
|
+
- where:
|
|
1930
|
+
slug: admin
|
|
1931
|
+
- where:
|
|
1932
|
+
slug: admin-lms
|
|
1933
|
+
- where:
|
|
1934
|
+
slug: lms-student
|
|
1935
|
+
|
|
1936
|
+
- url: /lms/platforma/notifications/:id/read
|
|
1937
|
+
method: PATCH
|
|
1927
1938
|
relations:
|
|
1928
1939
|
role:
|
|
1929
1940
|
- where:
|
|
@@ -1933,7 +1944,7 @@
|
|
|
1933
1944
|
- where:
|
|
1934
1945
|
slug: lms-student
|
|
1935
1946
|
|
|
1936
|
-
- url: /lms/
|
|
1947
|
+
- url: /lms/platforma/streak
|
|
1937
1948
|
method: GET
|
|
1938
1949
|
relations:
|
|
1939
1950
|
role:
|
|
@@ -1944,7 +1955,7 @@
|
|
|
1944
1955
|
- where:
|
|
1945
1956
|
slug: lms-student
|
|
1946
1957
|
|
|
1947
|
-
- url: /lms/
|
|
1958
|
+
- url: /lms/platforma/streak/activities
|
|
1948
1959
|
method: POST
|
|
1949
1960
|
relations:
|
|
1950
1961
|
role:
|
|
@@ -1955,6 +1966,61 @@
|
|
|
1955
1966
|
- where:
|
|
1956
1967
|
slug: lms-student
|
|
1957
1968
|
|
|
1969
|
+
- url: /lms/enterprise/training/student/class-groups
|
|
1970
|
+
method: GET
|
|
1971
|
+
relations:
|
|
1972
|
+
role:
|
|
1973
|
+
- where:
|
|
1974
|
+
slug: admin
|
|
1975
|
+
- where:
|
|
1976
|
+
slug: admin-lms
|
|
1977
|
+
- where:
|
|
1978
|
+
slug: lms-student
|
|
1979
|
+
|
|
1980
|
+
- url: /lms/enterprise/training/student/class-groups/:id
|
|
1981
|
+
method: GET
|
|
1982
|
+
relations:
|
|
1983
|
+
role:
|
|
1984
|
+
- where:
|
|
1985
|
+
slug: admin
|
|
1986
|
+
- where:
|
|
1987
|
+
slug: admin-lms
|
|
1988
|
+
- where:
|
|
1989
|
+
slug: lms-student
|
|
1990
|
+
|
|
1991
|
+
- url: /lms/enterprise/training/student/class-groups/:id/sessions
|
|
1992
|
+
method: GET
|
|
1993
|
+
relations:
|
|
1994
|
+
role:
|
|
1995
|
+
- where:
|
|
1996
|
+
slug: admin
|
|
1997
|
+
- where:
|
|
1998
|
+
slug: admin-lms
|
|
1999
|
+
- where:
|
|
2000
|
+
slug: lms-student
|
|
2001
|
+
|
|
2002
|
+
- url: /lms/enterprise/training/student/class-groups/:id/materials
|
|
2003
|
+
method: GET
|
|
2004
|
+
relations:
|
|
2005
|
+
role:
|
|
2006
|
+
- where:
|
|
2007
|
+
slug: admin
|
|
2008
|
+
- where:
|
|
2009
|
+
slug: admin-lms
|
|
2010
|
+
- where:
|
|
2011
|
+
slug: lms-student
|
|
2012
|
+
|
|
2013
|
+
- url: /lms/enterprise/training/student/class-groups/:id/my-evaluations
|
|
2014
|
+
method: GET
|
|
2015
|
+
relations:
|
|
2016
|
+
role:
|
|
2017
|
+
- where:
|
|
2018
|
+
slug: admin
|
|
2019
|
+
- where:
|
|
2020
|
+
slug: admin-lms
|
|
2021
|
+
- where:
|
|
2022
|
+
slug: lms-student
|
|
2023
|
+
|
|
1958
2024
|
- url: /lms/enterprise/training/admin/students
|
|
1959
2025
|
method: GET
|
|
1960
2026
|
relations:
|
|
@@ -365,7 +365,7 @@ function toApiCourseLevel(level: CourseSheetFormValues['nivel']) {
|
|
|
365
365
|
}
|
|
366
366
|
|
|
367
367
|
function toApiCourseStatus(status: CourseSheetFormValues['status']) {
|
|
368
|
-
if (status === '
|
|
368
|
+
if (status === 'publicado') return 'published';
|
|
369
369
|
if (status === 'rascunho') return 'draft';
|
|
370
370
|
return 'archived';
|
|
371
371
|
}
|
|
@@ -377,7 +377,7 @@ function toCourseFormLevel(level?: string): CourseSheetFormValues['nivel'] {
|
|
|
377
377
|
}
|
|
378
378
|
|
|
379
379
|
function toCourseFormStatus(status?: string): CourseSheetFormValues['status'] {
|
|
380
|
-
if (status === 'published') return '
|
|
380
|
+
if (status === 'published') return 'publicado';
|
|
381
381
|
if (status === 'archived') return 'arquivado';
|
|
382
382
|
return 'rascunho';
|
|
383
383
|
}
|
|
@@ -9,19 +9,15 @@ import {
|
|
|
9
9
|
CommandItem,
|
|
10
10
|
CommandList,
|
|
11
11
|
} from '@/components/ui/command';
|
|
12
|
-
import {
|
|
13
|
-
Field,
|
|
14
|
-
FieldDescription,
|
|
15
|
-
FieldError,
|
|
16
|
-
FieldLabel,
|
|
17
|
-
} from '@/components/ui/field';
|
|
18
12
|
import { EntityPicker } from '@/components/ui/entity-picker';
|
|
13
|
+
import { Field, FieldError, FieldLabel } from '@/components/ui/field';
|
|
19
14
|
import { Input } from '@/components/ui/input';
|
|
20
15
|
import {
|
|
21
16
|
Popover,
|
|
22
17
|
PopoverContent,
|
|
23
18
|
PopoverTrigger,
|
|
24
19
|
} from '@/components/ui/popover';
|
|
20
|
+
import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
|
|
25
21
|
import {
|
|
26
22
|
Select,
|
|
27
23
|
SelectContent,
|
|
@@ -36,7 +32,6 @@ import {
|
|
|
36
32
|
SheetHeader,
|
|
37
33
|
SheetTitle,
|
|
38
34
|
} from '@/components/ui/sheet';
|
|
39
|
-
import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
|
|
40
35
|
import { Textarea } from '@/components/ui/textarea';
|
|
41
36
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
42
37
|
import {
|
|
@@ -235,7 +230,7 @@ export function getCourseSheetSchema(t: (key: string) => string) {
|
|
|
235
230
|
tituloComercial: z.string().optional(),
|
|
236
231
|
descricao: z.string().optional(),
|
|
237
232
|
nivel: z.enum(['iniciante', 'intermediario', 'avancado']).optional(),
|
|
238
|
-
status: z.enum(['
|
|
233
|
+
status: z.enum(['publicado', 'rascunho', 'arquivado']).optional(),
|
|
239
234
|
offeringType: z.enum(['on_demand', 'agendado', 'hibrido']).optional(),
|
|
240
235
|
categorias: z.array(z.string()).optional(),
|
|
241
236
|
operationsProjectId: z.string().optional(),
|
|
@@ -252,7 +247,7 @@ export type CourseSheetFormValues = {
|
|
|
252
247
|
tituloComercial: string;
|
|
253
248
|
descricao: string;
|
|
254
249
|
nivel: 'iniciante' | 'intermediario' | 'avancado';
|
|
255
|
-
status: '
|
|
250
|
+
status: 'publicado' | 'rascunho' | 'arquivado';
|
|
256
251
|
offeringType: 'on_demand' | 'agendado' | 'hibrido';
|
|
257
252
|
categorias: string[];
|
|
258
253
|
operationsProjectId: string;
|
|
@@ -624,7 +619,9 @@ export function CourseFormSheet({
|
|
|
624
619
|
</Select>
|
|
625
620
|
)}
|
|
626
621
|
/>
|
|
627
|
-
<FieldError>
|
|
622
|
+
<FieldError>
|
|
623
|
+
{form.formState.errors.offeringType?.message}
|
|
624
|
+
</FieldError>
|
|
628
625
|
</Field>
|
|
629
626
|
</div>
|
|
630
627
|
|
|
@@ -719,8 +716,8 @@ export function CourseFormSheet({
|
|
|
719
716
|
/>
|
|
720
717
|
</SelectTrigger>
|
|
721
718
|
<SelectContent>
|
|
722
|
-
<SelectItem value="
|
|
723
|
-
{t('status.
|
|
719
|
+
<SelectItem value="publicado">
|
|
720
|
+
{t('status.published')}
|
|
724
721
|
</SelectItem>
|
|
725
722
|
<SelectItem value="rascunho">
|
|
726
723
|
{t('status.draft')}
|
|
@@ -959,7 +956,7 @@ export function CourseFormSheet({
|
|
|
959
956
|
</Button>
|
|
960
957
|
</SheetFooter>
|
|
961
958
|
</form>
|
|
962
|
-
|
|
959
|
+
</ResizableSheetContent>
|
|
963
960
|
</Sheet>
|
|
964
961
|
);
|
|
965
962
|
}
|
|
@@ -547,7 +547,7 @@ function toCourseFormLevel(level?: string): CourseSheetFormValues['nivel'] {
|
|
|
547
547
|
return 'iniciante';
|
|
548
548
|
}
|
|
549
549
|
function toCourseFormStatus(status?: string): CourseSheetFormValues['status'] {
|
|
550
|
-
if (status === 'published') return '
|
|
550
|
+
if (status === 'published') return 'publicado';
|
|
551
551
|
if (status === 'archived') return 'arquivado';
|
|
552
552
|
return 'rascunho';
|
|
553
553
|
}
|
|
@@ -557,7 +557,7 @@ function toApiCourseLevel(nivel: CourseSheetFormValues['nivel']) {
|
|
|
557
557
|
return 'beginner';
|
|
558
558
|
}
|
|
559
559
|
function toApiCourseStatus(status: CourseSheetFormValues['status']) {
|
|
560
|
-
if (status === '
|
|
560
|
+
if (status === 'publicado') return 'published';
|
|
561
561
|
if (status === 'arquivado') return 'archived';
|
|
562
562
|
return 'draft';
|
|
563
563
|
}
|
|
@@ -7,7 +7,7 @@ export type CourseEditFormValues = {
|
|
|
7
7
|
objetivos: string;
|
|
8
8
|
publicoAlvo: string;
|
|
9
9
|
nivel: 'iniciante' | 'intermediario' | 'avancado';
|
|
10
|
-
status: '
|
|
10
|
+
status: 'publicado' | 'rascunho' | 'arquivado';
|
|
11
11
|
tipoOferta: 'agendado' | 'sob_demanda' | 'hibrido';
|
|
12
12
|
localeId: string;
|
|
13
13
|
categorias: string[];
|
|
@@ -16,7 +16,7 @@ import { useStructureStore } from './store';
|
|
|
16
16
|
export function ConfirmDialog() {
|
|
17
17
|
const t = useTranslations('lms.CoursesPage.StructurePage.confirmDialog');
|
|
18
18
|
const confirmDialog = useStructureStore((s) => s.confirmDialog);
|
|
19
|
-
const closeConfirm
|
|
19
|
+
const closeConfirm = useStructureStore((s) => s.closeConfirm);
|
|
20
20
|
|
|
21
21
|
function handleConfirm() {
|
|
22
22
|
confirmDialog.onConfirm?.();
|
|
@@ -24,21 +24,32 @@ export function ConfirmDialog() {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
return (
|
|
27
|
-
<AlertDialog
|
|
27
|
+
<AlertDialog
|
|
28
|
+
open={confirmDialog.open}
|
|
29
|
+
onOpenChange={(open) => !open && closeConfirm()}
|
|
30
|
+
>
|
|
28
31
|
<AlertDialogContent>
|
|
29
32
|
<AlertDialogHeader>
|
|
30
33
|
<AlertDialogTitle>{confirmDialog.title}</AlertDialogTitle>
|
|
31
34
|
{confirmDialog.description && (
|
|
32
|
-
<AlertDialogDescription>
|
|
35
|
+
<AlertDialogDescription>
|
|
36
|
+
{confirmDialog.description}
|
|
37
|
+
</AlertDialogDescription>
|
|
33
38
|
)}
|
|
34
39
|
</AlertDialogHeader>
|
|
35
40
|
<AlertDialogFooter>
|
|
36
|
-
<AlertDialogCancel onClick={closeConfirm}>
|
|
41
|
+
<AlertDialogCancel onClick={closeConfirm}>
|
|
42
|
+
{t('cancel')}
|
|
43
|
+
</AlertDialogCancel>
|
|
37
44
|
<AlertDialogAction
|
|
38
45
|
onClick={handleConfirm}
|
|
39
|
-
className=
|
|
46
|
+
className={
|
|
47
|
+
confirmDialog.destructive
|
|
48
|
+
? 'bg-destructive text-destructive-foreground hover:bg-destructive/90'
|
|
49
|
+
: undefined
|
|
50
|
+
}
|
|
40
51
|
>
|
|
41
|
-
{t('confirm')}
|
|
52
|
+
{confirmDialog.confirmText || t('confirm')}
|
|
42
53
|
</AlertDialogAction>
|
|
43
54
|
</AlertDialogFooter>
|
|
44
55
|
</AlertDialogContent>
|
|
@@ -245,13 +245,20 @@ function toPtStatus(
|
|
|
245
245
|
status: ApiCourseDetail['status']
|
|
246
246
|
): CourseEditFormValues['status'] {
|
|
247
247
|
const n = normalizeEnumValue(status);
|
|
248
|
-
if (
|
|
248
|
+
if (
|
|
249
|
+
n === 'published' ||
|
|
250
|
+
n === 'active' ||
|
|
251
|
+
n === 'ativo' ||
|
|
252
|
+
n === 'publicado'
|
|
253
|
+
) {
|
|
254
|
+
return 'publicado';
|
|
255
|
+
}
|
|
249
256
|
if (n === 'archived' || n === 'arquivado') return 'arquivado';
|
|
250
257
|
return 'rascunho';
|
|
251
258
|
}
|
|
252
259
|
|
|
253
260
|
function toApiStatus(status: CourseEditFormValues['status']) {
|
|
254
|
-
if (status === '
|
|
261
|
+
if (status === 'publicado') return 'published';
|
|
255
262
|
if (status === 'arquivado') return 'archived';
|
|
256
263
|
return 'draft';
|
|
257
264
|
}
|
|
@@ -350,7 +357,7 @@ function buildSchema(t: (key: string) => string) {
|
|
|
350
357
|
objetivos: z.string().optional(),
|
|
351
358
|
publicoAlvo: z.string().optional(),
|
|
352
359
|
nivel: z.enum(['iniciante', 'intermediario', 'avancado']),
|
|
353
|
-
status: z.enum(['
|
|
360
|
+
status: z.enum(['publicado', 'rascunho', 'arquivado']),
|
|
354
361
|
tipoOferta: z.enum(['agendado', 'sob_demanda', 'hibrido']),
|
|
355
362
|
localeId: z.string().optional().default(''),
|
|
356
363
|
categorias: z.array(z.string()).optional().default([]),
|
|
@@ -596,7 +603,7 @@ export function EditorCourse() {
|
|
|
596
603
|
slug: data.slug,
|
|
597
604
|
name: data.nomeInterno,
|
|
598
605
|
description: data.descricaoPublica,
|
|
599
|
-
published: data.status === '
|
|
606
|
+
published: data.status === 'publicado',
|
|
600
607
|
});
|
|
601
608
|
void queryClient.invalidateQueries({
|
|
602
609
|
queryKey: courseStructureQueryKey(courseId),
|
|
@@ -653,7 +660,7 @@ export function EditorCourse() {
|
|
|
653
660
|
);
|
|
654
661
|
const STATUS_OPTIONS = useMemo(
|
|
655
662
|
() => [
|
|
656
|
-
{ value: '
|
|
663
|
+
{ value: 'publicado', label: t('status.published') },
|
|
657
664
|
{ value: 'rascunho', label: t('status.draft') },
|
|
658
665
|
{ value: 'arquivado', label: t('status.archived') },
|
|
659
666
|
],
|
|
@@ -1366,7 +1373,7 @@ export function EditorCourse() {
|
|
|
1366
1373
|
}
|
|
1367
1374
|
|
|
1368
1375
|
function onSubmit(data: CourseEditFormValues) {
|
|
1369
|
-
if (data.status === '
|
|
1376
|
+
if (data.status === 'publicado') {
|
|
1370
1377
|
const requiredProfileIds = new Set(linkedProfileIds);
|
|
1371
1378
|
const invalidLesson = lessons.find((lesson) => {
|
|
1372
1379
|
if (
|
|
@@ -1537,7 +1544,7 @@ export function EditorCourse() {
|
|
|
1537
1544
|
>
|
|
1538
1545
|
<TabsList
|
|
1539
1546
|
className={cn(
|
|
1540
|
-
'
|
|
1547
|
+
'mt-0 h-auto w-full shrink-0 rounded-none border-b bg-muted/50 px-2 py-1 sm:px-3',
|
|
1541
1548
|
'flex items-center gap-1 overflow-x-auto overflow-y-hidden whitespace-nowrap',
|
|
1542
1549
|
isMobile
|
|
1543
1550
|
? 'touch-pan-x snap-x snap-mandatory [scrollbar-width:thin] [&::-webkit-scrollbar]:h-1.5 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-muted-foreground/35'
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
Download,
|
|
14
14
|
ExternalLink,
|
|
15
15
|
Eye,
|
|
16
|
-
EyeOff,
|
|
17
16
|
FileText,
|
|
18
17
|
GripVertical,
|
|
19
18
|
HelpCircle,
|
|
@@ -39,6 +38,7 @@ import { useForm, useWatch } from 'react-hook-form';
|
|
|
39
38
|
import { toast } from 'sonner';
|
|
40
39
|
import { z } from 'zod';
|
|
41
40
|
|
|
41
|
+
import { CopyButton } from '@/components/copy-button';
|
|
42
42
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
|
43
43
|
import { Badge } from '@/components/ui/badge';
|
|
44
44
|
import { Button } from '@/components/ui/button';
|
|
@@ -140,6 +140,8 @@ import type {
|
|
|
140
140
|
VideoProvider,
|
|
141
141
|
} from './types';
|
|
142
142
|
|
|
143
|
+
const EMPTY_VIDEO_FRAMES: VideoFrame[] = [];
|
|
144
|
+
|
|
143
145
|
function formatFileSize(bytes: number): string {
|
|
144
146
|
if (bytes < 1024) return `${bytes} B`;
|
|
145
147
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -526,7 +528,7 @@ type FormValues = {
|
|
|
526
528
|
type: LessonType;
|
|
527
529
|
duration: number;
|
|
528
530
|
status: LessonStatus;
|
|
529
|
-
|
|
531
|
+
published: boolean;
|
|
530
532
|
publicDescription: string;
|
|
531
533
|
privateDescription: string;
|
|
532
534
|
videoProvider?: VideoProvider;
|
|
@@ -764,13 +766,14 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
764
766
|
const lesson = useStructureStore((s) =>
|
|
765
767
|
s.lessons.find((l) => l.id === lessonId)
|
|
766
768
|
);
|
|
769
|
+
const sessions = useStructureStore((s) => s.sessions);
|
|
767
770
|
const persistedVideoProvider: VideoProvider | undefined =
|
|
768
771
|
lesson?.videoProvider === 'youtube' || lesson?.videoProvider === 'vimeo'
|
|
769
772
|
? lesson.videoProvider
|
|
770
773
|
: lesson?.videoProvider
|
|
771
774
|
? 'file_storage'
|
|
772
775
|
: undefined;
|
|
773
|
-
const videoFrames = lesson?.frames ??
|
|
776
|
+
const videoFrames = lesson?.frames ?? EMPTY_VIDEO_FRAMES;
|
|
774
777
|
const updateLesson = useUpdateLessonMutation();
|
|
775
778
|
const updateTranscriptionSegments = useUpdateTranscriptionSegmentsMutation(
|
|
776
779
|
lesson?.id ?? null
|
|
@@ -792,7 +795,10 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
792
795
|
[
|
|
793
796
|
{ value: 'youtube' as VideoProvider, label: 'YouTube' },
|
|
794
797
|
{ value: 'vimeo' as VideoProvider, label: 'Vimeo' },
|
|
795
|
-
{
|
|
798
|
+
{
|
|
799
|
+
value: 'file_storage' as VideoProvider,
|
|
800
|
+
label: t('providers.fileStorage'),
|
|
801
|
+
},
|
|
796
802
|
] as { value: VideoProvider; label: string }[]
|
|
797
803
|
).filter((p) => {
|
|
798
804
|
if (p.value === 'youtube') return lmsSettings.youtubeEnabled;
|
|
@@ -819,7 +825,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
819
825
|
'finalizada',
|
|
820
826
|
'publicada',
|
|
821
827
|
] as const),
|
|
822
|
-
|
|
828
|
+
published: z.boolean(),
|
|
823
829
|
publicDescription: z.string(),
|
|
824
830
|
privateDescription: z.string(),
|
|
825
831
|
videoProvider: z
|
|
@@ -842,7 +848,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
842
848
|
type: lesson?.type ?? 'video',
|
|
843
849
|
duration: lesson?.duration ?? 0,
|
|
844
850
|
status: lesson?.status ?? 'preparada',
|
|
845
|
-
|
|
851
|
+
published: lesson?.published ?? false,
|
|
846
852
|
publicDescription: lesson?.publicDescription ?? '',
|
|
847
853
|
privateDescription: lesson?.privateDescription ?? '',
|
|
848
854
|
videoProvider:
|
|
@@ -1145,15 +1151,21 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1145
1151
|
const nextEntries = Object.entries(current).filter(([id]) =>
|
|
1146
1152
|
frameIds.has(id)
|
|
1147
1153
|
);
|
|
1154
|
+
if (nextEntries.length === Object.keys(current).length) return current;
|
|
1148
1155
|
return Object.fromEntries(nextEntries);
|
|
1149
1156
|
});
|
|
1150
1157
|
|
|
1151
1158
|
setFrameImageErrorIds((current) => {
|
|
1159
|
+
let changed = false;
|
|
1152
1160
|
const next = new Set<string>();
|
|
1153
1161
|
for (const id of current) {
|
|
1154
|
-
if (frameIds.has(id))
|
|
1162
|
+
if (frameIds.has(id)) {
|
|
1163
|
+
next.add(id);
|
|
1164
|
+
} else {
|
|
1165
|
+
changed = true;
|
|
1166
|
+
}
|
|
1155
1167
|
}
|
|
1156
|
-
return next;
|
|
1168
|
+
return changed ? next : current;
|
|
1157
1169
|
});
|
|
1158
1170
|
|
|
1159
1171
|
const frameSignatures = new Set(
|
|
@@ -1219,12 +1231,22 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1219
1231
|
if (cancelled) return;
|
|
1220
1232
|
|
|
1221
1233
|
setFrameAssetMetadataById((current) => {
|
|
1234
|
+
let changed = false;
|
|
1222
1235
|
const next = { ...current };
|
|
1223
1236
|
for (const result of results) {
|
|
1224
1237
|
if (result.status !== 'fulfilled') continue;
|
|
1225
|
-
|
|
1238
|
+
const currentMetadata = current[result.value.frameId];
|
|
1239
|
+
const nextMetadata = result.value.metadata;
|
|
1240
|
+
if (
|
|
1241
|
+
currentMetadata?.url === nextMetadata.url &&
|
|
1242
|
+
currentMetadata?.sizeLabel === nextMetadata.sizeLabel
|
|
1243
|
+
) {
|
|
1244
|
+
continue;
|
|
1245
|
+
}
|
|
1246
|
+
next[result.value.frameId] = nextMetadata;
|
|
1247
|
+
changed = true;
|
|
1226
1248
|
}
|
|
1227
|
-
return next;
|
|
1249
|
+
return changed ? next : current;
|
|
1228
1250
|
});
|
|
1229
1251
|
});
|
|
1230
1252
|
|
|
@@ -1365,6 +1387,10 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
1365
1387
|
const cfg = TYPE_CONFIG[lesson.type];
|
|
1366
1388
|
const Icon = cfg.icon;
|
|
1367
1389
|
const lessonTypeLabel = t(cfg.labelKey);
|
|
1390
|
+
const session = sessions.find((s) => s.id === lesson.sessionId);
|
|
1391
|
+
const lessonFullCode = session
|
|
1392
|
+
? `${session.code}_${lesson.code}_${lesson.title}`
|
|
1393
|
+
: null;
|
|
1368
1394
|
const originalVideoResource =
|
|
1369
1395
|
localResources.find((res) => res.type === 'video_original') ?? null;
|
|
1370
1396
|
const isDownloadingOriginalVideo = originalVideoResource
|
|
@@ -2392,7 +2418,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
2392
2418
|
setTranscriptionSegments(updater);
|
|
2393
2419
|
}
|
|
2394
2420
|
|
|
2395
|
-
function
|
|
2421
|
+
function persistLesson(values: FormValues, shouldConfirmAutoStatus: boolean) {
|
|
2396
2422
|
if (values.type === 'video') {
|
|
2397
2423
|
const segmentsPayload = transcriptionSegments
|
|
2398
2424
|
.map((segment) => ({
|
|
@@ -2428,6 +2454,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
2428
2454
|
sessionId: lesson!.sessionId,
|
|
2429
2455
|
formValues: {
|
|
2430
2456
|
...values,
|
|
2457
|
+
confirmarPublicacaoComStatus: shouldConfirmAutoStatus,
|
|
2431
2458
|
videoUrl:
|
|
2432
2459
|
values.type === 'video' && values.videoProvider === 'file_storage'
|
|
2433
2460
|
? ''
|
|
@@ -2439,15 +2466,39 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
2439
2466
|
},
|
|
2440
2467
|
},
|
|
2441
2468
|
{
|
|
2442
|
-
onSuccess: () => {
|
|
2469
|
+
onSuccess: (updatedLesson) => {
|
|
2443
2470
|
persistedInstructorIdsRef.current = [...selectedInstructorIds];
|
|
2444
|
-
form.reset({
|
|
2471
|
+
form.reset({
|
|
2472
|
+
...values,
|
|
2473
|
+
status: updatedLesson.statusProducao ?? values.status,
|
|
2474
|
+
published: updatedLesson.published ?? values.published,
|
|
2475
|
+
transcription: updatedLesson.transcricao ?? values.transcription,
|
|
2476
|
+
});
|
|
2445
2477
|
setResourcesDirty(false);
|
|
2446
2478
|
},
|
|
2447
2479
|
}
|
|
2448
2480
|
);
|
|
2449
2481
|
}
|
|
2450
2482
|
|
|
2483
|
+
function onSubmit(values: FormValues) {
|
|
2484
|
+
const shouldConfirmAutoStatus =
|
|
2485
|
+
values.published && values.status !== 'publicada';
|
|
2486
|
+
|
|
2487
|
+
if (shouldConfirmAutoStatus) {
|
|
2488
|
+
showConfirm({
|
|
2489
|
+
title: 'Confirmar publicação da aula?',
|
|
2490
|
+
description:
|
|
2491
|
+
'Esta aula será publicada e o status de produção será alterado automaticamente para Publicada. Deseja continuar?',
|
|
2492
|
+
confirmText: 'Publicar',
|
|
2493
|
+
destructive: false,
|
|
2494
|
+
onConfirm: () => persistLesson(values, true),
|
|
2495
|
+
});
|
|
2496
|
+
return;
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
persistLesson(values, false);
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2451
2502
|
// ── Question sheet helpers ────────────────────────────────────────────────
|
|
2452
2503
|
|
|
2453
2504
|
function openCreateQuestion() {
|
|
@@ -2581,15 +2632,29 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
2581
2632
|
<div className="flex-1 min-w-0">
|
|
2582
2633
|
<div className="flex items-center gap-1.5">
|
|
2583
2634
|
<span className="text-sm font-semibold truncate">
|
|
2584
|
-
{
|
|
2635
|
+
{lesson.title}
|
|
2585
2636
|
</span>
|
|
2586
2637
|
{isDirty && (
|
|
2587
2638
|
<CircleDot className="size-3 text-amber-500 shrink-0" />
|
|
2588
2639
|
)}
|
|
2589
2640
|
</div>
|
|
2590
|
-
<
|
|
2591
|
-
|
|
2592
|
-
|
|
2641
|
+
<div className="flex min-w-0 flex-wrap items-center gap-x-1.5 gap-y-1 text-[0.65rem] text-muted-foreground">
|
|
2642
|
+
<span className="shrink-0">
|
|
2643
|
+
{lesson.code} · {lessonTypeLabel}
|
|
2644
|
+
</span>
|
|
2645
|
+
{lessonFullCode && (
|
|
2646
|
+
<>
|
|
2647
|
+
<span className="text-muted-foreground/60">·</span>
|
|
2648
|
+
<span className="max-w-full truncate rounded border border-border/60 bg-background/70 px-1.5 py-0.5 font-mono text-[0.62rem] text-foreground/80">
|
|
2649
|
+
{lessonFullCode}
|
|
2650
|
+
</span>
|
|
2651
|
+
<CopyButton
|
|
2652
|
+
value={lessonFullCode}
|
|
2653
|
+
className="size-5 shrink-0 text-muted-foreground"
|
|
2654
|
+
/>
|
|
2655
|
+
</>
|
|
2656
|
+
)}
|
|
2657
|
+
</div>
|
|
2593
2658
|
</div>
|
|
2594
2659
|
{watchedStatus && (
|
|
2595
2660
|
<span
|
|
@@ -2629,7 +2694,7 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
2629
2694
|
onValueChange={(value) => setActiveTab(value as LessonEditorTab)}
|
|
2630
2695
|
className="flex flex-col flex-1 min-h-0 min-w-0"
|
|
2631
2696
|
>
|
|
2632
|
-
<TabsList className="
|
|
2697
|
+
<TabsList className="mt-0 h-auto w-full justify-start shrink-0 rounded-none border-b bg-muted/50 px-2 py-1 overflow-x-auto overflow-y-hidden whitespace-nowrap sm:px-3">
|
|
2633
2698
|
<TabsTrigger
|
|
2634
2699
|
value="dados"
|
|
2635
2700
|
className="h-6 px-2 text-[11px] shrink-0 sm:h-7 sm:px-2.5 sm:text-xs"
|
|
@@ -2848,42 +2913,16 @@ export function EditorLesson({ lessonId }: EditorLessonProps) {
|
|
|
2848
2913
|
|
|
2849
2914
|
<FormField
|
|
2850
2915
|
control={form.control}
|
|
2851
|
-
name="
|
|
2916
|
+
name="published"
|
|
2852
2917
|
render={({ field }) => (
|
|
2853
|
-
<FormItem>
|
|
2854
|
-
<FormLabel className="text-xs">
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
>
|
|
2861
|
-
<FormControl>
|
|
2862
|
-
<SelectTrigger className="h-8 text-xs w-full">
|
|
2863
|
-
<SelectValue />
|
|
2864
|
-
</SelectTrigger>
|
|
2865
|
-
</FormControl>
|
|
2866
|
-
<SelectContent>
|
|
2867
|
-
<SelectItem value="publico">
|
|
2868
|
-
<span className="flex items-center gap-1.5">
|
|
2869
|
-
<Eye className="size-3" />{' '}
|
|
2870
|
-
{t('lessonForm.public')}
|
|
2871
|
-
</span>
|
|
2872
|
-
</SelectItem>
|
|
2873
|
-
<SelectItem value="privado">
|
|
2874
|
-
<span className="flex items-center gap-1.5">
|
|
2875
|
-
<EyeOff className="size-3" />{' '}
|
|
2876
|
-
{t('lessonForm.private')}
|
|
2877
|
-
</span>
|
|
2878
|
-
</SelectItem>
|
|
2879
|
-
<SelectItem value="restrito">
|
|
2880
|
-
<span className="flex items-center gap-1.5">
|
|
2881
|
-
<Lock className="size-3" />{' '}
|
|
2882
|
-
{t('questionEditor.restricted')}
|
|
2883
|
-
</span>
|
|
2884
|
-
</SelectItem>
|
|
2885
|
-
</SelectContent>
|
|
2886
|
-
</Select>
|
|
2918
|
+
<FormItem className="flex items-center justify-between gap-3 rounded-md border px-3 h-8">
|
|
2919
|
+
<FormLabel className="text-xs">Publicada</FormLabel>
|
|
2920
|
+
<FormControl>
|
|
2921
|
+
<Switch
|
|
2922
|
+
checked={field.value}
|
|
2923
|
+
onCheckedChange={field.onChange}
|
|
2924
|
+
/>
|
|
2925
|
+
</FormControl>
|
|
2887
2926
|
<FormMessage className="text-xs" />
|
|
2888
2927
|
</FormItem>
|
|
2889
2928
|
)}
|