@hed-hog/lms 0.0.278 → 0.0.285
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/README.md +2 -0
- package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +28 -12
- package/hedhog/frontend/app/classes/page.tsx.ejs +28 -99
- package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +152 -109
- package/hedhog/frontend/app/courses/[id]/structure/page.tsx.ejs +20 -32
- package/hedhog/frontend/app/courses/page.tsx.ejs +31 -106
- package/hedhog/frontend/app/exams/[id]/questions/page.tsx.ejs +43 -17
- package/hedhog/frontend/app/exams/page.tsx.ejs +27 -98
- package/hedhog/frontend/app/training/page.tsx.ejs +29 -99
- package/hedhog/frontend/messages/en.json +14 -4
- package/hedhog/frontend/messages/pt.json +14 -4
- package/package.json +8 -8
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
EmptyState,
|
|
5
|
+
Page,
|
|
6
|
+
PageHeader,
|
|
7
|
+
PaginationFooter,
|
|
8
|
+
} from '@/components/entity-list';
|
|
4
9
|
import { Badge } from '@/components/ui/badge';
|
|
5
10
|
import { Button } from '@/components/ui/button';
|
|
6
11
|
import { Card, CardContent } from '@/components/ui/card';
|
|
@@ -54,10 +59,6 @@ import {
|
|
|
54
59
|
Award,
|
|
55
60
|
BarChart3,
|
|
56
61
|
BookOpen,
|
|
57
|
-
ChevronLeft,
|
|
58
|
-
ChevronRight,
|
|
59
|
-
ChevronsLeft,
|
|
60
|
-
ChevronsRight,
|
|
61
62
|
Eye,
|
|
62
63
|
FileCheck,
|
|
63
64
|
GraduationCap,
|
|
@@ -748,7 +749,7 @@ export default function CursosPage() {
|
|
|
748
749
|
/>
|
|
749
750
|
|
|
750
751
|
{/* ── KPI Cards ────────────────────────────────────────────────────── */}
|
|
751
|
-
<div className="mb-
|
|
752
|
+
<div className="mb-6 grid grid-cols-2 gap-4 lg:grid-cols-4">
|
|
752
753
|
{loading
|
|
753
754
|
? Array.from({ length: 4 }).map((_, i) => (
|
|
754
755
|
<Card key={i}>
|
|
@@ -790,7 +791,7 @@ export default function CursosPage() {
|
|
|
790
791
|
</div>
|
|
791
792
|
|
|
792
793
|
{/* ── Search bar ───────────────────────────────────────────────────── */}
|
|
793
|
-
<form onSubmit={handleSearch} className="mb-
|
|
794
|
+
<form onSubmit={handleSearch} className="mb-6 mt-0">
|
|
794
795
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
|
|
795
796
|
{/* Search input — grows to fill space */}
|
|
796
797
|
<div className="relative flex-1">
|
|
@@ -962,17 +963,14 @@ export default function CursosPage() {
|
|
|
962
963
|
))}
|
|
963
964
|
</div>
|
|
964
965
|
) : filteredCursos.length === 0 ? (
|
|
965
|
-
<
|
|
966
|
-
<BookOpen className="
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
<
|
|
972
|
-
|
|
973
|
-
{t('actions.createCourse')}
|
|
974
|
-
</Button>
|
|
975
|
-
</div>
|
|
966
|
+
<EmptyState
|
|
967
|
+
icon={<BookOpen className="h-12 w-12" />}
|
|
968
|
+
title={t('table.empty.title')}
|
|
969
|
+
description={t('table.empty.description')}
|
|
970
|
+
actionLabel={t('actions.createCourse')}
|
|
971
|
+
onAction={openCreateSheet}
|
|
972
|
+
actionIcon={<Plus className="mr-2 h-4 w-4" />}
|
|
973
|
+
/>
|
|
976
974
|
) : (
|
|
977
975
|
<motion.div
|
|
978
976
|
className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"
|
|
@@ -1093,7 +1091,7 @@ export default function CursosPage() {
|
|
|
1093
1091
|
)}
|
|
1094
1092
|
</Badge>
|
|
1095
1093
|
{curso.destaque && (
|
|
1096
|
-
<span className="inline-flex items-center gap-1 rounded-full bg-
|
|
1094
|
+
<span className="inline-flex items-center gap-1 rounded-full border border-amber-200/60 bg-linear-to-r from-amber-50 to-orange-50 px-2.5 py-0.5 text-[11px] font-medium text-amber-700 shadow-sm">
|
|
1097
1095
|
<Star className="size-3 fill-amber-400 text-amber-400" />{' '}
|
|
1098
1096
|
{t('form.flags.featured.label')}
|
|
1099
1097
|
</span>
|
|
@@ -1152,92 +1150,19 @@ export default function CursosPage() {
|
|
|
1152
1150
|
|
|
1153
1151
|
{/* ── Pagination footer ─────────────────────────────────────────────── */}
|
|
1154
1152
|
{!loading && filteredCursos.length > 0 && (
|
|
1155
|
-
<div className="mt-6
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
{
|
|
1159
|
-
{filteredCursos.length
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
<div className="flex items-center gap-1">
|
|
1169
|
-
<Button
|
|
1170
|
-
variant="outline"
|
|
1171
|
-
size="icon"
|
|
1172
|
-
className="size-8"
|
|
1173
|
-
onClick={() => setCurrentPage(1)}
|
|
1174
|
-
disabled={safePage === 1}
|
|
1175
|
-
aria-label={t('pagination.firstPage')}
|
|
1176
|
-
>
|
|
1177
|
-
<ChevronsLeft className="size-4" />
|
|
1178
|
-
</Button>
|
|
1179
|
-
<Button
|
|
1180
|
-
variant="outline"
|
|
1181
|
-
size="icon"
|
|
1182
|
-
className="size-8"
|
|
1183
|
-
onClick={() => setCurrentPage((p) => p - 1)}
|
|
1184
|
-
disabled={safePage === 1}
|
|
1185
|
-
aria-label={t('pagination.previousPage')}
|
|
1186
|
-
>
|
|
1187
|
-
<ChevronLeft className="size-4" />
|
|
1188
|
-
</Button>
|
|
1189
|
-
<span className="px-3 text-sm">
|
|
1190
|
-
{t('pagination.page')}{' '}
|
|
1191
|
-
<span className="font-semibold">{safePage}</span>{' '}
|
|
1192
|
-
{t('pagination.of')}{' '}
|
|
1193
|
-
<span className="font-semibold">{totalPages}</span>
|
|
1194
|
-
</span>
|
|
1195
|
-
<Button
|
|
1196
|
-
variant="outline"
|
|
1197
|
-
size="icon"
|
|
1198
|
-
className="size-8"
|
|
1199
|
-
onClick={() => setCurrentPage((p) => p + 1)}
|
|
1200
|
-
disabled={safePage === totalPages}
|
|
1201
|
-
aria-label={t('pagination.nextPage')}
|
|
1202
|
-
>
|
|
1203
|
-
<ChevronRight className="size-4" />
|
|
1204
|
-
</Button>
|
|
1205
|
-
<Button
|
|
1206
|
-
variant="outline"
|
|
1207
|
-
size="icon"
|
|
1208
|
-
className="size-8"
|
|
1209
|
-
onClick={() => setCurrentPage(totalPages)}
|
|
1210
|
-
disabled={safePage === totalPages}
|
|
1211
|
-
aria-label={t('pagination.lastPage')}
|
|
1212
|
-
>
|
|
1213
|
-
<ChevronsRight className="size-4" />
|
|
1214
|
-
</Button>
|
|
1215
|
-
</div>
|
|
1216
|
-
|
|
1217
|
-
{/* Items per page */}
|
|
1218
|
-
<div className="flex items-center gap-2 text-sm">
|
|
1219
|
-
<span className="text-muted-foreground">
|
|
1220
|
-
{t('pagination.itemsPerPage')}
|
|
1221
|
-
</span>
|
|
1222
|
-
<Select
|
|
1223
|
-
value={String(pageSize)}
|
|
1224
|
-
onValueChange={(v) => {
|
|
1225
|
-
setPageSize(Number(v));
|
|
1226
|
-
setCurrentPage(1);
|
|
1227
|
-
}}
|
|
1228
|
-
>
|
|
1229
|
-
<SelectTrigger className="h-8 w-16 text-sm">
|
|
1230
|
-
<SelectValue />
|
|
1231
|
-
</SelectTrigger>
|
|
1232
|
-
<SelectContent>
|
|
1233
|
-
{PAGE_SIZES.map((s) => (
|
|
1234
|
-
<SelectItem key={s} value={String(s)}>
|
|
1235
|
-
{s}
|
|
1236
|
-
</SelectItem>
|
|
1237
|
-
))}
|
|
1238
|
-
</SelectContent>
|
|
1239
|
-
</Select>
|
|
1240
|
-
</div>
|
|
1153
|
+
<div className="mt-6">
|
|
1154
|
+
<PaginationFooter
|
|
1155
|
+
currentPage={safePage}
|
|
1156
|
+
pageSize={pageSize}
|
|
1157
|
+
totalItems={filteredCursos.length}
|
|
1158
|
+
onPageChange={setCurrentPage}
|
|
1159
|
+
onPageSizeChange={(nextSize) => {
|
|
1160
|
+
setPageSize(nextSize);
|
|
1161
|
+
setCurrentPage(1);
|
|
1162
|
+
}}
|
|
1163
|
+
pageSizeOptions={PAGE_SIZES}
|
|
1164
|
+
selectedCount={selectedIds.size}
|
|
1165
|
+
/>
|
|
1241
1166
|
</div>
|
|
1242
1167
|
)}
|
|
1243
1168
|
|
|
@@ -1409,7 +1334,7 @@ export default function CursosPage() {
|
|
|
1409
1334
|
{CATEGORIAS.map((cat) => (
|
|
1410
1335
|
<label
|
|
1411
1336
|
key={cat}
|
|
1412
|
-
className="flex cursor-pointer items-center gap-2 rounded-md border p-2.5 text-sm hover:bg-muted has-
|
|
1337
|
+
className="flex cursor-pointer items-center gap-2 rounded-md border p-2.5 text-sm hover:bg-muted has-checked:border-foreground has-checked:bg-muted"
|
|
1413
1338
|
>
|
|
1414
1339
|
<Checkbox
|
|
1415
1340
|
checked={field.value.includes(cat)}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
EmptyState,
|
|
5
|
+
Page,
|
|
6
|
+
PageHeader,
|
|
7
|
+
PaginationFooter,
|
|
8
|
+
} from '@/components/entity-list';
|
|
4
9
|
import { Badge } from '@/components/ui/badge';
|
|
5
10
|
import { Button } from '@/components/ui/button';
|
|
6
11
|
import { Card, CardContent } from '@/components/ui/card';
|
|
@@ -167,6 +172,8 @@ const initialQuestoes: Questao[] = [
|
|
|
167
172
|
},
|
|
168
173
|
];
|
|
169
174
|
|
|
175
|
+
const PAGE_SIZE_OPTIONS = [6, 12, 24];
|
|
176
|
+
|
|
170
177
|
function SortableAlternativa({
|
|
171
178
|
alt,
|
|
172
179
|
index,
|
|
@@ -435,6 +442,8 @@ export default function QuestoesPage() {
|
|
|
435
442
|
const [loading, setLoading] = useState(true);
|
|
436
443
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
|
437
444
|
const [questoes, setQuestoes] = useState<Questao[]>(initialQuestoes);
|
|
445
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
446
|
+
const [pageSize, setPageSize] = useState(6);
|
|
438
447
|
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set());
|
|
439
448
|
const [sheetOpen, setSheetOpen] = useState(false);
|
|
440
449
|
const [editingQuestao, setEditingQuestao] = useState<Questao | null>(null);
|
|
@@ -463,6 +472,13 @@ export default function QuestoesPage() {
|
|
|
463
472
|
return () => clearTimeout(timer);
|
|
464
473
|
}, []);
|
|
465
474
|
|
|
475
|
+
const totalPages = Math.max(1, Math.ceil(questoes.length / pageSize));
|
|
476
|
+
const safePage = Math.min(Math.max(currentPage, 1), totalPages);
|
|
477
|
+
const paginatedQuestoes = questoes.slice(
|
|
478
|
+
(safePage - 1) * pageSize,
|
|
479
|
+
safePage * pageSize
|
|
480
|
+
);
|
|
481
|
+
|
|
466
482
|
function toggleExpand(qId: string) {
|
|
467
483
|
setExpandedIds((prev) => {
|
|
468
484
|
const next = new Set(prev);
|
|
@@ -730,20 +746,14 @@ export default function QuestoesPage() {
|
|
|
730
746
|
))}
|
|
731
747
|
</div>
|
|
732
748
|
) : questoes.length === 0 ? (
|
|
733
|
-
<
|
|
734
|
-
<
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
</p>
|
|
742
|
-
<Button onClick={openCreateSheet} className="mt-2 gap-2">
|
|
743
|
-
<Plus className="size-4" /> {t('empty.action')}
|
|
744
|
-
</Button>
|
|
745
|
-
</CardContent>
|
|
746
|
-
</Card>
|
|
749
|
+
<EmptyState
|
|
750
|
+
icon={<FileCheck className="h-12 w-12" />}
|
|
751
|
+
title={t('empty.title')}
|
|
752
|
+
description={t('empty.description')}
|
|
753
|
+
actionLabel={t('empty.action')}
|
|
754
|
+
onAction={openCreateSheet}
|
|
755
|
+
actionIcon={<Plus className="mr-2 h-4 w-4" />}
|
|
756
|
+
/>
|
|
747
757
|
) : (
|
|
748
758
|
<DndContext
|
|
749
759
|
sensors={sensors}
|
|
@@ -756,13 +766,13 @@ export default function QuestoesPage() {
|
|
|
756
766
|
>
|
|
757
767
|
<div className="flex flex-col gap-3">
|
|
758
768
|
<AnimatePresence>
|
|
759
|
-
{
|
|
769
|
+
{paginatedQuestoes.map((questao, qIndex) => {
|
|
760
770
|
const isExpanded = expandedIds.has(questao.id);
|
|
761
771
|
return (
|
|
762
772
|
<SortableQuestao
|
|
763
773
|
key={questao.id}
|
|
764
774
|
questao={questao}
|
|
765
|
-
qIndex={qIndex}
|
|
775
|
+
qIndex={(safePage - 1) * pageSize + qIndex}
|
|
766
776
|
isExpanded={isExpanded}
|
|
767
777
|
onToggleExpand={() => toggleExpand(questao.id)}
|
|
768
778
|
onEdit={() => openEditSheet(questao)}
|
|
@@ -779,6 +789,22 @@ export default function QuestoesPage() {
|
|
|
779
789
|
</SortableContext>
|
|
780
790
|
</DndContext>
|
|
781
791
|
)}
|
|
792
|
+
|
|
793
|
+
{!loading && questoes.length > 0 && (
|
|
794
|
+
<div className="mt-6">
|
|
795
|
+
<PaginationFooter
|
|
796
|
+
currentPage={safePage}
|
|
797
|
+
pageSize={pageSize}
|
|
798
|
+
totalItems={questoes.length}
|
|
799
|
+
onPageChange={setCurrentPage}
|
|
800
|
+
onPageSizeChange={(nextSize) => {
|
|
801
|
+
setPageSize(nextSize);
|
|
802
|
+
setCurrentPage(1);
|
|
803
|
+
}}
|
|
804
|
+
pageSizeOptions={PAGE_SIZE_OPTIONS}
|
|
805
|
+
/>
|
|
806
|
+
</div>
|
|
807
|
+
)}
|
|
782
808
|
</motion.div>
|
|
783
809
|
</div>
|
|
784
810
|
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
EmptyState,
|
|
5
|
+
Page,
|
|
6
|
+
PageHeader,
|
|
7
|
+
PaginationFooter,
|
|
8
|
+
} from '@/components/entity-list';
|
|
4
9
|
import { Badge } from '@/components/ui/badge';
|
|
5
10
|
import { Button } from '@/components/ui/button';
|
|
6
11
|
import { Card, CardContent } from '@/components/ui/card';
|
|
@@ -50,10 +55,6 @@ import {
|
|
|
50
55
|
BarChart3,
|
|
51
56
|
BookOpen,
|
|
52
57
|
CheckCircle2,
|
|
53
|
-
ChevronLeft,
|
|
54
|
-
ChevronRight,
|
|
55
|
-
ChevronsLeft,
|
|
56
|
-
ChevronsRight,
|
|
57
58
|
FileCheck,
|
|
58
59
|
FileQuestion,
|
|
59
60
|
GraduationCap,
|
|
@@ -585,7 +586,7 @@ export default function ExamesPage() {
|
|
|
585
586
|
</div>
|
|
586
587
|
|
|
587
588
|
{/* Search bar */}
|
|
588
|
-
<form onSubmit={handleSearch} className="-mt-
|
|
589
|
+
<form onSubmit={handleSearch} className="mb-6 mt-0">
|
|
589
590
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
|
|
590
591
|
<div className="relative flex-1">
|
|
591
592
|
<Search className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
|
@@ -651,17 +652,14 @@ export default function ExamesPage() {
|
|
|
651
652
|
))}
|
|
652
653
|
</div>
|
|
653
654
|
) : filteredExames.length === 0 ? (
|
|
654
|
-
<
|
|
655
|
-
<FileCheck className="
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
<
|
|
661
|
-
|
|
662
|
-
{t('empty.action')}
|
|
663
|
-
</Button>
|
|
664
|
-
</div>
|
|
655
|
+
<EmptyState
|
|
656
|
+
icon={<FileCheck className="h-12 w-12" />}
|
|
657
|
+
title={t('empty.title')}
|
|
658
|
+
description={t('empty.description')}
|
|
659
|
+
actionLabel={t('empty.action')}
|
|
660
|
+
onAction={openCreateSheet}
|
|
661
|
+
actionIcon={<Plus className="mr-2 h-4 w-4" />}
|
|
662
|
+
/>
|
|
665
663
|
) : (
|
|
666
664
|
<motion.div
|
|
667
665
|
className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"
|
|
@@ -853,87 +851,18 @@ export default function ExamesPage() {
|
|
|
853
851
|
|
|
854
852
|
{/* Pagination footer */}
|
|
855
853
|
{!loading && filteredExames.length > 0 && (
|
|
856
|
-
<div className="mt-6
|
|
857
|
-
<
|
|
858
|
-
{
|
|
859
|
-
{
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
{
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
variant="outline"
|
|
869
|
-
size="icon"
|
|
870
|
-
className="size-8"
|
|
871
|
-
onClick={() => setCurrentPage(1)}
|
|
872
|
-
disabled={safePage === 1}
|
|
873
|
-
aria-label={t('pagination.firstPage')}
|
|
874
|
-
>
|
|
875
|
-
<ChevronsLeft className="size-4" />
|
|
876
|
-
</Button>
|
|
877
|
-
<Button
|
|
878
|
-
variant="outline"
|
|
879
|
-
size="icon"
|
|
880
|
-
className="size-8"
|
|
881
|
-
onClick={() => setCurrentPage((p) => p - 1)}
|
|
882
|
-
disabled={safePage === 1}
|
|
883
|
-
aria-label={t('pagination.previousPage')}
|
|
884
|
-
>
|
|
885
|
-
<ChevronLeft className="size-4" />
|
|
886
|
-
</Button>
|
|
887
|
-
<span className="px-3 text-sm">
|
|
888
|
-
{t('pagination.page')}{' '}
|
|
889
|
-
<span className="font-semibold">{safePage}</span>{' '}
|
|
890
|
-
{t('pagination.of')}{' '}
|
|
891
|
-
<span className="font-semibold">{totalPages}</span>
|
|
892
|
-
</span>
|
|
893
|
-
<Button
|
|
894
|
-
variant="outline"
|
|
895
|
-
size="icon"
|
|
896
|
-
className="size-8"
|
|
897
|
-
onClick={() => setCurrentPage((p) => p + 1)}
|
|
898
|
-
disabled={safePage === totalPages}
|
|
899
|
-
aria-label={t('pagination.nextPage')}
|
|
900
|
-
>
|
|
901
|
-
<ChevronRight className="size-4" />
|
|
902
|
-
</Button>
|
|
903
|
-
<Button
|
|
904
|
-
variant="outline"
|
|
905
|
-
size="icon"
|
|
906
|
-
className="size-8"
|
|
907
|
-
onClick={() => setCurrentPage(totalPages)}
|
|
908
|
-
disabled={safePage === totalPages}
|
|
909
|
-
aria-label={t('pagination.lastPage')}
|
|
910
|
-
>
|
|
911
|
-
<ChevronsRight className="size-4" />
|
|
912
|
-
</Button>
|
|
913
|
-
</div>
|
|
914
|
-
<div className="flex items-center gap-2 text-sm">
|
|
915
|
-
<span className="text-muted-foreground">
|
|
916
|
-
{t('pagination.itemsPerPage')}
|
|
917
|
-
</span>
|
|
918
|
-
<Select
|
|
919
|
-
value={String(pageSize)}
|
|
920
|
-
onValueChange={(v) => {
|
|
921
|
-
setPageSize(Number(v));
|
|
922
|
-
setCurrentPage(1);
|
|
923
|
-
}}
|
|
924
|
-
>
|
|
925
|
-
<SelectTrigger className="h-8 w-16 text-sm">
|
|
926
|
-
<SelectValue />
|
|
927
|
-
</SelectTrigger>
|
|
928
|
-
<SelectContent>
|
|
929
|
-
{PAGE_SIZES.map((s) => (
|
|
930
|
-
<SelectItem key={s} value={String(s)}>
|
|
931
|
-
{s}
|
|
932
|
-
</SelectItem>
|
|
933
|
-
))}
|
|
934
|
-
</SelectContent>
|
|
935
|
-
</Select>
|
|
936
|
-
</div>
|
|
854
|
+
<div className="mt-6">
|
|
855
|
+
<PaginationFooter
|
|
856
|
+
currentPage={safePage}
|
|
857
|
+
pageSize={pageSize}
|
|
858
|
+
totalItems={filteredExames.length}
|
|
859
|
+
onPageChange={setCurrentPage}
|
|
860
|
+
onPageSizeChange={(nextSize) => {
|
|
861
|
+
setPageSize(nextSize);
|
|
862
|
+
setCurrentPage(1);
|
|
863
|
+
}}
|
|
864
|
+
pageSizeOptions={PAGE_SIZES}
|
|
865
|
+
/>
|
|
937
866
|
</div>
|
|
938
867
|
)}
|
|
939
868
|
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
EmptyState,
|
|
5
|
+
Page,
|
|
6
|
+
PageHeader,
|
|
7
|
+
PaginationFooter,
|
|
8
|
+
} from '@/components/entity-list';
|
|
4
9
|
import { Badge } from '@/components/ui/badge';
|
|
5
10
|
import { Button } from '@/components/ui/button';
|
|
6
11
|
import { Card, CardContent } from '@/components/ui/card';
|
|
@@ -46,10 +51,6 @@ import {
|
|
|
46
51
|
AlertTriangle,
|
|
47
52
|
BarChart3,
|
|
48
53
|
BookOpen,
|
|
49
|
-
ChevronLeft,
|
|
50
|
-
ChevronRight,
|
|
51
|
-
ChevronsLeft,
|
|
52
|
-
ChevronsRight,
|
|
53
54
|
Clock,
|
|
54
55
|
Eye,
|
|
55
56
|
FileCheck,
|
|
@@ -604,7 +605,7 @@ export default function TrainingPage() {
|
|
|
604
605
|
</div>
|
|
605
606
|
|
|
606
607
|
{/* Search bar */}
|
|
607
|
-
<form onSubmit={handleSearch} className="-mt-
|
|
608
|
+
<form onSubmit={handleSearch} className="mb-6 mt-0">
|
|
608
609
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
|
|
609
610
|
<div className="relative flex-1">
|
|
610
611
|
<Search className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
|
@@ -689,17 +690,14 @@ export default function TrainingPage() {
|
|
|
689
690
|
))}
|
|
690
691
|
</div>
|
|
691
692
|
) : filteredFormacoes.length === 0 ? (
|
|
692
|
-
<
|
|
693
|
-
<GraduationCap className="
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
<
|
|
699
|
-
|
|
700
|
-
{t('empty.action')}
|
|
701
|
-
</Button>
|
|
702
|
-
</div>
|
|
693
|
+
<EmptyState
|
|
694
|
+
icon={<GraduationCap className="h-12 w-12" />}
|
|
695
|
+
title={t('empty.title')}
|
|
696
|
+
description={t('empty.description')}
|
|
697
|
+
actionLabel={t('empty.action')}
|
|
698
|
+
onAction={openCreateSheet}
|
|
699
|
+
actionIcon={<Plus className="mr-2 h-4 w-4" />}
|
|
700
|
+
/>
|
|
703
701
|
) : (
|
|
704
702
|
<motion.div
|
|
705
703
|
className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"
|
|
@@ -719,7 +717,7 @@ export default function TrainingPage() {
|
|
|
719
717
|
return (
|
|
720
718
|
<motion.div key={formacao.id} variants={fadeUp}>
|
|
721
719
|
<Card
|
|
722
|
-
className="group relative cursor-pointer overflow-hidden transition-all duration-200 hover
|
|
720
|
+
className="group relative flex min-h-60 max-h-[270px] cursor-pointer flex-col overflow-hidden transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md"
|
|
723
721
|
onClick={() => handleCardClick(formacao)}
|
|
724
722
|
title={t('cards.tooltip')}
|
|
725
723
|
>
|
|
@@ -840,86 +838,18 @@ export default function TrainingPage() {
|
|
|
840
838
|
|
|
841
839
|
{/* Pagination footer */}
|
|
842
840
|
{!loading && filteredFormacoes.length > 0 && (
|
|
843
|
-
<div className="mt-6
|
|
844
|
-
<
|
|
845
|
-
{
|
|
846
|
-
{
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
{
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
size="icon"
|
|
856
|
-
className="size-8"
|
|
857
|
-
onClick={() => setCurrentPage(1)}
|
|
858
|
-
disabled={safePage === 1}
|
|
859
|
-
aria-label={t('pagination.firstPage')}
|
|
860
|
-
>
|
|
861
|
-
<ChevronsLeft className="size-4" />
|
|
862
|
-
</Button>
|
|
863
|
-
<Button
|
|
864
|
-
variant="outline"
|
|
865
|
-
size="icon"
|
|
866
|
-
className="size-8"
|
|
867
|
-
onClick={() => setCurrentPage((p) => p - 1)}
|
|
868
|
-
disabled={safePage === 1}
|
|
869
|
-
aria-label={t('pagination.previousPage')}
|
|
870
|
-
>
|
|
871
|
-
<ChevronLeft className="size-4" />
|
|
872
|
-
</Button>
|
|
873
|
-
<span className="px-3 text-sm">
|
|
874
|
-
{t('pagination.page')}{' '}
|
|
875
|
-
<span className="font-semibold">{safePage}</span>{' '}
|
|
876
|
-
{t('pagination.of')}{' '}
|
|
877
|
-
<span className="font-semibold">{totalPages}</span>
|
|
878
|
-
</span>
|
|
879
|
-
<Button
|
|
880
|
-
variant="outline"
|
|
881
|
-
size="icon"
|
|
882
|
-
className="size-8"
|
|
883
|
-
onClick={() => setCurrentPage((p) => p + 1)}
|
|
884
|
-
disabled={safePage === totalPages}
|
|
885
|
-
aria-label={t('pagination.nextPage')}
|
|
886
|
-
>
|
|
887
|
-
<ChevronRight className="size-4" />
|
|
888
|
-
</Button>
|
|
889
|
-
<Button
|
|
890
|
-
variant="outline"
|
|
891
|
-
size="icon"
|
|
892
|
-
className="size-8"
|
|
893
|
-
onClick={() => setCurrentPage(totalPages)}
|
|
894
|
-
disabled={safePage === totalPages}
|
|
895
|
-
aria-label={t('pagination.lastPage')}
|
|
896
|
-
>
|
|
897
|
-
<ChevronsRight className="size-4" />
|
|
898
|
-
</Button>
|
|
899
|
-
</div>
|
|
900
|
-
<div className="flex items-center gap-2 text-sm">
|
|
901
|
-
<span className="text-muted-foreground">
|
|
902
|
-
{t('pagination.itemsPerPage')}
|
|
903
|
-
</span>
|
|
904
|
-
<Select
|
|
905
|
-
value={String(pageSize)}
|
|
906
|
-
onValueChange={(v) => {
|
|
907
|
-
setPageSize(Number(v));
|
|
908
|
-
setCurrentPage(1);
|
|
909
|
-
}}
|
|
910
|
-
>
|
|
911
|
-
<SelectTrigger className="h-8 w-16 text-sm">
|
|
912
|
-
<SelectValue />
|
|
913
|
-
</SelectTrigger>
|
|
914
|
-
<SelectContent>
|
|
915
|
-
{PAGE_SIZES.map((s) => (
|
|
916
|
-
<SelectItem key={s} value={String(s)}>
|
|
917
|
-
{s}
|
|
918
|
-
</SelectItem>
|
|
919
|
-
))}
|
|
920
|
-
</SelectContent>
|
|
921
|
-
</Select>
|
|
922
|
-
</div>
|
|
841
|
+
<div className="mt-6">
|
|
842
|
+
<PaginationFooter
|
|
843
|
+
currentPage={safePage}
|
|
844
|
+
pageSize={pageSize}
|
|
845
|
+
totalItems={filteredFormacoes.length}
|
|
846
|
+
onPageChange={setCurrentPage}
|
|
847
|
+
onPageSizeChange={(nextSize) => {
|
|
848
|
+
setPageSize(nextSize);
|
|
849
|
+
setCurrentPage(1);
|
|
850
|
+
}}
|
|
851
|
+
pageSizeOptions={PAGE_SIZES}
|
|
852
|
+
/>
|
|
923
853
|
</div>
|
|
924
854
|
)}
|
|
925
855
|
|
|
@@ -1083,7 +1013,7 @@ export default function TrainingPage() {
|
|
|
1083
1013
|
{availableCursos.map((c) => (
|
|
1084
1014
|
<label
|
|
1085
1015
|
key={c.id}
|
|
1086
|
-
className="flex cursor-pointer items-center justify-between border-b p-2.5 last:border-0 hover:bg-muted has-
|
|
1016
|
+
className="flex cursor-pointer items-center justify-between border-b p-2.5 last:border-0 hover:bg-muted has-checked:bg-muted/50"
|
|
1087
1017
|
>
|
|
1088
1018
|
<div className="flex items-center gap-2">
|
|
1089
1019
|
<Checkbox
|