MÓDULO 3.3

📊 CRM Kanban + Dashboard de Métricas

Pipeline com drag-and-drop entre estágios, card de deal bem desenhado, dashboard com 4 métricas que conectam com receita, charts em Recharts e filtros sem poluir.

6
Tópicos
60
Minutos
Inter
Nível
Build
Tipo
1

🪜 Pipeline: Estados do Funil

Pipeline genérico não serve ninguém. Adaptar os estados ao nicho do cliente é o que faz o CRM virar ferramenta usada todo dia, não tela parada.

📋 Pipeline padrão B2B (default do InboxAI)

Lead novo contato Qualificado tem fit Proposta enviada Fechado ganhou ✓ Perdido churn ✗
Nicho Pipeline adaptado
Clínica médicaLead → Pré-agendado → Atendeu → Em tratamento → Alta
ImobiliáriaLead → Visitou → Negociando → Contrato → Fechado/Perdido
SaaS B2BLead → Demo → POC → Proposta → Closed Won/Lost
E-commerceCarrinho abandonado → Recuperado → Pago → Entregue → Fidelizado

💡 Estados terminais sempre dois

Todo pipeline acaba em Won ou Lost (ou equivalente). Sem o "Lost" claro, deals ficam acumulando em "Negociando" há 6 meses e o vendedor não sabe que perdeu. Forçar fechamento é parte da disciplina que o produto tem que impor.

2

🔄 Drag-and-Drop entre Colunas

Implementar DnD próprio dá mais trabalho que vale; biblioteca testada economiza dias e tem acessibilidade já resolvida. @hello-pangea/dnd é o fork mantido do react-beautiful-dnd e é a escolha óbvia em 2026.

📦 Instalação

npm install @hello-pangea/dnd

⚙️ Snippet — Kanban com DnD

'use client';
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
import type { DropResult } from '@hello-pangea/dnd';

export function PipelineBoard({ stages, deals, onMove }: Props) {
  const handleDragEnd = (result: DropResult) => {
    if (!result.destination) return;
    const { draggableId, destination } = result;
    onMove(draggableId, destination.droppableId, destination.index);
  };

  return (
    <DragDropContext onDragEnd={handleDragEnd}>
      <div className="flex gap-4 overflow-x-auto">
        {stages.map((stage) => (
          <Droppable key={stage.id} droppableId={stage.id}>
            {(provided) => (
              <div
                ref={provided.innerRef}
                {...provided.droppableProps}
                className="flex-shrink-0 w-72 bg-neutral-800 rounded-lg p-3"
              >
                <h3 className="text-sm font-bold text-purple-400 mb-3">
                  {stage.name} ({stage.dealCount})
                </h3>
                {deals.filter((d) => d.stageId === stage.id).map((deal, i) => (
                  <Draggable key={deal.id} draggableId={deal.id} index={i}>
                    {(p) => (
                      <div ref={p.innerRef} {...p.draggableProps} {...p.dragHandleProps}>
                        <DealCard deal={deal} />
                      </div>
                    )}
                  </Draggable>
                ))}
                {provided.placeholder}
              </div>
            )}
          </Droppable>
        ))}
      </div>
    </DragDropContext>
  );
}

⚡ Optimistic UI: o detalhe que separa amador de pro

Em onMove, atualize o estado local antes de chamar a API. Se a API falhar, reverta. Sem isso, o card "pula" pra origem 200ms depois — o usuário sente o lag e desconfia. Com isso, parece instantâneo.

3

💳 Card do Deal

Cards bem desenhados são lidos em 2 segundos. Mal desenhados, o vendedor abre cada um pra entender — e aí o kanban deixa de ter graça.

📐 Anatomia do card

Clínica Saúde+
Maria Santos
75%
R$ 18.500
há 3 dias ⚠️ 7d parado
B2B enterprise

📋 Hierarquia de info

  1. 1. Empresa + contato — quem é, em 1 segundo.
  2. 2. Valor — número grande, cor de receita (verde).
  3. 3. Probabilidade % — pill no canto.
  4. 4. Tempo parado — alerta visual após 7 dias.
  5. 5. Tags — só se houver, máximo 3.

⚙️ Snippet — DealCard

type Deal = {
  id: string;
  company: string;
  contactName: string;
  amount: number;
  probability: number;        // 0-100
  stageEnteredAt: number;     // epoch
  tags: string[];
};

