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

315 lines
9.4 KiB
TypeScript

"use client";
import React, {
createContext,
useContext,
useState,
useEffect,
ReactNode,
} from "react";
import { produtoService } from "@/services/produtoService";
import { ProdutoCompleto } from "@/services/produtosVendaService";
import { carrinhoApiService } from "@/services/carrinhoApiService";
import { toast } from "react-hot-toast";
interface ItemCarrinho {
produto: ProdutoCompleto;
quantidade: number;
}
interface CarrinhoContextType {
itens: ItemCarrinho[];
totalItens: number;
valorTotal: number;
carrinhoId: string | null;
sincronizandoApi: boolean;
adicionarItem: (
produto: ProdutoCompleto,
quantidade?: number
) => Promise<void>;
removerItem: (produtoId: string) => Promise<void>;
atualizarQuantidade: (produtoId: string, quantidade: number) => Promise<void>;
limparCarrinho: (restaurarEstoque?: boolean) => Promise<void>;
isCarrinhoAberto: boolean;
setIsCarrinhoAberto: (aberto: boolean) => void;
}
const CarrinhoContext = createContext<CarrinhoContextType | undefined>(
undefined
);
export { CarrinhoContext };
export const useCarrinho = () => {
const context = useContext(CarrinhoContext);
if (!context) {
throw new Error("useCarrinho deve ser usado dentro de um CarrinhoProvider");
}
return context;
};
interface CarrinhoProviderProps {
children: ReactNode;
}
// Função para obter o preço do produto de forma consistente
const obterPreco = (produto: ProdutoCompleto): number => {
const produtoData = produto as any;
// Retorna apenas o preco_final, se não existir retorna 0
return produtoData.preco_final || 0;
};
export const CarrinhoProvider: React.FC<CarrinhoProviderProps> = ({
children,
}) => {
const [itens, setItens] = useState<ItemCarrinho[]>([]);
const [isCarrinhoAberto, setIsCarrinhoAberto] = useState(false);
const [carrinhoId, setCarrinhoId] = useState<string | null>(null);
const [sincronizandoApi, setSincronizandoApi] = useState(false);
// Carregar carrinho do localStorage ao inicializar
useEffect(() => {
setTimeout(() => {
try {
const carrinhoSalvo = localStorage.getItem("carrinho-saveinmed");
const carrinhoIdSalvo = localStorage.getItem("carrinho-id");
if (carrinhoSalvo) {
const carrinhoParsed = JSON.parse(carrinhoSalvo);
setItens(carrinhoParsed);
}
if (carrinhoIdSalvo) {
setCarrinhoId(carrinhoIdSalvo);
}
} catch (error) {
}
}, 100);
}, []);
// Salvar carrinho no localStorage sempre que houver mudanças
useEffect(() => {
localStorage.setItem("carrinho-saveinmed", JSON.stringify(itens));
}, [itens]);
// Sincronizar com API BFF quando itens mudam (com debounce)
useEffect(() => {
if (itens.length === 0 && carrinhoId) {
// Verificar se estamos em páginas de checkout/pagamento
const isCheckoutPage = typeof window !== 'undefined' &&
(window.location.pathname.includes('/checkout') ||
window.location.pathname.includes('/pagamento'));
if (!isCheckoutPage) {
// Se não há itens mas há carrinho na API, excluir (apenas se não estamos no checkout)
setSincronizandoApi(true);
carrinhoApiService.excluir(carrinhoId)
.then(response => {
if (response.success) {
setCarrinhoId(null);
localStorage.removeItem("carrinho-id");
} else {
console.error("❌ [CARRINHO API] Erro ao excluir:", response.error);
}
})
.catch(error => {
console.error("💥 [CARRINHO API] Erro na exclusão:", error);
})
.finally(() => {
setSincronizandoApi(false);
});
} else {
}
return;
}
if (itens.length === 0) return; // Não fazer nada se não há itens
// Debounce: aguardar 1 segundo sem mudanças antes de sincronizar
const timeoutId = setTimeout(() => {
setSincronizandoApi(true);
if (carrinhoId) {
// Atualizar carrinho existente
carrinhoApiService.atualizar(carrinhoId, itens)
.then(response => {
if (response.success) {
} else {
console.error("❌ [CARRINHO API] Erro ao atualizar:", response.error);
}
})
.catch(error => {
console.error("💥 [CARRINHO API] Erro na atualização:", error);
})
.finally(() => {
setSincronizandoApi(false);
});
} else {
// Criar novo carrinho
carrinhoApiService.criar(itens)
.then(response => {
if (response.success && response.data) {
const novoCarrinhoId = response.data.$id || response.data.id;
if (novoCarrinhoId) {
setCarrinhoId(novoCarrinhoId);
localStorage.setItem("carrinho-id", novoCarrinhoId);
}
} else {
console.error("❌ [CARRINHO API] Erro ao criar:", response.error);
}
})
.catch(error => {
console.error("💥 [CARRINHO API] Erro na criação:", error);
})
.finally(() => {
setSincronizandoApi(false);
});
}
}, 1000);
return () => clearTimeout(timeoutId);
}, [itens, carrinhoId]);
const adicionarItem = async (
produto: ProdutoCompleto,
quantidade: number = 1
) => {
// Obter estoque disponível do produto
const estoqueDisponivel = produto.quantidade_estoque || 0;
setItens((prevItens) => {
const itemExistente = prevItens.find(
(item) => item.produto.id === produto.id
);
if (itemExistente) {
const novaQuantidade = itemExistente.quantidade + quantidade;
// Verificar se a nova quantidade não excede o estoque
if (novaQuantidade > estoqueDisponivel) {
console.warn(
`Tentativa de adicionar mais itens do que disponível em estoque. Disponível: ${estoqueDisponivel}, Tentando adicionar: ${novaQuantidade}`
);
toast.error(
`Estoque insuficiente! Disponível: ${estoqueDisponivel} unidades`
);
return prevItens; // Não adiciona se exceder o estoque
}
toast.success("Produto adicionado ao carrinho!");
return prevItens.map((item) =>
item.produto.id === produto.id
? { ...item, quantidade: novaQuantidade }
: item
);
} else {
// Verificar se a quantidade inicial não excede o estoque
if (quantidade > estoqueDisponivel) {
console.warn(
`Tentativa de adicionar mais itens do que disponível em estoque. Disponível: ${estoqueDisponivel}, Tentando adicionar: ${quantidade}`
);
toast.error(
`Estoque insuficiente! Disponível: ${estoqueDisponivel} unidades`
);
return prevItens; // Não adiciona se exceder o estoque
}
toast.success("Produto adicionado ao carrinho!");
return [...prevItens, { produto, quantidade }];
}
});
};
const removerItem = async (produtoId: string) => {
toast.success("Item removido do carrinho!");
setItens((prevItens) =>
prevItens.filter((item) => item.produto.id !== produtoId)
);
};
const atualizarQuantidade = async (produtoId: string, quantidade: number) => {
if (quantidade <= 0) {
await removerItem(produtoId);
return;
}
// Encontrar o item atual
const itemAtual = itens.find((item) => item.produto.id === produtoId);
if (itemAtual) {
// Verificar se há estoque suficiente para a nova quantidade
const estoqueDisponivel = itemAtual.produto.quantidade_estoque || 0;
if (quantidade > estoqueDisponivel) {
toast.error(
`Estoque insuficiente! Disponível: ${estoqueDisponivel} unidades`
);
return;
}
setItens((prevItens) =>
prevItens.map((item) => {
if (item.produto.id === produtoId) {
return { ...item, quantidade };
}
return item;
})
);
toast.success("Quantidade atualizada!");
}
};
const limparCarrinho = async (restaurarEstoque: boolean = false) => {
// Primeiro, excluir carrinho da API se existir
if (carrinhoId) {
try {
setSincronizandoApi(true);
const response = await carrinhoApiService.excluir(carrinhoId);
if (response.success) {
} else {
console.error("❌ [CARRINHO API] Erro ao excluir:", response.error);
}
} catch (error) {
console.error("💥 [CARRINHO API] Erro na exclusão:", error);
} finally {
setSincronizandoApi(false);
}
setCarrinhoId(null);
localStorage.removeItem("carrinho-id");
}
setItens([]);
toast.success("Carrinho limpo!");
};
const totalItens = itens.reduce((total, item) => total + item.quantidade, 0);
// Atualizar o cálculo do valor total para usar a nova função de preço
const valorTotal = itens.reduce((total, item) => {
const preco = obterPreco(item.produto);
return total + preco * item.quantidade;
}, 0);
const value: CarrinhoContextType = {
itens,
totalItens,
valorTotal,
carrinhoId,
sincronizandoApi,
adicionarItem,
removerItem,
atualizarQuantidade,
limparCarrinho,
isCarrinhoAberto,
setIsCarrinhoAberto,
};
return (
<CarrinhoContext.Provider value={value}>
{children}
</CarrinhoContext.Provider>
);
};