saveinmed/saveinmed-frontend/src/components/ProdutoList.tsx
Tiago Yamamoto b39caf0fd0 first commit
2025-12-17 13:58:26 -03:00

231 lines
6.3 KiB
TypeScript

import React, { useEffect, useCallback } from 'react';
import { Models } from 'appwrite';
import SearchBar from './SearchBar';
import DataTable, { Column } from './DataTable';
import Pagination from './Pagination';
import TableActions from './TableActions';
interface ProdutoListProps {
produtos: Models.Document[];
loading: boolean;
isCreating?: boolean;
error: string | null;
totalProdutos: number;
currentPage: number;
isChangingPage: boolean;
pageSize: number;
onEdit: (produto: Models.Document) => void;
onDelete: (id: string) => Promise<boolean>;
onPrevPage: () => void;
onNextPage: () => void;
onSearch: (termo: string) => void;
searchTerm?: string;
}
const ProdutoList: React.FC<ProdutoListProps> = ({
produtos,
loading,
isCreating = false,
error,
totalProdutos,
currentPage,
isChangingPage,
pageSize,
onEdit,
onDelete,
onPrevPage,
onNextPage,
onSearch,
searchTerm = ''
}) => {
const totalPages = Math.ceil(totalProdutos / pageSize);
const startItem = (currentPage - 1) * pageSize + 1;
const endItem = Math.min(currentPage * pageSize, totalProdutos);
const [search, setSearch] = React.useState(searchTerm);
// Sincronizar o estado local com o searchTerm externo
useEffect(() => {
setSearch(searchTerm);
}, [searchTerm]);
// Debounce para busca em tempo real
const debouncedSearch = useCallback(
debounce((term: string) => {
onSearch(term);
}, 500),
[onSearch]
);
// Função para lidar com mudanças no campo de busca
const handleSearchChange = (value: string) => {
setSearch(value);
debouncedSearch(value);
};
const handleSearch = () => {
onSearch(search);
};
// Função debounce
function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeout: NodeJS.Timeout;
return (...args: Parameters<T>) => {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), wait);
};
}
const handleDelete = async (id: string) => {
// Popup de confirmação é tratado pela função onDelete
await onDelete(id);
};
const formatarPreco = (preco: number) => {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL'
}).format(preco || 0);
};
const columns: Column<Models.Document>[] = [
{
key: 'nome',
header: 'Nome do Produto',
render: produto => {
console.log('🔍 Debug Produto:', {
id: produto.$id,
nome: produto.nome,
catalogoProdutos: produto['catalogo-produtos'],
allKeys: Object.keys(produto)
});
return produto.nome || produto['catalogo-produtos']?.descricao || 'N/A';
}
},
{
key: 'descricao',
header: 'Descrição (Catálogo)',
render: produto => produto['catalogo-produtos']?.descricao || 'N/A',
className: 'max-w-xs'
},
{
key: 'codigo',
header: 'Código',
render: produto => produto['catalogo-produtos']?.['codigo-interno'] || produto['catalogo-produtos']?.['codigo-ean'] || produto.codigo || 'N/A'
},
{
key: 'estoque',
header: 'Estoque',
render: produto => {
// Usar quantidade real do produto ou padrão se não informado
const estoque = produto.quantidade || produto.estoque || 0;
return (
<span className={`font-medium ${
estoque > 20 ? 'text-green-600' :
estoque > 5 ? 'text-yellow-600' :
'text-red-600'
}`}>
{estoque > 0 ? `${estoque} unidades` : 'Sem estoque'}
</span>
);
}
},
{
key: 'preco-fabrica',
header: 'Preço Fábrica',
render: produto => formatarPreco(produto['preco-original'] || produto['preco-fabrica'])
},
{
key: 'preco-atual',
header: 'Preço Atual',
render: produto => formatarPreco(produto['preco-atual'] || produto['preco-venda'])
},
{
key: 'laboratorio',
header: 'Laboratório',
render: produto => {
const laboratorios = produto['catalogo-produtos']?.laboratorios;
if (laboratorios && laboratorios.length > 0) {
return laboratorios[0].nome || 'N/A';
}
return produto.laboratorio || 'N/A';
}
},
{
key: 'categoria',
header: 'Categoria',
render: produto => produto['catalogo-produtos']?.categorias?.nome || produto.categoria || 'N/A'
}
];
return (
<>
{/* Barra de busca */}
<div className="mb-4">
<SearchBar
value={search}
onChange={handleSearchChange}
onSearch={handleSearch}
placeholder="Buscar por nome, descrição, código ou laboratório..."
/>
</div>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}
{isCreating && (
<div className="bg-indigo-50 border border-indigo-200 rounded-md p-3 mb-4">
<div className="flex items-center">
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-indigo-600 mr-2"></div>
<span className="text-indigo-800 text-sm">Criando novo produto...</span>
</div>
</div>
)}
{loading ? (
<div className="flex justify-center items-center py-8">
<div className="text-lg">Carregando produtos...</div>
</div>
) : (
<>
<DataTable
columns={columns}
data={produtos}
actions={(produto) => (
<TableActions
onEdit={() => onEdit(produto)}
onDelete={() => handleDelete(produto.$id)}
/>
)}
/>
<div className="mt-4 flex justify-between items-center">
<div className="text-sm text-gray-600">
{totalProdutos > 0 ? (
<>Mostrando {startItem} - {endItem} de {totalProdutos} produtos</>
) : (
'Total de produtos: 0'
)}
</div>
<Pagination
page={currentPage}
total={totalPages}
onPrev={onPrevPage}
onNext={onNextPage}
isChangingPage={isChangingPage}
/>
</div>
</>
)}
</>
);
};
export default ProdutoList;