first commit
This commit is contained in:
commit
c9fb67e751
423 files changed
+53287
No files matched your search
@@ -0,0 +1,146 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
Reference in new issue
Block a user