Construindo um dashboard de admin de e-commerce em Next.js: pedidos, reembolsos e estoque
As partes difíceis de um admin de loja em Next.js: dois status de pedido, falhas parciais em massa, contas de reembolso que fecham no centavo, ajustes de estoque e descontos.
Preet Suthar · · 6 min de leitura
- Next.js
- React
- E-commerce
- Dashboard
Um admin de e-commerce parece um app CRUD: uma tabela de pedidos, uma página por pedido, um formulário por produto. As tabelas são a parte fácil. A parte difícil é que todo número num admin de loja é dinheiro ou estoque, e os dois precisam continuar certos enquanto os clientes seguem comprando em segundo plano.
Foi isso que eu aprendi construindo o lado de admin de uma loja em Next.js: tabelas de pedidos com status honestos, uma página de pedido que mostra o que falta fazer, reembolsos que fecham no centavo, edições de estoque que sobrevivem a pedidos ao vivo e descontos que os lojistas conseguem ler.
Pedidos: dois status, não um
O primeiro erro é uma única coluna status. Um pedido tem pelo menos dois estados independentes: pagamento (pendente, autorizado, pago, parcialmente reembolsado, reembolsado) e envio (não enviado, parcialmente enviado, enviado). "Pago e não enviado" é a lista com que um lojista trabalha toda manhã, e uma única coluna de status não consegue expressar isso.
Mostre-os como duas pílulas. Não deixe a cor carregar o significado. As WCAG pedem que a cor não seja a única forma de mostrar uma informação, e "Parcialmente pago" em laranja claro ao lado de "Pago" em verde é difícil de distinguir para muita gente. Um texto mais um pequeno glifo, um círculo vazio, pela metade ou cheio, se lê num relance.
Visualizações salvas ficam na URL
Lojistas não navegam pelos pedidos, eles trabalham em listas: Não enviados, Não pagos, Abertos. Uma visualização salva é só filtros, uma ordenação e uma busca. Mantenha a query inteira na URL para que um link abra a mesma lista e o Voltar funcione, e leia-a no servidor no Next.js:
Quando os filtros atuais forem diferentes da visualização salva, marque a aba como modificada e ofereça Redefinir, Atualizar visualização e Salvar como nova visualização, do mesmo jeito que uma planilha avisa que há um filtro ativo.
Pedidos novos não devem mover a linha sob o cursor
Os pedidos chegam enquanto o lojista está olhando a lista. Se as linhas novas empurrarem tudo para baixo, o checkbox em que ele ia clicar vira outro pedido. Enquanto uma linha estiver em foco ou houver algo selecionado, segure os pedidos novos atrás de uma pílula "1 novo pedido · Mostrar" e só insira-os no topo quando nada estiver em andamento.
Ações em massa falham parcialmente
"Marcar 6 pedidos como enviados" às vezes vai enviar 5. Um está em espera, ou foi cancelado um minuto atrás. Projete a API para isso: retorne os ids que falharam, cada um com um motivo, e informe exatamente isso.
A mensagem então se escreve sozinha: "5 pedidos enviados. 1 não pôde ser enviado: #1031 está em espera", com um link para esse pedido. Um toast de erro tudo-ou-nada deixaria o lojista tentando adivinhar quais passaram.
A página do pedido mostra o que falta
Na página do pedido, separe os itens pelo que aconteceu com eles, não por como foram comprados. Cada remessa ganha o próprio card com a transportadora e o rastreio. Os itens reembolsados ganham o deles. E o card que mais importa é o que ainda falta enviar:
A linha do tempo abaixo deve ser montada a partir dos eventos do pedido, com títulos curtos vindos dos dados ("Pagamento de $564.41 capturado", "Enviado com UPS" com o código de rastreio em link), o horário exato num tooltip e eventos repetidos do mesmo tipo agrupados numa única linha. Comentários da equipe ficam no mesmo feed e são publicados de forma otimista, com uma nova tentativa se falharem.
A conta do reembolso tem que fechar no centavo
Os reembolsos são onde o código de admin mais erra com dinheiro. Um cliente comprou três itens com um desconto de $10 no pedido e impostos, e devolve um. Quanto ele recebe de volta?
Distribua os valores do pedido entre as linhas na proporção do preço de cada linha. Descontos pelo preço, impostos pelo preço depois do desconto. Dividir em centavos inteiros deixa restos, e o método do maior resto os distribui para que as partes sempre somem o valor original:
allocate(1000, [3000, 3000, 3000]) dá [334, 333, 333], que soma exatamente 1000.
Tem uma segunda armadilha. Reembolse hoje uma unidade de uma linha e as outras duas na semana que vem, e os totais precisam bater com o reembolso das três de uma vez. Dê a cada unidade uma fatia cumulativa da parte da linha em vez de arredondar cada reembolso por conta própria:
from é quantas unidades já foram reembolsadas. Não importa como os reembolsos sejam divididos, as fatias somam lineShare.
O resto da tela de reembolso é tratamento de estado. O frete pode ser reembolsado até o valor pago menos o que já foi reembolsado. O dinheiro volta para os meios de pagamento numa ordem fixa quando um pedido foi dividido entre um vale-presente e um cartão. Envie uma chave de idempotência para que uma requisição repetida nunca reembolse duas vezes, peça confirmação em reembolsos grandes e mostre "pendente no banco" como um estado próprio, e não como sucesso ou falha.
Estoque: ajuste, não sobrescreva
O editor de estoque ingênuo envia o novo número: "Sand / Large é 47". Se dois pedidos chegaram enquanto o lojista digitava, salvar 47 desfaz esses pedidos em silêncio.
Em vez disso, envie as mudanças como ajustes. "Recebidos 5" é um delta, e deltas podem ser aplicados com segurança em cima de qualquer coisa que tenha acontecido no meio-tempo. Quando o lojista realmente quer dizer uma contagem, "Definir como 47" depois de um inventário, transforme isso num delta contra a quantidade que ele viu e deixe o servidor rejeitar se essa quantidade tiver mudado:
Uma linha rejeitada fica no rascunho com uma mensagem como "Quantidade alterada para 40 por outro dispositivo", e Reaplicar a envia de novo em cima do novo número. A mutation inventoryAdjustQuantities da Shopify funciona do mesmo jeito com o campo changeFromQuantity.
Separe também o número. Em mãos é o que está no armazém, mas o estoque comprometido já foi vendido e o estoque indisponível está danificado ou retido. Os lojistas precisam ver o disponível separado do que está em mãos, ou vão vender estoque que já foi vendido.
Descontos como uma frase
Formulários de desconto costumam ser uma longa lista de campos, e os lojistas não sabem o que montaram até um cliente reclamar. Escreva a oferta como uma frase com partes editáveis, "Clientes ganham 15% de desconto em Ring Vase e Glass Carafe", coloque compra mínima, elegibilidade de clientes, limites de uso, combinações e agendamento em linhas recolhidas que mostram o valor atual, e mostre o preview de um checkout de exemplo com o desconto aplicado.
Avise sobre configurações arriscadas, como 100% de desconto sem limite de uso, e agende no fuso horário da loja, não no do navegador.
Uma loja, todas as páginas
A última peça é a consistência. Reembolse um item e o status de pagamento do pedido, a linha do tempo dele, o total gasto pelo cliente e o estoque que você repôs devem mudar juntos. Faça toda mutation retornar as entidades que alterou e atualize ou revalide cada visualização que as mostra. E bloqueie a navegação quando houver edições não salvas de produto, estoque ou desconto, incluindo o botão Voltar do navegador.
Os blocos do HextaUI para isso
Esses são os problemas em torno dos quais os blocos de admin do HextaUI Pro foram construídos. O Orders Table tem visualizações salvas na URL, pílulas separadas de pagamento e envio com glifos de progresso, e ações em massa que informam falhas parciais. O Refund faz a conta em calculateRefund(order, selection), uma função pura com testes que usa o método do maior resto. O Inventory salva ajustes, não sobrescritas, e deixa os lojistas reaplicarem linhas rejeitadas. O Discount Builder edita a oferta como uma frase ao lado de um preview de checkout ao vivo.
O Commerce Dashboard junta tudo isso com Order Detail, Product Editor e Sales Overview num único admin de loja sobre um store compartilhado, com rotas por hash por padrão ou as suas rotas do Next.js pela prop de router, e exemplos que mapeiam cada ação para a Shopify Admin API. O preview ao vivo é o dashboard completo, então você pode reembolsar um pedido e ver todas as páginas se atualizarem.
FAQ
Um pedido deve ter um status ou dois? Pelo menos dois: pagamento e envio são independentes, e os lojistas filtram por combinações deles.
Como eu divido um desconto entre os itens reembolsados? Em centavos inteiros, proporcionalmente ao preço de cada linha, usando o método do maior resto para que as partes somem exatamente.
Edições de estoque devem enviar a nova quantidade ou a diferença? A diferença. Deltas sobrevivem a pedidos feitos enquanto o lojista estava editando; uma contagem do tipo "definir como" deve ser conferida contra a quantidade em que se baseou.
Onde os filtros de um admin devem ficar? Na URL, para que as visualizações possam ser compartilhadas, recarregadas e navegadas com o Voltar.