@burdenoff/microfe-housingvista 2026.709.2 → 2026.709.3
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/HousingVistaRoutes.d.ts.map +1 -1
- package/dist/housingvista/HousingVistaRoutes.js +136 -37
- package/dist/housingvista/HousingVistaRoutes.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 +4 -4
- package/dist/housingvista/hooks/useHousingVistaAnalytics.js.map +1 -1
- package/dist/housingvista/pages/amc/AMCManagementPage.d.ts.map +1 -1
- package/dist/housingvista/pages/amc/AMCManagementPage.js +62 -60
- package/dist/housingvista/pages/amc/AMCManagementPage.js.map +1 -1
- package/dist/housingvista/pages/assets/AssetDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/assets/AssetDetailPage.js +141 -138
- package/dist/housingvista/pages/assets/AssetDetailPage.js.map +1 -1
- package/dist/housingvista/pages/assets/AssetManagementPage.d.ts.map +1 -1
- package/dist/housingvista/pages/assets/AssetManagementPage.js +193 -191
- package/dist/housingvista/pages/assets/AssetManagementPage.js.map +1 -1
- package/dist/housingvista/pages/chat/ChatPage.d.ts.map +1 -1
- package/dist/housingvista/pages/chat/ChatPage.js +115 -113
- package/dist/housingvista/pages/chat/ChatPage.js.map +1 -1
- package/dist/housingvista/pages/finance/HomeLoanDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/finance/HomeLoanDetailPage.js +152 -149
- package/dist/housingvista/pages/finance/HomeLoanDetailPage.js.map +1 -1
- package/dist/housingvista/pages/finance/HomeLoansPage.d.ts.map +1 -1
- package/dist/housingvista/pages/finance/HomeLoansPage.js +141 -139
- package/dist/housingvista/pages/finance/HomeLoansPage.js.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentDetailPage.js +290 -287
- package/dist/housingvista/pages/investments/InvestmentDetailPage.js.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentsPage.js +64 -62
- package/dist/housingvista/pages/investments/InvestmentsPage.js.map +1 -1
- package/dist/housingvista/pages/legal/LegalPage.d.ts.map +1 -1
- package/dist/housingvista/pages/legal/LegalPage.js +95 -93
- package/dist/housingvista/pages/legal/LegalPage.js.map +1 -1
- package/dist/housingvista/pages/legal/LegalServiceDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/legal/LegalServiceDetailPage.js +129 -126
- package/dist/housingvista/pages/legal/LegalServiceDetailPage.js.map +1 -1
- package/dist/housingvista/pages/market/AreaInsightsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/market/AreaInsightsPage.js +99 -97
- package/dist/housingvista/pages/market/AreaInsightsPage.js.map +1 -1
- package/dist/housingvista/pages/market/MarketAnalyticsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/market/MarketAnalyticsPage.js +138 -136
- package/dist/housingvista/pages/market/MarketAnalyticsPage.js.map +1 -1
- package/dist/housingvista/pages/marketplace/MarketplacePage.d.ts.map +1 -1
- package/dist/housingvista/pages/marketplace/MarketplacePage.js +99 -97
- package/dist/housingvista/pages/marketplace/MarketplacePage.js.map +1 -1
- package/dist/housingvista/pages/marketplace/ServiceDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/marketplace/ServiceDetailPage.js +137 -134
- package/dist/housingvista/pages/marketplace/ServiceDetailPage.js.map +1 -1
- package/dist/housingvista/pages/offers/OffersPage.d.ts.map +1 -1
- package/dist/housingvista/pages/offers/OffersPage.js +94 -92
- package/dist/housingvista/pages/offers/OffersPage.js.map +1 -1
- package/dist/housingvista/pages/properties/AddPropertyPage.d.ts.map +1 -1
- package/dist/housingvista/pages/properties/AddPropertyPage.js +280 -279
- package/dist/housingvista/pages/properties/AddPropertyPage.js.map +1 -1
- package/dist/housingvista/pages/rental/RentalManagementPage.d.ts.map +1 -1
- package/dist/housingvista/pages/rental/RentalManagementPage.js +153 -151
- package/dist/housingvista/pages/rental/RentalManagementPage.js.map +1 -1
- package/dist/housingvista/pages/settings/ProfilePage.d.ts.map +1 -1
- package/dist/housingvista/pages/settings/ProfilePage.js +52 -50
- package/dist/housingvista/pages/settings/ProfilePage.js.map +1 -1
- package/dist/housingvista/pages/settings/SettingsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/settings/SettingsPage.js +54 -52
- package/dist/housingvista/pages/settings/SettingsPage.js.map +1 -1
- package/dist/housingvista/pages/shortlist/ShortlistPage.d.ts.map +1 -1
- package/dist/housingvista/pages/shortlist/ShortlistPage.js +43 -41
- package/dist/housingvista/pages/shortlist/ShortlistPage.js.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionDetailPage.js +211 -208
- package/dist/housingvista/pages/transactions/TransactionDetailPage.js.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionsPage.js +85 -83
- package/dist/housingvista/pages/transactions/TransactionsPage.js.map +1 -1
- package/dist/housingvista/pages/verification/VerificationDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/verification/VerificationDetailPage.js +25 -22
- package/dist/housingvista/pages/verification/VerificationDetailPage.js.map +1 -1
- package/dist/housingvista/pages/verification/VerificationPage.d.ts.map +1 -1
- package/dist/housingvista/pages/verification/VerificationPage.js +123 -121
- package/dist/housingvista/pages/verification/VerificationPage.js.map +1 -1
- package/dist/housingvista/pages/viewings/ViewingsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/viewings/ViewingsPage.js +128 -126
- package/dist/housingvista/pages/viewings/ViewingsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MarketAnalyticsPage.js","names":[],"sources":["../../../../src/housingvista/pages/market/MarketAnalyticsPage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport { TrendingUp, TrendingDown, IndianRupee, Building, Gauge, ArrowUpRight, ArrowDownRight } from 'lucide-react';\nimport {\n LineChart,\n Line,\n XAxis,\n YAxis,\n CartesianGrid,\n Tooltip,\n ResponsiveContainer,\n Legend,\n AreaChart,\n Area,\n BarChart,\n Bar,\n} from 'recharts';\nimport { EmphasisPanel, NextSteps } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, StatCard, ChartCard, DataTable } from '../../components';\nimport type { Column } from '../../components';\nimport { useMarketTrends } from '../../hooks';\nimport { useServerPagination } from '../../hooks/useServerPagination';\nimport { cn, formatCurrency, formatINRCompact } from '../../utils/helpers';\nimport type { MarketTrend } from '../../types';\n\n// Theme-aware recharts colors: `var(--color-chart-*)` resolves to the semantic\n// chart tokens in both light and dark themes (the previous `var(--color-chart-1)`\n// wrapped an oklch value and rendered invalid).\nconst CHART = {\n price: 'var(--color-chart-1)',\n rent: 'var(--color-chart-2)',\n demand: 'var(--color-chart-1)',\n supply: 'var(--color-chart-3)',\n} as const;\n\nconst TOOLTIP_CONTENT_STYLE = {\n backgroundColor: 'var(--color-bg-elevated)',\n border: '1px solid var(--color-border-seam)',\n borderRadius: '0.5rem',\n fontSize: '0.75rem',\n} as const;\nconst TOOLTIP_LABEL_STYLE = { color: 'var(--color-text-primary)' } as const;\n\nexport function MarketAnalyticsPage() {\n const pagination = useServerPagination(10);\n const { data: trends, loading } = useMarketTrends(pagination.pagination);\n const [chartType, setChartType] = useState<'price' | 'rent' | 'demand'>('price');\n\n // Aggregate stats derived directly from the latest real market-trend record.\n const stats = useMemo(() => {\n if (trends.length === 0) return { avgPrice: 0, priceChange: 0, avgRent: 0, demandIndex: 0 };\n const latest = trends[trends.length - 1];\n return {\n avgPrice: latest.avgPrice,\n priceChange: latest.priceChangePercent,\n avgRent: latest.avgRent,\n demandIndex: latest.demandIndex,\n };\n }, [trends]);\n\n // Format chart data for month labels\n const chartData = useMemo(() => {\n return trends.map((t) => ({\n ...t,\n monthLabel: new Date(t.month + '-01').toLocaleDateString('en-US', { month: 'short', year: '2-digit' }),\n }));\n }, [trends]);\n\n // Table columns\n const columns: Column<MarketTrend & { monthLabel: string }>[] = [\n {\n key: 'monthLabel',\n header: 'Month',\n render: (item) => <span className=\"font-medium text-text-primary\">{item.monthLabel}</span>,\n },\n {\n key: 'avgPrice',\n header: 'Avg Price',\n render: (item) => <span className=\"text-text-primary\">{formatINRCompact(item.avgPrice)}</span>,\n },\n {\n key: 'avgRent',\n header: 'Avg Rent',\n render: (item) => <span className=\"text-text-primary\">{formatINRCompact(item.avgRent)}</span>,\n },\n {\n key: 'priceChangePercent',\n header: 'Change %',\n render: (item) => (\n <span\n className={cn(\n 'inline-flex items-center gap-0.5 text-xs font-medium',\n item.priceChangePercent >= 0 ? 'text-status-success-text' : 'text-status-error-text'\n )}\n >\n {item.priceChangePercent >= 0 ? <ArrowUpRight className=\"h-3 w-3\" /> : <ArrowDownRight className=\"h-3 w-3\" />}\n {item.priceChangePercent > 0 ? '+' : ''}\n {item.priceChangePercent}%\n </span>\n ),\n },\n {\n key: 'demandIndex',\n header: 'Demand',\n render: (item) => (\n <div className=\"flex items-center gap-2\">\n <div className=\"h-1.5 w-16 rounded-full bg-bg-sunken\">\n <div className=\"h-full rounded-full bg-chart-1\" style={{ width: `${item.demandIndex}%` }} />\n </div>\n <span className=\"text-xs text-text-secondary\">{item.demandIndex}</span>\n </div>\n ),\n },\n {\n key: 'supplyIndex',\n header: 'Supply',\n render: (item) => (\n <div className=\"flex items-center gap-2\">\n <div className=\"h-1.5 w-16 rounded-full bg-bg-sunken\">\n <div className=\"h-full rounded-full bg-chart-3\" style={{ width: `${item.supplyIndex}%` }} />\n </div>\n <span className=\"text-xs text-text-secondary\">{item.supplyIndex}</span>\n </div>\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"Market Analytics\"\n description=\"Track market trends, price movements, and demand-supply dynamics\"\n purpose=\"Read the market before you act. Watch how prices, rents, demand, and supply move over time so you can time a purchase, set the right asking price, or spot a cooling or heating neighborhood early.\"\n >\n <NextSteps\n storageKey=\"housingvista-market-analytics\"\n steps={[\n {\n id: 'rent-trend',\n label: 'Check the rent trend',\n description: 'Switch the chart to Rent to see how rental rates are moving.',\n onClick: () => setChartType('rent'),\n },\n {\n id: 'demand',\n label: 'Compare demand vs supply',\n description: 'See where the market favors buyers or sellers.',\n onClick: () => setChartType('demand'),\n },\n {\n id: 'monthly',\n label: 'Open the monthly comparison',\n description: 'Review the full month-by-month breakdown in the table.',\n href: '#market-monthly-comparison',\n },\n ]}\n />\n\n {/* Stats Row — primary insight zone (one EmphasisPanel per page) */}\n <EmphasisPanel className=\"p-4\">\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n label=\"Average Price\"\n value={formatINRCompact(stats.avgPrice)}\n icon={<IndianRupee className=\"h-5 w-5 text-accent-blue\" />}\n trend={stats.priceChange >= 0 ? 'up' : 'down'}\n change={stats.priceChange}\n featured\n />\n <StatCard\n label=\"Price Change (YoY)\"\n value={`${stats.priceChange >= 0 ? '+' : ''}${stats.priceChange}%`}\n icon={\n stats.priceChange >= 0 ? (\n <TrendingUp className=\"h-5 w-5 text-status-success-text\" />\n ) : (\n <TrendingDown className=\"h-5 w-5 text-status-error-text\" />\n )\n }\n />\n <StatCard\n label=\"Average Rent\"\n value={formatINRCompact(stats.avgRent)}\n icon={<Building className=\"h-5 w-5 text-accent-purple\" />}\n />\n <StatCard\n label=\"Demand Index\"\n value={stats.demandIndex}\n unit=\"/ 100\"\n icon={<Gauge className=\"h-5 w-5 text-status-success-text\" />}\n />\n </div>\n </EmphasisPanel>\n\n {/* Charts — trend (toggle) alongside demand-vs-supply, like the concept */}\n <div className=\"grid gap-6 xl:grid-cols-3\">\n <ChartCard\n className=\"xl:col-span-2\"\n title=\"Market Trends\"\n description=\"Monthly price, rent, and demand trends\"\n actions={\n <div className=\"flex gap-1\">\n {(['price', 'rent', 'demand'] as const).map((t) => (\n <button\n key={t}\n onClick={() => setChartType(t)}\n aria-pressed={chartType === t}\n className={cn(\n 'rounded-md px-2.5 py-1 text-xs font-medium transition-colors capitalize',\n chartType === t\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'text-text-secondary hover:bg-bg-sunken'\n )}\n >\n {t}\n </button>\n ))}\n </div>\n }\n >\n <div className=\"h-72\">\n {chartData.length === 0 ? (\n <div className=\"flex h-full items-center justify-center text-sm text-text-secondary\">\n No trend data yet.\n </div>\n ) : (\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n {chartType === 'price' ? (\n <AreaChart data={chartData}>\n <defs>\n <linearGradient id=\"priceGrad\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"5%\" stopColor={CHART.price} stopOpacity={0.3} />\n <stop offset=\"95%\" stopColor={CHART.price} stopOpacity={0} />\n </linearGradient>\n </defs>\n <CartesianGrid strokeDasharray=\"3 3\" className=\"stroke-border\" />\n <XAxis\n dataKey=\"monthLabel\"\n tick={{ fontSize: 11, fill: 'currentColor' }}\n className=\"text-text-secondary\"\n />\n <YAxis\n tick={{ fontSize: 11, fill: 'currentColor' }}\n tickFormatter={(v) => formatINRCompact(v)}\n width={64}\n className=\"text-text-secondary\"\n />\n <Tooltip\n contentStyle={TOOLTIP_CONTENT_STYLE}\n labelStyle={TOOLTIP_LABEL_STYLE}\n formatter={(value: number) => [formatCurrency(value), 'Avg Price']}\n />\n <Area\n type=\"monotone\"\n dataKey=\"avgPrice\"\n stroke={CHART.price}\n fill=\"url(#priceGrad)\"\n strokeWidth={2}\n />\n </AreaChart>\n ) : chartType === 'rent' ? (\n <LineChart data={chartData}>\n <CartesianGrid strokeDasharray=\"3 3\" className=\"stroke-border\" />\n <XAxis\n dataKey=\"monthLabel\"\n tick={{ fontSize: 11, fill: 'currentColor' }}\n className=\"text-text-secondary\"\n />\n <YAxis\n tick={{ fontSize: 11, fill: 'currentColor' }}\n tickFormatter={(v) => formatINRCompact(v)}\n width={64}\n className=\"text-text-secondary\"\n />\n <Tooltip\n contentStyle={TOOLTIP_CONTENT_STYLE}\n labelStyle={TOOLTIP_LABEL_STYLE}\n formatter={(value: number) => [formatCurrency(value), 'Avg Rent']}\n />\n <Line type=\"monotone\" dataKey=\"avgRent\" stroke={CHART.rent} strokeWidth={2} dot={{ r: 3 }} />\n </LineChart>\n ) : (\n <LineChart data={chartData}>\n <CartesianGrid strokeDasharray=\"3 3\" className=\"stroke-border\" />\n <XAxis\n dataKey=\"monthLabel\"\n tick={{ fontSize: 11, fill: 'currentColor' }}\n className=\"text-text-secondary\"\n />\n <YAxis tick={{ fontSize: 11, fill: 'currentColor' }} className=\"text-text-secondary\" />\n <Tooltip contentStyle={TOOLTIP_CONTENT_STYLE} labelStyle={TOOLTIP_LABEL_STYLE} />\n <Legend wrapperStyle={{ fontSize: '0.75rem' }} />\n <Line\n type=\"monotone\"\n dataKey=\"demandIndex\"\n stroke={CHART.demand}\n name=\"Demand\"\n strokeWidth={2}\n dot={{ r: 3 }}\n />\n <Line\n type=\"monotone\"\n dataKey=\"supplyIndex\"\n stroke={CHART.supply}\n name=\"Supply\"\n strokeWidth={2}\n dot={{ r: 3 }}\n />\n </LineChart>\n )}\n </ResponsiveContainer>\n )}\n </div>\n </ChartCard>\n\n <ChartCard title=\"Demand vs Supply\" description=\"Monthly demand & supply index\">\n <div className=\"h-72\">\n {chartData.length === 0 ? (\n <div className=\"flex h-full items-center justify-center text-sm text-text-secondary\">No data yet.</div>\n ) : (\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <BarChart data={chartData} barGap={2}>\n <CartesianGrid strokeDasharray=\"3 3\" className=\"stroke-border\" vertical={false} />\n <XAxis\n dataKey=\"monthLabel\"\n tick={{ fontSize: 11, fill: 'currentColor' }}\n className=\"text-text-secondary\"\n />\n <YAxis tick={{ fontSize: 11, fill: 'currentColor' }} className=\"text-text-secondary\" />\n <Tooltip\n contentStyle={TOOLTIP_CONTENT_STYLE}\n labelStyle={TOOLTIP_LABEL_STYLE}\n cursor={{ fill: 'var(--color-bg-sunken)', opacity: 0.4 }}\n />\n <Legend wrapperStyle={{ fontSize: '0.75rem' }} />\n <Bar dataKey=\"demandIndex\" name=\"Demand\" fill={CHART.demand} radius={[3, 3, 0, 0]} />\n <Bar dataKey=\"supplyIndex\" name=\"Supply\" fill={CHART.supply} radius={[3, 3, 0, 0]} />\n </BarChart>\n </ResponsiveContainer>\n )}\n </div>\n </ChartCard>\n </div>\n\n {/* Market Comparison Table */}\n <div id=\"market-monthly-comparison\" className=\"scroll-mt-6\">\n <h2 className=\"mb-3 text-lg font-semibold text-text-primary\">Monthly Comparison</h2>\n <DataTable\n columns={columns}\n data={chartData}\n keyField=\"month\"\n serverPagination={{\n page: pagination.page,\n pageSize: pagination.pageSize,\n hasNextPage: trends.length >= pagination.pageSize,\n loading,\n onPageChange: pagination.setPage,\n onPageSizeChange: pagination.setPageSize,\n }}\n />\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MarketAnalyticsPage.js","names":[],"sources":["../../../../src/housingvista/pages/market/MarketAnalyticsPage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport { TrendingUp, TrendingDown, IndianRupee, Building, Gauge, ArrowUpRight, ArrowDownRight } from 'lucide-react';\nimport {\n LineChart,\n Line,\n XAxis,\n YAxis,\n CartesianGrid,\n Tooltip,\n ResponsiveContainer,\n Legend,\n AreaChart,\n Area,\n BarChart,\n Bar,\n} from 'recharts';\nimport { EmphasisPanel, NextSteps } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, StatCard, ChartCard, DataTable } from '../../components';\nimport type { Column } from '../../components';\nimport { useMarketTrends } from '../../hooks';\nimport { useServerPagination } from '../../hooks/useServerPagination';\nimport { cn, formatCurrency, formatINRCompact } from '../../utils/helpers';\nimport type { MarketTrend } from '../../types';\nimport { useTrackPageView } from '../../hooks/useHousingVistaAnalytics';\n\n// Theme-aware recharts colors: `var(--color-chart-*)` resolves to the semantic\n// chart tokens in both light and dark themes (the previous `var(--color-chart-1)`\n// wrapped an oklch value and rendered invalid).\nconst CHART = {\n price: 'var(--color-chart-1)',\n rent: 'var(--color-chart-2)',\n demand: 'var(--color-chart-1)',\n supply: 'var(--color-chart-3)',\n} as const;\n\nconst TOOLTIP_CONTENT_STYLE = {\n backgroundColor: 'var(--color-bg-elevated)',\n border: '1px solid var(--color-border-seam)',\n borderRadius: '0.5rem',\n fontSize: '0.75rem',\n} as const;\nconst TOOLTIP_LABEL_STYLE = { color: 'var(--color-text-primary)' } as const;\n\nexport function MarketAnalyticsPage() {\n useTrackPageView('housingvista.market_analytics.viewed');\n const pagination = useServerPagination(10);\n const { data: trends, loading } = useMarketTrends(pagination.pagination);\n const [chartType, setChartType] = useState<'price' | 'rent' | 'demand'>('price');\n\n // Aggregate stats derived directly from the latest real market-trend record.\n const stats = useMemo(() => {\n if (trends.length === 0) return { avgPrice: 0, priceChange: 0, avgRent: 0, demandIndex: 0 };\n const latest = trends[trends.length - 1];\n return {\n avgPrice: latest.avgPrice,\n priceChange: latest.priceChangePercent,\n avgRent: latest.avgRent,\n demandIndex: latest.demandIndex,\n };\n }, [trends]);\n\n // Format chart data for month labels\n const chartData = useMemo(() => {\n return trends.map((t) => ({\n ...t,\n monthLabel: new Date(t.month + '-01').toLocaleDateString('en-US', { month: 'short', year: '2-digit' }),\n }));\n }, [trends]);\n\n // Table columns\n const columns: Column<MarketTrend & { monthLabel: string }>[] = [\n {\n key: 'monthLabel',\n header: 'Month',\n render: (item) => <span className=\"font-medium text-text-primary\">{item.monthLabel}</span>,\n },\n {\n key: 'avgPrice',\n header: 'Avg Price',\n render: (item) => <span className=\"text-text-primary\">{formatINRCompact(item.avgPrice)}</span>,\n },\n {\n key: 'avgRent',\n header: 'Avg Rent',\n render: (item) => <span className=\"text-text-primary\">{formatINRCompact(item.avgRent)}</span>,\n },\n {\n key: 'priceChangePercent',\n header: 'Change %',\n render: (item) => (\n <span\n className={cn(\n 'inline-flex items-center gap-0.5 text-xs font-medium',\n item.priceChangePercent >= 0 ? 'text-status-success-text' : 'text-status-error-text'\n )}\n >\n {item.priceChangePercent >= 0 ? <ArrowUpRight className=\"h-3 w-3\" /> : <ArrowDownRight className=\"h-3 w-3\" />}\n {item.priceChangePercent > 0 ? '+' : ''}\n {item.priceChangePercent}%\n </span>\n ),\n },\n {\n key: 'demandIndex',\n header: 'Demand',\n render: (item) => (\n <div className=\"flex items-center gap-2\">\n <div className=\"h-1.5 w-16 rounded-full bg-bg-sunken\">\n <div className=\"h-full rounded-full bg-chart-1\" style={{ width: `${item.demandIndex}%` }} />\n </div>\n <span className=\"text-xs text-text-secondary\">{item.demandIndex}</span>\n </div>\n ),\n },\n {\n key: 'supplyIndex',\n header: 'Supply',\n render: (item) => (\n <div className=\"flex items-center gap-2\">\n <div className=\"h-1.5 w-16 rounded-full bg-bg-sunken\">\n <div className=\"h-full rounded-full bg-chart-3\" style={{ width: `${item.supplyIndex}%` }} />\n </div>\n <span className=\"text-xs text-text-secondary\">{item.supplyIndex}</span>\n </div>\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"Market Analytics\"\n description=\"Track market trends, price movements, and demand-supply dynamics\"\n purpose=\"Read the market before you act. Watch how prices, rents, demand, and supply move over time so you can time a purchase, set the right asking price, or spot a cooling or heating neighborhood early.\"\n >\n <NextSteps\n storageKey=\"housingvista-market-analytics\"\n steps={[\n {\n id: 'rent-trend',\n label: 'Check the rent trend',\n description: 'Switch the chart to Rent to see how rental rates are moving.',\n onClick: () => setChartType('rent'),\n },\n {\n id: 'demand',\n label: 'Compare demand vs supply',\n description: 'See where the market favors buyers or sellers.',\n onClick: () => setChartType('demand'),\n },\n {\n id: 'monthly',\n label: 'Open the monthly comparison',\n description: 'Review the full month-by-month breakdown in the table.',\n href: '#market-monthly-comparison',\n },\n ]}\n />\n\n {/* Stats Row — primary insight zone (one EmphasisPanel per page) */}\n <EmphasisPanel className=\"p-4\">\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n label=\"Average Price\"\n value={formatINRCompact(stats.avgPrice)}\n icon={<IndianRupee className=\"h-5 w-5 text-accent-blue\" />}\n trend={stats.priceChange >= 0 ? 'up' : 'down'}\n change={stats.priceChange}\n featured\n />\n <StatCard\n label=\"Price Change (YoY)\"\n value={`${stats.priceChange >= 0 ? '+' : ''}${stats.priceChange}%`}\n icon={\n stats.priceChange >= 0 ? (\n <TrendingUp className=\"h-5 w-5 text-status-success-text\" />\n ) : (\n <TrendingDown className=\"h-5 w-5 text-status-error-text\" />\n )\n }\n />\n <StatCard\n label=\"Average Rent\"\n value={formatINRCompact(stats.avgRent)}\n icon={<Building className=\"h-5 w-5 text-accent-purple\" />}\n />\n <StatCard\n label=\"Demand Index\"\n value={stats.demandIndex}\n unit=\"/ 100\"\n icon={<Gauge className=\"h-5 w-5 text-status-success-text\" />}\n />\n </div>\n </EmphasisPanel>\n\n {/* Charts — trend (toggle) alongside demand-vs-supply, like the concept */}\n <div className=\"grid gap-6 xl:grid-cols-3\">\n <ChartCard\n className=\"xl:col-span-2\"\n title=\"Market Trends\"\n description=\"Monthly price, rent, and demand trends\"\n actions={\n <div className=\"flex gap-1\">\n {(['price', 'rent', 'demand'] as const).map((t) => (\n <button\n key={t}\n onClick={() => setChartType(t)}\n aria-pressed={chartType === t}\n className={cn(\n 'rounded-md px-2.5 py-1 text-xs font-medium transition-colors capitalize',\n chartType === t\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'text-text-secondary hover:bg-bg-sunken'\n )}\n >\n {t}\n </button>\n ))}\n </div>\n }\n >\n <div className=\"h-72\">\n {chartData.length === 0 ? (\n <div className=\"flex h-full items-center justify-center text-sm text-text-secondary\">\n No trend data yet.\n </div>\n ) : (\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n {chartType === 'price' ? (\n <AreaChart data={chartData}>\n <defs>\n <linearGradient id=\"priceGrad\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"5%\" stopColor={CHART.price} stopOpacity={0.3} />\n <stop offset=\"95%\" stopColor={CHART.price} stopOpacity={0} />\n </linearGradient>\n </defs>\n <CartesianGrid strokeDasharray=\"3 3\" className=\"stroke-border\" />\n <XAxis\n dataKey=\"monthLabel\"\n tick={{ fontSize: 11, fill: 'currentColor' }}\n className=\"text-text-secondary\"\n />\n <YAxis\n tick={{ fontSize: 11, fill: 'currentColor' }}\n tickFormatter={(v) => formatINRCompact(v)}\n width={64}\n className=\"text-text-secondary\"\n />\n <Tooltip\n contentStyle={TOOLTIP_CONTENT_STYLE}\n labelStyle={TOOLTIP_LABEL_STYLE}\n formatter={(value: number) => [formatCurrency(value), 'Avg Price']}\n />\n <Area\n type=\"monotone\"\n dataKey=\"avgPrice\"\n stroke={CHART.price}\n fill=\"url(#priceGrad)\"\n strokeWidth={2}\n />\n </AreaChart>\n ) : chartType === 'rent' ? (\n <LineChart data={chartData}>\n <CartesianGrid strokeDasharray=\"3 3\" className=\"stroke-border\" />\n <XAxis\n dataKey=\"monthLabel\"\n tick={{ fontSize: 11, fill: 'currentColor' }}\n className=\"text-text-secondary\"\n />\n <YAxis\n tick={{ fontSize: 11, fill: 'currentColor' }}\n tickFormatter={(v) => formatINRCompact(v)}\n width={64}\n className=\"text-text-secondary\"\n />\n <Tooltip\n contentStyle={TOOLTIP_CONTENT_STYLE}\n labelStyle={TOOLTIP_LABEL_STYLE}\n formatter={(value: number) => [formatCurrency(value), 'Avg Rent']}\n />\n <Line type=\"monotone\" dataKey=\"avgRent\" stroke={CHART.rent} strokeWidth={2} dot={{ r: 3 }} />\n </LineChart>\n ) : (\n <LineChart data={chartData}>\n <CartesianGrid strokeDasharray=\"3 3\" className=\"stroke-border\" />\n <XAxis\n dataKey=\"monthLabel\"\n tick={{ fontSize: 11, fill: 'currentColor' }}\n className=\"text-text-secondary\"\n />\n <YAxis tick={{ fontSize: 11, fill: 'currentColor' }} className=\"text-text-secondary\" />\n <Tooltip contentStyle={TOOLTIP_CONTENT_STYLE} labelStyle={TOOLTIP_LABEL_STYLE} />\n <Legend wrapperStyle={{ fontSize: '0.75rem' }} />\n <Line\n type=\"monotone\"\n dataKey=\"demandIndex\"\n stroke={CHART.demand}\n name=\"Demand\"\n strokeWidth={2}\n dot={{ r: 3 }}\n />\n <Line\n type=\"monotone\"\n dataKey=\"supplyIndex\"\n stroke={CHART.supply}\n name=\"Supply\"\n strokeWidth={2}\n dot={{ r: 3 }}\n />\n </LineChart>\n )}\n </ResponsiveContainer>\n )}\n </div>\n </ChartCard>\n\n <ChartCard title=\"Demand vs Supply\" description=\"Monthly demand & supply index\">\n <div className=\"h-72\">\n {chartData.length === 0 ? (\n <div className=\"flex h-full items-center justify-center text-sm text-text-secondary\">No data yet.</div>\n ) : (\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <BarChart data={chartData} barGap={2}>\n <CartesianGrid strokeDasharray=\"3 3\" className=\"stroke-border\" vertical={false} />\n <XAxis\n dataKey=\"monthLabel\"\n tick={{ fontSize: 11, fill: 'currentColor' }}\n className=\"text-text-secondary\"\n />\n <YAxis tick={{ fontSize: 11, fill: 'currentColor' }} className=\"text-text-secondary\" />\n <Tooltip\n contentStyle={TOOLTIP_CONTENT_STYLE}\n labelStyle={TOOLTIP_LABEL_STYLE}\n cursor={{ fill: 'var(--color-bg-sunken)', opacity: 0.4 }}\n />\n <Legend wrapperStyle={{ fontSize: '0.75rem' }} />\n <Bar dataKey=\"demandIndex\" name=\"Demand\" fill={CHART.demand} radius={[3, 3, 0, 0]} />\n <Bar dataKey=\"supplyIndex\" name=\"Supply\" fill={CHART.supply} radius={[3, 3, 0, 0]} />\n </BarChart>\n </ResponsiveContainer>\n )}\n </div>\n </ChartCard>\n </div>\n\n {/* Market Comparison Table */}\n <div id=\"market-monthly-comparison\" className=\"scroll-mt-6\">\n <h2 className=\"mb-3 text-lg font-semibold text-text-primary\">Monthly Comparison</h2>\n <DataTable\n columns={columns}\n data={chartData}\n keyField=\"month\"\n serverPagination={{\n page: pagination.page,\n pageSize: pagination.pageSize,\n hasNextPage: trends.length >= pagination.pageSize,\n loading,\n onPageChange: pagination.setPage,\n onPageSizeChange: pagination.setPageSize,\n }}\n />\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MarketplacePage.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/marketplace/MarketplacePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MarketplacePage.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/marketplace/MarketplacePage.tsx"],"names":[],"mappings":"AAqKA,wBAAgB,eAAe,gCA0H9B"}
|
|
@@ -3,16 +3,17 @@ import { PageLayout as t } from "../../components/PageLayout.js";
|
|
|
3
3
|
import { EmptyState as n } from "../../components/EmptyState.js";
|
|
4
4
|
import { FilterBar as r } from "../../components/FilterBar.js";
|
|
5
5
|
import { LoadingSkeleton as i } from "../../components/LoadingSkeleton.js";
|
|
6
|
-
import {
|
|
6
|
+
import { useTrackPageView as a } from "../../hooks/useHousingVistaAnalytics.js";
|
|
7
|
+
import { useMarketplaceServices as o } from "../../hooks/index.js";
|
|
7
8
|
import "../../components/index.js";
|
|
8
|
-
import { useServerPagination as
|
|
9
|
-
import { useEffect as
|
|
10
|
-
import { CheckCircle as
|
|
11
|
-
import { useNavigate as
|
|
12
|
-
import { Fragment as
|
|
13
|
-
import { EmphasisPanel as
|
|
9
|
+
import { useServerPagination as s } from "../../hooks/useServerPagination.js";
|
|
10
|
+
import { useEffect as c, useMemo as l, useState as u } from "react";
|
|
11
|
+
import { CheckCircle as d, Clock as f, MapPin as p, MessageCircle as m, Paintbrush as h, Phone as g, Shield as _, ShieldCheck as v, SprayCanIcon as y, Star as b, Trees as x, Wrench as S, Zap as C } from "lucide-react";
|
|
12
|
+
import { useNavigate as w } from "react-router-dom";
|
|
13
|
+
import { Fragment as T, jsx as E, jsxs as D } from "react/jsx-runtime";
|
|
14
|
+
import { EmphasisPanel as O, NextSteps as k, Pagination as A } from "@burdenoff/fe-libs/ui";
|
|
14
15
|
//#region src/housingvista/pages/marketplace/MarketplacePage.tsx
|
|
15
|
-
var
|
|
16
|
+
var j = 9, M = [
|
|
16
17
|
{
|
|
17
18
|
value: "all",
|
|
18
19
|
label: "All"
|
|
@@ -42,74 +43,74 @@ var A = 9, j = [
|
|
|
42
43
|
label: "Landscaping"
|
|
43
44
|
}
|
|
44
45
|
];
|
|
45
|
-
function
|
|
46
|
+
function N(e) {
|
|
46
47
|
switch (e) {
|
|
47
|
-
case "repair": return /* @__PURE__ */
|
|
48
|
-
case "maintenance": return /* @__PURE__ */
|
|
49
|
-
case "cleaning": return /* @__PURE__ */
|
|
50
|
-
case "renovation": return /* @__PURE__ */
|
|
51
|
-
case "security": return /* @__PURE__ */
|
|
52
|
-
case "landscaping": return /* @__PURE__ */
|
|
53
|
-
case "pest_control": return /* @__PURE__ */
|
|
54
|
-
default: return /* @__PURE__ */
|
|
48
|
+
case "repair": return /* @__PURE__ */ E(S, { className: "h-4 w-4" });
|
|
49
|
+
case "maintenance": return /* @__PURE__ */ E(C, { className: "h-4 w-4" });
|
|
50
|
+
case "cleaning": return /* @__PURE__ */ E(y, { className: "h-4 w-4" });
|
|
51
|
+
case "renovation": return /* @__PURE__ */ E(h, { className: "h-4 w-4" });
|
|
52
|
+
case "security": return /* @__PURE__ */ E(_, { className: "h-4 w-4" });
|
|
53
|
+
case "landscaping": return /* @__PURE__ */ E(x, { className: "h-4 w-4" });
|
|
54
|
+
case "pest_control": return /* @__PURE__ */ E(v, { className: "h-4 w-4" });
|
|
55
|
+
default: return /* @__PURE__ */ E(S, { className: "h-4 w-4" });
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
|
-
function
|
|
58
|
-
return /* @__PURE__ */
|
|
58
|
+
function P({ rating: t }) {
|
|
59
|
+
return /* @__PURE__ */ E("div", {
|
|
59
60
|
className: "flex items-center gap-0.5 text-status-warning-text",
|
|
60
|
-
children: Array.from({ length: 5 }).map((n, r) => /* @__PURE__ */
|
|
61
|
+
children: Array.from({ length: 5 }).map((n, r) => /* @__PURE__ */ E(b, { className: e("h-3.5 w-3.5", r < Math.floor(t) ? "fill-current" : r < t ? "fill-current opacity-50" : "fill-none text-text-secondary/30") }, r))
|
|
61
62
|
});
|
|
62
63
|
}
|
|
63
|
-
function
|
|
64
|
-
let t =
|
|
65
|
-
return /* @__PURE__ */
|
|
64
|
+
function F({ service: e }) {
|
|
65
|
+
let t = w();
|
|
66
|
+
return /* @__PURE__ */ D("div", {
|
|
66
67
|
className: "group flex flex-col overflow-hidden rounded-card border border-border-seam bg-bg-surface shadow-elevation-1 transition-[box-shadow,border-color] hover:border-border-strong hover:shadow-elevation-2",
|
|
67
|
-
children: [/* @__PURE__ */
|
|
68
|
+
children: [/* @__PURE__ */ D("div", {
|
|
68
69
|
className: "relative aspect-[16/10] overflow-hidden bg-bg-sunken",
|
|
69
70
|
children: [
|
|
70
|
-
e.imageUrl ? /* @__PURE__ */
|
|
71
|
+
e.imageUrl ? /* @__PURE__ */ E("img", {
|
|
71
72
|
src: e.imageUrl,
|
|
72
73
|
alt: e.name,
|
|
73
74
|
loading: "lazy",
|
|
74
75
|
className: "h-full w-full object-cover"
|
|
75
|
-
}) : /* @__PURE__ */
|
|
76
|
+
}) : /* @__PURE__ */ E("div", {
|
|
76
77
|
className: "flex h-full w-full items-center justify-center bg-gradient-to-br from-accent-blue-subtle to-status-success-bg-subtle text-text-secondary/50",
|
|
77
|
-
children: /* @__PURE__ */
|
|
78
|
+
children: /* @__PURE__ */ E("span", {
|
|
78
79
|
className: "[&>svg]:h-10 [&>svg]:w-10",
|
|
79
|
-
children:
|
|
80
|
+
children: N(e.category)
|
|
80
81
|
})
|
|
81
82
|
}),
|
|
82
|
-
/* @__PURE__ */
|
|
83
|
+
/* @__PURE__ */ D("span", {
|
|
83
84
|
className: "absolute left-3 top-3 inline-flex items-center gap-1 rounded-full bg-bg-surface/90 px-2 py-0.5 text-xs font-medium capitalize text-text-secondary shadow-elevation-1 backdrop-blur",
|
|
84
|
-
children: [
|
|
85
|
+
children: [N(e.category), e.category.replace(/_/g, " ")]
|
|
85
86
|
}),
|
|
86
|
-
e.featured && /* @__PURE__ */
|
|
87
|
+
e.featured && /* @__PURE__ */ E("span", {
|
|
87
88
|
className: "absolute right-3 top-3 rounded-full bg-status-warning-text px-2 py-0.5 text-xs font-semibold text-text-inverse shadow-elevation-1",
|
|
88
89
|
children: "Featured"
|
|
89
90
|
})
|
|
90
91
|
]
|
|
91
|
-
}), /* @__PURE__ */
|
|
92
|
+
}), /* @__PURE__ */ D("div", {
|
|
92
93
|
className: "flex flex-1 flex-col p-5",
|
|
93
94
|
children: [
|
|
94
|
-
/* @__PURE__ */
|
|
95
|
+
/* @__PURE__ */ D("div", {
|
|
95
96
|
className: "mb-2",
|
|
96
|
-
children: [/* @__PURE__ */
|
|
97
|
+
children: [/* @__PURE__ */ E("h3", {
|
|
97
98
|
className: "truncate text-sm font-semibold text-text-primary",
|
|
98
99
|
children: e.name
|
|
99
|
-
}), /* @__PURE__ */
|
|
100
|
+
}), /* @__PURE__ */ E("p", {
|
|
100
101
|
className: "text-xs text-text-secondary",
|
|
101
102
|
children: e.providerName
|
|
102
103
|
})]
|
|
103
104
|
}),
|
|
104
|
-
/* @__PURE__ */
|
|
105
|
+
/* @__PURE__ */ D("div", {
|
|
105
106
|
className: "mb-3 flex items-center gap-2",
|
|
106
107
|
children: [
|
|
107
|
-
/* @__PURE__ */
|
|
108
|
-
/* @__PURE__ */
|
|
108
|
+
/* @__PURE__ */ E(P, { rating: e.rating }),
|
|
109
|
+
/* @__PURE__ */ E("span", {
|
|
109
110
|
className: "text-xs font-medium text-text-primary",
|
|
110
111
|
children: e.rating
|
|
111
112
|
}),
|
|
112
|
-
/* @__PURE__ */
|
|
113
|
+
/* @__PURE__ */ D("span", {
|
|
113
114
|
className: "text-xs text-text-secondary",
|
|
114
115
|
children: [
|
|
115
116
|
"(",
|
|
@@ -119,136 +120,137 @@ function P({ service: e }) {
|
|
|
119
120
|
})
|
|
120
121
|
]
|
|
121
122
|
}),
|
|
122
|
-
/* @__PURE__ */
|
|
123
|
+
/* @__PURE__ */ E("p", {
|
|
123
124
|
className: "mb-3 line-clamp-2 text-xs text-text-secondary",
|
|
124
125
|
children: e.description
|
|
125
126
|
}),
|
|
126
|
-
/* @__PURE__ */
|
|
127
|
+
/* @__PURE__ */ D("div", {
|
|
127
128
|
className: "mb-3 flex items-center justify-between text-xs",
|
|
128
|
-
children: [/* @__PURE__ */
|
|
129
|
+
children: [/* @__PURE__ */ E("span", {
|
|
129
130
|
className: "font-semibold text-text-primary",
|
|
130
131
|
children: e.priceRange
|
|
131
|
-
}), /* @__PURE__ */
|
|
132
|
+
}), /* @__PURE__ */ D("span", {
|
|
132
133
|
className: "flex items-center gap-1 text-text-secondary",
|
|
133
|
-
children: [/* @__PURE__ */
|
|
134
|
+
children: [/* @__PURE__ */ E(f, { className: "h-3 w-3" }), e.responseTime]
|
|
134
135
|
})]
|
|
135
136
|
}),
|
|
136
|
-
/* @__PURE__ */
|
|
137
|
+
/* @__PURE__ */ D("div", {
|
|
137
138
|
className: "mb-4 flex flex-wrap items-center gap-2",
|
|
138
|
-
children: [e.verified && /* @__PURE__ */
|
|
139
|
+
children: [e.verified && /* @__PURE__ */ D("span", {
|
|
139
140
|
className: "inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2 py-0.5 text-xs font-medium text-status-success-text",
|
|
140
|
-
children: [/* @__PURE__ */
|
|
141
|
-
}), /* @__PURE__ */
|
|
141
|
+
children: [/* @__PURE__ */ E(d, { className: "h-3 w-3" }), "Verified"]
|
|
142
|
+
}), /* @__PURE__ */ D("span", {
|
|
142
143
|
className: "inline-flex items-center gap-1 rounded-full bg-status-info-bg-subtle px-2 py-0.5 text-xs text-status-info-text",
|
|
143
144
|
children: [
|
|
144
|
-
/* @__PURE__ */
|
|
145
|
+
/* @__PURE__ */ E(p, { className: "h-3 w-3" }),
|
|
145
146
|
e.availableAreas.length,
|
|
146
147
|
" area",
|
|
147
148
|
e.availableAreas.length === 1 ? "" : "s"
|
|
148
149
|
]
|
|
149
150
|
})]
|
|
150
151
|
}),
|
|
151
|
-
/* @__PURE__ */
|
|
152
|
+
/* @__PURE__ */ D("div", {
|
|
152
153
|
className: "mt-auto flex items-center gap-2",
|
|
153
|
-
children: [/* @__PURE__ */
|
|
154
|
+
children: [/* @__PURE__ */ D("button", {
|
|
154
155
|
onClick: () => t(`../marketplace/${e.id}`),
|
|
155
156
|
className: "flex-1 rounded-md bg-action-primary-bg px-3 py-2 text-xs font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg-hover",
|
|
156
|
-
children: [/* @__PURE__ */
|
|
157
|
-
}), /* @__PURE__ */
|
|
157
|
+
children: [/* @__PURE__ */ E(g, { className: "mr-1 inline h-3 w-3" }), "Contact"]
|
|
158
|
+
}), /* @__PURE__ */ E("button", {
|
|
158
159
|
"aria-label": `Message ${e.providerName}`,
|
|
159
160
|
onClick: () => t(`../marketplace/${e.id}`),
|
|
160
161
|
className: "rounded-md border border-border-subtle px-3 py-2 text-xs font-medium text-text-primary transition-colors hover:bg-bg-sunken",
|
|
161
|
-
children: /* @__PURE__ */
|
|
162
|
+
children: /* @__PURE__ */ E(m, { className: "h-3 w-3" })
|
|
162
163
|
})]
|
|
163
164
|
})
|
|
164
165
|
]
|
|
165
166
|
})]
|
|
166
167
|
});
|
|
167
168
|
}
|
|
168
|
-
function
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
169
|
+
function I() {
|
|
170
|
+
a("housingvista.marketplace.viewed");
|
|
171
|
+
let [d, f] = u(""), [p, m] = u(""), [h, g] = u("all"), _ = s(j), { setPage: v } = _;
|
|
172
|
+
c(() => {
|
|
173
|
+
let e = setTimeout(() => m(d), 300);
|
|
172
174
|
return () => clearTimeout(e);
|
|
173
|
-
}, [
|
|
174
|
-
let
|
|
175
|
+
}, [d]);
|
|
176
|
+
let y = l(() => {
|
|
175
177
|
let e = {};
|
|
176
|
-
return
|
|
177
|
-
}, [
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}, [
|
|
181
|
-
let { data:
|
|
182
|
-
filter:
|
|
183
|
-
pagination:
|
|
184
|
-
}),
|
|
185
|
-
return /* @__PURE__ */
|
|
178
|
+
return p.trim() && (e.search = p.trim()), h !== "all" && (e.category = h), Object.keys(e).length > 0 ? e : void 0;
|
|
179
|
+
}, [p, h]);
|
|
180
|
+
c(() => {
|
|
181
|
+
v(1);
|
|
182
|
+
}, [y, v]);
|
|
183
|
+
let { data: b, loading: x } = o({
|
|
184
|
+
filter: y,
|
|
185
|
+
pagination: _.pagination
|
|
186
|
+
}), S = b.length >= _.pageSize;
|
|
187
|
+
return /* @__PURE__ */ D(t, {
|
|
186
188
|
title: "Service Marketplace",
|
|
187
189
|
description: "Find trusted service providers for your property needs",
|
|
188
190
|
purpose: "Hire vetted professionals — plumbers, electricians, cleaners, painters, security, and landscapers — to keep your properties in top shape. Browse by category, compare ratings and response times, then contact or message a provider directly.",
|
|
189
191
|
children: [
|
|
190
|
-
/* @__PURE__ */
|
|
192
|
+
/* @__PURE__ */ E(O, {
|
|
191
193
|
className: "flex flex-wrap items-center gap-2 p-3",
|
|
192
|
-
children:
|
|
193
|
-
onClick: () =>
|
|
194
|
-
className: e("rounded-full px-4 py-1.5 text-sm font-medium transition-colors",
|
|
194
|
+
children: M.map((t) => /* @__PURE__ */ E("button", {
|
|
195
|
+
onClick: () => g(t.value),
|
|
196
|
+
className: e("rounded-full px-4 py-1.5 text-sm font-medium transition-colors", h === t.value ? "bg-action-primary-bg text-action-primary-text" : "border border-border-subtle bg-bg-surface text-text-secondary hover:bg-bg-sunken hover:text-text-primary"),
|
|
195
197
|
children: t.label
|
|
196
198
|
}, t.value))
|
|
197
199
|
}),
|
|
198
|
-
/* @__PURE__ */
|
|
199
|
-
searchValue:
|
|
200
|
-
onSearchChange:
|
|
200
|
+
/* @__PURE__ */ E(r, {
|
|
201
|
+
searchValue: d,
|
|
202
|
+
onSearchChange: f,
|
|
201
203
|
searchPlaceholder: "Search services, providers..."
|
|
202
204
|
}),
|
|
203
|
-
/* @__PURE__ */
|
|
205
|
+
/* @__PURE__ */ E(k, {
|
|
204
206
|
storageKey: "housingvista-marketplace",
|
|
205
207
|
steps: [
|
|
206
208
|
{
|
|
207
209
|
id: "book-repair",
|
|
208
210
|
label: "Fix something now",
|
|
209
211
|
description: "Find a plumber or repair pro for an urgent issue.",
|
|
210
|
-
onClick: () =>
|
|
212
|
+
onClick: () => g("repair")
|
|
211
213
|
},
|
|
212
214
|
{
|
|
213
215
|
id: "book-cleaning",
|
|
214
216
|
label: "Schedule a cleaning",
|
|
215
217
|
description: "Get a property move-in or turnover ready.",
|
|
216
|
-
onClick: () =>
|
|
218
|
+
onClick: () => g("cleaning")
|
|
217
219
|
},
|
|
218
220
|
{
|
|
219
221
|
id: "see-featured",
|
|
220
222
|
label: "Browse all providers",
|
|
221
223
|
description: "Clear filters to compare every vetted professional.",
|
|
222
224
|
onClick: () => {
|
|
223
|
-
|
|
225
|
+
g("all"), f("");
|
|
224
226
|
}
|
|
225
227
|
}
|
|
226
228
|
]
|
|
227
229
|
}),
|
|
228
|
-
/* @__PURE__ */
|
|
230
|
+
/* @__PURE__ */ D("p", {
|
|
229
231
|
className: "text-sm text-text-secondary",
|
|
230
232
|
children: [
|
|
231
|
-
|
|
233
|
+
b.length,
|
|
232
234
|
" service",
|
|
233
|
-
|
|
235
|
+
b.length === 1 ? "" : "s",
|
|
234
236
|
" shown",
|
|
235
|
-
|
|
237
|
+
_.page > 1 ? ` · page ${_.page}` : ""
|
|
236
238
|
]
|
|
237
239
|
}),
|
|
238
|
-
|
|
240
|
+
x ? /* @__PURE__ */ E(i, {
|
|
239
241
|
variant: "card",
|
|
240
242
|
count: 6
|
|
241
|
-
}) :
|
|
243
|
+
}) : b.length > 0 ? /* @__PURE__ */ D(T, { children: [/* @__PURE__ */ E("div", {
|
|
242
244
|
className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
243
|
-
children:
|
|
244
|
-
}), /* @__PURE__ */
|
|
245
|
-
page:
|
|
246
|
-
pageSize:
|
|
247
|
-
hasNextPage:
|
|
248
|
-
hasPreviousPage:
|
|
249
|
-
onPageChange:
|
|
250
|
-
onPageSizeChange:
|
|
251
|
-
})] }) : /* @__PURE__ */
|
|
245
|
+
children: b.map((e) => /* @__PURE__ */ E(F, { service: e }, e.id))
|
|
246
|
+
}), /* @__PURE__ */ E(A, {
|
|
247
|
+
page: _.page,
|
|
248
|
+
pageSize: _.pageSize,
|
|
249
|
+
hasNextPage: S,
|
|
250
|
+
hasPreviousPage: _.page > 1,
|
|
251
|
+
onPageChange: _.setPage,
|
|
252
|
+
onPageSizeChange: _.setPageSize
|
|
253
|
+
})] }) : /* @__PURE__ */ E(n, {
|
|
252
254
|
illustration: "empty-search",
|
|
253
255
|
title: "No services found",
|
|
254
256
|
description: "Try adjusting your search or category filter to find what you need."
|
|
@@ -257,6 +259,6 @@ function F() {
|
|
|
257
259
|
});
|
|
258
260
|
}
|
|
259
261
|
//#endregion
|
|
260
|
-
export {
|
|
262
|
+
export { I as MarketplacePage };
|
|
261
263
|
|
|
262
264
|
//# sourceMappingURL=MarketplacePage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MarketplacePage.js","names":[],"sources":["../../../../src/housingvista/pages/marketplace/MarketplacePage.tsx"],"sourcesContent":["import { useState, useEffect, useMemo } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n Star,\n Phone,\n MapPin,\n Shield,\n Wrench,\n Zap,\n SprayCanIcon,\n Paintbrush,\n ShieldCheck,\n Trees,\n CheckCircle,\n Clock,\n MessageCircle,\n} from 'lucide-react';\nimport { EmphasisPanel, NextSteps, Pagination } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, FilterBar, EmptyState, LoadingSkeleton } from '../../components';\nimport { useMarketplaceServices, type MarketplaceServiceFilterInput } from '../../hooks';\nimport { useServerPagination } from '../../hooks/useServerPagination';\nimport { cn } from '../../utils/helpers';\nimport type { MarketplaceService } from '../../types';\n\nconst PAGE_SIZE = 9;\n\nconst categories = [\n { value: 'all', label: 'All' },\n { value: 'repair', label: 'Plumbing' },\n { value: 'maintenance', label: 'Electrical' },\n { value: 'cleaning', label: 'Cleaning' },\n { value: 'renovation', label: 'Painting' },\n { value: 'security', label: 'Security' },\n { value: 'landscaping', label: 'Landscaping' },\n] as const;\n\nfunction getCategoryIcon(category: string) {\n switch (category) {\n case 'repair':\n return <Wrench className=\"h-4 w-4\" />;\n case 'maintenance':\n return <Zap className=\"h-4 w-4\" />;\n case 'cleaning':\n return <SprayCanIcon className=\"h-4 w-4\" />;\n case 'renovation':\n return <Paintbrush className=\"h-4 w-4\" />;\n case 'security':\n return <Shield className=\"h-4 w-4\" />;\n case 'landscaping':\n return <Trees className=\"h-4 w-4\" />;\n case 'pest_control':\n return <ShieldCheck className=\"h-4 w-4\" />;\n default:\n return <Wrench className=\"h-4 w-4\" />;\n }\n}\n\nfunction RatingStars({ rating }: { rating: number }) {\n return (\n <div className=\"flex items-center gap-0.5 text-status-warning-text\">\n {Array.from({ length: 5 }).map((_, i) => (\n <Star\n key={i}\n className={cn(\n 'h-3.5 w-3.5',\n i < Math.floor(rating)\n ? 'fill-current'\n : i < rating\n ? 'fill-current opacity-50'\n : 'fill-none text-text-secondary/30'\n )}\n />\n ))}\n </div>\n );\n}\n\nfunction ServiceCard({ service }: { service: MarketplaceService }) {\n const navigate = useNavigate();\n\n return (\n <div className=\"group flex flex-col overflow-hidden rounded-card border border-border-seam bg-bg-surface shadow-elevation-1 transition-[box-shadow,border-color] hover:border-border-strong hover:shadow-elevation-2\">\n {/* Image / branded placeholder + badges */}\n <div className=\"relative aspect-[16/10] overflow-hidden bg-bg-sunken\">\n {service.imageUrl ? (\n <img src={service.imageUrl} alt={service.name} loading=\"lazy\" className=\"h-full w-full object-cover\" />\n ) : (\n <div className=\"flex h-full w-full items-center justify-center bg-gradient-to-br from-accent-blue-subtle to-status-success-bg-subtle text-text-secondary/50\">\n <span className=\"[&>svg]:h-10 [&>svg]:w-10\">{getCategoryIcon(service.category)}</span>\n </div>\n )}\n <span className=\"absolute left-3 top-3 inline-flex items-center gap-1 rounded-full bg-bg-surface/90 px-2 py-0.5 text-xs font-medium capitalize text-text-secondary shadow-elevation-1 backdrop-blur\">\n {getCategoryIcon(service.category)}\n {service.category.replace(/_/g, ' ')}\n </span>\n {service.featured && (\n <span className=\"absolute right-3 top-3 rounded-full bg-status-warning-text px-2 py-0.5 text-xs font-semibold text-text-inverse shadow-elevation-1\">\n Featured\n </span>\n )}\n </div>\n\n <div className=\"flex flex-1 flex-col p-5\">\n {/* Header */}\n <div className=\"mb-2\">\n <h3 className=\"truncate text-sm font-semibold text-text-primary\">{service.name}</h3>\n <p className=\"text-xs text-text-secondary\">{service.providerName}</p>\n </div>\n\n {/* Rating */}\n <div className=\"mb-3 flex items-center gap-2\">\n <RatingStars rating={service.rating} />\n <span className=\"text-xs font-medium text-text-primary\">{service.rating}</span>\n <span className=\"text-xs text-text-secondary\">({service.reviewCount} reviews)</span>\n </div>\n\n {/* Description */}\n <p className=\"mb-3 line-clamp-2 text-xs text-text-secondary\">{service.description}</p>\n\n {/* Price + Response Time */}\n <div className=\"mb-3 flex items-center justify-between text-xs\">\n <span className=\"font-semibold text-text-primary\">{service.priceRange}</span>\n <span className=\"flex items-center gap-1 text-text-secondary\">\n <Clock className=\"h-3 w-3\" />\n {service.responseTime}\n </span>\n </div>\n\n {/* Availability + Verified */}\n <div className=\"mb-4 flex flex-wrap items-center gap-2\">\n {service.verified && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2 py-0.5 text-xs font-medium text-status-success-text\">\n <CheckCircle className=\"h-3 w-3\" />\n Verified\n </span>\n )}\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-info-bg-subtle px-2 py-0.5 text-xs text-status-info-text\">\n <MapPin className=\"h-3 w-3\" />\n {service.availableAreas.length} area{service.availableAreas.length !== 1 ? 's' : ''}\n </span>\n </div>\n\n {/* Actions */}\n <div className=\"mt-auto flex items-center gap-2\">\n <button\n onClick={() => navigate(`../marketplace/${service.id}`)}\n className=\"flex-1 rounded-md bg-action-primary-bg px-3 py-2 text-xs font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg-hover\"\n >\n <Phone className=\"mr-1 inline h-3 w-3\" />\n Contact\n </button>\n <button\n aria-label={`Message ${service.providerName}`}\n onClick={() => navigate(`../marketplace/${service.id}`)}\n className=\"rounded-md border border-border-subtle px-3 py-2 text-xs font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <MessageCircle className=\"h-3 w-3\" />\n </button>\n </div>\n </div>\n </div>\n );\n}\n\nexport function MarketplacePage() {\n const [search, setSearch] = useState('');\n const [debouncedSearch, setDebouncedSearch] = useState('');\n const [activeCategory, setActiveCategory] = useState<string>('all');\n const pagination = useServerPagination(PAGE_SIZE);\n const { setPage } = pagination;\n\n // Debounce the search box so we don't refetch on every keystroke.\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedSearch(search), 300);\n return () => clearTimeout(timer);\n }, [search]);\n\n // Server-side filter — category + search are resolved by the subgraph.\n const serverFilter = useMemo<MarketplaceServiceFilterInput | undefined>(() => {\n const filter: MarketplaceServiceFilterInput = {};\n if (debouncedSearch.trim()) filter.search = debouncedSearch.trim();\n if (activeCategory !== 'all') filter.category = activeCategory;\n return Object.keys(filter).length > 0 ? filter : undefined;\n }, [debouncedSearch, activeCategory]);\n\n // Reset to the first page whenever the server-side filter changes.\n useEffect(() => {\n setPage(1);\n }, [serverFilter, setPage]);\n\n const { data: services, loading } = useMarketplaceServices({\n filter: serverFilter,\n pagination: pagination.pagination,\n });\n\n const hasNextPage = services.length >= pagination.pageSize;\n\n return (\n <PageLayout\n title=\"Service Marketplace\"\n description=\"Find trusted service providers for your property needs\"\n purpose=\"Hire vetted professionals — plumbers, electricians, cleaners, painters, security, and landscapers — to keep your properties in top shape. Browse by category, compare ratings and response times, then contact or message a provider directly.\"\n >\n {/* Category Tabs — primary control zone (one EmphasisPanel per page) */}\n <EmphasisPanel className=\"flex flex-wrap items-center gap-2 p-3\">\n {categories.map((cat) => (\n <button\n key={cat.value}\n onClick={() => setActiveCategory(cat.value)}\n className={cn(\n 'rounded-full px-4 py-1.5 text-sm font-medium transition-colors',\n activeCategory === cat.value\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'border border-border-subtle bg-bg-surface text-text-secondary hover:bg-bg-sunken hover:text-text-primary'\n )}\n >\n {cat.label}\n </button>\n ))}\n </EmphasisPanel>\n\n {/* Search */}\n <FilterBar searchValue={search} onSearchChange={setSearch} searchPlaceholder=\"Search services, providers...\" />\n\n <NextSteps\n storageKey=\"housingvista-marketplace\"\n steps={[\n {\n id: 'book-repair',\n label: 'Fix something now',\n description: 'Find a plumber or repair pro for an urgent issue.',\n onClick: () => setActiveCategory('repair'),\n },\n {\n id: 'book-cleaning',\n label: 'Schedule a cleaning',\n description: 'Get a property move-in or turnover ready.',\n onClick: () => setActiveCategory('cleaning'),\n },\n {\n id: 'see-featured',\n label: 'Browse all providers',\n description: 'Clear filters to compare every vetted professional.',\n onClick: () => {\n setActiveCategory('all');\n setSearch('');\n },\n },\n ]}\n />\n\n {/* Results Count */}\n <p className=\"text-sm text-text-secondary\">\n {services.length} service{services.length !== 1 ? 's' : ''} shown\n {pagination.page > 1 ? ` · page ${pagination.page}` : ''}\n </p>\n\n {/* Service Grid */}\n {loading ? (\n <LoadingSkeleton variant=\"card\" count={6} />\n ) : services.length > 0 ? (\n <>\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {services.map((service) => (\n <ServiceCard key={service.id} service={service} />\n ))}\n </div>\n <Pagination\n page={pagination.page}\n pageSize={pagination.pageSize}\n hasNextPage={hasNextPage}\n hasPreviousPage={pagination.page > 1}\n onPageChange={pagination.setPage}\n onPageSizeChange={pagination.setPageSize}\n />\n </>\n ) : (\n <EmptyState\n illustration=\"empty-search\"\n title=\"No services found\"\n description=\"Try adjusting your search or category filter to find what you need.\"\n />\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MarketplacePage.js","names":[],"sources":["../../../../src/housingvista/pages/marketplace/MarketplacePage.tsx"],"sourcesContent":["import { useState, useEffect, useMemo } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n Star,\n Phone,\n MapPin,\n Shield,\n Wrench,\n Zap,\n SprayCanIcon,\n Paintbrush,\n ShieldCheck,\n Trees,\n CheckCircle,\n Clock,\n MessageCircle,\n} from 'lucide-react';\nimport { EmphasisPanel, NextSteps, Pagination } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, FilterBar, EmptyState, LoadingSkeleton } from '../../components';\nimport { useMarketplaceServices, type MarketplaceServiceFilterInput } from '../../hooks';\nimport { useServerPagination } from '../../hooks/useServerPagination';\nimport { cn } from '../../utils/helpers';\nimport type { MarketplaceService } from '../../types';\nimport { useTrackPageView } from '../../hooks/useHousingVistaAnalytics';\n\nconst PAGE_SIZE = 9;\n\nconst categories = [\n { value: 'all', label: 'All' },\n { value: 'repair', label: 'Plumbing' },\n { value: 'maintenance', label: 'Electrical' },\n { value: 'cleaning', label: 'Cleaning' },\n { value: 'renovation', label: 'Painting' },\n { value: 'security', label: 'Security' },\n { value: 'landscaping', label: 'Landscaping' },\n] as const;\n\nfunction getCategoryIcon(category: string) {\n switch (category) {\n case 'repair':\n return <Wrench className=\"h-4 w-4\" />;\n case 'maintenance':\n return <Zap className=\"h-4 w-4\" />;\n case 'cleaning':\n return <SprayCanIcon className=\"h-4 w-4\" />;\n case 'renovation':\n return <Paintbrush className=\"h-4 w-4\" />;\n case 'security':\n return <Shield className=\"h-4 w-4\" />;\n case 'landscaping':\n return <Trees className=\"h-4 w-4\" />;\n case 'pest_control':\n return <ShieldCheck className=\"h-4 w-4\" />;\n default:\n return <Wrench className=\"h-4 w-4\" />;\n }\n}\n\nfunction RatingStars({ rating }: { rating: number }) {\n return (\n <div className=\"flex items-center gap-0.5 text-status-warning-text\">\n {Array.from({ length: 5 }).map((_, i) => (\n <Star\n key={i}\n className={cn(\n 'h-3.5 w-3.5',\n i < Math.floor(rating)\n ? 'fill-current'\n : i < rating\n ? 'fill-current opacity-50'\n : 'fill-none text-text-secondary/30'\n )}\n />\n ))}\n </div>\n );\n}\n\nfunction ServiceCard({ service }: { service: MarketplaceService }) {\n const navigate = useNavigate();\n\n return (\n <div className=\"group flex flex-col overflow-hidden rounded-card border border-border-seam bg-bg-surface shadow-elevation-1 transition-[box-shadow,border-color] hover:border-border-strong hover:shadow-elevation-2\">\n {/* Image / branded placeholder + badges */}\n <div className=\"relative aspect-[16/10] overflow-hidden bg-bg-sunken\">\n {service.imageUrl ? (\n <img src={service.imageUrl} alt={service.name} loading=\"lazy\" className=\"h-full w-full object-cover\" />\n ) : (\n <div className=\"flex h-full w-full items-center justify-center bg-gradient-to-br from-accent-blue-subtle to-status-success-bg-subtle text-text-secondary/50\">\n <span className=\"[&>svg]:h-10 [&>svg]:w-10\">{getCategoryIcon(service.category)}</span>\n </div>\n )}\n <span className=\"absolute left-3 top-3 inline-flex items-center gap-1 rounded-full bg-bg-surface/90 px-2 py-0.5 text-xs font-medium capitalize text-text-secondary shadow-elevation-1 backdrop-blur\">\n {getCategoryIcon(service.category)}\n {service.category.replace(/_/g, ' ')}\n </span>\n {service.featured && (\n <span className=\"absolute right-3 top-3 rounded-full bg-status-warning-text px-2 py-0.5 text-xs font-semibold text-text-inverse shadow-elevation-1\">\n Featured\n </span>\n )}\n </div>\n\n <div className=\"flex flex-1 flex-col p-5\">\n {/* Header */}\n <div className=\"mb-2\">\n <h3 className=\"truncate text-sm font-semibold text-text-primary\">{service.name}</h3>\n <p className=\"text-xs text-text-secondary\">{service.providerName}</p>\n </div>\n\n {/* Rating */}\n <div className=\"mb-3 flex items-center gap-2\">\n <RatingStars rating={service.rating} />\n <span className=\"text-xs font-medium text-text-primary\">{service.rating}</span>\n <span className=\"text-xs text-text-secondary\">({service.reviewCount} reviews)</span>\n </div>\n\n {/* Description */}\n <p className=\"mb-3 line-clamp-2 text-xs text-text-secondary\">{service.description}</p>\n\n {/* Price + Response Time */}\n <div className=\"mb-3 flex items-center justify-between text-xs\">\n <span className=\"font-semibold text-text-primary\">{service.priceRange}</span>\n <span className=\"flex items-center gap-1 text-text-secondary\">\n <Clock className=\"h-3 w-3\" />\n {service.responseTime}\n </span>\n </div>\n\n {/* Availability + Verified */}\n <div className=\"mb-4 flex flex-wrap items-center gap-2\">\n {service.verified && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2 py-0.5 text-xs font-medium text-status-success-text\">\n <CheckCircle className=\"h-3 w-3\" />\n Verified\n </span>\n )}\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-info-bg-subtle px-2 py-0.5 text-xs text-status-info-text\">\n <MapPin className=\"h-3 w-3\" />\n {service.availableAreas.length} area{service.availableAreas.length !== 1 ? 's' : ''}\n </span>\n </div>\n\n {/* Actions */}\n <div className=\"mt-auto flex items-center gap-2\">\n <button\n onClick={() => navigate(`../marketplace/${service.id}`)}\n className=\"flex-1 rounded-md bg-action-primary-bg px-3 py-2 text-xs font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg-hover\"\n >\n <Phone className=\"mr-1 inline h-3 w-3\" />\n Contact\n </button>\n <button\n aria-label={`Message ${service.providerName}`}\n onClick={() => navigate(`../marketplace/${service.id}`)}\n className=\"rounded-md border border-border-subtle px-3 py-2 text-xs font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <MessageCircle className=\"h-3 w-3\" />\n </button>\n </div>\n </div>\n </div>\n );\n}\n\nexport function MarketplacePage() {\n useTrackPageView('housingvista.marketplace.viewed');\n const [search, setSearch] = useState('');\n const [debouncedSearch, setDebouncedSearch] = useState('');\n const [activeCategory, setActiveCategory] = useState<string>('all');\n const pagination = useServerPagination(PAGE_SIZE);\n const { setPage } = pagination;\n\n // Debounce the search box so we don't refetch on every keystroke.\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedSearch(search), 300);\n return () => clearTimeout(timer);\n }, [search]);\n\n // Server-side filter — category + search are resolved by the subgraph.\n const serverFilter = useMemo<MarketplaceServiceFilterInput | undefined>(() => {\n const filter: MarketplaceServiceFilterInput = {};\n if (debouncedSearch.trim()) filter.search = debouncedSearch.trim();\n if (activeCategory !== 'all') filter.category = activeCategory;\n return Object.keys(filter).length > 0 ? filter : undefined;\n }, [debouncedSearch, activeCategory]);\n\n // Reset to the first page whenever the server-side filter changes.\n useEffect(() => {\n setPage(1);\n }, [serverFilter, setPage]);\n\n const { data: services, loading } = useMarketplaceServices({\n filter: serverFilter,\n pagination: pagination.pagination,\n });\n\n const hasNextPage = services.length >= pagination.pageSize;\n\n return (\n <PageLayout\n title=\"Service Marketplace\"\n description=\"Find trusted service providers for your property needs\"\n purpose=\"Hire vetted professionals — plumbers, electricians, cleaners, painters, security, and landscapers — to keep your properties in top shape. Browse by category, compare ratings and response times, then contact or message a provider directly.\"\n >\n {/* Category Tabs — primary control zone (one EmphasisPanel per page) */}\n <EmphasisPanel className=\"flex flex-wrap items-center gap-2 p-3\">\n {categories.map((cat) => (\n <button\n key={cat.value}\n onClick={() => setActiveCategory(cat.value)}\n className={cn(\n 'rounded-full px-4 py-1.5 text-sm font-medium transition-colors',\n activeCategory === cat.value\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'border border-border-subtle bg-bg-surface text-text-secondary hover:bg-bg-sunken hover:text-text-primary'\n )}\n >\n {cat.label}\n </button>\n ))}\n </EmphasisPanel>\n\n {/* Search */}\n <FilterBar searchValue={search} onSearchChange={setSearch} searchPlaceholder=\"Search services, providers...\" />\n\n <NextSteps\n storageKey=\"housingvista-marketplace\"\n steps={[\n {\n id: 'book-repair',\n label: 'Fix something now',\n description: 'Find a plumber or repair pro for an urgent issue.',\n onClick: () => setActiveCategory('repair'),\n },\n {\n id: 'book-cleaning',\n label: 'Schedule a cleaning',\n description: 'Get a property move-in or turnover ready.',\n onClick: () => setActiveCategory('cleaning'),\n },\n {\n id: 'see-featured',\n label: 'Browse all providers',\n description: 'Clear filters to compare every vetted professional.',\n onClick: () => {\n setActiveCategory('all');\n setSearch('');\n },\n },\n ]}\n />\n\n {/* Results Count */}\n <p className=\"text-sm text-text-secondary\">\n {services.length} service{services.length !== 1 ? 's' : ''} shown\n {pagination.page > 1 ? ` · page ${pagination.page}` : ''}\n </p>\n\n {/* Service Grid */}\n {loading ? (\n <LoadingSkeleton variant=\"card\" count={6} />\n ) : services.length > 0 ? (\n <>\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {services.map((service) => (\n <ServiceCard key={service.id} service={service} />\n ))}\n </div>\n <Pagination\n page={pagination.page}\n pageSize={pagination.pageSize}\n hasNextPage={hasNextPage}\n hasPreviousPage={pagination.page > 1}\n onPageChange={pagination.setPage}\n onPageSizeChange={pagination.setPageSize}\n />\n </>\n ) : (\n <EmptyState\n illustration=\"empty-search\"\n title=\"No services found\"\n description=\"Try adjusting your search or category filter to find what you need.\"\n />\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServiceDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/marketplace/ServiceDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"ServiceDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/marketplace/ServiceDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AA4EH,wBAAgB,iBAAiB,gCA4NhC"}
|