@burdenoff/microfe-housingvista 2026.930.1 → 2026.930.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"PropertiesPage.js","names":[],"sources":["../../../../src/housingvista/pages/properties/PropertiesPage.tsx"],"sourcesContent":["/**\n * HousingVista Properties Listing Page\n * Migrated from legacy housingvista-frontend properties.tsx\n * @module housingvista\n */\n\nimport { useState, useMemo, useEffect, type FormEvent } from 'react';\nimport { useNavigate, useSearchParams } from 'react-router';\nimport { useTr } from '../../../shared/hooks';\nimport { Plus, Grid, List, Map, Building, SlidersHorizontal, X, Sparkles } from 'lucide-react';\nimport { EmphasisPanel, Pagination } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, FilterBar, PropertyCard, EmptyState, StatCard, LoadingSkeleton } from '../../components';\nimport { PropertyMapView } from '../../components/maps';\nimport type { LeafletBoundsResult } from '../../components/maps';\nimport {\n useProperties,\n useShortlists,\n useSaveShortlist,\n useRemoveShortlist,\n useDashboardStats,\n useNaturalLanguageSearch,\n type NlSearchFilter,\n type PropertyFilterInput,\n type PropertyGeoBoundsInput,\n} from '../../hooks';\nimport { useServerPagination } from '../../hooks/useServerPagination';\nimport { useHousingVistaAnalytics, useTrackPageView } from '../../hooks/useHousingVistaAnalytics';\nimport { notify } from '../../lib/toast';\nimport { cn, formatNumber } from '../../utils/helpers';\nimport type { PropertyType, PropertyStatus, PropertyCategory, ReraVerificationStatus } from '../../types';\n\n/** Encode/decode the map's bounds filter to/from URL search params so a map\n * search is shareable and survives a refresh (BOFF-4617). */\nfunction boundsToSearchParam(bounds: PropertyGeoBoundsInput): string {\n return [bounds.neLat, bounds.neLng, bounds.swLat, bounds.swLng].map((n) => n.toFixed(5)).join(',');\n}\n\nfunction boundsFromSearchParam(raw: string | null): PropertyGeoBoundsInput | undefined {\n if (!raw) return undefined;\n const parts = raw.split(',').map(Number);\n if (parts.length !== 4 || parts.some((n) => Number.isNaN(n))) return undefined;\n const [neLat, neLng, swLat, swLng] = parts;\n return { neLat, neLng, swLat, swLng };\n}\n\nconst PAGE_SIZE = 9;\n\nexport const PropertiesPage = () => {\n const navigate = useNavigate();\n const [searchParams, setSearchParams] = useSearchParams();\n const tr = useTr();\n const { emit, emitResource, emitSearch } = useHousingVistaAnalytics();\n useTrackPageView('housingvista.property.viewed');\n\n const propertyTypes: { value: PropertyType | 'all'; label: string }[] = [\n { value: 'all', label: tr('housingvista.properties.filters.propertyTypes.all', 'All Types') },\n { value: 'residential', label: tr('housingvista.properties.filters.propertyTypes.residential', 'Residential') },\n { value: 'commercial', label: tr('housingvista.properties.filters.propertyTypes.commercial', 'Commercial') },\n { value: 'industrial', label: tr('housingvista.properties.filters.propertyTypes.industrial', 'Industrial') },\n { value: 'agricultural', label: tr('housingvista.properties.filters.propertyTypes.agricultural', 'Agricultural') },\n { value: 'mixed_use', label: tr('housingvista.properties.filters.propertyTypes.mixedUse', 'Mixed Use') },\n ];\n\n const propertyStatuses: { value: PropertyStatus | 'all'; label: string }[] = [\n { value: 'all', label: tr('housingvista.properties.filters.propertyStatuses.all', 'All Status') },\n { value: 'for_sale', label: tr('housingvista.properties.filters.propertyStatuses.forSale', 'For Sale') },\n { value: 'for_rent', label: tr('housingvista.properties.filters.propertyStatuses.forRent', 'For Rent') },\n { value: 'sold', label: tr('housingvista.properties.filters.propertyStatuses.sold', 'Sold') },\n { value: 'rented', label: tr('housingvista.properties.filters.propertyStatuses.rented', 'Rented') },\n {\n value: 'under_contract',\n label: tr('housingvista.properties.filters.propertyStatuses.underContract', 'Under Contract'),\n },\n { value: 'off_market', label: tr('housingvista.properties.filters.propertyStatuses.offMarket', 'Off Market') },\n ];\n\n const propertyCategories: { value: PropertyCategory | 'all'; label: string }[] = [\n { value: 'all', label: tr('housingvista.properties.filters.propertyCategories.all', 'All Categories') },\n { value: 'apartment', label: tr('housingvista.properties.filters.propertyCategories.apartment', 'Apartment') },\n { value: 'house', label: tr('housingvista.properties.filters.propertyCategories.house', 'House') },\n { value: 'villa', label: tr('housingvista.properties.filters.propertyCategories.villa', 'Villa') },\n { value: 'office', label: tr('housingvista.properties.filters.propertyCategories.office', 'Office') },\n { value: 'retail', label: tr('housingvista.properties.filters.propertyCategories.retail', 'Retail') },\n { value: 'warehouse', label: tr('housingvista.properties.filters.propertyCategories.warehouse', 'Warehouse') },\n { value: 'land', label: tr('housingvista.properties.filters.propertyCategories.land', 'Land') },\n { value: 'plot', label: tr('housingvista.properties.filters.propertyCategories.plot', 'Plot') },\n ];\n\n const priceRanges = [\n { value: 'all', label: tr('housingvista.properties.filters.priceRanges.all', 'Any Price'), min: 0, max: Infinity },\n {\n value: 'under_100k',\n label: tr('housingvista.properties.filters.priceRanges.under100k', 'Under $100K'),\n min: 0,\n max: 100000,\n },\n {\n value: '100k_500k',\n label: tr('housingvista.properties.filters.priceRanges.100kTo500k', '$100K - $500K'),\n min: 100000,\n max: 500000,\n },\n {\n value: '500k_1m',\n label: tr('housingvista.properties.filters.priceRanges.500kTo1m', '$500K - $1M'),\n min: 500000,\n max: 1000000,\n },\n {\n value: 'over_1m',\n label: tr('housingvista.properties.filters.priceRanges.over1m', 'Over $1M'),\n min: 1000000,\n max: Infinity,\n },\n ];\n\n const bedroomOptions = [\n { value: 'all', label: tr('housingvista.properties.filters.bedrooms.all', 'Any Bedrooms') },\n { value: '1', label: '1+' },\n { value: '2', label: '2+' },\n { value: '3', label: '3+' },\n { value: '4', label: '4+' },\n { value: '5', label: '5+' },\n ];\n\n const [searchQuery, setSearchQuery] = useState('');\n const [selectedType, setSelectedType] = useState<PropertyType | 'all'>('all');\n const [selectedStatus, setSelectedStatus] = useState<PropertyStatus | 'all'>('all');\n const [selectedCategory, setSelectedCategory] = useState<PropertyCategory | 'all'>('all');\n const [selectedPriceRange, setSelectedPriceRange] = useState('all');\n const [selectedBedrooms, setSelectedBedrooms] = useState('all');\n const initialView = searchParams.get('view');\n const [viewMode, setViewModeState] = useState<'grid' | 'list' | 'map'>(\n initialView === 'map' || initialView === 'list' ? initialView : 'grid'\n );\n // Map-mode bounds filter (BOFF-4617) — round-tripped through the URL (`bounds`\n // query param) so a \"Search this area\" map view is shareable/bookmarkable.\n const [mapBounds, setMapBounds] = useState<PropertyGeoBoundsInput | undefined>(() =>\n boundsFromSearchParam(searchParams.get('bounds'))\n );\n const [showFilters, setShowFilters] = useState(false);\n\n const setViewMode = (mode: 'grid' | 'list' | 'map') => {\n setViewModeState(mode);\n const next = new URLSearchParams(searchParams);\n if (mode === 'grid') {\n next.delete('view');\n } else {\n next.set('view', mode);\n }\n if (mode !== 'map') {\n next.delete('bounds');\n }\n setSearchParams(next, { replace: true });\n };\n\n const handleSearchThisArea = (bounds: LeafletBoundsResult) => {\n setMapBounds(bounds);\n const next = new URLSearchParams(searchParams);\n next.set('view', 'map');\n next.set('bounds', boundsToSearchParam(bounds));\n setSearchParams(next, { replace: true });\n };\n\n const reraOptions: { value: ReraVerificationStatus | 'all'; label: string }[] = [\n { value: 'all', label: tr('housingvista.properties.filters.rera.all', 'Any RERA status') },\n { value: 'verified', label: tr('housingvista.properties.filters.rera.verified', 'RERA Verified') },\n { value: 'pending', label: tr('housingvista.properties.filters.rera.pending', 'RERA Pending') },\n { value: 'not_applicable', label: tr('housingvista.properties.filters.rera.notApplicable', 'Not RERA-applicable') },\n { value: 'rejected', label: tr('housingvista.properties.filters.rera.rejected', 'RERA Rejected') },\n ];\n\n const [selectedRera, setSelectedRera] = useState<ReraVerificationStatus | 'all'>('all');\n\n const priceRange = priceRanges.find((r) => r.value === selectedPriceRange) ?? priceRanges[0];\n const minBedrooms = selectedBedrooms === 'all' ? 0 : Number(selectedBedrooms);\n\n // Debounce the search box so we don't refetch on every keystroke.\n const [debouncedSearch, setDebouncedSearch] = useState('');\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedSearch(searchQuery), 300);\n return () => clearTimeout(timer);\n }, [searchQuery]);\n\n // Analytics: emit search event when a non-empty debounced query is entered.\n useEffect(() => {\n if (debouncedSearch.trim()) {\n emitSearch('properties');\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [debouncedSearch]);\n\n const { page, pageSize, pagination, setPage, setPageSize } = useServerPagination(PAGE_SIZE);\n\n // Filters carried over from an AI search (\"Switch to manual filters\" /\n // removing a parsed chip). Held separately from the dropdowns because the\n // panel cannot express every parsed value — an exact price ceiling, a city,\n // or an amenity list has no control of its own. The dropdowns stay\n // authoritative: a key the user picked by hand always beats the same key\n // carried over from a sentence, so the two can never disagree.\n const [nlAdopted, setNlAdopted] = useState<PropertyFilterInput | null>(null);\n\n // Every filter is resolved server-side (BOFF-3933) — the subgraph's\n // HousingVistaPropertyFilterInput now models price/bedrooms/category/RERA\n // directly, so pagination totals are always accurate at scale.\n const serverFilter = useMemo<PropertyFilterInput | undefined>(() => {\n const filter: PropertyFilterInput = {};\n if (debouncedSearch.trim()) filter.search = debouncedSearch.trim();\n if (selectedStatus !== 'all') filter.status = selectedStatus;\n if (selectedType !== 'all') filter.type = selectedType;\n if (selectedCategory !== 'all') filter.category = selectedCategory;\n if (priceRange.min > 0) filter.priceMin = priceRange.min;\n if (Number.isFinite(priceRange.max)) filter.priceMax = priceRange.max;\n if (minBedrooms > 0) filter.bedroomsMin = minBedrooms;\n if (selectedRera !== 'all') filter.reraVerificationStatus = selectedRera;\n if (nlAdopted) {\n if (filter.status == null && nlAdopted.status != null) filter.status = nlAdopted.status;\n if (filter.type == null && nlAdopted.type != null) filter.type = nlAdopted.type;\n if (filter.category == null && nlAdopted.category != null) filter.category = nlAdopted.category;\n if (filter.bedroomsMin == null && nlAdopted.bedroomsMin != null) filter.bedroomsMin = nlAdopted.bedroomsMin;\n if (filter.reraVerificationStatus == null && nlAdopted.reraVerificationStatus != null) {\n filter.reraVerificationStatus = nlAdopted.reraVerificationStatus;\n }\n // Price is one control (a bucket), so it is one decision: an untouched\n // bucket yields to the parsed range, a chosen bucket replaces it whole.\n if (filter.priceMin == null && filter.priceMax == null) {\n if (nlAdopted.priceMin != null) filter.priceMin = nlAdopted.priceMin;\n if (nlAdopted.priceMax != null) filter.priceMax = nlAdopted.priceMax;\n }\n if (nlAdopted.city) filter.city = nlAdopted.city;\n if (nlAdopted.amenities?.length) filter.amenities = nlAdopted.amenities;\n }\n return Object.keys(filter).length > 0 ? filter : undefined;\n }, [\n debouncedSearch,\n selectedStatus,\n selectedType,\n selectedCategory,\n priceRange,\n minBedrooms,\n selectedRera,\n nlAdopted,\n ]);\n\n // Reset to first page whenever the server-side filter changes.\n useEffect(() => {\n setPage(1);\n }, [serverFilter, setPage]);\n\n const { data: properties, loading } = useProperties({ filter: serverFilter, pagination, skip: viewMode === 'map' });\n\n // Map mode (BOFF-4617): a separate, wider fetch bounded by the map's current\n // viewport instead of the grid/list's page-by-page pagination. Reapplying the\n // same non-geo filters (status/type/category/price/bedrooms/RERA) keeps the\n // map in sync with the filter panel.\n const mapFilter = useMemo<PropertyFilterInput | undefined>(() => {\n if (!mapBounds) return serverFilter;\n return { ...(serverFilter ?? {}), bounds: mapBounds };\n }, [serverFilter, mapBounds]);\n const { data: mapProperties, loading: mapLoading } = useProperties({\n filter: mapFilter,\n pagination: { limit: 200, offset: 0 },\n skip: viewMode !== 'map',\n });\n // Portfolio-wide totals for the stats zone — independent of the current page slice.\n const { data: dashboardStats } = useDashboardStats();\n const { data: shortlists } = useShortlists();\n const { saveShortlist } = useSaveShortlist();\n const { removeShortlist } = useRemoveShortlist();\n\n const shortlistedIds = useMemo(() => new Set(shortlists.map((s) => s.propertyId)), [shortlists]);\n\n const toggleShortlist = async (propertyId: string) => {\n try {\n if (shortlistedIds.has(propertyId)) {\n await removeShortlist(propertyId);\n notify.success('Removed from shortlist');\n } else {\n await saveShortlist(propertyId);\n emitResource('housingvista.shortlist.property_added', propertyId);\n notify.success('Saved to shortlist');\n }\n } catch (err) {\n notify.error('Could not update shortlist', (err as Error).message);\n }\n };\n\n // All filtering happens server-side now (see `serverFilter` above); the\n // page just renders the page slice the server already filtered.\n const filteredProperties = properties;\n\n // BOFF-4129 — natural-language search. Single-shot: one free-text query in,\n // one structured filter + real property list out (no multi-turn refinement\n // in this pass). Kept independent of the regular filter panel above — an\n // active NL search replaces the displayed list until cleared.\n const [nlQuery, setNlQuery] = useState('');\n const [nlActive, setNlActive] = useState(false);\n const {\n search: runNlSearch,\n loading: nlLoading,\n method: nlMethod,\n fallbackReason: nlFallbackReason,\n filter: nlFilter,\n properties: nlResults,\n } = useNaturalLanguageSearch();\n\n useEffect(() => {\n if (nlActive && nlMethod) {\n emit(nlMethod === 'ai' ? 'housingvista.nlsearch.ai_used' : 'housingvista.nlsearch.fallback_used');\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [nlMethod]);\n\n const handleNlSearchSubmit = (e: FormEvent) => {\n e.preventDefault();\n const trimmed = nlQuery.trim();\n if (!trimmed) return;\n setNlActive(true);\n emitSearch('properties.natural_language');\n void runNlSearch(trimmed);\n };\n\n const clearNlSearch = () => {\n setNlActive(false);\n setNlQuery('');\n setNlAdopted(null);\n };\n\n // ── Parsed-filter chips (BOFF-4129) ───────────────────────────────────────\n // Everything the search extracted, shown as one labelled chip per field so\n // the reader can see exactly what the sentence was turned into — and drop any\n // part of it. Both `HousingVistaNlSearchFilter` (what the AI/keyword parser\n // returned) and `HousingVistaPropertyFilterInput` (what is actually being\n // queried once those filters are adopted) fit this shape.\n type ChipKey =\n | 'city'\n | 'type'\n | 'status'\n | 'category'\n | 'priceMin'\n | 'priceMax'\n | 'bedroomsMin'\n | 'reraVerificationStatus'\n | `amenity:${string}`;\n\n interface ParsedChip {\n key: ChipKey;\n label: string;\n value: string;\n }\n\n interface ChipSource {\n city?: string | null;\n type?: PropertyType | null;\n status?: PropertyStatus | null;\n category?: string | null;\n priceMin?: number | null;\n priceMax?: number | null;\n bedroomsMin?: number | null;\n amenities?: string[] | null;\n reraVerificationStatus?: string | null;\n }\n\n const optionLabel = (options: { value: string; label: string }[], value: string): string =>\n options.find((option) => option.value === value)?.label ?? value.replace(/[-_]/g, ' ');\n\n const chipsFrom = (source: ChipSource | null | undefined): ParsedChip[] => {\n if (!source) return [];\n const chips: ParsedChip[] = [];\n if (source.city) {\n chips.push({ key: 'city', label: tr('housingvista.properties.chips.city', 'City'), value: source.city });\n }\n if (source.bedroomsMin != null) {\n chips.push({\n key: 'bedroomsMin',\n label: tr('housingvista.properties.chips.bedrooms', 'Bedrooms'),\n value: `${source.bedroomsMin}+`,\n });\n }\n if (source.priceMin != null) {\n chips.push({\n key: 'priceMin',\n label: tr('housingvista.properties.chips.minPrice', 'Min price'),\n value: formatNumber(source.priceMin),\n });\n }\n if (source.priceMax != null) {\n chips.push({\n key: 'priceMax',\n label: tr('housingvista.properties.chips.maxPrice', 'Max price'),\n value: formatNumber(source.priceMax),\n });\n }\n if (source.type) {\n chips.push({\n key: 'type',\n label: tr('housingvista.properties.chips.type', 'Type'),\n value: optionLabel(propertyTypes, source.type),\n });\n }\n if (source.category) {\n chips.push({\n key: 'category',\n label: tr('housingvista.properties.chips.category', 'Category'),\n value: optionLabel(propertyCategories, source.category),\n });\n }\n if (source.status) {\n chips.push({\n key: 'status',\n label: tr('housingvista.properties.chips.status', 'Status'),\n value: optionLabel(propertyStatuses, source.status),\n });\n }\n if (source.reraVerificationStatus) {\n chips.push({\n key: 'reraVerificationStatus',\n label: tr('housingvista.properties.chips.rera', 'RERA'),\n value: optionLabel(reraOptions, source.reraVerificationStatus),\n });\n }\n for (const amenity of source.amenities ?? []) {\n chips.push({\n key: `amenity:${amenity}`,\n label: tr('housingvista.properties.chips.amenity', 'Amenity'),\n value: amenity,\n });\n }\n return chips;\n };\n\n /**\n * Hand the parsed filter to the page's own property query (the same one the\n * dropdown panel drives), optionally dropping one chip on the way. This is\n * what \"Switch to manual filters\" and a chip's × both do — the result list is\n * then a real `housingvistaProperties` query, not the single-shot AI result.\n */\n const adoptParsedFilters = (parsed: NlSearchFilter, omit?: ChipKey) => {\n const next: PropertyFilterInput = {};\n if (parsed.city && omit !== 'city') next.city = parsed.city;\n if (parsed.type && omit !== 'type') next.type = parsed.type;\n if (parsed.status && omit !== 'status') next.status = parsed.status;\n if (parsed.category && omit !== 'category') next.category = parsed.category;\n if (parsed.priceMin != null && omit !== 'priceMin') next.priceMin = parsed.priceMin;\n if (parsed.priceMax != null && omit !== 'priceMax') next.priceMax = parsed.priceMax;\n if (parsed.bedroomsMin != null && omit !== 'bedroomsMin') next.bedroomsMin = parsed.bedroomsMin;\n if (omit !== 'reraVerificationStatus') {\n // The parser types this as a plain string; only adopt a value the page's\n // own RERA control recognises, so we never send the subgraph an enum it\n // does not have.\n const rera = reraOptions.find((option) => option.value === parsed.reraVerificationStatus);\n if (rera && rera.value !== 'all') next.reraVerificationStatus = rera.value;\n }\n const amenities = (parsed.amenities ?? []).filter((amenity) => omit !== `amenity:${amenity}`);\n if (amenities.length > 0) next.amenities = amenities;\n\n setNlAdopted(Object.keys(next).length > 0 ? next : null);\n setNlActive(false);\n setShowFilters(true);\n };\n\n /** Drop one chip wherever it currently lives — a dropdown, or the adopted set. */\n const removeChip = (key: ChipKey) => {\n if (nlActive && nlFilter) {\n adoptParsedFilters(nlFilter, key);\n return;\n }\n switch (key) {\n case 'type':\n setSelectedType('all');\n break;\n case 'status':\n setSelectedStatus('all');\n break;\n case 'category':\n setSelectedCategory('all');\n break;\n case 'bedroomsMin':\n setSelectedBedrooms('all');\n break;\n case 'reraVerificationStatus':\n setSelectedRera('all');\n break;\n case 'priceMin':\n case 'priceMax':\n // Price is a single bucket control, so clearing either end clears the\n // bucket; the remaining adopted end (if any) is stripped below.\n setSelectedPriceRange('all');\n break;\n default:\n break;\n }\n setNlAdopted((prev) => {\n if (!prev) return prev;\n const next = { ...prev };\n if (\n key === 'city' ||\n key === 'type' ||\n key === 'status' ||\n key === 'category' ||\n key === 'priceMin' ||\n key === 'priceMax' ||\n key === 'bedroomsMin' ||\n key === 'reraVerificationStatus'\n ) {\n delete next[key];\n } else {\n const amenity = key.slice('amenity:'.length);\n const remaining = (next.amenities ?? []).filter((value) => value !== amenity);\n if (remaining.length > 0) next.amenities = remaining;\n else delete next.amenities;\n }\n return Object.keys(next).length > 0 ? next : null;\n });\n };\n\n /** Plain-English restatement of the filter actually in force. */\n const describeChips = (chips: ParsedChip[]): string =>\n chips.map((chip) => `${chip.label.toLowerCase()} ${chip.value}`).join(', ');\n\n const nlChips = nlActive ? chipsFrom(nlFilter) : chipsFrom(serverFilter);\n // While a search is in flight `nlFilter` still holds the PREVIOUS result, so\n // hold the chips back rather than showing filters that are not in force.\n const showChipRow = ((nlActive && !nlLoading) || nlAdopted !== null) && nlChips.length > 0;\n\n const displayProperties = nlActive ? nlResults : filteredProperties;\n const displayLoading = nlActive ? nlLoading : loading;\n\n const hasNextPage = properties.length >= pageSize;\n\n const hasActiveFilters =\n searchQuery ||\n selectedType !== 'all' ||\n selectedStatus !== 'all' ||\n selectedCategory !== 'all' ||\n selectedPriceRange !== 'all' ||\n selectedBedrooms !== 'all' ||\n selectedRera !== 'all' ||\n nlAdopted !== null;\n\n const clearFilters = () => {\n setSearchQuery('');\n setSelectedType('all');\n setSelectedStatus('all');\n setSelectedCategory('all');\n setSelectedPriceRange('all');\n setSelectedBedrooms('all');\n setSelectedRera('all');\n setNlAdopted(null);\n };\n\n return (\n <PageLayout\n title={tr('housingvista.properties.title', 'Properties')}\n description={tr(\n 'housingvista.properties.description',\n 'Discover your perfect property from our comprehensive listings'\n )}\n purpose={tr(\n 'housingvista.properties.purpose',\n 'Browse, search, and filter every property in your portfolio or on the market. Use the filters to narrow by type, status, price, and bedrooms, then open any listing to see full details, schedule viewings, or start a transaction.'\n )}\n actions={\n <button\n onClick={() => {\n emit('housingvista.property.create_clicked');\n navigate('new');\n }}\n className=\"inline-flex items-center gap-2 rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors hover:bg-foreground/90\"\n >\n <Plus className=\"h-4 w-4\" />\n {tr('housingvista.properties.actions.addProperty', 'Add Property')}\n </button>\n }\n >\n {/* Stats — primary insight zone (one EmphasisPanel per page) */}\n <EmphasisPanel className=\"p-4\">\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <StatCard\n label={tr('housingvista.properties.stats.totalProperties', 'Total Properties')}\n value={dashboardStats.totalProperties}\n icon={<Building className=\"h-5 w-5\" />}\n />\n <StatCard\n label={tr('housingvista.properties.stats.portfolioValue', 'Portfolio Value')}\n value={dashboardStats.totalValue}\n prefix=\"$\"\n />\n <StatCard\n label={tr('housingvista.properties.stats.occupancyRate', 'Occupancy Rate')}\n value={dashboardStats.occupancyRate}\n unit=\"%\"\n />\n <StatCard\n label={tr('housingvista.properties.stats.monthlyRevenue', 'Monthly Revenue')}\n value={dashboardStats.monthlyRevenue}\n prefix=\"$\"\n />\n </div>\n </EmphasisPanel>\n\n {/* BOFF-4129 — Natural-language search */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <form onSubmit={handleNlSearchSubmit} className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n <div className=\"flex flex-1 items-center gap-2\">\n <Sparkles className=\"h-4 w-4 shrink-0 text-text-secondary\" />\n <input\n type=\"text\"\n value={nlQuery}\n onChange={(e) => setNlQuery(e.target.value)}\n placeholder={tr(\n 'housingvista.properties.nlSearch.placeholder',\n 'Try \"2 bedroom apartment near downtown under $2000 with parking\"'\n )}\n className=\"h-9 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n <div className=\"flex items-center gap-3\">\n <button\n type=\"submit\"\n disabled={!nlQuery.trim() || nlLoading}\n className=\"inline-flex items-center gap-2 whitespace-nowrap rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors hover:bg-foreground/90 disabled:opacity-50\"\n >\n {nlLoading\n ? tr('housingvista.properties.nlSearch.searching', 'Searching…')\n : tr('housingvista.properties.nlSearch.submit', 'AI Search')}\n </button>\n {(nlActive || nlAdopted !== null) && (\n <button\n type=\"button\"\n onClick={clearNlSearch}\n className=\"whitespace-nowrap text-sm text-text-secondary hover:text-text-primary\"\n >\n {tr('housingvista.properties.nlSearch.clear', 'Clear AI search')}\n </button>\n )}\n </div>\n </form>\n\n {nlActive && !nlLoading && (\n <div className=\"mt-3 flex flex-wrap items-center gap-2 text-xs\">\n <span\n className={cn(\n 'rounded-full px-2 py-0.5 font-medium',\n nlMethod === 'ai' ? 'bg-action-primary-bg text-action-primary-text' : 'bg-bg-sunken text-text-secondary'\n )}\n >\n {nlMethod === 'ai'\n ? tr('housingvista.properties.nlSearch.methodAi', 'AI-parsed')\n : tr('housingvista.properties.nlSearch.methodKeyword', 'Keyword baseline (no AI configured)')}\n </span>\n {/* The subgraph says WHY it fell back to the keyword baseline —\n show it instead of leaving the downgrade unexplained. */}\n {nlFallbackReason && (\n <span className=\"rounded-full bg-status-warning-bg-subtle px-2 py-0.5 text-status-warning-text\">\n {nlFallbackReason}\n </span>\n )}\n </div>\n )}\n\n {showChipRow && (\n <div className=\"mt-3 space-y-2\">\n <p className=\"text-xs font-medium text-text-secondary\">\n {nlActive\n ? tr('housingvista.properties.nlSearch.chipsHeading', 'Filters we read from your sentence')\n : tr('housingvista.properties.nlSearch.chipsHeadingApplied', 'Filters applied from your sentence')}\n </p>\n <div className=\"flex flex-wrap items-center gap-2\">\n {nlChips.map((chip) => (\n <span\n key={chip.key}\n className=\"inline-flex items-center gap-1.5 rounded-full bg-status-info-bg-subtle px-2.5 py-1 text-xs text-status-info-text\"\n >\n <span className=\"font-medium\">{chip.label}:</span>\n <span>{chip.value}</span>\n <button\n type=\"button\"\n onClick={() => removeChip(chip.key)}\n aria-label={\n tr('housingvista.properties.nlSearch.removeChip', 'Remove filter') +\n `: ${chip.label} ${chip.value}`\n }\n className=\"rounded-full transition-opacity hover:opacity-70\"\n >\n <X className=\"h-3 w-3\" />\n </button>\n </span>\n ))}\n </div>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <p className=\"text-xs text-text-secondary\">\n {tr('housingvista.properties.nlSearch.readAs', 'How we read it')}: {describeChips(nlChips)}\n </p>\n {nlActive && nlFilter && (\n <button\n type=\"button\"\n onClick={() => adoptParsedFilters(nlFilter)}\n className=\"whitespace-nowrap text-xs font-medium text-primary hover:underline\"\n >\n {tr('housingvista.properties.nlSearch.switchToManual', 'Switch to manual filters')}\n </button>\n )}\n </div>\n </div>\n )}\n\n {nlActive && !nlLoading && nlChips.length === 0 && (\n <p className=\"mt-3 text-xs text-text-secondary\">\n {tr(\n 'housingvista.properties.nlSearch.noFiltersRead',\n 'No filters could be read from that sentence — these are unfiltered listings.'\n )}\n </p>\n )}\n </div>\n\n {/* Search + View Toggle */}\n <FilterBar\n searchValue={searchQuery}\n onSearchChange={setSearchQuery}\n searchPlaceholder={tr(\n 'housingvista.properties.search.placeholder',\n 'Search properties by name, city, or neighborhood...'\n )}\n filters={\n <button\n onClick={() => setShowFilters((prev) => !prev)}\n className={cn(\n 'inline-flex items-center gap-2 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium transition-colors',\n showFilters ? 'bg-foreground text-background' : 'text-text-primary hover:bg-bg-sunken'\n )}\n >\n <SlidersHorizontal className=\"h-4 w-4\" />\n {tr('housingvista.properties.filters.toggle', 'Filters')}\n {hasActiveFilters && (\n <span className=\"flex h-5 w-5 items-center justify-center rounded-full bg-action-primary-bg text-[10px] font-bold text-action-primary-text\">\n !\n </span>\n )}\n </button>\n }\n actions={\n <div className=\"flex items-center gap-1\">\n <button\n onClick={() => setViewMode('grid')}\n className={cn(\n 'rounded-md p-2 transition-colors',\n viewMode === 'grid'\n ? 'bg-foreground text-background'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-sunken'\n )}\n >\n <Grid className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setViewMode('list')}\n className={cn(\n 'rounded-md p-2 transition-colors',\n viewMode === 'list'\n ? 'bg-foreground text-background'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-sunken'\n )}\n >\n <List className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setViewMode('map')}\n aria-label={tr('housingvista.properties.viewMode.map', 'Map view')}\n className={cn(\n 'rounded-md p-2 transition-colors',\n viewMode === 'map'\n ? 'bg-foreground text-background'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-sunken'\n )}\n >\n <Map className=\"h-4 w-4\" />\n </button>\n </div>\n }\n />\n\n {/* Filter Dropdowns */}\n {showFilters && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <div className=\"flex items-center justify-between mb-4\">\n <h3 className=\"text-sm font-medium text-text-primary\">\n {tr('housingvista.properties.filters.title', 'Filters')}\n </h3>\n {hasActiveFilters && (\n <button\n onClick={clearFilters}\n className=\"inline-flex items-center gap-1 text-xs text-text-secondary hover:text-text-primary\"\n >\n <X className=\"h-3 w-3\" />\n {tr('housingvista.properties.filters.clearAll', 'Clear All')}\n </button>\n )}\n </div>\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-6\">\n {/* Type */}\n <div>\n <label className=\"mb-1.5 block text-xs font-medium text-text-secondary\">\n {tr('housingvista.properties.filters.labels.propertyType', 'Property Type')}\n </label>\n <select\n value={selectedType}\n onChange={(e) => setSelectedType(e.target.value as PropertyType | 'all')}\n className=\"h-9 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {propertyTypes.map((t) => (\n <option key={t.value} value={t.value}>\n {t.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* Status */}\n <div>\n <label className=\"mb-1.5 block text-xs font-medium text-text-secondary\">\n {tr('housingvista.properties.filters.labels.status', 'Status')}\n </label>\n <select\n value={selectedStatus}\n onChange={(e) => setSelectedStatus(e.target.value as PropertyStatus | 'all')}\n className=\"h-9 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {propertyStatuses.map((s) => (\n <option key={s.value} value={s.value}>\n {s.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* Category */}\n <div>\n <label className=\"mb-1.5 block text-xs font-medium text-text-secondary\">\n {tr('housingvista.properties.filters.labels.category', 'Category')}\n </label>\n <select\n value={selectedCategory}\n onChange={(e) => setSelectedCategory(e.target.value as PropertyCategory | 'all')}\n className=\"h-9 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {propertyCategories.map((c) => (\n <option key={c.value} value={c.value}>\n {c.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* Price Range */}\n <div>\n <label className=\"mb-1.5 block text-xs font-medium text-text-secondary\">\n {tr('housingvista.properties.filters.labels.priceRange', 'Price Range')}\n </label>\n <select\n value={selectedPriceRange}\n onChange={(e) => setSelectedPriceRange(e.target.value)}\n className=\"h-9 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {priceRanges.map((r) => (\n <option key={r.value} value={r.value}>\n {r.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* Bedrooms */}\n <div>\n <label className=\"mb-1.5 block text-xs font-medium text-text-secondary\">\n {tr('housingvista.properties.filters.labels.bedrooms', 'Bedrooms')}\n </label>\n <select\n value={selectedBedrooms}\n onChange={(e) => setSelectedBedrooms(e.target.value)}\n className=\"h-9 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {bedroomOptions.map((b) => (\n <option key={b.value} value={b.value}>\n {b.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* RERA verification (BOFF-4132) */}\n <div>\n <label className=\"mb-1.5 block text-xs font-medium text-text-secondary\">\n {tr('housingvista.properties.filters.labels.rera', 'RERA')}\n </label>\n <select\n value={selectedRera}\n onChange={(e) => setSelectedRera(e.target.value as ReraVerificationStatus | 'all')}\n className=\"h-9 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {reraOptions.map((r) => (\n <option key={r.value} value={r.value}>\n {r.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n </div>\n )}\n\n {/* Results Count */}\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-text-secondary\">\n {nlActive ? (\n <>\n {tr('housingvista.properties.results.showing', 'Showing')} {displayProperties.length}{' '}\n {tr('housingvista.properties.results.properties', 'properties')} ·{' '}\n {tr('housingvista.properties.nlSearch.resultsLabel', 'AI search results')}\n </>\n ) : viewMode === 'map' ? (\n <>\n {tr('housingvista.properties.results.showingOnMap', 'Showing')} {mapProperties.length}{' '}\n {tr('housingvista.properties.results.properties', 'properties')}\n {mapBounds ? ` · ${tr('housingvista.properties.results.inThisArea', 'in this area')}` : ''}\n </>\n ) : (\n <>\n {tr('housingvista.properties.results.showing', 'Showing')} {filteredProperties.length}{' '}\n {tr('housingvista.properties.results.properties', 'properties')}\n {page > 1 ? ` · page ${page}` : ''}\n </>\n )}\n </p>\n {!nlActive && hasActiveFilters && (\n <button onClick={clearFilters} className=\"text-sm text-text-secondary hover:text-text-primary\">\n {tr('housingvista.properties.results.clearFilters', 'Clear Filters')}\n </button>\n )}\n </div>\n\n {/* Property Map (BOFF-4617) */}\n {!nlActive && viewMode === 'map' && (\n <PropertyMapView\n properties={mapProperties}\n loading={mapLoading}\n selectedIds={shortlistedIds}\n onSelectProperty={(id) => navigate(id)}\n onSearchThisArea={handleSearchThisArea}\n />\n )}\n\n {/* Property Grid / List */}\n {!nlActive && viewMode === 'map' ? null : displayLoading ? (\n <LoadingSkeleton variant=\"card\" count={6} />\n ) : displayProperties.length > 0 ? (\n <>\n <div\n className={cn(\n nlActive || viewMode === 'grid' ? 'grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3' : 'space-y-4'\n )}\n >\n {displayProperties.map((property) => (\n <PropertyCard\n key={property.id}\n id={property.id}\n title={property.title}\n price={property.price.amount}\n priceType={property.price.type === 'rent' ? 'rent' : 'sale'}\n pricePeriod={property.price.period}\n currency={property.price.currency}\n location={[property.location.neighborhood, property.location.city].filter(Boolean).join(', ')}\n bedrooms={property.specifications.bedrooms}\n bathrooms={property.specifications.bathrooms}\n area={property.specifications.totalArea}\n imageUrl={property.images[0]?.url}\n status={property.status.replace('_', ' ')}\n type={property.category}\n onClick={() => navigate(property.id)}\n isShortlisted={shortlistedIds.has(property.id)}\n onToggleShortlist={() => toggleShortlist(property.id)}\n reraStatus={property.reraVerificationStatus}\n />\n ))}\n </div>\n\n {/* Pagination (server-side): working prev/next + page-size, no silent truncation.\n Not shown for AI search results (BOFF-4129) — a single-shot result set. */}\n {!nlActive && (\n <Pagination\n page={page}\n pageSize={pageSize}\n hasNextPage={hasNextPage}\n hasPreviousPage={page > 1}\n loading={loading}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n )}\n </>\n ) : (\n <EmptyState\n illustration=\"empty-search\"\n icon={<Building className=\"h-12 w-12\" />}\n title={tr('housingvista.properties.empty.title', 'No properties found')}\n description={\n nlActive\n ? tr(\n 'housingvista.properties.nlSearch.emptyDescription',\n 'No properties matched that search. Try rephrasing your query.'\n )\n : tr(\n 'housingvista.properties.empty.description',\n 'Try adjusting your search criteria or filters to find more properties.'\n )\n }\n action={\n <button\n onClick={nlActive ? clearNlSearch : clearFilters}\n className=\"rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors hover:bg-foreground/90\"\n >\n {nlActive\n ? tr('housingvista.properties.nlSearch.clear', 'Clear AI search')\n : tr('housingvista.properties.empty.clearAllFilters', 'Clear All Filters')}\n </button>\n }\n />\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"PropertiesPage.js","names":[],"sources":["../../../../src/housingvista/pages/properties/PropertiesPage.tsx"],"sourcesContent":["/**\n * HousingVista Properties Listing Page\n * Migrated from legacy housingvista-frontend properties.tsx\n * @module housingvista\n */\n\nimport { useState, useMemo, useEffect, type FormEvent } from 'react';\nimport { useNavigate, useSearchParams } from 'react-router';\nimport { useTr } from '../../../shared/hooks';\nimport { Plus, Grid, List, Map, Building, SlidersHorizontal, X, Sparkles } from 'lucide-react';\nimport { EmphasisPanel, Pagination } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, FilterBar, PropertyCard, EmptyState, StatCard, LoadingSkeleton } from '../../components';\nimport { PropertyMapView } from '../../components/maps';\nimport type { LeafletBoundsResult } from '../../components/maps';\nimport {\n useProperties,\n useShortlists,\n useSaveShortlist,\n useRemoveShortlist,\n useDashboardStats,\n useNaturalLanguageSearch,\n type NlSearchFilter,\n type PropertyFilterInput,\n type PropertyGeoBoundsInput,\n} from '../../hooks';\nimport { useServerPagination } from '../../hooks/useServerPagination';\nimport { useHousingVistaAnalytics, useTrackPageView } from '../../hooks/useHousingVistaAnalytics';\nimport { notify } from '../../lib/toast';\nimport { cn, formatNumber } from '../../utils/helpers';\nimport { amenityOptions } from '../../utils/amenities';\nimport type { PropertyType, PropertyStatus, PropertyCategory, ReraVerificationStatus } from '../../types';\n\n/** Encode/decode the map's bounds filter to/from URL search params so a map\n * search is shareable and survives a refresh (BOFF-4617). */\nfunction boundsToSearchParam(bounds: PropertyGeoBoundsInput): string {\n return [bounds.neLat, bounds.neLng, bounds.swLat, bounds.swLng].map((n) => n.toFixed(5)).join(',');\n}\n\nfunction boundsFromSearchParam(raw: string | null): PropertyGeoBoundsInput | undefined {\n if (!raw) return undefined;\n const parts = raw.split(',').map(Number);\n if (parts.length !== 4 || parts.some((n) => Number.isNaN(n))) return undefined;\n const [neLat, neLng, swLat, swLng] = parts;\n return { neLat, neLng, swLat, swLng };\n}\n\nconst PAGE_SIZE = 9;\n\nexport const PropertiesPage = () => {\n const navigate = useNavigate();\n const [searchParams, setSearchParams] = useSearchParams();\n const tr = useTr();\n const { emit, emitResource, emitSearch } = useHousingVistaAnalytics();\n useTrackPageView('housingvista.property.viewed');\n\n const propertyTypes: { value: PropertyType | 'all'; label: string }[] = [\n { value: 'all', label: tr('housingvista.properties.filters.propertyTypes.all', 'All Types') },\n { value: 'residential', label: tr('housingvista.properties.filters.propertyTypes.residential', 'Residential') },\n { value: 'commercial', label: tr('housingvista.properties.filters.propertyTypes.commercial', 'Commercial') },\n { value: 'industrial', label: tr('housingvista.properties.filters.propertyTypes.industrial', 'Industrial') },\n { value: 'agricultural', label: tr('housingvista.properties.filters.propertyTypes.agricultural', 'Agricultural') },\n { value: 'mixed_use', label: tr('housingvista.properties.filters.propertyTypes.mixedUse', 'Mixed Use') },\n ];\n\n const propertyStatuses: { value: PropertyStatus | 'all'; label: string }[] = [\n { value: 'all', label: tr('housingvista.properties.filters.propertyStatuses.all', 'All Status') },\n { value: 'for_sale', label: tr('housingvista.properties.filters.propertyStatuses.forSale', 'For Sale') },\n { value: 'for_rent', label: tr('housingvista.properties.filters.propertyStatuses.forRent', 'For Rent') },\n { value: 'sold', label: tr('housingvista.properties.filters.propertyStatuses.sold', 'Sold') },\n { value: 'rented', label: tr('housingvista.properties.filters.propertyStatuses.rented', 'Rented') },\n {\n value: 'under_contract',\n label: tr('housingvista.properties.filters.propertyStatuses.underContract', 'Under Contract'),\n },\n { value: 'off_market', label: tr('housingvista.properties.filters.propertyStatuses.offMarket', 'Off Market') },\n ];\n\n const propertyCategories: { value: PropertyCategory | 'all'; label: string }[] = [\n { value: 'all', label: tr('housingvista.properties.filters.propertyCategories.all', 'All Categories') },\n { value: 'apartment', label: tr('housingvista.properties.filters.propertyCategories.apartment', 'Apartment') },\n { value: 'house', label: tr('housingvista.properties.filters.propertyCategories.house', 'House') },\n { value: 'villa', label: tr('housingvista.properties.filters.propertyCategories.villa', 'Villa') },\n { value: 'office', label: tr('housingvista.properties.filters.propertyCategories.office', 'Office') },\n { value: 'retail', label: tr('housingvista.properties.filters.propertyCategories.retail', 'Retail') },\n { value: 'warehouse', label: tr('housingvista.properties.filters.propertyCategories.warehouse', 'Warehouse') },\n { value: 'land', label: tr('housingvista.properties.filters.propertyCategories.land', 'Land') },\n { value: 'plot', label: tr('housingvista.properties.filters.propertyCategories.plot', 'Plot') },\n ];\n\n const priceRanges = [\n { value: 'all', label: tr('housingvista.properties.filters.priceRanges.all', 'Any Price'), min: 0, max: Infinity },\n {\n value: 'under_100k',\n label: tr('housingvista.properties.filters.priceRanges.under100k', 'Under $100K'),\n min: 0,\n max: 100000,\n },\n {\n value: '100k_500k',\n label: tr('housingvista.properties.filters.priceRanges.100kTo500k', '$100K - $500K'),\n min: 100000,\n max: 500000,\n },\n {\n value: '500k_1m',\n label: tr('housingvista.properties.filters.priceRanges.500kTo1m', '$500K - $1M'),\n min: 500000,\n max: 1000000,\n },\n {\n value: 'over_1m',\n label: tr('housingvista.properties.filters.priceRanges.over1m', 'Over $1M'),\n min: 1000000,\n max: Infinity,\n },\n ];\n\n const bedroomOptions = [\n { value: 'all', label: tr('housingvista.properties.filters.bedrooms.all', 'Any Bedrooms') },\n { value: '1', label: '1+' },\n { value: '2', label: '2+' },\n { value: '3', label: '3+' },\n { value: '4', label: '4+' },\n { value: '5', label: '5+' },\n ];\n\n const [searchQuery, setSearchQuery] = useState('');\n const [selectedType, setSelectedType] = useState<PropertyType | 'all'>('all');\n const [selectedStatus, setSelectedStatus] = useState<PropertyStatus | 'all'>('all');\n const [selectedCategory, setSelectedCategory] = useState<PropertyCategory | 'all'>('all');\n const [selectedPriceRange, setSelectedPriceRange] = useState('all');\n const [selectedBedrooms, setSelectedBedrooms] = useState('all');\n const initialView = searchParams.get('view');\n const [viewMode, setViewModeState] = useState<'grid' | 'list' | 'map'>(\n initialView === 'map' || initialView === 'list' ? initialView : 'grid'\n );\n // Map-mode bounds filter (BOFF-4617) — round-tripped through the URL (`bounds`\n // query param) so a \"Search this area\" map view is shareable/bookmarkable.\n const [mapBounds, setMapBounds] = useState<PropertyGeoBoundsInput | undefined>(() =>\n boundsFromSearchParam(searchParams.get('bounds'))\n );\n const [showFilters, setShowFilters] = useState(false);\n\n const setViewMode = (mode: 'grid' | 'list' | 'map') => {\n setViewModeState(mode);\n const next = new URLSearchParams(searchParams);\n if (mode === 'grid') {\n next.delete('view');\n } else {\n next.set('view', mode);\n }\n if (mode !== 'map') {\n next.delete('bounds');\n }\n setSearchParams(next, { replace: true });\n };\n\n const handleSearchThisArea = (bounds: LeafletBoundsResult) => {\n setMapBounds(bounds);\n const next = new URLSearchParams(searchParams);\n next.set('view', 'map');\n next.set('bounds', boundsToSearchParam(bounds));\n setSearchParams(next, { replace: true });\n };\n\n const reraOptions: { value: ReraVerificationStatus | 'all'; label: string }[] = [\n { value: 'all', label: tr('housingvista.properties.filters.rera.all', 'Any RERA status') },\n { value: 'verified', label: tr('housingvista.properties.filters.rera.verified', 'RERA Verified') },\n { value: 'pending', label: tr('housingvista.properties.filters.rera.pending', 'RERA Pending') },\n { value: 'not_applicable', label: tr('housingvista.properties.filters.rera.notApplicable', 'Not RERA-applicable') },\n { value: 'rejected', label: tr('housingvista.properties.filters.rera.rejected', 'RERA Rejected') },\n ];\n\n const [selectedRera, setSelectedRera] = useState<ReraVerificationStatus | 'all'>('all');\n\n // Amenities multi-select (BOFF-8090). `selectedAmenities` holds catalog ids;\n // the query filter sends the catalog labels (backend matches amenity names).\n const [selectedAmenities, setSelectedAmenities] = useState<string[]>([]);\n const toggleFilterAmenity = (id: string) => {\n setSelectedAmenities((prev) => (prev.includes(id) ? prev.filter((a) => a !== id) : [...prev, id]));\n };\n\n const priceRange = priceRanges.find((r) => r.value === selectedPriceRange) ?? priceRanges[0];\n const minBedrooms = selectedBedrooms === 'all' ? 0 : Number(selectedBedrooms);\n\n // Debounce the search box so we don't refetch on every keystroke.\n const [debouncedSearch, setDebouncedSearch] = useState('');\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedSearch(searchQuery), 300);\n return () => clearTimeout(timer);\n }, [searchQuery]);\n\n // Analytics: emit search event when a non-empty debounced query is entered.\n useEffect(() => {\n if (debouncedSearch.trim()) {\n emitSearch('properties');\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [debouncedSearch]);\n\n const { page, pageSize, pagination, setPage, setPageSize } = useServerPagination(PAGE_SIZE);\n\n // Filters carried over from an AI search (\"Switch to manual filters\" /\n // removing a parsed chip). Held separately from the dropdowns because the\n // panel cannot express every parsed value — an exact price ceiling, a city,\n // or an amenity list has no control of its own. The dropdowns stay\n // authoritative: a key the user picked by hand always beats the same key\n // carried over from a sentence, so the two can never disagree.\n const [nlAdopted, setNlAdopted] = useState<PropertyFilterInput | null>(null);\n\n // Every filter is resolved server-side (BOFF-3933) — the subgraph's\n // HousingVistaPropertyFilterInput now models price/bedrooms/category/RERA\n // directly, so pagination totals are always accurate at scale.\n const serverFilter = useMemo<PropertyFilterInput | undefined>(() => {\n const filter: PropertyFilterInput = {};\n if (debouncedSearch.trim()) filter.search = debouncedSearch.trim();\n if (selectedStatus !== 'all') filter.status = selectedStatus;\n if (selectedType !== 'all') filter.type = selectedType;\n if (selectedCategory !== 'all') filter.category = selectedCategory;\n if (priceRange.min > 0) filter.priceMin = priceRange.min;\n if (Number.isFinite(priceRange.max)) filter.priceMax = priceRange.max;\n if (minBedrooms > 0) filter.bedroomsMin = minBedrooms;\n if (selectedRera !== 'all') filter.reraVerificationStatus = selectedRera;\n // Amenities: a manual selection always beats AI-carried ones (same rule as\n // the other dropdowns). Sent as catalog labels — the backend matches\n // against stored amenity names.\n if (selectedAmenities.length > 0) {\n filter.amenities = selectedAmenities.map((id) => amenityOptions.find((o) => o.id === id)?.label ?? id);\n }\n if (nlAdopted) {\n if (filter.status == null && nlAdopted.status != null) filter.status = nlAdopted.status;\n if (filter.type == null && nlAdopted.type != null) filter.type = nlAdopted.type;\n if (filter.category == null && nlAdopted.category != null) filter.category = nlAdopted.category;\n if (filter.bedroomsMin == null && nlAdopted.bedroomsMin != null) filter.bedroomsMin = nlAdopted.bedroomsMin;\n if (filter.reraVerificationStatus == null && nlAdopted.reraVerificationStatus != null) {\n filter.reraVerificationStatus = nlAdopted.reraVerificationStatus;\n }\n // Price is one control (a bucket), so it is one decision: an untouched\n // bucket yields to the parsed range, a chosen bucket replaces it whole.\n if (filter.priceMin == null && filter.priceMax == null) {\n if (nlAdopted.priceMin != null) filter.priceMin = nlAdopted.priceMin;\n if (nlAdopted.priceMax != null) filter.priceMax = nlAdopted.priceMax;\n }\n if (nlAdopted.city) filter.city = nlAdopted.city;\n // Manual selection beats AI-carried amenities (Codex P2): the adopted\n // list only fills the filter when the manual control selected nothing.\n if (filter.amenities == null && nlAdopted.amenities?.length) {\n filter.amenities = nlAdopted.amenities;\n }\n }\n return Object.keys(filter).length > 0 ? filter : undefined;\n }, [\n debouncedSearch,\n selectedStatus,\n selectedType,\n selectedCategory,\n priceRange,\n minBedrooms,\n selectedRera,\n selectedAmenities,\n nlAdopted,\n ]);\n\n // Reset to first page whenever the server-side filter changes.\n useEffect(() => {\n setPage(1);\n }, [serverFilter, setPage]);\n\n const { data: properties, loading } = useProperties({ filter: serverFilter, pagination, skip: viewMode === 'map' });\n\n // Map mode (BOFF-4617): a separate, wider fetch bounded by the map's current\n // viewport instead of the grid/list's page-by-page pagination. Reapplying the\n // same non-geo filters (status/type/category/price/bedrooms/RERA) keeps the\n // map in sync with the filter panel.\n const mapFilter = useMemo<PropertyFilterInput | undefined>(() => {\n if (!mapBounds) return serverFilter;\n return { ...(serverFilter ?? {}), bounds: mapBounds };\n }, [serverFilter, mapBounds]);\n const { data: mapProperties, loading: mapLoading } = useProperties({\n filter: mapFilter,\n pagination: { limit: 200, offset: 0 },\n skip: viewMode !== 'map',\n });\n // Portfolio-wide totals for the stats zone — independent of the current page slice.\n const { data: dashboardStats } = useDashboardStats();\n const { data: shortlists } = useShortlists();\n const { saveShortlist } = useSaveShortlist();\n const { removeShortlist } = useRemoveShortlist();\n\n const shortlistedIds = useMemo(() => new Set(shortlists.map((s) => s.propertyId)), [shortlists]);\n\n const toggleShortlist = async (propertyId: string) => {\n try {\n if (shortlistedIds.has(propertyId)) {\n await removeShortlist(propertyId);\n notify.success('Removed from shortlist');\n } else {\n await saveShortlist(propertyId);\n emitResource('housingvista.shortlist.property_added', propertyId);\n notify.success('Saved to shortlist');\n }\n } catch (err) {\n notify.error('Could not update shortlist', (err as Error).message);\n }\n };\n\n // All filtering happens server-side now (see `serverFilter` above); the\n // page just renders the page slice the server already filtered.\n const filteredProperties = properties;\n\n // BOFF-4129 — natural-language search. Single-shot: one free-text query in,\n // one structured filter + real property list out (no multi-turn refinement\n // in this pass). Kept independent of the regular filter panel above — an\n // active NL search replaces the displayed list until cleared.\n const [nlQuery, setNlQuery] = useState('');\n const [nlActive, setNlActive] = useState(false);\n const {\n search: runNlSearch,\n loading: nlLoading,\n method: nlMethod,\n fallbackReason: nlFallbackReason,\n filter: nlFilter,\n properties: nlResults,\n } = useNaturalLanguageSearch();\n\n useEffect(() => {\n if (nlActive && nlMethod) {\n emit(nlMethod === 'ai' ? 'housingvista.nlsearch.ai_used' : 'housingvista.nlsearch.fallback_used');\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [nlMethod]);\n\n const handleNlSearchSubmit = (e: FormEvent) => {\n e.preventDefault();\n const trimmed = nlQuery.trim();\n if (!trimmed) return;\n setNlActive(true);\n emitSearch('properties.natural_language');\n void runNlSearch(trimmed);\n };\n\n const clearNlSearch = () => {\n setNlActive(false);\n setNlQuery('');\n setNlAdopted(null);\n };\n\n // ── Parsed-filter chips (BOFF-4129) ───────────────────────────────────────\n // Everything the search extracted, shown as one labelled chip per field so\n // the reader can see exactly what the sentence was turned into — and drop any\n // part of it. Both `HousingVistaNlSearchFilter` (what the AI/keyword parser\n // returned) and `HousingVistaPropertyFilterInput` (what is actually being\n // queried once those filters are adopted) fit this shape.\n type ChipKey =\n | 'city'\n | 'type'\n | 'status'\n | 'category'\n | 'priceMin'\n | 'priceMax'\n | 'bedroomsMin'\n | 'reraVerificationStatus'\n | `amenity:${string}`;\n\n interface ParsedChip {\n key: ChipKey;\n label: string;\n value: string;\n }\n\n interface ChipSource {\n city?: string | null;\n type?: PropertyType | null;\n status?: PropertyStatus | null;\n category?: string | null;\n priceMin?: number | null;\n priceMax?: number | null;\n bedroomsMin?: number | null;\n amenities?: string[] | null;\n reraVerificationStatus?: string | null;\n }\n\n const optionLabel = (options: { value: string; label: string }[], value: string): string =>\n options.find((option) => option.value === value)?.label ?? value.replace(/[-_]/g, ' ');\n\n const chipsFrom = (source: ChipSource | null | undefined): ParsedChip[] => {\n if (!source) return [];\n const chips: ParsedChip[] = [];\n if (source.city) {\n chips.push({ key: 'city', label: tr('housingvista.properties.chips.city', 'City'), value: source.city });\n }\n if (source.bedroomsMin != null) {\n chips.push({\n key: 'bedroomsMin',\n label: tr('housingvista.properties.chips.bedrooms', 'Bedrooms'),\n value: `${source.bedroomsMin}+`,\n });\n }\n if (source.priceMin != null) {\n chips.push({\n key: 'priceMin',\n label: tr('housingvista.properties.chips.minPrice', 'Min price'),\n value: formatNumber(source.priceMin),\n });\n }\n if (source.priceMax != null) {\n chips.push({\n key: 'priceMax',\n label: tr('housingvista.properties.chips.maxPrice', 'Max price'),\n value: formatNumber(source.priceMax),\n });\n }\n if (source.type) {\n chips.push({\n key: 'type',\n label: tr('housingvista.properties.chips.type', 'Type'),\n value: optionLabel(propertyTypes, source.type),\n });\n }\n if (source.category) {\n chips.push({\n key: 'category',\n label: tr('housingvista.properties.chips.category', 'Category'),\n value: optionLabel(propertyCategories, source.category),\n });\n }\n if (source.status) {\n chips.push({\n key: 'status',\n label: tr('housingvista.properties.chips.status', 'Status'),\n value: optionLabel(propertyStatuses, source.status),\n });\n }\n if (source.reraVerificationStatus) {\n chips.push({\n key: 'reraVerificationStatus',\n label: tr('housingvista.properties.chips.rera', 'RERA'),\n value: optionLabel(reraOptions, source.reraVerificationStatus),\n });\n }\n for (const amenity of source.amenities ?? []) {\n chips.push({\n key: `amenity:${amenity}`,\n label: tr('housingvista.properties.chips.amenity', 'Amenity'),\n value: amenity,\n });\n }\n return chips;\n };\n\n /**\n * Hand the parsed filter to the page's own property query (the same one the\n * dropdown panel drives), optionally dropping one chip on the way. This is\n * what \"Switch to manual filters\" and a chip's × both do — the result list is\n * then a real `housingvistaProperties` query, not the single-shot AI result.\n */\n const adoptParsedFilters = (parsed: NlSearchFilter, omit?: ChipKey) => {\n const next: PropertyFilterInput = {};\n if (parsed.city && omit !== 'city') next.city = parsed.city;\n if (parsed.type && omit !== 'type') next.type = parsed.type;\n if (parsed.status && omit !== 'status') next.status = parsed.status;\n if (parsed.category && omit !== 'category') next.category = parsed.category;\n if (parsed.priceMin != null && omit !== 'priceMin') next.priceMin = parsed.priceMin;\n if (parsed.priceMax != null && omit !== 'priceMax') next.priceMax = parsed.priceMax;\n if (parsed.bedroomsMin != null && omit !== 'bedroomsMin') next.bedroomsMin = parsed.bedroomsMin;\n if (omit !== 'reraVerificationStatus') {\n // The parser types this as a plain string; only adopt a value the page's\n // own RERA control recognises, so we never send the subgraph an enum it\n // does not have.\n const rera = reraOptions.find((option) => option.value === parsed.reraVerificationStatus);\n if (rera && rera.value !== 'all') next.reraVerificationStatus = rera.value;\n }\n const amenities = (parsed.amenities ?? []).filter((amenity) => omit !== `amenity:${amenity}`);\n if (amenities.length > 0) next.amenities = amenities;\n\n setNlAdopted(Object.keys(next).length > 0 ? next : null);\n setNlActive(false);\n setShowFilters(true);\n };\n\n /** Drop one chip wherever it currently lives — a dropdown, or the adopted set. */\n const removeChip = (key: ChipKey) => {\n if (nlActive && nlFilter) {\n adoptParsedFilters(nlFilter, key);\n return;\n }\n switch (key) {\n case 'type':\n setSelectedType('all');\n break;\n case 'status':\n setSelectedStatus('all');\n break;\n case 'category':\n setSelectedCategory('all');\n break;\n case 'bedroomsMin':\n setSelectedBedrooms('all');\n break;\n case 'reraVerificationStatus':\n setSelectedRera('all');\n break;\n case 'priceMin':\n case 'priceMax':\n // Price is a single bucket control, so clearing either end clears the\n // bucket; the remaining adopted end (if any) is stripped below.\n setSelectedPriceRange('all');\n break;\n default:\n break;\n }\n if (key.startsWith('amenity:')) {\n // Amenity chips can come from the manual panel selection or from an\n // adopted AI parse — drop from whichever holds it.\n const amenityLabel = key.slice('amenity:'.length);\n setSelectedAmenities((prev) =>\n prev.filter((id) => (amenityOptions.find((o) => o.id === id)?.label ?? id) !== amenityLabel)\n );\n }\n setNlAdopted((prev) => {\n if (!prev) return prev;\n const next = { ...prev };\n if (\n key === 'city' ||\n key === 'type' ||\n key === 'status' ||\n key === 'category' ||\n key === 'priceMin' ||\n key === 'priceMax' ||\n key === 'bedroomsMin' ||\n key === 'reraVerificationStatus'\n ) {\n delete next[key];\n } else {\n const amenity = key.slice('amenity:'.length);\n const remaining = (next.amenities ?? []).filter((value) => value !== amenity);\n if (remaining.length > 0) next.amenities = remaining;\n else delete next.amenities;\n }\n return Object.keys(next).length > 0 ? next : null;\n });\n };\n\n /** Plain-English restatement of the filter actually in force. */\n const describeChips = (chips: ParsedChip[]): string =>\n chips.map((chip) => `${chip.label.toLowerCase()} ${chip.value}`).join(', ');\n\n const nlChips = nlActive ? chipsFrom(nlFilter) : chipsFrom(serverFilter);\n // While a search is in flight `nlFilter` still holds the PREVIOUS result, so\n // hold the chips back rather than showing filters that are not in force.\n const showChipRow = ((nlActive && !nlLoading) || nlAdopted !== null) && nlChips.length > 0;\n\n const displayProperties = nlActive ? nlResults : filteredProperties;\n const displayLoading = nlActive ? nlLoading : loading;\n\n const hasNextPage = properties.length >= pageSize;\n\n const hasActiveFilters =\n searchQuery ||\n selectedType !== 'all' ||\n selectedStatus !== 'all' ||\n selectedCategory !== 'all' ||\n selectedPriceRange !== 'all' ||\n selectedBedrooms !== 'all' ||\n selectedRera !== 'all' ||\n selectedAmenities.length > 0 ||\n nlAdopted !== null;\n\n const clearFilters = () => {\n setSearchQuery('');\n setSelectedType('all');\n setSelectedStatus('all');\n setSelectedCategory('all');\n setSelectedPriceRange('all');\n setSelectedBedrooms('all');\n setSelectedRera('all');\n setSelectedAmenities([]);\n setNlAdopted(null);\n };\n\n return (\n <PageLayout\n title={tr('housingvista.properties.title', 'Properties')}\n description={tr(\n 'housingvista.properties.description',\n 'Discover your perfect property from our comprehensive listings'\n )}\n purpose={tr(\n 'housingvista.properties.purpose',\n 'Browse, search, and filter every property in your portfolio or on the market. Use the filters to narrow by type, status, price, and bedrooms, then open any listing to see full details, schedule viewings, or start a transaction.'\n )}\n actions={\n <button\n onClick={() => {\n emit('housingvista.property.create_clicked');\n navigate('new');\n }}\n className=\"inline-flex items-center gap-2 rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors hover:bg-foreground/90\"\n >\n <Plus className=\"h-4 w-4\" />\n {tr('housingvista.properties.actions.addProperty', 'Add Property')}\n </button>\n }\n >\n {/* Stats — primary insight zone (one EmphasisPanel per page) */}\n <EmphasisPanel className=\"p-4\">\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <StatCard\n label={tr('housingvista.properties.stats.totalProperties', 'Total Properties')}\n value={dashboardStats.totalProperties}\n icon={<Building className=\"h-5 w-5\" />}\n />\n <StatCard\n label={tr('housingvista.properties.stats.portfolioValue', 'Portfolio Value')}\n value={dashboardStats.totalValue}\n prefix=\"$\"\n />\n <StatCard\n label={tr('housingvista.properties.stats.occupancyRate', 'Occupancy Rate')}\n value={dashboardStats.occupancyRate}\n unit=\"%\"\n />\n <StatCard\n label={tr('housingvista.properties.stats.monthlyRevenue', 'Monthly Revenue')}\n value={dashboardStats.monthlyRevenue}\n prefix=\"$\"\n />\n </div>\n </EmphasisPanel>\n\n {/* BOFF-4129 — Natural-language search */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <form onSubmit={handleNlSearchSubmit} className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n <div className=\"flex flex-1 items-center gap-2\">\n <Sparkles className=\"h-4 w-4 shrink-0 text-text-secondary\" />\n <input\n type=\"text\"\n value={nlQuery}\n onChange={(e) => setNlQuery(e.target.value)}\n placeholder={tr(\n 'housingvista.properties.nlSearch.placeholder',\n 'Try \"2 bedroom apartment near downtown under $2000 with parking\"'\n )}\n className=\"h-9 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n <div className=\"flex items-center gap-3\">\n <button\n type=\"submit\"\n disabled={!nlQuery.trim() || nlLoading}\n className=\"inline-flex items-center gap-2 whitespace-nowrap rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors hover:bg-foreground/90 disabled:opacity-50\"\n >\n {nlLoading\n ? tr('housingvista.properties.nlSearch.searching', 'Searching…')\n : tr('housingvista.properties.nlSearch.submit', 'AI Search')}\n </button>\n {(nlActive || nlAdopted !== null) && (\n <button\n type=\"button\"\n onClick={clearNlSearch}\n className=\"whitespace-nowrap text-sm text-text-secondary hover:text-text-primary\"\n >\n {tr('housingvista.properties.nlSearch.clear', 'Clear AI search')}\n </button>\n )}\n </div>\n </form>\n\n {nlActive && !nlLoading && (\n <div className=\"mt-3 flex flex-wrap items-center gap-2 text-xs\">\n <span\n className={cn(\n 'rounded-full px-2 py-0.5 font-medium',\n nlMethod === 'ai' ? 'bg-action-primary-bg text-action-primary-text' : 'bg-bg-sunken text-text-secondary'\n )}\n >\n {nlMethod === 'ai'\n ? tr('housingvista.properties.nlSearch.methodAi', 'AI-parsed')\n : tr('housingvista.properties.nlSearch.methodKeyword', 'Keyword baseline (no AI configured)')}\n </span>\n {/* The subgraph says WHY it fell back to the keyword baseline —\n show it instead of leaving the downgrade unexplained. */}\n {nlFallbackReason && (\n <span className=\"rounded-full bg-status-warning-bg-subtle px-2 py-0.5 text-status-warning-text\">\n {nlFallbackReason}\n </span>\n )}\n </div>\n )}\n\n {showChipRow && (\n <div className=\"mt-3 space-y-2\">\n <p className=\"text-xs font-medium text-text-secondary\">\n {nlActive\n ? tr('housingvista.properties.nlSearch.chipsHeading', 'Filters we read from your sentence')\n : tr('housingvista.properties.nlSearch.chipsHeadingApplied', 'Filters applied from your sentence')}\n </p>\n <div className=\"flex flex-wrap items-center gap-2\">\n {nlChips.map((chip) => (\n <span\n key={chip.key}\n className=\"inline-flex items-center gap-1.5 rounded-full bg-status-info-bg-subtle px-2.5 py-1 text-xs text-status-info-text\"\n >\n <span className=\"font-medium\">{chip.label}:</span>\n <span>{chip.value}</span>\n <button\n type=\"button\"\n onClick={() => removeChip(chip.key)}\n aria-label={\n tr('housingvista.properties.nlSearch.removeChip', 'Remove filter') +\n `: ${chip.label} ${chip.value}`\n }\n className=\"rounded-full transition-opacity hover:opacity-70\"\n >\n <X className=\"h-3 w-3\" />\n </button>\n </span>\n ))}\n </div>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <p className=\"text-xs text-text-secondary\">\n {tr('housingvista.properties.nlSearch.readAs', 'How we read it')}: {describeChips(nlChips)}\n </p>\n {nlActive && nlFilter && (\n <button\n type=\"button\"\n onClick={() => adoptParsedFilters(nlFilter)}\n className=\"whitespace-nowrap text-xs font-medium text-primary hover:underline\"\n >\n {tr('housingvista.properties.nlSearch.switchToManual', 'Switch to manual filters')}\n </button>\n )}\n </div>\n </div>\n )}\n\n {nlActive && !nlLoading && nlChips.length === 0 && (\n <p className=\"mt-3 text-xs text-text-secondary\">\n {tr(\n 'housingvista.properties.nlSearch.noFiltersRead',\n 'No filters could be read from that sentence — these are unfiltered listings.'\n )}\n </p>\n )}\n </div>\n\n {/* Search + View Toggle */}\n <FilterBar\n searchValue={searchQuery}\n onSearchChange={setSearchQuery}\n searchPlaceholder={tr(\n 'housingvista.properties.search.placeholder',\n 'Search properties by name, city, or neighborhood...'\n )}\n filters={\n <button\n onClick={() => setShowFilters((prev) => !prev)}\n className={cn(\n 'inline-flex items-center gap-2 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium transition-colors',\n showFilters ? 'bg-foreground text-background' : 'text-text-primary hover:bg-bg-sunken'\n )}\n >\n <SlidersHorizontal className=\"h-4 w-4\" />\n {tr('housingvista.properties.filters.toggle', 'Filters')}\n {hasActiveFilters && (\n <span className=\"flex h-5 w-5 items-center justify-center rounded-full bg-action-primary-bg text-[10px] font-bold text-action-primary-text\">\n !\n </span>\n )}\n </button>\n }\n actions={\n <div className=\"flex items-center gap-1\">\n <button\n onClick={() => setViewMode('grid')}\n className={cn(\n 'rounded-md p-2 transition-colors',\n viewMode === 'grid'\n ? 'bg-foreground text-background'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-sunken'\n )}\n >\n <Grid className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setViewMode('list')}\n className={cn(\n 'rounded-md p-2 transition-colors',\n viewMode === 'list'\n ? 'bg-foreground text-background'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-sunken'\n )}\n >\n <List className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setViewMode('map')}\n aria-label={tr('housingvista.properties.viewMode.map', 'Map view')}\n className={cn(\n 'rounded-md p-2 transition-colors',\n viewMode === 'map'\n ? 'bg-foreground text-background'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-sunken'\n )}\n >\n <Map className=\"h-4 w-4\" />\n </button>\n </div>\n }\n />\n\n {/* Filter Dropdowns */}\n {showFilters && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <div className=\"flex items-center justify-between mb-4\">\n <h3 className=\"text-sm font-medium text-text-primary\">\n {tr('housingvista.properties.filters.title', 'Filters')}\n </h3>\n {hasActiveFilters && (\n <button\n onClick={clearFilters}\n className=\"inline-flex items-center gap-1 text-xs text-text-secondary hover:text-text-primary\"\n >\n <X className=\"h-3 w-3\" />\n {tr('housingvista.properties.filters.clearAll', 'Clear All')}\n </button>\n )}\n </div>\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-6\">\n {/* Type */}\n <div>\n <label className=\"mb-1.5 block text-xs font-medium text-text-secondary\">\n {tr('housingvista.properties.filters.labels.propertyType', 'Property Type')}\n </label>\n <select\n value={selectedType}\n onChange={(e) => setSelectedType(e.target.value as PropertyType | 'all')}\n className=\"h-9 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {propertyTypes.map((t) => (\n <option key={t.value} value={t.value}>\n {t.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* Status */}\n <div>\n <label className=\"mb-1.5 block text-xs font-medium text-text-secondary\">\n {tr('housingvista.properties.filters.labels.status', 'Status')}\n </label>\n <select\n value={selectedStatus}\n onChange={(e) => setSelectedStatus(e.target.value as PropertyStatus | 'all')}\n className=\"h-9 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {propertyStatuses.map((s) => (\n <option key={s.value} value={s.value}>\n {s.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* Category */}\n <div>\n <label className=\"mb-1.5 block text-xs font-medium text-text-secondary\">\n {tr('housingvista.properties.filters.labels.category', 'Category')}\n </label>\n <select\n value={selectedCategory}\n onChange={(e) => setSelectedCategory(e.target.value as PropertyCategory | 'all')}\n className=\"h-9 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {propertyCategories.map((c) => (\n <option key={c.value} value={c.value}>\n {c.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* Price Range */}\n <div>\n <label className=\"mb-1.5 block text-xs font-medium text-text-secondary\">\n {tr('housingvista.properties.filters.labels.priceRange', 'Price Range')}\n </label>\n <select\n value={selectedPriceRange}\n onChange={(e) => setSelectedPriceRange(e.target.value)}\n className=\"h-9 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {priceRanges.map((r) => (\n <option key={r.value} value={r.value}>\n {r.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* Bedrooms */}\n <div>\n <label className=\"mb-1.5 block text-xs font-medium text-text-secondary\">\n {tr('housingvista.properties.filters.labels.bedrooms', 'Bedrooms')}\n </label>\n <select\n value={selectedBedrooms}\n onChange={(e) => setSelectedBedrooms(e.target.value)}\n className=\"h-9 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {bedroomOptions.map((b) => (\n <option key={b.value} value={b.value}>\n {b.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* RERA verification (BOFF-4132) */}\n <div>\n <label className=\"mb-1.5 block text-xs font-medium text-text-secondary\">\n {tr('housingvista.properties.filters.labels.rera', 'RERA')}\n </label>\n <select\n value={selectedRera}\n onChange={(e) => setSelectedRera(e.target.value as ReraVerificationStatus | 'all')}\n className=\"h-9 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {reraOptions.map((r) => (\n <option key={r.value} value={r.value}>\n {r.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n {/* Amenities (BOFF-8090) — multi-select chips. Selection wins over\n AI-carried amenities (same rule as the dropdowns); labels are\n sent to the backend filter, which requires all selected\n amenities to be present. */}\n <div className=\"mt-4\">\n <label className=\"mb-1.5 block text-xs font-medium text-text-secondary\">\n {tr('housingvista.properties.filters.labels.amenities', 'Amenities')}\n </label>\n <div className=\"flex flex-wrap gap-2\">\n {amenityOptions.map((amenity) => {\n const Icon = amenity.icon;\n const isSelected = selectedAmenities.includes(amenity.id);\n return (\n <button\n key={amenity.id}\n type=\"button\"\n onClick={() => toggleFilterAmenity(amenity.id)}\n aria-pressed={isSelected}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium transition-colors',\n isSelected\n ? 'border-action-primary-border bg-action-primary-bg/5 text-text-primary'\n : 'border-border-subtle text-text-secondary hover:bg-bg-sunken/50'\n )}\n >\n <Icon className=\"h-3.5 w-3.5\" />\n {amenity.label}\n </button>\n );\n })}\n </div>\n </div>\n </div>\n )}\n\n {/* Results Count */}\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-text-secondary\">\n {nlActive ? (\n <>\n {tr('housingvista.properties.results.showing', 'Showing')} {displayProperties.length}{' '}\n {tr('housingvista.properties.results.properties', 'properties')} ·{' '}\n {tr('housingvista.properties.nlSearch.resultsLabel', 'AI search results')}\n </>\n ) : viewMode === 'map' ? (\n <>\n {tr('housingvista.properties.results.showingOnMap', 'Showing')} {mapProperties.length}{' '}\n {tr('housingvista.properties.results.properties', 'properties')}\n {mapBounds ? ` · ${tr('housingvista.properties.results.inThisArea', 'in this area')}` : ''}\n </>\n ) : (\n <>\n {tr('housingvista.properties.results.showing', 'Showing')} {filteredProperties.length}{' '}\n {tr('housingvista.properties.results.properties', 'properties')}\n {page > 1 ? ` · page ${page}` : ''}\n </>\n )}\n </p>\n {!nlActive && hasActiveFilters && (\n <button onClick={clearFilters} className=\"text-sm text-text-secondary hover:text-text-primary\">\n {tr('housingvista.properties.results.clearFilters', 'Clear Filters')}\n </button>\n )}\n </div>\n\n {/* Property Map (BOFF-4617) */}\n {!nlActive && viewMode === 'map' && (\n <PropertyMapView\n properties={mapProperties}\n loading={mapLoading}\n selectedIds={shortlistedIds}\n onSelectProperty={(id) => navigate(id)}\n onSearchThisArea={handleSearchThisArea}\n />\n )}\n\n {/* Property Grid / List */}\n {!nlActive && viewMode === 'map' ? null : displayLoading ? (\n <LoadingSkeleton variant=\"card\" count={6} />\n ) : displayProperties.length > 0 ? (\n <>\n <div\n className={cn(\n nlActive || viewMode === 'grid' ? 'grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3' : 'space-y-4'\n )}\n >\n {displayProperties.map((property) => (\n <PropertyCard\n key={property.id}\n id={property.id}\n title={property.title}\n price={property.price.amount}\n priceType={property.price.type === 'rent' ? 'rent' : 'sale'}\n pricePeriod={property.price.period}\n currency={property.price.currency}\n location={[property.location.neighborhood, property.location.city].filter(Boolean).join(', ')}\n bedrooms={property.specifications.bedrooms}\n bathrooms={property.specifications.bathrooms}\n area={property.specifications.totalArea}\n imageUrl={property.images[0]?.url}\n status={property.status.replace('_', ' ')}\n type={property.category}\n onClick={() => navigate(property.id)}\n isShortlisted={shortlistedIds.has(property.id)}\n onToggleShortlist={() => toggleShortlist(property.id)}\n reraStatus={property.reraVerificationStatus}\n verificationStatus={property.verificationStatus}\n />\n ))}\n </div>\n\n {/* Pagination (server-side): working prev/next + page-size, no silent truncation.\n Not shown for AI search results (BOFF-4129) — a single-shot result set. */}\n {!nlActive && (\n <Pagination\n page={page}\n pageSize={pageSize}\n hasNextPage={hasNextPage}\n hasPreviousPage={page > 1}\n loading={loading}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n )}\n </>\n ) : (\n <EmptyState\n illustration=\"empty-search\"\n icon={<Building className=\"h-12 w-12\" />}\n title={tr('housingvista.properties.empty.title', 'No properties found')}\n description={\n nlActive\n ? tr(\n 'housingvista.properties.nlSearch.emptyDescription',\n 'No properties matched that search. Try rephrasing your query.'\n )\n : tr(\n 'housingvista.properties.empty.description',\n 'Try adjusting your search criteria or filters to find more properties.'\n )\n }\n action={\n <button\n onClick={nlActive ? clearNlSearch : clearFilters}\n className=\"rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors hover:bg-foreground/90\"\n >\n {nlActive\n ? tr('housingvista.properties.nlSearch.clear', 'Clear AI search')\n : tr('housingvista.properties.empty.clearAllFilters', 'Clear All Filters')}\n </button>\n }\n />\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"PropertyDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/properties/PropertyDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAqTH,eAAO,MAAM,kBAAkB,mCA4/D9B,CAAC"}
1
+ {"version":3,"file":"PropertyDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/properties/PropertyDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA4TH,eAAO,MAAM,kBAAkB,mCA4/D9B,CAAC"}