[MVPTN-88] Backup de código
This commit is contained in:
parent
2fd7af509f
commit
920b9d8aa6
6 changed files with 760 additions and 11 deletions
46
package-lock.json
generated
46
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
16
src/actions/text/GetNameInitials.ts
Normal file
16
src/actions/text/GetNameInitials.ts
Normal 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();
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue