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
| Arquivo | Responsabilidade |
|---|---|
features/catalog/catalog.controller.ts | Recebe o cliente vindo por parâmetro de rota, debounce e persistência do filtro |
features/catalog/components/header/header.catalog.tsx + .controller.ts | Alterna entre selecionar cliente, tabela de preço ou condição de pagamento |
storage/catalog-preferences.storage.ts | Persistência do filtro (catalog:filterState) e do modo de visualização |
services/product/product.service.ts | Aplica 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).
Escolhendo cliente, tabela de preço ou condição de pagamento direto no catálogo
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.