391 lines
13 KiB
TypeScript
391 lines
13 KiB
TypeScript
import React, { useState } from "react";
|
|
import { useParams, useNavigate } from "react-router-dom";
|
|
import { useClientes } from "../../hooks/useClientes";
|
|
import { Tab } from "@headlessui/react";
|
|
import {
|
|
ArrowLeftIcon,
|
|
PencilIcon,
|
|
UserIcon,
|
|
HomeIcon,
|
|
BanknotesIcon,
|
|
UserGroupIcon,
|
|
DocumentTextIcon,
|
|
} from "@heroicons/react/24/outline";
|
|
import { ClienteModal } from "./ClienteModal";
|
|
import {
|
|
formatCPF,
|
|
formatTelefone,
|
|
formatNome,
|
|
formatEmail,
|
|
formatMoeda,
|
|
formatTextoSimples,
|
|
formatData,
|
|
} from "../../utils/formatters";
|
|
|
|
function classNames(...classes: string[]) {
|
|
return classes.filter(Boolean).join(" ");
|
|
}
|
|
|
|
const tabs = [
|
|
{ id: "info", name: "Informações Básicas", icon: UserIcon },
|
|
{ id: "endereco", name: "Endereço", icon: HomeIcon },
|
|
{ id: "financeiro", name: "Informações Financeiras", icon: BanknotesIcon },
|
|
{ id: "familia", name: "Informações Familiares", icon: UserGroupIcon },
|
|
{ id: "processos", name: "Processos", icon: DocumentTextIcon },
|
|
];
|
|
|
|
interface InfoItemProps {
|
|
label: string;
|
|
value: string;
|
|
onEdit?: () => void;
|
|
isEditable?: boolean;
|
|
}
|
|
|
|
const InfoItem: React.FC<InfoItemProps> = ({
|
|
label,
|
|
value,
|
|
onEdit,
|
|
isEditable = true,
|
|
}) => (
|
|
<div className="relative group">
|
|
<dt className="text-sm font-medium text-gray-500">{label}</dt>
|
|
<dd className="mt-1 text-sm text-gray-900 flex items-center justify-between">
|
|
<span>{value}</span>
|
|
{isEditable && (
|
|
<button
|
|
onClick={onEdit}
|
|
className="opacity-0 group-hover:opacity-100 transition-opacity p-1 hover:bg-gray-100 rounded"
|
|
>
|
|
<PencilIcon className="h-4 w-4 text-blue-600" />
|
|
</button>
|
|
)}
|
|
</dd>
|
|
</div>
|
|
);
|
|
|
|
export const ClienteDetalhes: React.FC = () => {
|
|
const { id } = useParams<{ id: string }>();
|
|
const navigate = useNavigate();
|
|
const { clientes, isLoading } = useClientes();
|
|
const [selectedTab, setSelectedTab] = useState(0);
|
|
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
|
const [editingField, setEditingField] = useState<string | null>(null);
|
|
const cliente = clientes.find((c) => c.id === id);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex justify-center items-center h-64">
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!cliente) {
|
|
return <div>Cliente não encontrado</div>;
|
|
}
|
|
|
|
// Funções de formatação simplificadas
|
|
const formataCurrency = (valor: any): string => {
|
|
if (!valor) return "Não informado";
|
|
try {
|
|
return new Intl.NumberFormat("pt-BR", {
|
|
style: "currency",
|
|
currency: "BRL",
|
|
}).format(Number(valor));
|
|
} catch {
|
|
return "Não informado";
|
|
}
|
|
};
|
|
|
|
const formataTexto = (texto: any): string => {
|
|
if (!texto && texto !== false) return "Não informado";
|
|
if (typeof texto === "boolean") return texto ? "Sim" : "Não";
|
|
return String(texto);
|
|
};
|
|
|
|
const getTabForField = (field: string | null) => {
|
|
switch (field) {
|
|
case "nome":
|
|
case "cpfCnpj":
|
|
case "rg":
|
|
case "email":
|
|
case "telefone":
|
|
case "instagram":
|
|
case "estadoCivil":
|
|
case "dataNascimento":
|
|
return 0;
|
|
case "cep":
|
|
case "endereco":
|
|
case "complemento":
|
|
case "bairro":
|
|
case "cidade":
|
|
case "estado":
|
|
return 1;
|
|
case "profissao":
|
|
case "vinculoEmpregaticio":
|
|
case "renda":
|
|
case "declaraIR":
|
|
case "tipoPrevidencia":
|
|
case "contribuiPrevidencia":
|
|
return 2;
|
|
case "nomePai":
|
|
case "nomeMae":
|
|
case "conjugeNome":
|
|
case "conjugeCpf":
|
|
return 3;
|
|
default:
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center space-x-4">
|
|
<button
|
|
onClick={() => navigate("/clientes")}
|
|
className="p-2 hover:bg-gray-100 rounded-full"
|
|
>
|
|
<ArrowLeftIcon className="h-5 w-5 text-gray-500" />
|
|
</button>
|
|
<div>
|
|
<h1 className="text-2xl font-semibold text-gray-800">
|
|
{formatNome(cliente.nome)}
|
|
</h1>
|
|
<p className="text-sm text-gray-500">
|
|
Cadastrado em {new Date(cliente.dataCriacao).toLocaleDateString()}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={() => setIsEditModalOpen(true)}
|
|
className="flex items-center px-4 py-2 text-sm text-blue-600 hover:bg-blue-50 rounded-lg"
|
|
>
|
|
<PencilIcon className="h-4 w-4 mr-2" />
|
|
Editar
|
|
</button>
|
|
</div>
|
|
|
|
{/* Tabs */}
|
|
<Tab.Group selectedIndex={selectedTab} onChange={setSelectedTab}>
|
|
<Tab.List className="flex space-x-1 rounded-xl bg-blue-900/20 p-1">
|
|
{tabs.map((tab) => (
|
|
<Tab
|
|
key={tab.id}
|
|
className={({ selected }) =>
|
|
classNames(
|
|
"w-full rounded-lg py-2.5 text-sm font-medium leading-5",
|
|
"flex items-center justify-center",
|
|
selected
|
|
? "bg-white text-blue-700 shadow"
|
|
: "text-gray-600 hover:bg-white/[0.12] hover:text-blue-600",
|
|
)
|
|
}
|
|
>
|
|
<tab.icon className="h-4 w-4 mr-2" />
|
|
{tab.name}
|
|
</Tab>
|
|
))}
|
|
</Tab.List>
|
|
<Tab.Panels className="mt-2">
|
|
{/* Informações Básicas */}
|
|
<Tab.Panel className="bg-white rounded-xl p-6">
|
|
<dl className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<InfoItem
|
|
label="Nome"
|
|
value={formatNome(cliente.nome)}
|
|
onEdit={() => {
|
|
setEditingField("nome");
|
|
setIsEditModalOpen(true);
|
|
}}
|
|
/>
|
|
<InfoItem
|
|
label="CPF/CNPJ"
|
|
value={formatCPF(cliente.cpfCnpj)}
|
|
onEdit={() => {
|
|
setEditingField("cpfCnpj");
|
|
setIsEditModalOpen(true);
|
|
}}
|
|
/>
|
|
<InfoItem label="RG" value={formatTextoSimples(cliente.rg)} />
|
|
<InfoItem label="Email" value={formatEmail(cliente.email)} />
|
|
<InfoItem
|
|
label="Telefone"
|
|
value={formatTelefone(cliente.telefone)}
|
|
/>
|
|
<InfoItem
|
|
label="Instagram"
|
|
value={formatTextoSimples(cliente.instagram)}
|
|
/>
|
|
<InfoItem
|
|
label="Estado Civil"
|
|
value={formatTextoSimples(cliente.estadoCivil)}
|
|
/>
|
|
<InfoItem
|
|
label="Data de Nascimento"
|
|
value={formatData(cliente.dataNascimento)}
|
|
/>
|
|
</dl>
|
|
</Tab.Panel>
|
|
|
|
{/* Endereço */}
|
|
<Tab.Panel className="bg-white rounded-xl p-6">
|
|
<dl className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<InfoItem label="CEP" value={formataTexto(cliente.cep)} />
|
|
<InfoItem
|
|
label="Endereço"
|
|
value={formataTexto(`${cliente.endereco}, ${cliente.numero}`)}
|
|
/>
|
|
<InfoItem
|
|
label="Complemento"
|
|
value={formataTexto(cliente.complemento)}
|
|
/>
|
|
<InfoItem label="Bairro" value={formataTexto(cliente.bairro)} />
|
|
<InfoItem label="Cidade" value={formataTexto(cliente.cidade)} />
|
|
<InfoItem label="Estado" value={formataTexto(cliente.estado)} />
|
|
</dl>
|
|
</Tab.Panel>
|
|
|
|
{/* Informações Financeiras */}
|
|
<Tab.Panel className="bg-white rounded-xl p-6">
|
|
<dl className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<InfoItem
|
|
label="Profissão"
|
|
value={formatTextoSimples(cliente.profissao)}
|
|
/>
|
|
<InfoItem
|
|
label="Vínculo"
|
|
value={formatTextoSimples(cliente.vinculoEmpregaticio)}
|
|
/>
|
|
<InfoItem
|
|
label="Renda Mensal"
|
|
value={formatMoeda(Number(cliente.renda))}
|
|
/>
|
|
<InfoItem
|
|
label="Declara IR"
|
|
value={cliente.declaraIR ? "Sim" : "Não"}
|
|
/>
|
|
<InfoItem
|
|
label="Tipo Previdência"
|
|
value={formatTextoSimples(cliente.tipoPrevidencia)}
|
|
/>
|
|
<InfoItem
|
|
label="Contribui Previdência"
|
|
value={cliente.contribuiPrevidencia ? "Sim" : "Não"}
|
|
/>
|
|
</dl>
|
|
</Tab.Panel>
|
|
|
|
{/* Informações Familiares */}
|
|
<Tab.Panel className="bg-white rounded-xl p-6">
|
|
<div className="space-y-6">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<InfoItem
|
|
label="Nome do Pai"
|
|
value={formatNome(cliente.nomePai || "")}
|
|
/>
|
|
<InfoItem
|
|
label="Nome da Mãe"
|
|
value={formatNome(cliente.nomeMae || "")}
|
|
/>
|
|
<InfoItem
|
|
label="Nome do Cônjuge"
|
|
value={formatNome(cliente.conjugeNome || "")}
|
|
/>
|
|
<InfoItem
|
|
label="CPF do Cônjuge"
|
|
value={
|
|
cliente.conjugeCpf
|
|
? formatCPF(cliente.conjugeCpf)
|
|
: "Não informado"
|
|
}
|
|
/>
|
|
</div>
|
|
|
|
{/* Lista de Filhos */}
|
|
<div>
|
|
<h3 className="text-lg font-medium text-gray-900 mb-4">
|
|
Filhos
|
|
</h3>
|
|
{cliente.filhos && cliente.filhos.length > 0 ? (
|
|
<ul className="divide-y divide-gray-200">
|
|
{cliente.filhos.map((filho) => (
|
|
<li key={filho.id} className="py-3">
|
|
<p className="text-sm font-medium text-gray-900">
|
|
{filho.nome}
|
|
</p>
|
|
<p className="text-sm text-gray-500">
|
|
Nascimento:{" "}
|
|
{new Date(filho.dataNascimento).toLocaleDateString()}
|
|
</p>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
) : (
|
|
<p className="text-sm text-gray-500">
|
|
Nenhum filho cadastrado
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</Tab.Panel>
|
|
|
|
{/* Processos */}
|
|
<Tab.Panel className="bg-white rounded-xl p-6">
|
|
{cliente.processos && cliente.processos.length > 0 ? (
|
|
<ul className="divide-y divide-gray-200">
|
|
{cliente.processos.map((processo) => (
|
|
<li key={processo.id} className="py-4">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm font-medium text-gray-900">
|
|
{processo.titulo}
|
|
</p>
|
|
<p className="text-sm text-gray-500">
|
|
Nº {processo.numero}
|
|
</p>
|
|
</div>
|
|
<span
|
|
className={classNames(
|
|
"px-2 py-1 text-xs rounded-full",
|
|
processo.status === "em_andamento"
|
|
? "bg-blue-100 text-blue-800"
|
|
: processo.status === "concluido"
|
|
? "bg-green-100 text-green-800"
|
|
: "bg-gray-100 text-gray-800",
|
|
)}
|
|
>
|
|
{processo.status}
|
|
</span>
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
) : (
|
|
<p className="text-sm text-gray-500">
|
|
Nenhum processo cadastrado
|
|
</p>
|
|
)}
|
|
</Tab.Panel>
|
|
</Tab.Panels>
|
|
</Tab.Group>
|
|
|
|
{/* Modal de Edição */}
|
|
<ClienteModal
|
|
isOpen={isEditModalOpen}
|
|
cliente={cliente}
|
|
onClose={() => {
|
|
setIsEditModalOpen(false);
|
|
setEditingField(null);
|
|
}}
|
|
onSuccess={() => {
|
|
setIsEditModalOpen(false);
|
|
setEditingField(null);
|
|
}}
|
|
initialTab={getTabForField(editingField)}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|