@hed-hog/lms 0.0.365 → 0.0.366
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/class-group/class-group.controller.d.ts +1 -0
- package/dist/class-group/class-group.controller.d.ts.map +1 -1
- package/dist/class-group/class-group.service.d.ts +1 -0
- package/dist/class-group/class-group.service.d.ts.map +1 -1
- package/dist/course/course-structure.controller.d.ts +4 -2
- package/dist/course/course-structure.controller.d.ts.map +1 -1
- package/dist/course/course-structure.controller.js +6 -3
- package/dist/course/course-structure.controller.js.map +1 -1
- package/dist/course/course-video-agent-pipeline.service.d.ts +70 -0
- package/dist/course/course-video-agent-pipeline.service.d.ts.map +1 -0
- package/dist/course/course-video-agent-pipeline.service.js +398 -0
- package/dist/course/course-video-agent-pipeline.service.js.map +1 -0
- package/dist/course/course-video-hls.service.d.ts +14 -0
- package/dist/course/course-video-hls.service.d.ts.map +1 -1
- package/dist/course/course-video-hls.service.js +25 -8
- package/dist/course/course-video-hls.service.js.map +1 -1
- package/dist/course/course.controller.d.ts +2 -0
- 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 -0
- package/dist/course/course.module.js.map +1 -1
- package/dist/course/course.service.d.ts +2 -0
- package/dist/course/course.service.d.ts.map +1 -1
- package/dist/course/course.service.js +36 -2
- package/dist/course/course.service.js.map +1 -1
- package/dist/course/ffmpeg.util.d.ts +10 -0
- package/dist/course/ffmpeg.util.d.ts.map +1 -0
- package/dist/course/ffmpeg.util.js +79 -0
- package/dist/course/ffmpeg.util.js.map +1 -0
- package/dist/course/lms-bulk-upload-automation.service.d.ts +3 -1
- package/dist/course/lms-bulk-upload-automation.service.d.ts.map +1 -1
- package/dist/course/lms-bulk-upload-automation.service.js +7 -3
- package/dist/course/lms-bulk-upload-automation.service.js.map +1 -1
- package/dist/enterprise/training/training-admin.controller.d.ts +2 -0
- package/dist/enterprise/training/training-admin.controller.d.ts.map +1 -1
- package/dist/enterprise/training/training-admin.service.d.ts +2 -0
- package/dist/enterprise/training/training-admin.service.d.ts.map +1 -1
- package/dist/lms.module.d.ts.map +1 -1
- package/dist/lms.module.js +10 -0
- package/dist/lms.module.js.map +1 -1
- package/dist/platforma/dto/heartbeat.dto.d.ts +9 -0
- package/dist/platforma/dto/heartbeat.dto.d.ts.map +1 -0
- package/dist/platforma/dto/heartbeat.dto.js +50 -0
- package/dist/platforma/dto/heartbeat.dto.js.map +1 -0
- package/dist/platforma/handlers/emit-certificate.handler.d.ts +27 -0
- package/dist/platforma/handlers/emit-certificate.handler.d.ts.map +1 -0
- package/dist/platforma/handlers/emit-certificate.handler.js +117 -0
- package/dist/platforma/handlers/emit-certificate.handler.js.map +1 -0
- package/dist/platforma/handlers/lesson-heartbeat.handler.d.ts +31 -0
- package/dist/platforma/handlers/lesson-heartbeat.handler.d.ts.map +1 -0
- package/dist/platforma/handlers/lesson-heartbeat.handler.js +281 -0
- package/dist/platforma/handlers/lesson-heartbeat.handler.js.map +1 -0
- package/dist/platforma/platforma-heartbeat.service.d.ts +10 -0
- package/dist/platforma/platforma-heartbeat.service.d.ts.map +1 -0
- package/dist/platforma/platforma-heartbeat.service.js +50 -0
- package/dist/platforma/platforma-heartbeat.service.js.map +1 -0
- package/dist/platforma/platforma-performance.service.d.ts +121 -0
- package/dist/platforma/platforma-performance.service.d.ts.map +1 -0
- package/dist/platforma/platforma-performance.service.js +500 -0
- package/dist/platforma/platforma-performance.service.js.map +1 -0
- package/dist/platforma/platforma-search.service.d.ts +21 -0
- package/dist/platforma/platforma-search.service.d.ts.map +1 -0
- package/dist/platforma/platforma-search.service.js +64 -0
- package/dist/platforma/platforma-search.service.js.map +1 -0
- package/dist/platforma/platforma.controller.d.ts +115 -1
- package/dist/platforma/platforma.controller.d.ts.map +1 -1
- package/dist/platforma/platforma.controller.js +50 -2
- package/dist/platforma/platforma.controller.js.map +1 -1
- package/dist/realtime/lms-realtime.controller.d.ts +2 -0
- package/dist/realtime/lms-realtime.controller.d.ts.map +1 -1
- package/dist/realtime/lms-realtime.controller.js +31 -0
- package/dist/realtime/lms-realtime.controller.js.map +1 -1
- package/dist/realtime/lms-realtime.service.d.ts +1 -1
- package/dist/realtime/lms-realtime.service.d.ts.map +1 -1
- package/dist/realtime/lms-realtime.service.js.map +1 -1
- package/hedhog/frontend/app/certificates/models/page.tsx.ejs +182 -29
- package/hedhog/frontend/app/classes/_components/classes-calendar-view.tsx.ejs +277 -0
- package/hedhog/frontend/app/classes/page.tsx.ejs +127 -20
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-overview-tab.tsx.ejs +141 -30
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-xp-overview-tab.tsx.ejs +13 -13
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson-xp-tab.tsx.ejs +11 -23
- package/hedhog/frontend/app/courses/[id]/structure/_components/xp-premium-pills.tsx.ejs +1 -8
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-content-overview.ts.ejs +2 -0
- package/hedhog/frontend/app/courses/page.tsx.ejs +40 -9
- package/hedhog/frontend/app/enterprise/[id]/page.tsx.ejs +6 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-classes-calendar-tab.tsx.ejs +264 -0
- package/hedhog/frontend/app/enterprise/page.tsx.ejs +104 -47
- package/hedhog/frontend/app/exams/page.tsx.ejs +38 -4
- package/hedhog/frontend/app/instructors/page.tsx.ejs +87 -46
- package/hedhog/frontend/app/paths/page.tsx.ejs +38 -4
- package/hedhog/frontend/app/training/page.tsx.ejs +38 -4
- package/hedhog/frontend/messages/en.json +18 -0
- package/hedhog/frontend/messages/pt.json +21 -1
- package/hedhog/table/course_enrollment.yaml +3 -0
- package/hedhog/table/lesson_view_event.yaml +66 -0
- package/package.json +9 -8
- package/src/course/course-structure.controller.ts +3 -1
- package/src/course/course-video-agent-pipeline.service.ts +471 -0
- package/src/course/course-video-hls.service.ts +30 -10
- package/src/course/course.module.ts +5 -0
- package/src/course/course.service.ts +46 -1
- package/src/course/ffmpeg.util.ts +65 -0
- package/src/course/lms-bulk-upload-automation.service.ts +4 -1
- package/src/lms.module.ts +10 -0
- package/src/platforma/dto/heartbeat.dto.ts +30 -0
- package/src/platforma/handlers/emit-certificate.handler.ts +117 -0
- package/src/platforma/handlers/lesson-heartbeat.handler.ts +343 -0
- package/src/platforma/platforma-heartbeat.service.ts +33 -0
- package/src/platforma/platforma-performance.service.ts +606 -0
- package/src/platforma/platforma-search.service.ts +48 -0
- package/src/platforma/platforma.controller.ts +42 -0
- package/src/realtime/lms-realtime.controller.ts +27 -1
- package/src/realtime/lms-realtime.service.ts +2 -1
|
@@ -22,7 +22,14 @@ import {
|
|
|
22
22
|
TableRow,
|
|
23
23
|
} from '@/components/ui/table';
|
|
24
24
|
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
|
25
|
+
import {
|
|
26
|
+
Tooltip,
|
|
27
|
+
TooltipContent,
|
|
28
|
+
TooltipProvider,
|
|
29
|
+
TooltipTrigger,
|
|
30
|
+
} from '@/components/ui/tooltip';
|
|
25
31
|
import { useDebounce } from '@/hooks/use-debounce';
|
|
32
|
+
import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
|
|
26
33
|
import { formatDate } from '@/lib/format-date';
|
|
27
34
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
28
35
|
import {
|
|
@@ -51,7 +58,7 @@ const VIEW_STORAGE_KEY = 'lms-enterprise-view-mode';
|
|
|
51
58
|
|
|
52
59
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
53
60
|
|
|
54
|
-
const
|
|
61
|
+
const DEFAULT_PAGE_SIZES = [6, 12, 24, 48, 96] as const;
|
|
55
62
|
|
|
56
63
|
const STATUS_VARIANT: Record<
|
|
57
64
|
EnterpriseStatus,
|
|
@@ -90,6 +97,45 @@ export default function EnterprisePage() {
|
|
|
90
97
|
const debouncedSearch = useDebounce(search);
|
|
91
98
|
const { request, currentLocaleCode, getSettingValue } = useApp();
|
|
92
99
|
|
|
100
|
+
const { data: generalSettings } = useQuery<{
|
|
101
|
+
data: Array<{ slug: string; value: string }>;
|
|
102
|
+
}>({
|
|
103
|
+
queryKey: ['setting-group-general'],
|
|
104
|
+
queryFn: async () => {
|
|
105
|
+
const response = await request<{
|
|
106
|
+
data: Array<{ slug: string; value: string }>;
|
|
107
|
+
}>({
|
|
108
|
+
url: '/setting/group/general',
|
|
109
|
+
method: 'GET',
|
|
110
|
+
});
|
|
111
|
+
return response.data;
|
|
112
|
+
},
|
|
113
|
+
staleTime: 5 * 60 * 1000,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const pageSizeOptions = useMemo(() => {
|
|
117
|
+
const setting = generalSettings?.data?.find(
|
|
118
|
+
(s) => s.slug === 'pagination-page-sizes'
|
|
119
|
+
);
|
|
120
|
+
if (!setting?.value) return DEFAULT_PAGE_SIZES;
|
|
121
|
+
try {
|
|
122
|
+
const parsed = JSON.parse(setting.value) as string[];
|
|
123
|
+
const sizes = parsed
|
|
124
|
+
.map(Number)
|
|
125
|
+
.filter((n) => !isNaN(n) && n > 0)
|
|
126
|
+
.sort((a, b) => a - b);
|
|
127
|
+
return sizes.length > 0 ? sizes : DEFAULT_PAGE_SIZES;
|
|
128
|
+
} catch {
|
|
129
|
+
return DEFAULT_PAGE_SIZES;
|
|
130
|
+
}
|
|
131
|
+
}, [generalSettings]);
|
|
132
|
+
|
|
133
|
+
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
134
|
+
storageKey: 'pagination:global:pageSize',
|
|
135
|
+
defaultValue: 12,
|
|
136
|
+
allowedValues: pageSizeOptions,
|
|
137
|
+
});
|
|
138
|
+
|
|
93
139
|
type EnterpriseListResponse = {
|
|
94
140
|
data: EnterpriseAccount[];
|
|
95
141
|
total: number;
|
|
@@ -112,7 +158,7 @@ export default function EnterprisePage() {
|
|
|
112
158
|
queryKey: [
|
|
113
159
|
'lms-enterprise',
|
|
114
160
|
page,
|
|
115
|
-
|
|
161
|
+
pageSize,
|
|
116
162
|
debouncedSearch,
|
|
117
163
|
statusFilter,
|
|
118
164
|
crmFilter,
|
|
@@ -120,7 +166,7 @@ export default function EnterprisePage() {
|
|
|
120
166
|
queryFn: async () => {
|
|
121
167
|
const params = new URLSearchParams();
|
|
122
168
|
params.set('page', String(page));
|
|
123
|
-
params.set('pageSize', String(
|
|
169
|
+
params.set('pageSize', String(pageSize));
|
|
124
170
|
if (debouncedSearch) params.set('search', debouncedSearch);
|
|
125
171
|
if (statusFilter !== 'all') params.set('status', statusFilter);
|
|
126
172
|
if (crmFilter !== 'all') params.set('crmPersonId', crmFilter);
|
|
@@ -335,47 +381,56 @@ export default function EnterprisePage() {
|
|
|
335
381
|
|
|
336
382
|
<KpiCardsGrid items={kpiItems} />
|
|
337
383
|
|
|
338
|
-
<
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
384
|
+
<SearchBar
|
|
385
|
+
searchQuery={search}
|
|
386
|
+
onSearchChange={handleSearch}
|
|
387
|
+
onSearch={() => {}}
|
|
388
|
+
placeholder={t('filters.searchPlaceholder')}
|
|
389
|
+
controls={controls}
|
|
390
|
+
actions={
|
|
391
|
+
<TooltipProvider>
|
|
392
|
+
<ToggleGroup
|
|
393
|
+
type="single"
|
|
394
|
+
value={viewMode}
|
|
395
|
+
onValueChange={handleViewModeChange}
|
|
396
|
+
variant="outline"
|
|
397
|
+
size="sm"
|
|
398
|
+
aria-label={
|
|
399
|
+
t.has('view.modeAriaLabel') ? t('view.modeAriaLabel') : 'View mode'
|
|
400
|
+
}
|
|
401
|
+
>
|
|
402
|
+
<Tooltip>
|
|
403
|
+
<TooltipTrigger asChild>
|
|
404
|
+
<ToggleGroupItem
|
|
405
|
+
value="table"
|
|
406
|
+
className="cursor-pointer"
|
|
407
|
+
aria-label={t.has('view.table') ? t('view.table') : 'Table'}
|
|
408
|
+
>
|
|
409
|
+
<List className="h-4 w-4" />
|
|
410
|
+
</ToggleGroupItem>
|
|
411
|
+
</TooltipTrigger>
|
|
412
|
+
<TooltipContent>
|
|
413
|
+
{t.has('view.table') ? t('view.table') : 'Table'}
|
|
414
|
+
</TooltipContent>
|
|
415
|
+
</Tooltip>
|
|
416
|
+
<Tooltip>
|
|
417
|
+
<TooltipTrigger asChild>
|
|
418
|
+
<ToggleGroupItem
|
|
419
|
+
value="cards"
|
|
420
|
+
className="cursor-pointer"
|
|
421
|
+
aria-label={t.has('view.cards') ? t('view.cards') : 'Cards'}
|
|
422
|
+
>
|
|
423
|
+
<LayoutGrid className="h-4 w-4" />
|
|
424
|
+
</ToggleGroupItem>
|
|
425
|
+
</TooltipTrigger>
|
|
426
|
+
<TooltipContent>
|
|
427
|
+
{t.has('view.cards') ? t('view.cards') : 'Cards'}
|
|
428
|
+
</TooltipContent>
|
|
429
|
+
</Tooltip>
|
|
430
|
+
</ToggleGroup>
|
|
431
|
+
</TooltipProvider>
|
|
432
|
+
}
|
|
433
|
+
/>
|
|
379
434
|
|
|
380
435
|
{accounts.length === 0 && !isLoading ? (
|
|
381
436
|
<EmptyState
|
|
@@ -633,12 +688,14 @@ export default function EnterprisePage() {
|
|
|
633
688
|
|
|
634
689
|
<PaginationFooter
|
|
635
690
|
currentPage={page}
|
|
636
|
-
pageSize={
|
|
691
|
+
pageSize={pageSize}
|
|
637
692
|
totalItems={totalItems}
|
|
638
693
|
onPageChange={setPage}
|
|
639
|
-
onPageSizeChange={
|
|
640
|
-
|
|
694
|
+
onPageSizeChange={(next) => {
|
|
695
|
+
setPageSize(next);
|
|
696
|
+
setPage(1);
|
|
641
697
|
}}
|
|
698
|
+
pageSizeOptions={pageSizeOptions}
|
|
642
699
|
/>
|
|
643
700
|
|
|
644
701
|
<EnterpriseSheet
|
|
@@ -213,7 +213,7 @@ function toNumberOrFallback(value: unknown, fallback: number) {
|
|
|
213
213
|
|
|
214
214
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
215
215
|
|
|
216
|
-
const
|
|
216
|
+
const DEFAULT_PAGE_SIZES = [6, 12, 24, 48, 96] as const;
|
|
217
217
|
|
|
218
218
|
function formatTempo(minutos: number) {
|
|
219
219
|
if (minutos < 60) return `${minutos}min`;
|
|
@@ -263,10 +263,44 @@ export default function ExamesPage() {
|
|
|
263
263
|
|
|
264
264
|
// Pagination
|
|
265
265
|
const [currentPage, setCurrentPage] = useState(1);
|
|
266
|
+
|
|
267
|
+
const { data: generalSettings } = useQuery<{
|
|
268
|
+
data: Array<{ slug: string; value: string }>;
|
|
269
|
+
}>({
|
|
270
|
+
queryKey: ['setting-group-general'],
|
|
271
|
+
queryFn: async () => {
|
|
272
|
+
const response = await request<{
|
|
273
|
+
data: Array<{ slug: string; value: string }>;
|
|
274
|
+
}>({
|
|
275
|
+
url: '/setting/group/general',
|
|
276
|
+
method: 'GET',
|
|
277
|
+
});
|
|
278
|
+
return response.data;
|
|
279
|
+
},
|
|
280
|
+
staleTime: 5 * 60 * 1000,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const pageSizeOptions = useMemo(() => {
|
|
284
|
+
const setting = generalSettings?.data?.find(
|
|
285
|
+
(s) => s.slug === 'pagination-page-sizes'
|
|
286
|
+
);
|
|
287
|
+
if (!setting?.value) return DEFAULT_PAGE_SIZES;
|
|
288
|
+
try {
|
|
289
|
+
const parsed = JSON.parse(setting.value) as string[];
|
|
290
|
+
const sizes = parsed
|
|
291
|
+
.map(Number)
|
|
292
|
+
.filter((n) => !isNaN(n) && n > 0)
|
|
293
|
+
.sort((a, b) => a - b);
|
|
294
|
+
return sizes.length > 0 ? sizes : DEFAULT_PAGE_SIZES;
|
|
295
|
+
} catch {
|
|
296
|
+
return DEFAULT_PAGE_SIZES;
|
|
297
|
+
}
|
|
298
|
+
}, [generalSettings]);
|
|
299
|
+
|
|
266
300
|
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
267
301
|
storageKey: 'pagination:global:pageSize',
|
|
268
302
|
defaultValue: 12,
|
|
269
|
-
allowedValues:
|
|
303
|
+
allowedValues: pageSizeOptions,
|
|
270
304
|
});
|
|
271
305
|
|
|
272
306
|
const form = useForm<ExameForm>({
|
|
@@ -654,7 +688,7 @@ export default function ExamesPage() {
|
|
|
654
688
|
],
|
|
655
689
|
},
|
|
656
690
|
]}
|
|
657
|
-
|
|
691
|
+
actions={
|
|
658
692
|
<ViewModeToggle
|
|
659
693
|
viewMode={viewMode}
|
|
660
694
|
onViewModeChange={setViewMode}
|
|
@@ -1037,7 +1071,7 @@ export default function ExamesPage() {
|
|
|
1037
1071
|
setPageSize(nextPageSize);
|
|
1038
1072
|
setCurrentPage(1);
|
|
1039
1073
|
}}
|
|
1040
|
-
pageSizeOptions={
|
|
1074
|
+
pageSizeOptions={pageSizeOptions}
|
|
1041
1075
|
/>
|
|
1042
1076
|
</div>
|
|
1043
1077
|
)}
|
|
@@ -33,6 +33,12 @@ import {
|
|
|
33
33
|
TableRow,
|
|
34
34
|
} from '@/components/ui/table';
|
|
35
35
|
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
|
36
|
+
import {
|
|
37
|
+
Tooltip,
|
|
38
|
+
TooltipContent,
|
|
39
|
+
TooltipProvider,
|
|
40
|
+
TooltipTrigger,
|
|
41
|
+
} from '@/components/ui/tooltip';
|
|
36
42
|
import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
|
|
37
43
|
import { cn } from '@/lib/utils';
|
|
38
44
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
@@ -48,7 +54,7 @@ import {
|
|
|
48
54
|
Users,
|
|
49
55
|
} from 'lucide-react';
|
|
50
56
|
import { useTranslations } from 'next-intl';
|
|
51
|
-
import { useEffect, useState } from 'react';
|
|
57
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
52
58
|
import { toast } from 'sonner';
|
|
53
59
|
import { InstructorFormSheet } from './_components/instructor-form-sheet';
|
|
54
60
|
import type {
|
|
@@ -95,10 +101,43 @@ export default function InstructorsPage() {
|
|
|
95
101
|
const t = useTranslations('lms.InstructorsPage');
|
|
96
102
|
|
|
97
103
|
const [page, setPage] = useState(1);
|
|
104
|
+
const { data: generalSettings } = useQuery<{
|
|
105
|
+
data: Array<{ slug: string; value: string }>;
|
|
106
|
+
}>({
|
|
107
|
+
queryKey: ['setting-group-general'],
|
|
108
|
+
queryFn: async () => {
|
|
109
|
+
const response = await request<{
|
|
110
|
+
data: Array<{ slug: string; value: string }>;
|
|
111
|
+
}>({
|
|
112
|
+
url: '/setting/group/general',
|
|
113
|
+
method: 'GET',
|
|
114
|
+
});
|
|
115
|
+
return response.data;
|
|
116
|
+
},
|
|
117
|
+
staleTime: 5 * 60 * 1000,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const pageSizeOptions = useMemo(() => {
|
|
121
|
+
const setting = generalSettings?.data?.find(
|
|
122
|
+
(s) => s.slug === 'pagination-page-sizes'
|
|
123
|
+
);
|
|
124
|
+
if (!setting?.value) return [6, 12, 24, 48, 96];
|
|
125
|
+
try {
|
|
126
|
+
const parsed = JSON.parse(setting.value) as string[];
|
|
127
|
+
const sizes = parsed
|
|
128
|
+
.map(Number)
|
|
129
|
+
.filter((n) => !isNaN(n) && n > 0)
|
|
130
|
+
.sort((a, b) => a - b);
|
|
131
|
+
return sizes.length > 0 ? sizes : [6, 12, 24, 48, 96];
|
|
132
|
+
} catch {
|
|
133
|
+
return [6, 12, 24, 48, 96];
|
|
134
|
+
}
|
|
135
|
+
}, [generalSettings]);
|
|
136
|
+
|
|
98
137
|
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
99
138
|
storageKey: 'pagination:global:pageSize',
|
|
100
139
|
defaultValue: 12,
|
|
101
|
-
allowedValues:
|
|
140
|
+
allowedValues: pageSizeOptions,
|
|
102
141
|
});
|
|
103
142
|
const [searchInput, setSearchInput] = useState('');
|
|
104
143
|
const [debouncedSearch, setDebouncedSearch] = useState('');
|
|
@@ -329,50 +368,52 @@ export default function InstructorsPage() {
|
|
|
329
368
|
|
|
330
369
|
<KpiCardsGrid items={statsCards} />
|
|
331
370
|
|
|
332
|
-
<
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
<ToggleGroup
|
|
350
|
-
type="single"
|
|
351
|
-
value={viewMode}
|
|
352
|
-
onValueChange={handleViewModeChange}
|
|
353
|
-
variant="outline"
|
|
354
|
-
size="sm"
|
|
355
|
-
aria-label={t('viewMode.ariaLabel')}
|
|
356
|
-
>
|
|
357
|
-
<ToggleGroupItem
|
|
358
|
-
value="table"
|
|
359
|
-
className="gap-1.5 px-2.5"
|
|
360
|
-
aria-label={t('viewMode.tableAriaLabel')}
|
|
361
|
-
>
|
|
362
|
-
<List className="h-4 w-4" />
|
|
363
|
-
<span className="hidden sm:inline">{t('viewMode.table')}</span>
|
|
364
|
-
</ToggleGroupItem>
|
|
365
|
-
<ToggleGroupItem
|
|
366
|
-
value="cards"
|
|
367
|
-
className="gap-1.5 px-2.5"
|
|
368
|
-
aria-label={t('viewMode.cardsAriaLabel')}
|
|
371
|
+
<SearchBar
|
|
372
|
+
searchQuery={searchInput}
|
|
373
|
+
onSearchChange={(value) => {
|
|
374
|
+
setSearchInput(value);
|
|
375
|
+
}}
|
|
376
|
+
onSearch={() => setPage(1)}
|
|
377
|
+
placeholder={t('search.placeholder')}
|
|
378
|
+
controls={searchControls}
|
|
379
|
+
actions={
|
|
380
|
+
<TooltipProvider>
|
|
381
|
+
<ToggleGroup
|
|
382
|
+
type="single"
|
|
383
|
+
value={viewMode}
|
|
384
|
+
onValueChange={handleViewModeChange}
|
|
385
|
+
variant="outline"
|
|
386
|
+
size="sm"
|
|
387
|
+
aria-label={t('viewMode.ariaLabel')}
|
|
369
388
|
>
|
|
370
|
-
<
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
389
|
+
<Tooltip>
|
|
390
|
+
<TooltipTrigger asChild>
|
|
391
|
+
<ToggleGroupItem
|
|
392
|
+
value="table"
|
|
393
|
+
className="cursor-pointer"
|
|
394
|
+
aria-label={t('viewMode.tableAriaLabel')}
|
|
395
|
+
>
|
|
396
|
+
<List className="h-4 w-4" />
|
|
397
|
+
</ToggleGroupItem>
|
|
398
|
+
</TooltipTrigger>
|
|
399
|
+
<TooltipContent>{t('viewMode.table')}</TooltipContent>
|
|
400
|
+
</Tooltip>
|
|
401
|
+
<Tooltip>
|
|
402
|
+
<TooltipTrigger asChild>
|
|
403
|
+
<ToggleGroupItem
|
|
404
|
+
value="cards"
|
|
405
|
+
className="cursor-pointer"
|
|
406
|
+
aria-label={t('viewMode.cardsAriaLabel')}
|
|
407
|
+
>
|
|
408
|
+
<LayoutGrid className="h-4 w-4" />
|
|
409
|
+
</ToggleGroupItem>
|
|
410
|
+
</TooltipTrigger>
|
|
411
|
+
<TooltipContent>{t('viewMode.cards')}</TooltipContent>
|
|
412
|
+
</Tooltip>
|
|
413
|
+
</ToggleGroup>
|
|
414
|
+
</TooltipProvider>
|
|
415
|
+
}
|
|
416
|
+
/>
|
|
376
417
|
|
|
377
418
|
{isLoading ? (
|
|
378
419
|
viewMode === 'cards' ? (
|
|
@@ -726,7 +767,7 @@ export default function InstructorsPage() {
|
|
|
726
767
|
setPageSize(nextPageSize);
|
|
727
768
|
setPage(1);
|
|
728
769
|
}}
|
|
729
|
-
pageSizeOptions={
|
|
770
|
+
pageSizeOptions={pageSizeOptions}
|
|
730
771
|
/>
|
|
731
772
|
|
|
732
773
|
<InstructorFormSheet
|
|
@@ -462,7 +462,7 @@ const STATUS_MAP: Record<
|
|
|
462
462
|
encerrada: { label: 'Encerrada', variant: 'outline' },
|
|
463
463
|
};
|
|
464
464
|
|
|
465
|
-
const
|
|
465
|
+
const DEFAULT_PAGE_SIZES = [6, 12, 24, 48, 96] as const;
|
|
466
466
|
const API_TRAINING_CACHE_KEY = 'lms:training:api-cache';
|
|
467
467
|
|
|
468
468
|
// ── Animations ────────────────────────────────────────────────────────────────
|
|
@@ -578,10 +578,44 @@ export default function TrainingPage() {
|
|
|
578
578
|
|
|
579
579
|
// Pagination
|
|
580
580
|
const [currentPage, setCurrentPage] = useState(1);
|
|
581
|
+
|
|
582
|
+
const { data: generalSettings } = useQuery<{
|
|
583
|
+
data: Array<{ slug: string; value: string }>;
|
|
584
|
+
}>({
|
|
585
|
+
queryKey: ['setting-group-general'],
|
|
586
|
+
queryFn: async () => {
|
|
587
|
+
const response = await request<{
|
|
588
|
+
data: Array<{ slug: string; value: string }>;
|
|
589
|
+
}>({
|
|
590
|
+
url: '/setting/group/general',
|
|
591
|
+
method: 'GET',
|
|
592
|
+
});
|
|
593
|
+
return response.data;
|
|
594
|
+
},
|
|
595
|
+
staleTime: 5 * 60 * 1000,
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
const pageSizeOptions = useMemo(() => {
|
|
599
|
+
const setting = generalSettings?.data?.find(
|
|
600
|
+
(s) => s.slug === 'pagination-page-sizes'
|
|
601
|
+
);
|
|
602
|
+
if (!setting?.value) return DEFAULT_PAGE_SIZES;
|
|
603
|
+
try {
|
|
604
|
+
const parsed = JSON.parse(setting.value) as string[];
|
|
605
|
+
const sizes = parsed
|
|
606
|
+
.map(Number)
|
|
607
|
+
.filter((n) => !isNaN(n) && n > 0)
|
|
608
|
+
.sort((a, b) => a - b);
|
|
609
|
+
return sizes.length > 0 ? sizes : DEFAULT_PAGE_SIZES;
|
|
610
|
+
} catch {
|
|
611
|
+
return DEFAULT_PAGE_SIZES;
|
|
612
|
+
}
|
|
613
|
+
}, [generalSettings]);
|
|
614
|
+
|
|
581
615
|
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
582
616
|
storageKey: 'pagination:global:pageSize',
|
|
583
617
|
defaultValue: 12,
|
|
584
|
-
allowedValues:
|
|
618
|
+
allowedValues: pageSizeOptions,
|
|
585
619
|
});
|
|
586
620
|
|
|
587
621
|
const sensors = useSensors(
|
|
@@ -1500,7 +1534,7 @@ export default function TrainingPage() {
|
|
|
1500
1534
|
],
|
|
1501
1535
|
},
|
|
1502
1536
|
]}
|
|
1503
|
-
|
|
1537
|
+
actions={
|
|
1504
1538
|
<ViewModeToggle
|
|
1505
1539
|
viewMode={viewMode}
|
|
1506
1540
|
onViewModeChange={setViewMode}
|
|
@@ -1848,7 +1882,7 @@ export default function TrainingPage() {
|
|
|
1848
1882
|
setPageSize(nextPageSize);
|
|
1849
1883
|
setCurrentPage(1);
|
|
1850
1884
|
}}
|
|
1851
|
-
pageSizeOptions={
|
|
1885
|
+
pageSizeOptions={pageSizeOptions}
|
|
1852
1886
|
/>
|
|
1853
1887
|
</div>
|
|
1854
1888
|
)}
|
|
@@ -448,7 +448,7 @@ const STATUS_MAP: Record<
|
|
|
448
448
|
encerrada: { label: 'Encerrada', variant: 'outline' },
|
|
449
449
|
};
|
|
450
450
|
|
|
451
|
-
const
|
|
451
|
+
const DEFAULT_PAGE_SIZES = [6, 12, 24, 48, 96] as const;
|
|
452
452
|
const API_TRAINING_CACHE_KEY = 'lms:training:api-cache';
|
|
453
453
|
|
|
454
454
|
// ── Animations ────────────────────────────────────────────────────────────────
|
|
@@ -564,10 +564,44 @@ export default function TrainingPage() {
|
|
|
564
564
|
|
|
565
565
|
// Pagination
|
|
566
566
|
const [currentPage, setCurrentPage] = useState(1);
|
|
567
|
+
|
|
568
|
+
const { data: generalSettings } = useQuery<{
|
|
569
|
+
data: Array<{ slug: string; value: string }>;
|
|
570
|
+
}>({
|
|
571
|
+
queryKey: ['setting-group-general'],
|
|
572
|
+
queryFn: async () => {
|
|
573
|
+
const response = await request<{
|
|
574
|
+
data: Array<{ slug: string; value: string }>;
|
|
575
|
+
}>({
|
|
576
|
+
url: '/setting/group/general',
|
|
577
|
+
method: 'GET',
|
|
578
|
+
});
|
|
579
|
+
return response.data;
|
|
580
|
+
},
|
|
581
|
+
staleTime: 5 * 60 * 1000,
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
const pageSizeOptions = useMemo(() => {
|
|
585
|
+
const setting = generalSettings?.data?.find(
|
|
586
|
+
(s) => s.slug === 'pagination-page-sizes'
|
|
587
|
+
);
|
|
588
|
+
if (!setting?.value) return DEFAULT_PAGE_SIZES;
|
|
589
|
+
try {
|
|
590
|
+
const parsed = JSON.parse(setting.value) as string[];
|
|
591
|
+
const sizes = parsed
|
|
592
|
+
.map(Number)
|
|
593
|
+
.filter((n) => !isNaN(n) && n > 0)
|
|
594
|
+
.sort((a, b) => a - b);
|
|
595
|
+
return sizes.length > 0 ? sizes : DEFAULT_PAGE_SIZES;
|
|
596
|
+
} catch {
|
|
597
|
+
return DEFAULT_PAGE_SIZES;
|
|
598
|
+
}
|
|
599
|
+
}, [generalSettings]);
|
|
600
|
+
|
|
567
601
|
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
568
602
|
storageKey: 'pagination:global:pageSize',
|
|
569
603
|
defaultValue: 12,
|
|
570
|
-
allowedValues:
|
|
604
|
+
allowedValues: pageSizeOptions,
|
|
571
605
|
});
|
|
572
606
|
|
|
573
607
|
const sensors = useSensors(
|
|
@@ -1490,7 +1524,7 @@ export default function TrainingPage() {
|
|
|
1490
1524
|
],
|
|
1491
1525
|
},
|
|
1492
1526
|
]}
|
|
1493
|
-
|
|
1527
|
+
actions={
|
|
1494
1528
|
<ViewModeToggle
|
|
1495
1529
|
viewMode={viewMode}
|
|
1496
1530
|
onViewModeChange={setViewMode}
|
|
@@ -1838,7 +1872,7 @@ export default function TrainingPage() {
|
|
|
1838
1872
|
setPageSize(nextPageSize);
|
|
1839
1873
|
setCurrentPage(1);
|
|
1840
1874
|
}}
|
|
1841
|
-
pageSizeOptions={
|
|
1875
|
+
pageSizeOptions={pageSizeOptions}
|
|
1842
1876
|
/>
|
|
1843
1877
|
</div>
|
|
1844
1878
|
)}
|
|
@@ -555,6 +555,13 @@
|
|
|
555
555
|
"withTranscription": "With transcription",
|
|
556
556
|
"withXp": "With XP"
|
|
557
557
|
},
|
|
558
|
+
"videoPipeline": {
|
|
559
|
+
"title": "Video pipeline",
|
|
560
|
+
"description": "Video lessons by stage: total → has video → processed",
|
|
561
|
+
"total": "Video lessons",
|
|
562
|
+
"withVideo": "With video",
|
|
563
|
+
"withProcessedVideo": "Video processed"
|
|
564
|
+
},
|
|
558
565
|
"areas": {
|
|
559
566
|
"title": "Macro areas",
|
|
560
567
|
"description": "Areas mapped across lessons, ordered by XP weight"
|
|
@@ -3049,6 +3056,17 @@
|
|
|
3049
3056
|
"cancel": "Cancel",
|
|
3050
3057
|
"delete": "Remove"
|
|
3051
3058
|
}
|
|
3059
|
+
},
|
|
3060
|
+
"viewMode": {
|
|
3061
|
+
"list": "Show as list",
|
|
3062
|
+
"cards": "Show as cards"
|
|
3063
|
+
},
|
|
3064
|
+
"table": {
|
|
3065
|
+
"name": "Name",
|
|
3066
|
+
"slug": "Slug",
|
|
3067
|
+
"status": "Status",
|
|
3068
|
+
"updatedAt": "Updated at",
|
|
3069
|
+
"actions": "Actions"
|
|
3052
3070
|
}
|
|
3053
3071
|
},
|
|
3054
3072
|
"ClassesPage": {
|
|
@@ -564,6 +564,13 @@
|
|
|
564
564
|
"withTranscription": "Com transcrição",
|
|
565
565
|
"withXp": "Com XP"
|
|
566
566
|
},
|
|
567
|
+
"videoPipeline": {
|
|
568
|
+
"title": "Pipeline de vídeo",
|
|
569
|
+
"description": "Aulas de vídeo por estágio: total → com vídeo → processado",
|
|
570
|
+
"total": "Aulas de vídeo",
|
|
571
|
+
"withVideo": "Com vídeo",
|
|
572
|
+
"withProcessedVideo": "Vídeo processado"
|
|
573
|
+
},
|
|
567
574
|
"areas": {
|
|
568
575
|
"title": "Áreas macro",
|
|
569
576
|
"description": "Áreas mapeadas nas aulas, ordenadas por peso de XP"
|
|
@@ -3071,6 +3078,17 @@
|
|
|
3071
3078
|
"cancel": "Cancelar",
|
|
3072
3079
|
"delete": "Remover"
|
|
3073
3080
|
}
|
|
3081
|
+
},
|
|
3082
|
+
"viewMode": {
|
|
3083
|
+
"list": "Visualizar em lista",
|
|
3084
|
+
"cards": "Visualizar em cards"
|
|
3085
|
+
},
|
|
3086
|
+
"table": {
|
|
3087
|
+
"name": "Nome",
|
|
3088
|
+
"slug": "Slug",
|
|
3089
|
+
"status": "Status",
|
|
3090
|
+
"updatedAt": "Atualizado em",
|
|
3091
|
+
"actions": "Ações"
|
|
3074
3092
|
}
|
|
3075
3093
|
},
|
|
3076
3094
|
"ClassesPage": {
|
|
@@ -3152,7 +3170,8 @@
|
|
|
3152
3170
|
},
|
|
3153
3171
|
"viewMode": {
|
|
3154
3172
|
"list": "Visualizar em lista",
|
|
3155
|
-
"cards": "Visualizar em cards"
|
|
3173
|
+
"cards": "Visualizar em cards",
|
|
3174
|
+
"calendar": "Visualizar em calendário"
|
|
3156
3175
|
},
|
|
3157
3176
|
"pagination": {
|
|
3158
3177
|
"class": "turma",
|
|
@@ -4569,6 +4588,7 @@
|
|
|
4569
4588
|
"users": "Usuários",
|
|
4570
4589
|
"courses": "Cursos",
|
|
4571
4590
|
"classes": "Turmas",
|
|
4591
|
+
"classesCalendar": "Calendário de Turmas",
|
|
4572
4592
|
"billing": "Faturamento",
|
|
4573
4593
|
"students": "Alunos",
|
|
4574
4594
|
"administrators": "Administradores",
|