@hed-hog/lms 0.0.329 → 0.0.331
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/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +18 -8
- package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +10 -8
- 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/certificates/models/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +27 -27
- package/hedhog/frontend/app/classes/page.tsx.ejs +23 -15
- package/hedhog/frontend/app/courses/[id]/_components/CourseMultiEntityPicker.tsx.ejs +2 -2
- package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +8 -6
- package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +5 -3
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +1 -1
- 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/editor-lesson.tsx.ejs +228 -152
- 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 +78 -36
- 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/[id]/page.tsx.ejs +37 -41
- 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/evaluations/_components/evaluation-topic-form-sheet.tsx.ejs +1 -1
- package/hedhog/frontend/app/exams/page.tsx.ejs +6 -2
- package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +79 -59
- package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +145 -119
- package/hedhog/frontend/app/instructors/page.tsx.ejs +75 -54
- package/hedhog/frontend/app/paths/page.tsx.ejs +11 -7
- package/hedhog/frontend/app/reports/courses/page.tsx.ejs +5 -5
- package/hedhog/frontend/app/reports/dashboard/page.tsx.ejs +8 -8
- package/hedhog/frontend/app/reports/page.tsx.ejs +7 -7
- package/hedhog/frontend/app/reports/students/page.tsx.ejs +6 -6
- package/hedhog/frontend/app/training/page.tsx.ejs +5 -5
- package/hedhog/frontend/messages/en.json +899 -45
- package/hedhog/frontend/messages/pt.json +894 -38
- package/hedhog/frontend/widgets/active-classes-kpi.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/active-courses-kpi.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/approval-rate-kpi.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/class-calendar.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/completion-rate-kpi.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/issued-certificates-kpi.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/total-students-kpi.tsx.ejs +1 -1
- package/hedhog/table/instructor_qualification.yaml +1 -1
- package/hedhog/table/instructor_skill.yaml +1 -1
- package/package.json +7 -7
|
@@ -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';
|
|
@@ -17,36 +18,7 @@ import { Separator } from '@/components/ui/separator';
|
|
|
17
18
|
const SHORTCUT_GROUPS: {
|
|
18
19
|
heading: string;
|
|
19
20
|
items: { keys: string[]; description: string }[];
|
|
20
|
-
}[] = [
|
|
21
|
-
{
|
|
22
|
-
heading: 'Navegação',
|
|
23
|
-
items: [
|
|
24
|
-
{ keys: ['↑', '↓'], description: 'Navegar entre itens' },
|
|
25
|
-
{ keys: ['→'], description: 'Expandir sessão selecionada' },
|
|
26
|
-
{ keys: ['←'], description: 'Recolher sessão / ir para pai' },
|
|
27
|
-
{ keys: ['Enter'], description: 'Focar primeiro campo do editor' },
|
|
28
|
-
],
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
heading: 'Ações',
|
|
32
|
-
items: [
|
|
33
|
-
{ keys: ['Ctrl', 'S'], description: 'Salvar formulário do painel' },
|
|
34
|
-
{ keys: ['Ctrl', 'N'], description: 'Criar nova sessão ou aula' },
|
|
35
|
-
{ keys: ['Ctrl', 'C'], description: 'Copiar item selecionado' },
|
|
36
|
-
{ keys: ['Ctrl', 'V'], description: 'Colar no contexto atual' },
|
|
37
|
-
{ keys: ['Ctrl', 'D'], description: 'Duplicar item' },
|
|
38
|
-
{ keys: ['Delete'], description: 'Excluir item(ns) selecionado(s)' },
|
|
39
|
-
],
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
heading: 'Busca & Interface',
|
|
43
|
-
items: [
|
|
44
|
-
{ keys: ['Ctrl', 'F'], description: 'Focar campo de busca' },
|
|
45
|
-
{ keys: ['Ctrl', '/'], description: 'Abrir ajuda de atalhos' },
|
|
46
|
-
{ keys: ['Esc'], description: 'Limpar busca / seleção / foco' },
|
|
47
|
-
],
|
|
48
|
-
},
|
|
49
|
-
];
|
|
21
|
+
}[] = [];
|
|
50
22
|
|
|
51
23
|
// ── Component ─────────────────────────────────────────────────────────────────
|
|
52
24
|
|
|
@@ -56,18 +28,88 @@ interface ShortcutsHelpProps {
|
|
|
56
28
|
}
|
|
57
29
|
|
|
58
30
|
export function ShortcutsHelp({ open, onOpenChange }: ShortcutsHelpProps) {
|
|
31
|
+
const t = useTranslations('lms.CoursesPage.StructurePage.shortcuts');
|
|
32
|
+
const shortcutGroups = [
|
|
33
|
+
{
|
|
34
|
+
heading: t('groups.navigation.heading'),
|
|
35
|
+
items: [
|
|
36
|
+
{
|
|
37
|
+
keys: ['↑', '↓'],
|
|
38
|
+
description: t('groups.navigation.items.navigate'),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
keys: ['→'],
|
|
42
|
+
description: t('groups.navigation.items.expand'),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
keys: ['←'],
|
|
46
|
+
description: t('groups.navigation.items.collapse'),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
keys: ['Enter'],
|
|
50
|
+
description: t('groups.navigation.items.focusEditor'),
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
heading: t('groups.actions.heading'),
|
|
56
|
+
items: [
|
|
57
|
+
{
|
|
58
|
+
keys: ['Ctrl', 'S'],
|
|
59
|
+
description: t('groups.actions.items.savePanel'),
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
keys: ['Ctrl', 'N'],
|
|
63
|
+
description: t('groups.actions.items.createItem'),
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
keys: ['Ctrl', 'C'],
|
|
67
|
+
description: t('groups.actions.items.copyItem'),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
keys: ['Ctrl', 'V'],
|
|
71
|
+
description: t('groups.actions.items.pasteItem'),
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
keys: ['Ctrl', 'D'],
|
|
75
|
+
description: t('groups.actions.items.duplicateItem'),
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
keys: ['Delete'],
|
|
79
|
+
description: t('groups.actions.items.deleteItems'),
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
heading: t('groups.search.heading'),
|
|
85
|
+
items: [
|
|
86
|
+
{
|
|
87
|
+
keys: ['Ctrl', 'F'],
|
|
88
|
+
description: t('groups.search.items.focusSearch'),
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
keys: ['Ctrl', '/'],
|
|
92
|
+
description: t('groups.search.items.openHelp'),
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
keys: ['Esc'],
|
|
96
|
+
description: t('groups.search.items.clearState'),
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
];
|
|
59
101
|
return (
|
|
60
102
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
61
103
|
<DialogContent className="max-w-sm">
|
|
62
104
|
<DialogHeader>
|
|
63
105
|
<DialogTitle className="flex items-center gap-2">
|
|
64
106
|
<Keyboard className="size-4" />
|
|
65
|
-
|
|
107
|
+
{t('title')}
|
|
66
108
|
</DialogTitle>
|
|
67
109
|
</DialogHeader>
|
|
68
110
|
|
|
69
111
|
<div className="flex flex-col gap-4 mt-1">
|
|
70
|
-
{
|
|
112
|
+
{shortcutGroups.map((group, gi) => (
|
|
71
113
|
<div key={gi}>
|
|
72
114
|
<p className="text-[0.65rem] font-semibold uppercase tracking-wider text-muted-foreground mb-2">
|
|
73
115
|
{group.heading}
|
|
@@ -103,8 +145,7 @@ export function ShortcutsHelp({ open, onOpenChange }: ShortcutsHelpProps) {
|
|
|
103
145
|
</div>
|
|
104
146
|
|
|
105
147
|
<p className="text-[0.65rem] text-muted-foreground mt-1">
|
|
106
|
-
|
|
107
|
-
Ctrl+F e Ctrl+/ que funcionam em qualquer contexto.
|
|
148
|
+
{t('footer')}
|
|
108
149
|
</p>
|
|
109
150
|
</DialogContent>
|
|
110
151
|
</Dialog>
|
|
@@ -115,16 +156,17 @@ export function ShortcutsHelp({ open, onOpenChange }: ShortcutsHelpProps) {
|
|
|
115
156
|
* Convenience trigger button — renders standalone when no external state is needed.
|
|
116
157
|
*/
|
|
117
158
|
export function ShortcutsHelpTrigger({ onOpen }: { onOpen: () => void }) {
|
|
159
|
+
const t = useTranslations('lms.CoursesPage.StructurePage.shortcuts');
|
|
118
160
|
return (
|
|
119
161
|
<Button
|
|
120
162
|
variant="ghost"
|
|
121
163
|
size="sm"
|
|
122
164
|
className="h-8 gap-1.5 text-xs text-muted-foreground hover:text-foreground"
|
|
123
165
|
onClick={onOpen}
|
|
124
|
-
title=
|
|
166
|
+
title={t('triggerTitle')}
|
|
125
167
|
>
|
|
126
168
|
<Keyboard className="size-3.5" />
|
|
127
|
-
|
|
169
|
+
{t('triggerLabel')}
|
|
128
170
|
<Badge
|
|
129
171
|
variant="outline"
|
|
130
172
|
className="h-4 px-1 text-[0.6rem] font-mono ml-0.5"
|
package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { Separator } from '@/components/ui/separator';
|
|
11
11
|
import { Switch } from '@/components/ui/switch';
|
|
12
12
|
import { SlidersHorizontal } from 'lucide-react';
|
|
13
|
+
import { useTranslations } from 'next-intl';
|
|
13
14
|
|
|
14
15
|
import { useTreeDisplaySettings } from './use-tree-display-settings';
|
|
15
16
|
|
|
@@ -18,6 +19,7 @@ import { useTreeDisplaySettings } from './use-tree-display-settings';
|
|
|
18
19
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
19
20
|
|
|
20
21
|
export function TreeDisplaySettingsPopover() {
|
|
22
|
+
const t = useTranslations('lms.CoursesPage.StructurePage.displaySettings');
|
|
21
23
|
const {
|
|
22
24
|
showStatusDot,
|
|
23
25
|
showVisibility,
|
|
@@ -35,26 +37,26 @@ export function TreeDisplaySettingsPopover() {
|
|
|
35
37
|
variant="ghost"
|
|
36
38
|
size="sm"
|
|
37
39
|
className="h-8 gap-1.5 text-xs text-muted-foreground hover:text-foreground"
|
|
38
|
-
title=
|
|
40
|
+
title={t('title')}
|
|
39
41
|
>
|
|
40
42
|
<SlidersHorizontal className="size-3.5" />
|
|
41
|
-
<span className="sr-only sm:not-sr-only">
|
|
43
|
+
<span className="sr-only sm:not-sr-only">{t('label')}</span>
|
|
42
44
|
</Button>
|
|
43
45
|
</PopoverTrigger>
|
|
44
46
|
|
|
45
47
|
<PopoverContent align="end" className="w-56 p-0">
|
|
46
48
|
<div className="px-3 py-2.5 border-b">
|
|
47
|
-
<p className="text-xs font-semibold">
|
|
49
|
+
<p className="text-xs font-semibold">{t('sectionTitle')}</p>
|
|
48
50
|
<p className="text-[0.65rem] text-muted-foreground mt-0.5">
|
|
49
|
-
|
|
51
|
+
{t('sectionDescription')}
|
|
50
52
|
</p>
|
|
51
53
|
</div>
|
|
52
54
|
|
|
53
55
|
<div className="flex flex-col gap-0">
|
|
54
56
|
<SettingRow
|
|
55
57
|
id="show-status-dot"
|
|
56
|
-
label=
|
|
57
|
-
description=
|
|
58
|
+
label={t('showStatusDot.label')}
|
|
59
|
+
description={t('showStatusDot.description')}
|
|
58
60
|
checked={showStatusDot}
|
|
59
61
|
onCheckedChange={(v) => update({ showStatusDot: v })}
|
|
60
62
|
/>
|
|
@@ -63,8 +65,8 @@ export function TreeDisplaySettingsPopover() {
|
|
|
63
65
|
|
|
64
66
|
<SettingRow
|
|
65
67
|
id="show-visibility"
|
|
66
|
-
label=
|
|
67
|
-
description=
|
|
68
|
+
label={t('showVisibility.label')}
|
|
69
|
+
description={t('showVisibility.description')}
|
|
68
70
|
checked={showVisibility}
|
|
69
71
|
onCheckedChange={(v) => update({ showVisibility: v })}
|
|
70
72
|
/>
|
|
@@ -73,8 +75,8 @@ export function TreeDisplaySettingsPopover() {
|
|
|
73
75
|
|
|
74
76
|
<SettingRow
|
|
75
77
|
id="show-code"
|
|
76
|
-
label=
|
|
77
|
-
description=
|
|
78
|
+
label={t('showCode.label')}
|
|
79
|
+
description={t('showCode.description')}
|
|
78
80
|
checked={showCode}
|
|
79
81
|
onCheckedChange={(v) => update({ showCode: v })}
|
|
80
82
|
/>
|
|
@@ -83,8 +85,8 @@ export function TreeDisplaySettingsPopover() {
|
|
|
83
85
|
|
|
84
86
|
<SettingRow
|
|
85
87
|
id="show-video-indicator"
|
|
86
|
-
label=
|
|
87
|
-
description=
|
|
88
|
+
label={t('showVideoIndicator.label')}
|
|
89
|
+
description={t('showVideoIndicator.description')}
|
|
88
90
|
checked={showVideoIndicator}
|
|
89
91
|
onCheckedChange={(v) => update({ showVideoIndicator: v })}
|
|
90
92
|
/>
|
|
@@ -93,8 +95,8 @@ export function TreeDisplaySettingsPopover() {
|
|
|
93
95
|
|
|
94
96
|
<SettingRow
|
|
95
97
|
id="show-resources-indicator"
|
|
96
|
-
label=
|
|
97
|
-
description=
|
|
98
|
+
label={t('showResourcesIndicator.label')}
|
|
99
|
+
description={t('showResourcesIndicator.description')}
|
|
98
100
|
checked={showResourcesIndicator}
|
|
99
101
|
onCheckedChange={(v) => update({ showResourcesIndicator: v })}
|
|
100
102
|
/>
|
|
@@ -103,8 +105,8 @@ export function TreeDisplaySettingsPopover() {
|
|
|
103
105
|
|
|
104
106
|
<SettingRow
|
|
105
107
|
id="show-transcription-indicator"
|
|
106
|
-
label=
|
|
107
|
-
description=
|
|
108
|
+
label={t('showTranscriptionIndicator.label')}
|
|
109
|
+
description={t('showTranscriptionIndicator.description')}
|
|
108
110
|
checked={showTranscriptionIndicator}
|
|
109
111
|
onCheckedChange={(v) => update({ showTranscriptionIndicator: v })}
|
|
110
112
|
/>
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
Video,
|
|
16
16
|
type LucideIcon,
|
|
17
17
|
} from 'lucide-react';
|
|
18
|
+
import { useTranslations } from 'next-intl';
|
|
18
19
|
import { useEffect, useRef } from 'react';
|
|
19
20
|
import { useUpdateLessonMutation } from '../_data/use-course-structure-mutations';
|
|
20
21
|
import { HighlightedText } from './highlighted-text';
|
|
@@ -75,6 +76,7 @@ export function TreeRowLesson({
|
|
|
75
76
|
query,
|
|
76
77
|
onClick,
|
|
77
78
|
}: TreeRowLessonProps) {
|
|
79
|
+
const t = useTranslations('lms.CoursesPage.StructurePage.lessonRow');
|
|
78
80
|
const cfg = LESSON_TYPE_CONFIG[data.type];
|
|
79
81
|
const Icon = cfg.icon;
|
|
80
82
|
|
|
@@ -181,7 +183,7 @@ export function TreeRowLesson({
|
|
|
181
183
|
onClick={(e) => e.stopPropagation()}
|
|
182
184
|
onBlur={(e) => commitAndPersist(e.target.value)}
|
|
183
185
|
onKeyDown={handleKeyDown}
|
|
184
|
-
aria-label=
|
|
186
|
+
aria-label={t('rename')}
|
|
185
187
|
/>
|
|
186
188
|
) : (
|
|
187
189
|
<span className="truncate flex-1 text-xs leading-tight">
|
|
@@ -209,8 +211,8 @@ export function TreeRowLesson({
|
|
|
209
211
|
const VisIcon = visibilityCfg.icon;
|
|
210
212
|
return (
|
|
211
213
|
<span
|
|
212
|
-
|
|
213
|
-
|
|
214
|
+
title={t('visibility', { value: visibilityCfg.label })}
|
|
215
|
+
aria-label={t('visibility', { value: visibilityCfg.label })}
|
|
214
216
|
className="inline-flex items-center"
|
|
215
217
|
>
|
|
216
218
|
<VisIcon
|
|
@@ -223,15 +225,15 @@ export function TreeRowLesson({
|
|
|
223
225
|
{showStatusDot && statusCfg && (
|
|
224
226
|
<span
|
|
225
227
|
className={cn('size-1.5 rounded-full shrink-0', statusCfg.dot)}
|
|
226
|
-
title={
|
|
227
|
-
aria-label={
|
|
228
|
+
title={t('status', { value: statusCfg.label })}
|
|
229
|
+
aria-label={t('status', { value: statusCfg.label })}
|
|
228
230
|
/>
|
|
229
231
|
)}
|
|
230
232
|
{/* Video linked indicator */}
|
|
231
233
|
{showVideoIndicator && data.type === 'video' && data.videoUrl && (
|
|
232
234
|
<span
|
|
233
|
-
title=
|
|
234
|
-
aria-label=
|
|
235
|
+
title={t('videoLinked')}
|
|
236
|
+
aria-label={t('videoLinked')}
|
|
235
237
|
className="inline-flex items-center"
|
|
236
238
|
>
|
|
237
239
|
<Film className="size-3 shrink-0 text-violet-500" />
|
|
@@ -240,8 +242,8 @@ export function TreeRowLesson({
|
|
|
240
242
|
{/* Resources indicator */}
|
|
241
243
|
{showResourcesIndicator && data.resources.length > 0 && (
|
|
242
244
|
<span
|
|
243
|
-
title={
|
|
244
|
-
aria-label={
|
|
245
|
+
title={t('resources', { count: data.resources.length })}
|
|
246
|
+
aria-label={t('resourcesAria', { count: data.resources.length })}
|
|
245
247
|
className="inline-flex items-center gap-0.5"
|
|
246
248
|
>
|
|
247
249
|
<Paperclip className="size-3 shrink-0 text-sky-500" />
|
|
@@ -257,8 +259,8 @@ export function TreeRowLesson({
|
|
|
257
259
|
data.type === 'video' &&
|
|
258
260
|
data.transcription && (
|
|
259
261
|
<span
|
|
260
|
-
title=
|
|
261
|
-
aria-label=
|
|
262
|
+
title={t('hasTranscription')}
|
|
263
|
+
aria-label={t('hasTranscription')}
|
|
262
264
|
className="inline-flex items-center"
|
|
263
265
|
>
|
|
264
266
|
<ScrollText className="size-3 shrink-0 text-emerald-500" />
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { Input } from '@/components/ui/input';
|
|
4
4
|
import { cn } from '@/lib/utils';
|
|
5
5
|
import { ChevronDown, ChevronRight, Layers } from 'lucide-react';
|
|
6
|
+
import { useTranslations } from 'next-intl';
|
|
6
7
|
import { useEffect, useRef } from 'react';
|
|
7
8
|
import { useUpdateSessionMutation } from '../_data/use-course-structure-mutations';
|
|
8
9
|
import { HighlightedText } from './highlighted-text';
|
|
@@ -31,6 +32,7 @@ export function TreeRowSession({
|
|
|
31
32
|
onClick,
|
|
32
33
|
onToggleExpand,
|
|
33
34
|
}: TreeRowSessionProps) {
|
|
35
|
+
const t = useTranslations('lms.CoursesPage.StructurePage.sessionRow');
|
|
34
36
|
const inlineRenamingId = useStructureStore((s) => s.inlineRenamingId);
|
|
35
37
|
const cancelRename = useStructureStore((s) => s.cancelRename);
|
|
36
38
|
const startRename = useStructureStore((s) => s.startRename);
|
|
@@ -104,7 +106,7 @@ export function TreeRowSession({
|
|
|
104
106
|
<button
|
|
105
107
|
className="shrink-0 text-muted-foreground hover:text-foreground transition-colors p-0.5 rounded focus-visible:ring-1 focus-visible:ring-ring"
|
|
106
108
|
onClick={onToggleExpand}
|
|
107
|
-
aria-label={isExpanded ? '
|
|
109
|
+
aria-label={isExpanded ? t('collapse') : t('expand')}
|
|
108
110
|
tabIndex={-1}
|
|
109
111
|
>
|
|
110
112
|
{isExpanded ? (
|
|
@@ -129,7 +131,7 @@ export function TreeRowSession({
|
|
|
129
131
|
onClick={(e) => e.stopPropagation()}
|
|
130
132
|
onBlur={(e) => commitAndPersist(e.target.value)}
|
|
131
133
|
onKeyDown={handleKeyDown}
|
|
132
|
-
aria-label=
|
|
134
|
+
aria-label={t('rename')}
|
|
133
135
|
/>
|
|
134
136
|
) : (
|
|
135
137
|
<span className="truncate flex-1 font-medium leading-tight">
|
|
@@ -158,7 +160,7 @@ export function TreeRowSession({
|
|
|
158
160
|
? 'bg-muted-foreground/30'
|
|
159
161
|
: 'bg-emerald-500'
|
|
160
162
|
)}
|
|
161
|
-
title={data.published === false ? '
|
|
163
|
+
title={data.published === false ? t('draft') : t('published')}
|
|
162
164
|
/>
|
|
163
165
|
</div>
|
|
164
166
|
)}
|
package/hedhog/frontend/app/courses/[id]/structure/_components/use-course-structure-shortcuts.ts.ejs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useCallback, useEffect, useRef } from 'react';
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
4
5
|
import { toast } from 'sonner';
|
|
5
6
|
|
|
6
7
|
import type { SearchFilterHandle } from './search-filter';
|
|
@@ -44,6 +45,7 @@ export function useCourseStructureShortcuts({
|
|
|
44
45
|
onDuplicate,
|
|
45
46
|
onPaste,
|
|
46
47
|
}: UseCourseStructureShortcutsOptions) {
|
|
48
|
+
const t = useTranslations('lms.CoursesPage.StructurePage.shortcuts');
|
|
47
49
|
const course = useStructureStore((s) => s.course);
|
|
48
50
|
const sessions = useStructureStore((s) => s.sessions);
|
|
49
51
|
const lessons = useStructureStore((s) => s.lessons);
|
|
@@ -236,12 +238,15 @@ export function useCourseStructureShortcuts({
|
|
|
236
238
|
if (!ids.length) return;
|
|
237
239
|
const count = ids.length;
|
|
238
240
|
s.showConfirm({
|
|
239
|
-
title:
|
|
240
|
-
|
|
241
|
+
title: t('deleteTitle', {
|
|
242
|
+
label:
|
|
243
|
+
count > 1 ? `${count} ${t('items')}` : t('selectedItem'),
|
|
244
|
+
}),
|
|
245
|
+
description: t('deleteDescription'),
|
|
241
246
|
onConfirm: () => {
|
|
242
247
|
s.deleteSelected();
|
|
243
248
|
toast.success(
|
|
244
|
-
count > 1 ?
|
|
249
|
+
count > 1 ? t('itemsDeleted', { count }) : t('itemDeleted')
|
|
245
250
|
);
|
|
246
251
|
},
|
|
247
252
|
});
|
|
@@ -260,7 +265,7 @@ export function useCourseStructureShortcuts({
|
|
|
260
265
|
const ids = selectedKeys.map((key) => key.slice(key.indexOf(':') + 1));
|
|
261
266
|
s.copyItems(ids, type);
|
|
262
267
|
toast.success(
|
|
263
|
-
|
|
268
|
+
ids.length > 1 ? t('itemsCopied', { count: ids.length }) : t('itemCopied')
|
|
264
269
|
);
|
|
265
270
|
return;
|
|
266
271
|
}
|
|
@@ -272,7 +277,7 @@ export function useCourseStructureShortcuts({
|
|
|
272
277
|
onDuplicate();
|
|
273
278
|
} else {
|
|
274
279
|
s.duplicateSelected();
|
|
275
|
-
toast.success('
|
|
280
|
+
toast.success(t('itemDuplicated'));
|
|
276
281
|
}
|
|
277
282
|
return;
|
|
278
283
|
}
|
|
@@ -283,7 +288,7 @@ export function useCourseStructureShortcuts({
|
|
|
283
288
|
if (onPaste) {
|
|
284
289
|
onPaste();
|
|
285
290
|
} else {
|
|
286
|
-
toast('
|
|
291
|
+
toast(t('nothingToPaste'));
|
|
287
292
|
}
|
|
288
293
|
return;
|
|
289
294
|
}
|
|
@@ -293,16 +298,16 @@ export function useCourseStructureShortcuts({
|
|
|
293
298
|
e.preventDefault();
|
|
294
299
|
if (s.activeItemType === 'course') {
|
|
295
300
|
s.addSession();
|
|
296
|
-
toast.success('
|
|
301
|
+
toast.success(t('newSessionCreated'));
|
|
297
302
|
} else if (s.activeItemType === 'session' && s.activeItemId) {
|
|
298
303
|
s.addLesson(s.activeItemId);
|
|
299
|
-
toast.success('
|
|
304
|
+
toast.success(t('newLessonCreated'));
|
|
300
305
|
} else if (s.activeItemType === 'lesson') {
|
|
301
306
|
// Find parent session and add there
|
|
302
307
|
const lesson = s.lessons.find((l) => l.id === s.activeItemId);
|
|
303
308
|
if (lesson) {
|
|
304
309
|
s.addLesson(lesson.sessionId);
|
|
305
|
-
toast.success('
|
|
310
|
+
toast.success(t('newLessonSameSession'));
|
|
306
311
|
}
|
|
307
312
|
}
|
|
308
313
|
return;
|