@hed-hog/lms 0.0.312 → 0.0.315

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 (68) hide show
  1. package/dist/class-group/class-group.controller.d.ts +2 -2
  2. package/dist/class-group/class-group.service.d.ts +2 -2
  3. package/dist/enterprise/dto/enterprise-profile.dto.d.ts +13 -0
  4. package/dist/enterprise/dto/enterprise-profile.dto.d.ts.map +1 -0
  5. package/dist/enterprise/dto/enterprise-profile.dto.js +3 -0
  6. package/dist/enterprise/dto/enterprise-profile.dto.js.map +1 -0
  7. package/dist/enterprise/enterprise.controller.d.ts +3 -0
  8. package/dist/enterprise/enterprise.controller.d.ts.map +1 -1
  9. package/dist/enterprise/enterprise.controller.js +14 -0
  10. package/dist/enterprise/enterprise.controller.js.map +1 -1
  11. package/dist/enterprise/enterprise.service.d.ts +3 -0
  12. package/dist/enterprise/enterprise.service.d.ts.map +1 -1
  13. package/dist/enterprise/enterprise.service.js +128 -1
  14. package/dist/enterprise/enterprise.service.js.map +1 -1
  15. package/dist/instructor/instructor.controller.d.ts +23 -0
  16. package/dist/instructor/instructor.controller.d.ts.map +1 -1
  17. package/dist/instructor/instructor.controller.js +41 -0
  18. package/dist/instructor/instructor.controller.js.map +1 -1
  19. package/dist/instructor/instructor.service.d.ts +25 -0
  20. package/dist/instructor/instructor.service.d.ts.map +1 -1
  21. package/dist/instructor/instructor.service.js +126 -8
  22. package/dist/instructor/instructor.service.js.map +1 -1
  23. package/hedhog/data/menu.yaml +23 -7
  24. package/hedhog/data/role.yaml +9 -1
  25. package/hedhog/data/route.yaml +54 -0
  26. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +2 -1
  27. package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +44 -44
  28. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +362 -362
  29. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-panel.tsx.ejs +111 -111
  30. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +134 -134
  31. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-course.tsx.ejs +113 -113
  32. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +314 -314
  33. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-panel.tsx.ejs +62 -62
  34. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +173 -173
  35. package/hedhog/frontend/app/courses/[id]/structure/_components/drag-handle.tsx.ejs +58 -58
  36. package/hedhog/frontend/app/courses/[id]/structure/_components/drag-overlay.tsx.ejs +51 -51
  37. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +276 -276
  38. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +1216 -1216
  39. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +1824 -1824
  40. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +443 -443
  41. package/hedhog/frontend/app/courses/[id]/structure/_components/highlighted-text.tsx.ejs +40 -40
  42. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +185 -185
  43. package/hedhog/frontend/app/courses/[id]/structure/_components/multi-select-bar.tsx.ejs +264 -264
  44. package/hedhog/frontend/app/courses/[id]/structure/_components/search-filter.tsx.ejs +95 -95
  45. package/hedhog/frontend/app/courses/[id]/structure/_components/session-picker-dialog.tsx.ejs +73 -73
  46. package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +136 -136
  47. package/hedhog/frontend/app/courses/[id]/structure/_components/sortable-tree-row.tsx.ejs +80 -80
  48. package/hedhog/frontend/app/courses/[id]/structure/_components/store.ts.ejs +949 -949
  49. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-context-menu.tsx.ejs +525 -525
  50. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-helpers.ts.ejs +181 -181
  51. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-course.tsx.ejs +51 -51
  52. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +271 -271
  53. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +167 -167
  54. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row.tsx.ejs +108 -108
  55. package/hedhog/frontend/app/courses/[id]/structure/_components/use-course-structure-shortcuts.ts.ejs +318 -318
  56. package/hedhog/frontend/app/courses/[id]/structure/page.tsx.ejs +10 -10
  57. package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +2 -1
  58. package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +438 -0
  59. package/hedhog/frontend/app/instructors/_components/instructor-types.ts.ejs +40 -0
  60. package/hedhog/frontend/app/instructors/page.tsx.ejs +696 -0
  61. package/hedhog/frontend/app/training/page.tsx.ejs +2339 -0
  62. package/hedhog/table/enterprise_user.yaml +1 -1
  63. package/package.json +8 -8
  64. package/src/enterprise/dto/enterprise-profile.dto.ts +17 -0
  65. package/src/enterprise/enterprise.controller.ts +9 -1
  66. package/src/enterprise/enterprise.service.ts +147 -4
  67. package/src/instructor/instructor.controller.ts +36 -9
  68. 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),