[MVPTN-99] feat(CRUD): Inicio do formulário de cadastro de pessoa juridica com representantes

This commit is contained in:
Keven Willian Pereira de Souza 2025-10-01 11:15:02 -03:00
parent fb35c19a84
commit b91ce54475
57 changed files with 16748 additions and 7984 deletions

View file

@ -5,19 +5,24 @@ export default [
next,
{
plugins: {
import: importPlugin
import: importPlugin,
},
rules: {
"import/order": [
"error",
{
groups: ["builtin", "external", "internal", ["parent", "sibling", "index"]],
groups: [
"builtin",
"external",
"internal",
["parent", "sibling", "index"],
],
"newlines-between": "always",
alphabetize: { order: "asc", caseInsensitive: true }
}
alphabetize: { order: "asc", caseInsensitive: true },
},
],
"semi": ["error", "always"],
"quotes": ["error", "double"]
}
}
semi: ["error", "always"],
quotes: ["error", "double"],
},
},
];

940
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -55,8 +55,16 @@
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@typescript-eslint/eslint-plugin": "^8.45.0",
"@typescript-eslint/parser": "^8.45.0",
"eslint": "^8.57.1",
"eslint-config-next": "^15.5.4",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-unused-imports": "^4.2.0",
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.6.14",
"tailwindcss": "^4",
"tw-animate-css": "^1.3.7",
"typescript": "^5"

View file

@ -0,0 +1,23 @@
/**
* Formata um número de CNPJ no padrão 99.999.999/9999-99
*
* @param value - CNPJ em string ou number
* @returns CNPJ formatado ou string parcial se incompleto
*/
export function FormatCNPJ(value: string | number): string {
if (!value) return "";
// Converte para string e remove tudo que não seja número
const digits = String(value).replace(/\D/g, "");
// Garante que tenha no máximo 14 dígitos
const cleanValue = digits.slice(0, 14);
// Retorna parcialmente formatado se ainda não tiver 14 dígitos
if (cleanValue.length < 14) return cleanValue;
return cleanValue.replace(
/(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/,
"$1.$2.$3/$4-$5",
);
}

View file

@ -1,23 +1,20 @@
/**
* Formata um número de CPF no padrão 999.999.999-99
*
*
* @param value - CPF em string ou number
* @returns CPF formatado ou string vazia se inválido
*/
export function FormatCPF(value: string | number): string {
if (!value) return "";
if (!value) return "";
// Converte para string e remove tudo que não seja número
const digits = String(value).replace(/\D/g, "");
// Converte para string e remove tudo que não seja número
const digits = String(value).replace(/\D/g, "");
// Garante que tenha no máximo 11 dígitos
const cleanValue = digits.slice(0, 11);
// Garante que tenha no máximo 11 dígitos
const cleanValue = digits.slice(0, 11);
// Retorna formatado ou vazio se não tiver tamanho suficiente
if (cleanValue.length !== 11) return cleanValue;
// Retorna formatado ou vazio se não tiver tamanho suficiente
if (cleanValue.length !== 11) return cleanValue;
return cleanValue.replace(
/(\d{3})(\d{3})(\d{3})(\d{2})/,
"$1.$2.$3-$4"
);
return cleanValue.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, "$1.$2.$3-$4");
}

View file

@ -1,53 +1,55 @@
/**
* Formata uma data e hora brasileira (DD/MM/YYYY HH:mm)
*
*
* Suporta:
* - Entrada como string, Date ou number (timestamp)
* - Dados incompletos (apenas dia/mês, sem hora, etc.)
* - Retorna "-" se vazio ou inválido
*
*
* @param value - Data ou hora em string, Date ou timestamp
* @returns Data formatada no padrão DD/MM/YYYY HH:mm ou parcial
*/
export function FormatDateTime(value: string | Date | number | null | undefined): string {
if (!value) return "-";
export function FormatDateTime(
value: string | Date | number | null | undefined,
): string {
if (!value) return "-";
let date: Date;
let date: Date;
// Converte entrada para Date
if (value instanceof Date) {
date = value;
} else if (typeof value === "number") {
date = new Date(value);
} else if (typeof value === "string") {
// Remove caracteres extras e tenta criar Date
const cleanValue = value.trim().replace(/[^0-9]/g, "");
// Converte entrada para Date
if (value instanceof Date) {
date = value;
} else if (typeof value === "number") {
date = new Date(value);
} else if (typeof value === "string") {
// Remove caracteres extras e tenta criar Date
const cleanValue = value.trim().replace(/[^0-9]/g, "");
if (cleanValue.length === 8) {
// DDMMYYYY
const day = parseInt(cleanValue.slice(0, 2), 10);
const month = parseInt(cleanValue.slice(2, 4), 10) - 1;
const year = parseInt(cleanValue.slice(4, 8), 10);
date = new Date(year, month, day);
} else {
// Tenta parse padrão
const parsed = new Date(value);
if (isNaN(parsed.getTime())) return "-";
date = parsed;
}
if (cleanValue.length === 8) {
// DDMMYYYY
const day = parseInt(cleanValue.slice(0, 2), 10);
const month = parseInt(cleanValue.slice(2, 4), 10) - 1;
const year = parseInt(cleanValue.slice(4, 8), 10);
date = new Date(year, month, day);
} else {
return "-";
// Tenta parse padrão
const parsed = new Date(value);
if (isNaN(parsed.getTime())) return "-";
date = parsed;
}
} else {
return "-";
}
// Extrai partes da data
const day = date.getDate().toString().padStart(2, "0");
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const year = date.getFullYear();
const hours = date.getHours().toString().padStart(2, "0");
const minutes = date.getMinutes().toString().padStart(2, "0");
// Extrai partes da data
const day = date.getDate().toString().padStart(2, "0");
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const year = date.getFullYear();
const hours = date.getHours().toString().padStart(2, "0");
const minutes = date.getMinutes().toString().padStart(2, "0");
// Monta string parcialmente, dependendo da hora estar disponível
const hasTime = !(hours === "00" && minutes === "00");
// Monta string parcialmente, dependendo da hora estar disponível
const hasTime = !(hours === "00" && minutes === "00");
return `${day}/${month}/${year}${hasTime ? ` ${hours}:${minutes}` : ""}`;
return `${day}/${month}/${year}${hasTime ? ` ${hours}:${minutes}` : ""}`;
}

View file

@ -1,47 +1,51 @@
/**
* Formata um número de telefone brasileiro.
*
*
* Suporta:
* - Com ou sem DDD
* - Números incompletos
* - Telefones com 8 ou 9 dígitos
*
*
* @param value - Número de telefone em string ou number
* @returns Telefone formatado ou "-" se vazio
*/
export function FormatPhone(value: string | number): string {
if (!value) return "-";
if (!value) return "-";
// Converte para string e remove tudo que não for número
const digits = String(value).replace(/\D/g, "");
// Converte para string e remove tudo que não for número
const digits = String(value).replace(/\D/g, "");
// Se não tiver nada após limpar, retorna "-"
if (digits.length === 0) return "-";
// Se não tiver nada após limpar, retorna "-"
if (digits.length === 0) return "-";
// Garante no máximo 11 dígitos
const cleanValue = digits.slice(0, 11);
// Garante no máximo 11 dígitos
const cleanValue = digits.slice(0, 11);
// -------------------------------
// SEM DDD
// -------------------------------
if (cleanValue.length <= 8) {
// Até 8 dígitos → formato parcial
return cleanValue.replace(/(\d{4})(\d{0,4})/, "$1-$2").replace(/-$/, "");
}
// -------------------------------
// SEM DDD
// -------------------------------
if (cleanValue.length <= 8) {
// Até 8 dígitos → formato parcial
return cleanValue.replace(/(\d{4})(\d{0,4})/, "$1-$2").replace(/-$/, "");
}
// -------------------------------
// COM DDD
// -------------------------------
if (cleanValue.length === 9 || cleanValue.length === 10) {
// DDD + telefone de 8 dígitos
return cleanValue.replace(/^(\d{2})(\d{4})(\d{0,4})$/, "($1) $2-$3").replace(/-$/, "");
}
// -------------------------------
// COM DDD
// -------------------------------
if (cleanValue.length === 9 || cleanValue.length === 10) {
// DDD + telefone de 8 dígitos
return cleanValue
.replace(/^(\d{2})(\d{4})(\d{0,4})$/, "($1) $2-$3")
.replace(/-$/, "");
}
if (cleanValue.length === 11) {
// DDD + telefone de 9 dígitos
return cleanValue.replace(/^(\d{2})(\d{5})(\d{0,4})$/, "($1) $2-$3").replace(/-$/, "");
}
if (cleanValue.length === 11) {
// DDD + telefone de 9 dígitos
return cleanValue
.replace(/^(\d{2})(\d{5})(\d{0,4})$/, "($1) $2-$3")
.replace(/-$/, "");
}
// Caso genérico, se não cair em nenhuma regra
return cleanValue;
// Caso genérico, se não cair em nenhuma regra
return cleanValue;
}

View file

@ -1,6 +1,6 @@
/**
* Capitaliza a primeira letra de uma string.
*
*
* @param text - Texto que será capitalizado
* @returns String com a primeira letra em maiúscula
*/

View file

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

View file

@ -1,9 +1,9 @@
/**
* Verifica se um valor é vazio, null ou undefined
*
*
* @param data - Qualquer valor
* @returns true se estiver vazio, null ou undefined
*/
export default function empty(data: unknown): boolean {
return data == null || data === "" || data === false;
return data == null || data === "" || data === false;
}

View file

@ -1,4 +1,4 @@
'use client';
"use client";
import React, { useEffect, useState, useCallback } from "react";
@ -17,153 +17,157 @@ 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);
// 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();
// 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);
// 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,
);
// 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();
/**
* 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);
}, []);
/**
* 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);
}, []);
/**
* 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);
/**
* Salva os dados do formulário
*/
const handleSave = useCallback(async (formData: TPessoaInterface) => {
// Aguarda salvar o registro
await saveTCensec(formData);
// Coloca o botão em estado de loading
setButtonIsLoading(true);
// Remove o botão em estado de loading
setButtonIsLoading(false);
// Aguarda salvar o registro
await saveTCensec(formData);
// Atualiza a lista de dados
fetchTPessoa();
},
[saveTCensec, fetchTPessoa, handleCloseForm],
);
// Remove o botão em estado de loading
setButtonIsLoading(false);
/**
* Quando o usuário clica em "remover" na tabela
*/
const handleConfirmDelete = useCallback(
(item: TPessoaInterface) => {
// Define o item atual para remoção
setItemToDelete(item);
// Atualiza a lista de dados
fetchTPessoa();
// Abre o modal de confirmação
openConfirmDialog();
},
[openConfirmDialog],
);
}, [saveTCensec, fetchTPessoa, handleCloseForm]);
/**
* Executa a exclusão de fato quando o usuário confirma
*/
const handleDelete = useCallback(async () => {
// Protege contra null
if (!itemToDelete) return;
/**
* Quando o usuário clica em "remover" na tabela
*/
const handleConfirmDelete = useCallback((item: TPessoaInterface) => {
// Executa o Hook de remoção
await deleteTCensec(itemToDelete);
// Define o item atual para remoção
setItemToDelete(item);
// Atualiza a lista
await fetchTPessoa();
// Abre o modal de confirmação
openConfirmDialog();
// Limpa o item selecionado
setItemToDelete(null);
}, [openConfirmDialog]);
// Fecha o modal
handleCancel();
}, [itemToDelete, fetchTPessoa, handleCancel]);
/**
* Executa a exclusão de fato quando o usuário confirma
*/
const handleDelete = useCallback(async () => {
/**
* Busca inicial dos dados
*/
useEffect(() => {
fetchTPessoa();
}, []);
// Protege contra null
if (!itemToDelete) return;
/**
* Tela de loading enquanto carrega os dados
*/
if (tPessoa.length == 0) {
return <Loading type={2} />;
}
// Executa o Hook de remoção
await deleteTCensec(itemToDelete);
return (
<div>
{/* Cabeçalho */}
<Header
title={"Pessoas Físicas"}
description={"Gerenciamento de pessoas físicas"}
buttonText={"Nova Pessoa"}
buttonAction={() => {
handleOpenForm(null);
}}
/>
// Atualiza a lista
await fetchTPessoa();
{/* Tabela de Registros */}
<TPessoaTable
data={tPessoa}
onDelete={handleConfirmDelete}
onEdit={handleOpenForm}
/>
// Limpa o item selecionado
setItemToDelete(null);
{/* 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}
/>
// 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 Registros */}
<TPessoaTable
data={tPessoa}
onDelete={handleConfirmDelete}
onEdit={handleOpenForm}
/>
{/* 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>
);
}
{/* Formulário de criação/edição */}
<TPessoaForm
isOpen={isFormOpen}
data={selectedAndamento}
onClose={handleCloseForm}
onSave={handleSave}
buttonIsLoading={buttonIsLoading}
/>
</div>
);
}

View file

@ -0,0 +1,173 @@
"use client";
import React, { useEffect, useState, useCallback } from "react";
import Loading from "@/app/_components/loading/loading";
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";
import TPessoaJuridicaTable from "../../../_components/t_pessoa/juridica/TPessoaJuridicaTable";
import { useTPessoaJuridicaIndexHook } from "../../../_hooks/t_pessoa/juridica/useTPessoaJuridicaIndexHook";
import TPessoaJuridicaForm from "../../../_components/t_pessoa/juridica/TPessoaJuridicaForm";
export default function TPessoaFisica() {
// Controle de estado do botão
const [buttonIsLoading, setButtonIsLoading] = useState(false);
// Hooks para leitura e salvamento
const { tPessoa, fetchTPessoa } = useTPessoaJuridicaIndexHook();
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 Jurídicas"}
description={"Gerenciamento de pessoas jurídicas"}
buttonText={"Nova Pessoa"}
buttonAction={() => {
handleOpenForm(null);
}}
/>
{/* Tabela de Registros */}
<TPessoaJuridicaTable
data={tPessoa}
onDelete={handleConfirmDelete}
onEdit={handleOpenForm}
/>
{/* 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 */}
<TPessoaJuridicaForm
isOpen={isFormOpen}
data={selectedAndamento}
onClose={handleCloseForm}
onSave={handleSave}
buttonIsLoading={buttonIsLoading}
/>
</div>
);
}

