165 lines
15 KiB
Markdown
165 lines
15 KiB
Markdown
# Feature Specification: Filtro de Busca Avançada — FilterSidebar
|
||
|
||
**Feature Branch**: `024-filtro-busca-avancada`
|
||
**Created**: 2026-04-20
|
||
**Status**: Draft
|
||
**Fonte**: Solicitação de melhoria UX no sidebar de filtros da página `/imoveis`
|
||
|
||
---
|
||
|
||
## Contexto
|
||
|
||
O `FilterSidebar` da página `/imoveis` já existe e é funcional, mas apresenta três lacunas de usabilidade que impactam a descoberta de imóveis:
|
||
|
||
1. **Ausência de busca cross-categoria dentro do sidebar**: o usuário precisa abrir seção por seção para encontrar um tipo de imóvel ou bairro específico.
|
||
2. **Todas as seções abertas por padrão**: ao carregar a página, o sidebar está completamente expandido, gerando sobrecarga visual e exigindo scroll antes mesmo de ver os resultados.
|
||
3. **Listas longas sem hierarquia de popularidade**: bairros e tipos são listados em ordem arbitrária; itens raramente usados ocupam o mesmo espaço visual que os mais procurados, dificultando a seleção rápida.
|
||
|
||
---
|
||
|
||
## User Scenarios & Testing
|
||
|
||
### User Story 1 — Campo de Busca Cross-Categoria no Sidebar (Priority: P1)
|
||
|
||
Um visitante que sabe exatamente o que procura (ex.: "Copacabana", "Cobertura") digita o termo no campo de busca do sidebar e vê instantaneamente em qual categoria aquela opção se enquadra, podendo selecioná-la com um clique sem precisar abrir seções manualmente.
|
||
|
||
**Why this priority**: A busca cross-categoria resolve o maior obstáculo de navegação do sidebar: o usuário com intenção definida não sabe em qual seção procurar. É o ganho de UX mais alto com menor complexidade de implementação — nenhum dado novo de backend é necessário, pois os dados já chegam via `catalog.ts`.
|
||
|
||
**Independent Test**: Digitar "Copa" no campo de busca do sidebar e verificar que aparece uma lista de sugestões agrupadas mostrando "Copacabana" sob o grupo "Bairro"; clicar em "Copacabana" e confirmar que o filtro de bairro é aplicado.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** a página `/imoveis` com o sidebar visível, **When** o usuário vê o topo do sidebar, **Then** um campo de busca com placeholder "Buscar filtro…" está disponível acima das seções accordion.
|
||
2. **Given** o campo de busca do sidebar com o valor "apar", **When** o usuário para de digitar por 200ms, **Then** uma lista de sugestões aparece inline (não em dropdown popup) mostrando entradas agrupadas, por exemplo: grupo "Tipo de imóvel" → "Apartamento".
|
||
3. **Given** sugestões de busca visíveis com múltiplas categorias correspondentes, **When** o usuário vê a lista, **Then** cada grupo tem um cabeçalho de categoria (ex.: "Tipo de imóvel", "Bairro", "Cidade") e os itens correspondentes abaixo.
|
||
4. **Given** uma sugestão visível, **When** o usuário clica nela, **Then** o filtro correspondente é aplicado (equivalente a selecionar o item na seção accordion), o campo de busca é limpo e a seção relevante é expandida para mostrar o item selecionado.
|
||
5. **Given** o campo de busca preenchido sem nenhuma correspondência, **When** a busca é executada, **Then** uma mensagem "Nenhum filtro encontrado para "[termo]"" é exibida no lugar das sugestões.
|
||
6. **Given** o campo de busca preenchido, **When** o usuário pressiona Escape ou limpa o campo, **Then** as sugestões são ocultadas e o estado das seções retorna ao normal.
|
||
7. **Given** a navegação por teclado com sugestões visíveis, **When** o usuário pressiona as teclas de seta (↓↑) e Enter, **Then** ele pode selecionar uma sugestão sem usar o mouse.
|
||
|
||
---
|
||
|
||
### User Story 2 — Seção de Preço Aberta por Padrão, Demais Colapsadas (Priority: P1)
|
||
|
||
Um visitante que acaba de chegar na página `/imoveis` encontra o sidebar com uma experiência limpa: apenas a seção de preço está expandida, tornando o filtro mais importante imediatamente visível, enquanto as demais seções ficam colapsadas e acessíveis sob demanda.
|
||
|
||
**Why this priority**: Juntamente com a busca cross-categoria, esta mudança tem impacto imediato na percepção de organização do sidebar sem exigir dados adicionais do backend. A seção de preço é o filtro de maior influência na decisão do usuário, justificando seu destaque inicial.
|
||
|
||
**Independent Test**: Carregar `/imoveis` pela primeira vez (ou sem parâmetros de URL) e verificar que somente a seção "Preço" está expandida; todas as demais seções (Tipo, Quartos, Bairros, etc.) estão colapsadas.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** a página `/imoveis` carregada sem filtros ativos, **When** o sidebar renderiza, **Then** apenas a seção "Preço" está expandida (`defaultOpen = true`); todas as demais seções têm `defaultOpen = false`.
|
||
2. **Given** filtros ativos presentes na URL (ex.: `?city=1&bedrooms=2`), **When** o sidebar renderiza, **Then** as seções que contêm filtros ativos ficam automaticamente expandidas, além da seção de Preço.
|
||
3. **Given** o usuário colapsou manualmente a seção de Preço e navega para outra página e retorna, **When** o sidebar renderiza, **Then** o estado de colapso/expansão das seções volta ao padrão (Preço aberto, demais fechados), pois esse estado não é persistido.
|
||
|
||
---
|
||
|
||
### User Story 3 — Listas com "Ver mais" e Ordenação por Popularidade (Priority: P2)
|
||
|
||
Um visitante que navega pelos filtros de bairro ou tipo de imóvel vê imediatamente os N itens mais relevantes (com mais imóveis disponíveis), pode expandir para ver todos com "Ver mais", e identifica visualmente os itens mais populares por meio de badges ou destaque.
|
||
|
||
**Why this priority**: Listas longas sem hierarquia sobrecarregam a interface e enterram as opções mais úteis. Exibir os mais populares primeiro e truncar com "Ver mais" é o padrão de portais imobiliários líderes. Requer dados de contagem do backend, tornando-o de implementação mais complexa que as stories P1.
|
||
|
||
**Independent Test**: Abrir a seção "Bairros" no sidebar e verificar que apenas os 5 bairros com mais imóveis são exibidos inicialmente, com badge "Popular" no primeiro; clicar em "Ver mais" e confirmar que todos os bairros são exibidos.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** uma seção de filtro com mais de 5 itens (ex.: Bairros), **When** o accordion é expandido, **Then** apenas os 5 primeiros itens são exibidos, ordenados do mais popular (mais imóveis associados) para o menos popular.
|
||
2. **Given** uma seção com mais de 5 itens exibindo os primeiros 5, **When** o usuário clica no botão "Ver mais (N)", **Then** todos os itens restantes são exibidos inline (sem modal ou navegação), e o botão muda para "Ver menos".
|
||
3. **Given** uma seção expandida com todos os itens visíveis, **When** o usuário clica em "Ver menos", **Then** a lista retorna a exibir apenas os 5 primeiros e o scroll retorna ao início da seção.
|
||
4. **Given** os itens de uma seção ordenados por popularidade, **When** o usuário vê a lista, **Then** os 3 itens com mais imóveis associados exibem um badge "Popular" ao lado do nome.
|
||
5. **Given** um filtro já selecionado que não está entre os 5 primeiros da lista, **When** o usuário reabre a seção com o filtro ativo, **Then** o item selecionado é exibido mesmo que esteja além dos 5 iniciais (o truncamento não oculta itens selecionados).
|
||
6. **Given** uma seção com 5 itens ou menos, **When** o accordion é expandido, **Then** o botão "Ver mais" não é exibido.
|
||
|
||
---
|
||
|
||
### Edge Cases
|
||
|
||
- O que acontece quando a busca cross-categoria retorna o mesmo item em múltiplas categorias (ex.: cidade e bairro com o mesmo nome)?
|
||
- Como o sistema lida com termos de busca contendo caracteres especiais (acentos, cedilha, hifens)?
|
||
- O que acontece quando um filtro selecionado não aparece mais na lista porque o backend não o retornou (ex.: cidade removida do catálogo)?
|
||
- Como o "Ver mais" se comporta quando um item selecionado está entre os ocultos — ele precisa estar visível mesmo sem clicar em "Ver mais"?
|
||
- O que acontece se os dados de popularidade (contagem de imóveis por bairro/tipo) ainda estão carregando quando o accordion é expandido?
|
||
- Como a seção de preço se comporta quando o parâmetro `listing_type` muda entre "venda" e "aluguel" (os presets já mudam — o estado de expansão da seção é preservado)?
|
||
- Qual é o comportamento do campo de busca do sidebar quando o catálogo ainda está carregando (`catalogLoading = true`)?
|
||
|
||
---
|
||
|
||
## Requirements
|
||
|
||
### Functional Requirements
|
||
|
||
#### Busca Cross-Categoria no Sidebar
|
||
|
||
- **FR-001**: O `FilterSidebar` DEVE exibir um campo de busca textual no topo, acima de todas as seções accordion, com placeholder "Buscar filtro…".
|
||
- **FR-002**: O campo de busca DEVE pesquisar simultaneamente em todas as categorias de filtro disponíveis: tipos de imóvel, cidades, bairros e comodidades.
|
||
- **FR-003**: A busca DEVE aplicar debounce de 200ms antes de exibir resultados, para não travar a interface durante digitação rápida.
|
||
- **FR-004**: Os resultados DEVEM ser apresentados agrupados por categoria, com o cabeçalho de cada grupo claramente identificado (ex.: "Tipo de imóvel", "Bairro", "Cidade", "Comodidade").
|
||
- **FR-005**: A busca DEVE ser case-insensitive e ignorar acentuação para maximizar correspondências (ex.: "copacabana" deve encontrar "Copacabana", "apto" deve encontrar "Apartamento").
|
||
- **FR-006**: Ao clicar em uma sugestão, o filtro correspondente DEVE ser aplicado imediatamente, o campo de busca DEVE ser limpo e a seção do accordion correspondente DEVE ser expandida.
|
||
- **FR-007**: Quando `catalogLoading = true`, o campo de busca DEVE estar desabilitado com estado visual de loading (cursor não permitido, opacidade reduzida).
|
||
- **FR-008**: A navegação por teclado (↑↓ para mover entre sugestões, Enter para selecionar, Escape para fechar) DEVE ser suportada para acessibilidade.
|
||
|
||
#### Estado Inicial das Seções
|
||
|
||
- **FR-009**: Ao carregar o `FilterSidebar` sem filtros pré-ativos na URL, apenas a seção "Preço" DEVE ter `defaultOpen = true`; todas as demais seções DEVEM ter `defaultOpen = false`.
|
||
- **FR-010**: Quando filtros pré-ativos existem na URL, as seções que contêm esses filtros ativos DEVEM ser inicialmente expandidas além da seção de Preço.
|
||
- **FR-011**: O estado de expansão das seções NÃO DEVE ser persistido entre sessões — cada carregamento de página retorna ao estado padrão.
|
||
|
||
#### Listas com Truncamento e Popularidade
|
||
|
||
- **FR-012**: Seções com mais de 5 itens DEVEM exibir apenas os 5 primeiros inicialmente, com um botão "Ver mais (N)" indicando quantos itens adicionais existem.
|
||
- **FR-013**: Os itens de cada seção DEVEM ser ordenados por popularidade, definida como a contagem de imóveis ativos associados àquele item (ex.: número de imóveis em cada bairro, número de imóveis de cada tipo).
|
||
- **FR-014**: Os 3 itens mais populares de cada seção DEVEM exibir um badge "Popular" ao lado do label.
|
||
- **FR-015**: Itens com filtro ativo (selecionados) DEVEM sempre ser visíveis, independentemente de estarem entre os 5 primeiros ou não.
|
||
- **FR-016**: O backend DEVE fornecer dados de contagem de imóveis por categoria (tipo de imóvel, cidade, bairro) para permitir ordenação por popularidade na camada de catálogo.
|
||
- **FR-017**: O botão "Ver mais" DEVE ter comportamento toggle: ao ser clicado novamente, exibe "Ver menos" e retorna a lista ao truncamento inicial.
|
||
|
||
### Requisitos Não-Funcionais
|
||
|
||
- **NFR-001**: A busca cross-categoria DEVE executar inteiramente no cliente (sem chamadas adicionais à API), utilizando os dados de catálogo já carregados.
|
||
- **NFR-002**: A animação de expansão/colapso das sugestões de busca DEVE seguir a mesma curva de animação das seções accordion existentes (CSS grid trick, `duration-200 ease-out`).
|
||
- **NFR-003**: O visual de todos os elementos novos (campo de busca, badges, botão "Ver mais") DEVE seguir os design tokens do projeto: `textTertiary`, `textSecondary`, `borderSubtle`, `borderStandard`, `surface`, `brand`.
|
||
- **NFR-004**: Nenhuma alteração visual ou de comportamento DEVE impactar o funcionamento dos filtros existentes (os filtros aplicados via accordion continuam funcionando normalmente).
|
||
- **NFR-005**: A contagem de imóveis por categoria retornada pelo backend DEVE ser calculada apenas sobre imóveis ativos e disponíveis, sem expor dados de imóveis inativos.
|
||
|
||
### Key Entities
|
||
|
||
- **CatalogItem com Contagem**: Extensão das entidades de catálogo existentes (tipo de imóvel, cidade, bairro) com o campo `property_count` indicando quantos imóveis ativos estão associados.
|
||
- **SugestãoDeFiltro**: Resultado da busca cross-categoria, composto por `category` (grupo), `label` (texto exibido), `value` (identificador) e `filterKey` (chave do filtro a ser aplicado em `PropertyFilters`).
|
||
- **EstadoDeExpansãoDaSeção**: Mapeamento interno de `sectionKey → boolean` controlando quais seções do accordion estão abertas; inicializado a partir dos filtros ativos da URL.
|
||
|
||
---
|
||
|
||
## Success Criteria
|
||
|
||
### Measurable Outcomes
|
||
|
||
- **SC-001**: Usuários com intenção definida (sabem o bairro ou tipo que buscam) conseguem aplicar o filtro desejado em menos de 3 interações a partir do campo de busca do sidebar.
|
||
- **SC-002**: A seção de preço é a primeira coisa interativa que o usuário vê ao abrir o sidebar em 100% dos carregamentos sem filtros pré-ativos.
|
||
- **SC-003**: Em seções com mais de 5 itens, os 5 exibidos inicialmente cobrem pelo menos 70% do volume de imóveis disponíveis (validação pelo ordenamento por popularidade).
|
||
- **SC-004**: O tempo de resposta do campo de busca cross-categoria (desde o fim do debounce até a exibição das sugestões) é imperceptível para o usuário (abaixo de 50ms, pois é processamento local).
|
||
- **SC-005**: Nenhum filtro previamente funcional quebra após a implementação — todos os cenários de aceitação da spec `023-ux-melhorias-imoveis` continuam passando.
|
||
|
||
---
|
||
|
||
## Assumptions
|
||
|
||
- Os dados de catálogo (tipos, cidades, bairros, comodidades) já estão disponíveis via `catalog.ts` no momento em que o `FilterSidebar` renderiza; não há nova chamada de API necessária para a busca cross-categoria.
|
||
- A ordenação por popularidade requer uma única adição ao endpoint de catálogo existente (`property_count` por item), não um endpoint separado.
|
||
- O número de itens por seção raramente ultrapassa 20–30 em produção para este SaaS (portfólio de uma imobiliária), tornando o processamento de busca local viável sem paginação.
|
||
- O estado de expansão das seções não será persistido em `localStorage` nesta versão; a persistência pode ser adicionada em iteração futura se houver demanda.
|
||
- O badge "Popular" é puramente visual e não afeta a lógica de filtragem.
|
||
- A busca cross-categoria opera sobre dados do catálogo (tipos, cidades, bairros, comodidades) e não sobre dados de imóveis individuais (endereços, códigos) — a busca por endereço/código já está coberta pelo campo de busca global da feature `023`.
|
||
|
||
---
|
||
|
||
## Fora do Escopo
|
||
|
||
- Salvar buscas favoritas ou histórico de filtros por usuário.
|
||
- Busca cross-categoria que inclua imóveis individuais (por código, endereço ou título) — isso é responsabilidade do campo de busca global já especificado em `023-ux-melhorias-imoveis`.
|
||
- Ordenação de resultados de imóveis por popularidade de bairro (apenas os filtros do sidebar são ordenados por popularidade, não os cards de imóveis).
|
||
- Criação de um sistema completo de analytics de filtros — `property_count` é suficiente como proxy de popularidade nesta versão.
|
||
- Filtros de busca cross-categoria em tela mobile (sheet/modal) — este spec cobre apenas o sidebar em desktop; a experiência mobile é tratada em feature separada.
|
||
- Internacionalização dos labels de categoria ou dos badges.
|