[MVPTN-88] Backup de código

This commit is contained in:
Keven Willian Pereira de Souza 2025-09-25 11:35:35 -03:00
parent 2fd7af509f
commit 920b9d8aa6
6 changed files with 760 additions and 11 deletions

46
package-lock.json generated
View file

@ -23,6 +23,7 @@
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8",
"@tanstack/react-table": "^8.21.3",
"@tinymce/tinymce-react": "^6.3.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@ -1985,6 +1986,39 @@
"tailwindcss": "4.1.12"
}
},
"node_modules/@tanstack/react-table": {
"version": "8.21.3",
"resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz",
"integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==",
"license": "MIT",
"dependencies": {
"@tanstack/table-core": "8.21.3"
},
"engines": {
"node": ">=12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
}
},
"node_modules/@tanstack/table-core": {
"version": "8.21.3",
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz",
"integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tinymce/tinymce-react": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@tinymce/tinymce-react/-/tinymce-react-6.3.0.tgz",
@ -2286,12 +2320,6 @@
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT"
},
"node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
@ -2301,6 +2329,12 @@
"node": ">=14"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT"
},
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",

View file

@ -24,6 +24,7 @@
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8",
"@tanstack/react-table": "^8.21.3",
"@tinymce/tinymce-react": "^6.3.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",

View file

@ -0,0 +1,16 @@
export default function GetNameInitials(data: string): string {
if (!data) return "";
// Remove espaços extras no início e no fim e divide em palavras
const palavras = data.trim().split(/\s+/);
if (palavras.length === 0) return "";
if (palavras.length === 1) {
// Apenas uma palavra → retorna as duas primeiras letras
return palavras[0].substring(0, 2).toUpperCase();
}
// Duas ou mais palavras → retorna a primeira letra das duas primeiras palavras
return (palavras[0].charAt(0) + palavras[1].charAt(0)).toUpperCase();
}

View file