View file

@ -34,7 +34,7 @@ import {
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
SelectValue,
} from "@/components/ui/select";
// Define o tipo do formulário com base no schema Zod
@ -50,38 +50,34 @@ interface Props {
// Componente principal do formulário
export default function GCidadeForm({ isOpen, data, onClose, onSave }: Props) {
//
const { gUf, fetchGUf } = useGUfReadHook();
//
const { gUf, fetchGUf } = useGUfReadHook();
// Inicializa o react-hook-form integrado ao Zod para validação
const form = useForm<FormValues>({
resolver: zodResolver(GCidadeSchema),
defaultValues: {
cidade_id: 0,
uf: "",
cidade_nome: "",
codigo_ibge: "",
codigo_gyn: "",
},
});
// Inicializa o react-hook-form integrado ao Zod para validação
const form = useForm<FormValues>({
resolver: zodResolver(GCidadeSchema),
defaultValues: {
cidade_id: 0,
uf: "",
cidade_nome: "",
codigo_ibge: "",
codigo_gyn: ""
},
});
// Quando recebe dados para edição, atualiza os valores do formulário
useEffect(() => {
// Se existir dados, reseta o formulário com os dados informados
if (data) form.reset(data);
// Quando recebe dados para edição, atualiza os valores do formulário
useEffect(() => {
const loadData = async () => {
// Aguarda a busca terminar
await fetchGUf();
};
// Se existir dados, reseta o formulário com os dados informados
if (data) form.reset(data);
const loadData = async () => {
// Aguarda a busca terminar
await fetchGUf();
};
// Dispara a função
loadData();
}, [data, form]);
// Dispara a função
loadData();
}, [data, form]);
return (
<Dialog
@ -131,38 +127,38 @@ export default function GCidadeForm({ isOpen, data, onClose, onSave }: Props) {
)}
/>
{/* Tipo */}
<FormField
control={form.control}
name="uf"
render={({ field }) => (
<FormItem>
<FormLabel>
UF
</FormLabel>
<Select
value={String(field.value)}
// Carrega o valor selecionado
onValueChange={(val) => field.onChange(val)}
>
<FormControl className="w-full">
<SelectTrigger>
<SelectValue placeholder="Selecione o estado desejado" />
</SelectTrigger>
</FormControl>
<SelectContent>
{gUf.map((item) => (
<SelectItem key={item.g_uf_id} value={String(item.sigla)}>
{item.nome}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
{/* Tipo */}
<FormField
control={form.control}
name="uf"
render={({ field }) => (
<FormItem>
<FormLabel>UF</FormLabel>
<Select
value={String(field.value)}
// Carrega o valor selecionado
onValueChange={(val) => field.onChange(val)}
>
<FormControl className="w-full">
<SelectTrigger>
<SelectValue placeholder="Selecione o estado desejado" />
</SelectTrigger>
</FormControl>
<SelectContent>
{gUf.map((item) => (
<SelectItem
key={item.g_uf_id}
value={String(item.sigla)}
>
{item.nome}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
{/* Rodapé do diálogo com botões */}
<DialogFooter className="mt-4">

View file

@ -1,16 +1,21 @@
'use client';
"use client";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { ArrowUpDownIcon, EllipsisIcon, PencilIcon, Trash2Icon } from "lucide-react";
import {
ArrowUpDownIcon,
EllipsisIcon,
PencilIcon,
Trash2Icon,
} from "lucide-react";
import { ColumnDef } from "@tanstack/react-table";
import GetNameInitials from "@/actions/text/GetNameInitials";
@ -24,210 +29,205 @@ import empty from "@/actions/validations/empty";
// Tipagem das props
interface TPessoaTableProps {
data: TPessoaInterface[];
onEdit: (item: TPessoaInterface, isEditingFormStatus: boolean) => void;
onDelete: (item: TPessoaInterface, isEditingFormStatus: boolean) => void;
data: TPessoaInterface[];
onEdit: (item: TPessoaInterface, isEditingFormStatus: boolean) => void;
onDelete: (item: TPessoaInterface, isEditingFormStatus: boolean) => void;
}
/**
* Função para criar a definição das colunas da tabela
*/
function createPessoaColumns(
onEdit: (item: TPessoaInterface, isEditingFormStatus: boolean) => void,
onDelete: (item: TPessoaInterface, isEditingFormStatus: boolean) => void
onEdit: (item: TPessoaInterface, isEditingFormStatus: boolean) => void,
onDelete: (item: TPessoaInterface, isEditingFormStatus: boolean) => void,
): ColumnDef<TPessoaInterface>[] {
return [
// ID
{
accessorKey: "pessoa_id",
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
return [
// ID
{
accessorKey: "pessoa_id",
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
# <ArrowUpDownIcon className="ml-1 h-4 w-4" />
</Button>
),
cell: ({ row }) => Number(row.getValue("pessoa_id")),
enableSorting: false,
},
// Nome / Email / Foto
{
id: "nome_completo",
accessorFn: (row) => row,
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Nome / Email{" "}
<ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => {
const pessoa = row.original;
return (
<div className="flex items-center gap-3">
{/* Foto ou Iniciais */}
<div className="w-10 h-10 rounded-full bg-gray-200 flex items-center justify-center overflow-hidden">
{pessoa.foto ? (
<img
src={pessoa.foto}
alt={pessoa.nome || "Avatar"}
className="w-full h-full object-cover"
/>
) : (
<span className="text-sm font-medium text-gray-700">
{GetNameInitials(pessoa.nome)}
</span>
)}
</div>
{/* Nome e Email */}
<div>
<div className="font-semibold text-gray-900 capitalize">
{pessoa.nome || "-"}
</div>
<div className="text-sm text-gray-500">
{empty(pessoa.email) ? "Email não informado" : pessoa.email}
</div>
</div>
</div>
);
},
sortingFn: (a, b) =>
(a.original.nome?.toLowerCase() || "").localeCompare(
b.original.nome?.toLowerCase() || "",
),
},
// CPF
{
accessorKey: "cpf_cnpj",
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 }) => FormatCPF(row.getValue("cpf_cnpj")),
},
// Telefone
{
accessorKey: "telefone",
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Telefone <ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => FormatPhone(row.getValue("telefone")),
},
// Cidade / UF
{
id: "cidade_uf",
accessorFn: (row) => `${row.cidade}/${row.uf}`,
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Cidade/UF <ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => <span>{row.getValue("cidade_uf") || "-"}</span>,
sortingFn: (a, b) =>
`${a.original.cidade}/${a.original.uf}`
.toLowerCase()
.localeCompare(`${b.original.cidade}/${b.original.uf}`.toLowerCase()),
},
// Data de cadastro
{
accessorKey: "data_cadastro",
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Cadastro <ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => FormatDateTime(row.getValue("data_cadastro")),
sortingFn: "datetime",
},
// Ações
{
id: "actions",
header: "Ações",
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={() => onEdit(pessoa, true)}
>
# <ArrowUpDownIcon className="ml-1 h-4 w-4" />
</Button>
),
cell: ({ row }) => Number(row.getValue("pessoa_id")),
enableSorting: false,
},
// Nome / Email / Foto
{
id: "nome_completo",
accessorFn: (row) => row,
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
<PencilIcon className="mr-2 h-4 w-4" />
Editar
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="cursor-pointer text-red-600"
onSelect={() => onDelete(pessoa, true)}
>
Nome / Email <ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => {
const pessoa = row.original;
return (
<div className="flex items-center gap-3">
{/* Foto ou Iniciais */}
<div className="w-10 h-10 rounded-full bg-gray-200 flex items-center justify-center overflow-hidden">
{pessoa.foto ? (
<img
src={pessoa.foto}
alt={pessoa.nome || "Avatar"}
className="w-full h-full object-cover"
/>
) : (
<span className="text-sm font-medium text-gray-700">
{GetNameInitials(pessoa.nome)}
</span>
)}
</div>
{/* Nome e Email */}
<div>
<div className="font-semibold text-gray-900 capitalize">
{pessoa.nome || "-"}
</div>
<div className="text-sm text-gray-500">
{empty(pessoa.email) ? "Email não informado" : pessoa.email}
</div>
</div>
</div>
);
},
sortingFn: (a, b) =>
(a.original.nome?.toLowerCase() || "").localeCompare(
b.original.nome?.toLowerCase() || ""
),
},
// CPF
{
accessorKey: "cpf_cnpj",
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 }) => (
FormatCPF(row.getValue("cpf_cnpj"))
),
},
// Telefone
{
accessorKey: "telefone",
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Telefone <ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => (
FormatPhone(row.getValue("telefone"))
),
},
// Cidade / UF
{
id: "cidade_uf",
accessorFn: (row) => `${row.cidade}/${row.uf}`,
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Cidade/UF <ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => <span>{row.getValue("cidade_uf") || "-"}</span>,
sortingFn: (a, b) =>
`${a.original.cidade}/${a.original.uf}`.toLowerCase()
.localeCompare(`${b.original.cidade}/${b.original.uf}`.toLowerCase()),
},
// Data de cadastro
{
accessorKey: "data_cadastro",
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Cadastro <ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => (
FormatDateTime(row.getValue("data_cadastro"))
),
sortingFn: "datetime",
},
// Ações
{
id: "actions",
header: "Ações",
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={() => onEdit(pessoa, true)}
>
<PencilIcon className="mr-2 h-4 w-4" />
Editar
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="cursor-pointer text-red-600"
onSelect={() => onDelete(pessoa, true)}
>
<Trash2Icon className="mr-2 h-4 w-4" />
Remover
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
);
},
enableSorting: false,
enableHiding: false,
},
];
<Trash2Icon className="mr-2 h-4 w-4" />
Remover
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
);
},
enableSorting: false,
enableHiding: false,
},
];
}
/**
* Componente principal da tabela
*/
export default function TPessoaTable({
data,
onEdit,
onDelete,
data,
onEdit,
onDelete,
}: TPessoaTableProps) {
const columns = createPessoaColumns(onEdit, onDelete);
return (
<div>
<DataTable
data={data}
columns={columns}
filterColumn="nome_completo"
filterPlaceholder="Buscar por nome ou email..."
/>
</div>
);
}
const columns = createPessoaColumns(onEdit, onDelete);
return (
<div>
<DataTable
data={data}
columns={columns}
filterColumn="nome_completo"
filterPlaceholder="Buscar por nome ou email..."
/>
</div>
);
}

View file

@ -0,0 +1,464 @@
"use client";
import z from "zod";
import React, { useEffect } from "react";
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { TPessoaSchema } from "../../../_schemas/TPessoaSchema";
import LoadingButton from "@/app/_components/loadingButton/LoadingButton";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { HouseIcon, IdCardIcon, UserIcon } from "lucide-react";
import { Textarea } from "@/components/ui/textarea";
import { useTPessoaRepresentanteIndexHook } from "../../../_hooks/t_pessoa_representante/useTPessoaRepresentanteIndexHook";
import TPessoaRepresentantePage from "../../t_pessoa_representante/TPessoaRepresentantePage";
type FormValues = z.infer<typeof TPessoaSchema>;
interface TPessoaFormProps {
isOpen: boolean;
data: FormValues | null;
onClose: (item: null, isFormStatus: boolean) => void;
onSave: (data: FormValues) => void;
buttonIsLoading: boolean;
}
export default function TPessoaJuridicaForm({
isOpen,
data,
onClose,
onSave,
buttonIsLoading,
}: TPessoaFormProps) {
const { tPessoaRepresentante, fetchTPessoaRepresentante } =
useTPessoaRepresentanteIndexHook();
// Inicializa o react-hook-form com schema zod
const form = useForm<FormValues>({
resolver: zodResolver(TPessoaSchema),
defaultValues: {
nome: "",
pessoa_id: 0,
},
});
// Atualiza o formulário quando recebe dados para edição
useEffect(() => {
// Carregamento de dados sincronos
const loadData = async () => {
// Se existir dados, reseta o formulário com os dados informados
if (data) form.reset(data);
// Aguarda a busca terminar
await fetchTPessoaRepresentante();
};
// Dispara a função
loadData();
}, [data, form]);
return (
<Dialog
open={isOpen}
onOpenChange={(open) => {
if (!open) onClose(null, false);
}}
>
<DialogContent className="w-full max-w-full sm:max-w-3xl md:max-w-4xl lg:max-w-5xl p-6">
<DialogHeader>
<DialogTitle>Pessoa</DialogTitle>
<DialogDescription>Preencha os dados da pessoa</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSave)} className="space-y-6">
{/* Tabs */}
<Tabs defaultValue="dadosPessoais" className="space-y-4">
<TabsList className="w-full flex">
<TabsTrigger
className="flex-1 text-center cursor-pointer"
value="dadosPessoais"
>
<UserIcon className="me-1" />
Dados Pessoais
</TabsTrigger>
<TabsTrigger
className="flex-1 text-center cursor-pointer"
value="endereco"
>
<HouseIcon /> Endereço
</TabsTrigger>
<TabsTrigger
className="flex-1 text-center cursor-pointer"
value="documentos"
>
<IdCardIcon /> Representantes
</TabsTrigger>
</TabsList>
<div className="max-h-[80vh] overflow-y-auto">
{/* Dados Pessoais */}
<TabsContent value="dadosPessoais" className="space-y-4">
<div className="grid grid-cols-12 gap-4">
{/* Razão Social */}
<div className="col-span-12 sm:col-span-12 md:col-span-6">
<FormField
control={form.control}
name="nome"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Razão Social</FormLabel>
<FormControl>
<Input
{...field}
placeholder="Digite a razão social"
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Nome Fantasia */}
<div className="col-span-12 sm:col-span-12 md:col-span-6">
<FormField
control={form.control}
name="nome_fantasia"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Nome Fantasia</FormLabel>
<FormControl>
<Input
{...field}
placeholder="Digite o nome fantasia"
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Inscrição Estadual */}
<div className="col-span-12 sm:col-span-12 md:col-span-6">
<FormField
control={form.control}
name="inscricao_municipal"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Inscrição Estadual</FormLabel>
<FormControl>
<Input
{...field}
placeholder="Digite a inscrição estadual"
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* CNPJ */}
<div className="col-span-12 sm:col-span-12 md:col-span-6">
<FormField
control={form.control}
name="cpf_cnpj"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>CNPJ</FormLabel>
<FormControl>
<Input
{...field}
placeholder="Digite o CNPJ"
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Observação */}
<div className="col-span-12 sm:col-span-12 md:col-span-12">
<FormField
control={form.control}
name="observacao"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Observação</FormLabel>
<FormControl>
<Textarea
{...field}
placeholder="Digite alguma observação"
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
</TabsContent>
{/* Endereço */}
<TabsContent value="endereco" className="space-y-4">
<div className="grid grid-cols-12 gap-4 w-full">
{/* País */}
<div className="col-span-12 sm:col-span-12 md:col-span-6">
<FormField
control={form.control}
name="uf_residencia"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>País</FormLabel>
<FormControl>
<Input
{...field}
placeholder="Digite o nome"
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* UF */}
<div className="col-span-12 sm:col-span-12 md:col-span-2">
<FormField
control={form.control}
name="uf_residencia"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>UF</FormLabel>
<FormControl>
<Input
{...field}
placeholder="Digite o nome"
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* CEP */}
<div className="col-span-12 sm:col-span-12 md:col-span-4">
<FormField
control={form.control}
name="cep"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>CEP</FormLabel>
<FormControl>
<Input
{...field}
placeholder="Digite o nome"
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Cidade */}
<div className="col-span-12 sm:col-span-12 md:col-span-6">
<FormField
control={form.control}
name="cidade_nat_id"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Cidade</FormLabel>
<FormControl>
<Input
{...field}
placeholder="Digite o nome"
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Município */}
<div className="col-span-12 sm:col-span-12 md:col-span-6">
<FormField
control={form.control}
name="municipio_id"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Município</FormLabel>
<FormControl>
<Input
{...field}
placeholder="Digite o nome"
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Bairro */}
<div className="col-span-12 sm:col-span-12 md:col-span-6">
<FormField
control={form.control}
name="bairro"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Bairro</FormLabel>
<FormControl>
<Input
{...field}
placeholder="Digite o nome"
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Logradouro */}
<div className="col-span-12 sm:col-span-12 md:col-span-6">
<FormField
control={form.control}
name="tb_tipologradouro_id"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Logradouro</FormLabel>
<FormControl>
<Input
{...field}
placeholder="Digite o nome"
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Número */}
<div className="col-span-12 sm:col-span-12 md:col-span-4">
<FormField
control={form.control}
name="numero_end"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Número</FormLabel>
<FormControl>
<Input
{...field}
placeholder="Digite o nome"
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Unidade */}
<div className="col-span-12 sm:col-span-12 md:col-span-4">
<FormField
control={form.control}
name="unidade"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Unidade</FormLabel>
<FormControl>
<Input
{...field}
placeholder="Digite o nome"
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Cidade não encontrada */}
<div className="col-span-12 sm:col-span-12 md:col-span-4">
<FormField
control={form.control}
name="uf_residencia"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Cidade não encontrada</FormLabel>
<FormControl>
<Input
{...field}
placeholder="Digite o nome"
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
</TabsContent>
{/* Documentos */}
<TabsContent value="documentos" className="space-y-4">
<TPessoaRepresentantePage />
</TabsContent>
</div>
</Tabs>
{/* Rodapé do Dialog */}
<DialogFooter className="mt-4">
<DialogClose asChild>
<Button
variant="outline"
type="button"
onClick={() => onClose(null, false)}
className="cursor-pointer"
>
Cancelar
</Button>
</DialogClose>
<LoadingButton
text="Salvar"
textLoading="Aguarde..."
type="submit"
loading={buttonIsLoading}
/>
</DialogFooter>
{/* Campo oculto */}
<input type="hidden" {...form.register("pessoa_id")} />
</form>
</Form>
</DialogContent>
</Dialog>
);
}

