@hed-hog/lms 0.0.279 → 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.
@@ -1,6 +1,11 @@
1
1
  'use client';
2
2
 
3
- import { Page, PageHeader } from '@/components/entity-list';
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-2 grid grid-cols-2 gap-4 lg:grid-cols-4">
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-2 mt-0">
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
- <div className="flex flex-col items-center justify-center py-20 text-center">
966
- <BookOpen className="mb-4 size-12 text-muted-foreground/40" />
967
- <p className="text-lg font-medium">{t('table.empty.title')}</p>
968
- <p className="mt-1 text-sm text-muted-foreground">
969
- {t('table.empty.description')}
970
- </p>
971
- <Button className="mt-6 gap-2" onClick={openCreateSheet}>
972
- <Plus className="size-4" />
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-gradient-to-r from-amber-50 to-orange-50 px-2.5 py-0.5 text-[11px] font-medium text-amber-700 border border-amber-200/60 shadow-sm">
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 flex flex-col items-center justify-between gap-4 sm:flex-row">
1156
- {/* Total count */}
1157
- <p className="text-sm text-muted-foreground">
1158
- {filteredCursos.length}{' '}
1159
- {filteredCursos.length === 1
1160
- ? t('pagination.course')
1161
- : t('pagination.courses')}{' '}
1162
- {filteredCursos.length === 1
1163
- ? t('pagination.found')
1164
- : t('pagination.foundPlural')}
1165
- </p>
1166
-
1167
- {/* Center: page nav */}
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-[:checked]:border-foreground has-[:checked]:bg-muted"
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 { Page, PageHeader } from '@/components/entity-list';
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
- <Card>
734
- <CardContent className="flex flex-col items-center gap-3 py-16">
735
- <div className="flex size-12 items-center justify-center rounded-full bg-muted">
736
- <FileCheck className="size-6 text-muted-foreground" />
737
- </div>
738
- <p className="font-medium">{t('empty.title')}</p>
739
- <p className="text-sm text-muted-foreground">
740
- {t('empty.description')}
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
- {questoes.map((questao, qIndex) => {
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 { Page, PageHeader } from '@/components/entity-list';
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-4 mb-2">
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
- <div className="flex flex-col items-center justify-center py-20 text-center">
655
- <FileCheck className="mb-4 size-12 text-muted-foreground/40" />
656
- <p className="text-lg font-medium">{t('empty.title')}</p>
657
- <p className="mt-1 text-sm text-muted-foreground">
658
- {t('empty.description')}
659
- </p>
660
- <Button className="mt-6 gap-2" onClick={openCreateSheet}>
661
- <Plus className="size-4" />
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 flex flex-col items-center justify-between gap-4 sm:flex-row">
857
- <p className="text-sm text-muted-foreground">
858
- {filteredExames.length}{' '}
859
- {filteredExames.length !== 1
860
- ? t('pagination.examsPlural')
861
- : t('pagination.exams')}{' '}
862
- {filteredExames.length !== 1
863
- ? t('pagination.foundPlural')
864
- : t('pagination.found')}
865
- </p>
866
- <div className="flex items-center gap-1">
867
- <Button
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 { Page, PageHeader } from '@/components/entity-list';
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-4 mb-2">
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
- <div className="flex flex-col items-center justify-center py-20 text-center">
693
- <GraduationCap className="mb-4 size-12 text-muted-foreground/40" />
694
- <p className="text-lg font-medium">{t('empty.title')}</p>
695
- <p className="mt-1 text-sm text-muted-foreground">
696
- {t('empty.description')}
697
- </p>
698
- <Button className="mt-6 gap-2" onClick={openCreateSheet}>
699
- <Plus className="size-4" />
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:shadow-md hover:-translate-y-0.5 min-h-[240px] max-h-[270px] flex flex-col"
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 flex flex-col items-center justify-between gap-4 sm:flex-row">
844
- <p className="text-sm text-muted-foreground">
845
- {filteredFormacoes.length}{' '}
846
- {filteredFormacoes.length !== 1
847
- ? t('pagination.formacoes')
848
- : t('pagination.formacao')}{' '}
849
- {t('pagination.found')}
850
- {filteredFormacoes.length !== 1 ? t('pagination.foundPlural') : ''}
851
- </p>
852
- <div className="flex items-center gap-1">
853
- <Button
854
- variant="outline"
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-[:checked]:bg-muted/50"
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