3.5 KiB
3.5 KiB
Data Model — Melhorias UX/UI (023)
Nenhuma migration de banco necessária. Todos os campos utilizados já existem no modelo
Property.
Entidades Existentes (campos utilizados nesta feature)
Property (backend/app/models/property.py)
| Campo | Tipo SQLAlchemy | Tipo Python | Sprint | Utilização |
|---|---|---|---|---|
title |
VARCHAR(200) |
str |
1 | Busca textual q (ILIKE) |
address |
VARCHAR(300) |
str | None |
1 | Busca textual q (ILIKE) |
code |
VARCHAR(30) |
str | None |
1 | Busca textual q (ILIKE) |
neighborhood_id |
INTEGER FK → neighborhoods.id |
int | None |
1 | Join para busca q em Neighborhood.name |
price |
NUMERIC(12,2) |
Decimal |
2 | Ordenação price_asc / price_desc |
area_m2 |
INTEGER |
int |
2 | Ordenação area_desc |
created_at |
DATETIME |
datetime |
2/3 | Ordenação newest; badge "Novo" (frontend) |
is_featured |
BOOLEAN |
bool |
3 | Badge "Destaque" no card |
Neighborhood (backend/app/models/location.py)
| Campo | Tipo SQLAlchemy | Utilização |
|---|---|---|
id |
INTEGER PK |
Join com Property.neighborhood_id |
name |
VARCHAR |
Busca textual q (ILIKE) |
Tipos Frontend Adicionados (frontend/src/services/properties.ts)
PropertyFilters — campos novos
// Adição aos campos existentes:
q?: string // busca textual livre
sort?: SortOption
SortOption (novo tipo)
type SortOption =
| 'relevance' // default — equivale a created_at DESC no backend
| 'price_asc'
| 'price_desc'
| 'area_desc'
| 'newest'
ViewMode (novo tipo local — apenas frontend)
type ViewMode = 'list' | 'grid'
// Persiste em localStorage com key 'imoveis_view_mode'
Entidades de UI (apenas frontend — sem persistência no banco)
ActiveFilterChip
Tipo derivado calculado a partir de PropertyFilters + dados do catálogo:
interface ActiveFilterChip {
key: string // identificador único (ex: 'city_id', 'q', 'bedrooms_min')
label: string // texto exibido no chip (ex: 'São Paulo', 'Busca: "Jardins"')
onRemove: () => void // callback que remove este filtro específico
}
EmptyStateSuggestion
interface EmptyStateSuggestion {
label: string // ex: 'Remover filtro de bairro'
relaxedFilters: PropertyFilters
count: number // total de imóveis com o filtro relaxado
}
Validação de Entrada — Backend
O parâmetro q não é validado via Pydantic (é query param de GET, sem body).
Sanitização aplicada diretamente na rota:
q = args.get("q", "").strip()
# Comprimento máximo razoável (evitar payloads abusivos)
if len(q) > 200:
q = q[:200]
O parâmetro sort é validado via whitelist implícita no sort_map.get(sort, default).
Diagrama de Relacionamentos (campos relevantes)
Property
├── title (VARCHAR 200) ─── ILIKE com q
├── address (VARCHAR 300) ─── ILIKE com q
├── code (VARCHAR 30) ─── ILIKE com q
├── neighborhood_id (FK) ─┐
│ ├── JOIN → Neighborhood.name ─── ILIKE com q
├── price (NUMERIC 12,2) ─── ORDER BY price_asc/desc
├── area_m2 (INTEGER) ─── ORDER BY area_desc
├── created_at (DATETIME) ─── ORDER BY newest; badge "Novo" no frontend
└── is_featured (BOOLEAN) ─── badge "Destaque" no frontend