View file

@ -0,0 +1,218 @@
"use client";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
ArrowUpDownIcon,
EllipsisIcon,
PencilIcon,
Trash2Icon,
} from "lucide-react";
import { ColumnDef } from "@tanstack/react-table";
import GetNameInitials from "@/actions/text/GetNameInitials";
import { DataTable } from "@/app/_components/dataTable/DataTable";
import TPessoaInterface from "../../../_interfaces/TPessoaInterface";
import { FormatCPF } from "@/actions/CPF/FormatCPF";
import { FormatPhone } from "@/actions/phone/FormatPhone";
import { FormatDateTime } from "@/actions/dateTime/FormatDateTime";
import empty from "@/actions/validations/empty";
import { FormatCNPJ } from "@/actions/CNPJ/FormatCNPJ";
// Tipagem das props
interface TPessoaJuridicaTableProps {
data: TPessoaInterface[];
onEdit: (item: TPessoaInterface, isEditingFormStatus: boolean) => void;
onDelete: (item: TPessoaInterface, isEditingFormStatus: boolean) => void;
}
/**
* Função para criar a definição das colunas da tabela
*/
function createPessoaColumns(
onEdit: (item: TPessoaInterface, isEditingFormStatus: boolean) => void,
onDelete: (item: TPessoaInterface, isEditingFormStatus: boolean) => void,
): ColumnDef<TPessoaInterface>[] {
return [
// ID
{
accessorKey: "pessoa_id",
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
# <ArrowUpDownIcon className="ml-1 h-4 w-4" />
</Button>
),
cell: ({ row }) => Number(row.getValue("pessoa_id")),
enableSorting: false,
},
// Nome / Email / Foto
{
id: "nome_completo",
accessorFn: (row) => row,
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Nome / Email{" "}
<ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => {
const pessoa = row.original;
return (
<div className="flex items-center gap-3">
{/* Nome e Email */}
<div>
<div className="font-semibold text-gray-900 capitalize">
{pessoa.nome || "-"}
</div>
<div className="text-sm text-gray-500">
{empty(pessoa.email) ? "Email não informado" : pessoa.email}
</div>
</div>
</div>
);
},
sortingFn: (a, b) =>
(a.original.nome?.toLowerCase() || "").localeCompare(
b.original.nome?.toLowerCase() || "",
),
},
// CPF
{
accessorKey: "cpf_cnpj",
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
CNPJ <ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => FormatCNPJ(row.getValue("cpf_cnpj")),
},
// Telefone
{
accessorKey: "telefone",
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Telefone <ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => FormatPhone(row.getValue("telefone")),
},
// Cidade / UF
{
id: "cidade_uf",
accessorFn: (row) => `${row.cidade}/${row.uf}`,
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Cidade/UF <ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => <span>{row.getValue("cidade_uf") || "-"}</span>,
sortingFn: (a, b) =>
`${a.original.cidade}/${a.original.uf}`
.toLowerCase()
.localeCompare(`${b.original.cidade}/${b.original.uf}`.toLowerCase()),
},
// Data de cadastro
{
accessorKey: "data_cadastro",
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Cadastro <ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => FormatDateTime(row.getValue("data_cadastro")),
sortingFn: "datetime",
},
// Ações
{
id: "actions",
header: "Ações",
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={() => onEdit(pessoa, true)}
>
<PencilIcon className="mr-2 h-4 w-4" />
Editar
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="cursor-pointer text-red-600"
onSelect={() => onDelete(pessoa, true)}
>
<Trash2Icon className="mr-2 h-4 w-4" />
Remover
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
);
},
enableSorting: false,
enableHiding: false,
},
];
}
/**
* Componente principal da tabela
*/
export default function TPessoaJuridicaTable({
data,
onEdit,
onDelete,
}: TPessoaJuridicaTableProps) {
const columns = createPessoaColumns(onEdit, onDelete);
return (
<div>
<DataTable
data={data}
columns={columns}
filterColumn="nome_completo"
filterPlaceholder="Buscar por nome ou email..."
/>
</div>
);
}

