@hed-hog/lms 0.0.314 → 0.0.316

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.
Files changed (67) hide show
  1. package/dist/enterprise/dto/enterprise-profile.dto.d.ts +13 -0
  2. package/dist/enterprise/dto/enterprise-profile.dto.d.ts.map +1 -0
  3. package/dist/enterprise/dto/enterprise-profile.dto.js +3 -0
  4. package/dist/enterprise/dto/enterprise-profile.dto.js.map +1 -0
  5. package/dist/enterprise/enterprise.controller.d.ts +3 -0
  6. package/dist/enterprise/enterprise.controller.d.ts.map +1 -1
  7. package/dist/enterprise/enterprise.controller.js +14 -0
  8. package/dist/enterprise/enterprise.controller.js.map +1 -1
  9. package/dist/enterprise/enterprise.service.d.ts +3 -0
  10. package/dist/enterprise/enterprise.service.d.ts.map +1 -1
  11. package/dist/enterprise/enterprise.service.js +128 -1
  12. package/dist/enterprise/enterprise.service.js.map +1 -1
  13. package/dist/instructor/instructor.controller.d.ts +23 -0
  14. package/dist/instructor/instructor.controller.d.ts.map +1 -1
  15. package/dist/instructor/instructor.controller.js +41 -0
  16. package/dist/instructor/instructor.controller.js.map +1 -1
  17. package/dist/instructor/instructor.service.d.ts +25 -0
  18. package/dist/instructor/instructor.service.d.ts.map +1 -1
  19. package/dist/instructor/instructor.service.js +126 -8
  20. package/dist/instructor/instructor.service.js.map +1 -1
  21. package/hedhog/data/menu.yaml +23 -7
  22. package/hedhog/data/role.yaml +17 -1
  23. package/hedhog/data/route.yaml +48 -0
  24. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +2 -1
  25. package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +44 -44
  26. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +362 -362
  27. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-panel.tsx.ejs +111 -111
  28. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +134 -134
  29. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-course.tsx.ejs +113 -113
  30. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +314 -314
  31. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-panel.tsx.ejs +62 -62
  32. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +173 -173
  33. package/hedhog/frontend/app/courses/[id]/structure/_components/drag-handle.tsx.ejs +58 -58
  34. package/hedhog/frontend/app/courses/[id]/structure/_components/drag-overlay.tsx.ejs +51 -51
  35. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +276 -276
  36. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +1216 -1216
  37. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +1824 -1824
  38. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +443 -443
  39. package/hedhog/frontend/app/courses/[id]/structure/_components/highlighted-text.tsx.ejs +40 -40
  40. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +185 -185
  41. package/hedhog/frontend/app/courses/[id]/structure/_components/multi-select-bar.tsx.ejs +264 -264
  42. package/hedhog/frontend/app/courses/[id]/structure/_components/search-filter.tsx.ejs +95 -95
  43. package/hedhog/frontend/app/courses/[id]/structure/_components/session-picker-dialog.tsx.ejs +73 -73
  44. package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +136 -136
  45. package/hedhog/frontend/app/courses/[id]/structure/_components/sortable-tree-row.tsx.ejs +80 -80
  46. package/hedhog/frontend/app/courses/[id]/structure/_components/store.ts.ejs +949 -949
  47. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-context-menu.tsx.ejs +525 -525
  48. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-helpers.ts.ejs +181 -181
  49. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-course.tsx.ejs +51 -51
  50. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +271 -271
  51. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +167 -167
  52. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row.tsx.ejs +108 -108
  53. package/hedhog/frontend/app/courses/[id]/structure/_components/use-course-structure-shortcuts.ts.ejs +318 -318
  54. package/hedhog/frontend/app/courses/[id]/structure/page.tsx.ejs +10 -10
  55. package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +2 -1
  56. package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +438 -0
  57. package/hedhog/frontend/app/instructors/_components/instructor-types.ts.ejs +40 -0
  58. package/hedhog/frontend/app/instructors/page.tsx.ejs +696 -0
  59. package/hedhog/frontend/app/training/page.tsx.ejs +2339 -0
  60. package/hedhog/query/add_route_role.sql +15 -0
  61. package/hedhog/table/enterprise_user.yaml +1 -1
  62. package/package.json +6 -6
  63. package/src/enterprise/dto/enterprise-profile.dto.ts +17 -0
  64. package/src/enterprise/enterprise.controller.ts +9 -1
  65. package/src/enterprise/enterprise.service.ts +147 -4
  66. package/src/instructor/instructor.controller.ts +36 -9
  67. package/src/instructor/instructor.service.ts +140 -10
@@ -1,318 +1,318 @@
1
- 'use client';
2
-
3
- import { useCallback, useEffect, useRef } from 'react';
4
- import { toast } from 'sonner';
5
-
6
- import type { SearchFilterHandle } from './search-filter';
7
- import { useStructureStore } from './store';
8
- import { buildVisibleItems } from './tree-helpers';
9
-
10
- /**
11
- * Checks whether keyboard shortcut handling should be suppressed
12
- * (the user is typing in a form control or rich-text editor).
13
- */
14
- function isEditingTarget(el: Element | null): boolean {
15
- if (!el) return false;
16
- const tag = (el as HTMLElement).tagName.toLowerCase();
17
- if (tag === 'input' || tag === 'textarea' || tag === 'select') return true;
18
- // contenteditable (Tiptap, etc.)
19
- if ((el as HTMLElement).isContentEditable) return true;
20
- // shadcn Select portal, combobox, etc. — has [role=combobox] or [role=listbox]
21
- const role = (el as HTMLElement).getAttribute('role');
22
- if (role === 'combobox' || role === 'listbox' || role === 'option')
23
- return true;
24
- return false;
25
- }
26
-
27
- interface UseCourseStructureShortcutsOptions {
28
- /** Ref to the SearchFilter imperative handle so Ctrl+F / Escape can focus/clear it. */
29
- searchRef: React.RefObject<SearchFilterHandle | null>;
30
- /** Ref to the detail panel wrapper div (for Enter key focusing). */
31
- detailPanelRef: React.RefObject<HTMLDivElement | null>;
32
- /** Callback to open/close the shortcuts help sheet. */
33
- onToggleHelp: () => void;
34
- /** API-backed duplicate (session or lesson based on active item). */
35
- onDuplicate?: () => void;
36
- /** API-backed paste (lesson or session clipboard). */
37
- onPaste?: () => void;
38
- }
39
-
40
- export function useCourseStructureShortcuts({
41
- searchRef,
42
- detailPanelRef,
43
- onToggleHelp,
44
- onDuplicate,
45
- onPaste,
46
- }: UseCourseStructureShortcutsOptions) {
47
- const course = useStructureStore((s) => s.course);
48
- const sessions = useStructureStore((s) => s.sessions);
49
- const lessons = useStructureStore((s) => s.lessons);
50
- const expandedIds = useStructureStore((s) => s.expandedIds);
51
- const filterQuery = useStructureStore((s) => s.filterQuery);
52
- const selectedIds = useStructureStore((s) => s.selectedIds);
53
-
54
- const activeItemId = useStructureStore((s) => s.activeItemId);
55
- const activeItemType = useStructureStore((s) => s.activeItemType);
56
-
57
- const selectItem = useStructureStore((s) => s.selectItem);
58
- const clearSelection = useStructureStore((s) => s.clearSelection);
59
- const setFilter = useStructureStore((s) => s.setFilter);
60
- const toggleExpand = useStructureStore((s) => s.toggleExpand);
61
- const copyItems = useStructureStore((s) => s.copyItems);
62
- const addSession = useStructureStore((s) => s.addSession);
63
- const addLesson = useStructureStore((s) => s.addLesson);
64
- const deleteSession = useStructureStore((s) => s.deleteSession);
65
- const deleteLesson = useStructureStore((s) => s.deleteLesson);
66
- const deleteSelected = useStructureStore((s) => s.deleteSelected);
67
- const duplicateSelected = useStructureStore((s) => s.duplicateSelected);
68
- const showConfirm = useStructureStore((s) => s.showConfirm);
69
-
70
- // Keep a stable ref to store values so handlers don't stale-close
71
- const stateRef = useRef({
72
- course,
73
- sessions,
74
- lessons,
75
- expandedIds,
76
- filterQuery,
77
- selectedIds,
78
- activeItemId,
79
- activeItemType,
80
- selectItem,
81
- clearSelection,
82
- setFilter,
83
- toggleExpand,
84
- copyItems,
85
- addSession,
86
- addLesson,
87
- deleteSession,
88
- deleteLesson,
89
- deleteSelected,
90
- duplicateSelected,
91
- showConfirm,
92
- });
93
-
94
- useEffect(() => {
95
- stateRef.current = {
96
- course,
97
- sessions,
98
- lessons,
99
- expandedIds,
100
- filterQuery,
101
- selectedIds,
102
- activeItemId,
103
- activeItemType,
104
- selectItem,
105
- clearSelection,
106
- setFilter,
107
- toggleExpand,
108
- copyItems,
109
- addSession,
110
- addLesson,
111
- deleteSession,
112
- deleteLesson,
113
- deleteSelected,
114
- duplicateSelected,
115
- showConfirm,
116
- };
117
- });
118
-
119
- const handleKeyDown = useCallback(
120
- (e: KeyboardEvent) => {
121
- const target = document.activeElement;
122
- const ctrl = e.ctrlKey || e.metaKey;
123
-
124
- // ── Ctrl/Cmd + ? always work (help, search) ──────────────────────────
125
- if (ctrl && e.key === '/') {
126
- e.preventDefault();
127
- onToggleHelp();
128
- return;
129
- }
130
-
131
- if (ctrl && e.key.toLowerCase() === 'f') {
132
- e.preventDefault();
133
- searchRef.current?.focus();
134
- return;
135
- }
136
-
137
- // ── Ctrl/Cmd + S — save active detail panel form ──────────────────────
138
- if (ctrl && e.key.toLowerCase() === 's') {
139
- e.preventDefault();
140
- if (detailPanelRef.current) {
141
- const submitBtn =
142
- detailPanelRef.current.querySelector<HTMLButtonElement>(
143
- 'button[type="submit"]'
144
- );
145
- submitBtn?.click();
146
- }
147
- return;
148
- }
149
-
150
- // ── Suppress all other shortcuts when editing ─────────────────────────
151
- if (isEditingTarget(target)) return;
152
-
153
- const s = stateRef.current;
154
-
155
- // Build the current visible list (same logic as the tree)
156
- const { items } = buildVisibleItems(
157
- s.course,
158
- s.sessions,
159
- s.lessons,
160
- s.expandedIds,
161
- s.filterQuery
162
- );
163
-
164
- const currentIdx = items.findIndex((i) => i.id === s.activeItemId);
165
- const current = currentIdx !== -1 ? items[currentIdx] : null;
166
-
167
- // ── Arrow navigation ──────────────────────────────────────────────────
168
- if (e.key === 'ArrowDown') {
169
- e.preventDefault();
170
- const next = items[currentIdx + 1];
171
- if (next) s.selectItem(next.id, next.type, {}, items);
172
- return;
173
- }
174
-
175
- if (e.key === 'ArrowUp') {
176
- e.preventDefault();
177
- const prev = items[currentIdx - 1];
178
- if (prev) s.selectItem(prev.id, prev.type, {}, items);
179
- return;
180
- }
181
-
182
- if (e.key === 'ArrowRight') {
183
- e.preventDefault();
184
- if (current?.type === 'session' && !s.expandedIds.has(current.id)) {
185
- s.toggleExpand(current.id);
186
- }
187
- return;
188
- }
189
-
190
- if (e.key === 'ArrowLeft') {
191
- e.preventDefault();
192
- if (current?.type === 'session' && s.expandedIds.has(current.id)) {
193
- s.toggleExpand(current.id);
194
- } else if (current?.type === 'lesson') {
195
- // Go to parent session
196
- const parent = items.find(
197
- (i) => i.type === 'session' && i.id === current.data.sessionId
198
- );
199
- if (parent) s.selectItem(parent.id, 'session', {}, items);
200
- }
201
- return;
202
- }
203
-
204
- // ── Enter — focus first detail field ─────────────────────────────────
205
- if (e.key === 'Enter') {
206
- e.preventDefault();
207
- if (detailPanelRef.current) {
208
- const firstInput = detailPanelRef.current.querySelector<HTMLElement>(
209
- 'input:not([disabled]),textarea:not([disabled])'
210
- );
211
- firstInput?.focus();
212
- }
213
- return;
214
- }
215
-
216
- // ── Escape ────────────────────────────────────────────────────────────
217
- if (e.key === 'Escape') {
218
- e.preventDefault();
219
- if (s.filterQuery) {
220
- s.setFilter('');
221
- searchRef.current?.clear();
222
- } else if (s.selectedIds.size > 1) {
223
- s.clearSelection();
224
- } else {
225
- (document.activeElement as HTMLElement)?.blur();
226
- }
227
- return;
228
- }
229
-
230
- // ── Delete ────────────────────────────────────────────────────────────
231
- if (e.key === 'Delete') {
232
- e.preventDefault();
233
- const ids = [...s.selectedIds].filter(
234
- (key) => key !== `course:${s.course.id}`
235
- );
236
- if (!ids.length) return;
237
- const count = ids.length;
238
- s.showConfirm({
239
- title: `Excluir ${count > 1 ? count + ' itens' : 'item selecionado'}?`,
240
- description: 'Esta ação não pode ser desfeita.',
241
- onConfirm: () => {
242
- s.deleteSelected();
243
- toast.success(
244
- count > 1 ? `${count} itens excluídos` : 'Item excluído'
245
- );
246
- },
247
- });
248
- return;
249
- }
250
-
251
- // ── Ctrl/Cmd + C ─────────────────────────────────────────────────────
252
- if (ctrl && e.key.toLowerCase() === 'c') {
253
- e.preventDefault();
254
- const selectedKeys = [...s.selectedIds].filter(
255
- (key) => key.startsWith('lesson:') || key.startsWith('session:')
256
- );
257
- if (!selectedKeys.length) return;
258
- const firstKey = selectedKeys[0]!;
259
- const type = firstKey.startsWith('lesson:') ? 'lesson' : 'session';
260
- const ids = selectedKeys.map((key) => key.slice(key.indexOf(':') + 1));
261
- s.copyItems(ids, type);
262
- toast.success(
263
- `${ids.length > 1 ? ids.length + ' itens copiados' : 'Item copiado'}`
264
- );
265
- return;
266
- }
267
-
268
- // ── Ctrl/Cmd + D — duplicate ──────────────────────────────────────────
269
- if (ctrl && e.key.toLowerCase() === 'd') {
270
- e.preventDefault();
271
- if (onDuplicate) {
272
- onDuplicate();
273
- } else {
274
- s.duplicateSelected();
275
- toast.success('Item duplicado');
276
- }
277
- return;
278
- }
279
-
280
- // ── Ctrl/Cmd + V — paste ──────────────────────────────────────────────
281
- if (ctrl && e.key.toLowerCase() === 'v') {
282
- e.preventDefault();
283
- if (onPaste) {
284
- onPaste();
285
- } else {
286
- toast('Nada para colar');
287
- }
288
- return;
289
- }
290
-
291
- // ── Ctrl/Cmd + N — new item ───────────────────────────────────────────
292
- if (ctrl && e.key.toLowerCase() === 'n') {
293
- e.preventDefault();
294
- if (s.activeItemType === 'course') {
295
- s.addSession();
296
- toast.success('Nova sessão criada');
297
- } else if (s.activeItemType === 'session' && s.activeItemId) {
298
- s.addLesson(s.activeItemId);
299
- toast.success('Nova aula criada');
300
- } else if (s.activeItemType === 'lesson') {
301
- // Find parent session and add there
302
- const lesson = s.lessons.find((l) => l.id === s.activeItemId);
303
- if (lesson) {
304
- s.addLesson(lesson.sessionId);
305
- toast.success('Nova aula criada na mesma sessão');
306
- }
307
- }
308
- return;
309
- }
310
- },
311
- [onToggleHelp, searchRef, detailPanelRef, onDuplicate, onPaste]
312
- );
313
-
314
- useEffect(() => {
315
- window.addEventListener('keydown', handleKeyDown);
316
- return () => window.removeEventListener('keydown', handleKeyDown);
317
- }, [handleKeyDown]);
318
- }
1
+ 'use client';
2
+
3
+ import { useCallback, useEffect, useRef } from 'react';
4
+ import { toast } from 'sonner';
5
+
6
+ import type { SearchFilterHandle } from './search-filter';
7
+ import { useStructureStore } from './store';
8
+ import { buildVisibleItems } from './tree-helpers';
9
+
10
+ /**
11
+ * Checks whether keyboard shortcut handling should be suppressed
12
+ * (the user is typing in a form control or rich-text editor).
13
+ */
14
+ function isEditingTarget(el: Element | null): boolean {
15
+ if (!el) return false;
16
+ const tag = (el as HTMLElement).tagName.toLowerCase();
17
+ if (tag === 'input' || tag === 'textarea' || tag === 'select') return true;
18
+ // contenteditable (Tiptap, etc.)
19
+ if ((el as HTMLElement).isContentEditable) return true;
20
+ // shadcn Select portal, combobox, etc. — has [role=combobox] or [role=listbox]
21
+ const role = (el as HTMLElement).getAttribute('role');
22
+ if (role === 'combobox' || role === 'listbox' || role === 'option')
23
+ return true;
24
+ return false;
25
+ }
26
+
27
+ interface UseCourseStructureShortcutsOptions {
28
+ /** Ref to the SearchFilter imperative handle so Ctrl+F / Escape can focus/clear it. */
29
+ searchRef: React.RefObject<SearchFilterHandle | null>;
30
+ /** Ref to the detail panel wrapper div (for Enter key focusing). */
31
+ detailPanelRef: React.RefObject<HTMLDivElement | null>;
32
+ /** Callback to open/close the shortcuts help sheet. */
33
+ onToggleHelp: () => void;
34
+ /** API-backed duplicate (session or lesson based on active item). */
35
+ onDuplicate?: () => void;
36
+ /** API-backed paste (lesson or session clipboard). */
37
+ onPaste?: () => void;
38
+ }
39
+
40
+ export function useCourseStructureShortcuts({
41
+ searchRef,
42
+ detailPanelRef,
43
+ onToggleHelp,
44
+ onDuplicate,
45
+ onPaste,
46
+ }: UseCourseStructureShortcutsOptions) {
47
+ const course = useStructureStore((s) => s.course);
48
+ const sessions = useStructureStore((s) => s.sessions);
49
+ const lessons = useStructureStore((s) => s.lessons);
50
+ const expandedIds = useStructureStore((s) => s.expandedIds);
51
+ const filterQuery = useStructureStore((s) => s.filterQuery);
52
+ const selectedIds = useStructureStore((s) => s.selectedIds);
53
+
54
+ const activeItemId = useStructureStore((s) => s.activeItemId);
55
+ const activeItemType = useStructureStore((s) => s.activeItemType);
56
+
57
+ const selectItem = useStructureStore((s) => s.selectItem);
58
+ const clearSelection = useStructureStore((s) => s.clearSelection);
59
+ const setFilter = useStructureStore((s) => s.setFilter);
60
+ const toggleExpand = useStructureStore((s) => s.toggleExpand);
61
+ const copyItems = useStructureStore((s) => s.copyItems);
62
+ const addSession = useStructureStore((s) => s.addSession);
63
+ const addLesson = useStructureStore((s) => s.addLesson);
64
+ const deleteSession = useStructureStore((s) => s.deleteSession);
65
+ const deleteLesson = useStructureStore((s) => s.deleteLesson);
66
+ const deleteSelected = useStructureStore((s) => s.deleteSelected);
67
+ const duplicateSelected = useStructureStore((s) => s.duplicateSelected);
68
+ const showConfirm = useStructureStore((s) => s.showConfirm);
69
+
70
+ // Keep a stable ref to store values so handlers don't stale-close
71
+ const stateRef = useRef({
72
+ course,
73
+ sessions,
74
+ lessons,
75
+ expandedIds,
76
+ filterQuery,
77
+ selectedIds,
78
+ activeItemId,
79
+ activeItemType,
80
+ selectItem,
81
+ clearSelection,
82
+ setFilter,
83
+ toggleExpand,
84
+ copyItems,
85
+ addSession,
86
+ addLesson,
87
+ deleteSession,
88
+ deleteLesson,
89
+ deleteSelected,
90
+ duplicateSelected,
91
+ showConfirm,
92
+ });
93
+
94
+ useEffect(() => {
95
+ stateRef.current = {
96
+ course,
97
+ sessions,
98
+ lessons,
99
+ expandedIds,
100
+ filterQuery,
101
+ selectedIds,
102
+ activeItemId,
103
+ activeItemType,
104
+ selectItem,
105
+ clearSelection,
106
+ setFilter,
107
+ toggleExpand,
108
+ copyItems,
109
+ addSession,
110
+ addLesson,
111
+ deleteSession,
112
+ deleteLesson,
113
+ deleteSelected,
114
+ duplicateSelected,
115
+ showConfirm,
116
+ };
117
+ });
118
+
119
+ const handleKeyDown = useCallback(
120
+ (e: KeyboardEvent) => {
121
+ const target = document.activeElement;
122
+ const ctrl = e.ctrlKey || e.metaKey;
123
+
124
+ // ── Ctrl/Cmd + ? always work (help, search) ──────────────────────────
125
+ if (ctrl && e.key === '/') {
126
+ e.preventDefault();
127
+ onToggleHelp();
128
+ return;
129
+ }
130
+
131
+ if (ctrl && e.key.toLowerCase() === 'f') {
132
+ e.preventDefault();
133
+ searchRef.current?.focus();
134
+ return;
135
+ }
136
+
137
+ // ── Ctrl/Cmd + S — save active detail panel form ──────────────────────
138
+ if (ctrl && e.key.toLowerCase() === 's') {
139
+ e.preventDefault();
140
+ if (detailPanelRef.current) {
141
+ const submitBtn =
142
+ detailPanelRef.current.querySelector<HTMLButtonElement>(
143
+ 'button[type="submit"]'
144
+ );
145
+ submitBtn?.click();
146
+ }
147
+ return;
148
+ }
149
+
150
+ // ── Suppress all other shortcuts when editing ─────────────────────────
151
+ if (isEditingTarget(target)) return;
152
+
153
+ const s = stateRef.current;
154
+
155
+ // Build the current visible list (same logic as the tree)
156
+ const { items } = buildVisibleItems(
157
+ s.course,
158
+ s.sessions,
159
+ s.lessons,
160
+ s.expandedIds,
161
+ s.filterQuery
162
+ );
163
+
164
+ const currentIdx = items.findIndex((i) => i.id === s.activeItemId);
165
+ const current = currentIdx !== -1 ? items[currentIdx] : null;
166
+
167
+ // ── Arrow navigation ──────────────────────────────────────────────────
168
+ if (e.key === 'ArrowDown') {
169
+ e.preventDefault();
170
+ const next = items[currentIdx + 1];
171
+ if (next) s.selectItem(next.id, next.type, {}, items);
172
+ return;
173
+ }
174
+
175
+ if (e.key === 'ArrowUp') {
176
+ e.preventDefault();
177
+ const prev = items[currentIdx - 1];
178
+ if (prev) s.selectItem(prev.id, prev.type, {}, items);
179
+ return;
180
+ }
181
+
182
+ if (e.key === 'ArrowRight') {
183
+ e.preventDefault();
184
+ if (current?.type === 'session' && !s.expandedIds.has(current.id)) {
185
+ s.toggleExpand(current.id);
186
+ }
187
+ return;
188
+ }
189
+
190
+ if (e.key === 'ArrowLeft') {
191
+ e.preventDefault();
192
+ if (current?.type === 'session' && s.expandedIds.has(current.id)) {
193
+ s.toggleExpand(current.id);
194
+ } else if (current?.type === 'lesson') {
195
+ // Go to parent session
196
+ const parent = items.find(
197
+ (i) => i.type === 'session' && i.id === current.data.sessionId
198
+ );
199
+ if (parent) s.selectItem(parent.id, 'session', {}, items);
200
+ }
201
+ return;
202
+ }
203
+
204
+ // ── Enter — focus first detail field ─────────────────────────────────
205
+ if (e.key === 'Enter') {
206
+ e.preventDefault();
207
+ if (detailPanelRef.current) {
208
+ const firstInput = detailPanelRef.current.querySelector<HTMLElement>(
209
+ 'input:not([disabled]),textarea:not([disabled])'
210
+ );
211
+ firstInput?.focus();
212
+ }
213
+ return;
214
+ }
215
+
216
+ // ── Escape ────────────────────────────────────────────────────────────
217
+ if (e.key === 'Escape') {
218
+ e.preventDefault();
219
+ if (s.filterQuery) {
220
+ s.setFilter('');
221
+ searchRef.current?.clear();
222
+ } else if (s.selectedIds.size > 1) {
223
+ s.clearSelection();
224
+ } else {
225
+ (document.activeElement as HTMLElement)?.blur();
226
+ }
227
+ return;
228
+ }
229
+
230
+ // ── Delete ────────────────────────────────────────────────────────────
231
+ if (e.key === 'Delete') {
232
+ e.preventDefault();
233
+ const ids = [...s.selectedIds].filter(
234
+ (key) => key !== `course:${s.course.id}`
235
+ );
236
+ if (!ids.length) return;
237
+ const count = ids.length;
238
+ s.showConfirm({
239
+ title: `Excluir ${count > 1 ? count + ' itens' : 'item selecionado'}?`,
240
+ description: 'Esta ação não pode ser desfeita.',
241
+ onConfirm: () => {
242
+ s.deleteSelected();
243
+ toast.success(
244
+ count > 1 ? `${count} itens excluídos` : 'Item excluído'
245
+ );
246
+ },
247
+ });
248
+ return;
249
+ }
250
+
251
+ // ── Ctrl/Cmd + C ─────────────────────────────────────────────────────
252
+ if (ctrl && e.key.toLowerCase() === 'c') {
253
+ e.preventDefault();
254
+ const selectedKeys = [...s.selectedIds].filter(
255
+ (key) => key.startsWith('lesson:') || key.startsWith('session:')
256
+ );
257
+ if (!selectedKeys.length) return;
258
+ const firstKey = selectedKeys[0]!;
259
+ const type = firstKey.startsWith('lesson:') ? 'lesson' : 'session';
260
+ const ids = selectedKeys.map((key) => key.slice(key.indexOf(':') + 1));
261
+ s.copyItems(ids, type);
262
+ toast.success(
263
+ `${ids.length > 1 ? ids.length + ' itens copiados' : 'Item copiado'}`
264
+ );
265
+ return;
266
+ }
267
+
268
+ // ── Ctrl/Cmd + D — duplicate ──────────────────────────────────────────
269
+ if (ctrl && e.key.toLowerCase() === 'd') {
270
+ e.preventDefault();
271
+ if (onDuplicate) {
272
+ onDuplicate();
273
+ } else {
274
+ s.duplicateSelected();
275
+ toast.success('Item duplicado');
276
+ }
277
+ return;
278
+ }
279
+
280
+ // ── Ctrl/Cmd + V — paste ──────────────────────────────────────────────
281
+ if (ctrl && e.key.toLowerCase() === 'v') {
282
+ e.preventDefault();
283
+ if (onPaste) {
284
+ onPaste();
285
+ } else {
286
+ toast('Nada para colar');
287
+ }
288
+ return;
289
+ }
290
+
291
+ // ── Ctrl/Cmd + N — new item ───────────────────────────────────────────
292
+ if (ctrl && e.key.toLowerCase() === 'n') {
293
+ e.preventDefault();
294
+ if (s.activeItemType === 'course') {
295
+ s.addSession();
296
+ toast.success('Nova sessão criada');
297
+ } else if (s.activeItemType === 'session' && s.activeItemId) {
298
+ s.addLesson(s.activeItemId);
299
+ toast.success('Nova aula criada');
300
+ } else if (s.activeItemType === 'lesson') {
301
+ // Find parent session and add there
302
+ const lesson = s.lessons.find((l) => l.id === s.activeItemId);
303
+ if (lesson) {
304
+ s.addLesson(lesson.sessionId);
305
+ toast.success('Nova aula criada na mesma sessão');
306
+ }
307
+ }
308
+ return;
309
+ }
310
+ },
311
+ [onToggleHelp, searchRef, detailPanelRef, onDuplicate, onPaste]
312
+ );
313
+
314
+ useEffect(() => {
315
+ window.addEventListener('keydown', handleKeyDown);
316
+ return () => window.removeEventListener('keydown', handleKeyDown);
317
+ }, [handleKeyDown]);
318
+ }
@@ -1,10 +1,10 @@
1
- import { redirect } from 'next/navigation';
2
-
3
- export default async function CourseStructureRedirectPage({
4
- params,
5
- }: {
6
- params: Promise<{ id: string }>;
7
- }) {
8
- const { id } = await params;
9
- redirect(`/lms/courses/${id}`);
10
- }
1
+ import { redirect } from 'next/navigation';
2
+
3
+ export default async function CourseStructureRedirectPage({
4
+ params,
5
+ }: {
6
+ params: Promise<{ id: string }>;
7
+ }) {
8
+ const { id } = await params;
9
+ redirect(`/lms/courses/${id}`);
10
+ }
@@ -132,7 +132,8 @@ export function EnterpriseCourseCreateSheet({
132
132
  setSaving(true);
133
133
  try {
134
134
  const payload = {
135
- slug: data.nomeInterno.trim(),
135
+ name: data.nomeInterno.trim(),
136
+ slug: data.slug.trim(),
136
137
  title: data.tituloComercial,
137
138
  description: data.descricao,
138
139
  level: toApiLevel(data.nivel),