Sistema de Filtros de Relatório — Vona
O sistema de filtros de relatório do Vona sincroniza o estado dos filtros com a URL via search params, exibindo um modal de edição e chips de ação rápida. Todos os componentes são exportados de um único módulo em _components/report-filter/.
Configurar o useReportFilters
O hook useReportFilters lê e escreve os filtros ativos como search params da URL. Recebe um ReportFilterConfig e retorna o estado e as funções de controle.
import { useReportFilters } from '../_components/report-filter';
const filterConfig: ReportFilterConfig = {
fields: ['intervalStart', 'intervalEnd', 'state', 'group'],
stateOptions: [
{ value: 'SP', label: 'São Paulo' },
{ value: 'RJ', label: 'Rio de Janeiro' },
],
};
const {
activeFilters, // { intervalStart?: string, state?: string, ... }
activeCount, // número de filtros ativos
applyFilters, // (filters: ActiveFilters) => void
removeFilter, // (key: FilterFieldKey) => void
clearFilters, // () => void
getChips, // () => FilterChip[]
} = useReportFilters(filterConfig);
Os filtros são persistidos em search params: ?intervalStart=2025-01-01&state=SP. Chamar applyFilters ou removeFilter navega para a nova URL via router.push.
Adicionar o modal e os chips
import { useState } from 'react';
import {
ReportFilterButton,
ReportFilterChips,
ReportFilterModal,
useReportFilters,
} from '../_components/report-filter';
export function ReportHeader() {
const [modalOpen, setModalOpen] = useState(false);
const { activeFilters, activeCount, applyFilters, removeFilter, clearFilters, getChips } =
useReportFilters(filterConfig);
return (
<>
<ReportFilterButton
activeCount={activeCount}
onClick={() => setModalOpen(true)}
/>
<ReportFilterChips
chips={getChips()}
onRemove={removeFilter}
onClearAll={clearFilters}
/>
<ReportFilterModal
open={modalOpen}
onClose={() => setModalOpen(false)}
config={filterConfig}
initialValues={activeFilters}
onConfirm={(filters) => {
applyFilters(filters);
setModalOpen(false);
}}
/>
</>
);
}
Definir o ReportFilterConfig
ReportFilterConfig controla quais campos aparecem no modal e quais opções estão disponíveis em cada select.
interface ReportFilterConfig {
/** Quais campos exibir no modal. */
fields: FilterFieldKey[];
saleModalityOptions?: FilterOption[];
acronymOptions?: FilterOption[];
stateOptions?: FilterOption[];
groupOptions?: FilterOption[];
dealershipOptions?: FilterOption[];
}
Campos disponíveis (FilterFieldKey):
| Campo | Label exibido | Componente no modal |
|---|---|---|
intervalStart | De | DateRangePicker (compartilhado com intervalEnd) |
intervalEnd | Até | DateRangePicker (compartilhado com intervalStart) |
saleModality | Modalidade de venda | NativeSelect |
acronyms | Siglas | NativeSelect |
state | Estado | Select (shadcn) |
group | Grupo | Select (shadcn) |
dealerships | Concessionárias | NativeSelect |
personType | Tipo de pessoa | NativeSelect (Física / Jurídica / Ambos) |
Inclua em fields apenas os campos que fazem sentido para o relatório. Um campo em fields que não tenha as options correspondentes simplesmente não aparece no modal (stateOptions, groupOptions, etc. só são renderizados se a array existir).
DateRangePicker
DateRangePicker é o componente de seleção de intervalo de datas usado pelos campos intervalStart e intervalEnd. Pode ser usado de forma independente.
import { DateRangePicker, type DateRange } from '../_components/report-filter/date-range-picker';
const [range, setRange] = useState<DateRange | undefined>();
<DateRangePicker
value={range}
onChange={setRange}
data-test="meu-date-picker"
/>
Props:
| Prop | Tipo | Obrigatório | Descrição |
|---|---|---|---|
value | DateRange | undefined | Não | Intervalo selecionado ({ from: Date, to?: Date }) |
onChange | (range: DateRange | undefined) => void | Não | Callback chamado ao confirmar |
className | string | Não | Classe CSS adicional no botão trigger |
data-test | string | Não | Atributo de teste (padrão: "date-range-picker") |
O picker abre um Popover com calendário de dois meses em pt-BR. A seleção só é confirmada ao clicar em Confirmar — fechar o Popover descarta a seleção pendente.
Chips de filtro ativo
ReportFilterChips exibe os filtros ativos como tags removíveis. Retorna null quando não há filtros ativos.
<ReportFilterChips
chips={getChips()} // FilterChip[]
onRemove={removeFilter}
onClearAll={clearFilters}
/>
Os chips usam os labels das options quando disponíveis (ex: exibe "São Paulo" em vez de "SP"). Para personType, os labels são Física, Jurídica e Ambos.
Exportações do módulo
// Componentes
export { ReportFilterButton } from './report-filter-button';
export { ReportFilterChips } from './report-filter-chips';
export { ReportFilterModal } from './report-filter-modal';
// Hook
export { useReportFilters } from './use-report-filters';
// Utils
export { ALL_FILTER_KEYS, FILTER_LABELS, PERSON_TYPE_LABELS, getChipsFromFilters } from './report-filter.utils';
// Types
export type { ActiveFilters, FilterChip, FilterFieldKey, FilterOption, ReportFilterConfig } from './types';
Adicionando opções de filtro vindas do servidor
As opções dos selects (stateOptions, groupOptions, etc.) geralmente vêm de uma query ao DW via service. O padrão nas páginas existentes é buscar as opções no Server Component e passá-las para o componente de cabeçalho:
// page.tsx (Server Component)
const { stateOptions, groupOptions } = await createRankingService().getFilterOptions();
return <RankingHeader stateOptions={stateOptions} groupOptions={groupOptions} />;
// RankingHeader (Client Component)
const filterConfig: ReportFilterConfig = {
fields: ['intervalStart', 'intervalEnd', 'state', 'group', 'personType'],
stateOptions,
groupOptions,
};