View file

@ -0,0 +1,346 @@
"use client";
import z from "zod";
import React, { useEffect } from "react";
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { TPessoaSchema } from "../../_schemas/TPessoaSchema";
import LoadingButton from "@/app/_components/loadingButton/LoadingButton";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
ArrowUpDownIcon,
CheckIcon,
ChevronsUpDownIcon,
EllipsisIcon,
HouseIcon,
IdCardIcon,
PencilIcon,
Trash2Icon,
UserIcon,
} from "lucide-react";
import { Sexo } from "@/enums/SexoEnum";
import { useGTBEstadoCivilReadHook } from "../../_hooks/g_tb_estadocivil/useGTBEstadoCivilReadHook";
import GetCapitalize from "@/actions/text/GetCapitalize";
import { useGTBRegimeComunhaoReadHook } from "../../_hooks/g_tb_regimecomunhao/useGTBRegimeComunhaoReadHook";
import { useGTBProfissaoReadHook } from "../../_hooks/g_tb_profissao/useGTBProfissaoReadHook";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import { cn } from "@/lib/utils";
import { useTPessoaIndexHook } from "../../_hooks/t_pessoa/useTPessoaIndexHook";
import TPessoaTable from "../t_pessoa/TPessoaTable";
import TPessoaInterface from "../../_interfaces/TPessoaInterface";
import { ColumnDef } from "@tanstack/react-table";
import GetNameInitials from "@/actions/text/GetNameInitials";
import empty from "@/actions/validations/empty";
import { FormatCPF } from "@/actions/CPF/FormatCPF";
import { FormatPhone } from "@/actions/phone/FormatPhone";
import {
DropdownMenu,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { DropdownMenuContent } from "@radix-ui/react-dropdown-menu";
import { DataTable } from "@/app/_components/dataTable/DataTable";
import { Checkbox } from "@/components/ui/checkbox";
type FormValues = z.infer<typeof TPessoaSchema>;
interface TPessoaRepresentanteFormProps {
isOpen: boolean;
data: FormValues | null;
onClose: (item: null, isFormStatus: boolean) => void;
onSave: (data: FormValues) => void;
buttonIsLoading: boolean;
}
/**
* Função para criar a definição das colunas da tabela
*/
function createPessoaColumns(
onEdit: (item: TPessoaInterface, isEditingFormStatus: boolean) => void,
onDelete: (item: TPessoaInterface, isEditingFormStatus: boolean) => void,
): ColumnDef<TPessoaInterface>[] {
return [
{
id: "select",
header: ({ table }) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
},
// ID
{
accessorKey: "pessoa_representante_id",
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
# <ArrowUpDownIcon className="ml-1 h-4 w-4" />
</Button>
),
cell: ({ row }) => Number(row.getValue("pessoa_representante_id")),
enableSorting: false,
},
// Nome / Email / Foto
{
id: "nome_completo",
accessorFn: (row) => row,
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Nome / Email{" "}
<ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => {
const pessoa = row.original;
return (
<div className="flex items-center gap-3">
{/* Foto ou Iniciais */}
<div className="w-10 h-10 rounded-full bg-gray-200 flex items-center justify-center overflow-hidden">
{pessoa.foto ? (
<img
src={pessoa.foto}
alt={pessoa.nome || "Avatar"}
className="w-full h-full object-cover"
/>
) : (
<span className="text-sm font-medium text-gray-700">
{GetNameInitials(pessoa.nome)}
</span>
)}
</div>
{/* Nome e Email */}
<div>
<div className="font-semibold text-gray-900 capitalize">
{pessoa.nome || "-"}
</div>
<div className="text-sm text-gray-500">
{empty(pessoa.email) ? "Email não informado" : pessoa.email}
</div>
</div>
</div>
);
},
sortingFn: (a, b) =>
(a.original.nome?.toLowerCase() || "").localeCompare(
b.original.nome?.toLowerCase() || "",
),
},
// CPF
{
accessorKey: "cpf_cnpj",
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 }) => FormatCPF(row.getValue("cpf_cnpj")),
},
// Telefone
{
accessorKey: "telefone",
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Telefone <ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => FormatPhone(row.getValue("telefone")),
},
// Ações
{
id: "actions",
header: "Ações",
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={() => onEdit(pessoa, true)}
>
<PencilIcon className="mr-2 h-4 w-4" />
Editar
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="cursor-pointer text-red-600"
onSelect={() => onDelete(pessoa, true)}
>
<Trash2Icon className="mr-2 h-4 w-4" />
Remover
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
);
},
enableSorting: false,
enableHiding: false,
},
];
}
export default function TPessoaRepresentanteForm({
isOpen,
data,
onClose,
onSave,
buttonIsLoading,
}: TPessoaRepresentanteFormProps) {
const { tPessoa, fetchTPessoa } = useTPessoaIndexHook();
// Inicializa o react-hook-form com schema zod
const form = useForm<FormValues>({
resolver: zodResolver(TPessoaSchema),
defaultValues: {
nome: "",
pessoa_id: 0,
},
});
// Atualiza o formulário quando recebe dados para edição
useEffect(() => {
const loadData = async () => {
// Se existir dados, reseta o formulário com os dados informados
if (data) form.reset(data);
// Aguarda a busca terminar
await fetchTPessoa();
};
// Dispara a função
loadData();
}, [data, form]);
const columns = createPessoaColumns(
() => {},
() => {},
);
return (
<Dialog
open={isOpen}
onOpenChange={(open) => {
if (!open) onClose(null, false);
}}
>
<DialogContent className="w-full max-w-full sm:max-w-3xl md:max-w-4xl lg:max-w-5xl p-6">
<DialogHeader>
<DialogTitle>Representante</DialogTitle>
<DialogDescription>Busque o representante desejado</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSave)} className="space-y-6">
<div className="max-h-[50vh] overflow-y-auto">
<DataTable
data={tPessoa}
columns={columns}
filterColumn="nome_completo"
filterPlaceholder="Buscar por nome ou email..."
/>
</div>
{/* Rodapé do Dialog */}
<DialogFooter className="mt-4">
<DialogClose asChild>
<Button
variant="outline"
type="button"
onClick={() => onClose(null, false)}
className="cursor-pointer"
>
Cancelar
</Button>
</DialogClose>
<LoadingButton
text="Salvar"
textLoading="Aguarde..."
type="submit"
loading={buttonIsLoading}
/>
</DialogFooter>
{/* Campo oculto */}
<input type="hidden" {...form.register("pessoa_id")} />
</form>
</Form>
</DialogContent>
</Dialog>
);
}

View file

@ -0,0 +1,177 @@
"use client";
import React, { useEffect, useState, useCallback } from "react";
import Loading from "@/app/_components/loading/loading";
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 TPessoaRepresentanteTable from "./TPessoaRepresentanteTable";
import { useTPessoaRepresentanteIndexHook } from "../../_hooks/t_pessoa_representante/useTPessoaRepresentanteIndexHook";
import { Button } from "@/components/ui/button";
import Header from "@/app/_components/structure/Header";
import TPessoaRepresentanteForm from "./TPessoaRepresentanteForm";
export default function TPessoaRepresentantePage() {
// Controle de estado do botão
const [buttonIsLoading, setButtonIsLoading] = useState(false);
// Hooks para leitura e salvamento
const { tPessoaRepresentante, fetchTPessoaRepresentante } =
useTPessoaRepresentanteIndexHook();
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
fetchTPessoaRepresentante();
},
[saveTCensec, fetchTPessoaRepresentante, 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 fetchTPessoaRepresentante();
// Limpa o item selecionado
setItemToDelete(null);
// Fecha o modal
handleCancel();
}, [itemToDelete, fetchTPessoaRepresentante, handleCancel]);
/**
* Busca inicial dos dados
*/
useEffect(() => {
fetchTPessoaRepresentante();
}, []);
/**
* Tela de loading enquanto carrega os dados
*/
if (tPessoaRepresentante.length == 0) {
return <Loading type={2} />;
}
return (
<div>
{/* Cabeçalho */}
<Header
title={"Representantes"}
description={"Gerenciamento de representantes"}
buttonText={"Novo representante"}
buttonAction={() => {
handleOpenForm(null);
}}
/>
{/* Tabela de Registros */}
<TPessoaRepresentanteTable
data={tPessoaRepresentante}
onDelete={handleConfirmDelete}
onEdit={handleOpenForm}
/>
{/* 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 */}
<TPessoaRepresentanteForm
isOpen={isFormOpen}
data={selectedAndamento}
onClose={handleCloseForm}
onSave={handleSave}
buttonIsLoading={buttonIsLoading}
/>
</div>
);
}

