Inicio / Trilha 2 / Modulo 2.4
MODULO 2.4

🔍 Filtros e Busca

Sistemas de busca inteligente, filtros avancados, date pickers e faceted search para encontrar dados rapidamente.

📚
6 Topicos
⏱️
50 min
📊
Intermediario
🎯
Pratico
1

🌐 Busca Global vs Contextual

Dois paradigmas de busca que juntos cobrem todas necessidades do usuario

Todo dashboard profissional implementa dois tipos de busca: a busca global (pesquisa em todo o sistema) e a busca contextual (filtra dados na visualizacao atual). A busca global e tipicamente ativada por um atalho de teclado (Cmd+K ou Ctrl+K) e abre um modal "Command Palette" que permite acessar qualquer recurso do sistema rapidamente.

A busca contextual fica inline na interface, geralmente acima de tabelas ou listas, e filtra apenas os dados visiveis naquela view. Enquanto a busca global navega entre secoes, a contextual refina o que ja esta na tela. Dashboards de alta qualidade sempre oferecem ambas.

Box de Detalhamento

Busca Global (Command Palette)
  • Atalho: Cmd+K (Mac) ou Ctrl+K (Win)
  • Pesquisa em todas entidades (clientes, pedidos, produtos)
  • Executa acoes (criar pedido, abrir relatorio)
  • Mostra resultados recentes para acesso rapido
  • Navega entre paginas do sistema
Busca Contextual (Inline)
  • Campo de texto acima de tabelas/listas
  • Filtra apenas dados da view atual
  • Pesquisa em todas colunas visiveis
  • Mostra contador de resultados (ex: "23 de 156")
  • Nao navega, apenas refina visualizacao

Box de Dados

70%
Reducao no tempo de navegacao
Cmd+K
Atalho padrao da industria
2x
Mais rapido que menus
85%
Usuarios power preferem

Command Palette (Busca Global)

🔍 Buscar em tudo... ⌘K
Recentes
📋 Pedido #12345 Pedidos
👤 Cliente: Ana Silva Clientes
Acoes
Novo Pedido
📊 Ver Relatorio de Vendas

💡 Dica Pratica

Implemente debounce de 300ms na busca para evitar requisicoes excessivas enquanto usuario digita. Use bibliotecas como lodash.debounce ou use-debounce para React. Isso melhora performance e reduz carga no servidor.

2

🧩 Filtros Basicos e Compostos

Construindo sistemas de filtragem poderosos com logica AND/OR

Filtros basicos permitem selecionar um valor unico (status = "Ativo"). Filtros compostos combinam multiplos criterios usando logica booleana. A maioria dos dashboards usa AND entre diferentes tipos de filtro (status E categoria E data) e OR dentro do mesmo tipo (status = "Ativo" OU "Pendente").

Uma consulta como "Pedidos pendentes do mes passado acima de R$1.000" combina tres filtros: status (pendente), data (mes passado) e valor (> R$1.000). Sem filtros compostos, essa analise seria impossivel ou muito trabalhosa.

Box de Detalhamento

Single Select

Apenas uma opcao por vez

◯ Ativo
◉ Pendente
◯ Cancelado
Multi Select

Varias opcoes (logica OR)

☑ Ativo
☑ Pendente
☐ Cancelado
Range Filter

Valor entre min e max

Min: R$ 100
Max: R$ 5.000
filters.js
// Estrutura de estado dos filtros
const filters = {
  status: ['ativo', 'pendente'],  // multi-select (OR)
  categoria: 'eletronicos',       // single-select
  valorMin: 100,                  // range
  valorMax: 5000,
  dataInicio: '2024-01-01',       // date range
  dataFim: '2024-12-31'
};

// Aplicar filtros (AND entre tipos, OR dentro de multi-select)
function applyFilters(data, filters) {
  return data.filter(item => {
    // Status (OR entre opcoes selecionadas)
    if (filters.status.length > 0 &&
        !filters.status.includes(item.status)) {
      return false;
    }

    // Categoria (match exato)
    if (filters.categoria &&
        item.categoria !== filters.categoria) {
      return false;
    }

    // Range de valor
    if (item.valor < filters.valorMin ||
        item.valor > filters.valorMax) {
      return false;
    }

    // Range de data
    const itemDate = new Date(item.data);
    if (itemDate < new Date(filters.dataInicio) ||
        itemDate > new Date(filters.dataFim)) {
      return false;
    }

    return true; // Passou todos os filtros (AND)
  });
}
✓ Fazer
  • • Mostrar contador de resultados apos filtrar
  • • Permitir limpar todos filtros com um clique
  • • Manter filtros ao navegar entre paginas
  • • Usar defaults inteligentes (ex: ultimos 30 dias)
