@burdenoff/microfe-housingvista 2026.720.2 → 2026.730.1
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.
- package/dist/housingvista/hooks/index.d.ts +32 -0
- package/dist/housingvista/hooks/index.d.ts.map +1 -1
- package/dist/housingvista/hooks/index.js +336 -299
- package/dist/housingvista/hooks/index.js.map +1 -1
- package/dist/housingvista/hooks/useHousingVistaAnalytics.d.ts +1 -1
- package/dist/housingvista/hooks/useHousingVistaAnalytics.d.ts.map +1 -1
- package/dist/housingvista/hooks/useHousingVistaAnalytics.js.map +1 -1
- package/dist/housingvista/pages/properties/PropertiesPage.d.ts.map +1 -1
- package/dist/housingvista/pages/properties/PropertiesPage.js +253 -172
- package/dist/housingvista/pages/properties/PropertiesPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -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 } from 'react';\nimport { useNavigate, useSearchParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Plus, Grid, List, Map, Building, SlidersHorizontal, X } 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 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 } 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 { t } = useI18n();\n const { emit, emitResource, emitSearch } = useHousingVistaAnalytics();\n useTrackPageView('housingvista.property.viewed');\n\n const tr = (key: string, fallback: string) => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\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 // 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 return Object.keys(filter).length > 0 ? filter : undefined;\n }, [debouncedSearch, selectedStatus, selectedType, selectedCategory, priceRange, minBedrooms, selectedRera]);\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 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\n const clearFilters = () => {\n setSearchQuery('');\n setSelectedType('all');\n setSelectedStatus('all');\n setSelectedCategory('all');\n setSelectedPriceRange('all');\n setSelectedBedrooms('all');\n setSelectedRera('all');\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 {/* 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 {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 {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 {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 {viewMode === 'map' ? null : loading ? (\n <LoadingSkeleton variant=\"card\" count={6} />\n ) : filteredProperties.length > 0 ? (\n <>\n <div\n className={cn(viewMode === 'grid' ? 'grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3' : 'space-y-4')}\n >\n {filteredProperties.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 <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 <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={tr(\n 'housingvista.properties.empty.description',\n 'Try adjusting your search criteria or filters to find more properties.'\n )}\n action={\n <button\n onClick={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 {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-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\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 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 } 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 { t } = useI18n();\n const { emit, emitResource, emitSearch } = useHousingVistaAnalytics();\n useTrackPageView('housingvista.property.viewed');\n\n const tr = (key: string, fallback: string) => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\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 // 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 return Object.keys(filter).length > 0 ? filter : undefined;\n }, [debouncedSearch, selectedStatus, selectedType, selectedCategory, priceRange, minBedrooms, selectedRera]);\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 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 };\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\n const clearFilters = () => {\n setSearchQuery('');\n setSelectedType('all');\n setSelectedStatus('all');\n setSelectedCategory('all');\n setSelectedPriceRange('all');\n setSelectedBedrooms('all');\n setSelectedRera('all');\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 && (\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 {nlFilter?.city && (\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-text-secondary\">{nlFilter.city}</span>\n )}\n {nlFilter?.bedroomsMin != null && (\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-text-secondary\">\n {nlFilter.bedroomsMin}+ bed\n </span>\n )}\n {nlFilter?.priceMax != null && (\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-text-secondary\">\n under {nlFilter.priceMax.toLocaleString()}\n </span>\n )}\n {nlFilter?.priceMin != null && (\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-text-secondary\">\n over {nlFilter.priceMin.toLocaleString()}\n </span>\n )}\n {nlFilter?.category && (\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-text-secondary\">{nlFilter.category}</span>\n )}\n {nlFilter?.amenities?.map((a) => (\n <span key={a} className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-text-secondary\">\n {a}\n </span>\n ))}\n </div>\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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-housingvista",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.730.1",
|
|
4
4
|
"description": "Burdenoff HousingVista Microfrontend - Comprehensive real estate platform for property management, investment, and transactions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|