View file

@ -0,0 +1,191 @@
"use client";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
ArrowUpDownIcon,
EllipsisIcon,
PencilIcon,
Trash2Icon,
} from "lucide-react";
import { ColumnDef } from "@tanstack/react-table";
import GetNameInitials from "@/actions/text/GetNameInitials";
import { DataTable } from "@/app/_components/dataTable/DataTable";
import TPessoaInterface from "../../_interfaces/TPessoaInterface";
import { FormatCPF } from "@/actions/CPF/FormatCPF";
import { FormatPhone } from "@/actions/phone/FormatPhone";
import empty from "@/actions/validations/empty";
import { Checkbox } from "@/components/ui/checkbox";
// Tipagem das props
interface TPessoaRepresentanteTableProps {
data: TPessoaInterface[];
onEdit: (item: TPessoaInterface, isEditingFormStatus: boolean) => void;
onDelete: (item: TPessoaInterface, isEditingFormStatus: boolean) => void;
}
/**
* Função para criar a definição das colunas da tabela
*/
function createPessoaColumns(
onEdit: (item: TPessoaInterface, isEditingFormStatus: boolean) => void,
onDelete: (item: TPessoaInterface, isEditingFormStatus: boolean) => void,
): ColumnDef<TPessoaInterface>[] {
return [
// ID
{
accessorKey: "pessoa_representante_id",
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
# <ArrowUpDownIcon className="ml-1 h-4 w-4" />
</Button>
),
cell: ({ row }) => Number(row.getValue("pessoa_representante_id")),
enableSorting: false,
},
// Nome / Email / Foto
{
id: "nome_completo",
accessorFn: (row) => row,
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Nome / Email{" "}
<ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => {
const pessoa = row.original;
return (
<div className="flex items-center gap-3">
{/* Foto ou Iniciais */}
<div className="w-10 h-10 rounded-full bg-gray-200 flex items-center justify-center overflow-hidden">
{pessoa.foto ? (
<img
src={pessoa.foto}
alt={pessoa.nome || "Avatar"}
className="w-full h-full object-cover"
/>
) : (
<span className="text-sm font-medium text-gray-700">
{GetNameInitials(pessoa.nome)}
</span>
)}
</div>
{/* Nome e Email */}
<div>
<div className="font-semibold text-gray-900 capitalize">
{pessoa.nome || "-"}
</div>
<div className="text-sm text-gray-500">
{empty(pessoa.email) ? "Email não informado" : pessoa.email}
</div>
</div>
</div>
);
},
sortingFn: (a, b) =>
(a.original.nome?.toLowerCase() || "").localeCompare(
b.original.nome?.toLowerCase() || "",
),
},
// CPF
{
accessorKey: "cpf_cnpj",
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 }) => FormatCPF(row.getValue("cpf_cnpj")),
},
// Telefone
{
accessorKey: "telefone",
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Telefone <ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => FormatPhone(row.getValue("telefone")),
},
// Ações
{
id: "actions",
header: "Ações",
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 text-red-600"
onSelect={() => onDelete(pessoa, true)}
>
<Trash2Icon className="mr-2 h-4 w-4" />
Remover
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
);
},
enableSorting: false,
enableHiding: false,
},
];
}
/**
* Componente principal da tabela
*/
export default function TPessoaRepresentanteTable({
data,
onEdit,
onDelete,
}: TPessoaRepresentanteTableProps) {
const columns = createPessoaColumns(onEdit, onDelete);
return (
<div>
<DataTable
data={data}
columns={columns}
filterColumn="nome_completo"
filterPlaceholder="Buscar por nome ou email..."
/>
</div>
);
}

View file

@ -1,8 +1,8 @@
// Importa o serviço de API que será utilizado para realizar requisições HTTP
import API from "@/services/api/Api";
import API from "@/services/api/Api";
// Importa o enum que contém os métodos HTTP disponíveis (GET, POST, PUT, DELETE)
import { Methods } from "@/services/api/enums/ApiMethodEnum";
import { Methods } from "@/services/api/enums/ApiMethodEnum";
// Importa a interface tipada que define a estrutura dos dados de uma cidade
import GCidadeInterface from "../../_interfaces/GCidadeInterface";
@ -10,22 +10,20 @@ import GCidadeInterface from "../../_interfaces/GCidadeInterface";
// Importa função que encapsula chamadas assíncronas e trata erros automaticamente
import { withClientErrorHandler } from "@/actions/withClientErrorHandler/withClientErrorHandler";
// Função assíncrona que implementa a lógica de salvar (criar/atualizar) uma cidade
async function executeGcidadeSaveData(data: GCidadeInterface) {
// Verifica se existe ID da cidade para decidir se é atualização (PUT) ou criação (POST)
const isUpdate = Boolean(data.cidade_id);
// Verifica se existe ID da cidade para decidir se é atualização (PUT) ou criação (POST)
const isUpdate = Boolean(data.cidade_id);
// Instancia o cliente da API para enviar a requisição
const api = new API();
// Instancia o cliente da API para enviar a requisição
const api = new API();
// Executa a requisição para a API com o método apropriado e envia os dados no corpo
return await api.send({
method: isUpdate ? Methods.PUT : Methods.POST, // PUT se atualizar, POST se criar
endpoint: `administrativo/g_cidade/${data.cidade_id || ''}`, // endpoint dinâmico
body: data // payload enviado para a API
});
// Executa a requisição para a API com o método apropriado e envia os dados no corpo
return await api.send({
method: isUpdate ? Methods.PUT : Methods.POST, // PUT se atualizar, POST se criar
endpoint: `administrativo/g_cidade/${data.cidade_id || ""}`, // endpoint dinâmico
body: data, // payload enviado para a API
});
}
// Exporta a função de salvar cidade já encapsulada com tratamento de erros

View file

@ -6,14 +6,12 @@ import { Methods } from "@/services/api/enums/ApiMethodEnum";
// Exporta por padrão a função assíncrona GUfIndexData
export default async function GUfIndexData() {
// Cria uma instância da classe API para executar a requisição
const api = new API();
// Cria uma instância da classe API para executar a requisição
const api = new API();
// Executa a chamada GET para o endpoint "administrativo/g_uf/" e retorna a resposta
return await api.send({
method: Methods.GET, // Define que o método HTTP é GET
endpoint: `administrativo/g_uf/` // Define o endpoint a ser acessado
});
// Executa a chamada GET para o endpoint "administrativo/g_uf/" e retorna a resposta
return await api.send({
method: Methods.GET, // Define que o método HTTP é GET
endpoint: `administrativo/g_uf/`, // Define o endpoint a ser acessado
});
}

View file

@ -0,0 +1,54 @@
import { withClientErrorHandler } from "@/actions/withClientErrorHandler/withClientErrorHandler";
async function executeTPessoaRepresentanteIndexData() {
return Promise.resolve({
status: 200,
message: "Dados localizados",
data: [
{
pessoa_representante_id: 1,
nome: "Keven Willian",
email: "keven@oriustecnologia.com.br",
tipo: "Proprietário",
cpf_cnpj: "70341828173",
telefone: "62983372306",
},
{
pessoa_representante_id: 2,
nome: "Mariana Silva",
email: "keven@oriustecnologia.com.br",
tipo: "Sócio",
cpf_cnpj: "70341828173",
telefone: "62983372306",
},
{
pessoa_representante_id: 3,
nome: "Lucas Oliveira",
tipo: "Administrador",
email: "keven@oriustecnologia.com.br",
cpf_cnpj: "70341828173",
telefone: "62983372306",
},
{
pessoa_representante_id: 4,
nome: "Fernanda Costa",
email: "keven@oriustecnologia.com.br",
tipo: "Procurador",
cpf_cnpj: "70341828173",
telefone: "62983372306",
},
{
pessoa_representante_id: 5,
nome: "Rafael Gomes",
email: "keven@oriustecnologia.com.br",
tipo: "Sócio",
cpf_cnpj: "70341828173",
telefone: "62983372306",
},
],
});
}
export const TPessoaRepresentanteIndexData = withClientErrorHandler(
executeTPessoaRepresentanteIndexData,
);

View file

@ -15,10 +15,10 @@ export const useGCidadeSaveHook = () => {
// Guardar os dados localizados
setGCidade(response.data);
// Guardar os dados localizados
setGCidade(response.data);
// Guardar os dados localizados
setGCidade(response.data);
console.log(response)
console.log(response);
// Manda a resposta para o verificador de resposta
setResponse(response);
@ -26,13 +26,11 @@ export const useGCidadeSaveHook = () => {
// Fecha o formulário automaticamente após salvar
setIsOpen(false);
console.log(response)
console.log(response);
// Manda a resposta para o verificador de resposta
setResponse(response);
// Manda a resposta para o verificador de resposta
setResponse(response);
};
}
return { gCidade, saveGCidade }
}
return { gCidade, saveGCidade };
};

View file

@ -1,30 +1,26 @@
'use client'
"use client";
import { useResponse } from "@/app/_response/ResponseContext"
import { useResponse } from "@/app/_response/ResponseContext";
import { use, useState } from "react";
import GUfInterface from "../../_interfaces/GUfInterface";
import GUfIndexService from "../../_services/g_uf/GUfIndexService";
export const useGUfReadHook = () => {
const { setResponse } = useResponse();
const { setResponse } = useResponse();
// Controle dos dados obtidos via API
const [gUf, setGUf] = useState<GUfInterface[]>([]);
// Controle dos dados obtidos via API
const [gUf, setGUf] = useState<GUfInterface[]>([]);
const fetchGUf = async () => {
// Realiza a requisição para a api
const response = await GUfIndexService();
const fetchGUf = async () => {
// Armazena os dados da resposta
setGUf(response.data);
// Realiza a requisição para a api
const response = await GUfIndexService();
// Envia os dados da resposta para ser tratado
setResponse(response);
};
// Armazena os dados da resposta
setGUf(response.data);
// Envia os dados da resposta para ser tratado
setResponse(response);
}
return { gUf, fetchGUf }
}
return { gUf, fetchGUf };
};

View file

@ -0,0 +1,15 @@
import { useResponse } from "@/app/_response/ResponseContext";
import TPessoaJuridicaInterface from "../../../_interfaces/TPessoaJuridicaInterface";
import { TCensecDeleteService } from "../../../_services/t_censec/TCensecDeleteService";
export const useTPessoaJuridicaDeleteHook = () => {
const { setResponse } = useResponse();
const deleteTCensec = async (data: TPessoaJuridicaInterface) => {
const response = await TCensecDeleteService(data);
setResponse(response);
};
return { deleteTCensec };
};

View file

