@hed-hog/lms 0.0.314 → 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,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
+ }