Pular para o conteúdo principal

Cliente e Persistência de Filtro

Última atualização: 15 de julho de 2026

Visão Geral

O filtro do catálogo é persistido em MMKV e sobrevive a navegações e reaberturas do app. O ponto de entrada mais comum para preenchê-lo é vir de um cliente já selecionado na carteira, mas o cabeçalho do catálogo também permite escolher cliente, tabela de preço ou condição de pagamento diretamente, sem depender da carteira.

Arquivos-chave

ArquivoResponsabilidade
features/catalog/catalog.controller.tsRecebe o cliente vindo por parâmetro de rota, debounce e persistência do filtro
features/catalog/components/header/header.catalog.tsx + .controller.tsAlterna entre selecionar cliente, tabela de preço ou condição de pagamento
storage/catalog-preferences.storage.tsPersistência do filtro (catalog:filterState) e do modo de visualização
services/product/product.service.tsAplica o cálculo de preço quando há tabela de preço no filtro

Chegando ao catálogo a partir de um cliente

O caminho documentado em Seleção e Navegação grava o cliente tanto no storage do catálogo quanto nos parâmetros de rota. O controller do catálogo aplica esse cliente vindo por parâmetro assim que a tela monta:

// catalog.controller.ts
useEffect(() => {
if (!selectedClientParam) return;

try {
const parsedClient = JSON.parse(selectedClientParam) as Client;
if (!parsedClient || typeof parsedClient !== "object") return;

setFilter((previous) => ({
...previous,
letter: "",
client: parsedClient,
nomeCliente: parsedClient.nomeFantasia || parsedClient.razaoSocial || parsedClient.idExterno || null,
}));
} catch (error) {
console.warn("[CatalogController] Falha ao aplicar cliente do catálogo", error);
}
}, [selectedClientAtParam, selectedClientParam]);

O efeito depende de selectedClientAtParam, um timestamp enviado junto do cliente, exatamente para conseguir disparar de novo mesmo que o mesmo cliente seja selecionado duas vezes em sequência (o parâmetro selectedClient sozinho não mudaria, mas o timestamp muda).

O cabeçalho do catálogo tem um seletor que alterna o que o campo de busca principal representa: cliente (padrão), tabela de preço, ou condição de pagamento. Ao escolher "cliente" por esse caminho, o app abre a própria tela de carteira em modo de seleção:

// header.catalog.tsx
<ClientSelectModal
visible={openClientSelectModal}
onSelect={/* grava o cliente escolhido no filtro do catálogo */}
onClose={() => setOpenClientSelectModal(false)}
/>

ClientSelectModal é só um wrapper em modal para a mesma tela features/client/client.tsx usada pela carteira, no modo isSelectClient descrito em Seleção e Navegação. Não existe um segundo seletor de cliente reimplementado para o catálogo.

O filtro é salvo com debounce, não a cada tecla

Toda alteração de filtro passa por um debounce de 900ms antes de dois efeitos disparar: buscar produtos de novo, e persistir o filtro atual no storage:

// catalog.controller.ts
const DEBOUNCE_DELAY_MS = 900;
const debouncedFilter = useDebounce(filter, DEBOUNCE_DELAY_MS);

useEffect(() => {
currentPage.current = 1;
getData(1);
}, [debouncedFilter, productQuerySettingsKey]);

useEffect(() => {
setCatalogFilterState(debouncedFilter);
}, [debouncedFilter]);

Isso evita disparar uma busca no SQLite e uma escrita no MMKV a cada caractere digitado no campo de busca, esperando 900ms de silêncio antes de agir. productQuerySettingsKey combina duas settings (showProductWithoutPrice, showProductOnlyWithStock) numa única string estável, então o efeito de busca também roda de novo se o usuário mudar essas preferências, sem precisar recriar a função de busca inteira.

Tabela de preço e cálculo de preço não vêm automaticamente do cliente

Vale um cuidado aqui: escolher um cliente na carteira e abrir o catálogo preenche filter.client, mas não preenche filter.codTab. O cálculo de preço via calculatePriceService só roda quando existe uma tabela de preço no filtro:

// product.service.ts
if (params?.filter?.codTab) {
products = calculatePriceService.calculatePrice({
products,
codTab: params?.filter?.codTab,
client: params?.filter?.client ?? undefined,
condicaoPagamento: params?.filter?.condicaoPagamento,
company: params?.filter?.company,
});
}

Na prática, isso significa que abrir o catálogo só com um cliente selecionado, sem escolher também uma tabela de preço pelo cabeçalho, não aciona o cálculo de preço personalizado por cliente. O Client tem um campo codTab próprio (a tabela de preço padrão dele), mas nenhum código nesta feature lê esse campo do cliente para preencher filter.codTab automaticamente. Se o comportamento esperado é que selecionar um cliente já traga a tabela de preço dele, vale confirmar se isso é uma lacuna a corrigir ou uma decisão deliberada de exigir a escolha explícita da tabela de preço.

Armadilhas conhecidas

Não assuma que selecionar um cliente já é suficiente para ver preços calculados no catálogo. Confirme se filter.codTab está preenchido antes de investigar por que um produto aparece sem o preço esperado. E como o filtro é debounced em 900ms, uma escrita direta em storage/catalog-preferences.storage.ts fora desse fluxo (por exemplo, um teste automatizado que lê o storage logo após uma mudança de filtro) pode ler um valor ainda não persistido, se não esperar esse intervalo.