@ -0,0 +1,20 @@
import { useResponse } from "@/app/_response/ResponseContext";
import { TPessoaJuridicaIndexService } from "../../../_services/t_pessoa/juridica/TPessoaJuridicaIndexService";
import { useState } from "react";
import TPessoaJuridicaInterface from "../../../_interfaces/TPessoaJuridicaInterface";
export const useTPessoaJuridicaIndexHook = () => {
const { setResponse } = useResponse();
const [tPessoa, setTPessoa] = useState<TPessoaJuridicaInterface[]>([]);
const fetchTPessoa = async () => {
const response = await TPessoaJuridicaIndexService();
setTPessoa(response.data);
setResponse(response);
};
return { tPessoa, fetchTPessoa };
};

View file

@ -0,0 +1,33 @@
"use client";
import { useResponse } from "@/app/_response/ResponseContext";
import { useState } from "react";
import TPessoaInterface from "../../../_interfaces/TPessoaInterface";
import { TCensecSaveService } from "../../../_services/t_censec/TCensecSaveService";
export const useTPessoaJuridicaSaveHook = () => {
const { setResponse } = useResponse();
const [tCensec, setTCensec] = useState<TPessoaInterface>();
// controla se o formulário está aberto ou fechado
const [isOpen, setIsOpen] = useState(false);
const saveTCensec = async (data: TPessoaInterface) => {
const response = await TCensecSaveService(data);
// Armazena os dados da repsota
setTCensec(response.data);
// Define os dados da respota(toast, modal, etc)
setResponse(response);
// Fecha o formulário automaticamente após salvar
setIsOpen(false);
// Retorna os valores de forma imediata
return response.data;
};
return { tCensec, saveTCensec };
};

View file

@ -1,19 +1,15 @@
import { useResponse } from "@/app/_response/ResponseContext"
import { useResponse } from "@/app/_response/ResponseContext";
import TPessoaInterface from "../../_interfaces/TPessoaInterface";
import { TCensecDeleteService } from "../../_services/t_censec/TCensecDeleteService";
export const useTPessoaDeleteHook = () => {
const { setResponse } = useResponse();
const { setResponse } = useResponse();
const deleteTCensec = async (data: TPessoaInterface) => {
const response = await TCensecDeleteService(data);
const deleteTCensec = async (data: TPessoaInterface) => {
setResponse(response);
};
const response = await TCensecDeleteService(data);
setResponse(response);
}
return { deleteTCensec }
}
return { deleteTCensec };
};

View file

@ -1,24 +1,20 @@
import { useResponse } from "@/app/_response/ResponseContext";
import { TPessoaIndexService } from "../../_services/t_pessoa/TPessoaIndexService"
import { TPessoaIndexService } from "../../_services/t_pessoa/TPessoaIndexService";
import { useState } from "react";
import TPessoaInterface from "../../_interfaces/TPessoaInterface";
export const useTPessoaIndexHook = () => {
const { setResponse } = useResponse();
const { setResponse } = useResponse();
const [tPessoa, setTPessoa] = useState<TPessoaInterface[]>([]);
const [tPessoa, setTPessoa] = useState<TPessoaInterface[]>([]);
const fetchTPessoa = async () => {
const response = await TPessoaIndexService();
const fetchTPessoa = async () => {
setTPessoa(response.data);
const response = await TPessoaIndexService();
setResponse(response);
};
setTPessoa(response.data);
setResponse(response)
}
return { tPessoa, fetchTPessoa }
}
return { tPessoa, fetchTPessoa };
};

View file

@ -1,37 +1,33 @@
'use client'
"use client";
import { useResponse } from "@/app/_response/ResponseContext"
import { useResponse } from "@/app/_response/ResponseContext";
import { useState } from "react";
import TPessoaInterface from "../../_interfaces/TPessoaInterface";
import { TCensecSaveService } from "../../_services/t_censec/TCensecSaveService";
export const useTPessoaSaveHook = () => {
const { setResponse } = useResponse();
const { setResponse } = useResponse();
const [tCensec, setTCensec] = useState<TPessoaInterface>();
const [tCensec, setTCensec] = useState<TPessoaInterface>();
// controla se o formulário está aberto ou fechado
const [isOpen, setIsOpen] = useState(false);
// controla se o formulário está aberto ou fechado
const [isOpen, setIsOpen] = useState(false);
const saveTCensec = async (data: TPessoaInterface) => {
const response = await TCensecSaveService(data);
const saveTCensec = async (data: TPessoaInterface) => {
// Armazena os dados da repsota
setTCensec(response.data);
const response = await TCensecSaveService(data);
// Define os dados da respota(toast, modal, etc)
setResponse(response);
// Armazena os dados da repsota
setTCensec(response.data);
// Fecha o formulário automaticamente após salvar
setIsOpen(false);
// Define os dados da respota(toast, modal, etc)
setResponse(response);
// Retorna os valores de forma imediata
return response.data;
};
// Fecha o formulário automaticamente após salvar
setIsOpen(false);
// Retorna os valores de forma imediata
return response.data;
}
return { tCensec, saveTCensec }
}
return { tCensec, saveTCensec };
};

View file

@ -0,0 +1,15 @@
import { useResponse } from "@/app/_response/ResponseContext";
import TPessoaInterface from "../../_interfaces/TPessoaInterface";
import { TCensecDeleteService } from "../../_services/t_censec/TCensecDeleteService";
export const useTPessoaDeleteHook = () => {
const { setResponse } = useResponse();
const deleteTCensec = async (data: TPessoaInterface) => {
const response = await TCensecDeleteService(data);
setResponse(response);
};
return { deleteTCensec };
};

View file

@ -0,0 +1,23 @@
import { useResponse } from "@/app/_response/ResponseContext";
import { TPessoaIndexService } from "../../_services/t_pessoa/TPessoaIndexService";
import { useState } from "react";
import TPessoaInterface from "../../_interfaces/TPessoaInterface";
import { TPessoaRepresentanteIndexData } from "../../_data/TPessoaRepresentante/TPessoaRepresentanteIndexData";
export const useTPessoaRepresentanteIndexHook = () => {
const { setResponse } = useResponse();
const [tPessoaRepresentante, setTPessoaRepresentante] = useState<
TPessoaInterface[]
>([]);
const fetchTPessoaRepresentante = async () => {
const response = await TPessoaRepresentanteIndexData();
setTPessoaRepresentante(response.data);
setResponse(response);
};
return { tPessoaRepresentante, fetchTPessoaRepresentante };
};

View file

@ -0,0 +1,33 @@
"use client";
import { useResponse } from "@/app/_response/ResponseContext";
import { useState } from "react";
import TPessoaInterface from "../../_interfaces/TPessoaInterface";
import { TCensecSaveService } from "../../_services/t_censec/TCensecSaveService";
export const useTPessoaSaveHook = () => {
const { setResponse } = useResponse();
const [tCensec, setTCensec] = useState<TPessoaInterface>();
// controla se o formulário está aberto ou fechado
const [isOpen, setIsOpen] = useState(false);
const saveTCensec = async (data: TPessoaInterface) => {
const response = await TCensecSaveService(data);
// Armazena os dados da repsota
setTCensec(response.data);
// Define os dados da respota(toast, modal, etc)
setResponse(response);
// Fecha o formulário automaticamente após salvar
setIsOpen(false);
// Retorna os valores de forma imediata
return response.data;
};
return { tCensec, saveTCensec };
};

View file

@ -1,8 +1,6 @@
export default interface GUfInterface {
g_uf_id?: number,
sigla: string,
nome: string,
codigo_uf_ibge?: string,
g_uf_id?: number;
sigla: string;
nome: string;
codigo_uf_ibge?: string;
}

View file

@ -1,63 +1,63 @@
export default interface TPessoaInterface {
pessoa_id?: number;
pessoa_tipo?: string;
nome?: string;
nacionalidade?: string;
documento?: string;
tb_documentotipo_id?: number;
tb_profissao_id?: number;
tb_estadocivil_id?: number;
nome_pai?: string;
nome_mae?: string;
data_cadastro?: string; // ou Date
naturalidade?: string;
telefone?: string;
endereco?: string;
cidade?: string;
uf?: string;
data_nascimento?: string; // ou Date
sexo?: string;
tb_regimecomunhao_id?: number;
pessoa_conjuge_id?: number;
email?: string;
documento_numero?: string;
bairro?: string;
cep?: string;
documento_expedicao?: string; // ou Date
documento_validade?: string; // ou Date
observacao?: string;
cpf_cnpj?: string;
cpf_terceiro?: string;
nome_fantasia?: string;
texto?: string;
ddd?: number;
cert_casamento_numero?: string;
cert_casamento_folha?: string;
cert_casamento_livro?: string;
cert_casamento_cartorio?: string;
cert_casamento_data?: string; // ou Date
cert_casamento_lei?: string;
pessoa_conjuge_nome?: string;
estrangeiro_nat?: string;
estrangeiro_nat_tb_pais_id?: number;
estrangeiro_res_tb_pais_id?: number;
estrangeiro_res?: string;
municipio_id?: number;
documento_orgao?: string;
documento_uf?: string;
uf_residencia?: string;
inscricao_municipal?: string;
enviado_cnncnb?: boolean;
data_auteracao?: string; // ou Date
data_envioccn?: string; // ou Date
ccnregistros_id?: number;
observacao_envioccn?: string;
observacao_envio_ccn?: string;
deficiencias?: string;
grau_instrucao?: string;
cidade_nat_id?: number;
tb_tipologradouro_id?: number;
unidade?: string;
numero_end?: string;
foto?: string;
}
pessoa_id?: number;
pessoa_tipo?: string;
nome?: string;
nacionalidade?: string;
documento?: string;
tb_documentotipo_id?: number;
tb_profissao_id?: number;
tb_estadocivil_id?: number;
nome_pai?: string;
nome_mae?: string;
data_cadastro?: string; // ou Date
naturalidade?: string;
telefone?: string;
endereco?: string;
cidade?: string;
uf?: string;
data_nascimento?: string; // ou Date
sexo?: string;
tb_regimecomunhao_id?: number;
pessoa_conjuge_id?: number;
email?: string;
documento_numero?: string;
bairro?: string;
cep?: string;
documento_expedicao?: string; // ou Date
documento_validade?: string; // ou Date
observacao?: string;
cpf_cnpj?: string;
cpf_terceiro?: string;
nome_fantasia?: string;
texto?: string;
ddd?: number;
cert_casamento_numero?: string;
cert_casamento_folha?: string;
cert_casamento_livro?: string;
cert_casamento_cartorio?: string;
cert_casamento_data?: string; // ou Date
cert_casamento_lei?: string;
pessoa_conjuge_nome?: string;
estrangeiro_nat?: string;
estrangeiro_nat_tb_pais_id?: number;
estrangeiro_res_tb_pais_id?: number;
estrangeiro_res?: string;
municipio_id?: number;
documento_orgao?: string;
documento_uf?: string;
uf_residencia?: string;
inscricao_municipal?: string;
enviado_cnncnb?: boolean;
data_auteracao?: string; // ou Date
data_envioccn?: string; // ou Date
ccnregistros_id?: number;
observacao_envioccn?: string;
observacao_envio_ccn?: string;
deficiencias?: string;
grau_instrucao?: string;
cidade_nat_id?: number;
tb_tipologradouro_id?: number;
unidade?: string;
numero_end?: string;
foto?: string;
}

