@hed-hog/lms 0.0.351 → 0.0.354
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/course/course-audio-transcription.service.d.ts +29 -0
- package/dist/course/course-audio-transcription.service.d.ts.map +1 -0
- package/dist/course/course-audio-transcription.service.js +291 -0
- package/dist/course/course-audio-transcription.service.js.map +1 -0
- package/dist/course/course-lesson.controller.d.ts +10 -0
- package/dist/course/course-lesson.controller.d.ts.map +1 -0
- package/dist/course/course-lesson.controller.js +62 -0
- package/dist/course/course-lesson.controller.js.map +1 -0
- package/dist/course/course-structure.controller.d.ts +41 -15
- package/dist/course/course-structure.controller.d.ts.map +1 -1
- package/dist/course/course-structure.controller.js +50 -6
- package/dist/course/course-structure.controller.js.map +1 -1
- package/dist/course/course-structure.service.d.ts +50 -15
- package/dist/course/course-structure.service.d.ts.map +1 -1
- package/dist/course/course-structure.service.js +238 -73
- package/dist/course/course-structure.service.js.map +1 -1
- package/dist/course/course-video-conversion.service.d.ts +20 -2
- package/dist/course/course-video-conversion.service.d.ts.map +1 -1
- package/dist/course/course-video-conversion.service.js +730 -10
- package/dist/course/course-video-conversion.service.js.map +1 -1
- package/dist/course/course.controller.d.ts +24 -8
- package/dist/course/course.controller.d.ts.map +1 -1
- package/dist/course/course.module.d.ts.map +1 -1
- package/dist/course/course.module.js +5 -3
- package/dist/course/course.module.js.map +1 -1
- package/dist/course/course.service.d.ts +24 -8
- package/dist/course/course.service.d.ts.map +1 -1
- package/dist/course/course.service.js +112 -176
- package/dist/course/course.service.js.map +1 -1
- package/dist/course/dto/create-course-lesson-frame.dto.d.ts +5 -0
- package/dist/course/dto/create-course-lesson-frame.dto.d.ts.map +1 -0
- package/dist/course/dto/create-course-lesson-frame.dto.js +30 -0
- package/dist/course/dto/create-course-lesson-frame.dto.js.map +1 -0
- package/dist/course/dto/create-course-structure-lesson.dto.d.ts +4 -2
- package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
- package/dist/course/dto/create-course-structure-lesson.dto.js +10 -3
- package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
- package/dist/course/dto/create-course.dto.d.ts +1 -1
- package/dist/course/dto/create-course.dto.d.ts.map +1 -1
- package/dist/course/dto/create-course.dto.js +6 -6
- package/dist/course/dto/create-course.dto.js.map +1 -1
- package/dist/course/dto/update-course-lesson-frame.dto.d.ts +5 -0
- package/dist/course/dto/update-course-lesson-frame.dto.d.ts.map +1 -0
- package/dist/course/dto/update-course-lesson-frame.dto.js +32 -0
- package/dist/course/dto/update-course-lesson-frame.dto.js.map +1 -0
- package/dist/course/dto/update-course-resources.dto.d.ts +4 -2
- package/dist/course/dto/update-course-resources.dto.d.ts.map +1 -1
- package/dist/course/dto/update-course-resources.dto.js +10 -3
- package/dist/course/dto/update-course-resources.dto.js.map +1 -1
- package/dist/course/dto/update-transcription-segments.dto.d.ts +10 -0
- package/dist/course/dto/update-transcription-segments.dto.d.ts.map +1 -0
- package/dist/course/dto/update-transcription-segments.dto.js +38 -0
- package/dist/course/dto/update-transcription-segments.dto.js.map +1 -0
- package/dist/course/lms-setting.controller.d.ts +13 -0
- package/dist/course/lms-setting.controller.d.ts.map +1 -0
- package/dist/course/lms-setting.controller.js +53 -0
- package/dist/course/lms-setting.controller.js.map +1 -0
- package/dist/enterprise/training/training-admin.service.d.ts.map +1 -1
- package/dist/enterprise/training/training-admin.service.js +74 -33
- package/dist/enterprise/training/training-admin.service.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lms.module.d.ts.map +1 -1
- package/dist/lms.module.js +6 -0
- package/dist/lms.module.js.map +1 -1
- package/hedhog/data/route.yaml +63 -0
- package/hedhog/data/setting_group.yaml +76 -0
- package/hedhog/frontend/app/courses/[id]/_components/CourseCertificateCard.tsx.ejs +3 -0
- package/hedhog/frontend/app/courses/[id]/_components/CourseClassificationCard.tsx.ejs +10 -1
- package/hedhog/frontend/app/courses/[id]/_components/CourseContentCard.tsx.ejs +7 -2
- package/hedhog/frontend/app/courses/[id]/_components/CourseDangerZoneCard.tsx.ejs +5 -2
- package/hedhog/frontend/app/courses/[id]/_components/CourseFlagsCard.tsx.ejs +7 -1
- package/hedhog/frontend/app/courses/[id]/_components/CourseMediaCard.tsx.ejs +3 -0
- package/hedhog/frontend/app/courses/[id]/_components/CourseMultiEntityPicker.tsx.ejs +95 -50
- package/hedhog/frontend/app/courses/[id]/_components/CourseRelationsCard.tsx.ejs +11 -36
- package/hedhog/frontend/app/courses/[id]/_components/CourseSectionCard.tsx.ejs +19 -5
- package/hedhog/frontend/app/courses/[id]/_components/course-edit-types.ts.ejs +1 -1
- package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +30 -10
- package/hedhog/frontend/app/courses/[id]/structure/_components/CourseInstructorsSummaryCard.tsx.ejs +95 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +29 -11
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-panel.tsx.ejs +42 -31
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +10 -1
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +51 -9
- package/hedhog/frontend/app/courses/[id]/structure/_components/drag-handle.tsx.ejs +25 -22
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +12 -9
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +315 -229
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +3220 -534
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +20 -15
- package/hedhog/frontend/app/courses/[id]/structure/_components/icon-action-tooltip.tsx.ejs +35 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/multi-select-bar.tsx.ejs +76 -67
- package/hedhog/frontend/app/courses/[id]/structure/_components/search-filter.tsx.ejs +13 -10
- package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +18 -16
- package/hedhog/frontend/app/courses/[id]/structure/_components/sortable-tree-row.tsx.ejs +6 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-context-menu.tsx.ejs +1 -1
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs +23 -11
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-helpers.ts.ejs +48 -9
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-course.tsx.ejs +11 -2
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +121 -15
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +69 -14
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row.tsx.ejs +11 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +31 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +32 -6
- package/hedhog/frontend/app/courses/[id]/structure/_data/services/course-structure.service.ts.ejs +57 -3
- package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +46 -6
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +14 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-lesson-audio-files.ts.ejs +23 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-lms-settings-query.ts.ejs +34 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-transcription-segments.ts.ejs +76 -0
- package/hedhog/frontend/messages/en.json +39 -3
- package/hedhog/frontend/messages/pt.json +39 -3
- package/hedhog/table/course.yaml +8 -0
- package/hedhog/table/course_lesson_file.yaml +12 -4
- package/hedhog/table/course_lesson_transcription_segment.yaml +22 -0
- package/hedhog/table/course_lesson_video_frame.yaml +25 -0
- package/package.json +9 -9
- package/src/course/course-audio-transcription.service.ts +393 -0
- package/src/course/course-lesson.controller.ts +28 -0
- package/src/course/course-structure.controller.ts +49 -3
- package/src/course/course-structure.service.ts +294 -32
- package/src/course/course-video-conversion.service.ts +972 -6
- package/src/course/course.module.ts +5 -3
- package/src/course/course.service.ts +87 -139
- package/src/course/dto/create-course-lesson-frame.dto.ts +14 -0
- package/src/course/dto/create-course-structure-lesson.dto.ts +19 -3
- package/src/course/dto/create-course.dto.ts +5 -5
- package/src/course/dto/update-course-lesson-frame.dto.ts +16 -0
- package/src/course/dto/update-course-resources.dto.ts +18 -3
- package/src/course/dto/update-transcription-segments.dto.ts +20 -0
- package/src/course/lms-setting.controller.ts +30 -0
- package/src/enterprise/training/training-admin.service.ts +77 -24
- package/src/index.ts +2 -0
- package/src/lms.module.ts +6 -0
- package/hedhog/table/course_instructor.yaml +0 -27
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
useDeleteSessionMutation,
|
|
49
49
|
useUpdateSessionMutation,
|
|
50
50
|
} from '../_data/use-course-structure-mutations';
|
|
51
|
+
import { IconActionTooltip } from './icon-action-tooltip';
|
|
51
52
|
import { useStructureStore } from './store';
|
|
52
53
|
import type { Visibility } from './types';
|
|
53
54
|
|
|
@@ -161,22 +162,26 @@ export function EditorSession({ sessionId }: EditorSessionProps) {
|
|
|
161
162
|
>
|
|
162
163
|
{session.published ? 'Publicada' : 'Oculta'}
|
|
163
164
|
</Badge>
|
|
164
|
-
<
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
size="icon"
|
|
168
|
-
className="size-7 text-destructive/60 hover:text-destructive shrink-0"
|
|
169
|
-
title="Excluir sessão"
|
|
170
|
-
aria-label="Excluir sessão"
|
|
171
|
-
disabled={deleteSession.isPending}
|
|
172
|
-
onClick={handleDelete}
|
|
165
|
+
<IconActionTooltip
|
|
166
|
+
label="Excluir sessão"
|
|
167
|
+
asWrapper={deleteSession.isPending}
|
|
173
168
|
>
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
169
|
+
<Button
|
|
170
|
+
type="button"
|
|
171
|
+
variant="ghost"
|
|
172
|
+
size="icon"
|
|
173
|
+
className="size-7 text-destructive/60 hover:text-destructive shrink-0"
|
|
174
|
+
aria-label="Excluir sessão"
|
|
175
|
+
disabled={deleteSession.isPending}
|
|
176
|
+
onClick={handleDelete}
|
|
177
|
+
>
|
|
178
|
+
{deleteSession.isPending ? (
|
|
179
|
+
<Loader2 className="size-3.5 animate-spin" />
|
|
180
|
+
) : (
|
|
181
|
+
<Trash2 className="size-3.5" />
|
|
182
|
+
)}
|
|
183
|
+
</Button>
|
|
184
|
+
</IconActionTooltip>
|
|
180
185
|
</div>
|
|
181
186
|
|
|
182
187
|
{/* ── Scrollable body ───────────────────────────────────────────────── */}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Tooltip,
|
|
7
|
+
TooltipContent,
|
|
8
|
+
TooltipProvider,
|
|
9
|
+
TooltipTrigger,
|
|
10
|
+
} from '@/components/ui/tooltip';
|
|
11
|
+
|
|
12
|
+
type IconActionTooltipProps = {
|
|
13
|
+
label: ReactNode;
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
16
|
+
asWrapper?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function IconActionTooltip({
|
|
20
|
+
label,
|
|
21
|
+
children,
|
|
22
|
+
side = 'top',
|
|
23
|
+
asWrapper = false,
|
|
24
|
+
}: IconActionTooltipProps) {
|
|
25
|
+
return (
|
|
26
|
+
<TooltipProvider>
|
|
27
|
+
<Tooltip>
|
|
28
|
+
<TooltipTrigger asChild>
|
|
29
|
+
{asWrapper ? <span className="inline-flex">{children}</span> : children}
|
|
30
|
+
</TooltipTrigger>
|
|
31
|
+
<TooltipContent side={side}>{label}</TooltipContent>
|
|
32
|
+
</Tooltip>
|
|
33
|
+
</TooltipProvider>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
useDuplicateSessionMutation,
|
|
13
13
|
useMoveLessonsMutation,
|
|
14
14
|
} from '../_data/use-course-structure-mutations';
|
|
15
|
+
import { IconActionTooltip } from './icon-action-tooltip';
|
|
15
16
|
import { useStructureStore } from './store';
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -175,92 +176,100 @@ export function MultiSelectBar() {
|
|
|
175
176
|
<div className="flex-1" />
|
|
176
177
|
|
|
177
178
|
{/* Copy */}
|
|
178
|
-
<
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
179
|
+
<IconActionTooltip label={t('copyTitle')}>
|
|
180
|
+
<Button
|
|
181
|
+
variant="ghost"
|
|
182
|
+
size="icon"
|
|
183
|
+
className="size-6 text-muted-foreground hover:text-foreground"
|
|
184
|
+
aria-label={t('copyAria')}
|
|
185
|
+
onClick={handleCopy}
|
|
186
|
+
>
|
|
187
|
+
<Copy className="size-3" />
|
|
188
|
+
</Button>
|
|
189
|
+
</IconActionTooltip>
|
|
188
190
|
|
|
189
191
|
{/* Duplicate */}
|
|
190
|
-
<
|
|
191
|
-
variant="ghost"
|
|
192
|
-
size="icon"
|
|
193
|
-
className="size-6 text-muted-foreground hover:text-foreground"
|
|
194
|
-
title={t('duplicateTitle')}
|
|
195
|
-
aria-label={t('duplicateAria')}
|
|
196
|
-
disabled={isDuplicating}
|
|
197
|
-
onClick={handleDuplicate}
|
|
198
|
-
>
|
|
199
|
-
{isDuplicating ? (
|
|
200
|
-
<Loader2 className="size-3 animate-spin" />
|
|
201
|
-
) : (
|
|
202
|
-
<svg
|
|
203
|
-
viewBox="0 0 24 24"
|
|
204
|
-
className="size-3 fill-none stroke-current stroke-2"
|
|
205
|
-
strokeLinecap="round"
|
|
206
|
-
strokeLinejoin="round"
|
|
207
|
-
aria-hidden
|
|
208
|
-
>
|
|
209
|
-
<rect x="9" y="9" width="13" height="13" rx="2" />
|
|
210
|
-
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
|
211
|
-
</svg>
|
|
212
|
-
)}
|
|
213
|
-
</Button>
|
|
214
|
-
|
|
215
|
-
{/* Move (lessons only) */}
|
|
216
|
-
{canMove && (
|
|
192
|
+
<IconActionTooltip label={t('duplicateTitle')} asWrapper={isDuplicating}>
|
|
217
193
|
<Button
|
|
218
194
|
variant="ghost"
|
|
219
195
|
size="icon"
|
|
220
196
|
className="size-6 text-muted-foreground hover:text-foreground"
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
onClick={handleMove}
|
|
197
|
+
aria-label={t('duplicateAria')}
|
|
198
|
+
disabled={isDuplicating}
|
|
199
|
+
onClick={handleDuplicate}
|
|
225
200
|
>
|
|
226
|
-
{
|
|
201
|
+
{isDuplicating ? (
|
|
227
202
|
<Loader2 className="size-3 animate-spin" />
|
|
228
203
|
) : (
|
|
229
|
-
<
|
|
204
|
+
<svg
|
|
205
|
+
viewBox="0 0 24 24"
|
|
206
|
+
className="size-3 fill-none stroke-current stroke-2"
|
|
207
|
+
strokeLinecap="round"
|
|
208
|
+
strokeLinejoin="round"
|
|
209
|
+
aria-hidden
|
|
210
|
+
>
|
|
211
|
+
<rect x="9" y="9" width="13" height="13" rx="2" />
|
|
212
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
|
213
|
+
</svg>
|
|
230
214
|
)}
|
|
231
215
|
</Button>
|
|
216
|
+
</IconActionTooltip>
|
|
217
|
+
|
|
218
|
+
{/* Move (lessons only) */}
|
|
219
|
+
{canMove && (
|
|
220
|
+
<IconActionTooltip label={t('moveTitle')} asWrapper={isMoving}>
|
|
221
|
+
<Button
|
|
222
|
+
variant="ghost"
|
|
223
|
+
size="icon"
|
|
224
|
+
className="size-6 text-muted-foreground hover:text-foreground"
|
|
225
|
+
aria-label={t('moveAria')}
|
|
226
|
+
disabled={isMoving}
|
|
227
|
+
onClick={handleMove}
|
|
228
|
+
>
|
|
229
|
+
{isMoving ? (
|
|
230
|
+
<Loader2 className="size-3 animate-spin" />
|
|
231
|
+
) : (
|
|
232
|
+
<FolderOpen className="size-3" />
|
|
233
|
+
)}
|
|
234
|
+
</Button>
|
|
235
|
+
</IconActionTooltip>
|
|
232
236
|
)}
|
|
233
237
|
|
|
234
238
|
{/* Delete */}
|
|
235
|
-
<
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
className="size-6 text-destructive/60 hover:text-destructive"
|
|
239
|
-
title={t('deleteActionTitle')}
|
|
240
|
-
aria-label={t('deleteActionAria')}
|
|
241
|
-
disabled={bulkDelete.isPending}
|
|
242
|
-
onClick={handleDelete}
|
|
239
|
+
<IconActionTooltip
|
|
240
|
+
label={t('deleteActionTitle')}
|
|
241
|
+
asWrapper={bulkDelete.isPending}
|
|
243
242
|
>
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
243
|
+
<Button
|
|
244
|
+
variant="ghost"
|
|
245
|
+
size="icon"
|
|
246
|
+
className="size-6 text-destructive/60 hover:text-destructive"
|
|
247
|
+
aria-label={t('deleteActionAria')}
|
|
248
|
+
disabled={bulkDelete.isPending}
|
|
249
|
+
onClick={handleDelete}
|
|
250
|
+
>
|
|
251
|
+
{bulkDelete.isPending ? (
|
|
252
|
+
<Loader2 className="size-3 animate-spin" />
|
|
253
|
+
) : (
|
|
254
|
+
<Trash2 className="size-3" />
|
|
255
|
+
)}
|
|
256
|
+
</Button>
|
|
257
|
+
</IconActionTooltip>
|
|
250
258
|
|
|
251
259
|
<Separator orientation="vertical" className="mx-0.5 h-3.5" />
|
|
252
260
|
|
|
253
261
|
{/* Deselect */}
|
|
254
|
-
<
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
262
|
+
<IconActionTooltip label={t('clearTitle')}>
|
|
263
|
+
<Button
|
|
264
|
+
variant="ghost"
|
|
265
|
+
size="icon"
|
|
266
|
+
className="size-6 text-muted-foreground hover:text-foreground"
|
|
267
|
+
aria-label={t('clearAria')}
|
|
268
|
+
onClick={clearSelection}
|
|
269
|
+
>
|
|
270
|
+
<X className="size-3" />
|
|
271
|
+
</Button>
|
|
272
|
+
</IconActionTooltip>
|
|
264
273
|
</div>
|
|
265
274
|
);
|
|
266
275
|
}
|
|
@@ -14,6 +14,7 @@ import { Button } from '@/components/ui/button';
|
|
|
14
14
|
import { Input } from '@/components/ui/input';
|
|
15
15
|
import { cn } from '@/lib/utils';
|
|
16
16
|
import { useDebounce } from '@/hooks/use-debounce';
|
|
17
|
+
import { IconActionTooltip } from './icon-action-tooltip';
|
|
17
18
|
import { useStructureStore } from './store';
|
|
18
19
|
|
|
19
20
|
export interface SearchFilterHandle {
|
|
@@ -77,15 +78,17 @@ export const SearchFilter = forwardRef<SearchFilterHandle, SearchFilterProps>(
|
|
|
77
78
|
{resultCount}
|
|
78
79
|
</span>
|
|
79
80
|
)}
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
81
|
+
<IconActionTooltip label="Limpar busca">
|
|
82
|
+
<Button
|
|
83
|
+
variant="ghost"
|
|
84
|
+
size="icon"
|
|
85
|
+
className="size-5 rounded-sm shrink-0"
|
|
86
|
+
onClick={() => setValue('')}
|
|
87
|
+
aria-label="Limpar busca"
|
|
88
|
+
>
|
|
89
|
+
<X className="size-3" />
|
|
90
|
+
</Button>
|
|
91
|
+
</IconActionTooltip>
|
|
89
92
|
</div>
|
|
90
93
|
)}
|
|
91
94
|
</div>
|
|
@@ -93,4 +96,4 @@ export const SearchFilter = forwardRef<SearchFilterHandle, SearchFilterProps>(
|
|
|
93
96
|
}
|
|
94
97
|
);
|
|
95
98
|
|
|
96
|
-
SearchFilter.displayName = 'SearchFilter';
|
|
99
|
+
SearchFilter.displayName = 'SearchFilter';
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from '@/components/ui/sheet';
|
|
13
13
|
import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
|
|
14
14
|
import { Separator } from '@/components/ui/separator';
|
|
15
|
+
import { IconActionTooltip } from './icon-action-tooltip';
|
|
15
16
|
|
|
16
17
|
// ── Data ──────────────────────────────────────────────────────────────────────
|
|
17
18
|
|
|
@@ -167,22 +168,23 @@ export function ShortcutsHelp({ open, onOpenChange }: ShortcutsHelpProps) {
|
|
|
167
168
|
export function ShortcutsHelpTrigger({ onOpen }: { onOpen: () => void }) {
|
|
168
169
|
const t = useTranslations('lms.CoursesPage.StructurePage.shortcuts');
|
|
169
170
|
return (
|
|
170
|
-
<
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
>
|
|
178
|
-
<Keyboard className="size-3.5" />
|
|
179
|
-
<span className="sr-only sm:not-sr-only">{t('triggerLabel')}</span>
|
|
180
|
-
<Badge
|
|
181
|
-
variant="outline"
|
|
182
|
-
className="ml-0.5 hidden h-4 px-1 text-[0.6rem] font-mono sm:inline-flex"
|
|
171
|
+
<IconActionTooltip label={t('triggerTitle')} side="bottom">
|
|
172
|
+
<Button
|
|
173
|
+
variant="ghost"
|
|
174
|
+
size="sm"
|
|
175
|
+
className="h-8 gap-1.5 px-2 text-xs text-muted-foreground hover:text-foreground sm:px-3"
|
|
176
|
+
onClick={onOpen}
|
|
177
|
+
aria-label={t('triggerTitle')}
|
|
183
178
|
>
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
179
|
+
<Keyboard className="size-3.5" />
|
|
180
|
+
<span className="sr-only sm:not-sr-only">{t('triggerLabel')}</span>
|
|
181
|
+
<Badge
|
|
182
|
+
variant="outline"
|
|
183
|
+
className="ml-0.5 hidden h-4 px-1 text-[0.6rem] font-mono sm:inline-flex"
|
|
184
|
+
>
|
|
185
|
+
Ctrl+/
|
|
186
|
+
</Badge>
|
|
187
|
+
</Button>
|
|
188
|
+
</IconActionTooltip>
|
|
187
189
|
);
|
|
188
190
|
}
|
|
@@ -15,6 +15,10 @@ interface SortableTreeRowProps {
|
|
|
15
15
|
isMatched: boolean;
|
|
16
16
|
isEffectivelyExpanded: boolean;
|
|
17
17
|
lessonCountMap: Map<string, number>;
|
|
18
|
+
sessionIndicatorMap: Map<
|
|
19
|
+
string,
|
|
20
|
+
{ resourceCount: number; videoCount: number }
|
|
21
|
+
>;
|
|
18
22
|
visibleItems: FlatItem[];
|
|
19
23
|
/** When true (filter active) drag is disabled. */
|
|
20
24
|
dragDisabled: boolean;
|
|
@@ -28,6 +32,7 @@ export function SortableTreeRow({
|
|
|
28
32
|
isMatched,
|
|
29
33
|
isEffectivelyExpanded,
|
|
30
34
|
lessonCountMap,
|
|
35
|
+
sessionIndicatorMap,
|
|
31
36
|
visibleItems,
|
|
32
37
|
dragDisabled,
|
|
33
38
|
}: SortableTreeRowProps) {
|
|
@@ -72,6 +77,7 @@ export function SortableTreeRow({
|
|
|
72
77
|
isMatched={isMatched}
|
|
73
78
|
isEffectivelyExpanded={isEffectivelyExpanded}
|
|
74
79
|
lessonCountMap={lessonCountMap}
|
|
80
|
+
sessionIndicatorMap={sessionIndicatorMap}
|
|
75
81
|
visibleItems={visibleItems}
|
|
76
82
|
/>
|
|
77
83
|
</div>
|
|
@@ -558,7 +558,7 @@ export function TreeRootContextMenu({
|
|
|
558
558
|
|
|
559
559
|
return (
|
|
560
560
|
<ContextMenu>
|
|
561
|
-
<ContextMenuTrigger className="
|
|
561
|
+
<ContextMenuTrigger className="flex flex-col flex-1 min-h-0 w-full">
|
|
562
562
|
{children}
|
|
563
563
|
</ContextMenuTrigger>
|
|
564
564
|
<ContextMenuContent className="w-56">
|
package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs
CHANGED
|
@@ -9,6 +9,12 @@ import {
|
|
|
9
9
|
} from '@/components/ui/popover';
|
|
10
10
|
import { Separator } from '@/components/ui/separator';
|
|
11
11
|
import { Switch } from '@/components/ui/switch';
|
|
12
|
+
import {
|
|
13
|
+
Tooltip,
|
|
14
|
+
TooltipContent,
|
|
15
|
+
TooltipProvider,
|
|
16
|
+
TooltipTrigger,
|
|
17
|
+
} from '@/components/ui/tooltip';
|
|
12
18
|
import { SlidersHorizontal } from 'lucide-react';
|
|
13
19
|
import { useTranslations } from 'next-intl';
|
|
14
20
|
|
|
@@ -32,17 +38,23 @@ export function TreeDisplaySettingsPopover() {
|
|
|
32
38
|
|
|
33
39
|
return (
|
|
34
40
|
<Popover>
|
|
35
|
-
<
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
<TooltipProvider>
|
|
42
|
+
<Tooltip>
|
|
43
|
+
<TooltipTrigger asChild>
|
|
44
|
+
<PopoverTrigger asChild>
|
|
45
|
+
<Button
|
|
46
|
+
variant="ghost"
|
|
47
|
+
size="sm"
|
|
48
|
+
className="h-8 gap-1.5 text-xs text-muted-foreground hover:text-foreground"
|
|
49
|
+
>
|
|
50
|
+
<SlidersHorizontal className="size-3.5" />
|
|
51
|
+
<span className="sr-only sm:not-sr-only">{t('label')}</span>
|
|
52
|
+
</Button>
|
|
53
|
+
</PopoverTrigger>
|
|
54
|
+
</TooltipTrigger>
|
|
55
|
+
<TooltipContent side="bottom">{t('title')}</TooltipContent>
|
|
56
|
+
</Tooltip>
|
|
57
|
+
</TooltipProvider>
|
|
46
58
|
|
|
47
59
|
<PopoverContent align="end" className="w-56 p-0">
|
|
48
60
|
<div className="px-3 py-2.5 border-b">
|
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
import type { Course, FlatItem, Lesson, Session } from './types';
|
|
2
2
|
|
|
3
|
+
export interface SessionIndicatorCounts {
|
|
4
|
+
resourceCount: number;
|
|
5
|
+
videoCount: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function isUploadedVideoResourceType(type: string): boolean {
|
|
9
|
+
return (
|
|
10
|
+
type === 'video_original' ||
|
|
11
|
+
type.startsWith('video_profile:') ||
|
|
12
|
+
type.startsWith('video/')
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getLessonUploadedVideoCount(lesson: Lesson): number {
|
|
17
|
+
return lesson.resources.filter((resource) =>
|
|
18
|
+
isUploadedVideoResourceType(resource.type)
|
|
19
|
+
).length;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getLessonAttachedResourceCount(lesson: Lesson): number {
|
|
23
|
+
return lesson.resources.filter(
|
|
24
|
+
(resource) => !isUploadedVideoResourceType(resource.type)
|
|
25
|
+
).length;
|
|
26
|
+
}
|
|
27
|
+
|
|
3
28
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
29
|
// Text Highlight Helpers
|
|
5
30
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -52,17 +77,11 @@ function lessonMatchesQuery(l: Lesson, q: string): boolean {
|
|
|
52
77
|
}
|
|
53
78
|
|
|
54
79
|
function sessionMatchesQuery(s: Session, q: string): boolean {
|
|
55
|
-
return (
|
|
56
|
-
s.title.toLowerCase().includes(q) ||
|
|
57
|
-
s.code.toLowerCase().includes(q)
|
|
58
|
-
);
|
|
80
|
+
return s.title.toLowerCase().includes(q) || s.code.toLowerCase().includes(q);
|
|
59
81
|
}
|
|
60
82
|
|
|
61
83
|
function courseMatchesQuery(c: Course, q: string): boolean {
|
|
62
|
-
return (
|
|
63
|
-
c.title.toLowerCase().includes(q) ||
|
|
64
|
-
c.code.toLowerCase().includes(q)
|
|
65
|
-
);
|
|
84
|
+
return c.title.toLowerCase().includes(q) || c.code.toLowerCase().includes(q);
|
|
66
85
|
}
|
|
67
86
|
|
|
68
87
|
/**
|
|
@@ -179,4 +198,24 @@ export function buildLessonCountMap(lessons: Lesson[]): Map<string, number> {
|
|
|
179
198
|
map.set(lesson.sessionId, (map.get(lesson.sessionId) ?? 0) + 1);
|
|
180
199
|
}
|
|
181
200
|
return map;
|
|
182
|
-
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function buildSessionIndicatorMap(
|
|
204
|
+
lessons: Lesson[]
|
|
205
|
+
): Map<string, SessionIndicatorCounts> {
|
|
206
|
+
const map = new Map<string, SessionIndicatorCounts>();
|
|
207
|
+
|
|
208
|
+
for (const lesson of lessons) {
|
|
209
|
+
const current = map.get(lesson.sessionId) ?? {
|
|
210
|
+
resourceCount: 0,
|
|
211
|
+
videoCount: 0,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
current.resourceCount += getLessonAttachedResourceCount(lesson);
|
|
215
|
+
current.videoCount += getLessonUploadedVideoCount(lesson);
|
|
216
|
+
|
|
217
|
+
map.set(lesson.sessionId, current);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return map;
|
|
221
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { cn } from '@/lib/utils';
|
|
4
4
|
import { BookOpen } from 'lucide-react';
|
|
5
5
|
import { HighlightedText } from './highlighted-text';
|
|
6
|
+
import { useTreeDisplaySettings } from './use-tree-display-settings';
|
|
6
7
|
import type { Course } from './types';
|
|
7
8
|
|
|
8
9
|
interface TreeRowCourseProps {
|
|
@@ -21,6 +22,8 @@ export function TreeRowCourse({
|
|
|
21
22
|
query,
|
|
22
23
|
onClick,
|
|
23
24
|
}: TreeRowCourseProps) {
|
|
25
|
+
const { showCode } = useTreeDisplaySettings();
|
|
26
|
+
|
|
24
27
|
return (
|
|
25
28
|
<div
|
|
26
29
|
onClick={onClick}
|
|
@@ -28,7 +31,8 @@ export function TreeRowCourse({
|
|
|
28
31
|
aria-selected={isActive}
|
|
29
32
|
className={cn(
|
|
30
33
|
'flex items-center gap-2 px-3 py-1.5 rounded-md cursor-pointer select-none transition-colors text-sm font-semibold h-full',
|
|
31
|
-
isActive &&
|
|
34
|
+
isActive &&
|
|
35
|
+
'bg-accent text-accent-foreground ring-1 ring-inset ring-primary/20',
|
|
32
36
|
isSelected && !isActive && 'bg-accent/60',
|
|
33
37
|
!isActive && !isSelected && 'hover:bg-muted/60'
|
|
34
38
|
)}
|
|
@@ -37,6 +41,11 @@ export function TreeRowCourse({
|
|
|
37
41
|
<span className="truncate flex-1">
|
|
38
42
|
<HighlightedText text={data.title} query={query} />
|
|
39
43
|
</span>
|
|
44
|
+
{showCode && (
|
|
45
|
+
<span className="inline-flex items-center rounded border border-border/60 bg-muted/60 px-1 text-[0.6rem] font-mono text-muted-foreground leading-4 shrink-0">
|
|
46
|
+
{data.code}
|
|
47
|
+
</span>
|
|
48
|
+
)}
|
|
40
49
|
<span
|
|
41
50
|
className={cn(
|
|
42
51
|
'text-[0.6rem] px-1.5 py-0.5 rounded-full shrink-0 font-medium leading-none',
|
|
@@ -49,4 +58,4 @@ export function TreeRowCourse({
|
|
|
49
58
|
</span>
|
|
50
59
|
</div>
|
|
51
60
|
);
|
|
52
|
-
}
|
|
61
|
+
}
|