@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,167 +1,167 @@
1
- 'use client';
2
-
3
- import { Input } from '@/components/ui/input';
4
- import { cn } from '@/lib/utils';
5
- import { ChevronDown, ChevronRight, Layers } from 'lucide-react';
6
- import { useEffect, useRef } from 'react';
7
- import { useUpdateSessionMutation } from '../_data/use-course-structure-mutations';
8
- import { HighlightedText } from './highlighted-text';
9
- import { useStructureStore } from './store';
10
- import type { Session } from './types';
11
-
12
- interface TreeRowSessionProps {
13
- data: Session;
14
- lessonCount: number;
15
- isExpanded: boolean;
16
- isSelected: boolean;
17
- isActive: boolean;
18
- query: string;
19
- isMatched: boolean;
20
- onClick: (e: React.MouseEvent) => void;
21
- onToggleExpand: (e: React.MouseEvent) => void;
22
- }
23
-
24
- export function TreeRowSession({
25
- data,
26
- lessonCount,
27
- isExpanded,
28
- isSelected,
29
- isActive,
30
- query,
31
- onClick,
32
- onToggleExpand,
33
- }: TreeRowSessionProps) {
34
- const inlineRenamingId = useStructureStore((s) => s.inlineRenamingId);
35
- const cancelRename = useStructureStore((s) => s.cancelRename);
36
- const startRename = useStructureStore((s) => s.startRename);
37
- const renameItem = useStructureStore((s) => s.renameItem);
38
-
39
- const updateSession = useUpdateSessionMutation();
40
-
41
- const isRenaming = inlineRenamingId === data.id;
42
- const inputRef = useRef<HTMLInputElement>(null);
43
-
44
- useEffect(() => {
45
- if (isRenaming) {
46
- inputRef.current?.focus();
47
- inputRef.current?.select();
48
- }
49
- }, [isRenaming]);
50
-
51
- function commitAndPersist(newTitle: string) {
52
- const trimmed = newTitle.trim() || data.title;
53
- renameItem(data.id, trimmed);
54
- cancelRename();
55
- updateSession.mutate({
56
- sessionId: data.id,
57
- formValues: {
58
- title: trimmed,
59
- duration: data.duration,
60
- code: data.code,
61
- description: '',
62
- visibility: data.visibility ?? 'publico',
63
- published: data.published ?? false,
64
- },
65
- });
66
- }
67
-
68
- function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
69
- e.stopPropagation();
70
- if (e.key === 'Enter') {
71
- commitAndPersist(inputRef.current?.value ?? data.title);
72
- } else if (e.key === 'Escape') {
73
- cancelRename();
74
- }
75
- }
76
-
77
- const durationH = Math.floor((data.duration ?? 0) / 60);
78
- const durationM = (data.duration ?? 0) % 60;
79
- const durationLabel =
80
- durationH > 0
81
- ? `${durationH}h${durationM > 0 ? ` ${durationM}m` : ''}`
82
- : `${durationM}m`;
83
-
84
- return (
85
- <div
86
- onClick={isRenaming ? undefined : onClick}
87
- onDoubleClick={(e) => {
88
- e.stopPropagation();
89
- startRename(data.id);
90
- }}
91
- role="treeitem"
92
- aria-expanded={isExpanded}
93
- aria-selected={isActive}
94
- className={cn(
95
- 'flex items-center gap-1.5 px-2 py-1.5 rounded-md cursor-pointer select-none text-sm group h-full',
96
- 'transition-colors duration-100',
97
- isActive &&
98
- 'bg-accent text-accent-foreground ring-1 ring-inset ring-primary/20',
99
- isSelected && !isActive && 'bg-accent/60',
100
- !isActive && !isSelected && 'hover:bg-muted/60'
101
- )}
102
- >
103
- {/* Expand/collapse chevron */}
104
- <button
105
- className="shrink-0 text-muted-foreground hover:text-foreground transition-colors p-0.5 rounded focus-visible:ring-1 focus-visible:ring-ring"
106
- onClick={onToggleExpand}
107
- aria-label={isExpanded ? 'Recolher sessão' : 'Expandir sessão'}
108
- tabIndex={-1}
109
- >
110
- {isExpanded ? (
111
- <ChevronDown className="size-3.5" />
112
- ) : (
113
- <ChevronRight className="size-3.5" />
114
- )}
115
- </button>
116
-
117
- {/* Session icon */}
118
- <Layers
119
- className="size-3.5 shrink-0 text-blue-500 dark:text-blue-400"
120
- aria-hidden
121
- />
122
-
123
- {/* Inline rename or title */}
124
- {isRenaming ? (
125
- <Input
126
- ref={inputRef}
127
- defaultValue={data.title}
128
- className="h-5 text-xs px-1 py-0 flex-1 min-w-0"
129
- onClick={(e) => e.stopPropagation()}
130
- onBlur={(e) => commitAndPersist(e.target.value)}
131
- onKeyDown={handleKeyDown}
132
- aria-label="Renomear sessão"
133
- />
134
- ) : (
135
- <span className="truncate flex-1 font-medium leading-tight">
136
- <HighlightedText text={data.title} query={query} />
137
- </span>
138
- )}
139
-
140
- {/* Meta badges (hidden during rename) */}
141
- {!isRenaming && (
142
- <div className="flex items-center gap-1 shrink-0">
143
- {/* Duration - show on hover */}
144
- {data.duration > 0 && (
145
- <span className="text-[0.6rem] text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity tabular-nums">
146
- {durationLabel}
147
- </span>
148
- )}
149
- {/* Lesson count - always visible */}
150
- <span className="text-[0.62rem] font-medium tabular-nums text-muted-foreground min-w-[1.5rem] text-right">
151
- {lessonCount}
152
- </span>
153
- {/* Published status dot */}
154
- <span
155
- className={cn(
156
- 'size-1.5 rounded-full shrink-0',
157
- data.published === false
158
- ? 'bg-muted-foreground/30'
159
- : 'bg-emerald-500'
160
- )}
161
- title={data.published === false ? 'Rascunho' : 'Publicada'}
162
- />
163
- </div>
164
- )}
165
- </div>
166
- );
167
- }
1
+ 'use client';
2
+
3
+ import { Input } from '@/components/ui/input';
4
+ import { cn } from '@/lib/utils';
5
+ import { ChevronDown, ChevronRight, Layers } from 'lucide-react';
6
+ import { useEffect, useRef } from 'react';
7
+ import { useUpdateSessionMutation } from '../_data/use-course-structure-mutations';
8
+ import { HighlightedText } from './highlighted-text';
9
+ import { useStructureStore } from './store';
10
+ import type { Session } from './types';
11
+
12
+ interface TreeRowSessionProps {
13
+ data: Session;
14
+ lessonCount: number;
15
+ isExpanded: boolean;
16
+ isSelected: boolean;
17
+ isActive: boolean;
18
+ query: string;
19
+ isMatched: boolean;
20
+ onClick: (e: React.MouseEvent) => void;
21
+ onToggleExpand: (e: React.MouseEvent) => void;
22
+ }
23
+
24
+ export function TreeRowSession({
25
+ data,
26
+ lessonCount,
27
+ isExpanded,
28
+ isSelected,
29
+ isActive,
30
+ query,
31
+ onClick,
32
+ onToggleExpand,
33
+ }: TreeRowSessionProps) {
34
+ const inlineRenamingId = useStructureStore((s) => s.inlineRenamingId);
35
+ const cancelRename = useStructureStore((s) => s.cancelRename);
36
+ const startRename = useStructureStore((s) => s.startRename);
37
+ const renameItem = useStructureStore((s) => s.renameItem);
38
+
39
+ const updateSession = useUpdateSessionMutation();
40
+
41
+ const isRenaming = inlineRenamingId === data.id;
42
+ const inputRef = useRef<HTMLInputElement>(null);
43
+
44
+ useEffect(() => {
45
+ if (isRenaming) {
46
+ inputRef.current?.focus();
47
+ inputRef.current?.select();
48
+ }
49
+ }, [isRenaming]);
50
+
51
+ function commitAndPersist(newTitle: string) {
52
+ const trimmed = newTitle.trim() || data.title;
53
+ renameItem(data.id, trimmed);
54
+ cancelRename();
55
+ updateSession.mutate({
56
+ sessionId: data.id,
57
+ formValues: {
58
+ title: trimmed,
59
+ duration: data.duration,
60
+ code: data.code,
61
+ description: '',
62
+ visibility: data.visibility ?? 'publico',
63
+ published: data.published ?? false,
64
+ },
65
+ });
66
+ }
67
+
68
+ function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
69
+ e.stopPropagation();
70
+ if (e.key === 'Enter') {
71
+ commitAndPersist(inputRef.current?.value ?? data.title);
72
+ } else if (e.key === 'Escape') {
73
+ cancelRename();
74
+ }
75
+ }
76
+
77
+ const durationH = Math.floor((data.duration ?? 0) / 60);
78
+ const durationM = (data.duration ?? 0) % 60;
79
+ const durationLabel =
80
+ durationH > 0
81
+ ? `${durationH}h${durationM > 0 ? ` ${durationM}m` : ''}`
82
+ : `${durationM}m`;
83
+
84
+ return (
85
+ <div
86
+ onClick={isRenaming ? undefined : onClick}
87
+ onDoubleClick={(e) => {
88
+ e.stopPropagation();
89
+ startRename(data.id);
90
+ }}
91
+ role="treeitem"
92
+ aria-expanded={isExpanded}
93
+ aria-selected={isActive}
94
+ className={cn(
95
+ 'flex items-center gap-1.5 px-2 py-1.5 rounded-md cursor-pointer select-none text-sm group h-full',
96
+ 'transition-colors duration-100',
97
+ isActive &&
98
+ 'bg-accent text-accent-foreground ring-1 ring-inset ring-primary/20',
99
+ isSelected && !isActive && 'bg-accent/60',
100
+ !isActive && !isSelected && 'hover:bg-muted/60'
101
+ )}
102
+ >
103
+ {/* Expand/collapse chevron */}
104
+ <button
105
+ className="shrink-0 text-muted-foreground hover:text-foreground transition-colors p-0.5 rounded focus-visible:ring-1 focus-visible:ring-ring"
106
+ onClick={onToggleExpand}
107
+ aria-label={isExpanded ? 'Recolher sessão' : 'Expandir sessão'}
108
+ tabIndex={-1}
109
+ >
110
+ {isExpanded ? (
111
+ <ChevronDown className="size-3.5" />
112
+ ) : (
113
+ <ChevronRight className="size-3.5" />
114
+ )}
115
+ </button>
116
+
117
+ {/* Session icon */}
118
+ <Layers
119
+ className="size-3.5 shrink-0 text-blue-500 dark:text-blue-400"
120
+ aria-hidden
121
+ />
122
+
123
+ {/* Inline rename or title */}
124
+ {isRenaming ? (
125
+ <Input
126
+ ref={inputRef}
127
+ defaultValue={data.title}
128
+ className="h-5 text-xs px-1 py-0 flex-1 min-w-0"
129
+ onClick={(e) => e.stopPropagation()}
130
+ onBlur={(e) => commitAndPersist(e.target.value)}
131
+ onKeyDown={handleKeyDown}
132
+ aria-label="Renomear sessão"
133
+ />
134
+ ) : (
135
+ <span className="truncate flex-1 font-medium leading-tight">
136
+ <HighlightedText text={data.title} query={query} />
137
+ </span>
138
+ )}
139
+
140
+ {/* Meta badges (hidden during rename) */}
141
+ {!isRenaming && (
142
+ <div className="flex items-center gap-1 shrink-0">
143
+ {/* Duration - show on hover */}
144
+ {data.duration > 0 && (
145
+ <span className="text-[0.6rem] text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity tabular-nums">
146
+ {durationLabel}
147
+ </span>
148
+ )}
149
+ {/* Lesson count - always visible */}
150
+ <span className="text-[0.62rem] font-medium tabular-nums text-muted-foreground min-w-[1.5rem] text-right">
151
+ {lessonCount}
152
+ </span>
153
+ {/* Published status dot */}
154
+ <span
155
+ className={cn(
156
+ 'size-1.5 rounded-full shrink-0',
157
+ data.published === false
158
+ ? 'bg-muted-foreground/30'
159
+ : 'bg-emerald-500'
160
+ )}
161
+ title={data.published === false ? 'Rascunho' : 'Publicada'}
162
+ />
163
+ </div>
164
+ )}
165
+ </div>
166
+ );
167
+ }
@@ -1,108 +1,108 @@
1
- 'use client';
2
-
3
- import { useCallback } from 'react';
4
-
5
- import { useStructureStore } from './store';
6
- import { TreeContextMenu } from './tree-context-menu';
7
- import { TreeRowCourse } from './tree-row-course';
8
- import { TreeRowLesson } from './tree-row-lesson';
9
- import { TreeRowSession } from './tree-row-session';
10
- import type { FlatItem, Lesson, Session } from './types';
11
-
12
- interface TreeRowProps {
13
- item: FlatItem;
14
- isActive: boolean;
15
- isSelected: boolean;
16
- query: string;
17
- isMatched: boolean;
18
- isEffectivelyExpanded: boolean;
19
- lessonCountMap: Map<string, number>;
20
- /** Full ordered visible list — required for SHIFT-range selection. */
21
- visibleItems: FlatItem[];
22
- }
23
-
24
- export function TreeRow({
25
- item,
26
- isActive,
27
- isSelected,
28
- query,
29
- isMatched,
30
- isEffectivelyExpanded,
31
- lessonCountMap,
32
- visibleItems,
33
- }: TreeRowProps) {
34
- const selectItem = useStructureStore((s) => s.selectItem);
35
- const toggleExpand = useStructureStore((s) => s.toggleExpand);
36
- const setMobileSheetOpen = useStructureStore((s) => s.setMobileSheetOpen);
37
-
38
- const handleClick = useCallback(
39
- (e: React.MouseEvent) => {
40
- e.stopPropagation();
41
- const modifiers = {
42
- ctrl: e.ctrlKey || e.metaKey,
43
- shift: e.shiftKey,
44
- };
45
- selectItem(item.id, item.type, modifiers, visibleItems);
46
- // Close mobile sheet only on non-session items (sessions expand in-place)
47
- if (item.type !== 'session' && !modifiers.ctrl && !modifiers.shift) {
48
- setMobileSheetOpen(false);
49
- }
50
- },
51
- [item.id, item.type, selectItem, setMobileSheetOpen, visibleItems]
52
- );
53
-
54
- const handleToggleExpand = useCallback(
55
- (e: React.MouseEvent) => {
56
- e.stopPropagation();
57
- toggleExpand(item.id);
58
- },
59
- [item.id, toggleExpand]
60
- );
61
-
62
- if (item.type === 'course') {
63
- return (
64
- <TreeContextMenu item={item}>
65
- <TreeRowCourse
66
- data={item.data}
67
- isSelected={isSelected}
68
- isActive={isActive}
69
- query={query}
70
- isMatched={isMatched}
71
- onClick={handleClick}
72
- />
73
- </TreeContextMenu>
74
- );
75
- }
76
-
77
- if (item.type === 'session') {
78
- const lessonCount = lessonCountMap.get(item.id) ?? 0;
79
- return (
80
- <TreeContextMenu item={item}>
81
- <TreeRowSession
82
- data={item.data as Session}
83
- lessonCount={lessonCount}
84
- isExpanded={isEffectivelyExpanded}
85
- isSelected={isSelected}
86
- isActive={isActive}
87
- query={query}
88
- isMatched={isMatched}
89
- onClick={handleClick}
90
- onToggleExpand={handleToggleExpand}
91
- />
92
- </TreeContextMenu>
93
- );
94
- }
95
-
96
- return (
97
- <TreeContextMenu item={item}>
98
- <TreeRowLesson
99
- data={item.data as Lesson}
100
- isSelected={isSelected}
101
- isActive={isActive}
102
- query={query}
103
- isMatched={isMatched}
104
- onClick={handleClick}
105
- />
106
- </TreeContextMenu>
107
- );
108
- }
1
+ 'use client';
2
+
3
+ import { useCallback } from 'react';
4
+
5
+ import { useStructureStore } from './store';
6
+ import { TreeContextMenu } from './tree-context-menu';
7
+ import { TreeRowCourse } from './tree-row-course';
8
+ import { TreeRowLesson } from './tree-row-lesson';
9
+ import { TreeRowSession } from './tree-row-session';
10
+ import type { FlatItem, Lesson, Session } from './types';
11
+
12
+ interface TreeRowProps {
13
+ item: FlatItem;
14
+ isActive: boolean;
15
+ isSelected: boolean;
16
+ query: string;
17
+ isMatched: boolean;
18
+ isEffectivelyExpanded: boolean;
19
+ lessonCountMap: Map<string, number>;
20
+ /** Full ordered visible list — required for SHIFT-range selection. */
21
+ visibleItems: FlatItem[];
22
+ }
23
+
24
+ export function TreeRow({
25
+ item,
26
+ isActive,
27
+ isSelected,
28
+ query,
29
+ isMatched,
30
+ isEffectivelyExpanded,
31
+ lessonCountMap,
32
+ visibleItems,
33
+ }: TreeRowProps) {
34
+ const selectItem = useStructureStore((s) => s.selectItem);
35
+ const toggleExpand = useStructureStore((s) => s.toggleExpand);
36
+ const setMobileSheetOpen = useStructureStore((s) => s.setMobileSheetOpen);
37
+
38
+ const handleClick = useCallback(
39
+ (e: React.MouseEvent) => {
40
+ e.stopPropagation();
41
+ const modifiers = {
42
+ ctrl: e.ctrlKey || e.metaKey,
43
+ shift: e.shiftKey,
44
+ };
45
+ selectItem(item.id, item.type, modifiers, visibleItems);
46
+ // Close mobile sheet only on non-session items (sessions expand in-place)
47
+ if (item.type !== 'session' && !modifiers.ctrl && !modifiers.shift) {
48
+ setMobileSheetOpen(false);
49
+ }
50
+ },
51
+ [item.id, item.type, selectItem, setMobileSheetOpen, visibleItems]
52
+ );
53
+
54
+ const handleToggleExpand = useCallback(
55
+ (e: React.MouseEvent) => {
56
+ e.stopPropagation();
57
+ toggleExpand(item.id);
58
+ },
59
+ [item.id, toggleExpand]
60
+ );
61
+
62
+ if (item.type === 'course') {
63
+ return (
64
+ <TreeContextMenu item={item}>
65
+ <TreeRowCourse
66
+ data={item.data}
67
+ isSelected={isSelected}
68
+ isActive={isActive}
69
+ query={query}
70
+ isMatched={isMatched}
71
+ onClick={handleClick}
72
+ />
73
+ </TreeContextMenu>
74
+ );
75
+ }
76
+
77
+ if (item.type === 'session') {
78
+ const lessonCount = lessonCountMap.get(item.id) ?? 0;
79
+ return (
80
+ <TreeContextMenu item={item}>
81
+ <TreeRowSession
82
+ data={item.data as Session}
83
+ lessonCount={lessonCount}
84
+ isExpanded={isEffectivelyExpanded}
85
+ isSelected={isSelected}
86
+ isActive={isActive}
87
+ query={query}
88
+ isMatched={isMatched}
89
+ onClick={handleClick}
90
+ onToggleExpand={handleToggleExpand}
91
+ />
92
+ </TreeContextMenu>
93
+ );
94
+ }
95
+
96
+ return (
97
+ <TreeContextMenu item={item}>
98
+ <TreeRowLesson
99
+ data={item.data as Lesson}
100
+ isSelected={isSelected}
101
+ isActive={isActive}
102
+ query={query}
103
+ isMatched={isMatched}
104
+ onClick={handleClick}
105
+ />
106
+ </TreeContextMenu>
107
+ );
108
+ }