View file

@ -0,0 +1,63 @@
export default interface TPessoaJuridicaInterface {
pessoa_id?: number;
pessoa_tipo?: string;
nome?: string;
nacionalidade?: string;
documento?: string;
tb_documentotipo_id?: number;
tb_profissao_id?: number;
tb_estadocivil_id?: number;
nome_pai?: string;
nome_mae?: string;
data_cadastro?: string; // ou Date
naturalidade?: string;
telefone?: string;
endereco?: string;
cidade?: string;
uf?: string;
data_nascimento?: string; // ou Date
sexo?: string;
tb_regimecomunhao_id?: number;
pessoa_conjuge_id?: number;
email?: string;
documento_numero?: string;
bairro?: string;
cep?: string;
documento_expedicao?: string; // ou Date
documento_validade?: string; // ou Date
observacao?: string;
cpf_cnpj?: string;
cpf_terceiro?: string;
nome_fantasia?: string;
texto?: string;
ddd?: number;
cert_casamento_numero?: string;
cert_casamento_folha?: string;
cert_casamento_livro?: string;
cert_casamento_cartorio?: string;
cert_casamento_data?: string; // ou Date
cert_casamento_lei?: string;
pessoa_conjuge_nome?: string;
estrangeiro_nat?: string;
estrangeiro_nat_tb_pais_id?: number;
estrangeiro_res_tb_pais_id?: number;
estrangeiro_res?: string;
municipio_id?: number;
documento_orgao?: string;
documento_uf?: string;
uf_residencia?: string;
inscricao_municipal?: string;
enviado_cnncnb?: boolean;
data_auteracao?: string; // ou Date
data_envioccn?: string; // ou Date
ccnregistros_id?: number;
observacao_envioccn?: string;
observacao_envio_ccn?: string;
deficiencias?: string;
grau_instrucao?: string;
cidade_nat_id?: number;
tb_tipologradouro_id?: number;
unidade?: string;
numero_end?: string;
foto?: string;
}

View file

@ -0,0 +1,5 @@
export default interface TPessoaJuridicaInterface {
pessoa_representante_id: number;
nome: string;
tipo: string;
}

View file

@ -1,10 +1,12 @@
import z from "zod";
export const GCidadeSchema = z.object({
cidade_id: z.number().optional(),
uf: z.string().min(1, "A UF é obrigatória").max(2, "A UF deve ter no máximo 2 caracteres"),
cidade_nome: z.string().min(1, "O nome da cidade é obrigatório"),
codigo_ibge: z.string().optional(),
codigo_gyn: z.string().optional()
})
cidade_id: z.number().optional(),
uf: z
.string()
.min(1, "A UF é obrigatória")
.max(2, "A UF deve ter no máximo 2 caracteres"),
cidade_nome: z.string().min(1, "O nome da cidade é obrigatório"),
codigo_ibge: z.string().optional(),
codigo_gyn: z.string().optional(),
});

View file

@ -0,0 +1,67 @@
import z from "zod";
export const TPessoaJuridicaSchema = z.object({
pessoa_id: z.number().optional(),
pessoa_tipo: z.string().optional(),
nome: z
.string()
.min(1, "O campo deve ser preenchido")
.max(120, "O nome excede 120 caracteres"),
nacionalidade: z.string().optional(),
documento: z.string().optional(),
tb_documentotipo_id: z.number().optional(),
tb_profissao_id: z.number().optional(),
tb_estadocivil_id: z.number().optional(),
nome_pai: z.string().optional(),
nome_mae: z.string().optional(),
data_cadastro: z.string().optional(), // ou z.string().datetime()
naturalidade: z.string().optional(),
telefone: z.string().optional(),
endereco: z.string().optional(),
cidade: z.string().optional(),
uf: z.string().optional(),
data_nascimento: z.string().optional(), // ou z.string().datetime()
sexo: z.string().optional(),
tb_regimecomunhao_id: z.number().optional(),
pessoa_conjuge_id: z.number().optional(),
email: z.string().email().optional(),
documento_numero: z.string().optional(),
bairro: z.string().optional(),
cep: z.string().optional(),
documento_expedicao: z.string().optional(), // ou z.string().datetime()
documento_validade: z.string().optional(), // ou z.string().datetime()
observacao: z.string().optional(),
cpf_cnpj: z.string().optional(),
cpf_terceiro: z.string().optional(),
nome_fantasia: z.string().optional(),
texto: z.string().optional(),
ddd: z.number().optional(),
cert_casamento_numero: z.string().optional(),
cert_casamento_folha: z.string().optional(),
cert_casamento_livro: z.string().optional(),
cert_casamento_cartorio: z.string().optional(),
cert_casamento_data: z.string().optional(), // ou z.string().datetime()
cert_casamento_lei: z.string().optional(),
pessoa_conjuge_nome: z.string().optional(),
estrangeiro_nat: z.string().optional(),
estrangeiro_nat_tb_pais_id: z.number().optional(),
estrangeiro_res_tb_pais_id: z.number().optional(),
estrangeiro_res: z.string().optional(),
municipio_id: z.number().optional(),
documento_orgao: z.string().optional(),
documento_uf: z.string().optional(),
uf_residencia: z.string().optional(),
inscricao_municipal: z.string().optional(),
enviado_cnncnb: z.boolean().optional(),
data_auteracao: z.string().optional(), // ou z.string().datetime()
data_envioccn: z.string().optional(), // ou z.string().datetime()
ccnregistros_id: z.number().optional(),
observacao_envioccn: z.string().optional(),
observacao_envio_ccn: z.string().optional(),
deficiencias: z.string().optional(),
grau_instrucao: z.string().optional(),
cidade_nat_id: z.number().optional(),
tb_tipologradouro_id: z.number().optional(),
unidade: z.string().optional(),
numero_end: z.string().optional(),
});

View file

@ -0,0 +1,7 @@
import z, { number } from "zod";
export const TPessoaRepresentante = z.object({
pessoa_representante_id: z.number().optional,
nome: z.string(),
tipo: z.string(),
});

View file

@ -1,64 +1,67 @@
import z from "zod";
export const TPessoaSchema = z.object({
pessoa_id: z.number().optional(),
pessoa_tipo: z.string().optional(),
nome: z.string().min(1, "O campo deve ser preenchido").max(120, "O nome excede 120 caracteres"),
nacionalidade: z.string().optional(),
documento: z.string().optional(),
tb_documentotipo_id: z.number().optional(),
tb_profissao_id: z.number().optional(),
tb_estadocivil_id: z.number().optional(),
nome_pai: z.string().optional(),
nome_mae: z.string().optional(),
data_cadastro: z.string().optional(), // ou z.string().datetime()
naturalidade: z.string().optional(),
telefone: z.string().optional(),
endereco: z.string().optional(),
cidade: z.string().optional(),
uf: z.string().optional(),
data_nascimento: z.string().optional(), // ou z.string().datetime()
sexo: z.string().optional(),
tb_regimecomunhao_id: z.number().optional(),
pessoa_conjuge_id: z.number().optional(),
email: z.string().email().optional(),
documento_numero: z.string().optional(),
bairro: z.string().optional(),
cep: z.string().optional(),
documento_expedicao: z.string().optional(), // ou z.string().datetime()
documento_validade: z.string().optional(), // ou z.string().datetime()
observacao: z.string().optional(),
cpf_cnpj: z.string().optional(),
cpf_terceiro: z.string().optional(),
nome_fantasia: z.string().optional(),
texto: z.string().optional(),
ddd: z.number().optional(),
cert_casamento_numero: z.string().optional(),
cert_casamento_folha: z.string().optional(),
cert_casamento_livro: z.string().optional(),
cert_casamento_cartorio: z.string().optional(),
cert_casamento_data: z.string().optional(), // ou z.string().datetime()
cert_casamento_lei: z.string().optional(),
pessoa_conjuge_nome: z.string().optional(),
estrangeiro_nat: z.string().optional(),
estrangeiro_nat_tb_pais_id: z.number().optional(),
estrangeiro_res_tb_pais_id: z.number().optional(),
estrangeiro_res: z.string().optional(),
municipio_id: z.number().optional(),
documento_orgao: z.string().optional(),
documento_uf: z.string().optional(),
uf_residencia: z.string().optional(),
inscricao_municipal: z.string().optional(),
enviado_cnncnb: z.boolean().optional(),
data_auteracao: z.string().optional(), // ou z.string().datetime()
data_envioccn: z.string().optional(), // ou z.string().datetime()
ccnregistros_id: z.number().optional(),
observacao_envioccn: z.string().optional(),
observacao_envio_ccn: z.string().optional(),
deficiencias: z.string().optional(),
grau_instrucao: z.string().optional(),
cidade_nat_id: z.number().optional(),
tb_tipologradouro_id: z.number().optional(),
unidade: z.string().optional(),
numero_end: z.string().optional(),
pessoa_id: z.number().optional(),
pessoa_tipo: z.string().optional(),
nome: z
.string()
.min(1, "O campo deve ser preenchido")
.max(120, "O nome excede 120 caracteres"),
nacionalidade: z.string().optional(),
documento: z.string().optional(),
tb_documentotipo_id: z.number().optional(),
tb_profissao_id: z.number().optional(),
tb_estadocivil_id: z.number().optional(),
nome_pai: z.string().optional(),
nome_mae: z.string().optional(),
data_cadastro: z.string().optional(), // ou z.string().datetime()
naturalidade: z.string().optional(),
telefone: z.string().optional(),
endereco: z.string().optional(),
cidade: z.string().optional(),
uf: z.string().optional(),
data_nascimento: z.string().optional(), // ou z.string().datetime()
sexo: z.string().optional(),
tb_regimecomunhao_id: z.number().optional(),
pessoa_conjuge_id: z.number().optional(),
email: z.string().email().optional(),
documento_numero: z.string().optional(),
bairro: z.string().optional(),
cep: z.string().optional(),
documento_expedicao: z.string().optional(), // ou z.string().datetime()
documento_validade: z.string().optional(), // ou z.string().datetime()
observacao: z.string().optional(),
cpf_cnpj: z.string().optional(),
cpf_terceiro: z.string().optional(),
nome_fantasia: z.string().optional(),
texto: z.string().optional(),
ddd: z.number().optional(),
cert_casamento_numero: z.string().optional(),
cert_casamento_folha: z.string().optional(),
cert_casamento_livro: z.string().optional(),
cert_casamento_cartorio: z.string().optional(),
cert_casamento_data: z.string().optional(), // ou z.string().datetime()
cert_casamento_lei: z.string().optional(),
pessoa_conjuge_nome: z.string().optional(),
estrangeiro_nat: z.string().optional(),
estrangeiro_nat_tb_pais_id: z.number().optional(),
estrangeiro_res_tb_pais_id: z.number().optional(),
estrangeiro_res: z.string().optional(),
municipio_id: z.number().optional(),
documento_orgao: z.string().optional(),
documento_uf: z.string().optional(),
uf_residencia: z.string().optional(),
inscricao_municipal: z.string().optional(),
enviado_cnncnb: z.boolean().optional(),
data_auteracao: z.string().optional(), // ou z.string().datetime()
data_envioccn: z.string().optional(), // ou z.string().datetime()
ccnregistros_id: z.number().optional(),
observacao_envioccn: z.string().optional(),
observacao_envio_ccn: z.string().optional(),
deficiencias: z.string().optional(),
grau_instrucao: z.string().optional(),
cidade_nat_id: z.number().optional(),
tb_tipologradouro_id: z.number().optional(),
unidade: z.string().optional(),
numero_end: z.string().optional(),
});

