@hed-hog/lms 0.0.330 → 0.0.338
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 +3 -3
- package/dist/class-group/class-group.service.d.ts +3 -3
- package/dist/course/course.service.d.ts.map +1 -1
- package/dist/course/course.service.js +12 -20
- package/dist/course/course.service.js.map +1 -1
- package/dist/enterprise/enterprise.controller.d.ts +72 -0
- package/dist/enterprise/enterprise.controller.d.ts.map +1 -1
- package/dist/enterprise/enterprise.controller.js +10 -0
- package/dist/enterprise/enterprise.controller.js.map +1 -1
- package/dist/enterprise/enterprise.service.d.ts +78 -0
- package/dist/enterprise/enterprise.service.d.ts.map +1 -1
- package/dist/enterprise/enterprise.service.js +413 -40
- package/dist/enterprise/enterprise.service.js.map +1 -1
- package/dist/enterprise/training/training-admin.controller.d.ts +6 -3
- package/dist/enterprise/training/training-admin.controller.d.ts.map +1 -1
- package/dist/enterprise/training/training-admin.controller.js +10 -6
- package/dist/enterprise/training/training-admin.controller.js.map +1 -1
- package/dist/enterprise/training/training-admin.service.d.ts +8 -2
- package/dist/enterprise/training/training-admin.service.d.ts.map +1 -1
- package/dist/enterprise/training/training-admin.service.js +108 -52
- package/dist/enterprise/training/training-admin.service.js.map +1 -1
- package/dist/enterprise/training/training-viewer.controller.d.ts +3 -0
- package/dist/enterprise/training/training-viewer.controller.d.ts.map +1 -1
- package/dist/evaluation/evaluation.controller.d.ts +4 -4
- package/dist/evaluation/evaluation.service.d.ts +4 -4
- package/dist/instructor/dto/create-instructor-skill.dto.d.ts +0 -4
- package/dist/instructor/dto/create-instructor-skill.dto.d.ts.map +1 -1
- package/dist/instructor/dto/create-instructor-skill.dto.js +0 -21
- package/dist/instructor/dto/create-instructor-skill.dto.js.map +1 -1
- package/dist/instructor/dto/update-instructor-skill.dto.d.ts +0 -4
- package/dist/instructor/dto/update-instructor-skill.dto.d.ts.map +1 -1
- package/dist/instructor/dto/update-instructor-skill.dto.js +0 -22
- package/dist/instructor/dto/update-instructor-skill.dto.js.map +1 -1
- package/dist/instructor/instructor-skill.controller.d.ts +4 -4
- package/dist/instructor/instructor-skill.service.d.ts +4 -7
- package/dist/instructor/instructor-skill.service.d.ts.map +1 -1
- package/dist/instructor/instructor-skill.service.js +2 -89
- package/dist/instructor/instructor-skill.service.js.map +1 -1
- package/dist/instructor/instructor.controller.d.ts +20 -0
- package/dist/instructor/instructor.controller.d.ts.map +1 -1
- package/dist/instructor/instructor.controller.js +19 -0
- package/dist/instructor/instructor.controller.js.map +1 -1
- package/dist/instructor/instructor.service.d.ts +25 -0
- package/dist/instructor/instructor.service.d.ts.map +1 -1
- package/dist/instructor/instructor.service.js +70 -18
- package/dist/instructor/instructor.service.js.map +1 -1
- package/dist/lms.module.d.ts.map +1 -1
- package/dist/lms.module.js.map +1 -1
- package/hedhog/data/route.yaml +23 -1
- package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +42 -24
- package/hedhog/frontend/app/_components/create-lms-instructor-sheet.tsx.ejs +591 -0
- package/hedhog/frontend/app/certificates/issued/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/certificates/models/page.tsx.ejs +7 -2
- package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +17 -17
- package/hedhog/frontend/app/classes/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/courses/[id]/_components/CourseClassificationCard.tsx.ejs +3 -33
- package/hedhog/frontend/app/courses/[id]/_components/CourseContentCard.tsx.ejs +9 -9
- package/hedhog/frontend/app/courses/[id]/_components/CourseMainInfoCard.tsx.ejs +109 -0
- package/hedhog/frontend/app/courses/[id]/_components/CourseMultiEntityPicker.tsx.ejs +42 -15
- package/hedhog/frontend/app/courses/[id]/_components/CourseRelationsCard.tsx.ejs +76 -81
- package/hedhog/frontend/app/courses/[id]/_components/CourseSummaryCard.tsx.ejs +60 -0
- package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +3 -3
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-scheduled-classes-tab.tsx.ejs +406 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +1 -1
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +134 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-course.tsx.ejs +113 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +314 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +174 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +242 -33
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +228 -152
- package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +185 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +71 -31
- package/hedhog/frontend/app/courses/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/enterprise/[id]/page.tsx.ejs +37 -41
- package/hedhog/frontend/app/enterprise/_components/enterprise-activity-timeline.tsx.ejs +87 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-admin-create-sheet.tsx.ejs +4 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-administrators-tab.tsx.ejs +31 -5
- package/hedhog/frontend/app/enterprise/_components/enterprise-classes-tab.tsx.ejs +79 -20
- package/hedhog/frontend/app/enterprise/_components/enterprise-company-identity-card.tsx.ejs +11 -2
- package/hedhog/frontend/app/enterprise/_components/enterprise-course-edit-sheet.tsx.ejs +201 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-courses-tab.tsx.ejs +55 -24
- package/hedhog/frontend/app/enterprise/_components/enterprise-detail-sheet.tsx.ejs +430 -296
- package/hedhog/frontend/app/enterprise/_components/enterprise-mocks.ts.ejs +277 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-overview-analytics.tsx.ejs +205 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-person-edit-sheet.tsx.ejs +97 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-sheet.tsx.ejs +82 -57
- package/hedhog/frontend/app/enterprise/_components/enterprise-student-create-sheet.tsx.ejs +4 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-students-tab.tsx.ejs +60 -22
- package/hedhog/frontend/app/enterprise/_components/enterprise-types.ts.ejs +54 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-user-create-sheet.tsx.ejs +211 -0
- package/hedhog/frontend/app/enterprise/page.tsx.ejs +39 -7
- package/hedhog/frontend/app/evaluations/_components/evaluation-topic-form-sheet.tsx.ejs +1 -1
- package/hedhog/frontend/app/exams/[id]/questions/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/exams/page.tsx.ejs +12 -3
- package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +51 -104
- package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +712 -427
- package/hedhog/frontend/app/instructors/page.tsx.ejs +77 -53
- package/hedhog/frontend/app/paths/page.tsx.ejs +14 -5
- package/hedhog/frontend/app/reports/courses/page.tsx.ejs +5 -5
- package/hedhog/frontend/app/reports/dashboard/page.tsx.ejs +8 -8
- package/hedhog/frontend/app/reports/evaluations/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/reports/page.tsx.ejs +7 -7
- package/hedhog/frontend/app/reports/students/page.tsx.ejs +6 -6
- package/hedhog/frontend/app/training/page.tsx.ejs +8 -3
- package/hedhog/frontend/messages/en.json +394 -55
- package/hedhog/frontend/messages/pt.json +389 -48
- package/hedhog/frontend/widgets/active-classes-kpi.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/active-courses-kpi.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/approval-rate-kpi.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/class-calendar.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/completion-rate-kpi.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/issued-certificates-kpi.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/total-students-kpi.tsx.ejs +1 -1
- package/hedhog/table/enterprise_student_license_event.yaml +30 -0
- package/hedhog/table/instructor_qualification.yaml +1 -1
- package/hedhog/table/instructor_skill.yaml +0 -11
- package/package.json +8 -8
- package/src/course/course.service.ts +12 -24
- package/src/enterprise/enterprise.controller.ts +5 -0
- package/src/enterprise/enterprise.service.ts +507 -29
- package/src/enterprise/training/training-admin.controller.ts +4 -0
- package/src/enterprise/training/training-admin.service.ts +115 -51
- package/src/instructor/dto/create-instructor-skill.dto.ts +0 -17
- package/src/instructor/dto/update-instructor-skill.dto.ts +0 -18
- package/src/instructor/instructor-skill.service.ts +2 -97
- package/src/instructor/instructor.controller.ts +16 -0
- package/src/instructor/instructor.service.ts +85 -10
- package/src/lms.module.ts +1 -0
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
} from 'lucide-react';
|
|
57
57
|
import { useTranslations } from 'next-intl';
|
|
58
58
|
import { useEffect, useState } from 'react';
|
|
59
|
+
import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
|
|
59
60
|
import { toast } from 'sonner';
|
|
60
61
|
import { InstructorFormSheet } from './_components/instructor-form-sheet';
|
|
61
62
|
import type {
|
|
@@ -73,6 +74,15 @@ const QUALIFICATION_LABELS: Record<string, string> = {
|
|
|
73
74
|
'class-sessions': 'Sessões de turma',
|
|
74
75
|
};
|
|
75
76
|
|
|
77
|
+
function getQualificationLabel(
|
|
78
|
+
slug: string,
|
|
79
|
+
t: (key: string) => string
|
|
80
|
+
): string {
|
|
81
|
+
if (slug === 'course-lessons') return t('qualificationLabels.courseLessons');
|
|
82
|
+
if (slug === 'class-sessions') return t('qualificationLabels.classSessions');
|
|
83
|
+
return QUALIFICATION_LABELS[slug] ?? slug;
|
|
84
|
+
}
|
|
85
|
+
|
|
76
86
|
function getInstructorInitials(name: string) {
|
|
77
87
|
return name
|
|
78
88
|
.split(' ')
|
|
@@ -93,7 +103,11 @@ export default function InstructorsPage() {
|
|
|
93
103
|
const t = useTranslations('lms.InstructorsPage');
|
|
94
104
|
|
|
95
105
|
const [page, setPage] = useState(1);
|
|
96
|
-
const [pageSize, setPageSize] =
|
|
106
|
+
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
107
|
+
storageKey: 'pagination:global:pageSize',
|
|
108
|
+
defaultValue: 12,
|
|
109
|
+
allowedValues: [6, 12, 24, 48],
|
|
110
|
+
});
|
|
97
111
|
const [searchInput, setSearchInput] = useState('');
|
|
98
112
|
const [debouncedSearch, setDebouncedSearch] = useState('');
|
|
99
113
|
const [statusFilter, setStatusFilter] = useState('all');
|
|
@@ -239,7 +253,7 @@ export default function InstructorsPage() {
|
|
|
239
253
|
const statsCards = [
|
|
240
254
|
{
|
|
241
255
|
key: 'total',
|
|
242
|
-
title: '
|
|
256
|
+
title: t('kpis.total'),
|
|
243
257
|
value: stats.total,
|
|
244
258
|
icon: Users,
|
|
245
259
|
accentClassName: 'from-slate-500/20 via-slate-400/10 to-transparent',
|
|
@@ -247,7 +261,7 @@ export default function InstructorsPage() {
|
|
|
247
261
|
},
|
|
248
262
|
{
|
|
249
263
|
key: 'active',
|
|
250
|
-
title: '
|
|
264
|
+
title: t('kpis.active'),
|
|
251
265
|
value: stats.active,
|
|
252
266
|
icon: UserRoundPen,
|
|
253
267
|
accentClassName: 'from-green-500/20 via-emerald-500/10 to-transparent',
|
|
@@ -255,7 +269,7 @@ export default function InstructorsPage() {
|
|
|
255
269
|
},
|
|
256
270
|
{
|
|
257
271
|
key: 'inactive',
|
|
258
|
-
title: '
|
|
272
|
+
title: t('kpis.inactive'),
|
|
259
273
|
value: stats.inactive,
|
|
260
274
|
icon: UserRoundPen,
|
|
261
275
|
accentClassName: 'from-gray-500/20 via-gray-400/10 to-transparent',
|
|
@@ -272,11 +286,11 @@ export default function InstructorsPage() {
|
|
|
272
286
|
setStatusFilter(value);
|
|
273
287
|
setPage(1);
|
|
274
288
|
},
|
|
275
|
-
placeholder: '
|
|
289
|
+
placeholder: t('filters.statusPlaceholder'),
|
|
276
290
|
options: [
|
|
277
|
-
{ value: 'all', label: '
|
|
278
|
-
{ value: 'active', label: '
|
|
279
|
-
{ value: 'inactive', label: '
|
|
291
|
+
{ value: 'all', label: t('filters.allStatuses') },
|
|
292
|
+
{ value: 'active', label: t('form.fields.active') },
|
|
293
|
+
{ value: 'inactive', label: t('form.fields.inactive') },
|
|
280
294
|
],
|
|
281
295
|
},
|
|
282
296
|
{
|
|
@@ -287,11 +301,17 @@ export default function InstructorsPage() {
|
|
|
287
301
|
setQualificationFilter(value);
|
|
288
302
|
setPage(1);
|
|
289
303
|
},
|
|
290
|
-
placeholder: '
|
|
304
|
+
placeholder: t('filters.qualificationPlaceholder'),
|
|
291
305
|
options: [
|
|
292
|
-
{ value: 'all', label: '
|
|
293
|
-
{
|
|
294
|
-
|
|
306
|
+
{ value: 'all', label: t('filters.allQualifications') },
|
|
307
|
+
{
|
|
308
|
+
value: 'course-lessons',
|
|
309
|
+
label: t('qualificationLabels.courseLessons'),
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
value: 'class-sessions',
|
|
313
|
+
label: t('qualificationLabels.classSessions'),
|
|
314
|
+
},
|
|
295
315
|
],
|
|
296
316
|
},
|
|
297
317
|
];
|
|
@@ -300,15 +320,15 @@ export default function InstructorsPage() {
|
|
|
300
320
|
<Page>
|
|
301
321
|
<PageHeader
|
|
302
322
|
breadcrumbs={[
|
|
303
|
-
{ label: '
|
|
304
|
-
{ label: '
|
|
305
|
-
{ label: '
|
|
323
|
+
{ label: t('breadcrumbs.home'), href: '/' },
|
|
324
|
+
{ label: t('breadcrumbs.lms'), href: '/lms' },
|
|
325
|
+
{ label: t('breadcrumbs.instructors') },
|
|
306
326
|
]}
|
|
307
|
-
title=
|
|
308
|
-
description=
|
|
327
|
+
title={t('header.title')}
|
|
328
|
+
description={t('header.description')}
|
|
309
329
|
actions={[
|
|
310
330
|
{
|
|
311
|
-
label: '
|
|
331
|
+
label: t('form.title'),
|
|
312
332
|
onClick: openCreateSheet,
|
|
313
333
|
icon: <Plus className="h-4 w-4" />,
|
|
314
334
|
},
|
|
@@ -325,14 +345,14 @@ export default function InstructorsPage() {
|
|
|
325
345
|
setSearchInput(value);
|
|
326
346
|
}}
|
|
327
347
|
onSearch={() => setPage(1)}
|
|
328
|
-
placeholder=
|
|
348
|
+
placeholder={t('search.placeholder')}
|
|
329
349
|
controls={searchControls}
|
|
330
|
-
/>
|
|
350
|
+
/>{' '}
|
|
331
351
|
</div>
|
|
332
352
|
|
|
333
353
|
<div className="flex items-center justify-between gap-3 sm:justify-start xl:justify-end">
|
|
334
354
|
<span className="text-xs font-medium text-muted-foreground">
|
|
335
|
-
|
|
355
|
+
{t('viewMode.label')}
|
|
336
356
|
</span>
|
|
337
357
|
<ToggleGroup
|
|
338
358
|
type="single"
|
|
@@ -340,23 +360,23 @@ export default function InstructorsPage() {
|
|
|
340
360
|
onValueChange={handleViewModeChange}
|
|
341
361
|
variant="outline"
|
|
342
362
|
size="sm"
|
|
343
|
-
aria-label=
|
|
363
|
+
aria-label={t('viewMode.ariaLabel')}
|
|
344
364
|
>
|
|
345
365
|
<ToggleGroupItem
|
|
346
366
|
value="table"
|
|
347
367
|
className="gap-1.5 px-2.5"
|
|
348
|
-
aria-label=
|
|
368
|
+
aria-label={t('viewMode.tableAriaLabel')}
|
|
349
369
|
>
|
|
350
370
|
<List className="h-4 w-4" />
|
|
351
|
-
<span className="hidden sm:inline">
|
|
371
|
+
<span className="hidden sm:inline">{t('viewMode.table')}</span>
|
|
352
372
|
</ToggleGroupItem>
|
|
353
373
|
<ToggleGroupItem
|
|
354
374
|
value="cards"
|
|
355
375
|
className="gap-1.5 px-2.5"
|
|
356
|
-
aria-label=
|
|
376
|
+
aria-label={t('viewMode.cardsAriaLabel')}
|
|
357
377
|
>
|
|
358
378
|
<LayoutGrid className="h-4 w-4" />
|
|
359
|
-
<span className="hidden sm:inline">
|
|
379
|
+
<span className="hidden sm:inline">{t('viewMode.cards')}</span>
|
|
360
380
|
</ToggleGroupItem>
|
|
361
381
|
</ToggleGroup>
|
|
362
382
|
</div>
|
|
@@ -393,9 +413,9 @@ export default function InstructorsPage() {
|
|
|
393
413
|
) : paginate.data.length === 0 ? (
|
|
394
414
|
<EmptyState
|
|
395
415
|
icon={<UserRoundPen className="h-12 w-12" />}
|
|
396
|
-
title=
|
|
397
|
-
description=
|
|
398
|
-
actionLabel=
|
|
416
|
+
title={t('emptyState.title')}
|
|
417
|
+
description={t('emptyState.description')}
|
|
418
|
+
actionLabel={t('form.title')}
|
|
399
419
|
actionIcon={<Plus className="mr-2 h-4 w-4" />}
|
|
400
420
|
onAction={openCreateSheet}
|
|
401
421
|
/>
|
|
@@ -406,14 +426,14 @@ export default function InstructorsPage() {
|
|
|
406
426
|
<Table>
|
|
407
427
|
<TableHeader>
|
|
408
428
|
<TableRow>
|
|
409
|
-
<TableHead>
|
|
410
|
-
<TableHead>
|
|
411
|
-
<TableHead>
|
|
412
|
-
<TableHead>
|
|
413
|
-
<TableHead>
|
|
414
|
-
<TableHead>
|
|
415
|
-
<TableHead>
|
|
416
|
-
<TableHead>
|
|
429
|
+
<TableHead>{t('table.instructor')}</TableHead>
|
|
430
|
+
<TableHead>{t('table.email')}</TableHead>
|
|
431
|
+
<TableHead>{t('table.phone')}</TableHead>
|
|
432
|
+
<TableHead>{t('table.qualifications')}</TableHead>
|
|
433
|
+
<TableHead>{t('table.skills')}</TableHead>
|
|
434
|
+
<TableHead>{t('table.hourlyRate')}</TableHead>
|
|
435
|
+
<TableHead>{t('table.training')}</TableHead>
|
|
436
|
+
<TableHead>{t('table.status')}</TableHead>
|
|
417
437
|
<TableHead className="w-10" />
|
|
418
438
|
</TableRow>
|
|
419
439
|
</TableHeader>
|
|
@@ -476,7 +496,7 @@ export default function InstructorsPage() {
|
|
|
476
496
|
variant="outline"
|
|
477
497
|
className="border-blue-500/20 bg-blue-500/10 px-2 py-0.5 text-[11px] font-medium text-blue-600"
|
|
478
498
|
>
|
|
479
|
-
{
|
|
499
|
+
{getQualificationLabel(slug, t)}
|
|
480
500
|
</Badge>
|
|
481
501
|
)
|
|
482
502
|
)}
|
|
@@ -518,14 +538,14 @@ export default function InstructorsPage() {
|
|
|
518
538
|
variant="outline"
|
|
519
539
|
className="border-amber-500/20 bg-amber-500/10 px-2.5 py-1 text-xs font-medium text-amber-600"
|
|
520
540
|
>
|
|
521
|
-
|
|
541
|
+
{t('training.active')}
|
|
522
542
|
</Badge>
|
|
523
543
|
) : instructor.userId ? (
|
|
524
544
|
<Badge
|
|
525
545
|
variant="outline"
|
|
526
546
|
className="border-gray-500/20 bg-gray-500/10 px-2.5 py-1 text-xs font-medium text-gray-500"
|
|
527
547
|
>
|
|
528
|
-
|
|
548
|
+
{t('training.disabled')}
|
|
529
549
|
</Badge>
|
|
530
550
|
) : (
|
|
531
551
|
<span className="text-xs text-muted-foreground">
|
|
@@ -543,7 +563,9 @@ export default function InstructorsPage() {
|
|
|
543
563
|
: 'border-gray-500/20 bg-gray-500/10 text-gray-600'
|
|
544
564
|
)}
|
|
545
565
|
>
|
|
546
|
-
{instructor.status === 'active'
|
|
566
|
+
{instructor.status === 'active'
|
|
567
|
+
? t('form.fields.active')
|
|
568
|
+
: t('form.fields.inactive')}
|
|
547
569
|
</Badge>
|
|
548
570
|
</TableCell>
|
|
549
571
|
<TableCell>
|
|
@@ -562,7 +584,7 @@ export default function InstructorsPage() {
|
|
|
562
584
|
onClick={() => openEditSheet(instructor)}
|
|
563
585
|
>
|
|
564
586
|
<Pencil className="mr-2 h-4 w-4" />
|
|
565
|
-
|
|
587
|
+
{t('actions.edit')}
|
|
566
588
|
</DropdownMenuItem>
|
|
567
589
|
<DropdownMenuSeparator />
|
|
568
590
|
<DropdownMenuItem
|
|
@@ -573,7 +595,7 @@ export default function InstructorsPage() {
|
|
|
573
595
|
}}
|
|
574
596
|
>
|
|
575
597
|
<Trash2 className="mr-2 h-4 w-4" />
|
|
576
|
-
|
|
598
|
+
{t('actions.delete')}
|
|
577
599
|
</DropdownMenuItem>
|
|
578
600
|
</DropdownMenuContent>
|
|
579
601
|
</DropdownMenu>
|
|
@@ -641,7 +663,7 @@ export default function InstructorsPage() {
|
|
|
641
663
|
onClick={() => openEditSheet(instructor)}
|
|
642
664
|
>
|
|
643
665
|
<Pencil className="mr-2 h-4 w-4" />
|
|
644
|
-
|
|
666
|
+
{t('actions.edit')}
|
|
645
667
|
</DropdownMenuItem>
|
|
646
668
|
<DropdownMenuSeparator />
|
|
647
669
|
<DropdownMenuItem
|
|
@@ -652,7 +674,7 @@ export default function InstructorsPage() {
|
|
|
652
674
|
}}
|
|
653
675
|
>
|
|
654
676
|
<Trash2 className="mr-2 h-4 w-4" />
|
|
655
|
-
|
|
677
|
+
{t('actions.delete')}
|
|
656
678
|
</DropdownMenuItem>
|
|
657
679
|
</DropdownMenuContent>
|
|
658
680
|
</DropdownMenu>
|
|
@@ -668,7 +690,9 @@ export default function InstructorsPage() {
|
|
|
668
690
|
: 'border-gray-500/20 bg-gray-500/10 text-gray-600'
|
|
669
691
|
)}
|
|
670
692
|
>
|
|
671
|
-
{instructor.status === 'active'
|
|
693
|
+
{instructor.status === 'active'
|
|
694
|
+
? t('form.fields.active')
|
|
695
|
+
: t('form.fields.inactive')}
|
|
672
696
|
</Badge>
|
|
673
697
|
{instructor.qualificationSlugs.map((slug) => (
|
|
674
698
|
<Badge
|
|
@@ -676,7 +700,7 @@ export default function InstructorsPage() {
|
|
|
676
700
|
variant="outline"
|
|
677
701
|
className="border-blue-500/20 bg-blue-500/10 px-2 py-0.5 text-[11px] font-medium text-blue-600"
|
|
678
702
|
>
|
|
679
|
-
{
|
|
703
|
+
{getQualificationLabel(slug, t)}
|
|
680
704
|
</Badge>
|
|
681
705
|
))}
|
|
682
706
|
{(instructor.skills ?? []).map((skill) => (
|
|
@@ -723,23 +747,23 @@ export default function InstructorsPage() {
|
|
|
723
747
|
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
|
724
748
|
<AlertDialogContent>
|
|
725
749
|
<AlertDialogHeader>
|
|
726
|
-
<AlertDialogTitle>
|
|
750
|
+
<AlertDialogTitle>{t('deleteDialog.title')}</AlertDialogTitle>
|
|
727
751
|
<AlertDialogDescription>
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
752
|
+
{t('deleteDialog.description', {
|
|
753
|
+
name: instructorToDelete?.name ?? '',
|
|
754
|
+
})}
|
|
731
755
|
</AlertDialogDescription>
|
|
732
756
|
</AlertDialogHeader>
|
|
733
757
|
<div className="flex justify-end gap-2">
|
|
734
758
|
<AlertDialogCancel disabled={isDeleting}>
|
|
735
|
-
|
|
759
|
+
{t('actions.cancel')}
|
|
736
760
|
</AlertDialogCancel>
|
|
737
761
|
<AlertDialogAction
|
|
738
762
|
onClick={handleDeleteConfirm}
|
|
739
763
|
disabled={isDeleting}
|
|
740
764
|
className="bg-red-600 hover:bg-red-700"
|
|
741
765
|
>
|
|
742
|
-
{isDeleting ? '
|
|
766
|
+
{isDeleting ? t('actions.deleting') : t('actions.delete')}
|
|
743
767
|
</AlertDialogAction>
|
|
744
768
|
</div>
|
|
745
769
|
</AlertDialogContent>
|
|
@@ -103,6 +103,7 @@ import {
|
|
|
103
103
|
import { useTranslations } from 'next-intl';
|
|
104
104
|
import { useRouter } from 'next/navigation';
|
|
105
105
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
106
|
+
import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
|
|
106
107
|
import { Controller, useForm, useWatch } from 'react-hook-form';
|
|
107
108
|
import { toast } from 'sonner';
|
|
108
109
|
import { z } from 'zod';
|
|
@@ -565,7 +566,11 @@ export default function TrainingPage() {
|
|
|
565
566
|
|
|
566
567
|
// Pagination
|
|
567
568
|
const [currentPage, setCurrentPage] = useState(1);
|
|
568
|
-
const [pageSize, setPageSize] =
|
|
569
|
+
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
570
|
+
storageKey: 'pagination:global:pageSize',
|
|
571
|
+
defaultValue: 12,
|
|
572
|
+
allowedValues: PAGE_SIZES,
|
|
573
|
+
});
|
|
569
574
|
|
|
570
575
|
// Double-click tracking
|
|
571
576
|
const clickTimers = useRef<Map<number, ReturnType<typeof setTimeout>>>(
|
|
@@ -1894,13 +1899,13 @@ export default function TrainingPage() {
|
|
|
1894
1899
|
</Field>
|
|
1895
1900
|
<Field>
|
|
1896
1901
|
<FieldLabel htmlFor="descricao">
|
|
1897
|
-
{t('form.fields.
|
|
1902
|
+
{t('form.fields.description.label')}{' '}
|
|
1898
1903
|
<span className="text-destructive">*</span>
|
|
1899
1904
|
</FieldLabel>
|
|
1900
1905
|
<Textarea
|
|
1901
1906
|
id="descricao"
|
|
1902
1907
|
rows={3}
|
|
1903
|
-
placeholder={t('form.fields.
|
|
1908
|
+
placeholder={t('form.fields.description.placeholder')}
|
|
1904
1909
|
{...form.register('descricao')}
|
|
1905
1910
|
/>
|
|
1906
1911
|
<FieldError>
|
|
@@ -1984,7 +1989,9 @@ export default function TrainingPage() {
|
|
|
1984
1989
|
|
|
1985
1990
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
1986
1991
|
<Field>
|
|
1987
|
-
<FieldLabel htmlFor="primaryColor">
|
|
1992
|
+
<FieldLabel htmlFor="primaryColor">
|
|
1993
|
+
{t('form.fields.primaryColor.label')}
|
|
1994
|
+
</FieldLabel>
|
|
1988
1995
|
<Controller
|
|
1989
1996
|
name="primaryColor"
|
|
1990
1997
|
control={form.control}
|
|
@@ -2011,7 +2018,9 @@ export default function TrainingPage() {
|
|
|
2011
2018
|
</Field>
|
|
2012
2019
|
|
|
2013
2020
|
<Field>
|
|
2014
|
-
<FieldLabel htmlFor="secondaryColor">
|
|
2021
|
+
<FieldLabel htmlFor="secondaryColor">
|
|
2022
|
+
{t('form.fields.secondaryColor.label')}
|
|
2023
|
+
</FieldLabel>
|
|
2015
2024
|
<Controller
|
|
2016
2025
|
name="secondaryColor"
|
|
2017
2026
|
control={form.control}
|
|
@@ -429,7 +429,7 @@ export default function ReportCoursesPage() {
|
|
|
429
429
|
}
|
|
430
430
|
};
|
|
431
431
|
|
|
432
|
-
const renderEmptyState = (className = 'min-h-
|
|
432
|
+
const renderEmptyState = (className = 'min-h-45') => (
|
|
433
433
|
<EmptyState
|
|
434
434
|
icon={Inbox}
|
|
435
435
|
title={t('emptyState.title')}
|
|
@@ -495,7 +495,7 @@ export default function ReportCoursesPage() {
|
|
|
495
495
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
|
496
496
|
{Array.from({ length: 4 }).map((_, i) => (
|
|
497
497
|
<Card key={i} className="overflow-hidden border-border/70 py-0">
|
|
498
|
-
<div className="h-1 w-full bg-
|
|
498
|
+
<div className="h-1 w-full bg-linear-to-r from-slate-300/70 via-slate-200 to-transparent" />
|
|
499
499
|
<CardContent className="p-6">
|
|
500
500
|
<Skeleton className="mb-3 h-4 w-28" />
|
|
501
501
|
<Skeleton className="mb-2 h-8 w-24" />
|
|
@@ -544,7 +544,7 @@ export default function ReportCoursesPage() {
|
|
|
544
544
|
<Card>
|
|
545
545
|
<CardContent className="p-6">
|
|
546
546
|
<Skeleton className="mb-4 h-5 w-40" />
|
|
547
|
-
<Skeleton className="h-
|
|
547
|
+
<Skeleton className="h-60 w-full" />
|
|
548
548
|
</CardContent>
|
|
549
549
|
</Card>
|
|
550
550
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
|
@@ -664,7 +664,7 @@ export default function ReportCoursesPage() {
|
|
|
664
664
|
<Table>
|
|
665
665
|
<TableHeader>
|
|
666
666
|
<TableRow>
|
|
667
|
-
<TableHead className="w-
|
|
667
|
+
<TableHead className="w-11">#</TableHead>
|
|
668
668
|
<TableHead>{t('table.course')}</TableHead>
|
|
669
669
|
<TableHead>{t('table.students')}</TableHead>
|
|
670
670
|
<TableHead className="hidden sm:table-cell">
|
|
@@ -789,7 +789,7 @@ export default function ReportCoursesPage() {
|
|
|
789
789
|
{loading ? (
|
|
790
790
|
<div className="p-6">
|
|
791
791
|
<Skeleton className="mb-4 h-5 w-48" />
|
|
792
|
-
<Skeleton className="h-
|
|
792
|
+
<Skeleton className="h-45 w-full" />
|
|
793
793
|
</div>
|
|
794
794
|
) : filteredCategoryPerformance.length === 0 ? (
|
|
795
795
|
<div className="px-6 pb-6">
|
|
@@ -452,7 +452,7 @@ export default function ReportsDashboardPage() {
|
|
|
452
452
|
}
|
|
453
453
|
};
|
|
454
454
|
|
|
455
|
-
const renderEmptyState = (className = 'min-h-
|
|
455
|
+
const renderEmptyState = (className = 'min-h-45') => (
|
|
456
456
|
<EmptyState
|
|
457
457
|
icon={Inbox}
|
|
458
458
|
title={t('emptyState.title')}
|
|
@@ -493,7 +493,7 @@ export default function ReportsDashboardPage() {
|
|
|
493
493
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
|
494
494
|
{Array.from({ length: 4 }).map((_, i) => (
|
|
495
495
|
<Card key={i} className="overflow-hidden border-border/70 py-0">
|
|
496
|
-
<div className="h-1 w-full bg-
|
|
496
|
+
<div className="h-1 w-full bg-linear-to-r from-slate-300/70 via-slate-200 to-transparent" />
|
|
497
497
|
<CardContent className="p-6">
|
|
498
498
|
<Skeleton className="mb-3 h-4 w-28" />
|
|
499
499
|
<Skeleton className="mb-2 h-8 w-24" />
|
|
@@ -534,7 +534,7 @@ export default function ReportsDashboardPage() {
|
|
|
534
534
|
<Card>
|
|
535
535
|
<CardContent className="p-6">
|
|
536
536
|
<Skeleton className="mb-4 h-5 w-44" />
|
|
537
|
-
<Skeleton className="h-
|
|
537
|
+
<Skeleton className="h-60 w-full" />
|
|
538
538
|
</CardContent>
|
|
539
539
|
</Card>
|
|
540
540
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
|
@@ -626,13 +626,13 @@ export default function ReportsDashboardPage() {
|
|
|
626
626
|
<Card>
|
|
627
627
|
<CardContent className="p-6">
|
|
628
628
|
<Skeleton className="mb-4 h-5 w-40" />
|
|
629
|
-
<Skeleton className="h-
|
|
629
|
+
<Skeleton className="h-75 w-full" />
|
|
630
630
|
</CardContent>
|
|
631
631
|
</Card>
|
|
632
632
|
<Card>
|
|
633
633
|
<CardContent className="p-6">
|
|
634
634
|
<Skeleton className="mb-4 h-5 w-40" />
|
|
635
|
-
<Skeleton className="h-
|
|
635
|
+
<Skeleton className="h-75 w-full" />
|
|
636
636
|
</CardContent>
|
|
637
637
|
</Card>
|
|
638
638
|
</>
|
|
@@ -654,7 +654,7 @@ export default function ReportsDashboardPage() {
|
|
|
654
654
|
<Table>
|
|
655
655
|
<TableHeader>
|
|
656
656
|
<TableRow>
|
|
657
|
-
<TableHead className="w-
|
|
657
|
+
<TableHead className="w-11">#</TableHead>
|
|
658
658
|
<TableHead>{t('table.course')}</TableHead>
|
|
659
659
|
<TableHead>{t('table.students')}</TableHead>
|
|
660
660
|
<TableHead>{t('table.score')}</TableHead>
|
|
@@ -736,13 +736,13 @@ export default function ReportsDashboardPage() {
|
|
|
736
736
|
<Card>
|
|
737
737
|
<CardContent className="p-6">
|
|
738
738
|
<Skeleton className="mb-4 h-5 w-44" />
|
|
739
|
-
<Skeleton className="h-
|
|
739
|
+
<Skeleton className="h-65 w-full" />
|
|
740
740
|
</CardContent>
|
|
741
741
|
</Card>
|
|
742
742
|
<Card>
|
|
743
743
|
<CardContent className="p-6">
|
|
744
744
|
<Skeleton className="mb-4 h-5 w-44" />
|
|
745
|
-
<Skeleton className="h-
|
|
745
|
+
<Skeleton className="h-65 w-full" />
|
|
746
746
|
</CardContent>
|
|
747
747
|
</Card>
|
|
748
748
|
</>
|
|
@@ -61,6 +61,7 @@ import {
|
|
|
61
61
|
import { useTranslations } from 'next-intl';
|
|
62
62
|
import { useRouter } from 'next/navigation';
|
|
63
63
|
import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react';
|
|
64
|
+
import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
|
|
64
65
|
import type { DateRange } from 'react-day-picker';
|
|
65
66
|
|
|
66
67
|
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
@@ -360,7 +361,11 @@ export default function EvaluationsReportPage() {
|
|
|
360
361
|
|
|
361
362
|
// Pagination
|
|
362
363
|
const [currentPage, setCurrentPage] = useState(1);
|
|
363
|
-
const [pageSize, setPageSize] =
|
|
364
|
+
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
365
|
+
storageKey: 'pagination:global:pageSize',
|
|
366
|
+
defaultValue: 12,
|
|
367
|
+
allowedValues: PAGE_SIZES,
|
|
368
|
+
});
|
|
364
369
|
|
|
365
370
|
// Reset page when filters change
|
|
366
371
|
useEffect(() => {
|
|
@@ -412,7 +412,7 @@ export default function ReportsDashboardPage() {
|
|
|
412
412
|
}
|
|
413
413
|
};
|
|
414
414
|
|
|
415
|
-
const renderEmptyState = (className = 'min-h-
|
|
415
|
+
const renderEmptyState = (className = 'min-h-45') => (
|
|
416
416
|
<div
|
|
417
417
|
className={`flex flex-col items-center justify-center rounded-lg border border-dashed bg-muted/20 px-6 py-10 text-center ${className}`}
|
|
418
418
|
>
|
|
@@ -496,7 +496,7 @@ export default function ReportsDashboardPage() {
|
|
|
496
496
|
<Card>
|
|
497
497
|
<CardContent className="p-6">
|
|
498
498
|
<Skeleton className="mb-4 h-5 w-44" />
|
|
499
|
-
<Skeleton className="h-
|
|
499
|
+
<Skeleton className="h-60 w-full" />
|
|
500
500
|
</CardContent>
|
|
501
501
|
</Card>
|
|
502
502
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
|
@@ -640,13 +640,13 @@ export default function ReportsDashboardPage() {
|
|
|
640
640
|
<Card>
|
|
641
641
|
<CardContent className="p-6">
|
|
642
642
|
<Skeleton className="mb-4 h-5 w-40" />
|
|
643
|
-
<Skeleton className="h-
|
|
643
|
+
<Skeleton className="h-75 w-full" />
|
|
644
644
|
</CardContent>
|
|
645
645
|
</Card>
|
|
646
646
|
<Card>
|
|
647
647
|
<CardContent className="p-6">
|
|
648
648
|
<Skeleton className="mb-4 h-5 w-40" />
|
|
649
|
-
<Skeleton className="h-
|
|
649
|
+
<Skeleton className="h-75 w-full" />
|
|
650
650
|
</CardContent>
|
|
651
651
|
</Card>
|
|
652
652
|
</>
|
|
@@ -668,7 +668,7 @@ export default function ReportsDashboardPage() {
|
|
|
668
668
|
<Table>
|
|
669
669
|
<TableHeader>
|
|
670
670
|
<TableRow>
|
|
671
|
-
<TableHead className="w-
|
|
671
|
+
<TableHead className="w-11">#</TableHead>
|
|
672
672
|
<TableHead>{t('table.course')}</TableHead>
|
|
673
673
|
<TableHead>{t('table.students')}</TableHead>
|
|
674
674
|
<TableHead>{t('table.score')}</TableHead>
|
|
@@ -750,13 +750,13 @@ export default function ReportsDashboardPage() {
|
|
|
750
750
|
<Card>
|
|
751
751
|
<CardContent className="p-6">
|
|
752
752
|
<Skeleton className="mb-4 h-5 w-44" />
|
|
753
|
-
<Skeleton className="h-
|
|
753
|
+
<Skeleton className="h-65 w-full" />
|
|
754
754
|
</CardContent>
|
|
755
755
|
</Card>
|
|
756
756
|
<Card>
|
|
757
757
|
<CardContent className="p-6">
|
|
758
758
|
<Skeleton className="mb-4 h-5 w-44" />
|
|
759
|
-
<Skeleton className="h-
|
|
759
|
+
<Skeleton className="h-65 w-full" />
|
|
760
760
|
</CardContent>
|
|
761
761
|
</Card>
|
|
762
762
|
</>
|
|
@@ -422,7 +422,7 @@ export default function ReportStudentsPage() {
|
|
|
422
422
|
}
|
|
423
423
|
};
|
|
424
424
|
|
|
425
|
-
const renderEmptyState = (className = 'min-h-
|
|
425
|
+
const renderEmptyState = (className = 'min-h-45') => (
|
|
426
426
|
<EmptyState
|
|
427
427
|
icon={Inbox}
|
|
428
428
|
title={t('emptyState.title')}
|
|
@@ -480,7 +480,7 @@ export default function ReportStudentsPage() {
|
|
|
480
480
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
|
481
481
|
{Array.from({ length: 4 }).map((_, i) => (
|
|
482
482
|
<Card key={i} className="overflow-hidden border-border/70 py-0">
|
|
483
|
-
<div className="h-1 w-full bg-
|
|
483
|
+
<div className="h-1 w-full bg-linear-to-r from-slate-300/70 via-slate-200 to-transparent" />
|
|
484
484
|
<CardContent className="p-6">
|
|
485
485
|
<Skeleton className="mb-3 h-4 w-28" />
|
|
486
486
|
<Skeleton className="mb-2 h-8 w-24" />
|
|
@@ -529,7 +529,7 @@ export default function ReportStudentsPage() {
|
|
|
529
529
|
<Card>
|
|
530
530
|
<CardContent className="p-6">
|
|
531
531
|
<Skeleton className="mb-4 h-5 w-44" />
|
|
532
|
-
<Skeleton className="h-
|
|
532
|
+
<Skeleton className="h-60 w-full" />
|
|
533
533
|
</CardContent>
|
|
534
534
|
</Card>
|
|
535
535
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
|
@@ -611,13 +611,13 @@ export default function ReportStudentsPage() {
|
|
|
611
611
|
<Card>
|
|
612
612
|
<CardContent className="p-6">
|
|
613
613
|
<Skeleton className="mb-4 h-5 w-40" />
|
|
614
|
-
<Skeleton className="h-
|
|
614
|
+
<Skeleton className="h-75 w-full" />
|
|
615
615
|
</CardContent>
|
|
616
616
|
</Card>
|
|
617
617
|
<Card>
|
|
618
618
|
<CardContent className="p-6">
|
|
619
619
|
<Skeleton className="mb-4 h-5 w-44" />
|
|
620
|
-
<Skeleton className="h-
|
|
620
|
+
<Skeleton className="h-75 w-full" />
|
|
621
621
|
</CardContent>
|
|
622
622
|
</Card>
|
|
623
623
|
</>
|
|
@@ -718,7 +718,7 @@ export default function ReportStudentsPage() {
|
|
|
718
718
|
{loading ? (
|
|
719
719
|
<div className="p-6">
|
|
720
720
|
<Skeleton className="mb-4 h-5 w-48" />
|
|
721
|
-
<Skeleton className="h-
|
|
721
|
+
<Skeleton className="h-45 w-full" />
|
|
722
722
|
</div>
|
|
723
723
|
) : filteredCategoryPerformance.length === 0 ? (
|
|
724
724
|
<div className="px-6 pb-6">
|