export function DealCard({ deal }: { deal: Deal }) {
  const daysIdle = Math.floor((Date.now() - deal.stageEnteredAt) / 86400000);
  const isStale = daysIdle >= 7;

  return (
    <div className="bg-neutral-700 rounded-lg p-3 mb-2 hover:ring-1 hover:ring-purple-500">
      <div className="flex justify-between items-start mb-1">
        <div className="font-semibold text-sm">{deal.company}</div>
        <span className="text-xs bg-purple-500/20 text-purple-400 px-2 rounded-full">
          {deal.probability}%
        </span>
      </div>
      <div className="text-xs text-neutral-400 mb-2">{deal.contactName}</div>
      <div className="text-lg font-bold text-emerald-400 mb-2">
        {deal.amount.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' })}
      </div>
      {isStale && (
        <span className="text-xs bg-amber-500/20 text-amber-400 px-2 rounded-full">
          ⚠️ {daysIdle}d parado
        </span>
      )}
    </div>
  );
}

⏰ O alerta de "dias parado" é o segredo

Vendedor procrastina deal complicado. Sem sinal visual, ele some no meio dos 50 cards. Com o badge âmbar após 7 dias, vira chamada à ação no formato exato em que o cérebro humano responde — vermelho/âmbar = "olha aqui agora".

4

📈 Dashboard: Métricas-Chave

Dashboard com 30 métricas é dashboard que ninguém olha. Foco em 4 que conectam com receita é o que faz dono de negócio abrir todo dia.

⏱️ Tempo de resposta ↓ 18%
2m 14s
Mediana primeira resposta — 7 dias
🎯 Conversão ↑ 4pp
23.7%
Lead → Fechado — mês atual
💰 Pipeline ↑ R$ 42k
R$ 384k
Valor ponderado por probabilidade
📥 CAC ↑ 8%
R$ 287
Custo por aquisição — todos canais

🎯 Vanity vs Actionable

Métrica vanity: "1.247 conversas neste mês". Bonita, inacionável.

Métrica actionable: "tempo mediano de resposta subiu de 2m pra 4m". Mostra o quê fazer: contratar mais um operador, ativar IA de primeira resposta, ou redistribuir turno.

5

📊 Charts com Recharts

Recharts é a biblioteca de fato pra Next.js — declarativa, baseada em SVG, fácil de customizar. Saber configurar bem cada chart (cores, tooltip, eixos) diferencia dashboard amador de profissional.

📦 Instalação

npm install recharts
📈

LineChart

Tendência ao longo do tempo. Conversões/dia, tempo de resposta semanal.

📊

BarChart

Comparação entre categorias. Volume por canal, ranking de vendedores.

🍩

DonutChart

Proporção de um todo. % por canal, distribuição de status.

⚙️ Snippet — LineChart configurado

import {
  LineChart, Line, XAxis, YAxis,
  Tooltip, ResponsiveContainer, CartesianGrid,
} from 'recharts';

export function ResponseTimeChart({ data }: { data: Point[] }) {
  return (
    <ResponsiveContainer width="100%" height={240}>
      <LineChart data={data}>
        <CartesianGrid strokeDasharray="3 3" stroke="#374151" />
        <XAxis dataKey="day" stroke="#9ca3af" fontSize={12} />
        <YAxis stroke="#9ca3af" fontSize={12} unit="m" />
        <Tooltip
          contentStyle={{ background: '#1f2937', border: '1px solid #7c3aed' }}
          labelStyle={{ color: '#a78bfa' }}
        />
        <Line
          type="monotone"
          dataKey="responseMin"
          stroke="#7c3aed"
          strokeWidth={2}
          dot={{ fill: '#7c3aed', r: 3 }}
          activeDot={{ r: 6 }}
        />
      </LineChart>
    </ResponsiveContainer>
  );
}

🎨 Paleta consistente em todos os charts

Defina uma paleta global (purple-400 primário, emerald-400 sucesso, amber-400 alerta, red-400 crítico) e use em todo dashboard. Charts coloridos arbitrariamente parecem amadores. 3-4 cores semânticas é o teto.

6

🔍 Filtros e Segmentações

Filtro mal feito ocupa metade da tela e ninguém usa. Filtro bem feito (tipo Linear) destrava produtividade sem custo visual — aparece quando precisa, some quando não.

❌ Filtros mal feitos

  • • 8 dropdowns sempre visíveis
  • • Sem indicador de quantos filtros ativos
  • • Reset sumido no canto
  • • Não persiste ao recarregar
  • • Não dá pra salvar combinação

✅ Filtros bem feitos

  • ✓ Botão "+ Filter" abre popover
  • ✓ Pills horizontais mostram ativos
  • ✓ X em cada pill remove individual
  • ✓ "Clear all" só aparece com 2+
  • ✓ Salvar como "Saved view"

🏷️ Filtros essenciais para CRM

Filtro UI sugerida Caso de uso
CanalMulti-select com íconesVer só leads do WhatsApp
AtendenteAvatar pickerMeus deals
Faixa de valorRange sliderDeals enterprise (R$ 10k+)
TagCombobox com autocompleteVertical específica
Status temporalPills exclusivos"Parados > 7 dias"

💾 Saved Views: o cliente vai pedir

A partir do segundo mês de uso, todo cliente pede "salvar essa combinação de filtros". Antecipe — implemente Saved Views já. Use query params na URL pra cada view ser compartilhável (link copiado abre o filtro). Ganho de UX gigante por trabalho mínimo.

O que Aprendemos

Pipeline com 4-5 estágios + 2 terminais — adapte ao nicho; Won/Lost obrigam fechamento.
DnD com @hello-pangea/dnd + optimistic UI — bibioteca testada vence reinventar.
Card lido em 2 segundos: empresa, valor, %, dias parado — alerta âmbar após 7 dias é o salto de UX.
4 métricas que importam: tempo, conversão, pipeline, CAC — actionable > vanity sempre.
Recharts com paleta semântica de 3-4 cores — Line, Bar e Donut cobrem 95% dos casos.
Filtros progressivos + Saved Views compartilháveis — UX que aparece só quando precisa.

Próximo Módulo:

3.4 — Browser Embutido + Iteração Visual: o recurso exclusivo do Codex App que corta ciclos de feedback de minutos para segundos.