View file

@ -1,18 +1,16 @@
// Função que envolve qualquer ação assíncrona para capturar e tratar erros do cliente
import { withClientErrorHandler } from "@/actions/withClientErrorHandler/withClientErrorHandler";
import { withClientErrorHandler } from "@/actions/withClientErrorHandler/withClientErrorHandler";
// Função que salva os dados da cidade via API (ou mock)
import { GCidadeSaveData } from "../../_data/GCidade/GCidadeSaveData";
import { GCidadeSaveData } from "../../_data/GCidade/GCidadeSaveData";
// Interface tipada da cidade
import GCidadeInterface from "../../_interfaces/GCidadeInterface";
import GCidadeInterface from "../../_interfaces/GCidadeInterface";
// Função assíncrona que executa o salvamento de uma cidade
async function executeGCidadeSaveService(data: GCidadeInterface) {
// Chama a função que salva os dados da cidade
const response = await GCidadeSaveData(data);
// Chama a função que salva os dados da cidade
const response = await GCidadeSaveData(data);
// Retorna a resposta do salvamento
return response;

View file

@ -1,9 +1,7 @@
import GUfIndexData from "../../_data/GUf/GUfIndexData";
export default async function GUfIndexService() {
const response = await GUfIndexData();
const response = await GUfIndexData();
return response;
}
return response;
}

View file

@ -2,11 +2,11 @@ import { withClientErrorHandler } from "@/actions/withClientErrorHandler/withCli
import { TPessoaIndexData } from "../../_data/TPessoa/TPessoaIndexData";
async function executeTPessoaIndexService() {
const response = TPessoaIndexData();
const response = TPessoaIndexData();
return response;
return response;
}
export const TPessoaIndexService = withClientErrorHandler(executeTPessoaIndexService);
export const TPessoaIndexService = withClientErrorHandler(
executeTPessoaIndexService,
);

View file

@ -0,0 +1,12 @@
import { withClientErrorHandler } from "@/actions/withClientErrorHandler/withClientErrorHandler";
import { TPessoaJuridicaIndexData } from "../../../_data/TPessoa/juridica/TPessoaJuridicaIndexData";
async function executeTPessoaJuridicaIndexService() {
const response = TPessoaJuridicaIndexData();
return response;
}
export const TPessoaJuridicaIndexService = withClientErrorHandler(
executeTPessoaJuridicaIndexService,
);

View file

@ -0,0 +1,12 @@
import { withClientErrorHandler } from "@/actions/withClientErrorHandler/withClientErrorHandler";
import { TPessoaRepresentanteIndexData } from "../../_data/TPessoaRepresentante/TPessoaRepresentanteIndexData";
async function executeTPessoaRepresentanteIndexService() {
const response = TPessoaRepresentanteIndexData();
return response;
}
export const TPessoaRepresentanteIndexService = withClientErrorHandler(
executeTPessoaRepresentanteIndexService,
);

View file

@ -2,178 +2,199 @@
import React from "react";
import {
ColumnDef,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
SortingState,
ColumnFiltersState,
VisibilityState,
RowSelectionState,
ColumnDef,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
SortingState,
ColumnFiltersState,
VisibilityState,
RowSelectionState,
} from "@tanstack/react-table";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuTrigger,
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { ChevronLeftIcon, ChevronRightIcon, EyeIcon } from "lucide-react";
// Tipagem genérica
export interface DataTableProps<TData> {
data: TData[];
columns: ColumnDef<TData, any>[];
filterColumn?: string; // Define qual coluna será usada para filtro
filterPlaceholder?: string;
onEdit?: (item: TData) => void;
onDelete?: (item: TData) => void;
data: TData[];
columns: ColumnDef<TData, any>[];
filterColumn?: string; // Define qual coluna será usada para filtro
filterPlaceholder?: string;
onEdit?: (item: TData) => void;
onDelete?: (item: TData) => void;
}
export function DataTable<TData>({
data,
columns,
filterColumn,
filterPlaceholder = "Buscar...",
onEdit,
onDelete,
}: DataTableProps<TData>) {
// Estados internos da tabela
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[],
);
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
// Configuração da tabela
const table = useReactTable({
data,
columns,
filterColumn,
filterPlaceholder = "Buscar...",
onEdit,
onDelete,
}: DataTableProps<TData>) {
// Estados internos da tabela
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
},
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
});
// Configuração da tabela
const table = useReactTable({
data,
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="space-y-4">
{/* Filtros e colunas */}
<div className="flex items-center gap-2">
{filterColumn && (
<Input
placeholder={filterPlaceholder}
value={
(table
.getColumn(filterColumn as string)
?.getFilterValue() as string) ?? ""
}
onChange={(e) =>
table
.getColumn(filterColumn as string)
?.setFilterValue(e.target.value)
}
className="w-full"
/>
)}
return (
<div className="space-y-4">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto cursor-pointer">
<EyeIcon /> 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)}
className="cursor-pointer"
>
{col.id}
</DropdownMenuCheckboxItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
{/* Filtros e colunas */}
<div className="flex items-center gap-2">
{filterColumn && (
<Input
placeholder={filterPlaceholder}
value={(table.getColumn(filterColumn as string)?.getFilterValue() as string) ?? ""}
onChange={(e) => table.getColumn(filterColumn as string)?.setFilterValue(e.target.value)}
className="w-full"
/>
)}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto cursor-pointer">
<EyeIcon /> 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)}
className="cursor-pointer"
>
{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} className="cursor-pointer">
{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>
{/* 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(),
)}
</TableBody>
</Table>
</div>
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id} className="cursor-pointer">
{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 gap-2">
<Button
className="cursor-pointer"
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<ChevronLeftIcon />
Anterior
</Button>
<Button
className="cursor-pointer"
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Próxima
<ChevronRightIcon />
</Button>
</div>
</div>
);
{/* Paginação */}
<div className="flex items-center justify-end gap-2">
<Button
className="cursor-pointer"
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<ChevronLeftIcon />
Anterior
</Button>
<Button
className="cursor-pointer"
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Próxima
<ChevronRightIcon />
</Button>
</div>
</div>
);
}

View file

@ -41,7 +41,6 @@ export const ResponseProvider: React.FC<{ children: ReactNode }> = ({
};
export const useResponse = () => {
const context = useContext(ResponseContext);
if (!context)
throw new Error("useResponse must be used within ResponseProvider");

View file

@ -8,7 +8,7 @@ import { toast } from "sonner";
export default function Response() {
const { response, clearResponse } = useResponse();
console.log(response)
console.log(response);
useEffect(() => {
switch (Number(response?.status)) {

View file

@ -148,6 +148,10 @@ const data = {
title: "Pessoas/Físicas",
url: "/cadastros/pessoa/fisica",
},
{
title: "Pessoas/Jurídica",
url: "/cadastros/pessoa/juridica",
},
],
},
{

View file

@ -1,17 +1,17 @@
"use client"
"use client";
import * as React from "react"
import { Command as CommandPrimitive } from "cmdk"
import { SearchIcon } from "lucide-react"
import * as React from "react";
import { Command as CommandPrimitive } from "cmdk";
import { SearchIcon } from "lucide-react";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
} from "@/components/ui/dialog";
function Command({
className,
@ -22,11 +22,11 @@ function Command({
data-slot="command"
className={cn(
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
className
className,
)}
{...props}
/>
)
);
}
function CommandDialog({
@ -37,10 +37,10 @@ function CommandDialog({
showCloseButton = true,
...props
}: React.ComponentProps<typeof Dialog> & {
title?: string
description?: string
className?: string
showCloseButton?: boolean
title?: string;
description?: string;
className?: string;
showCloseButton?: boolean;
}) {
return (
<Dialog {...props}>
@ -57,7 +57,7 @@ function CommandDialog({
</Command>
</DialogContent>
</Dialog>
)
);
}
function CommandInput({
@ -74,12 +74,12 @@ function CommandInput({
data-slot="command-input"
className={cn(
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
className
className,
)}
{...props}
/>
</div>
)
);
}
function CommandList({
@ -91,11 +91,11 @@ function CommandList({
data-slot="command-list"
className={cn(
"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
className
className,
)}
{...props}
/>
)
);
}
function CommandEmpty({
@ -107,7 +107,7 @@ function CommandEmpty({
className="py-6 text-center text-sm"
{...props}
/>
)
);
}
function CommandGroup({
@ -119,11 +119,11 @@ function CommandGroup({
data-slot="command-group"
className={cn(
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
className
className,
)}
{...props}
/>
)
);
}
function CommandSeparator({
@ -136,7 +136,7 @@ function CommandSeparator({
className={cn("bg-border -mx-1 h-px", className)}
{...props}
/>
)
);
}
function CommandItem({
@ -148,11 +148,11 @@ function CommandItem({
data-slot="command-item"
className={cn(
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
className,
)}
{...props}
/>
)
);
}
function CommandShortcut({
@ -164,11 +164,11 @@ function CommandShortcut({
data-slot="command-shortcut"
className={cn(
"text-muted-foreground ml-auto text-xs tracking-widest",
className
className,
)}
{...props}
/>
)
);
}
export {
@ -181,4 +181,4 @@ export {
CommandItem,
CommandShortcut,
CommandSeparator,
}
};

View file

@ -1,9 +1,9 @@
"use client"
"use client";
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import * as React from "react";
import * as TabsPrimitive from "@radix-ui/react-tabs";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
function Tabs({
className,
@ -15,7 +15,7 @@ function Tabs({
className={cn("flex flex-col gap-2", className)}
{...props}
/>
)
);
}
function TabsList({
@ -27,11 +27,11 @@ function TabsList({
data-slot="tabs-list"
className={cn(
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
className
className,
)}
{...props}
/>
)
);
}
function TabsTrigger({
@ -43,11 +43,11 @@ function TabsTrigger({
data-slot="tabs-trigger"
className={cn(
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
className,
)}
{...props}
/>
)
);
}
function TabsContent({
@ -60,7 +60,7 @@ function TabsContent({
className={cn("flex-1 outline-none", className)}
{...props}
/>
)
);
}
export { Tabs, TabsList, TabsTrigger, TabsContent }
export { Tabs, TabsList, TabsTrigger, TabsContent };

View file

@ -1,6 +1,6 @@
import * as React from "react"
import * as React from "react";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
return (
@ -8,11 +8,11 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
data-slot="textarea"
className={cn(
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
className,
)}
{...props}
/>
)
);
}
export { Textarea }
export { Textarea };

View file

@ -1,4 +1,4 @@
export enum Sexo {
M = 'Masculino',
F = 'Feminino',
}
M = "Masculino",
F = "Feminino",
}