✗ Evitar
  • • Filtros que resultam em zero dados sem aviso
  • • Logica de filtro confusa ou inconsistente
  • • Muitos filtros sem organizacao (use grupos)
  • • Resetar filtros ao mudar de aba
3

📅 Date Picker e Range de Datas

O filtro mais usado em dashboards apos busca textual

Dashboards sao inerentemente temporais. Perguntas como "Como foi ontem vs semana passada?" ou "Qual a tendencia do ultimo trimestre?" dependem de filtros de data bem implementados. O Date Range Picker e provavelmente o componente de filtro mais importante do seu dashboard.

Presets aceleram significativamente a selecao. "Ultimos 7 dias", "Este mes", "Ano passado" cobrem 80% dos casos de uso. O calendario completo fica disponivel para casos especificos via opcao "Personalizado".

Box de Detalhamento

Presets Essenciais
  • Hoje
  • Ontem
  • Ultimos 7 dias
  • Ultimos 30 dias
  • Este mes
  • Mes passado
  • Este trimestre
  • Personalizado...
Bibliotecas Recomendadas
  • react-datepicker - Mais popular para React
  • flatpickr - Vanilla JS, leve e customizavel
  • date-fns - Manipulacao de datas (nao UI)
  • react-day-picker - Altamente customizavel

Anatomia do Date Range Picker

┌──────────────────────────────────────────────────────────┐
│ 📅 01/01/2024 → 31/01/2024                        [▼]   │ ← Trigger
├──────────────────────────────────────────────────────────┤
│ Presets:                                                 │
│ [Hoje] [Ontem] [7 dias] [30 dias] [Este mes] [Custom]   │ ← Atalhos
├──────────────────────────────────────────────────────────┤
│        Janeiro 2024                Fevereiro 2024       │
│ Do Se Te Qu Qu Se Sa           Do Se Te Qu Qu Se Sa     │
│        1  2  3  4  5  6              ████  1  2  3      │
│  7  8  9 10 11 12 13            4  5  6  7  8  9 10     │
│ 14 ████████████████ 20         11 12 13 14 15 16 17     │ ← Range
│ 21 22 23 24 25 26 27           18 19 20 21 22 23 24     │
│ 28 29 30 31                    25 26 27 28 29           │
├──────────────────────────────────────────────────────────┤
│                         [Cancelar]  [Aplicar]           │ ← Acoes
└──────────────────────────────────────────────────────────┘
DateRangePicker.jsx
import { useState } from 'react';
import { subDays, startOfMonth, endOfMonth, format } from 'date-fns';

const presets = [
  { label: 'Hoje', getValue: () => [new Date(), new Date()] },
  { label: 'Ultimos 7 dias', getValue: () => [subDays(new Date(), 7), new Date()] },
  { label: 'Este mes', getValue: () => [startOfMonth(new Date()), new Date()] },
  { label: 'Mes passado', getValue: () => {
    const lastMonth = subDays(startOfMonth(new Date()), 1);
    return [startOfMonth(lastMonth), endOfMonth(lastMonth)];
  }},
];

