390 lines
12 KiB
Markdown
390 lines
12 KiB
Markdown
|
|
# Guia de Uso do Carrinho de Compras - VendaWeb React
|
||
|
|
|
||
|
|
## 📋 Visão Geral
|
||
|
|
|
||
|
|
Este documento descreve como funciona o sistema de carrinho de compras no frontend React, baseado na implementação funcional do portal Angular. O guia cobre desde a adição de itens até a gestão completa do carrinho.
|
||
|
|
|
||
|
|
## 🏗️ Arquitetura
|
||
|
|
|
||
|
|
```
|
||
|
|
vendaweb_react/
|
||
|
|
├── src/
|
||
|
|
│ ├── hooks/
|
||
|
|
│ │ └── useCart.ts # Hook customizado para gerenciar estado do carrinho
|
||
|
|
│ ├── services/
|
||
|
|
│ │ └── shopping.service.ts # Serviço para interações com a API do carrinho
|
||
|
|
│ └── contexts/
|
||
|
|
│ └── AuthContext.tsx # Contexto de autenticação (necessário para token)
|
||
|
|
├── components/
|
||
|
|
│ └── CartDrawer.tsx # Componente visual do carrinho
|
||
|
|
└── views/
|
||
|
|
└── CheckoutView.tsx # View de checkout
|
||
|
|
```
|
||
|
|
|
||
|
|
## 🔑 Conceitos Fundamentais
|
||
|
|
|
||
|
|
### 1. ID do Carrinho (`idCart`)
|
||
|
|
|
||
|
|
- **Tipo**: `string | null`
|
||
|
|
- **Armazenamento**: `localStorage.getItem("cart")`
|
||
|
|
- **Comportamento**:
|
||
|
|
- Quando `null`: Backend cria um novo carrinho e retorna o `idCart` gerado
|
||
|
|
- Quando existe: Backend adiciona o item ao carrinho existente
|
||
|
|
- **IMPORTANTE**: Sempre enviar `idCart: null` (não string vazia) quando não há carrinho
|
||
|
|
|
||
|
|
### 2. ID do Item (`id`)
|
||
|
|
|
||
|
|
- **Tipo**: `string | null`
|
||
|
|
- **Comportamento**:
|
||
|
|
- Quando `null`: Backend cria um novo item e retorna o `id` (UUID) gerado
|
||
|
|
- Quando existe: Backend atualiza o item existente
|
||
|
|
- **IMPORTANTE**: Sempre enviar `id: null` para novos itens
|
||
|
|
|
||
|
|
### 3. Fluxo de Adição de Item
|
||
|
|
|
||
|
|
```
|
||
|
|
1. Usuário seleciona produto
|
||
|
|
↓
|
||
|
|
2. productToShoppingItem() converte Product → ShoppingItem
|
||
|
|
↓
|
||
|
|
3. createItemShopping() envia POST /shopping/item
|
||
|
|
↓
|
||
|
|
4. Backend cria/atualiza carrinho e retorna idCart
|
||
|
|
↓
|
||
|
|
5. Frontend salva idCart no localStorage
|
||
|
|
↓
|
||
|
|
6. useCart hook recarrega itens do carrinho
|
||
|
|
```
|
||
|
|
|
||
|
|
## 📝 Estrutura do Payload
|
||
|
|
|
||
|
|
### Payload para Adicionar Item (POST /shopping/item)
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
{
|
||
|
|
"id": null, // Sempre null para novos itens
|
||
|
|
"idCart": null, // null se não há carrinho, UUID se existe
|
||
|
|
"invoiceStore": "4", // Código da loja
|
||
|
|
"idProduct": 25960, // ID do produto (número)
|
||
|
|
"description": "Nome do Produto",
|
||
|
|
"image": "http://...", // URL da imagem ou "" se não houver
|
||
|
|
"productType": "S", // Tipo do produto
|
||
|
|
"percentUpQuantity": 0, // Sempre 0
|
||
|
|
"upQuantity": 0, // Sempre 0
|
||
|
|
"quantity": 1, // Quantidade
|
||
|
|
"price": 20.99, // Preço de venda
|
||
|
|
"deliveryType": "EN", // Tipo de entrega
|
||
|
|
"stockStore": "4", // Código da loja de estoque
|
||
|
|
"seller": 1, // ID do vendedor
|
||
|
|
"discount": 0, // Desconto percentual (sempre 0 inicialmente)
|
||
|
|
"discountValue": 0, // Valor do desconto (sempre 0 inicialmente)
|
||
|
|
"ean": 7895024019601, // Código EAN (ou idProduct se não houver)
|
||
|
|
"promotion": 20.99, // Preço promocional (0 se não houver promoção)
|
||
|
|
"listPrice": 33.9, // Preço de tabela
|
||
|
|
"userDiscount": null, // Sempre null
|
||
|
|
"mutiple": 1, // Múltiplo de venda
|
||
|
|
"auxDescription": null, // Descrição auxiliar (cor para tintométrico)
|
||
|
|
"smallDescription": "#ARG...", // Descrição curta (NÃO usar description como fallback)
|
||
|
|
"brand": "PORTOKOLL", // Marca
|
||
|
|
"base": "N", // Base tintométrica (S/N)
|
||
|
|
"line": null, // Linha tintométrica
|
||
|
|
"can": null, // Lata
|
||
|
|
"letter": null // Letra tintométrica
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Regras Importantes
|
||
|
|
|
||
|
|
1. **`id` e `idCart`**: Sempre presentes no payload, mesmo que sejam `null`
|
||
|
|
2. **`smallDescription`**: Usar apenas o campo `smallDescription` do produto (não usar `description` como fallback)
|
||
|
|
3. **`promotion`**:
|
||
|
|
- Se produto tem `promotion > 0`: usar esse valor
|
||
|
|
- Se `price < listPrice`: usar `price` como `promotion`
|
||
|
|
- Caso contrário: usar `0`
|
||
|
|
4. **`image`**: String vazia `""` quando não há imagem (não `null`)
|
||
|
|
5. **`color`**: Remover do payload se for `null` (não incluir o campo)
|
||
|
|
|
||
|
|
## 🔧 Uso do Hook `useCart`
|
||
|
|
|
||
|
|
### Importação
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { useCart } from './src/hooks/useCart';
|
||
|
|
```
|
||
|
|
|
||
|
|
### Uso Básico
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
const {
|
||
|
|
cart, // OrderItem[] - Itens do carrinho
|
||
|
|
cartId, // string | null - ID do carrinho
|
||
|
|
isLoading, // boolean - Estado de carregamento
|
||
|
|
error, // string | null - Mensagem de erro
|
||
|
|
addToCart, // (product: Product | OrderItem) => Promise<void>
|
||
|
|
updateQuantity, // (id: string, delta: number) => Promise<void>
|
||
|
|
removeFromCart, // (id: string) => Promise<void>
|
||
|
|
refreshCart, // () => Promise<void>
|
||
|
|
clearCart, // () => void
|
||
|
|
} = useCart();
|
||
|
|
```
|
||
|
|
|
||
|
|
### Exemplo Completo
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { useCart } from './src/hooks/useCart';
|
||
|
|
import { Product } from './types';
|
||
|
|
|
||
|
|
function ProductCard({ product }: { product: Product }) {
|
||
|
|
const { addToCart, isLoading } = useCart();
|
||
|
|
|
||
|
|
const handleAddToCart = async () => {
|
||
|
|
try {
|
||
|
|
await addToCart(product);
|
||
|
|
// Item adicionado com sucesso
|
||
|
|
// O hook automaticamente:
|
||
|
|
// 1. Cria o item no backend
|
||
|
|
// 2. Salva o idCart retornado no localStorage
|
||
|
|
// 3. Recarrega os itens do carrinho
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Erro ao adicionar item:', error);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<button onClick={handleAddToCart} disabled={isLoading}>
|
||
|
|
{isLoading ? 'Adicionando...' : 'Adicionar ao Carrinho'}
|
||
|
|
</button>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## 🛠️ Uso do Serviço `shoppingService`
|
||
|
|
|
||
|
|
### Métodos Principais
|
||
|
|
|
||
|
|
#### 1. `createItemShopping(item: ShoppingItem): Promise<ShoppingItem>`
|
||
|
|
|
||
|
|
Adiciona um item ao carrinho.
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { shoppingService } from './src/services/shopping.service';
|
||
|
|
import { Product } from './types';
|
||
|
|
|
||
|
|
// Converter Product para ShoppingItem
|
||
|
|
const shoppingItem = shoppingService.productToShoppingItem(product);
|
||
|
|
|
||
|
|
// Criar item no carrinho
|
||
|
|
const result = await shoppingService.createItemShopping(shoppingItem);
|
||
|
|
|
||
|
|
// O idCart será salvo automaticamente no localStorage
|
||
|
|
// Se result.idCart existir, será salvo
|
||
|
|
```
|
||
|
|
|
||
|
|
**Comportamento**:
|
||
|
|
- Se `item.idCart` é `null`: Backend cria novo carrinho
|
||
|
|
- Se `item.idCart` existe: Backend adiciona ao carrinho existente
|
||
|
|
- Sempre retorna o `idCart` (novo ou existente)
|
||
|
|
- Remove `paymentPlan` e `billing` do localStorage após sucesso
|
||
|
|
|
||
|
|
#### 2. `productToShoppingItem(product: Product | OrderItem): ShoppingItem`
|
||
|
|
|
||
|
|
Converte um `Product` ou `OrderItem` para `ShoppingItem`.
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
const product: Product = {
|
||
|
|
id: "123",
|
||
|
|
code: "25960",
|
||
|
|
name: "Produto Exemplo",
|
||
|
|
price: 20.99,
|
||
|
|
// ... outros campos
|
||
|
|
};
|
||
|
|
|
||
|
|
const shoppingItem = shoppingService.productToShoppingItem(product);
|
||
|
|
// Retorna ShoppingItem pronto para ser enviado ao backend
|
||
|
|
```
|
||
|
|
|
||
|
|
**Regras de Conversão**:
|
||
|
|
- `idProduct`: Extraído de `product.idProduct`, `product.id` ou `product.code`
|
||
|
|
- `description`: `product.name` ou `product.description`
|
||
|
|
- `smallDescription`: Apenas `product.smallDescription` (sem fallback)
|
||
|
|
- `promotion`: Calculado conforme regras acima
|
||
|
|
- `ean`: `product.ean` ou `idProduct` como fallback
|
||
|
|
- `idCart`: Obtido do `localStorage.getItem("cart")` (pode ser `null`)
|
||
|
|
|
||
|
|
#### 3. `updateQuantityItemShopping(item: ShoppingItem): Promise<void>`
|
||
|
|
|
||
|
|
Atualiza a quantidade de um item no carrinho.
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
const item = cart.find(i => i.id === itemId);
|
||
|
|
const shoppingItem = shoppingService.productToShoppingItem({
|
||
|
|
...item,
|
||
|
|
quantity: newQuantity
|
||
|
|
});
|
||
|
|
shoppingItem.id = item.id; // IMPORTANTE: Usar o UUID do item
|
||
|
|
shoppingItem.idCart = cartId;
|
||
|
|
|
||
|
|
await shoppingService.updateQuantityItemShopping(shoppingItem);
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 4. `deleteItemShopping(id: string): Promise<void>`
|
||
|
|
|
||
|
|
Remove um item do carrinho.
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// IMPORTANTE: id deve ser o UUID do item (não idProduct)
|
||
|
|
await shoppingService.deleteItemShopping(itemId);
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 5. `getShoppingItems(idCart: string): Promise<ShoppingItem[]>`
|
||
|
|
|
||
|
|
Obtém todos os itens de um carrinho.
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
const cartId = shoppingService.getCart();
|
||
|
|
if (cartId) {
|
||
|
|
const items = await shoppingService.getShoppingItems(cartId);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## ⚠️ Validações e Regras de Negócio
|
||
|
|
|
||
|
|
### 1. Produto Tintométrico
|
||
|
|
|
||
|
|
Produtos com `base === "S"` requerem `auxDescription` (cor selecionada).
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Validação automática em createItemShopping()
|
||
|
|
if (base === "S" && auxDescription === "") {
|
||
|
|
throw new Error("Esse produto só pode ser adicionado com coloração selecionada");
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. IDs dos Itens
|
||
|
|
|
||
|
|
- **Novos itens**: Sempre enviar `id: null`
|
||
|
|
- **Itens existentes**: Usar o UUID retornado pelo backend
|
||
|
|
- **IMPORTANTE**: Não usar `idProduct` como `id` do item do carrinho
|
||
|
|
|
||
|
|
### 3. ID do Carrinho
|
||
|
|
|
||
|
|
- **Primeiro item**: `idCart: null` → Backend cria novo carrinho
|
||
|
|
- **Itens subsequentes**: `idCart: <UUID>` → Backend adiciona ao carrinho existente
|
||
|
|
- **Armazenamento**: Sempre salvar o `idCart` retornado no `localStorage`
|
||
|
|
|
||
|
|
## 🔍 Debugging
|
||
|
|
|
||
|
|
### Logs do Serviço
|
||
|
|
|
||
|
|
O serviço gera logs detalhados com prefixo `🛒 [SHOPPING]`:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
console.log("🛒 [SHOPPING] createItemShopping: " + JSON.stringify(cleanItem));
|
||
|
|
console.log("🛒 [SHOPPING] Item criado com sucesso:", result);
|
||
|
|
console.log("🛒 [SHOPPING] idCart retornado:", result.idCart);
|
||
|
|
```
|
||
|
|
|
||
|
|
### Verificar Estado do Carrinho
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// No console do navegador
|
||
|
|
localStorage.getItem("cart"); // ID do carrinho
|
||
|
|
localStorage.getItem("token"); // Token de autenticação
|
||
|
|
```
|
||
|
|
|
||
|
|
### Testar Requisição Manualmente
|
||
|
|
|
||
|
|
Use o script `test_add_item.ps1` para testar a adição de itens via curl:
|
||
|
|
|
||
|
|
```powershell
|
||
|
|
# 1. Obter token do localStorage
|
||
|
|
# 2. Editar test_add_item.ps1 com o token
|
||
|
|
# 3. Executar: .\test_add_item.ps1
|
||
|
|
```
|
||
|
|
|
||
|
|
## 🐛 Problemas Comuns e Soluções
|
||
|
|
|
||
|
|
### Erro 400: "Erro ao criar item no carrinho de compras"
|
||
|
|
|
||
|
|
**Causas possíveis**:
|
||
|
|
1. `idCart` sendo enviado como string vazia `""` em vez de `null`
|
||
|
|
2. `smallDescription` usando `description` como fallback (muito longo)
|
||
|
|
3. `promotion` calculado incorretamente
|
||
|
|
4. Campos obrigatórios faltando ou com valores inválidos
|
||
|
|
|
||
|
|
**Solução**: Verificar logs do console e comparar payload com o exemplo funcional.
|
||
|
|
|
||
|
|
### Item não aparece no carrinho após adicionar
|
||
|
|
|
||
|
|
**Causas possíveis**:
|
||
|
|
1. `idCart` não foi salvo no `localStorage`
|
||
|
|
2. `refreshCart()` não foi chamado após adicionar item
|
||
|
|
3. Hook `useCart` não está recarregando itens
|
||
|
|
|
||
|
|
**Solução**: Verificar se `result.idCart` foi salvo e se `loadCartItems()` foi chamado.
|
||
|
|
|
||
|
|
### Erro ao remover item
|
||
|
|
|
||
|
|
**Causas possíveis**:
|
||
|
|
1. `id` do item não é um UUID válido
|
||
|
|
2. `id` está usando `idProduct` em vez do UUID do backend
|
||
|
|
|
||
|
|
**Solução**: Garantir que `item.id` seja o UUID retornado pelo backend, não `idProduct`.
|
||
|
|
|
||
|
|
## 📚 Referências
|
||
|
|
|
||
|
|
- **Backend API**: `POST /api/v1/shopping/item`
|
||
|
|
- **Angular Reference**: `vendaweb_portal/src/app/sales/product-detail/product-detail.component.ts`
|
||
|
|
- **Backend Service**: `vendaweb_api/src/sales/shopping/shopping.service.ts`
|
||
|
|
|
||
|
|
## ✅ Checklist de Implementação
|
||
|
|
|
||
|
|
Ao implementar funcionalidades de carrinho, verificar:
|
||
|
|
|
||
|
|
- [ ] `id` e `idCart` sempre presentes no payload (mesmo que `null`)
|
||
|
|
- [ ] `smallDescription` usa apenas campo do produto (sem fallback)
|
||
|
|
- [ ] `promotion` calculado corretamente
|
||
|
|
- [ ] `idCart` salvo no `localStorage` após criação
|
||
|
|
- [ ] `refreshCart()` chamado após modificações
|
||
|
|
- [ ] Validação de produto tintométrico implementada
|
||
|
|
- [ ] IDs dos itens são UUIDs (não `idProduct`)
|
||
|
|
- [ ] Tratamento de erros implementado
|
||
|
|
- [ ] Logs de debug adicionados
|
||
|
|
|
||
|
|
## 🔄 Fluxo Completo de Exemplo
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// 1. Usuário seleciona produto
|
||
|
|
const product: Product = await productService.getProductDetail(storeId, productId);
|
||
|
|
|
||
|
|
// 2. Adicionar ao carrinho usando hook
|
||
|
|
const { addToCart } = useCart();
|
||
|
|
await addToCart(product);
|
||
|
|
|
||
|
|
// 3. Hook internamente:
|
||
|
|
// - Converte Product → ShoppingItem
|
||
|
|
// - Envia POST /shopping/item com idCart: null (se primeiro item)
|
||
|
|
// - Backend cria carrinho e retorna idCart
|
||
|
|
// - Salva idCart no localStorage
|
||
|
|
// - Recarrega itens do carrinho
|
||
|
|
|
||
|
|
// 4. Atualizar quantidade
|
||
|
|
const { updateQuantity } = useCart();
|
||
|
|
await updateQuantity(itemId, 1); // +1
|
||
|
|
|
||
|
|
// 5. Remover item
|
||
|
|
const { removeFromCart } = useCart();
|
||
|
|
await removeFromCart(itemId);
|
||
|
|
|
||
|
|
// 6. Limpar carrinho
|
||
|
|
const { clearCart } = useCart();
|
||
|
|
clearCart(); // Limpa estado e localStorage
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Última atualização**: 2026-01-06
|
||
|
|
**Versão**: 1.0
|
||
|
|
**Autor**: Sistema de Documentação Automática
|
||
|
|
|