401 lines
No EOL
17 KiB
TypeScript
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; |