@ -0,0 +1,249 @@
"use client";
import * as React from "react";
import {
ColumnDef,
useReactTable,
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
getFilteredRowModel,
SortingState,
ColumnFiltersState,
VisibilityState,
RowSelectionState,
flexRender,
} from "@tanstack/react-table";
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from "@/components/ui/table";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuLabel,
DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
import { Checkbox } from "@/components/ui/checkbox";
import { MoreHorizontal, ArrowUpDown } from "lucide-react";
// Define o tipo dos dados
type User = {
id: string;
name: string;
email: string;
role: "admin" | "user" | "guest";
status: "active" | "inactive";
};
// Exemplo de dados
const users: User[] = [
{ id: "1", name: "Alice", email: "alice@example.com", role: "admin", status: "active" },
{ id: "2", name: "Bob", email: "bob@example.com", role: "user", status: "inactive" },
{ id: "3", name: "Carol", email: "carol@example.com", role: "user", status: "active" },
{ id: "4", name: "Dave", email: "dave@example.com", role: "guest", status: "active" },
// ... mais
];
// Definição de colunas
const columns: ColumnDef<User>[] = [
{
id: "select",
header: ({ table }) => (
<Checkbox
checked={table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && "indeterminate")}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all rows"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label={`Select row ${row.index}`}
/>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "name",
header: "Name",
cell: ({ row }) => <span>{row.getValue("name")}</span>,
},
{
accessorKey: "email",
header: ({ column }) => (
<Button variant="ghost" onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}>
Email <ArrowUpDown className="ml-1 h-4 w-4" />
</Button>
),
cell: ({ row }) => <span className="lowercase">{row.getValue("email")}</span>,
},
{
accessorKey: "role",
header: "Role",
cell: ({ row }) => <span>{row.getValue("role")}</span>,
},
{
accessorKey: "status",
header: "Status",
cell: ({ row }) => (
<span className={row.getValue("status") === "active" ? "text-green-600" : "text-red-600"}>
{row.getValue("status")}
</span>
),
},
{
id: "actions",
header: "Actions",
cell: ({ row }) => {
const user = row.original;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Open actions</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem onClick={() => alert(`Editar usuário ${user.id}`)}>Edit</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert(`Deletar usuário ${user.id}`)}>Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
},
enableHiding: false,
},
];
export default function UsersTable() {
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
const table = useReactTable({
data: users,
columns,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
},
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
});
return (
<div className="p-4">
{/* Filtro */}
<div className="flex items-center mb-4 space-x-2">
<Input
placeholder="Buscar por email..."
value={(table.getColumn("email")?.getFilterValue() as string) ?? ""}
onChange={(e) => table.getColumn("email")?.setFilterValue(e.target.value)}
className="max-w-sm"
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto">
Colunas visíveis
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((col) => col.getCanHide())
.map((col) => (
<DropdownMenuCheckboxItem
key={col.id}
checked={col.getIsVisible()}
onCheckedChange={(v) => col.toggleVisibility(!!v)}
>
{col.id}
</DropdownMenuCheckboxItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
{/* Tabela */}
<div className="overflow-hidden rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
Nenhum resultado encontrado.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
{/* Paginação */}
<div className="flex items-center justify-end space-x-2 mt-4">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Anterior
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Próxima
</Button>
</div>
{/* Quantidade de linhas selecionadas */}
<div className="mt-2 text-sm text-gray-600">
{Object.keys(rowSelection).length} de {table.getFilteredRowModel().rows.length} selecionadas
</div>
</div>
);
}

View file

@ -0,0 +1,174 @@
'use client';
import { useEffect, useState, useCallback } from "react";
import { Card, CardContent } from "@/components/ui/card";
import Loading from "@/app/_components/loading/loading";
import TPessoaTable from "../../../_components/t_pessoa/TPessoaTable";
import TPessoaForm from "../../../_components/t_pessoa/TPessoaForm";
import { useTPessoaIndexHook } from "../../../_hooks/t_pessoa/useTPessoaIndexHook";
import { useTPessoaSaveHook } from "../../../_hooks/t_pessoa/useTPessoaSaveHook";
import { useTPessoaDeleteHook } from "../../../_hooks/t_pessoa/useTPessoaDeleteHook";
import ConfirmDialog from "@/app/_components/confirm_dialog/ConfirmDialog";
import { useConfirmDialog } from "@/app/_components/confirm_dialog/useConfirmDialog";
import TPessoaInterface from "../../../_interfaces/TPessoaInterface";
import Header from "@/app/_components/structure/Header";
export default function TPessoaFisica() {
// Controle de estado do botão
const [buttonIsLoading, setButtonIsLoading] = useState(false);
// Hooks para leitura e salvamento
const { tPessoa, fetchTPessoa } = useTPessoaIndexHook();
const { saveTCensec } = useTPessoaSaveHook();
const { deleteTCensec } = useTPessoaDeleteHook();
// Estados
const [selectedAndamento, setSelectedAndamento] = useState<TPessoaInterface | null>(null);
const [isFormOpen, setIsFormOpen] = useState(false);
// Estado para saber qual item será deletado
const [itemToDelete, setItemToDelete] = useState<TPessoaInterface | null>(null);
/**
* Hook do modal de confirmação
*/
const {
isOpen: isConfirmOpen,
openDialog: openConfirmDialog,
handleConfirm,
handleCancel,
} = useConfirmDialog();
/**
* Abre o formulário no modo de edição ou criação
*/
const handleOpenForm = useCallback((data: TPessoaInterface | null) => {
setSelectedAndamento(data);
setIsFormOpen(true);
}, []);
/**
* Fecha o formulário e limpa o andamento selecionado
*/
const handleCloseForm = useCallback(() => {
setSelectedAndamento(null);
setIsFormOpen(false);
}, []);
/**
* Salva os dados do formulário
*/
const handleSave = useCallback(async (formData: TPessoaInterface) => {
// Coloca o botão em estado de loading
setButtonIsLoading(true);
// Aguarda salvar o registro
await saveTCensec(formData);
// Remove o botão em estado de loading
setButtonIsLoading(false);
// Atualiza a lista de dados
fetchTPessoa();
}, [saveTCensec, fetchTPessoa, handleCloseForm]);
/**
* Quando o usuário clica em "remover" na tabela
*/
const handleConfirmDelete = useCallback((item: TPessoaInterface) => {
// Define o item atual para remoção
setItemToDelete(item);
// Abre o modal de confirmação
openConfirmDialog();
}, [openConfirmDialog]);
/**
* Executa a exclusão de fato quando o usuário confirma
*/
const handleDelete = useCallback(async () => {
// Protege contra null
if (!itemToDelete) return;
// Executa o Hook de remoção
await deleteTCensec(itemToDelete);
// Atualiza a lista
await fetchTPessoa();
// Limpa o item selecionado
setItemToDelete(null);
// Fecha o modal
handleCancel();
}, [itemToDelete, fetchTPessoa, handleCancel]);
/**
* Busca inicial dos dados
*/
useEffect(() => {
fetchTPessoa();
}, []);
/**
* Tela de loading enquanto carrega os dados
*/
if (tPessoa.length == 0) {
return <Loading type={2} />;
}
return (
<div>
{/* Cabeçalho */}
<Header
title={"Pessoas Físicas"}
description={"Gerenciamento de pessoas físicas"}
buttonText={"Nova Pessoa"}
buttonAction={() => { handleOpenForm(null) }}
/>
{/* Tabela de andamentos */}
<Card>
<CardContent>
<TPessoaTable
data={tPessoa}
onEdit={handleOpenForm}
onDelete={handleConfirmDelete}
/>
</CardContent>
</Card>
{/* Modal de confirmação */}
<ConfirmDialog
isOpen={isConfirmOpen}
title="Confirmar exclusão"
description="Atenção"
message={`Deseja realmente excluir o andamento "${itemToDelete?.nome}"?`}
confirmText="Sim, excluir"
cancelText="Cancelar"
onConfirm={handleDelete}
onCancel={handleCancel}
/>
{/* Formulário de criação/edição */}
<TPessoaForm
isOpen={isFormOpen}
data={selectedAndamento}
onClose={handleCloseForm}
onSave={handleSave}
buttonIsLoading={buttonIsLoading}
/>
</div>
); 4
}

View file

@ -1,6 +1,6 @@
'use client';
import { useEffect, useState, useCallback } from "react";
import React, { useEffect, useState, useCallback } from "react";
import { Card, CardContent } from "@/components/ui/card";
import Loading from "@/app/_components/loading/loading";
@ -16,6 +16,159 @@ import { useConfirmDialog } from "@/app/_components/confirm_dialog/useConfirmDia
import TPessoaInterface from "../../../_interfaces/TPessoaInterface";
import Header from "@/app/_components/structure/Header";
import { ColumnDef, ColumnFiltersState, flexRender, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, RowSelectionState, SortingState, useReactTable, VisibilityState } from "@tanstack/react-table";
import { Checkbox } from "@/components/ui/checkbox";
import { Button } from "@/components/ui/button";
import { ArrowUpDownIcon, EllipsisIcon, MoreHorizontalIcon, PencilIcon, Trash2Icon } from "lucide-react";
import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { TPessoaIndexData } from "../../../_data/TPessoa/TPessoaIndexData";
import { Input } from "@/components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import GetNameInitials from "@/actions/text/GetNameInitials";
const pessoas = await TPessoaIndexData();
// Definição de colunas
const columns: ColumnDef<TPessoaInterface>[] = [
{
id: "select",
header: ({ table }) => (
<Checkbox
checked={table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && "indeterminate")}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all rows"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label={`Select row ${row.index}`}
/>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "#",
header: ({ column }) => (
<Button variant="ghost" onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}>
# <ArrowUpDownIcon className="ml-1 h-4 w-4" />
</Button>
),
cell: ({ row }) => <span>{row.getValue("pessoa_id") || ''}</span>,
},
{
accessorKey: "foto_nome_email",
header: ({ column }) => (
<Button variant="ghost" onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}>
Nome <ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => (
<div className="flex items-center gap-3">
{/* Avatar */}
<div className="w-10 h-10 rounded-full bg-gray-200 flex items-center justify-center overflow-hidden">
{row.getValue("foto") ? (
<img
src={row.getValue("foto")}
alt={row.getValue("nome") || "Avatar"}
className="w-full h-full object-cover"
/>
) : (
<span className="text-sm font-medium text-gray-700">
{GetNameInitials(row.getValue("nome"))}
</span>
)}
</div>
{/* Nome e Email */}
<div>
<div className="font-semibold text-gray-900">
{row.getValue("nome") || "-"}
</div>
<div className="text-sm text-gray-500">
{row.getValue("email") || "-"}
</div>
</div>
</div>
),
},
{
accessorKey: "cpf",
header: ({ column }) => (
<Button variant="ghost" onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}>
CPF <ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => (
row.getValue("cpf_cnpj")
),
},
{
accessorKey: "telefone",
header: "Telefone",
cell: ({ row }) => (
<span>{row.getValue("telefone")}</span>
),
},
{
accessorKey: "cidade_uf",
header: "Cidade/UF",
cell: ({ row }) => (
row.getValue("cidade") + "/" + row.getValue("uf")
),
},
{
accessorKey: "data_cadastro",
header: "Cadastro",
cell: ({ row }) => (
row.getValue("data_cadastro")
),
},
{
id: "actions",
header: "Actions",
cell: ({ row }) => {
const pessoa = row.original;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="cursor-pointer"
>
<EllipsisIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent side="left" align="start">
<DropdownMenuGroup>
<DropdownMenuItem
className="cursor-pointer"
onSelect={() => { }}
>
<PencilIcon className="mr-2 h-4 w-4" />
Editar
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="cursor-pointer text-red-600"
onSelect={() => { }}
>
<Trash2Icon className="mr-2 h-4 w-4" />
Remover
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
);
},
enableHiding: false,
},
];
export default function TPessoaFisica() {
@ -121,6 +274,31 @@ export default function TPessoaFisica() {
fetchTPessoa();
}, []);
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
const table = useReactTable({
data: pessoas.data,
columns,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
},
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
});
/**
* Tela de loading enquanto carrega os dados
*/
@ -132,9 +310,9 @@ export default function TPessoaFisica() {
<div>
{/* Cabeçalho */}
<Header
title={"CENSEC"}
description={"Gerenciamento de Centrais"}
buttonText={"Nova Central"}
title={"Pessoas Físicas"}
description={"Gerenciamento de pessoas físicas"}
buttonText={"Nova Pessoa"}
buttonAction={() => { handleOpenForm(null) }}
/>
@ -149,6 +327,103 @@ export default function TPessoaFisica() {
</CardContent>
</Card>
<div className="p-4">
{/* Filtro */}
<div className="flex items-center mb-4 space-x-2">
<Input
placeholder="Buscar por email..."
value={(table.getColumn("foto_nome_email")?.getFilterValue() as string) ?? ""}
onChange={(e) => table.getColumn("foto_nome_email")?.setFilterValue(e.target.value)}
className="max-w-sm"
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto">
Colunas visíveis
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((col) => col.getCanHide())
.map((col) => (
<DropdownMenuCheckboxItem
key={col.id}
checked={col.getIsVisible()}
onCheckedChange={(v) => col.toggleVisibility(!!v)}
>
{col.id}
</DropdownMenuCheckboxItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
{/* Tabela */}
<div className="overflow-hidden rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
Nenhum resultado encontrado.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
{/* Paginação */}
<div className="flex items-center justify-end space-x-2 mt-4">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Anterior
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Próxima
</Button>
</div>
{/* Quantidade de linhas selecionadas */}
<div className="mt-2 text-sm text-gray-600">
{Object.keys(rowSelection).length} de {table.getFilteredRowModel().rows.length} selecionadas
</div>
</div>
{/* Modal de confirmação */}
<ConfirmDialog
isOpen={isConfirmOpen}
@ -170,5 +445,5 @@ export default function TPessoaFisica() {
buttonIsLoading={buttonIsLoading}
/>
</div>
); 4
);
}