saveinmed/frontend/src/components/CarrinhoCompras.tsx

401 lines
No EOL
17 KiB
TypeScript

'use client';
import React, { useState, useRef, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useCarrinho } from '@/contexts/CarrinhoContext';
import { pedidoApiService } from '@/services/pedidoApiService';
import { ShoppingCartIcon, XMarkIcon, TrashIcon } from '@heroicons/react/24/outline';
import Link from 'next/link';
import { toast } from 'react-hot-toast';
const CarrinhoCompras: React.FC = () => {
const router = useRouter();
const {
itens,
totalItens,
valorTotal,
carrinhoId,
removerItem,
atualizarQuantidade,
limparCarrinho,
isCarrinhoAberto,
setIsCarrinhoAberto,
} = useCarrinho();
const [finalizandoPedido, setFinalizandoPedido] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
// Função de teste para debug
const testarFinalizacao = async () => {
console.log('🧪 [TESTE] Iniciando teste de finalização');
console.log('🧪 [TESTE] Itens:', itens);
console.log('🧪 [TESTE] Total itens:', totalItens);
console.log('🧪 [TESTE] Valor total:', valorTotal);
console.log('🧪 [TESTE] Carrinho ID:', carrinhoId);
try {
setFinalizandoPedido(true);
await new Promise(resolve => setTimeout(resolve, 100));
console.log('🧪 [TESTE] Simulando criação de pedido...');
const response = await pedidoApiService.criar(itens, carrinhoId || undefined);
console.log('🧪 [TESTE] Resposta:', response);
if (response.success) {
toast.success('Teste de pedido OK!');
} else {
toast.error(`Erro no teste: ${response.error}`);
}
} catch (error) {
console.error('🧪 [TESTE] Erro:', error);
toast.error('Erro no teste');
} finally {
setFinalizandoPedido(false);
}
};
// Fechar dropdown ao clicar fora
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsCarrinhoAberto(false);
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [setIsCarrinhoAberto]);
const formatarPreco = (preco: number) => {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL'
}).format(preco);
};
const obterPreco = (produto: any) => {
// Retorna apenas o preco_final, se não existir retorna 0
return produto.preco_final || 0;
};
const handleFinalizarCompra = async (event?: React.MouseEvent) => {
console.log('🚀 [Header] handleFinalizarCompra INICIADO');
console.log('🚀 [Header] Event recebido:', event);
console.log('🚀 [Header] Estado inicial finalizandoPedido:', finalizandoPedido);
// Prevenir propagação e comportamento padrão
if (event) {
event.preventDefault();
event.stopPropagation();
console.log('🚀 [Header] Event prevented');
}
// Verificar se já está processando para evitar duplo clique
if (finalizandoPedido) {
console.log('⚠️ [Header] Já está finalizando, ignorando clique duplicado');
return;
}
// Verificar carrinho novamente no momento do clique
const itensAtual = itens;
console.log('🚀 [Header] Itens atuais:', itensAtual);
console.log('🚀 [Header] Quantidade de itens:', itensAtual?.length);
if (!itensAtual || itensAtual.length === 0) {
console.log('❌ [Header] Carrinho vazio!');
toast.error("Adicione produtos ao carrinho antes de finalizar a compra");
return;
}
try {
setFinalizandoPedido(true);
console.log('🛒 [Header] Iniciando criação de pedido...', {
itensCarrinho: itensAtual.length,
carrinhoId,
valorTotal: totalItens
});
let pedidoId: string | null = null;
console.log('🆕 [Header] Criando novo pedido...');
// Aguardar um pequeno delay para garantir sincronização do estado
await new Promise(resolve => setTimeout(resolve, 100));
// Criar pedido na API BFF
const response = await pedidoApiService.criar(itensAtual, carrinhoId || undefined);
if (response.success) {
setIsCarrinhoAberto(false);
pedidoId = response.data?.$id || response.data?.id;
// Aguardar um momento antes do redirecionamento
await new Promise(resolve => setTimeout(resolve, 500));
console.log('🔄 [Header] Redirecionando para checkout com pedido:', pedidoId);
try {
await router.push(`/checkout?pedido=${pedidoId}`);
} catch (routerError) {
console.warn('⚠️ [Header] Router falhou, usando window.location:', routerError);
window.location.href = `/checkout?pedido=${pedidoId}`;
}
} else {
console.warn('⚠️ [Header] Erro ao criar pedido, tentando recuperar pedido existente...', response.error);
let recuperado = false;
// 1. Tentar buscar pedido pendente existente para este carrinho
if (carrinhoId) {
const pedidoExistente = await pedidoApiService.buscarPendentePorCarrinho(carrinhoId);
if (pedidoExistente.success && pedidoExistente.data) {
console.log('✅ [Header] Pedido pendente encontrado:', pedidoExistente.data);
pedidoId = pedidoExistente.data.$id || pedidoExistente.data.id;
toast.success('Retomando pedido existente...');
recuperado = true;
setIsCarrinhoAberto(false);
await new Promise(resolve => setTimeout(resolve, 500));
router.push(`/checkout?pedido=${pedidoId}`);
}
}
// 2. Se não conseguiu recuperar, tentar criar um NOVO pedido forçando unique() (já configurado no serviço, mas tentando novamente caso seja erro transiente)
if (!recuperado) {
console.log('⚠️ [Header] Não foi possível recuperar pedido existente. Tentando criar um novo...');
// Aguardar um pouco antes de tentar novamente
await new Promise(resolve => setTimeout(resolve, 500));
const novaTentativa = await pedidoApiService.criar(itensAtual, carrinhoId || undefined);
if (novaTentativa.success) {
console.log('✅ [Header] Novo pedido criado com sucesso na segunda tentativa:', novaTentativa.data);
pedidoId = novaTentativa.data?.$id || novaTentativa.data?.id;
toast.success('Novo pedido criado com sucesso!');
setIsCarrinhoAberto(false);
await new Promise(resolve => setTimeout(resolve, 500));
router.push(`/checkout?pedido=${pedidoId}`);
} else {
// Se falhar novamente, aí sim mostra o erro original/novo
console.error('❌ [Header] Erro ao criar pedido (tentativa 2):', novaTentativa.error);
toast.error(`Erro ao criar pedido: ${response.error}`);
router.push("/checkout");
}
}
}
} catch (error) {
console.error('💥 [Header] Erro na finalização da compra:', error);
toast.error("Erro ao finalizar compra. Tente novamente.");
} finally {
// Aguardar um momento antes de liberar o botão
setTimeout(() => {
setFinalizandoPedido(false);
}, 1000);
}
};
return (
<div className="relative" ref={dropdownRef}>
{/* Botão do Carrinho */}
<button
onClick={() => setIsCarrinhoAberto(!isCarrinhoAberto)}
className="relative p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors duration-200"
>
<ShoppingCartIcon className="w-6 h-6" />
{totalItens > 0 && (
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center font-medium">
{totalItens > 99 ? '99+' : totalItens}
</span>
)}
</button>
{/* Dropdown do Carrinho */}
{isCarrinhoAberto && (
<div className="absolute right-0 mt-2 w-96 bg-white rounded-lg shadow-lg border border-gray-200 z-50">
{/* Header do Carrinho */}
<div className="flex items-center justify-between p-4 border-b border-gray-200">
<h3 className="text-lg font-semibold text-gray-900">Carrinho de Compras</h3>
<button
onClick={() => setIsCarrinhoAberto(false)}
className="text-gray-400 hover:text-gray-600 p-1 rounded"
>
<XMarkIcon className="w-5 h-5" />
</button>
</div>
{/* Conteúdo do Carrinho */}
<div className="max-h-96 overflow-y-auto">
{itens.length === 0 ? (
<div className="p-6 text-center">
<ShoppingCartIcon className="w-12 h-12 text-gray-300 mx-auto mb-3" />
<p className="text-gray-500 mb-4">Seu carrinho está vazio</p>
<Link
href="/produtos"
onClick={() => setIsCarrinhoAberto(false)}
className="inline-block bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
>
Ver Produtos
</Link>
</div>
) : (
<div className="p-4 space-y-4">
{itens.map((item) => {
const preco = obterPreco(item.produto);
return (
<div key={item.produto.id} className="flex items-start space-x-3 p-3 bg-gray-50 rounded-lg">
{/* Informações do Produto */}
<div className="flex-1 min-w-0">
<h4 className="text-sm font-medium text-gray-900 truncate">
{item.produto.nome || 'Produto'}
</h4>
{/* Código EAN */}
{item.produto.codigo_ean && (
<p className="text-xs text-gray-500">
EAN: {item.produto.codigo_ean}
</p>
)}
{/* Laboratório e Categoria */}
<div className="flex items-center gap-2 text-xs text-blue-600 mt-1">
{item.produto.cat_nome && (
<span>{item.produto.cat_nome}</span>
)}
{item.produto.cat_nome && item.produto.lab_nome && (
<span>|</span>
)}
{item.produto.lab_nome && (
<span>{item.produto.lab_nome}</span>
)}
</div>
<p className="text-sm font-medium text-green-600 mt-1">
{formatarPreco(preco)}
</p>
</div>
{/* Área de Controles */}
<div className="flex flex-col items-end space-y-2">
{/* Controles de Quantidade */}
<div className="flex items-center gap-2">
<div className="flex items-center border border-gray-300 rounded-lg">
<button
onClick={() => atualizarQuantidade(item.produto.id, item.quantidade - 1)}
className="px-2 py-1 text-gray-600 hover:text-gray-800 hover:bg-gray-100 rounded-l-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent"
disabled={item.quantidade <= 1}
title={item.quantidade <= 1 ? "Quantidade mínima atingida" : "Diminuir quantidade"}
>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 12H4" />
</svg>
</button>
<input
type="number"
min="1"
max={item.produto.quantidade_estoque || 999}
value={item.quantidade}
onChange={(e) => {
const newQty = parseInt(e.target.value) || 1;
const maxQty = item.produto.quantidade_estoque || 999;
if (newQty >= 1 && newQty <= maxQty) {
atualizarQuantidade(item.produto.id, newQty);
}
}}
className="px-2 py-1 text-xs font-medium text-gray-900 bg-gray-50 border-x border-gray-300 min-w-[35px] text-center focus:outline-none focus:bg-white focus:border-blue-500"
/>
<button
onClick={() => atualizarQuantidade(item.produto.id, item.quantidade + 1)}
className="px-2 py-1 text-gray-600 hover:text-gray-800 hover:bg-gray-100 rounded-r-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent"
disabled={item.quantidade >= (item.produto.quantidade_estoque || 0)}
title={item.quantidade >= (item.produto.quantidade_estoque || 0) ? "Estoque máximo atingido" : "Aumentar quantidade"}
>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
</button>
</div>
{/* Botão Remover */}
<button
onClick={() => removerItem(item.produto.id)}
className="p-1 text-red-400 hover:text-red-600 hover:bg-red-50 rounded"
>
<TrashIcon className="w-4 h-4" />
</button>
</div>
{/* Estoque disponível */}
<span className="text-xs text-gray-500">
Estoque: {item.produto.quantidade_estoque || 0}
</span>
</div>
</div>
);
})}
</div>
)}
</div>
{/* Footer do Carrinho */}
{itens.length > 0 && (
<div className="border-t border-gray-200 p-4">
<div className="flex items-center justify-between mb-3">
<span className="text-sm font-medium text-gray-900">Total:</span>
<span className="text-lg font-bold text-green-600">
{formatarPreco(valorTotal)}
</span>
</div>
<div className="flex space-x-2">
<button
onClick={() => limparCarrinho()}
className="flex-1 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition-colors text-sm"
>
Limpar
</button>
{/* Botão de teste */}
<button
onClick={testarFinalizacao}
disabled={finalizandoPedido}
className="flex-1 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors text-sm disabled:opacity-50 disabled:cursor-not-allowed"
>
{finalizandoPedido ? 'Testando...' : 'Teste API'}
</button>
<button
onClick={(e) => {
console.log('🔘 [Header] Botão Finalizar clicado!');
console.log('🔘 [Header] Event:', e);
console.log('🔘 [Header] Estado finalizando:', finalizandoPedido);
console.log('🔘 [Header] Itens no carrinho:', itens.length);
handleFinalizarCompra(e);
}}
disabled={finalizandoPedido}
className="flex-1 bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition-colors text-sm disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
{finalizandoPedido ? (
<>
<div className="animate-spin rounded-full h-3 w-3 border-b-2 border-white"></div>
<span>Criando...</span>
</>
) : (
'Finalizar'
)}
</button>
</div>
</div>
)}
</div>
)}
</div>
);
};
export default CarrinhoCompras;