Signed-off-by: Rodrigo B. de Sousa Martins <rodrigo@martinsevieira.adv.br>
790 lines
29 KiB
TypeScript
790 lines
29 KiB
TypeScript
import React, {
|
|
useState,
|
|
useEffect,
|
|
useCallback,
|
|
forwardRef,
|
|
useRef,
|
|
} from "react";
|
|
import { Dialog, Transition } from "@headlessui/react";
|
|
import { XMarkIcon } from "@heroicons/react/24/outline";
|
|
import { useForm } from "react-hook-form";
|
|
import axios from "axios";
|
|
import { Cliente } from "../../types/entities";
|
|
import { useClientes } from "../../hooks/useClientes";
|
|
import { ORGAOS_EMISSORES, PREVIDENCIA_OPTIONS } from "../../utils/constants";
|
|
|
|
const estadosCivis = [
|
|
{ value: "solteiro", label: "Solteiro(a)" },
|
|
{ value: "casado", label: "Casado(a)" },
|
|
{ value: "divorciado", label: "Divorciado(a)" },
|
|
{ value: "uniao_estavel", label: "União Estável" },
|
|
{ value: "viuvo", label: "Viúvo(a)" },
|
|
];
|
|
|
|
const estados = [
|
|
"AC",
|
|
"AL",
|
|
"AP",
|
|
"AM",
|
|
"BA",
|
|
"CE",
|
|
"DF",
|
|
"ES",
|
|
"GO",
|
|
"MA",
|
|
"MT",
|
|
"MS",
|
|
"MG",
|
|
"PA",
|
|
"PB",
|
|
"PR",
|
|
"PE",
|
|
"PI",
|
|
"RJ",
|
|
"RN",
|
|
"RS",
|
|
"RO",
|
|
"RR",
|
|
"SC",
|
|
"SP",
|
|
"SE",
|
|
"TO",
|
|
];
|
|
|
|
const VINCULOS_EMPREGATICIO = [
|
|
{ value: "mei", label: "MEI" },
|
|
{ value: "pj", label: "Pessoa Jurídica" },
|
|
{ value: "clt", label: "CLT" },
|
|
{ value: "autonomo", label: "Autônomo" },
|
|
{ value: "servidor_publico", label: "Servidor Público" },
|
|
];
|
|
|
|
// Atualizar estados para o formato de options
|
|
const estadosOptions = estados.map((uf) => ({ value: uf, label: uf }));
|
|
|
|
// Componente de input com forwardRef
|
|
const FormInput = forwardRef<HTMLInputElement, any>(
|
|
({ label, error, className = "", ...props }, ref) => (
|
|
<div className={`${className} relative`}>
|
|
<label className="block text-sm font-semibold text-gray-700 mb-2">
|
|
{label}
|
|
{props.required && <span className="text-red-500 ml-1">*</span>}
|
|
</label>
|
|
<input
|
|
ref={ref}
|
|
className={`
|
|
w-full px-4 py-3 rounded-lg
|
|
border border-gray-300
|
|
bg-white text-gray-900
|
|
transition duration-150 ease-in-out
|
|
placeholder-gray-400
|
|
focus:ring-2 focus:ring-blue-500 focus:border-transparent
|
|
hover:border-gray-400
|
|
${error ? "border-red-300 focus:ring-red-500" : ""}
|
|
`}
|
|
{...props}
|
|
/>
|
|
{error && (
|
|
<div className="absolute -bottom-5 left-0">
|
|
<p className="text-xs text-red-500">{error}</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
),
|
|
);
|
|
|
|
// Componente de select com forwardRef
|
|
const FormSelect = forwardRef<HTMLSelectElement, any>(
|
|
({ label, error, options, className = "", ...props }, ref) => (
|
|
<div className={`${className} relative`}>
|
|
<label className="block text-sm font-semibold text-gray-700 mb-2">
|
|
{label}
|
|
{props.required && <span className="text-red-500 ml-1">*</span>}
|
|
</label>
|
|
<select
|
|
ref={ref}
|
|
className={`
|
|
w-full px-4 py-3 rounded-lg
|
|
border border-gray-300
|
|
bg-white text-gray-900
|
|
transition duration-150 ease-in-out
|
|
focus:ring-2 focus:ring-blue-500 focus:border-transparent
|
|
hover:border-gray-400
|
|
appearance-none
|
|
${error ? "border-red-300 focus:ring-red-500" : ""}
|
|
`}
|
|
{...props}
|
|
>
|
|
<option value="">Selecione...</option>
|
|
{options.map((opt) => (
|
|
<option key={opt.value} value={opt.value}>
|
|
{opt.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
{error && (
|
|
<div className="absolute -bottom-5 left-0">
|
|
<p className="text-xs text-red-500">{error}</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
),
|
|
);
|
|
|
|
// Componente de checkbox com forwardRef
|
|
const FormCheckbox = forwardRef<HTMLInputElement, any>(
|
|
({ label, ...props }, ref) => (
|
|
<label className="flex items-center space-x-3 cursor-pointer group">
|
|
<input
|
|
type="checkbox"
|
|
ref={ref}
|
|
className="
|
|
w-5 h-5 rounded
|
|
border-gray-300 text-blue-600
|
|
focus:ring-2 focus:ring-blue-500
|
|
transition duration-150 ease-in-out
|
|
cursor-pointer
|
|
"
|
|
{...props}
|
|
/>
|
|
<span className="text-sm text-gray-700 group-hover:text-gray-900">
|
|
{label}
|
|
</span>
|
|
</label>
|
|
),
|
|
);
|
|
|
|
// Adicionar displayName para os componentes
|
|
FormInput.displayName = "FormInput";
|
|
FormSelect.displayName = "FormSelect";
|
|
FormCheckbox.displayName = "FormCheckbox";
|
|
|
|
interface ClienteModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
cliente: Cliente | null;
|
|
onSuccess?: () => void;
|
|
initialTab?: number;
|
|
}
|
|
|
|
export const ClienteModal: React.FC<ClienteModalProps> = ({
|
|
isOpen,
|
|
onClose,
|
|
cliente,
|
|
onSuccess,
|
|
initialTab = 0,
|
|
}) => {
|
|
const [activeTab, setActiveTab] = useState(initialTab);
|
|
const [isLoadingCep, setIsLoadingCep] = useState(false);
|
|
// Novo estado para controlar os valores do endereço
|
|
const [enderecoState, setEnderecoState] = useState({
|
|
endereco: "",
|
|
bairro: "",
|
|
cidade: "",
|
|
estado: "",
|
|
preenchido: false,
|
|
});
|
|
|
|
const {
|
|
register,
|
|
handleSubmit,
|
|
formState: { errors },
|
|
reset,
|
|
setValue,
|
|
watch,
|
|
getValues,
|
|
} = useForm();
|
|
const { createCliente, updateCliente } = useClientes();
|
|
const formPopulated = useRef(false);
|
|
|
|
// Monitorar o valor do CEP para autopreenchimento
|
|
const cepValue = watch("cep");
|
|
|
|
// Função para buscar dados do CEP - versão corrigida
|
|
const fetchAddressByCep = useCallback(async (cep: string) => {
|
|
if (!cep || cep.length !== 8) return;
|
|
|
|
try {
|
|
setIsLoadingCep(true);
|
|
const formattedCep = cep.replace(/\D/g, ""); // Remove caracteres não numéricos
|
|
const response = await axios.get(
|
|
`https://viacep.com.br/ws/${formattedCep}/json/`,
|
|
);
|
|
|
|
if (response.data && !response.data.erro) {
|
|
const endereco = response.data.logradouro || "";
|
|
const bairro = response.data.bairro || "";
|
|
const cidade = response.data.localidade || "";
|
|
const estado = response.data.uf || "";
|
|
|
|
// Só preencher endereço/bairro se vier valor da API
|
|
if (endereco) setValue("endereco", endereco, { shouldValidate: true });
|
|
if (bairro) setValue("bairro", bairro, { shouldValidate: true });
|
|
setValue("cidade", cidade, { shouldValidate: true });
|
|
setValue("estado", estado, { shouldValidate: true });
|
|
|
|
// Atualizar estado apenas para cidade/estado (não controlar endereço/bairro)
|
|
setEnderecoState({
|
|
endereco: endereco,
|
|
bairro: bairro,
|
|
cidade: cidade,
|
|
estado: estado,
|
|
preenchido: false, // Não usar mais para travar campos
|
|
});
|
|
|
|
// Focar no próximo campo apenas se o usuário não estiver digitando ativamente
|
|
// Esta alteração evita interromper o fluxo natural de preenchimento
|
|
const activeElement = document.activeElement;
|
|
const isUserTyping =
|
|
activeElement &&
|
|
(activeElement.tagName === 'INPUT' || activeElement.tagName === 'SELECT') &&
|
|
activeElement !== document.querySelector('input[name="cep"]');
|
|
|
|
// Só focar no campo número se o usuário não estiver digitando em outro campo
|
|
if (!isUserTyping) {
|
|
setTimeout(() => {
|
|
const numeroInput = document.querySelector(
|
|
'input[name="numero"]'
|
|
) as HTMLInputElement;
|
|
if (numeroInput) numeroInput.focus();
|
|
}, 100);
|
|
}
|
|
} else {
|
|
// Se CEP não encontrado, limpar campos e permitir edição manual
|
|
setEnderecoState({
|
|
endereco: "",
|
|
bairro: "",
|
|
cidade: "",
|
|
estado: "",
|
|
preenchido: false,
|
|
});
|
|
setValue("endereco", "");
|
|
setValue("bairro", "");
|
|
setValue("cidade", "");
|
|
setValue("estado", "");
|
|
console.error("CEP não encontrado");
|
|
alert("CEP não encontrado. Por favor, verifique o CEP informado.");
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro ao buscar CEP:", error);
|
|
alert("Erro ao buscar informações do CEP. Por favor, tente novamente.");
|
|
} finally {
|
|
setIsLoadingCep(false);
|
|
}
|
|
}, [setValue]);
|
|
|
|
// Efeito para monitorar mudanças no CEP com debounce otimizado
|
|
const [autoFetchEnabled, setAutoFetchEnabled] = useState(true);
|
|
|
|
useEffect(() => {
|
|
if (!autoFetchEnabled) return;
|
|
|
|
// Limpar qualquer timer existente
|
|
const timer = setTimeout(() => {
|
|
const cleanCep = cepValue?.replace(/\D/g, "");
|
|
if (cleanCep && cleanCep.length === 8) {
|
|
fetchAddressByCep(cleanCep);
|
|
setAutoFetchEnabled(false); // Desativar busca automática após primeiro preenchimento
|
|
setTimeout(() => setAutoFetchEnabled(true), 1000); // Reativar depois de 1 segundo
|
|
}
|
|
}, 200); // Reduzir debounce para resposta mais rápida
|
|
|
|
return () => clearTimeout(timer);
|
|
}, [cepValue, fetchAddressByCep, autoFetchEnabled]);
|
|
|
|
// Função para preencher o formulário com os dados do cliente
|
|
const populateForm = useCallback(
|
|
(clienteData: Cliente) => {
|
|
console.log("Populando formulário com:", clienteData);
|
|
Object.keys(clienteData).forEach((key) => {
|
|
if (key === "dataNascimento" && clienteData[key]) {
|
|
// Formatar a data para o formato YYYY-MM-DD esperado pelo input type="date"
|
|
const data = new Date(clienteData[key]);
|
|
setValue(key, data.toISOString().split("T")[0]);
|
|
} else if (
|
|
key !== "id" &&
|
|
key !== "dataCriacao" &&
|
|
key !== "processos" &&
|
|
key !== "filhos"
|
|
) {
|
|
setValue(key, clienteData[key]);
|
|
}
|
|
});
|
|
},
|
|
[setValue],
|
|
);
|
|
|
|
// Efeito para carregar os dados quando o modal abrir ou o cliente mudar
|
|
useEffect(() => {
|
|
if (isOpen && cliente && !formPopulated.current) {
|
|
console.log("Populando formulário com:", cliente);
|
|
Object.keys(cliente).forEach((key) => {
|
|
if (key === "dataNascimento" && cliente[key]) {
|
|
const data = new Date(cliente[key]);
|
|
setValue(key, data.toISOString().split("T")[0]);
|
|
} else if (
|
|
key !== "id" &&
|
|
key !== "dataCriacao" &&
|
|
key !== "processos" &&
|
|
key !== "filhos"
|
|
) {
|
|
setValue(key, cliente[key]);
|
|
}
|
|
});
|
|
formPopulated.current = true;
|
|
}
|
|
|
|
if (!isOpen) {
|
|
formPopulated.current = false;
|
|
reset();
|
|
}
|
|
}, [isOpen, cliente, setValue, reset]);
|
|
|
|
const onSubmit = async (data: Partial<Cliente>) => {
|
|
try {
|
|
// Converter nome para maiúsculas
|
|
const cleanData = Object.fromEntries(
|
|
Object.entries(data).filter(([_, value]) => {
|
|
if (value === "") return false;
|
|
if (value === null) return false;
|
|
if (value === undefined) return false;
|
|
return true;
|
|
}),
|
|
);
|
|
|
|
// Formatar campos específicos e converter nome para maiúsculas
|
|
const formattedData = {
|
|
...cleanData,
|
|
nome: cleanData.nome?.toUpperCase(),
|
|
nomeMae: cleanData.nomeMae?.toUpperCase(),
|
|
nomePai: cleanData.nomePai?.toUpperCase(),
|
|
conjugeNome: cleanData.conjugeNome?.toUpperCase(),
|
|
// Converter campos numéricos
|
|
renda: cleanData.renda ? Number(cleanData.renda) : null,
|
|
// Formatar data de nascimento para ISO String
|
|
dataNascimento: cleanData.dataNascimento
|
|
? new Date(cleanData.dataNascimento).toISOString()
|
|
: null,
|
|
// Garantir campos obrigatórios
|
|
email: cleanData.email,
|
|
telefone: cleanData.telefone,
|
|
cpfCnpj: cleanData.cpfCnpj,
|
|
endereco: cleanData.endereco,
|
|
cep: cleanData.cep,
|
|
numero: cleanData.numero,
|
|
bairro: cleanData.bairro,
|
|
cidade: cleanData.cidade,
|
|
estado: cleanData.estado,
|
|
// Converter booleanos
|
|
declaraIR: Boolean(cleanData.declaraIR),
|
|
contribuiPrevidencia: Boolean(cleanData.contribuiPrevidencia),
|
|
usaDNI: Boolean(cleanData.usaDNI),
|
|
possuiFilhos: Boolean(cleanData.possuiFilhos),
|
|
};
|
|
|
|
console.log("Dados enviados:", formattedData); // Debug
|
|
|
|
if (cliente) {
|
|
await updateCliente({
|
|
id: cliente.id,
|
|
data: formattedData,
|
|
});
|
|
} else {
|
|
await createCliente(formattedData);
|
|
}
|
|
|
|
onSuccess?.();
|
|
onClose();
|
|
} catch (error) {
|
|
console.error("Erro detalhado:", error);
|
|
if (axios.isAxiosError(error)) {
|
|
alert(error.response?.data?.error || "Erro ao salvar cliente");
|
|
} else {
|
|
alert("Erro ao salvar cliente");
|
|
}
|
|
}
|
|
};
|
|
|
|
// Reset form on modal close
|
|
const handleClose = () => {
|
|
reset();
|
|
onClose();
|
|
};
|
|
|
|
const tabs = [
|
|
{ id: "pessoal", label: "Dados Pessoais" },
|
|
{ id: "endereco", label: "Endereço" },
|
|
{ id: "financeiro", label: "Dados Financeiros" },
|
|
{ id: "familia", label: "Filiação" },
|
|
];
|
|
|
|
return (
|
|
<Transition show={isOpen} as={React.Fragment}>
|
|
<Dialog onClose={handleClose} className="relative z-50">
|
|
<Transition.Child
|
|
enter="ease-out duration-300"
|
|
enterFrom="opacity-0"
|
|
enterTo="opacity-100"
|
|
leave="ease-in duration-200"
|
|
leaveFrom="opacity-100"
|
|
leaveTo="opacity-0"
|
|
>
|
|
<div className="fixed inset-0 bg-black/30 backdrop-blur-sm" />
|
|
</Transition.Child>
|
|
|
|
<div className="fixed inset-0 overflow-y-auto">
|
|
<div className="flex min-h-full items-center justify-center p-4">
|
|
<Transition.Child
|
|
enter="ease-out duration-300"
|
|
enterFrom="opacity-0 scale-95"
|
|
enterTo="opacity-100 scale-100"
|
|
leave="ease-in duration-200"
|
|
leaveFrom="opacity-100 scale-100"
|
|
leaveTo="opacity-0 scale-95"
|
|
>
|
|
<Dialog.Panel className="mx-auto max-w-4xl w-full bg-white dark:bg-gray-800 rounded-xl shadow-2xl">
|
|
{/* Header com abas */}
|
|
<div className="border-b border-gray-200 dark:border-gray-700 p-6 bg-gray-50 dark:bg-gray-900 rounded-t-xl">
|
|
<div className="flex justify-between items-center mb-6">
|
|
<Dialog.Title className="text-2xl font-bold text-gray-900 dark:text-white">
|
|
{cliente ? "Editar Cliente" : "Novo Cliente"}
|
|
</Dialog.Title>
|
|
<button
|
|
onClick={handleClose}
|
|
className="text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400"
|
|
>
|
|
<XMarkIcon className="h-6 w-6" />
|
|
</button>
|
|
</div>
|
|
<div className="flex space-x-1">
|
|
{tabs.map((tab, idx) => (
|
|
<button
|
|
key={tab.id}
|
|
onClick={() => setActiveTab(idx)}
|
|
className={`
|
|
flex items-center px-6 py-3 text-sm font-medium rounded-t-lg
|
|
transition-all duration-200 ease-in-out
|
|
${
|
|
activeTab === idx
|
|
? "bg-white text-blue-600 border-t-2 border-blue-600 shadow-sm"
|
|
: "text-gray-500 hover:text-gray-700 hover:bg-gray-100"
|
|
}
|
|
`}
|
|
>
|
|
{tab.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Formulário */}
|
|
<form
|
|
onSubmit={handleSubmit(onSubmit)}
|
|
className="p-6 bg-white dark:bg-gray-800"
|
|
>
|
|
<div className="space-y-8 mb-8">
|
|
{/* Conteúdo das abas */}
|
|
{activeTab === 0 && (
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<FormInput
|
|
label="Nome"
|
|
{...register("nome", {
|
|
required: "Nome é obrigatório",
|
|
})}
|
|
error={errors.nome?.message}
|
|
/>
|
|
<FormInput
|
|
label="CPF/CNPJ"
|
|
{...register("cpfCnpj", {
|
|
required: "CPF/CNPJ é obrigatório",
|
|
})}
|
|
error={errors.cpfCnpj?.message}
|
|
/>
|
|
<div className="col-span-2 mb-4">
|
|
<FormCheckbox
|
|
label="Usar DNI (Documento Nacional de Identidade)"
|
|
{...register("usaDNI")}
|
|
onChange={(e) => {
|
|
if (e.target.checked) {
|
|
setValue("rg", watch("cpfCnpj"));
|
|
}
|
|
}}
|
|
/>
|
|
</div>
|
|
<FormInput
|
|
label="RG"
|
|
{...register("rg")}
|
|
disabled={watch("usaDNI")}
|
|
/>
|
|
<div className="col-span-2 grid grid-cols-2 gap-4">
|
|
<FormSelect
|
|
label="Órgão Emissor"
|
|
{...register("orgaoEmissor")}
|
|
options={ORGAOS_EMISSORES}
|
|
/>
|
|
<FormSelect
|
|
label="Estado do RG"
|
|
{...register("estadoRg")}
|
|
options={estadosOptions}
|
|
/>
|
|
</div>
|
|
<FormInput
|
|
label="Email"
|
|
type="email"
|
|
{...register("email", {
|
|
required: "Email é obrigatório",
|
|
})}
|
|
error={errors.email?.message}
|
|
/>
|
|
<FormInput
|
|
label="Telefone"
|
|
{...register("telefone", {
|
|
required: "Telefone é obrigatório",
|
|
})}
|
|
error={errors.telefone?.message}
|
|
/>
|
|
<FormSelect
|
|
label="Estado Civil"
|
|
{...register("estadoCivil")}
|
|
options={estadosCivis}
|
|
/>
|
|
<FormInput
|
|
label="Instagram"
|
|
{...register("instagram")}
|
|
placeholder="@"
|
|
/>
|
|
<FormInput
|
|
label="Profissão"
|
|
{...register("profissao")}
|
|
/>
|
|
<FormInput
|
|
label="Data de Nascimento"
|
|
type="date"
|
|
{...register("dataNascimento")}
|
|
max={new Date().toISOString().split("T")[0]} // Impede datas futuras
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === 1 && (
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="relative">
|
|
<FormInput
|
|
label="CEP"
|
|
{...register("cep", {
|
|
required: "CEP é obrigatório",
|
|
pattern: {
|
|
value: /^[0-9]{5}-?[0-9]{3}$/,
|
|
message: "CEP inválido",
|
|
},
|
|
})}
|
|
error={errors.cep?.message}
|
|
placeholder="00000-000"
|
|
maxLength={9}
|
|
value={watch("cep") || ""}
|
|
onChange={(e) => {
|
|
// Formatação automática do CEP (00000-000)
|
|
let value = e.target.value.replace(/\D/g, "");
|
|
if (value.length > 5) {
|
|
value =
|
|
value.substring(0, 5) +
|
|
"-" +
|
|
value.substring(5, 8);
|
|
}
|
|
setValue("cep", value, { shouldValidate: true });
|
|
}}
|
|
onBlur={(e) => {
|
|
// Forçar busca ao sair do campo
|
|
const cleanCep = e.target.value.replace(/\D/g, "");
|
|
if (cleanCep && cleanCep.length === 8) {
|
|
fetchAddressByCep(cleanCep);
|
|
}
|
|
}}
|
|
/>
|
|
{isLoadingCep && (
|
|
<div className="absolute right-3 top-9">
|
|
<svg
|
|
className="animate-spin h-5 w-5 text-blue-500"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<circle
|
|
className="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
strokeWidth="4"
|
|
></circle>
|
|
<path
|
|
className="opacity-75"
|
|
fill="currentColor"
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
></path>
|
|
</svg>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<FormInput
|
|
className="md:col-span-2"
|
|
label="Endereço"
|
|
{...register("endereco", {
|
|
required: "Endereço é obrigatório",
|
|
})}
|
|
error={errors.endereco?.message}
|
|
// Remover value/controlado por enderecoState, deixar react-hook-form controlar
|
|
onChange={(e) => {
|
|
setValue("endereco", e.target.value, { shouldValidate: true });
|
|
}}
|
|
/>
|
|
<FormInput
|
|
label="Número"
|
|
{...register("numero", {
|
|
required: "Número é obrigatório",
|
|
})}
|
|
error={errors.numero?.message}
|
|
/>
|
|
<FormInput
|
|
label="Complemento"
|
|
{...register("complemento")}
|
|
/>
|
|
<FormInput
|
|
label="Bairro"
|
|
{...register("bairro", {
|
|
required: "Bairro é obrigatório",
|
|
})}
|
|
error={errors.bairro?.message}
|
|
// Remover value/controlado por enderecoState, deixar react-hook-form controlar
|
|
onChange={(e) => {
|
|
setValue("bairro", e.target.value, { shouldValidate: true });
|
|
}}
|
|
/>
|
|
<FormInput
|
|
label="Cidade"
|
|
{...register("cidade", {
|
|
required: "Cidade é obrigatória",
|
|
})}
|
|
error={errors.cidade?.message}
|
|
value={typeof watch("cidade") === "string" ? watch("cidade") : enderecoState.cidade}
|
|
onChange={(e) => {
|
|
setValue("cidade", e.target.value, { shouldValidate: true });
|
|
setEnderecoState(prev => ({...prev, cidade: e.target.value}));
|
|
}}
|
|
/>
|
|
<FormSelect
|
|
label="Estado"
|
|
{...register("estado", {
|
|
required: "Estado é obrigatório",
|
|
})}
|
|
options={estadosOptions}
|
|
error={errors.estado?.message}
|
|
value={typeof watch("estado") === "string" ? watch("estado") : enderecoState.estado}
|
|
onChange={(e) => {
|
|
setValue("estado", e.target.value, { shouldValidate: true });
|
|
setEnderecoState(prev => ({...prev, estado: e.target.value}));
|
|
}}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === 2 && (
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<FormSelect
|
|
label="Vínculo Empregatício"
|
|
{...register("vinculoEmpregaticio")}
|
|
options={VINCULOS_EMPREGATICIO}
|
|
/>
|
|
<FormInput
|
|
label="Renda Mensal"
|
|
type="number"
|
|
step="0.01"
|
|
{...register("renda")}
|
|
/>
|
|
<div className="col-span-2">
|
|
<FormCheckbox
|
|
label="Declara Imposto de Renda"
|
|
{...register("declaraIR")}
|
|
/>
|
|
</div>
|
|
<FormSelect
|
|
label="Tipo de Previdência"
|
|
{...register("tipoPrevidencia")}
|
|
options={PREVIDENCIA_OPTIONS}
|
|
/>
|
|
<div className="col-span-2">
|
|
<FormCheckbox
|
|
label="Contribui para Previdência"
|
|
{...register("contribuiPrevidencia")}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === 3 && (
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="col-span-2 mb-4">
|
|
<FormCheckbox
|
|
label="Possui Filhos?"
|
|
{...register("possuiFilhos")}
|
|
/>
|
|
</div>
|
|
<FormInput
|
|
label="Nome do Pai"
|
|
{...register("nomePai")}
|
|
/>
|
|
<FormInput
|
|
label="Nome da Mãe"
|
|
{...register("nomeMae")}
|
|
/>
|
|
<FormInput
|
|
label="Nome do Cônjuge"
|
|
{...register("conjugeNome")}
|
|
/>
|
|
<FormInput
|
|
label="CPF do Cônjuge"
|
|
{...register("conjugeCpf")}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Botões de ação */}
|
|
<div className="flex justify-end space-x-3 pt-6 border-t">
|
|
<button
|
|
type="button"
|
|
onClick={handleClose}
|
|
className="
|
|
px-6 py-2.5 text-sm font-medium
|
|
text-gray-700 bg-white
|
|
border border-gray-300 rounded-lg
|
|
hover:bg-gray-50
|
|
focus:outline-none focus:ring-2 focus:ring-gray-200
|
|
transition duration-200
|
|
"
|
|
>
|
|
Cancelar
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
className="
|
|
px-6 py-2.5 text-sm font-medium
|
|
text-white bg-blue-600 rounded-lg
|
|
hover:bg-blue-700
|
|
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
|
|
transition duration-200
|
|
"
|
|
>
|
|
{cliente ? "Salvar" : "Criar"}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</Dialog.Panel>
|
|
</Transition.Child>
|
|
</div>
|
|
</div>
|
|
</Dialog>
|
|
</Transition>
|
|
);
|
|
};
|