147 lines
5.4 KiB
TypeScript
147 lines
5.4 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { useQuery } from 'react-query';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { PencilIcon } from '@heroicons/react/24/outline';
|
|
import api from '../../services/api';
|
|
import { formatCPF, formatTelefone } from '../../utils/formatters';
|
|
|
|
interface ClientesListProps {
|
|
searchTerm: string;
|
|
onEdit: (cliente: any) => void;
|
|
}
|
|
|
|
export const ClientesList: React.FC<ClientesListProps> = ({ searchTerm, onEdit }) => {
|
|
const navigate = useNavigate();
|
|
const [debouncedTerm, setDebouncedTerm] = useState(searchTerm);
|
|
|
|
// Implementar debounce para a busca
|
|
useEffect(() => {
|
|
const timer = setTimeout(() => {
|
|
setDebouncedTerm(searchTerm);
|
|
}, 300); // 300ms de delay
|
|
|
|
return () => clearTimeout(timer);
|
|
}, [searchTerm]);
|
|
|
|
const { data: clientes = [], isLoading } = useQuery(
|
|
['clientes', debouncedTerm],
|
|
async () => {
|
|
const response = await api.get('/clientes', {
|
|
params: {
|
|
search: debouncedTerm.toUpperCase()
|
|
}
|
|
});
|
|
|
|
// Filtrar localmente para melhor resposta
|
|
let filteredClientes = response.data;
|
|
if (debouncedTerm) {
|
|
const searchTerms = debouncedTerm.toUpperCase().split(' ');
|
|
filteredClientes = response.data.filter((cliente: any) =>
|
|
searchTerms.every(term =>
|
|
cliente.nome.includes(term) ||
|
|
cliente.cpfCnpj.includes(term) ||
|
|
cliente.email?.includes(term.toLowerCase())
|
|
)
|
|
);
|
|
}
|
|
|
|
// Ordenar por nome
|
|
return filteredClientes.sort((a: any, b: any) =>
|
|
a.nome.localeCompare(b.nome)
|
|
);
|
|
},
|
|
{
|
|
staleTime: 1000 * 60, // 1 minuto
|
|
cacheTime: 1000 * 60 * 5, // 5 minutos
|
|
keepPreviousData: true // Manter dados anteriores enquanto carrega novos
|
|
}
|
|
);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
|
<div className="animate-pulse flex space-x-4">
|
|
<div className="flex-1 space-y-4">
|
|
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-3/4"></div>
|
|
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded"></div>
|
|
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-5/6"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
|
{clientes.length === 0 ? (
|
|
<div className="p-6 text-center text-gray-500 dark:text-gray-400">
|
|
Nenhum cliente encontrado
|
|
</div>
|
|
) : (
|
|
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
<thead className="bg-gray-50 dark:bg-gray-900">
|
|
<tr>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
Nome
|
|
</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
CPF/CNPJ
|
|
</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
Telefone
|
|
</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
Email
|
|
</th>
|
|
<th className="relative px-6 py-3">
|
|
<span className="sr-only">Ações</span>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
|
|
{clientes.map((cliente: any) => (
|
|
<tr
|
|
key={cliente.id}
|
|
className="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors cursor-pointer bg-white dark:bg-gray-800"
|
|
onClick={() => navigate(`/clientes/${cliente.id}`)}
|
|
>
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
<div className="text-sm font-medium text-gray-900 dark:text-white">
|
|
{cliente.nome}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
<div className="text-sm text-gray-500 dark:text-gray-400">
|
|
{formatCPF(cliente.cpfCnpj)}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
<div className="text-sm text-gray-500 dark:text-gray-400">
|
|
{formatTelefone(cliente.telefone)}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
<div className="text-sm text-gray-500 dark:text-gray-400">
|
|
{cliente.email}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
onEdit(cliente);
|
|
}}
|
|
className="text-blue-600 dark:text-blue-400 hover:text-blue-900 dark:hover:text-blue-300"
|
|
>
|
|
<PencilIcon className="h-5 w-5" />
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|