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( ({ label, error, className = "", ...props }, ref) => (
{error && (

{error}

)}
), ); // Componente de select com forwardRef const FormSelect = forwardRef( ({ label, error, options, className = "", ...props }, ref) => (
{error && (

{error}

)}
), ); // Componente de checkbox com forwardRef const FormCheckbox = forwardRef( ({ label, ...props }, ref) => ( ), ); // 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 = ({ 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) => { 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 (
{/* Header com abas */}
{cliente ? "Editar Cliente" : "Novo Cliente"}
{tabs.map((tab, idx) => ( ))}
{/* Formulário */}
{/* Conteúdo das abas */} {activeTab === 0 && (
{ if (e.target.checked) { setValue("rg", watch("cpfCnpj")); } }} />
)} {activeTab === 1 && (
{ // 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 && (
)}
{ setValue("endereco", e.target.value, { shouldValidate: true }); }} /> { setValue("bairro", e.target.value, { shouldValidate: true }); }} /> { setValue("cidade", e.target.value, { shouldValidate: true }); setEnderecoState(prev => ({...prev, cidade: e.target.value})); }} /> { setValue("estado", e.target.value, { shouldValidate: true }); setEnderecoState(prev => ({...prev, estado: e.target.value})); }} />
)} {activeTab === 2 && (
)} {activeTab === 3 && (
)}
{/* Botões de ação */}
); };