function DateRangePicker({ value, onChange }) {
  const [isOpen, setIsOpen] = useState(false);

  const formatRange = ([start, end]) =>
    `${format(start, 'dd/MM/yyyy')} → ${format(end, 'dd/MM/yyyy')}`;

  return (
    <div className="relative">
      <button onClick={() => setIsOpen(!isOpen)}>
        📅 {formatRange(value)}
      </button>
      {isOpen && (
        <div className="absolute mt-2 bg-dark-800 border rounded-lg p-4">
          {presets.map(preset => (
            <button key={preset.label} onClick={() => {
              onChange(preset.getValue());
              setIsOpen(false);
            }}>
              {preset.label}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

💡 Dica Pratica

Sempre armazene datas em UTC no backend e converta para timezone do usuario no frontend. Use ISO 8601 (2024-01-15T10:30:00Z) para APIs. Isso evita bugs em dashboards usados por equipes em diferentes fusos horarios.

4

🎯 Faceted Search

Filtros inteligentes que se adaptam aos dados disponiveis

Faceted Search (busca facetada) mostra filtros dinamicos com contadores baseados nos dados disponiveis. Ao selecionar um filtro, os outros atualizam automaticamente para mostrar apenas opcoes que ainda tem resultados. Isso evita "becos sem saida" onde usuario combina filtros que resultam em zero dados.

Esse padrao e onipresente em e-commerce (Amazon, Mercado Livre) e essencial para dashboards com muitas dimensoes de analise. O usuario sempre ve quantos itens cada opcao retornara antes de clicar.

Box de Detalhamento

Estado Inicial (600 itens)
Status
☐ Ativo450
☐ Pendente120
☐ Cancelado30
Categoria
☐ Eletronicos200
☐ Roupas300
☐ Alimentos100
Apos filtrar por "Ativo"
Status
☑ Ativo450
☐ Pendente0
☐ Cancelado0
Categoria (atualizado)
☐ Eletronicos180
☐ Roupas220
☐ Alimentos50

Box de Dados

95%
E-commerces usam
0
Becos sem saida
40%
Menos cliques
Real-time
Contadores atualizados
facetedSearch.js
// Calcula facetas baseado nos dados filtrados
function calculateFacets(allData, currentFilters) {
  // Primeiro, aplica todos os filtros exceto o da faceta sendo calculada
  const facets = {};

  // Para cada tipo de faceta
  ['status', 'categoria', 'regiao'].forEach(facetType => {
    // Aplica todos os filtros EXCETO este tipo
    const filtersWithoutThis = { ...currentFilters };
    delete filtersWithoutThis[facetType];

    const filteredData = applyFilters(allData, filtersWithoutThis);

    // Conta ocorrencias de cada valor nesta faceta
    facets[facetType] = {};
    filteredData.forEach(item => {
      const value = item[facetType];
      facets[facetType][value] = (facets[facetType][value] || 0) + 1;
    });
  });

  return facets;
}

// Exemplo de retorno:
// {
//   status: { ativo: 450, pendente: 0, cancelado: 0 },
//   categoria: { eletronicos: 180, roupas: 220, alimentos: 50 },
//   regiao: { sul: 150, sudeste: 200, norte: 100 }
// }

💡 Dica Pratica

Para datasets grandes, calcule facetas no backend com agregacoes SQL ou ElasticSearch. Evite calcular no frontend com mais de 10.000 itens. Use GROUP BY e COUNT para performance.

5

🏷️ Filter Pills e Tags

Indicadores visuais dos filtros ativos para transparencia total

Filter Pills sao indicadores visuais dos filtros ativos, exibidos como tags removiveis. Sem eles, usuarios frequentemente esquecem quais filtros aplicaram e acham que os dados estao "errados" quando na verdade estao filtrados. Pills criam transparencia e controle.

Cada pill mostra o nome do filtro e seu valor, com um botao X para remover. Uma opcao "Limpar todos" deve estar sempre disponivel quando ha filtros ativos. Pills geralmente ficam logo acima da area de dados filtrados.

Exemplo de Filter Pills

Status: Ativo Data: Jan 2024 Valor: R$ 100 - R$ 5.000
Mostrando 234 de 1.450 registros

Box de Detalhamento

Anatomia do Filter Pill
  • 1. Label: Nome do filtro (ex: "Status")
  • 2. Valor: Valor selecionado (ex: "Ativo")
  • 3. Separador: Dois pontos ou icone
  • 4. Botao fechar: Remove o filtro (×)
Boas Praticas
  • Mostrar contador de resultados filtrados
  • Agrupar multiplos valores do mesmo tipo
  • Truncar valores longos com ellipsis
  • Tooltip com valor completo se truncado
FilterPills.jsx
function FilterPills({ filters, onRemove, onClearAll }) {
  const activeFilters = Object.entries(filters)
    .filter(([_, value]) => value !== null && value !== 'todos');

  if (activeFilters.length === 0) return null;

  return (
    <div className="flex flex-wrap gap-2 mb-4">
      {activeFilters.map(([key, value]) => (
        <span
          key={key}
          className="inline-flex items-center px-3 py-1 rounded-full
                     bg-blue-500/20 text-blue-400 text-sm"
        >
          {formatLabel(key)}: {formatValue(value)}
          <button
            onClick={() => onRemove(key)}
            className="ml-2 hover:text-white"
          >
            ×
          </button>
        </span>
      ))}

      <button
        onClick={onClearAll}
        className="text-neutral-400 hover:text-white text-sm"
      >
        Limpar todos
      </button>
    </div>
  );
}

// Helper para formatar ranges de data
function formatValue(value) {
  if (Array.isArray(value)) {
    return value.join(' - ');
  }
  if (typeof value === 'object' && value.min !== undefined) {
    return `${value.min} - ${value.max}`;
  }
  return value;
}
✓ Fazer
  • • Mostrar pills imediatamente apos aplicar filtro
  • • Animar entrada/saida das pills
  • • Permitir editar valor clicando na pill
  • • Manter pills visiveis mesmo com scroll
✗ Evitar
  • • Esconder pills em menus ou accordions
  • • Nao ter opcao de limpar todos
  • • Pills muito grandes ocupando tela toda
  • • Nao mostrar contador de resultados
6

🔗 Persistencia de Filtros na URL

URLs compartilhaveis com estado preservado

Sincronizar filtros com query parameters da URL e uma pratica essencial em dashboards profissionais. Isso permite compartilhar links com filtros pre-aplicados, usar o botao voltar do navegador, e preservar estado ao atualizar a pagina.

"Olha esse relatorio" + link com filtros e muito mais util que instruir alguem a navegar ate uma pagina e aplicar filtros manualmente. Bookmarks funcionam, refresh nao perde estado, e a experiencia se torna previsivel.

Exemplo de URL com Filtros

https://dashboard.com/vendas?status=ativo&categoria=eletronicos&de=2024-01-01&ate=2024-01-31&ordenar=valor&direcao=desc
status = ativo
categoria = eletronicos
de = 2024-01-01
ate = 2024-01-31
ordenar = valor
direcao = desc

Box de Detalhamento

Beneficios
  • Compartilhar filtros especificos com colegas
  • Salvar em bookmarks para acesso rapido
  • Botao voltar preserva estado anterior
  • Refresh nao perde configuracao
  • Deep linking para emails e notificacoes
Quando usar pushState vs replaceState
  • pushState: Mudanca significativa (novo filtro), cria entrada no historico
  • replaceState: Ajustes menores (digitando no campo), nao polui historico
useUrlFilters.js
import { useEffect, useState } from 'react';

// Hook para sincronizar filtros com URL
function useUrlFilters(defaultFilters) {
  const [filters, setFilters] = useState(() => {
    // Inicializa com valores da URL ou defaults
    return getFiltersFromURL() || defaultFilters;
  });

  // Atualiza URL quando filtros mudam
  useEffect(() => {
    updateURL(filters);
  }, [filters]);

  // Escuta mudancas no historico (botao voltar)
  useEffect(() => {
    const handlePopState = () => {
      setFilters(getFiltersFromURL() || defaultFilters);
    };
    window.addEventListener('popstate', handlePopState);
    return () => window.removeEventListener('popstate', handlePopState);
  }, []);

  return [filters, setFilters];
}

function getFiltersFromURL() {
  const params = new URLSearchParams(window.location.search);
  if (params.toString() === '') return null;

  return {
    status: params.get('status') || 'todos',
    categoria: params.get('categoria') || null,
    dataInicio: params.get('de') || null,
    dataFim: params.get('ate') || null,
    ordenar: params.get('ordenar') || 'data',
    direcao: params.get('direcao') || 'desc'
  };
}

function updateURL(filters) {
  const params = new URLSearchParams();

  // So adiciona parametros com valor
  Object.entries(filters).forEach(([key, value]) => {
    if (value && value !== 'todos') {
      params.set(key, value);
    }
  });

  const newURL = params.toString()
    ? `${window.location.pathname}?${params}`
    : window.location.pathname;

  window.history.pushState({}, '', newURL);
}

Box de Dados

100%
Dashboards pro usam
5x
Mais facil colaborar
0
Estado perdido
SEO
Friendly para crawlers

💡 Dica Pratica

Use React Router ou Next.js useSearchParams para gerenciar query params de forma mais robusta. Evite manipular window.location diretamente em SPAs - use as abstraccoes do framework para evitar conflitos com o roteador.

📋 Resumo do Modulo

Tabelas e DataGrids Layouts e Grid Systems