@hed-hog/lms 0.0.328 → 0.0.330
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/instructor/instructor.service.d.ts.map +1 -1
- package/dist/instructor/instructor.service.js +22 -16
- package/dist/instructor/instructor.service.js.map +1 -1
- package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +18 -8
- package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +7 -5
- package/hedhog/frontend/app/_components/create-lms-person-sheet.tsx.ejs +5 -9
- package/hedhog/frontend/app/_components/create-lms-student-person-sheet.tsx.ejs +5 -9
- package/hedhog/frontend/app/certificates/models/LeftPanel.tsx.ejs +15 -14
- package/hedhog/frontend/app/certificates/models/RightPanel.tsx.ejs +66 -29
- package/hedhog/frontend/app/certificates/models/TemplateEditorPage.tsx.ejs +4 -2
- package/hedhog/frontend/app/certificates/models/TopBar.tsx.ejs +44 -34
- package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +10 -10
- package/hedhog/frontend/app/classes/page.tsx.ejs +23 -15
- package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +5 -3
- package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +5 -3
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-panel.tsx.ejs +9 -7
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-skeleton.tsx.ejs +3 -1
- package/hedhog/frontend/app/courses/[id]/structure/_components/drag-handle.tsx.ejs +4 -2
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +24 -23
- package/hedhog/frontend/app/courses/[id]/structure/_components/multi-select-bar.tsx.ejs +21 -19
- package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +7 -5
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs +18 -16
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +13 -11
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +5 -3
- package/hedhog/frontend/app/courses/[id]/structure/_components/use-course-structure-shortcuts.ts.ejs +14 -9
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +42 -25
- package/hedhog/frontend/app/enterprise/_components/enterprise-admin-create-sheet.tsx.ejs +3 -1
- package/hedhog/frontend/app/enterprise/_components/enterprise-administrators-tab.tsx.ejs +10 -8
- package/hedhog/frontend/app/enterprise/_components/enterprise-classes-tab.tsx.ejs +22 -20
- package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +3 -3
- package/hedhog/frontend/app/enterprise/_components/enterprise-courses-tab.tsx.ejs +21 -19
- package/hedhog/frontend/app/enterprise/_components/enterprise-sheet.tsx.ejs +34 -36
- package/hedhog/frontend/app/enterprise/_components/enterprise-student-create-sheet.tsx.ejs +3 -1
- package/hedhog/frontend/app/enterprise/_components/enterprise-students-tab.tsx.ejs +7 -5
- package/hedhog/frontend/app/enterprise/page.tsx.ejs +106 -54
- package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +79 -59
- package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +92 -26
- package/hedhog/frontend/app/instructors/page.tsx.ejs +4 -2
- package/hedhog/frontend/messages/en.json +619 -13
- package/hedhog/frontend/messages/pt.json +619 -13
- package/package.json +7 -7
- package/src/instructor/instructor.service.ts +22 -19
- package/hedhog/frontend/app/_components/create-lms-instructor-sheet.tsx.ejs +0 -591
- package/hedhog/frontend/app/courses/[id]/_components/CourseMainInfoCard.tsx.ejs +0 -109
- package/hedhog/frontend/app/courses/[id]/_components/CourseSummaryCard.tsx.ejs +0 -60
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +0 -134
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-course.tsx.ejs +0 -113
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +0 -314
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +0 -174
- package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +0 -185
- package/hedhog/frontend/app/enterprise/_components/enterprise-mocks.ts.ejs +0 -277
- package/hedhog/frontend/app/enterprise/_components/enterprise-user-create-sheet.tsx.ejs +0 -207
|
@@ -1522,7 +1522,7 @@ export default function TurmasPage() {
|
|
|
1522
1522
|
setCourseSheetOpen(false);
|
|
1523
1523
|
toast.success(courseSheetT('toasts.courseCreated'));
|
|
1524
1524
|
} catch {
|
|
1525
|
-
toast.error('
|
|
1525
|
+
toast.error(t('messages.classCreateCourseError'));
|
|
1526
1526
|
} finally {
|
|
1527
1527
|
setSavingCourse(false);
|
|
1528
1528
|
}
|
|
@@ -1606,7 +1606,7 @@ export default function TurmasPage() {
|
|
|
1606
1606
|
notifyLmsDashboardUpdated();
|
|
1607
1607
|
setSheetOpen(false);
|
|
1608
1608
|
} catch {
|
|
1609
|
-
toast.error('
|
|
1609
|
+
toast.error(t('messages.classSaveError'));
|
|
1610
1610
|
} finally {
|
|
1611
1611
|
setSaving(false);
|
|
1612
1612
|
}
|
|
@@ -1627,7 +1627,7 @@ export default function TurmasPage() {
|
|
|
1627
1627
|
setTurmaToDelete(null);
|
|
1628
1628
|
setDeleteDialogOpen(false);
|
|
1629
1629
|
} catch {
|
|
1630
|
-
toast.error('
|
|
1630
|
+
toast.error(t('messages.classDeleteError'));
|
|
1631
1631
|
}
|
|
1632
1632
|
}
|
|
1633
1633
|
|
|
@@ -2321,9 +2321,15 @@ export default function TurmasPage() {
|
|
|
2321
2321
|
entityLabel={t('form.fields.course.label')}
|
|
2322
2322
|
initialSelectedLabel={selectedCourseTitle}
|
|
2323
2323
|
searchPlaceholder={t('form.fields.course.placeholder')}
|
|
2324
|
-
emptyStateDescription=
|
|
2325
|
-
|
|
2326
|
-
|
|
2324
|
+
emptyStateDescription={t(
|
|
2325
|
+
'components.entityPicker.courses.empty'
|
|
2326
|
+
)}
|
|
2327
|
+
loadingLabel={t(
|
|
2328
|
+
'components.entityPicker.courses.loading'
|
|
2329
|
+
)}
|
|
2330
|
+
noResultsLabel={t(
|
|
2331
|
+
'components.entityPicker.courses.empty'
|
|
2332
|
+
)}
|
|
2327
2333
|
showCreateButton={false}
|
|
2328
2334
|
renderOption={({ option }) => (
|
|
2329
2335
|
<div className="flex items-center gap-2 py-0.5">
|
|
@@ -2398,7 +2404,7 @@ export default function TurmasPage() {
|
|
|
2398
2404
|
size="icon"
|
|
2399
2405
|
className="shrink-0"
|
|
2400
2406
|
onClick={openCourseCreateSheet}
|
|
2401
|
-
aria-label=
|
|
2407
|
+
aria-label={courseSheetT('actions.createCourse')}
|
|
2402
2408
|
>
|
|
2403
2409
|
<Plus className="h-4 w-4" />
|
|
2404
2410
|
</Button>
|
|
@@ -2833,8 +2839,10 @@ export default function TurmasPage() {
|
|
|
2833
2839
|
placeholder={t('form.fields.professor.placeholder')}
|
|
2834
2840
|
initialSelectedLabel={watchedFormValues.professor ?? ''}
|
|
2835
2841
|
searchPlaceholder={t('form.fields.professor.placeholder')}
|
|
2836
|
-
emptyStateDescription=
|
|
2837
|
-
|
|
2842
|
+
emptyStateDescription={t(
|
|
2843
|
+
'form.fields.professor.emptyState'
|
|
2844
|
+
)}
|
|
2845
|
+
noResultsLabel={t('form.fields.professor.noResults')}
|
|
2838
2846
|
showCreateButton={false}
|
|
2839
2847
|
clearable={false}
|
|
2840
2848
|
getOptionValue={(opt) => opt.id}
|
|
@@ -2952,7 +2960,7 @@ export default function TurmasPage() {
|
|
|
2952
2960
|
size="icon"
|
|
2953
2961
|
className="shrink-0"
|
|
2954
2962
|
onClick={() => setCreateProfessorDialogOpen(true)}
|
|
2955
|
-
aria-label=
|
|
2963
|
+
aria-label={t('sheet.lessonForm.createInstructor')}
|
|
2956
2964
|
>
|
|
2957
2965
|
<Plus className="h-4 w-4" />
|
|
2958
2966
|
</Button>
|
|
@@ -2991,11 +2999,11 @@ export default function TurmasPage() {
|
|
|
2991
2999
|
open={createProfessorDialogOpen}
|
|
2992
3000
|
onOpenChange={setCreateProfessorDialogOpen}
|
|
2993
3001
|
onCreated={handleProfessorCreated}
|
|
2994
|
-
title=
|
|
2995
|
-
description=
|
|
2996
|
-
submitLabel=
|
|
2997
|
-
successMessage=
|
|
2998
|
-
errorMessage=
|
|
3002
|
+
title={t('sheet.lessonForm.createInstructorTitle')}
|
|
3003
|
+
description={t('sheet.lessonForm.createInstructorDescription')}
|
|
3004
|
+
submitLabel={t('sheet.lessonForm.createInstructorSubmit')}
|
|
3005
|
+
successMessage={t('sheet.lessonForm.createInstructorSuccess')}
|
|
3006
|
+
errorMessage={t('sheet.lessonForm.createInstructorError')}
|
|
2999
3007
|
defaultQualificationSlugs={['class-sessions']}
|
|
3000
3008
|
/>
|
|
3001
3009
|
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
RefreshCw,
|
|
28
28
|
Video,
|
|
29
29
|
} from 'lucide-react';
|
|
30
|
+
import { useTranslations } from 'next-intl';
|
|
30
31
|
import { useRouter } from 'next/navigation';
|
|
31
32
|
|
|
32
33
|
import { ConfirmDialog } from './structure/_components/confirm-dialog';
|
|
@@ -66,6 +67,7 @@ type ApiCourseSummary = {
|
|
|
66
67
|
};
|
|
67
68
|
|
|
68
69
|
export default function CourseStructurePage({ params }: Props) {
|
|
70
|
+
const t = useTranslations('lms.CoursesPage.StructurePage');
|
|
69
71
|
const { id } = use(params);
|
|
70
72
|
const isMobile = useIsMobile();
|
|
71
73
|
const router = useRouter();
|
|
@@ -349,10 +351,10 @@ export default function CourseStructurePage({ params }: Props) {
|
|
|
349
351
|
variant="outline"
|
|
350
352
|
onClick={() => setMobileSheetOpen(true)}
|
|
351
353
|
className="gap-2"
|
|
352
|
-
aria-label=
|
|
354
|
+
aria-label={t('mobile.openStructure')}
|
|
353
355
|
>
|
|
354
356
|
<Menu className="size-4" />
|
|
355
|
-
|
|
357
|
+
{t('breadcrumbs.structure')}
|
|
356
358
|
</Button>
|
|
357
359
|
</div>
|
|
358
360
|
|
|
@@ -367,7 +369,7 @@ export default function CourseStructurePage({ params }: Props) {
|
|
|
367
369
|
<SheetContent side="left" className="w-[320px] p-0 flex flex-col">
|
|
368
370
|
<SheetHeader className="px-4 py-3 border-b shrink-0">
|
|
369
371
|
<SheetTitle className="text-sm">
|
|
370
|
-
{course.title}
|
|
372
|
+
{t('mobile.sheetTitle', { title: course.title })}
|
|
371
373
|
</SheetTitle>
|
|
372
374
|
</SheetHeader>
|
|
373
375
|
<div className="flex-1 min-h-0 overflow-hidden">
|
|
@@ -10,9 +10,11 @@ import {
|
|
|
10
10
|
AlertDialogHeader,
|
|
11
11
|
AlertDialogTitle,
|
|
12
12
|
} from '@/components/ui/alert-dialog';
|
|
13
|
+
import { useTranslations } from 'next-intl';
|
|
13
14
|
import { useStructureStore } from './store';
|
|
14
15
|
|
|
15
16
|
export function ConfirmDialog() {
|
|
17
|
+
const t = useTranslations('lms.CoursesPage.StructurePage.confirmDialog');
|
|
16
18
|
const confirmDialog = useStructureStore((s) => s.confirmDialog);
|
|
17
19
|
const closeConfirm = useStructureStore((s) => s.closeConfirm);
|
|
18
20
|
|
|
@@ -31,15 +33,15 @@ export function ConfirmDialog() {
|
|
|
31
33
|
)}
|
|
32
34
|
</AlertDialogHeader>
|
|
33
35
|
<AlertDialogFooter>
|
|
34
|
-
<AlertDialogCancel onClick={closeConfirm}>
|
|
36
|
+
<AlertDialogCancel onClick={closeConfirm}>{t('cancel')}</AlertDialogCancel>
|
|
35
37
|
<AlertDialogAction
|
|
36
38
|
onClick={handleConfirm}
|
|
37
39
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
38
40
|
>
|
|
39
|
-
|
|
41
|
+
{t('confirm')}
|
|
40
42
|
</AlertDialogAction>
|
|
41
43
|
</AlertDialogFooter>
|
|
42
44
|
</AlertDialogContent>
|
|
43
45
|
</AlertDialog>
|
|
44
46
|
);
|
|
45
|
-
}
|
|
47
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { Button } from '@/components/ui/button';
|
|
4
4
|
import { cn } from '@/lib/utils';
|
|
5
5
|
import { ChevronsDownUp, ChevronsUpDown, Loader2, Plus } from 'lucide-react';
|
|
6
|
+
import { useTranslations } from 'next-intl';
|
|
6
7
|
import { forwardRef, useMemo } from 'react';
|
|
7
8
|
import { useCreateSessionMutation } from '../_data/use-course-structure-mutations';
|
|
8
9
|
import { CourseTreeDnd } from './course-tree-dnd';
|
|
@@ -12,6 +13,7 @@ import { useStructureStore } from './store';
|
|
|
12
13
|
import { buildVisibleItems } from './tree-helpers';
|
|
13
14
|
|
|
14
15
|
export const CourseTreePanel = forwardRef<SearchFilterHandle>((_, ref) => {
|
|
16
|
+
const t = useTranslations('lms.CoursesPage.StructurePage');
|
|
15
17
|
const course = useStructureStore((s) => s.course);
|
|
16
18
|
const sessions = useStructureStore((s) => s.sessions);
|
|
17
19
|
const lessons = useStructureStore((s) => s.lessons);
|
|
@@ -57,10 +59,10 @@ export const CourseTreePanel = forwardRef<SearchFilterHandle>((_, ref) => {
|
|
|
57
59
|
)}
|
|
58
60
|
title={
|
|
59
61
|
allExpanded
|
|
60
|
-
? '
|
|
61
|
-
: '
|
|
62
|
+
? t('tree.collapseAllShortcut')
|
|
63
|
+
: t('tree.expandAllShortcut')
|
|
62
64
|
}
|
|
63
|
-
aria-label={allExpanded ? '
|
|
65
|
+
aria-label={allExpanded ? t('tree.collapseAll') : t('tree.expandAll')}
|
|
64
66
|
onClick={allExpanded ? collapseAll : expandAll}
|
|
65
67
|
disabled={isFiltering}
|
|
66
68
|
>
|
|
@@ -75,8 +77,8 @@ export const CourseTreePanel = forwardRef<SearchFilterHandle>((_, ref) => {
|
|
|
75
77
|
variant="ghost"
|
|
76
78
|
size="icon"
|
|
77
79
|
className="size-8 shrink-0"
|
|
78
|
-
title=
|
|
79
|
-
aria-label=
|
|
80
|
+
title={t('tree.addSession')}
|
|
81
|
+
aria-label={t('tree.addSession')}
|
|
80
82
|
disabled={createSession.isPending}
|
|
81
83
|
onClick={() => createSession.mutate()}
|
|
82
84
|
>
|
|
@@ -92,8 +94,8 @@ export const CourseTreePanel = forwardRef<SearchFilterHandle>((_, ref) => {
|
|
|
92
94
|
{isFiltering && resultCount !== undefined && (
|
|
93
95
|
<div className="px-3 py-1 text-[0.65rem] text-muted-foreground bg-muted/30 border-b shrink-0">
|
|
94
96
|
{resultCount === 0
|
|
95
|
-
? '
|
|
96
|
-
:
|
|
97
|
+
? t('search.noResults')
|
|
98
|
+
: t('search.results', { count: resultCount })}
|
|
97
99
|
</div>
|
|
98
100
|
)}
|
|
99
101
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Skeleton } from '@/components/ui/skeleton';
|
|
2
|
+
import { useTranslations } from 'next-intl';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* CourseTreeSkeleton
|
|
@@ -7,11 +8,12 @@ import { Skeleton } from '@/components/ui/skeleton';
|
|
|
7
8
|
* the API. Mimics the visual shape of session headers and lesson rows.
|
|
8
9
|
*/
|
|
9
10
|
export function CourseTreeSkeleton() {
|
|
11
|
+
const t = useTranslations('lms.CoursesPage.StructurePage');
|
|
10
12
|
return (
|
|
11
13
|
<div
|
|
12
14
|
className="flex flex-col gap-1 p-3"
|
|
13
15
|
aria-busy="true"
|
|
14
|
-
aria-label=
|
|
16
|
+
aria-label={t('loading')}
|
|
15
17
|
>
|
|
16
18
|
{/* Course header row */}
|
|
17
19
|
<div className="flex items-center gap-2 px-2 py-1.5">
|
|
@@ -4,6 +4,7 @@ import { cn } from '@/lib/utils';
|
|
|
4
4
|
import type { DraggableAttributes } from '@dnd-kit/core';
|
|
5
5
|
import type { SyntheticListenerMap } from '@dnd-kit/core/dist/hooks/utilities';
|
|
6
6
|
import { GripVertical } from 'lucide-react';
|
|
7
|
+
import { useTranslations } from 'next-intl';
|
|
7
8
|
|
|
8
9
|
interface DragHandleProps {
|
|
9
10
|
listeners?: SyntheticListenerMap;
|
|
@@ -23,6 +24,7 @@ export function DragHandle({
|
|
|
23
24
|
disabled,
|
|
24
25
|
className,
|
|
25
26
|
}: DragHandleProps) {
|
|
27
|
+
const t = useTranslations('lms.CoursesPage.StructurePage.dragHandle');
|
|
26
28
|
if (disabled) {
|
|
27
29
|
return (
|
|
28
30
|
<span
|
|
@@ -30,7 +32,7 @@ export function DragHandle({
|
|
|
30
32
|
'shrink-0 size-5 flex items-center justify-center opacity-20 cursor-not-allowed',
|
|
31
33
|
className
|
|
32
34
|
)}
|
|
33
|
-
title=
|
|
35
|
+
title={t('disabled')}
|
|
34
36
|
>
|
|
35
37
|
<GripVertical className="size-3.5" />
|
|
36
38
|
</span>
|
|
@@ -48,7 +50,7 @@ export function DragHandle({
|
|
|
48
50
|
'transition-colors touch-none',
|
|
49
51
|
className
|
|
50
52
|
)}
|
|
51
|
-
title=
|
|
53
|
+
title={t('enabled')}
|
|
52
54
|
// Prevent click events from bubbling to the row selection handler
|
|
53
55
|
onClick={(e) => e.stopPropagation()}
|
|
54
56
|
>
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
Video,
|
|
11
11
|
X,
|
|
12
12
|
} from 'lucide-react';
|
|
13
|
+
import { useTranslations } from 'next-intl';
|
|
13
14
|
import { useState } from 'react';
|
|
14
15
|
import { toast } from 'sonner';
|
|
15
16
|
|
|
@@ -43,6 +44,7 @@ const STATUS_LABELS: Record<LessonStatus, string> = {
|
|
|
43
44
|
// ── Component ─────────────────────────────────────────────────────────────────
|
|
44
45
|
|
|
45
46
|
export function EditorBulk() {
|
|
47
|
+
const t = useTranslations('lms.CoursesPage.StructurePage.bulkEditor');
|
|
46
48
|
const selectedIds = useStructureStore((s) => s.selectedIds);
|
|
47
49
|
const sessions = useStructureStore((s) => s.sessions);
|
|
48
50
|
const lessons = useStructureStore((s) => s.lessons);
|
|
@@ -74,8 +76,8 @@ export function EditorBulk() {
|
|
|
74
76
|
);
|
|
75
77
|
toast.success(
|
|
76
78
|
lines.length
|
|
77
|
-
?
|
|
78
|
-
: '
|
|
79
|
+
? t('toast.preview', { changes: lines.join(', ') })
|
|
80
|
+
: t('toast.none')
|
|
79
81
|
);
|
|
80
82
|
}
|
|
81
83
|
|
|
@@ -93,11 +95,11 @@ export function EditorBulk() {
|
|
|
93
95
|
)}
|
|
94
96
|
</div>
|
|
95
97
|
<div className="flex-1 min-w-0">
|
|
96
|
-
<p className="text-sm font-semibold">
|
|
98
|
+
<p className="text-sm font-semibold">{t('title')}</p>
|
|
97
99
|
<p className="text-[0.65rem] text-muted-foreground">
|
|
98
100
|
{selectedIds.size}{' '}
|
|
99
|
-
{allLessons ? '
|
|
100
|
-
|
|
101
|
+
{allLessons ? t('types.lessons') : allSessions ? t('types.sessions') : t('types.items')}{' '}
|
|
102
|
+
{t('types.selected')}
|
|
101
103
|
</p>
|
|
102
104
|
</div>
|
|
103
105
|
<Button
|
|
@@ -105,7 +107,7 @@ export function EditorBulk() {
|
|
|
105
107
|
size="icon"
|
|
106
108
|
className="size-7 shrink-0"
|
|
107
109
|
onClick={clearSelection}
|
|
108
|
-
title=
|
|
110
|
+
title={t('clearSelection')}
|
|
109
111
|
>
|
|
110
112
|
<X className="size-3.5" />
|
|
111
113
|
</Button>
|
|
@@ -148,7 +150,7 @@ export function EditorBulk() {
|
|
|
148
150
|
<Card>
|
|
149
151
|
<CardHeader className="px-3 pt-3 pb-2">
|
|
150
152
|
<CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
|
151
|
-
|
|
153
|
+
{t('productionStatus')}
|
|
152
154
|
</CardTitle>
|
|
153
155
|
</CardHeader>
|
|
154
156
|
<CardContent className="px-3 pb-3">
|
|
@@ -157,14 +159,14 @@ export function EditorBulk() {
|
|
|
157
159
|
onValueChange={(v) => setStatus(v as LessonStatus)}
|
|
158
160
|
>
|
|
159
161
|
<SelectTrigger className="h-8 text-xs w-full">
|
|
160
|
-
<SelectValue placeholder=
|
|
162
|
+
<SelectValue placeholder={t('keepCurrent')} />
|
|
161
163
|
</SelectTrigger>
|
|
162
164
|
<SelectContent>
|
|
163
|
-
<SelectItem value="preparada">
|
|
164
|
-
<SelectItem value="gravada">
|
|
165
|
-
<SelectItem value="editada">
|
|
166
|
-
<SelectItem value="finalizada">
|
|
167
|
-
<SelectItem value="publicada">
|
|
165
|
+
<SelectItem value="preparada">{t('status.preparada')}</SelectItem>
|
|
166
|
+
<SelectItem value="gravada">{t('status.gravada')}</SelectItem>
|
|
167
|
+
<SelectItem value="editada">{t('status.editada')}</SelectItem>
|
|
168
|
+
<SelectItem value="finalizada">{t('status.finalizada')}</SelectItem>
|
|
169
|
+
<SelectItem value="publicada">{t('status.publicada')}</SelectItem>
|
|
168
170
|
</SelectContent>
|
|
169
171
|
</Select>
|
|
170
172
|
</CardContent>
|
|
@@ -175,7 +177,7 @@ export function EditorBulk() {
|
|
|
175
177
|
<Card>
|
|
176
178
|
<CardHeader className="px-3 pt-3 pb-2">
|
|
177
179
|
<CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
|
178
|
-
|
|
180
|
+
{t('visibilityTitle')}
|
|
179
181
|
</CardTitle>
|
|
180
182
|
</CardHeader>
|
|
181
183
|
<CardContent className="px-3 pb-3">
|
|
@@ -184,22 +186,22 @@ export function EditorBulk() {
|
|
|
184
186
|
onValueChange={(v) => setVisibility(v as Visibility)}
|
|
185
187
|
>
|
|
186
188
|
<SelectTrigger className="h-8 text-xs w-full">
|
|
187
|
-
<SelectValue placeholder=
|
|
189
|
+
<SelectValue placeholder={t('keepCurrent')} />
|
|
188
190
|
</SelectTrigger>
|
|
189
191
|
<SelectContent>
|
|
190
192
|
<SelectItem value="publico">
|
|
191
193
|
<span className="flex items-center gap-1.5">
|
|
192
|
-
<Eye className="size-3" />
|
|
194
|
+
<Eye className="size-3" /> {t('visibility.publico')}
|
|
193
195
|
</span>
|
|
194
196
|
</SelectItem>
|
|
195
197
|
<SelectItem value="privado">
|
|
196
198
|
<span className="flex items-center gap-1.5">
|
|
197
|
-
<EyeOff className="size-3" />
|
|
199
|
+
<EyeOff className="size-3" /> {t('visibility.privado')}
|
|
198
200
|
</span>
|
|
199
201
|
</SelectItem>
|
|
200
202
|
<SelectItem value="restrito">
|
|
201
203
|
<span className="flex items-center gap-1.5">
|
|
202
|
-
<Lock className="size-3" />
|
|
204
|
+
<Lock className="size-3" /> {t('visibility.restrito')}
|
|
203
205
|
</span>
|
|
204
206
|
</SelectItem>
|
|
205
207
|
</SelectContent>
|
|
@@ -213,13 +215,13 @@ export function EditorBulk() {
|
|
|
213
215
|
<CardHeader className="px-3 pt-3 pb-2">
|
|
214
216
|
<CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wider flex items-center gap-1.5">
|
|
215
217
|
<FolderInput className="size-3" />
|
|
216
|
-
|
|
218
|
+
{t('moveToSession')}
|
|
217
219
|
</CardTitle>
|
|
218
220
|
</CardHeader>
|
|
219
221
|
<CardContent className="px-3 pb-3">
|
|
220
222
|
<Select value={targetSession} onValueChange={setTargetSession}>
|
|
221
223
|
<SelectTrigger className="h-8 text-xs w-full">
|
|
222
|
-
<SelectValue placeholder=
|
|
224
|
+
<SelectValue placeholder={t('selectSession')} />
|
|
223
225
|
</SelectTrigger>
|
|
224
226
|
<SelectContent>
|
|
225
227
|
{sessions.map((s) => (
|
|
@@ -238,8 +240,7 @@ export function EditorBulk() {
|
|
|
238
240
|
|
|
239
241
|
<div className="rounded-md border border-amber-200 bg-amber-50/60 dark:border-amber-800 dark:bg-amber-950/30 px-3 py-2">
|
|
240
242
|
<p className="text-[0.65rem] text-amber-700 dark:text-amber-400">
|
|
241
|
-
|
|
242
|
-
Clique em salvar para ver a pré-visualização.
|
|
243
|
+
{t('apiNotice')}
|
|
243
244
|
</p>
|
|
244
245
|
</div>
|
|
245
246
|
</div>
|
|
@@ -257,7 +258,7 @@ export function EditorBulk() {
|
|
|
257
258
|
onClick={clearSelection}
|
|
258
259
|
>
|
|
259
260
|
<X className="size-3 mr-1" />
|
|
260
|
-
|
|
261
|
+
{t('clearSelection')}
|
|
261
262
|
</Button>
|
|
262
263
|
<div className="flex-1" />
|
|
263
264
|
<Button
|
|
@@ -4,6 +4,7 @@ import { Button } from '@/components/ui/button';
|
|
|
4
4
|
import { Separator } from '@/components/ui/separator';
|
|
5
5
|
import { cn } from '@/lib/utils';
|
|
6
6
|
import { Copy, FolderOpen, Loader2, Trash2, X } from 'lucide-react';
|
|
7
|
+
import { useTranslations } from 'next-intl';
|
|
7
8
|
import { toast } from 'sonner';
|
|
8
9
|
import {
|
|
9
10
|
useBulkDeleteMutation,
|
|
@@ -18,6 +19,7 @@ import { useStructureStore } from './store';
|
|
|
18
19
|
* Fixed below the search toolbar inside CourseTreePanel.
|
|
19
20
|
*/
|
|
20
21
|
export function MultiSelectBar() {
|
|
22
|
+
const t = useTranslations('lms.CoursesPage.StructurePage.multiSelectBar');
|
|
21
23
|
const selectedIds = useStructureStore((s) => s.selectedIds);
|
|
22
24
|
const lessons = useStructureStore((s) => s.lessons);
|
|
23
25
|
const sessions = useStructureStore((s) => s.sessions);
|
|
@@ -59,12 +61,12 @@ export function MultiSelectBar() {
|
|
|
59
61
|
function handleCopy() {
|
|
60
62
|
if (allAreLessons) {
|
|
61
63
|
copyItems(selectedLessonIds, 'lesson');
|
|
62
|
-
toast.success(
|
|
64
|
+
toast.success(t('copyLessonsSuccess', { count }));
|
|
63
65
|
} else if (allAreSessions) {
|
|
64
66
|
copyItems(selectedSessionIds, 'session');
|
|
65
|
-
toast.success(
|
|
67
|
+
toast.success(t('copySessionsSuccess', { count }));
|
|
66
68
|
} else {
|
|
67
|
-
toast('
|
|
69
|
+
toast(t('copyMixedError'));
|
|
68
70
|
}
|
|
69
71
|
}
|
|
70
72
|
|
|
@@ -82,7 +84,7 @@ export function MultiSelectBar() {
|
|
|
82
84
|
);
|
|
83
85
|
};
|
|
84
86
|
duplicateNext(0);
|
|
85
|
-
toast.success(
|
|
87
|
+
toast.success(t('duplicatingLessons', { count }));
|
|
86
88
|
} else if (allAreSessions) {
|
|
87
89
|
const duplicateNext = (index: number) => {
|
|
88
90
|
if (index >= selectedSessionIds.length) return;
|
|
@@ -92,7 +94,7 @@ export function MultiSelectBar() {
|
|
|
92
94
|
);
|
|
93
95
|
};
|
|
94
96
|
duplicateNext(0);
|
|
95
|
-
toast.success(
|
|
97
|
+
toast.success(t('duplicatingSessions', { count }));
|
|
96
98
|
}
|
|
97
99
|
}
|
|
98
100
|
|
|
@@ -139,10 +141,10 @@ export function MultiSelectBar() {
|
|
|
139
141
|
.map((l) => ({ lessonId: l.id, sessionId: l.sessionId }));
|
|
140
142
|
|
|
141
143
|
showConfirm({
|
|
142
|
-
title:
|
|
144
|
+
title: t('deleteTitle', { label }),
|
|
143
145
|
description: allAreSessions
|
|
144
|
-
? '
|
|
145
|
-
: '
|
|
146
|
+
? t('deleteSessionsDescription')
|
|
147
|
+
: t('deleteDescription'),
|
|
146
148
|
onConfirm: () =>
|
|
147
149
|
bulkDelete.mutate({
|
|
148
150
|
sessionIds: selectedSessionIds,
|
|
@@ -154,7 +156,7 @@ export function MultiSelectBar() {
|
|
|
154
156
|
return (
|
|
155
157
|
<div
|
|
156
158
|
role="toolbar"
|
|
157
|
-
aria-label={
|
|
159
|
+
aria-label={t('toolbarAria', { count })}
|
|
158
160
|
className={cn(
|
|
159
161
|
'flex items-center gap-0.5 px-2 py-1 shrink-0 border-b',
|
|
160
162
|
'bg-primary/5 dark:bg-primary/10'
|
|
@@ -177,8 +179,8 @@ export function MultiSelectBar() {
|
|
|
177
179
|
variant="ghost"
|
|
178
180
|
size="icon"
|
|
179
181
|
className="size-6 text-muted-foreground hover:text-foreground"
|
|
180
|
-
title=
|
|
181
|
-
aria-label=
|
|
182
|
+
title={t('copyTitle')}
|
|
183
|
+
aria-label={t('copyAria')}
|
|
182
184
|
onClick={handleCopy}
|
|
183
185
|
>
|
|
184
186
|
<Copy className="size-3" />
|
|
@@ -189,8 +191,8 @@ export function MultiSelectBar() {
|
|
|
189
191
|
variant="ghost"
|
|
190
192
|
size="icon"
|
|
191
193
|
className="size-6 text-muted-foreground hover:text-foreground"
|
|
192
|
-
title=
|
|
193
|
-
aria-label=
|
|
194
|
+
title={t('duplicateTitle')}
|
|
195
|
+
aria-label={t('duplicateAria')}
|
|
194
196
|
disabled={isDuplicating}
|
|
195
197
|
onClick={handleDuplicate}
|
|
196
198
|
>
|
|
@@ -216,8 +218,8 @@ export function MultiSelectBar() {
|
|
|
216
218
|
variant="ghost"
|
|
217
219
|
size="icon"
|
|
218
220
|
className="size-6 text-muted-foreground hover:text-foreground"
|
|
219
|
-
title=
|
|
220
|
-
aria-label=
|
|
221
|
+
title={t('moveTitle')}
|
|
222
|
+
aria-label={t('moveAria')}
|
|
221
223
|
disabled={isMoving}
|
|
222
224
|
onClick={handleMove}
|
|
223
225
|
>
|
|
@@ -234,8 +236,8 @@ export function MultiSelectBar() {
|
|
|
234
236
|
variant="ghost"
|
|
235
237
|
size="icon"
|
|
236
238
|
className="size-6 text-destructive/60 hover:text-destructive"
|
|
237
|
-
title=
|
|
238
|
-
aria-label=
|
|
239
|
+
title={t('deleteActionTitle')}
|
|
240
|
+
aria-label={t('deleteActionAria')}
|
|
239
241
|
disabled={bulkDelete.isPending}
|
|
240
242
|
onClick={handleDelete}
|
|
241
243
|
>
|
|
@@ -253,8 +255,8 @@ export function MultiSelectBar() {
|
|
|
253
255
|
variant="ghost"
|
|
254
256
|
size="icon"
|
|
255
257
|
className="size-6 text-muted-foreground hover:text-foreground"
|
|
256
|
-
title=
|
|
257
|
-
aria-label=
|
|
258
|
+
title={t('clearTitle')}
|
|
259
|
+
aria-label={t('clearAria')}
|
|
258
260
|
onClick={clearSelection}
|
|
259
261
|
>
|
|
260
262
|
<X className="size-3" />
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Keyboard } from 'lucide-react';
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
4
5
|
|
|
5
6
|
import { Badge } from '@/components/ui/badge';
|
|
6
7
|
import { Button } from '@/components/ui/button';
|
|
@@ -56,13 +57,14 @@ interface ShortcutsHelpProps {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
export function ShortcutsHelp({ open, onOpenChange }: ShortcutsHelpProps) {
|
|
60
|
+
const t = useTranslations('lms.CoursesPage.StructurePage.shortcuts');
|
|
59
61
|
return (
|
|
60
62
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
61
63
|
<DialogContent className="max-w-sm">
|
|
62
64
|
<DialogHeader>
|
|
63
65
|
<DialogTitle className="flex items-center gap-2">
|
|
64
66
|
<Keyboard className="size-4" />
|
|
65
|
-
|
|
67
|
+
{t('title')}
|
|
66
68
|
</DialogTitle>
|
|
67
69
|
</DialogHeader>
|
|
68
70
|
|
|
@@ -103,8 +105,7 @@ export function ShortcutsHelp({ open, onOpenChange }: ShortcutsHelpProps) {
|
|
|
103
105
|
</div>
|
|
104
106
|
|
|
105
107
|
<p className="text-[0.65rem] text-muted-foreground mt-1">
|
|
106
|
-
|
|
107
|
-
Ctrl+F e Ctrl+/ que funcionam em qualquer contexto.
|
|
108
|
+
{t('footer')}
|
|
108
109
|
</p>
|
|
109
110
|
</DialogContent>
|
|
110
111
|
</Dialog>
|
|
@@ -115,16 +116,17 @@ export function ShortcutsHelp({ open, onOpenChange }: ShortcutsHelpProps) {
|
|
|
115
116
|
* Convenience trigger button — renders standalone when no external state is needed.
|
|
116
117
|
*/
|
|
117
118
|
export function ShortcutsHelpTrigger({ onOpen }: { onOpen: () => void }) {
|
|
119
|
+
const t = useTranslations('lms.CoursesPage.StructurePage.shortcuts');
|
|
118
120
|
return (
|
|
119
121
|
<Button
|
|
120
122
|
variant="ghost"
|
|
121
123
|
size="sm"
|
|
122
124
|
className="h-8 gap-1.5 text-xs text-muted-foreground hover:text-foreground"
|
|
123
125
|
onClick={onOpen}
|
|
124
|
-
title=
|
|
126
|
+
title={t('triggerTitle')}
|
|
125
127
|
>
|
|
126
128
|
<Keyboard className="size-3.5" />
|
|
127
|
-
|
|
129
|
+
{t('triggerLabel')}
|
|
128
130
|
<Badge
|
|
129
131
|
variant="outline"
|
|
130
132
|
className="h-4 px-1 text-[0.6rem] font-mono ml-0.5"
|