🌐 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
Command Palette (Busca Global)
💡 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.
🧩 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
Multi Select
Varias opcoes (logica OR)
Range Filter
Valor entre min e max
// 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
📅 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 └──────────────────────────────────────────────────────────┘
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.
🎯 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)
Apos filtrar por "Ativo"
Box de Dados
// 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.
🏷️ 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
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
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
🔗 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
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
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
💡 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.