@burdenoff/microfe-housingvista 2026.703.13 → 2026.709.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generated/global-operations.d.ts +2 -0
- package/dist/generated/global-operations.d.ts.map +1 -0
- package/dist/generated/global-types.d.ts +23527 -0
- package/dist/generated/global-types.d.ts.map +1 -0
- package/dist/generated/wspace-operations.d.ts +267 -0
- package/dist/generated/wspace-operations.d.ts.map +1 -0
- package/dist/generated/wspace-operations.js +67 -0
- package/dist/generated/wspace-operations.js.map +1 -0
- package/dist/generated/wspace-types.d.ts +57868 -0
- package/dist/generated/wspace-types.d.ts.map +1 -0
- package/dist/housingvista/HousingVistaRoot.d.ts +5 -0
- package/dist/housingvista/HousingVistaRoot.d.ts.map +1 -0
- package/dist/housingvista/HousingVistaRoutes.d.ts +7 -0
- package/dist/housingvista/HousingVistaRoutes.d.ts.map +1 -0
- package/dist/housingvista/components/Breadcrumbs.d.ts +13 -0
- package/dist/housingvista/components/Breadcrumbs.d.ts.map +1 -0
- package/dist/housingvista/components/ChartCard.d.ts +12 -0
- package/dist/housingvista/components/ChartCard.d.ts.map +1 -0
- package/dist/housingvista/components/ContactOwnerDialog.d.ts +33 -0
- package/dist/housingvista/components/ContactOwnerDialog.d.ts.map +1 -0
- package/dist/housingvista/components/DataTable.d.ts +42 -0
- package/dist/housingvista/components/DataTable.d.ts.map +1 -0
- package/dist/housingvista/components/EmptyState.d.ts +19 -0
- package/dist/housingvista/components/EmptyState.d.ts.map +1 -0
- package/dist/housingvista/components/ErrorBoundary.d.ts +19 -0
- package/dist/housingvista/components/ErrorBoundary.d.ts.map +1 -0
- package/dist/housingvista/components/FileUploader.d.ts +16 -0
- package/dist/housingvista/components/FileUploader.d.ts.map +1 -0
- package/dist/housingvista/components/FilterBar.d.ts +13 -0
- package/dist/housingvista/components/FilterBar.d.ts.map +1 -0
- package/dist/housingvista/components/LoadingSkeleton.d.ts +10 -0
- package/dist/housingvista/components/LoadingSkeleton.d.ts.map +1 -0
- package/dist/housingvista/components/PageLayout.d.ts +18 -0
- package/dist/housingvista/components/PageLayout.d.ts.map +1 -0
- package/dist/housingvista/components/PropertyCard.d.ts +26 -0
- package/dist/housingvista/components/PropertyCard.d.ts.map +1 -0
- package/dist/housingvista/components/ReviewsSection.d.ts +12 -0
- package/dist/housingvista/components/ReviewsSection.d.ts.map +1 -0
- package/dist/housingvista/components/StatCard.d.ts +18 -0
- package/dist/housingvista/components/StatCard.d.ts.map +1 -0
- package/dist/housingvista/components/StatusBadge.d.ts +12 -0
- package/dist/housingvista/components/StatusBadge.d.ts.map +1 -0
- package/dist/housingvista/components/index.d.ts +17 -0
- package/dist/housingvista/components/index.d.ts.map +1 -0
- package/dist/housingvista/context/HousingVistaContext.d.ts +15 -0
- package/dist/housingvista/context/HousingVistaContext.d.ts.map +1 -0
- package/dist/housingvista/hooks/index.d.ts +804 -0
- package/dist/housingvista/hooks/index.d.ts.map +1 -0
- package/dist/housingvista/hooks/index.js +1 -0
- package/dist/housingvista/hooks/index.js.map +1 -1
- package/dist/housingvista/hooks/messages.d.ts +65 -0
- package/dist/housingvista/hooks/messages.d.ts.map +1 -0
- package/dist/housingvista/hooks/platform.d.ts +140 -0
- package/dist/housingvista/hooks/platform.d.ts.map +1 -0
- package/dist/housingvista/hooks/useHousingVistaAnalytics.d.ts +16 -0
- package/dist/housingvista/hooks/useHousingVistaAnalytics.d.ts.map +1 -0
- package/dist/housingvista/hooks/useHousingVistaAnalytics.js +57 -0
- package/dist/housingvista/hooks/useHousingVistaAnalytics.js.map +1 -0
- package/dist/housingvista/hooks/useServerPagination.d.ts +12 -0
- package/dist/housingvista/hooks/useServerPagination.d.ts.map +1 -0
- package/dist/housingvista/index.d.ts +13 -0
- package/dist/housingvista/index.d.ts.map +1 -0
- package/dist/housingvista/index.js +2 -0
- package/dist/housingvista/lib/toast.d.ts +11 -0
- package/dist/housingvista/lib/toast.d.ts.map +1 -0
- package/dist/housingvista/pages/HomePage.d.ts +7 -0
- package/dist/housingvista/pages/HomePage.d.ts.map +1 -0
- package/dist/housingvista/pages/HomePage.js +135 -132
- package/dist/housingvista/pages/HomePage.js.map +1 -1
- package/dist/housingvista/pages/amc/AMCManagementPage.d.ts +2 -0
- package/dist/housingvista/pages/amc/AMCManagementPage.d.ts.map +1 -0
- package/dist/housingvista/pages/analytics/AnalyticsPage.d.ts +2 -0
- package/dist/housingvista/pages/analytics/AnalyticsPage.d.ts.map +1 -0
- package/dist/housingvista/pages/analytics/AnalyticsPage.js +136 -134
- package/dist/housingvista/pages/analytics/AnalyticsPage.js.map +1 -1
- package/dist/housingvista/pages/assets/AssetDetailPage.d.ts +9 -0
- package/dist/housingvista/pages/assets/AssetDetailPage.d.ts.map +1 -0
- package/dist/housingvista/pages/assets/AssetManagementPage.d.ts +2 -0
- package/dist/housingvista/pages/assets/AssetManagementPage.d.ts.map +1 -0
- package/dist/housingvista/pages/assets/AssetManagementPage.js.map +1 -1
- package/dist/housingvista/pages/chat/ChatPage.d.ts +14 -0
- package/dist/housingvista/pages/chat/ChatPage.d.ts.map +1 -0
- package/dist/housingvista/pages/finance/HomeLoanDetailPage.d.ts +11 -0
- package/dist/housingvista/pages/finance/HomeLoanDetailPage.d.ts.map +1 -0
- package/dist/housingvista/pages/finance/HomeLoansPage.d.ts +2 -0
- package/dist/housingvista/pages/finance/HomeLoansPage.d.ts.map +1 -0
- package/dist/housingvista/pages/investments/InvestmentDetailPage.d.ts +2 -0
- package/dist/housingvista/pages/investments/InvestmentDetailPage.d.ts.map +1 -0
- package/dist/housingvista/pages/investments/InvestmentsPage.d.ts +2 -0
- package/dist/housingvista/pages/investments/InvestmentsPage.d.ts.map +1 -0
- package/dist/housingvista/pages/investments/InvestmentsPage.js.map +1 -1
- package/dist/housingvista/pages/legal/LegalPage.d.ts +2 -0
- package/dist/housingvista/pages/legal/LegalPage.d.ts.map +1 -0
- package/dist/housingvista/pages/legal/LegalServiceDetailPage.d.ts +12 -0
- package/dist/housingvista/pages/legal/LegalServiceDetailPage.d.ts.map +1 -0
- package/dist/housingvista/pages/market/AreaInsightsPage.d.ts +2 -0
- package/dist/housingvista/pages/market/AreaInsightsPage.d.ts.map +1 -0
- package/dist/housingvista/pages/market/MarketAnalyticsPage.d.ts +2 -0
- package/dist/housingvista/pages/market/MarketAnalyticsPage.d.ts.map +1 -0
- package/dist/housingvista/pages/marketplace/MarketplacePage.d.ts +2 -0
- package/dist/housingvista/pages/marketplace/MarketplacePage.d.ts.map +1 -0
- package/dist/housingvista/pages/marketplace/ServiceDetailPage.d.ts +12 -0
- package/dist/housingvista/pages/marketplace/ServiceDetailPage.d.ts.map +1 -0
- package/dist/housingvista/pages/offers/OffersPage.d.ts +10 -0
- package/dist/housingvista/pages/offers/OffersPage.d.ts.map +1 -0
- package/dist/housingvista/pages/properties/AddPropertyPage.d.ts +8 -0
- package/dist/housingvista/pages/properties/AddPropertyPage.d.ts.map +1 -0
- package/dist/housingvista/pages/properties/AddPropertyPage.js +279 -278
- package/dist/housingvista/pages/properties/AddPropertyPage.js.map +1 -1
- package/dist/housingvista/pages/properties/PropertiesPage.d.ts +7 -0
- package/dist/housingvista/pages/properties/PropertiesPage.d.ts.map +1 -0
- package/dist/housingvista/pages/properties/PropertiesPage.js +93 -86
- package/dist/housingvista/pages/properties/PropertiesPage.js.map +1 -1
- package/dist/housingvista/pages/properties/PropertyDetailPage.d.ts +9 -0
- package/dist/housingvista/pages/properties/PropertyDetailPage.d.ts.map +1 -0
- package/dist/housingvista/pages/properties/PropertyDetailPage.js +514 -511
- package/dist/housingvista/pages/properties/PropertyDetailPage.js.map +1 -1
- package/dist/housingvista/pages/rental/RentalManagementPage.d.ts +2 -0
- package/dist/housingvista/pages/rental/RentalManagementPage.d.ts.map +1 -0
- package/dist/housingvista/pages/settings/ProfilePage.d.ts +2 -0
- package/dist/housingvista/pages/settings/ProfilePage.d.ts.map +1 -0
- package/dist/housingvista/pages/settings/SettingsPage.d.ts +2 -0
- package/dist/housingvista/pages/settings/SettingsPage.d.ts.map +1 -0
- package/dist/housingvista/pages/shortlist/ShortlistPage.d.ts +8 -0
- package/dist/housingvista/pages/shortlist/ShortlistPage.d.ts.map +1 -0
- package/dist/housingvista/pages/transactions/TransactionDetailPage.d.ts +8 -0
- package/dist/housingvista/pages/transactions/TransactionDetailPage.d.ts.map +1 -0
- package/dist/housingvista/pages/transactions/TransactionsPage.d.ts +7 -0
- package/dist/housingvista/pages/transactions/TransactionsPage.d.ts.map +1 -0
- package/dist/housingvista/pages/transactions/milestones.d.ts +35 -0
- package/dist/housingvista/pages/transactions/milestones.d.ts.map +1 -0
- package/dist/housingvista/pages/verification/VerificationDetailPage.d.ts +10 -0
- package/dist/housingvista/pages/verification/VerificationDetailPage.d.ts.map +1 -0
- package/dist/housingvista/pages/verification/VerificationPage.d.ts +2 -0
- package/dist/housingvista/pages/verification/VerificationPage.d.ts.map +1 -0
- package/dist/housingvista/pages/viewings/ViewingsPage.d.ts +9 -0
- package/dist/housingvista/pages/viewings/ViewingsPage.d.ts.map +1 -0
- package/dist/housingvista/services/assistantApi.d.ts +90 -0
- package/dist/housingvista/services/assistantApi.d.ts.map +1 -0
- package/dist/housingvista/services/assistantApi.js +222 -0
- package/dist/housingvista/services/assistantApi.js.map +1 -0
- package/dist/housingvista/services/createSandboxAssistantTransport.d.ts +42 -0
- package/dist/housingvista/services/createSandboxAssistantTransport.d.ts.map +1 -0
- package/dist/housingvista/services/createSandboxAssistantTransport.js +120 -0
- package/dist/housingvista/services/createSandboxAssistantTransport.js.map +1 -0
- package/dist/housingvista/store/housingvistaStore.d.ts +72 -0
- package/dist/housingvista/store/housingvistaStore.d.ts.map +1 -0
- package/dist/housingvista/types/assistant.d.ts +16 -0
- package/dist/housingvista/types/assistant.d.ts.map +1 -0
- package/dist/housingvista/types/index.d.ts +1328 -0
- package/dist/housingvista/types/index.d.ts.map +1 -0
- package/dist/housingvista/utils/helpers.d.ts +25 -0
- package/dist/housingvista/utils/helpers.d.ts.map +1 -0
- package/dist/housingvista/utils/index.d.ts +2 -0
- package/dist/housingvista/utils/index.d.ts.map +1 -0
- package/dist/housingvista/utils/pageContext.d.ts +25 -0
- package/dist/housingvista/utils/pageContext.d.ts.map +1 -0
- package/dist/housingvista/utils/pageContext.js +77 -0
- package/dist/housingvista/utils/pageContext.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -3
- package/package.json +6 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../../src/housingvista/pages/analytics/AnalyticsPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { TrendingUp, DollarSign, Building, Activity, Download, Clock, BarChart3 } from 'lucide-react';\nimport {\n AreaChart,\n Area,\n LineChart,\n Line,\n PieChart,\n Pie,\n Cell,\n XAxis,\n YAxis,\n CartesianGrid,\n Tooltip,\n ResponsiveContainer,\n} from 'recharts';\nimport { EmphasisPanel, NextSteps } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components';\nimport { StatCard } from '../../components';\nimport { ChartCard } from '../../components';\nimport { DataTable, type Column } from '../../components';\nimport { EmptyState } from '../../components';\nimport { LoadingSkeleton } from '../../components';\nimport { useAnalyticsData } from '../../hooks';\nimport { notify } from '../../lib/toast';\nimport { cn, formatCurrency, formatNumber } from '../../utils/helpers';\n\n// Distribution slice colours drawn from the semantic chart palette (full oklch tokens).\nconst CHART_COLORS = ['var(--color-chart-1)', 'var(--color-chart-2)', 'var(--color-chart-3)', 'var(--color-chart-4)', 'var(--color-chart-5)'];\nconst REVENUE_COLOR = 'var(--color-chart-2)';\nconst EXPENSE_COLOR = 'var(--color-chart-1)';\n\n// Shared, theme-aware tooltip styling for every recharts surface on this page.\nconst tooltipContentStyle: React.CSSProperties = {\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-subtle)',\n borderRadius: 10,\n fontSize: 12,\n boxShadow: '0 8px 24px rgb(0 0 0 / 0.12)',\n};\nconst tooltipLabelStyle: React.CSSProperties = { color: 'var(--color-text-primary)', fontWeight: 600 };\nconst axisTick = { fontSize: 11, fill: 'var(--color-text-secondary)' } as const;\n\n// The backend series is monthly; this range control slices to the most recent N\n// months so the same real data can be viewed at different horizons (0 = all).\nconst rangeOptions: { value: number; label: string }[] = [\n { value: 3, label: '3M' },\n { value: 6, label: '6M' },\n { value: 12, label: '12M' },\n { value: 0, label: 'All' },\n];\n\nexport function AnalyticsPage() {\n const { data: analyticsData, loading } = useAnalyticsData();\n const [range, setRange] = useState<number>(12);\n\n // Client-side CSV export of the loaded analytics data (KPIs + monthly revenue).\n const handleExportCsv = () => {\n if (!analyticsData) return;\n\n const escapeCsv = (val: string | number) => {\n const s = String(val);\n return /[\",\\n]/.test(s) ? `\"${s.replace(/\"/g, '\"\"')}\"` : s;\n };\n\n const lines: string[] = [];\n lines.push('KPI,Value,Unit,Change %');\n for (const kpi of analyticsData.kpis) {\n lines.push([kpi.label, kpi.value, kpi.unit, kpi.changePercent].map(escapeCsv).join(','));\n }\n lines.push('');\n lines.push('Month,Revenue,Expenses,Net Income');\n for (const row of analyticsData.revenueByMonth) {\n lines.push([row.month, row.revenue, row.expenses, row.revenue - row.expenses].map(escapeCsv).join(','));\n }\n\n const blob = new Blob([lines.join('\\n')], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = `housingvista-analytics-${new Date().toISOString().slice(0, 10)}.csv`;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n\n notify.success('Report exported', 'Your analytics data was downloaded as a CSV file.');\n };\n\n const revenueColumns: Column<{ month: string; revenue: number; expenses: number }>[] = [\n {\n key: 'month',\n header: 'Month',\n render: (item) => <span className=\"font-medium text-text-primary\">{item.month}</span>,\n },\n {\n key: 'revenue',\n header: 'Revenue',\n render: (item) => <span className=\"font-medium text-status-success-text\">{formatCurrency(item.revenue)}</span>,\n },\n {\n key: 'expenses',\n header: 'Expenses',\n render: (item) => <span className=\"font-medium text-status-error-text\">{formatCurrency(item.expenses)}</span>,\n },\n {\n key: 'net',\n header: 'Net Income',\n sortable: false,\n render: (item) => {\n const net = item.revenue - item.expenses;\n return (\n <span className={cn('font-medium', net >= 0 ? 'text-status-success-text' : 'text-status-error-text')}>\n {formatCurrency(net)}\n </span>\n );\n },\n },\n {\n key: 'margin',\n header: 'Margin',\n sortable: false,\n render: (item) => {\n const margin = item.revenue > 0 ? ((item.revenue - item.expenses) / item.revenue) * 100 : 0;\n return (\n <span className={cn('font-medium', margin >= 0 ? 'text-status-success-text' : 'text-status-error-text')}>\n {margin.toFixed(1)}%\n </span>\n );\n },\n },\n ];\n\n // ---- Loading ----\n if (loading) {\n return (\n <PageLayout title=\"Analytics\" description=\"Track your property portfolio performance and market insights\">\n <LoadingSkeleton className=\"h-28\" />\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <LoadingSkeleton className=\"h-64\" />\n <LoadingSkeleton className=\"h-64\" />\n </div>\n </PageLayout>\n );\n }\n\n // ---- Empty / not-yet-computed ----\n const hasData =\n analyticsData &&\n (analyticsData.kpis.length > 0 ||\n analyticsData.revenueByMonth.length > 0 ||\n analyticsData.propertyTypeDistribution.length > 0);\n\n if (!hasData) {\n return (\n <PageLayout title=\"Analytics\" description=\"Track your property portfolio performance and market insights\">\n <EmptyState\n illustration=\"empty-generic\"\n icon={<BarChart3 className=\"h-12 w-12\" />}\n title=\"No analytics yet\"\n description=\"Once you have properties, transactions, and rentals recorded, revenue, occupancy, and portfolio insights will appear here.\"\n />\n </PageLayout>\n );\n }\n\n const kpis = analyticsData.kpis;\n const propertyTypeDistribution = analyticsData.propertyTypeDistribution;\n\n // Range control slices the real monthly series to its most recent N entries.\n const sliceRange = <T,>(rows: T[]): T[] => (range > 0 ? rows.slice(-range) : rows);\n const revenueByMonth = sliceRange(analyticsData.revenueByMonth);\n const occupancyRate = sliceRange(analyticsData.occupancyRate);\n\n const revenueChartData = revenueByMonth.map((r) => ({\n month: r.month,\n revenue: r.revenue,\n expenses: r.expenses,\n net: r.revenue - r.expenses,\n }));\n const distributionTotal = propertyTypeDistribution.reduce((s, d) => s + d.value, 0);\n const distributionData = propertyTypeDistribution.map((d, idx) => ({\n name: d.type.charAt(0).toUpperCase() + d.type.slice(1),\n value: d.value,\n count: d.count,\n color: CHART_COLORS[idx % CHART_COLORS.length],\n }));\n\n const totalRevenue = revenueByMonth.reduce((s, r) => s + r.revenue, 0);\n const totalExpenses = revenueByMonth.reduce((s, r) => s + r.expenses, 0);\n const netIncome = totalRevenue - totalExpenses;\n const profitMargin = totalRevenue > 0 ? (netIncome / totalRevenue) * 100 : 0;\n\n return (\n <PageLayout\n title=\"Analytics\"\n description=\"Track your property portfolio performance and market insights\"\n purpose=\"Understand how your portfolio is actually performing — revenue versus expenses, occupancy, property-type mix, and profitability — so you can spot trends, compare periods, and decide where to buy, sell, or raise rents.\"\n actions={\n <button\n onClick={handleExportCsv}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Download className=\"h-4 w-4\" />\n Export Report\n </button>\n }\n >\n <NextSteps\n storageKey=\"housingvista-analytics\"\n steps={[\n {\n id: 'performance',\n label: 'Drill into monthly performance',\n description: 'Review revenue, expenses, net income, and margin month by month.',\n href: '#analytics-performance',\n },\n ]}\n />\n\n {/* Period range control — slices the real monthly series to a horizon */}\n <div className=\"flex flex-wrap items-center gap-3\">\n <span className=\"text-sm font-medium text-text-secondary\">Period</span>\n <div\n role=\"group\"\n aria-label=\"Select analytics period\"\n className=\"inline-flex rounded-lg border border-border-subtle bg-bg-sunken/60 p-1\"\n >\n {rangeOptions.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n aria-pressed={range === option.value}\n onClick={() => setRange(option.value)}\n className={cn(\n 'rounded-md px-3 py-1.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n range === option.value\n ? 'bg-action-primary-bg text-action-primary-text shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n )}\n >\n {option.label}\n </button>\n ))}\n </div>\n </div>\n\n {/* Stats Overview Row — primary insight zone (one EmphasisPanel per page) */}\n {kpis.length > 0 && (\n <EmphasisPanel className=\"p-4\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {kpis.map((kpi) => {\n const icons: Record<string, typeof DollarSign> = {\n 'Portfolio Value': DollarSign,\n 'Monthly Revenue': TrendingUp,\n 'Occupancy Rate': Building,\n 'Avg. Days to Rent': Clock,\n };\n const IconComponent = icons[kpi.label] || Activity;\n\n return (\n <StatCard\n key={kpi.id}\n label={kpi.label}\n value={\n kpi.unit === 'USD'\n ? kpi.value\n : `${formatNumber(kpi.value)}${kpi.unit === '%' ? '%' : ` ${kpi.unit}`}`\n }\n prefix={kpi.unit === 'USD' ? '$' : undefined}\n icon={<IconComponent className=\"h-4 w-4\" />}\n trend={kpi.trend === 'up' ? 'up' : kpi.trend === 'down' ? 'down' : 'stable'}\n change={kpi.changePercent}\n />\n );\n })}\n </div>\n </EmphasisPanel>\n )}\n\n {/* Charts Section — revenue area chart (wide) + property-type donut (aside) */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {revenueChartData.length > 0 && (\n <ChartCard\n title=\"Revenue vs Expenses\"\n description=\"Real income against costs across the selected period\"\n className=\"lg:col-span-2\"\n actions={\n <div className=\"flex items-center gap-4 text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1.5\">\n <span className=\"h-2.5 w-2.5 rounded-full\" style={{ background: REVENUE_COLOR }} />\n Revenue\n </span>\n <span className=\"flex items-center gap-1.5\">\n <span className=\"h-2.5 w-2.5 rounded-full\" style={{ background: EXPENSE_COLOR }} />\n Expenses\n </span>\n </div>\n }\n >\n <div className=\"h-72 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <AreaChart data={revenueChartData} margin={{ top: 8, right: 8, left: 0, bottom: 0 }}>\n <defs>\n <linearGradient id=\"revGrad\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"5%\" stopColor={REVENUE_COLOR} stopOpacity={0.35} />\n <stop offset=\"95%\" stopColor={REVENUE_COLOR} stopOpacity={0.02} />\n </linearGradient>\n <linearGradient id=\"expGrad\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"5%\" stopColor={EXPENSE_COLOR} stopOpacity={0.3} />\n <stop offset=\"95%\" stopColor={EXPENSE_COLOR} stopOpacity={0.02} />\n </linearGradient>\n </defs>\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--color-border-subtle)\" vertical={false} />\n <XAxis dataKey=\"month\" tick={axisTick} tickLine={false} axisLine={false} />\n <YAxis\n tick={axisTick}\n tickLine={false}\n axisLine={false}\n width={52}\n tickFormatter={(v: number) => formatCurrency(v)}\n />\n <Tooltip\n contentStyle={tooltipContentStyle}\n labelStyle={tooltipLabelStyle}\n formatter={(value: number, name) => [formatCurrency(value), name]}\n />\n <Area\n type=\"monotone\"\n dataKey=\"revenue\"\n name=\"Revenue\"\n stroke={REVENUE_COLOR}\n strokeWidth={2.5}\n fill=\"url(#revGrad)\"\n />\n <Area\n type=\"monotone\"\n dataKey=\"expenses\"\n name=\"Expenses\"\n stroke={EXPENSE_COLOR}\n strokeWidth={2.5}\n fill=\"url(#expGrad)\"\n />\n </AreaChart>\n </ResponsiveContainer>\n </div>\n </ChartCard>\n )}\n\n {distributionData.length > 0 && (\n <ChartCard title=\"Property-Type Distribution\" description=\"Portfolio value split by type\">\n <div className=\"relative h-56 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <PieChart>\n <Pie\n data={distributionData}\n dataKey=\"value\"\n nameKey=\"name\"\n innerRadius={58}\n outerRadius={84}\n paddingAngle={2}\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={2}\n >\n {distributionData.map((entry) => (\n <Cell key={entry.name} fill={entry.color} />\n ))}\n </Pie>\n <Tooltip\n contentStyle={tooltipContentStyle}\n labelStyle={tooltipLabelStyle}\n formatter={(value: number, _n, item) => [\n `${formatCurrency(value)} · ${item?.payload?.count ?? 0} properties`,\n item?.payload?.name,\n ]}\n />\n </PieChart>\n </ResponsiveContainer>\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center\">\n <span className=\"text-xs text-text-secondary\">Total value</span>\n <span className=\"text-lg font-bold text-text-primary\">{formatCurrency(distributionTotal)}</span>\n </div>\n </div>\n <ul className=\"mt-3 space-y-2\">\n {distributionData.map((entry) => (\n <li key={entry.name} className=\"flex items-center justify-between text-sm\">\n <span className=\"flex items-center gap-2\">\n <span className=\"h-2.5 w-2.5 rounded-full\" style={{ background: entry.color }} />\n <span className=\"text-text-primary\">{entry.name}</span>\n </span>\n <span className=\"text-text-secondary\">\n {distributionTotal > 0 ? Math.round((entry.value / distributionTotal) * 100) : 0}%\n </span>\n </li>\n ))}\n </ul>\n </ChartCard>\n )}\n </div>\n\n {/* Occupancy trend (wide) + quick stats (aside) */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {occupancyRate.length > 0 && (\n <ChartCard\n title=\"Occupancy Trend\"\n description=\"Share of units occupied over the selected period\"\n className=\"lg:col-span-2\"\n >\n <div className=\"h-56 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <LineChart data={occupancyRate} margin={{ top: 8, right: 12, left: 0, bottom: 0 }}>\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--color-border-subtle)\" vertical={false} />\n <XAxis dataKey=\"month\" tick={axisTick} tickLine={false} axisLine={false} />\n <YAxis\n tick={axisTick}\n tickLine={false}\n axisLine={false}\n width={40}\n domain={[0, 100]}\n tickFormatter={(v: number) => `${v}%`}\n />\n <Tooltip\n contentStyle={tooltipContentStyle}\n labelStyle={tooltipLabelStyle}\n formatter={(value: number) => [`${value}%`, 'Occupancy']}\n />\n <Line\n type=\"monotone\"\n dataKey=\"rate\"\n stroke=\"var(--color-chart-2)\"\n strokeWidth={2.5}\n dot={{ r: 3, fill: 'var(--color-chart-2)', strokeWidth: 0 }}\n activeDot={{ r: 5 }}\n />\n </LineChart>\n </ResponsiveContainer>\n </div>\n </ChartCard>\n )}\n\n {revenueByMonth.length > 0 && (\n <ChartCard title=\"Period Summary\" description=\"Totals across the selected range\">\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between rounded-lg bg-bg-sunken/50 p-3\">\n <div>\n <p className=\"text-sm text-text-secondary\">Total Revenue</p>\n <p className=\"text-lg font-bold text-text-primary\">{formatCurrency(totalRevenue)}</p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm text-text-secondary\">Total Expenses</p>\n <p className=\"text-lg font-bold text-text-primary\">{formatCurrency(totalExpenses)}</p>\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"rounded-lg bg-status-success-bg-subtle p-3 text-center\">\n <p className=\"text-xl font-bold text-status-success-text\">{formatCurrency(netIncome)}</p>\n <p className=\"text-xs text-text-secondary\">Net Income</p>\n </div>\n <div className=\"rounded-lg bg-status-info-bg-subtle p-3 text-center\">\n <p className=\"text-xl font-bold text-status-info-text\">{profitMargin.toFixed(1)}%</p>\n <p className=\"text-xs text-text-secondary\">Profit Margin</p>\n </div>\n </div>\n </div>\n </ChartCard>\n )}\n </div>\n\n {/* Performance Metrics Table */}\n {revenueByMonth.length > 0 && (\n <div id=\"analytics-performance\" className=\"scroll-mt-6\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Monthly Performance</h2>\n <DataTable columns={revenueColumns} data={revenueByMonth} keyField=\"month\" pageSize={12} />\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,IAAM,IAAe;CAAC;CAAwB;CAAwB;CAAwB;CAAwB;CAAuB,EACvI,IAAgB,wBAChB,IAAgB,wBAGhB,IAA2C;CAC/C,YAAY;CACZ,QAAQ;CACR,cAAc;CACd,UAAU;CACV,WAAW;CACZ,EACK,IAAyC;CAAE,OAAO;CAA6B,YAAY;CAAK,EAChG,IAAW;CAAE,UAAU;CAAI,MAAM;CAA+B,EAIhE,IAAmD;CACvD;EAAE,OAAO;EAAG,OAAO;EAAM;CACzB;EAAE,OAAO;EAAG,OAAO;EAAM;CACzB;EAAE,OAAO;EAAI,OAAO;EAAO;CAC3B;EAAE,OAAO;EAAG,OAAO;EAAO;CAC3B;AAED,SAAgB,IAAgB;CAC9B,IAAM,EAAE,MAAM,GAAe,eAAY,GAAkB,EACrD,CAAC,GAAO,KAAY,EAAiB,GAAG,EAGxC,UAAwB;AAC5B,MAAI,CAAC,EAAe;EAEpB,IAAM,KAAa,MAAyB;GAC1C,IAAM,IAAI,OAAO,EAAI;AACrB,UAAO,SAAS,KAAK,EAAE,GAAG,IAAI,EAAE,QAAQ,MAAM,OAAK,CAAC,KAAK;KAGrD,IAAkB,EAAE;AAC1B,IAAM,KAAK,0BAA0B;AACrC,OAAK,IAAM,KAAO,EAAc,KAC9B,GAAM,KAAK;GAAC,EAAI;GAAO,EAAI;GAAO,EAAI;GAAM,EAAI;GAAc,CAAC,IAAI,EAAU,CAAC,KAAK,IAAI,CAAC;AAG1F,EADA,EAAM,KAAK,GAAG,EACd,EAAM,KAAK,oCAAoC;AAC/C,OAAK,IAAM,KAAO,EAAc,eAC9B,GAAM,KAAK;GAAC,EAAI;GAAO,EAAI;GAAS,EAAI;GAAU,EAAI,UAAU,EAAI;GAAS,CAAC,IAAI,EAAU,CAAC,KAAK,IAAI,CAAC;EAGzG,IAAM,IAAO,IAAI,KAAK,CAAC,EAAM,KAAK,KAAK,CAAC,EAAE,EAAE,MAAM,2BAA2B,CAAC,EACxE,IAAM,IAAI,gBAAgB,EAAK,EAC/B,IAAO,SAAS,cAAc,IAAI;AAQxC,EAPA,EAAK,OAAO,GACZ,EAAK,WAAW,2CAA0B,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,OAChF,SAAS,KAAK,YAAY,EAAK,EAC/B,EAAK,OAAO,EACZ,SAAS,KAAK,YAAY,EAAK,EAC/B,IAAI,gBAAgB,EAAI,EAExB,EAAO,QAAQ,mBAAmB,oDAAoD;IAGlF,IAAiF;EACrF;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MAAS,kBAAC,QAAD;IAAM,WAAU;cAAiC,EAAK;IAAa,CAAA;GACtF;EACD;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MAAS,kBAAC,QAAD;IAAM,WAAU;cAAwC,EAAe,EAAK,QAAQ;IAAQ,CAAA;GAC/G;EACD;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MAAS,kBAAC,QAAD;IAAM,WAAU;cAAsC,EAAe,EAAK,SAAS;IAAQ,CAAA;GAC9G;EACD;GACE,KAAK;GACL,QAAQ;GACR,UAAU;GACV,SAAS,MAAS;IAChB,IAAM,IAAM,EAAK,UAAU,EAAK;AAChC,WACE,kBAAC,QAAD;KAAM,WAAW,EAAG,eAAe,KAAO,IAAI,6BAA6B,yBAAyB;eACjG,EAAe,EAAI;KACf,CAAA;;GAGZ;EACD;GACE,KAAK;GACL,QAAQ;GACR,UAAU;GACV,SAAS,MAAS;IAChB,IAAM,IAAS,EAAK,UAAU,KAAM,EAAK,UAAU,EAAK,YAAY,EAAK,UAAW,MAAM;AAC1F,WACE,kBAAC,QAAD;KAAM,WAAW,EAAG,eAAe,KAAU,IAAI,6BAA6B,yBAAyB;eAAvG,CACG,EAAO,QAAQ,EAAE,EAAC,IACd;;;GAGZ;EACF;AAGD,KAAI,EACF,QACE,kBAAC,GAAD;EAAY,OAAM;EAAY,aAAY;YAA1C,CACE,kBAAC,GAAD,EAAiB,WAAU,QAAS,CAAA,EACpC,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAiB,WAAU,QAAS,CAAA,EACpC,kBAAC,GAAD,EAAiB,WAAU,QAAS,CAAA,CAChC;KACK;;AAWjB,KAAI,EALF,MACC,EAAc,KAAK,SAAS,KAC3B,EAAc,eAAe,SAAS,KACtC,EAAc,yBAAyB,SAAS,IAGlD,QACE,kBAAC,GAAD;EAAY,OAAM;EAAY,aAAY;YACxC,kBAAC,GAAD;GACE,cAAa;GACb,MAAM,kBAAC,IAAD,EAAW,WAAU,aAAc,CAAA;GACzC,OAAM;GACN,aAAY;GACZ,CAAA;EACS,CAAA;CAIjB,IAAM,IAAO,EAAc,MACrB,IAA2B,EAAc,0BAGzC,KAAkB,MAAoB,IAAQ,IAAI,EAAK,MAAM,CAAC,EAAM,GAAG,GACvE,IAAiB,EAAW,EAAc,eAAe,EACzD,IAAgB,EAAW,EAAc,cAAc,EAEvD,IAAmB,EAAe,KAAK,OAAO;EAClD,OAAO,EAAE;EACT,SAAS,EAAE;EACX,UAAU,EAAE;EACZ,KAAK,EAAE,UAAU,EAAE;EACpB,EAAE,EACG,IAAoB,EAAyB,QAAQ,GAAG,MAAM,IAAI,EAAE,OAAO,EAAE,EAC7E,IAAmB,EAAyB,KAAK,GAAG,OAAS;EACjE,MAAM,EAAE,KAAK,OAAO,EAAE,CAAC,aAAa,GAAG,EAAE,KAAK,MAAM,EAAE;EACtD,OAAO,EAAE;EACT,OAAO,EAAE;EACT,OAAO,EAAa,IAAM,EAAa;EACxC,EAAE,EAEG,IAAe,EAAe,QAAQ,GAAG,MAAM,IAAI,EAAE,SAAS,EAAE,EAChE,IAAgB,EAAe,QAAQ,GAAG,MAAM,IAAI,EAAE,UAAU,EAAE,EAClE,IAAY,IAAe,GAC3B,KAAe,IAAe,IAAK,IAAY,IAAgB,MAAM;AAE3E,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SAAQ;EACR,SACE,kBAAC,UAAD;GACE,SAAS;GACT,WAAU;aAFZ,CAIE,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA,EAAA,gBAEzB;;YAXb;GAcE,kBAAC,IAAD;IACE,YAAW;IACX,OAAO,CACL;KACE,IAAI;KACJ,OAAO;KACP,aAAa;KACb,MAAM;KACP,CACF;IACD,CAAA;GAGF,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAA0C;KAAa,CAAA,EACvE,kBAAC,OAAD;KACE,MAAK;KACL,cAAW;KACX,WAAU;eAET,EAAa,KAAK,MACjB,kBAAC,UAAD;MAEE,MAAK;MACL,gBAAc,MAAU,EAAO;MAC/B,eAAe,EAAS,EAAO,MAAM;MACrC,WAAW,EACT,6JACA,MAAU,EAAO,QACb,4DACA,8CACL;gBAEA,EAAO;MACD,EAZF,EAAO,MAYL,CACT;KACE,CAAA,CACF;;GAGL,EAAK,SAAS,KACb,kBAAC,GAAD;IAAe,WAAU;cACvB,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAK,KAAK,MAAQ;MAOjB,IAAM,IAN2C;OAC/C,mBAAmB;OACnB,mBAAmB;OACnB,kBAAkB;OAClB,qBAAqB;OACtB,CAC2B,EAAI,UAAU;AAE1C,aACE,kBAAC,GAAD;OAEE,OAAO,EAAI;OACX,OACE,EAAI,SAAS,QACT,EAAI,QACJ,GAAG,EAAa,EAAI,MAAM,GAAG,EAAI,SAAS,MAAM,MAAM,IAAI,EAAI;OAEpE,QAAQ,EAAI,SAAS,QAAQ,MAAM,KAAA;OACnC,MAAM,kBAAC,GAAD,EAAe,WAAU,WAAY,CAAA;OAC3C,OAAO,EAAI,UAAU,OAAO,OAAO,EAAI,UAAU,SAAS,SAAS;OACnE,QAAQ,EAAI;OACZ,EAXK,EAAI,GAWT;OAEJ;KACE,CAAA;IACQ,CAAA;GAIlB,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAiB,SAAS,KACzB,kBAAC,GAAD;KACE,OAAM;KACN,aAAY;KACZ,WAAU;KACV,SACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAhB,CACE,kBAAC,QAAD;QAAM,WAAU;QAA2B,OAAO,EAAE,YAAY,GAAe;QAAI,CAAA,EAAA,UAE9E;UACP,kBAAC,QAAD;OAAM,WAAU;iBAAhB,CACE,kBAAC,QAAD;QAAM,WAAU;QAA2B,OAAO,EAAE,YAAY,GAAe;QAAI,CAAA,EAAA,WAE9E;SACH;;eAGR,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OAAqB,OAAM;OAAO,QAAO;iBACvC,kBAAC,IAAD;QAAW,MAAM;QAAkB,QAAQ;SAAE,KAAK;SAAG,OAAO;SAAG,MAAM;SAAG,QAAQ;SAAG;kBAAnF;SACE,kBAAC,QAAD,EAAA,UAAA,CACE,kBAAC,kBAAD;UAAgB,IAAG;UAAU,IAAG;UAAI,IAAG;UAAI,IAAG;UAAI,IAAG;oBAArD,CACE,kBAAC,QAAD;WAAM,QAAO;WAAK,WAAW;WAAe,aAAa;WAAQ,CAAA,EACjE,kBAAC,QAAD;WAAM,QAAO;WAAM,WAAW;WAAe,aAAa;WAAQ,CAAA,CACnD;aACjB,kBAAC,kBAAD;UAAgB,IAAG;UAAU,IAAG;UAAI,IAAG;UAAI,IAAG;UAAI,IAAG;oBAArD,CACE,kBAAC,QAAD;WAAM,QAAO;WAAK,WAAW;WAAe,aAAa;WAAO,CAAA,EAChE,kBAAC,QAAD;WAAM,QAAO;WAAM,WAAW;WAAe,aAAa;WAAQ,CAAA,CACnD;YACZ,EAAA,CAAA;SACP,kBAAC,GAAD;UAAe,iBAAgB;UAAM,QAAO;UAA6B,UAAU;UAAS,CAAA;SAC5F,kBAAC,GAAD;UAAO,SAAQ;UAAQ,MAAM;UAAU,UAAU;UAAO,UAAU;UAAS,CAAA;SAC3E,kBAAC,GAAD;UACE,MAAM;UACN,UAAU;UACV,UAAU;UACV,OAAO;UACP,gBAAgB,MAAc,EAAe,EAAE;UAC/C,CAAA;SACF,kBAAC,GAAD;UACE,cAAc;UACd,YAAY;UACZ,YAAY,GAAe,MAAS,CAAC,EAAe,EAAM,EAAE,EAAK;UACjE,CAAA;SACF,kBAAC,GAAD;UACE,MAAK;UACL,SAAQ;UACR,MAAK;UACL,QAAQ;UACR,aAAa;UACb,MAAK;UACL,CAAA;SACF,kBAAC,GAAD;UACE,MAAK;UACL,SAAQ;UACR,MAAK;UACL,QAAQ;UACR,aAAa;UACb,MAAK;UACL,CAAA;SACQ;;OACQ,CAAA;MAClB,CAAA;KACI,CAAA,EAGb,EAAiB,SAAS,KACzB,kBAAC,GAAD;KAAW,OAAM;KAA6B,aAAY;eAA1D,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAqB,OAAM;OAAO,QAAO;iBACvC,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,GAAD;QACE,MAAM;QACN,SAAQ;QACR,SAAQ;QACR,aAAa;QACb,aAAa;QACb,cAAc;QACd,QAAO;QACP,aAAa;kBAEZ,EAAiB,KAAK,MACrB,kBAAC,GAAD,EAAuB,MAAM,EAAM,OAAS,EAAjC,EAAM,KAA2B,CAC5C;QACE,CAAA,EACN,kBAAC,GAAD;QACE,cAAc;QACd,YAAY;QACZ,YAAY,GAAe,GAAI,MAAS,CACtC,GAAG,EAAe,EAAM,CAAC,KAAK,GAAM,SAAS,SAAS,EAAE,cACxD,GAAM,SAAS,KAChB;QACD,CAAA,CACO,EAAA,CAAA;OACS,CAAA,EACtB,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBAA8B;QAAkB,CAAA,EAChE,kBAAC,QAAD;QAAM,WAAU;kBAAuC,EAAe,EAAkB;QAAQ,CAAA,CAC5F;SACF;SACN,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAiB,KAAK,MACrB,kBAAC,MAAD;OAAqB,WAAU;iBAA/B,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CACE,kBAAC,QAAD;SAAM,WAAU;SAA2B,OAAO,EAAE,YAAY,EAAM,OAAO;SAAI,CAAA,EACjF,kBAAC,QAAD;SAAM,WAAU;mBAAqB,EAAM;SAAY,CAAA,CAClD;WACP,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CACG,IAAoB,IAAI,KAAK,MAAO,EAAM,QAAQ,IAAqB,IAAI,GAAG,GAAE,IAC5E;UACJ;SARI,EAAM,KAQV,CACL;MACC,CAAA,CACK;OAEV;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAc,SAAS,KACtB,kBAAC,GAAD;KACE,OAAM;KACN,aAAY;KACZ,WAAU;eAEV,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OAAqB,OAAM;OAAO,QAAO;iBACvC,kBAAC,IAAD;QAAW,MAAM;QAAe,QAAQ;SAAE,KAAK;SAAG,OAAO;SAAI,MAAM;SAAG,QAAQ;SAAG;kBAAjF;SACE,kBAAC,GAAD;UAAe,iBAAgB;UAAM,QAAO;UAA6B,UAAU;UAAS,CAAA;SAC5F,kBAAC,GAAD;UAAO,SAAQ;UAAQ,MAAM;UAAU,UAAU;UAAO,UAAU;UAAS,CAAA;SAC3E,kBAAC,GAAD;UACE,MAAM;UACN,UAAU;UACV,UAAU;UACV,OAAO;UACP,QAAQ,CAAC,GAAG,IAAI;UAChB,gBAAgB,MAAc,GAAG,EAAE;UACnC,CAAA;SACF,kBAAC,GAAD;UACE,cAAc;UACd,YAAY;UACZ,YAAY,MAAkB,CAAC,GAAG,EAAM,IAAI,YAAY;UACxD,CAAA;SACF,kBAAC,GAAD;UACE,MAAK;UACL,SAAQ;UACR,QAAO;UACP,aAAa;UACb,KAAK;WAAE,GAAG;WAAG,MAAM;WAAwB,aAAa;WAAG;UAC3D,WAAW,EAAE,GAAG,GAAG;UACnB,CAAA;SACQ;;OACQ,CAAA;MAClB,CAAA;KACI,CAAA,EAGb,EAAe,SAAS,KACvB,kBAAC,GAAD;KAAW,OAAM;KAAiB,aAAY;eAC5C,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;QAAG,WAAU;kBAA8B;QAAiB,CAAA,EAC5D,kBAAC,KAAD;QAAG,WAAU;kBAAuC,EAAe,EAAa;QAAK,CAAA,CACjF,EAAA,CAAA,EACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,KAAD;SAAG,WAAU;mBAA8B;SAAkB,CAAA,EAC7D,kBAAC,KAAD;SAAG,WAAU;mBAAuC,EAAe,EAAc;SAAK,CAAA,CAClF;UACF;UACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,KAAD;SAAG,WAAU;mBAA8C,EAAe,EAAU;SAAK,CAAA,EACzF,kBAAC,KAAD;SAAG,WAAU;mBAA8B;SAAc,CAAA,CACrD;WACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAb,CAAwD,GAAa,QAAQ,EAAE,EAAC,IAAK;YACrF,kBAAC,KAAD;SAAG,WAAU;mBAA8B;SAAiB,CAAA,CACxD;UACF;SACF;;KACI,CAAA,CAEV;;GAGL,EAAe,SAAS,KACvB,kBAAC,OAAD;IAAK,IAAG;IAAwB,WAAU;cAA1C,CACE,kBAAC,MAAD;KAAI,WAAU;eAA+C;KAAwB,CAAA,EACrF,kBAAC,GAAD;KAAW,SAAS;KAAgB,MAAM;KAAgB,UAAS;KAAQ,UAAU;KAAM,CAAA,CACvF;;GAEG"}
|
|
1
|
+
{"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../../src/housingvista/pages/analytics/AnalyticsPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { TrendingUp, DollarSign, Building, Activity, Download, Clock, BarChart3 } from 'lucide-react';\nimport {\n AreaChart,\n Area,\n LineChart,\n Line,\n PieChart,\n Pie,\n Cell,\n XAxis,\n YAxis,\n CartesianGrid,\n Tooltip,\n ResponsiveContainer,\n} from 'recharts';\nimport { EmphasisPanel, NextSteps } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components';\nimport { StatCard } from '../../components';\nimport { ChartCard } from '../../components';\nimport { DataTable, type Column } from '../../components';\nimport { EmptyState } from '../../components';\nimport { LoadingSkeleton } from '../../components';\nimport { useAnalyticsData } from '../../hooks';\nimport { useHousingVistaAnalytics, useTrackPageView } from '../../hooks/useHousingVistaAnalytics';\nimport { notify } from '../../lib/toast';\nimport { cn, formatCurrency, formatNumber } from '../../utils/helpers';\n\n// Distribution slice colours drawn from the semantic chart palette (full oklch tokens).\nconst CHART_COLORS = [\n 'var(--color-chart-1)',\n 'var(--color-chart-2)',\n 'var(--color-chart-3)',\n 'var(--color-chart-4)',\n 'var(--color-chart-5)',\n];\nconst REVENUE_COLOR = 'var(--color-chart-2)';\nconst EXPENSE_COLOR = 'var(--color-chart-1)';\n\n// Shared, theme-aware tooltip styling for every recharts surface on this page.\nconst tooltipContentStyle: React.CSSProperties = {\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-subtle)',\n borderRadius: 10,\n fontSize: 12,\n boxShadow: '0 8px 24px rgb(0 0 0 / 0.12)',\n};\nconst tooltipLabelStyle: React.CSSProperties = { color: 'var(--color-text-primary)', fontWeight: 600 };\nconst axisTick = { fontSize: 11, fill: 'var(--color-text-secondary)' } as const;\n\n// The backend series is monthly; this range control slices to the most recent N\n// months so the same real data can be viewed at different horizons (0 = all).\nconst rangeOptions: { value: number; label: string }[] = [\n { value: 3, label: '3M' },\n { value: 6, label: '6M' },\n { value: 12, label: '12M' },\n { value: 0, label: 'All' },\n];\n\nexport function AnalyticsPage() {\n useTrackPageView('housingvista.analytics.viewed');\n const { emitExport } = useHousingVistaAnalytics();\n const { data: analyticsData, loading } = useAnalyticsData();\n const [range, setRange] = useState<number>(12);\n\n // Client-side CSV export of the loaded analytics data (KPIs + monthly revenue).\n const handleExportCsv = () => {\n if (!analyticsData) return;\n\n const escapeCsv = (val: string | number) => {\n const s = String(val);\n return /[\",\\n]/.test(s) ? `\"${s.replace(/\"/g, '\"\"')}\"` : s;\n };\n\n const lines: string[] = [];\n lines.push('KPI,Value,Unit,Change %');\n for (const kpi of analyticsData.kpis) {\n lines.push([kpi.label, kpi.value, kpi.unit, kpi.changePercent].map(escapeCsv).join(','));\n }\n lines.push('');\n lines.push('Month,Revenue,Expenses,Net Income');\n for (const row of analyticsData.revenueByMonth) {\n lines.push([row.month, row.revenue, row.expenses, row.revenue - row.expenses].map(escapeCsv).join(','));\n }\n\n const blob = new Blob([lines.join('\\n')], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = `housingvista-analytics-${new Date().toISOString().slice(0, 10)}.csv`;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n\n emitExport('analytics');\n notify.success('Report exported', 'Your analytics data was downloaded as a CSV file.');\n };\n\n const revenueColumns: Column<{ month: string; revenue: number; expenses: number }>[] = [\n {\n key: 'month',\n header: 'Month',\n render: (item) => <span className=\"font-medium text-text-primary\">{item.month}</span>,\n },\n {\n key: 'revenue',\n header: 'Revenue',\n render: (item) => <span className=\"font-medium text-status-success-text\">{formatCurrency(item.revenue)}</span>,\n },\n {\n key: 'expenses',\n header: 'Expenses',\n render: (item) => <span className=\"font-medium text-status-error-text\">{formatCurrency(item.expenses)}</span>,\n },\n {\n key: 'net',\n header: 'Net Income',\n sortable: false,\n render: (item) => {\n const net = item.revenue - item.expenses;\n return (\n <span className={cn('font-medium', net >= 0 ? 'text-status-success-text' : 'text-status-error-text')}>\n {formatCurrency(net)}\n </span>\n );\n },\n },\n {\n key: 'margin',\n header: 'Margin',\n sortable: false,\n render: (item) => {\n const margin = item.revenue > 0 ? ((item.revenue - item.expenses) / item.revenue) * 100 : 0;\n return (\n <span className={cn('font-medium', margin >= 0 ? 'text-status-success-text' : 'text-status-error-text')}>\n {margin.toFixed(1)}%\n </span>\n );\n },\n },\n ];\n\n // ---- Loading ----\n if (loading) {\n return (\n <PageLayout title=\"Analytics\" description=\"Track your property portfolio performance and market insights\">\n <LoadingSkeleton className=\"h-28\" />\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <LoadingSkeleton className=\"h-64\" />\n <LoadingSkeleton className=\"h-64\" />\n </div>\n </PageLayout>\n );\n }\n\n // ---- Empty / not-yet-computed ----\n const hasData =\n analyticsData &&\n (analyticsData.kpis.length > 0 ||\n analyticsData.revenueByMonth.length > 0 ||\n analyticsData.propertyTypeDistribution.length > 0);\n\n if (!hasData) {\n return (\n <PageLayout title=\"Analytics\" description=\"Track your property portfolio performance and market insights\">\n <EmptyState\n illustration=\"empty-generic\"\n icon={<BarChart3 className=\"h-12 w-12\" />}\n title=\"No analytics yet\"\n description=\"Once you have properties, transactions, and rentals recorded, revenue, occupancy, and portfolio insights will appear here.\"\n />\n </PageLayout>\n );\n }\n\n const kpis = analyticsData.kpis;\n const propertyTypeDistribution = analyticsData.propertyTypeDistribution;\n\n // Range control slices the real monthly series to its most recent N entries.\n const sliceRange = <T,>(rows: T[]): T[] => (range > 0 ? rows.slice(-range) : rows);\n const revenueByMonth = sliceRange(analyticsData.revenueByMonth);\n const occupancyRate = sliceRange(analyticsData.occupancyRate);\n\n const revenueChartData = revenueByMonth.map((r) => ({\n month: r.month,\n revenue: r.revenue,\n expenses: r.expenses,\n net: r.revenue - r.expenses,\n }));\n const distributionTotal = propertyTypeDistribution.reduce((s, d) => s + d.value, 0);\n const distributionData = propertyTypeDistribution.map((d, idx) => ({\n name: d.type.charAt(0).toUpperCase() + d.type.slice(1),\n value: d.value,\n count: d.count,\n color: CHART_COLORS[idx % CHART_COLORS.length],\n }));\n\n const totalRevenue = revenueByMonth.reduce((s, r) => s + r.revenue, 0);\n const totalExpenses = revenueByMonth.reduce((s, r) => s + r.expenses, 0);\n const netIncome = totalRevenue - totalExpenses;\n const profitMargin = totalRevenue > 0 ? (netIncome / totalRevenue) * 100 : 0;\n\n return (\n <PageLayout\n title=\"Analytics\"\n description=\"Track your property portfolio performance and market insights\"\n purpose=\"Understand how your portfolio is actually performing — revenue versus expenses, occupancy, property-type mix, and profitability — so you can spot trends, compare periods, and decide where to buy, sell, or raise rents.\"\n actions={\n <button\n onClick={handleExportCsv}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Download className=\"h-4 w-4\" />\n Export Report\n </button>\n }\n >\n <NextSteps\n storageKey=\"housingvista-analytics\"\n steps={[\n {\n id: 'performance',\n label: 'Drill into monthly performance',\n description: 'Review revenue, expenses, net income, and margin month by month.',\n href: '#analytics-performance',\n },\n ]}\n />\n\n {/* Period range control — slices the real monthly series to a horizon */}\n <div className=\"flex flex-wrap items-center gap-3\">\n <span className=\"text-sm font-medium text-text-secondary\">Period</span>\n <div\n role=\"group\"\n aria-label=\"Select analytics period\"\n className=\"inline-flex rounded-lg border border-border-subtle bg-bg-sunken/60 p-1\"\n >\n {rangeOptions.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n aria-pressed={range === option.value}\n onClick={() => setRange(option.value)}\n className={cn(\n 'rounded-md px-3 py-1.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n range === option.value\n ? 'bg-action-primary-bg text-action-primary-text shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n )}\n >\n {option.label}\n </button>\n ))}\n </div>\n </div>\n\n {/* Stats Overview Row — primary insight zone (one EmphasisPanel per page) */}\n {kpis.length > 0 && (\n <EmphasisPanel className=\"p-4\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {kpis.map((kpi) => {\n const icons: Record<string, typeof DollarSign> = {\n 'Portfolio Value': DollarSign,\n 'Monthly Revenue': TrendingUp,\n 'Occupancy Rate': Building,\n 'Avg. Days to Rent': Clock,\n };\n const IconComponent = icons[kpi.label] || Activity;\n\n return (\n <StatCard\n key={kpi.id}\n label={kpi.label}\n value={\n kpi.unit === 'USD'\n ? kpi.value\n : `${formatNumber(kpi.value)}${kpi.unit === '%' ? '%' : ` ${kpi.unit}`}`\n }\n prefix={kpi.unit === 'USD' ? '$' : undefined}\n icon={<IconComponent className=\"h-4 w-4\" />}\n trend={kpi.trend === 'up' ? 'up' : kpi.trend === 'down' ? 'down' : 'stable'}\n change={kpi.changePercent}\n />\n );\n })}\n </div>\n </EmphasisPanel>\n )}\n\n {/* Charts Section — revenue area chart (wide) + property-type donut (aside) */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {revenueChartData.length > 0 && (\n <ChartCard\n title=\"Revenue vs Expenses\"\n description=\"Real income against costs across the selected period\"\n className=\"lg:col-span-2\"\n actions={\n <div className=\"flex items-center gap-4 text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1.5\">\n <span className=\"h-2.5 w-2.5 rounded-full\" style={{ background: REVENUE_COLOR }} />\n Revenue\n </span>\n <span className=\"flex items-center gap-1.5\">\n <span className=\"h-2.5 w-2.5 rounded-full\" style={{ background: EXPENSE_COLOR }} />\n Expenses\n </span>\n </div>\n }\n >\n <div className=\"h-72 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <AreaChart data={revenueChartData} margin={{ top: 8, right: 8, left: 0, bottom: 0 }}>\n <defs>\n <linearGradient id=\"revGrad\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"5%\" stopColor={REVENUE_COLOR} stopOpacity={0.35} />\n <stop offset=\"95%\" stopColor={REVENUE_COLOR} stopOpacity={0.02} />\n </linearGradient>\n <linearGradient id=\"expGrad\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"5%\" stopColor={EXPENSE_COLOR} stopOpacity={0.3} />\n <stop offset=\"95%\" stopColor={EXPENSE_COLOR} stopOpacity={0.02} />\n </linearGradient>\n </defs>\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--color-border-subtle)\" vertical={false} />\n <XAxis dataKey=\"month\" tick={axisTick} tickLine={false} axisLine={false} />\n <YAxis\n tick={axisTick}\n tickLine={false}\n axisLine={false}\n width={52}\n tickFormatter={(v: number) => formatCurrency(v)}\n />\n <Tooltip\n contentStyle={tooltipContentStyle}\n labelStyle={tooltipLabelStyle}\n formatter={(value: number, name) => [formatCurrency(value), name]}\n />\n <Area\n type=\"monotone\"\n dataKey=\"revenue\"\n name=\"Revenue\"\n stroke={REVENUE_COLOR}\n strokeWidth={2.5}\n fill=\"url(#revGrad)\"\n />\n <Area\n type=\"monotone\"\n dataKey=\"expenses\"\n name=\"Expenses\"\n stroke={EXPENSE_COLOR}\n strokeWidth={2.5}\n fill=\"url(#expGrad)\"\n />\n </AreaChart>\n </ResponsiveContainer>\n </div>\n </ChartCard>\n )}\n\n {distributionData.length > 0 && (\n <ChartCard title=\"Property-Type Distribution\" description=\"Portfolio value split by type\">\n <div className=\"relative h-56 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <PieChart>\n <Pie\n data={distributionData}\n dataKey=\"value\"\n nameKey=\"name\"\n innerRadius={58}\n outerRadius={84}\n paddingAngle={2}\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={2}\n >\n {distributionData.map((entry) => (\n <Cell key={entry.name} fill={entry.color} />\n ))}\n </Pie>\n <Tooltip\n contentStyle={tooltipContentStyle}\n labelStyle={tooltipLabelStyle}\n formatter={(value: number, _n, item) => [\n `${formatCurrency(value)} · ${item?.payload?.count ?? 0} properties`,\n item?.payload?.name,\n ]}\n />\n </PieChart>\n </ResponsiveContainer>\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center\">\n <span className=\"text-xs text-text-secondary\">Total value</span>\n <span className=\"text-lg font-bold text-text-primary\">{formatCurrency(distributionTotal)}</span>\n </div>\n </div>\n <ul className=\"mt-3 space-y-2\">\n {distributionData.map((entry) => (\n <li key={entry.name} className=\"flex items-center justify-between text-sm\">\n <span className=\"flex items-center gap-2\">\n <span className=\"h-2.5 w-2.5 rounded-full\" style={{ background: entry.color }} />\n <span className=\"text-text-primary\">{entry.name}</span>\n </span>\n <span className=\"text-text-secondary\">\n {distributionTotal > 0 ? Math.round((entry.value / distributionTotal) * 100) : 0}%\n </span>\n </li>\n ))}\n </ul>\n </ChartCard>\n )}\n </div>\n\n {/* Occupancy trend (wide) + quick stats (aside) */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {occupancyRate.length > 0 && (\n <ChartCard\n title=\"Occupancy Trend\"\n description=\"Share of units occupied over the selected period\"\n className=\"lg:col-span-2\"\n >\n <div className=\"h-56 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <LineChart data={occupancyRate} margin={{ top: 8, right: 12, left: 0, bottom: 0 }}>\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--color-border-subtle)\" vertical={false} />\n <XAxis dataKey=\"month\" tick={axisTick} tickLine={false} axisLine={false} />\n <YAxis\n tick={axisTick}\n tickLine={false}\n axisLine={false}\n width={40}\n domain={[0, 100]}\n tickFormatter={(v: number) => `${v}%`}\n />\n <Tooltip\n contentStyle={tooltipContentStyle}\n labelStyle={tooltipLabelStyle}\n formatter={(value: number) => [`${value}%`, 'Occupancy']}\n />\n <Line\n type=\"monotone\"\n dataKey=\"rate\"\n stroke=\"var(--color-chart-2)\"\n strokeWidth={2.5}\n dot={{ r: 3, fill: 'var(--color-chart-2)', strokeWidth: 0 }}\n activeDot={{ r: 5 }}\n />\n </LineChart>\n </ResponsiveContainer>\n </div>\n </ChartCard>\n )}\n\n {revenueByMonth.length > 0 && (\n <ChartCard title=\"Period Summary\" description=\"Totals across the selected range\">\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between rounded-lg bg-bg-sunken/50 p-3\">\n <div>\n <p className=\"text-sm text-text-secondary\">Total Revenue</p>\n <p className=\"text-lg font-bold text-text-primary\">{formatCurrency(totalRevenue)}</p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm text-text-secondary\">Total Expenses</p>\n <p className=\"text-lg font-bold text-text-primary\">{formatCurrency(totalExpenses)}</p>\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"rounded-lg bg-status-success-bg-subtle p-3 text-center\">\n <p className=\"text-xl font-bold text-status-success-text\">{formatCurrency(netIncome)}</p>\n <p className=\"text-xs text-text-secondary\">Net Income</p>\n </div>\n <div className=\"rounded-lg bg-status-info-bg-subtle p-3 text-center\">\n <p className=\"text-xl font-bold text-status-info-text\">{profitMargin.toFixed(1)}%</p>\n <p className=\"text-xs text-text-secondary\">Profit Margin</p>\n </div>\n </div>\n </div>\n </ChartCard>\n )}\n </div>\n\n {/* Performance Metrics Table */}\n {revenueByMonth.length > 0 && (\n <div id=\"analytics-performance\" className=\"scroll-mt-6\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Monthly Performance</h2>\n <DataTable columns={revenueColumns} data={revenueByMonth} keyField=\"month\" pageSize={12} />\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HousingVista Asset Detail Page
|
|
3
|
+
* Full performance view for a single owned asset backed by the single-item
|
|
4
|
+
* `housingvistaAsset(id)` query — current value, appreciation, rental yield,
|
|
5
|
+
* ROI, and purchase history. Read-only: no fabricated actions.
|
|
6
|
+
* @module housingvista
|
|
7
|
+
*/
|
|
8
|
+
export declare function AssetDetailPage(): import("react").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=AssetDetailPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AssetDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/assets/AssetDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAwCH,wBAAgB,eAAe,gCAwQ9B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AssetManagementPage.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/assets/AssetManagementPage.tsx"],"names":[],"mappings":"AAqHA,wBAAgB,mBAAmB,gCAipBlC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssetManagementPage.js","names":[],"sources":["../../../../src/housingvista/pages/assets/AssetManagementPage.tsx"],"sourcesContent":["import { useState, useMemo, useEffect, type FormEvent } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n Building,\n DollarSign,\n TrendingUp,\n BarChart3,\n Plus,\n Download,\n ArrowUpRight,\n ArrowDownRight,\n MapPin,\n Eye,\n ImageOff,\n Percent,\n Target,\n} from 'lucide-react';\nimport { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts';\nimport {\n EmphasisPanel,\n NextSteps,\n Button,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components';\nimport { StatCard } from '../../components';\nimport { ChartCard } from '../../components';\nimport { DataTable, type Column } from '../../components';\nimport { StatusBadge } from '../../components';\nimport { FilterBar } from '../../components';\nimport { EmptyState } from '../../components';\nimport { useAssets, useCreateAsset } from '../../hooks';\nimport { useServerPagination } from '../../hooks/useServerPagination';\nimport { notify } from '../../lib/toast';\nimport { cn, formatCurrency } from '../../utils/helpers';\nimport type { Asset, PropertyType, PropertyCategory } from '../../types';\n\nconst inputClass =\n 'h-10 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\n\nconst CHART_COLORS = ['var(--color-chart-1)', 'var(--color-chart-2)', 'var(--color-chart-3)', 'var(--color-chart-4)', 'var(--color-chart-5)'];\nconst assetTooltipStyle: React.CSSProperties = {\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-subtle)',\n borderRadius: 10,\n fontSize: 12,\n boxShadow: '0 8px 24px rgb(0 0 0 / 0.12)',\n};\n\n/** Branded image with a tasteful gradient placeholder when no image is available. */\nfunction AssetImage({ src, alt, className }: { src?: string | null; alt: string; className?: string }) {\n if (src) {\n return <img src={src} alt={alt} className={cn('object-cover', className)} />;\n }\n return (\n <div\n className={cn('flex items-center justify-center bg-gradient-to-br from-bg-sunken to-bg-sunken/50', className)}\n aria-label={`${alt} — no image`}\n role=\"img\"\n >\n <ImageOff className=\"h-8 w-8 text-text-secondary/40\" />\n </div>\n );\n}\n\nconst assetTypeOptions: { value: PropertyType; label: string }[] = [\n { value: 'residential', label: 'Residential' },\n { value: 'commercial', label: 'Commercial' },\n { value: 'industrial', label: 'Industrial' },\n { value: 'agricultural', label: 'Agricultural' },\n { value: 'mixed_use', label: 'Mixed use' },\n];\n\nconst assetCategoryOptions: { value: PropertyCategory; label: string }[] = [\n { value: 'apartment', label: 'Apartment' },\n { value: 'house', label: 'House' },\n { value: 'villa', label: 'Villa' },\n { value: 'office', label: 'Office' },\n { value: 'retail', label: 'Retail' },\n { value: 'warehouse', label: 'Warehouse' },\n { value: 'land', label: 'Land' },\n { value: 'plot', label: 'Plot' },\n];\n\nconst assetStatusOptions: { value: Asset['status']; label: string }[] = [\n { value: 'active', label: 'Active' },\n { value: 'under_renovation', label: 'Under renovation' },\n { value: 'pending_sale', label: 'Pending sale' },\n { value: 'sold', label: 'Sold' },\n];\n\nfunction getStatusVariant(status: Asset['status']): 'success' | 'warning' | 'info' | 'muted' {\n switch (status) {\n case 'active':\n return 'success';\n case 'under_renovation':\n return 'warning';\n case 'pending_sale':\n return 'info';\n case 'sold':\n return 'muted';\n default:\n return 'muted';\n }\n}\n\nexport function AssetManagementPage() {\n const navigate = useNavigate();\n const pagination = useServerPagination(10);\n const [search, setSearch] = useState('');\n const [debouncedSearch, setDebouncedSearch] = useState('');\n\n // Debounce the raw search input into the value that feeds the server filter.\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedSearch(search), 300);\n return () => clearTimeout(timer);\n }, [search]);\n\n const { data: assets, loading } = useAssets({\n filter: { search: debouncedSearch.trim() || undefined },\n pagination: pagination.pagination,\n });\n const { createAsset, loading: creating } = useCreateAsset();\n\n const handleSearchChange = (value: string) => {\n setSearch(value);\n pagination.setPage(1);\n };\n\n // Add-asset dialog state\n const [createOpen, setCreateOpen] = useState(false);\n const [title, setTitle] = useState('');\n const [type, setType] = useState<PropertyType>('residential');\n const [category, setCategory] = useState<PropertyCategory>('apartment');\n const [location, setLocation] = useState('');\n const [currentValue, setCurrentValue] = useState('');\n const [purchasePrice, setPurchasePrice] = useState('');\n const [purchaseDate, setPurchaseDate] = useState('');\n const [rentalYield, setRentalYield] = useState('');\n const [imageUrl, setImageUrl] = useState('');\n const [status, setStatus] = useState<Asset['status']>('active');\n\n const resetForm = () => {\n setTitle('');\n setType('residential');\n setCategory('apartment');\n setLocation('');\n setCurrentValue('');\n setPurchasePrice('');\n setPurchaseDate('');\n setRentalYield('');\n setImageUrl('');\n setStatus('active');\n };\n\n const openCreate = () => {\n resetForm();\n setCreateOpen(true);\n };\n\n const handleCreate = async (e: FormEvent) => {\n e.preventDefault();\n if (!title.trim()) {\n notify.warning('Enter an asset title');\n return;\n }\n if (!location.trim()) {\n notify.warning('Enter a location');\n return;\n }\n const current = Number(currentValue);\n const purchase = Number(purchasePrice);\n if (!Number.isFinite(current) || current <= 0) {\n notify.warning('Enter a valid current value');\n return;\n }\n if (!Number.isFinite(purchase) || purchase <= 0) {\n notify.warning('Enter a valid purchase price');\n return;\n }\n const yieldValue = rentalYield.trim() ? Number(rentalYield) : undefined;\n if (yieldValue !== undefined && !Number.isFinite(yieldValue)) {\n notify.warning('Enter a valid rental yield');\n return;\n }\n // Appreciation is derived from the real purchase vs. current value, never fabricated.\n const appreciationPercent = purchase > 0 ? ((current - purchase) / purchase) * 100 : 0;\n try {\n await createAsset({\n title: title.trim(),\n type,\n category,\n location: location.trim(),\n currentValue: current,\n purchasePrice: purchase,\n purchaseDate: purchaseDate ? new Date(purchaseDate).toISOString() : undefined,\n appreciationPercent: Number(appreciationPercent.toFixed(2)),\n rentalYield: yieldValue,\n status,\n imageUrl: imageUrl.trim() || undefined,\n });\n notify.success('Asset added', `${title.trim()} was added to your portfolio.`);\n setCreateOpen(false);\n resetForm();\n } catch (err) {\n notify.error('Could not add asset', (err as Error).message);\n }\n };\n\n const handleExport = () => {\n if (assets.length === 0) {\n notify.warning('Nothing to export', 'There are no assets to export yet.');\n return;\n }\n const escape = (value: string) => `\"${value.replace(/\"/g, '\"\"')}\"`;\n const headers = [\n 'Title',\n 'Type',\n 'Category',\n 'Location',\n 'Purchase Price',\n 'Current Value',\n 'Appreciation %',\n 'Rental Yield %',\n 'Status',\n ];\n const rows = assets.map((a) =>\n [\n a.title,\n a.type,\n a.category,\n a.location,\n String(a.purchasePrice),\n String(a.currentValue),\n String(a.appreciationPercent),\n a.rentalYield != null ? String(a.rentalYield) : '',\n a.status,\n ]\n .map((cell) => escape(String(cell)))\n .join(',')\n );\n const csv = [headers.map(escape).join(','), ...rows].join('\\n');\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = `housingvista-assets-${new Date().toISOString().slice(0, 10)}.csv`;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n notify.success('Export ready', `${assets.length} assets exported to CSV.`);\n };\n\n const portfolioStats = useMemo(() => {\n const totalValue = assets.reduce((sum, a) => sum + a.currentValue, 0);\n const totalMonthlyIncome = assets\n .filter((a) => a.rentalYield)\n .reduce((sum, a) => sum + (a.currentValue * (a.rentalYield || 0)) / 100 / 12, 0);\n const avgAppreciation =\n assets.length > 0 ? assets.reduce((sum, a) => sum + a.appreciationPercent, 0) / assets.length : 0;\n const totalPurchasePrice = assets.reduce((sum, a) => sum + a.purchasePrice, 0);\n const totalROI = totalPurchasePrice > 0 ? ((totalValue - totalPurchasePrice) / totalPurchasePrice) * 100 : 0;\n return { totalValue, totalMonthlyIncome, avgAppreciation, totalROI };\n }, [assets]);\n\n // Portfolio value split by property type — real current values, never fabricated.\n const valueByType = useMemo(() => {\n const byType = new Map<string, number>();\n for (const asset of assets) {\n byType.set(asset.type, (byType.get(asset.type) ?? 0) + asset.currentValue);\n }\n const rows = Array.from(byType.entries())\n .filter(([, value]) => value > 0)\n .map(([type, value], idx) => ({\n type,\n label: type.charAt(0).toUpperCase() + type.slice(1).replace('_', ' '),\n value,\n color: CHART_COLORS[idx % CHART_COLORS.length],\n }))\n .sort((a, b) => b.value - a.value);\n const total = rows.reduce((s, r) => s + r.value, 0);\n return { rows, total };\n }, [assets]);\n\n const assetColumns: Column<Asset>[] = [\n {\n key: 'title',\n header: 'Property',\n render: (item) => (\n <div className=\"flex items-center gap-3\">\n <AssetImage src={item.imageUrl} alt={item.title} className=\"h-10 w-10 shrink-0 rounded-md\" />\n <div>\n <div className=\"font-medium text-text-primary\">{item.title}</div>\n <div className=\"flex items-center gap-1 text-xs text-text-secondary\">\n <MapPin className=\"h-3 w-3\" />\n {item.location}\n </div>\n </div>\n </div>\n ),\n },\n {\n key: 'type',\n header: 'Type',\n render: (item) => <span className=\"text-sm capitalize text-text-primary\">{item.type}</span>,\n },\n {\n key: 'purchasePrice',\n header: 'Purchase Price',\n render: (item) => <span className=\"text-sm text-text-primary\">{formatCurrency(item.purchasePrice)}</span>,\n },\n {\n key: 'currentValue',\n header: 'Current Value',\n render: (item) => (\n <span className=\"text-sm font-medium text-text-primary\">{formatCurrency(item.currentValue)}</span>\n ),\n },\n {\n key: 'appreciationPercent',\n header: 'Change',\n render: (item) => {\n const change = item.currentValue - item.purchasePrice;\n const isPositive = change >= 0;\n return (\n <div\n className={cn(\n 'flex items-center gap-1 text-sm font-medium',\n isPositive ? 'text-status-success-text' : 'text-status-error-text'\n )}\n >\n {isPositive ? <ArrowUpRight className=\"h-3 w-3\" /> : <ArrowDownRight className=\"h-3 w-3\" />}\n {formatCurrency(Math.abs(change))}\n <span className=\"text-xs\">({item.appreciationPercent}%)</span>\n </div>\n );\n },\n },\n {\n key: 'status',\n header: 'Status',\n render: (item) => <StatusBadge label={item.status.replace('_', ' ')} variant={getStatusVariant(item.status)} />,\n },\n {\n key: 'actions',\n header: '',\n sortable: false,\n render: (item) => (\n <button\n onClick={(e) => {\n e.stopPropagation();\n navigate(`../assets/${item.id}`);\n }}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-2 py-1 text-xs font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Eye className=\"h-3 w-3\" />\n View\n </button>\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"Asset Management\"\n description=\"Monitor and manage your property portfolio performance\"\n purpose=\"See how every property you own is performing as an investment — current value, appreciation, rental yield, and overall ROI in one portfolio view. Use it to decide what to hold, renovate, or sell.\"\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n onClick={handleExport}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Download className=\"h-4 w-4\" />\n Export\n </button>\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg-hover\"\n >\n <Plus className=\"h-4 w-4\" />\n Add Asset\n </button>\n </div>\n }\n >\n {/* Portfolio Value Stats — primary insight zone (one EmphasisPanel per page) */}\n <EmphasisPanel className=\"p-4\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n label=\"Total Value\"\n value={portfolioStats.totalValue}\n prefix=\"$\"\n icon={<DollarSign className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Monthly Income\"\n value={Math.round(portfolioStats.totalMonthlyIncome)}\n prefix=\"$\"\n icon={<TrendingUp className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Avg. Appreciation\"\n value={`${portfolioStats.avgAppreciation.toFixed(1)}%`}\n icon={<BarChart3 className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Overall ROI\"\n value={`${portfolioStats.totalROI.toFixed(1)}%`}\n icon={<Building className=\"h-4 w-4\" />}\n />\n </div>\n </EmphasisPanel>\n\n <NextSteps\n storageKey=\"housingvista-assets\"\n steps={[\n {\n id: 'open-asset',\n label: 'Open an asset to see full performance',\n description: 'Drill into value history, yield, and ROI for any property.',\n onClick: () => {\n const first = assets[0];\n if (first) navigate(`../assets/${first.id}`);\n },\n },\n {\n id: 'compare-market',\n label: 'Compare against market trends',\n description: 'Check market analytics to benchmark your portfolio.',\n onClick: () => navigate('../market-analytics'),\n },\n {\n id: 'add-asset',\n label: 'Add a property you already own',\n description: 'Bring an existing holding into your portfolio view.',\n onClick: openCreate,\n },\n ]}\n />\n\n {/* Portfolio overview: rich asset cards (left) + value-by-type donut (aside) */}\n {assets.length > 0 && (\n <div className=\"grid grid-cols-1 gap-6 xl:grid-cols-3\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:col-span-2\">\n {assets.map((asset) => {\n const change = asset.currentValue - asset.purchasePrice;\n const isPositive = change >= 0;\n const roi = asset.purchasePrice > 0 ? (change / asset.purchasePrice) * 100 : 0;\n return (\n <button\n key={asset.id}\n type=\"button\"\n onClick={() => navigate(`../assets/${asset.id}`)}\n className=\"group overflow-hidden rounded-card border border-border-seam bg-bg-surface text-left shadow-elevation-1 transition-[box-shadow,border-color] hover:border-border-strong hover:shadow-elevation-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <div className=\"relative aspect-[16/9] overflow-hidden\">\n <AssetImage src={asset.imageUrl} alt={asset.title} className=\"h-full w-full\" />\n <div className=\"absolute left-3 top-3\">\n <StatusBadge\n label={asset.status.replace('_', ' ')}\n variant={getStatusVariant(asset.status)}\n dot\n />\n </div>\n <div\n className={cn(\n 'absolute right-3 top-3 inline-flex items-center gap-0.5 rounded-full px-2 py-0.5 text-xs font-semibold backdrop-blur-sm',\n isPositive\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-status-error-bg-subtle text-status-error-text'\n )}\n >\n {isPositive ? <ArrowUpRight className=\"h-3 w-3\" /> : <ArrowDownRight className=\"h-3 w-3\" />}\n {asset.appreciationPercent}%\n </div>\n </div>\n <div className=\"p-4\">\n <h3 className=\"truncate font-medium text-text-primary\">{asset.title}</h3>\n <p className=\"mt-0.5 flex items-center gap-1 text-xs text-text-secondary\">\n <MapPin className=\"h-3 w-3 shrink-0\" />\n <span className=\"truncate\">{asset.location}</span>\n </p>\n <div className=\"mt-3 flex items-end justify-between\">\n <div>\n <p className=\"text-xs text-text-secondary\">Current Value</p>\n <p className=\"text-lg font-bold text-text-primary\">{formatCurrency(asset.currentValue)}</p>\n </div>\n <span className=\"rounded-md bg-bg-sunken px-2 py-1 text-xs font-medium capitalize text-text-secondary\">\n {asset.category}\n </span>\n </div>\n <div className=\"mt-3 grid grid-cols-2 gap-3 border-t border-border-subtle pt-3 text-xs\">\n <div className=\"flex items-center gap-1.5 text-text-secondary\">\n <Percent className=\"h-3.5 w-3.5\" />\n <span>Yield</span>\n <span className=\"ml-auto font-semibold text-text-primary\">\n {asset.rentalYield != null ? `${asset.rentalYield}%` : '—'}\n </span>\n </div>\n <div className=\"flex items-center gap-1.5 text-text-secondary\">\n <Target className=\"h-3.5 w-3.5\" />\n <span>ROI</span>\n <span\n className={cn(\n 'ml-auto font-semibold',\n roi >= 0 ? 'text-status-success-text' : 'text-status-error-text'\n )}\n >\n {roi.toFixed(1)}%\n </span>\n </div>\n </div>\n </div>\n </button>\n );\n })}\n </div>\n\n {valueByType.rows.length > 0 && (\n <aside className=\"xl:col-span-1\">\n <ChartCard title=\"Portfolio by Type\" description=\"Current value split across property types\">\n <div className=\"relative h-52 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <PieChart>\n <Pie\n data={valueByType.rows}\n dataKey=\"value\"\n nameKey=\"label\"\n innerRadius={56}\n outerRadius={82}\n paddingAngle={2}\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={2}\n >\n {valueByType.rows.map((entry) => (\n <Cell key={entry.type} fill={entry.color} />\n ))}\n </Pie>\n <Tooltip\n contentStyle={assetTooltipStyle}\n formatter={(value: number, _n, item) => [formatCurrency(value), item?.payload?.label]}\n />\n </PieChart>\n </ResponsiveContainer>\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center\">\n <span className=\"text-xs text-text-secondary\">Total value</span>\n <span className=\"text-lg font-bold text-text-primary\">{formatCurrency(valueByType.total)}</span>\n </div>\n </div>\n <ul className=\"mt-4 space-y-2.5\">\n {valueByType.rows.map((entry) => (\n <li key={entry.type} className=\"flex items-center justify-between text-sm\">\n <span className=\"flex items-center gap-2\">\n <span className=\"h-2.5 w-2.5 rounded-full\" style={{ background: entry.color }} />\n <span className=\"text-text-primary\">{entry.label}</span>\n </span>\n <span className=\"font-medium text-text-secondary\">\n {valueByType.total > 0 ? Math.round((entry.value / valueByType.total) * 100) : 0}%\n </span>\n </li>\n ))}\n </ul>\n </ChartCard>\n </aside>\n )}\n </div>\n )}\n\n {/* Asset Table */}\n <div>\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">All Assets</h2>\n <FilterBar\n searchValue={search}\n onSearchChange={handleSearchChange}\n searchPlaceholder=\"Search assets by name, location, or type...\"\n />\n <div className=\"mt-4\">\n {assets.length > 0 ? (\n <DataTable\n columns={assetColumns}\n data={assets}\n keyField=\"id\"\n onRowClick={(item) => navigate(`../assets/${item.id}`)}\n pageSize={10}\n serverPagination={{\n page: pagination.page,\n pageSize: pagination.pageSize,\n hasNextPage: assets.length >= pagination.pageSize,\n loading,\n onPageChange: pagination.setPage,\n onPageSizeChange: pagination.setPageSize,\n }}\n />\n ) : (\n <EmptyState\n illustration=\"empty-search\"\n icon={<Building className=\"h-12 w-12\" />}\n title=\"No assets found\"\n description=\"Try adjusting your search criteria or add a new asset.\"\n action={\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg-hover\"\n >\n <Plus className=\"h-4 w-4\" />\n Add Asset\n </button>\n }\n />\n )}\n </div>\n </div>\n\n {/* Add Asset dialog */}\n <ResponsiveDialog open={createOpen} onOpenChange={setCreateOpen}>\n <ResponsiveDialogContent className=\"sm:max-w-lg\">\n <form onSubmit={handleCreate}>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add asset</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Bring a property you own into your portfolio to track its value and performance.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3 py-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Title *</label>\n <input\n type=\"text\"\n required\n placeholder=\"e.g. Riverside Villa\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Location *</label>\n <input\n type=\"text\"\n required\n placeholder=\"City, State\"\n value={location}\n onChange={(e) => setLocation(e.target.value)}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Type</label>\n <select value={type} onChange={(e) => setType(e.target.value as PropertyType)} className={inputClass}>\n {assetTypeOptions.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Category</label>\n <select\n value={category}\n onChange={(e) => setCategory(e.target.value as PropertyCategory)}\n className={inputClass}\n >\n {assetCategoryOptions.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Purchase price (USD) *</label>\n <input\n type=\"number\"\n required\n min={1}\n step=\"any\"\n placeholder=\"0\"\n value={purchasePrice}\n onChange={(e) => setPurchasePrice(e.target.value)}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Current value (USD) *</label>\n <input\n type=\"number\"\n required\n min={1}\n step=\"any\"\n placeholder=\"0\"\n value={currentValue}\n onChange={(e) => setCurrentValue(e.target.value)}\n className={inputClass}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-3\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Purchase date</label>\n <input\n type=\"date\"\n value={purchaseDate}\n onChange={(e) => setPurchaseDate(e.target.value)}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Rental yield (%)</label>\n <input\n type=\"number\"\n min={0}\n step=\"any\"\n placeholder=\"0\"\n value={rentalYield}\n onChange={(e) => setRentalYield(e.target.value)}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Status</label>\n <select\n value={status}\n onChange={(e) => setStatus(e.target.value as Asset['status'])}\n className={inputClass}\n >\n {assetStatusOptions.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Image URL</label>\n <input\n type=\"url\"\n placeholder=\"https://…\"\n value={imageUrl}\n onChange={(e) => setImageUrl(e.target.value)}\n className={inputClass}\n />\n </div>\n </div>\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setCreateOpen(false)}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={creating || !title.trim() || !location.trim()}>\n {creating ? 'Adding…' : 'Add asset'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AssetManagementPage.js","names":[],"sources":["../../../../src/housingvista/pages/assets/AssetManagementPage.tsx"],"sourcesContent":["import { useState, useMemo, useEffect, type FormEvent } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n Building,\n DollarSign,\n TrendingUp,\n BarChart3,\n Plus,\n Download,\n ArrowUpRight,\n ArrowDownRight,\n MapPin,\n Eye,\n ImageOff,\n Percent,\n Target,\n} from 'lucide-react';\nimport { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts';\nimport {\n EmphasisPanel,\n NextSteps,\n Button,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components';\nimport { StatCard } from '../../components';\nimport { ChartCard } from '../../components';\nimport { DataTable, type Column } from '../../components';\nimport { StatusBadge } from '../../components';\nimport { FilterBar } from '../../components';\nimport { EmptyState } from '../../components';\nimport { useAssets, useCreateAsset } from '../../hooks';\nimport { useServerPagination } from '../../hooks/useServerPagination';\nimport { notify } from '../../lib/toast';\nimport { cn, formatCurrency } from '../../utils/helpers';\nimport type { Asset, PropertyType, PropertyCategory } from '../../types';\n\nconst inputClass =\n 'h-10 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\n\nconst CHART_COLORS = [\n 'var(--color-chart-1)',\n 'var(--color-chart-2)',\n 'var(--color-chart-3)',\n 'var(--color-chart-4)',\n 'var(--color-chart-5)',\n];\nconst assetTooltipStyle: React.CSSProperties = {\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-subtle)',\n borderRadius: 10,\n fontSize: 12,\n boxShadow: '0 8px 24px rgb(0 0 0 / 0.12)',\n};\n\n/** Branded image with a tasteful gradient placeholder when no image is available. */\nfunction AssetImage({ src, alt, className }: { src?: string | null; alt: string; className?: string }) {\n if (src) {\n return <img src={src} alt={alt} className={cn('object-cover', className)} />;\n }\n return (\n <div\n className={cn('flex items-center justify-center bg-gradient-to-br from-bg-sunken to-bg-sunken/50', className)}\n aria-label={`${alt} — no image`}\n role=\"img\"\n >\n <ImageOff className=\"h-8 w-8 text-text-secondary/40\" />\n </div>\n );\n}\n\nconst assetTypeOptions: { value: PropertyType; label: string }[] = [\n { value: 'residential', label: 'Residential' },\n { value: 'commercial', label: 'Commercial' },\n { value: 'industrial', label: 'Industrial' },\n { value: 'agricultural', label: 'Agricultural' },\n { value: 'mixed_use', label: 'Mixed use' },\n];\n\nconst assetCategoryOptions: { value: PropertyCategory; label: string }[] = [\n { value: 'apartment', label: 'Apartment' },\n { value: 'house', label: 'House' },\n { value: 'villa', label: 'Villa' },\n { value: 'office', label: 'Office' },\n { value: 'retail', label: 'Retail' },\n { value: 'warehouse', label: 'Warehouse' },\n { value: 'land', label: 'Land' },\n { value: 'plot', label: 'Plot' },\n];\n\nconst assetStatusOptions: { value: Asset['status']; label: string }[] = [\n { value: 'active', label: 'Active' },\n { value: 'under_renovation', label: 'Under renovation' },\n { value: 'pending_sale', label: 'Pending sale' },\n { value: 'sold', label: 'Sold' },\n];\n\nfunction getStatusVariant(status: Asset['status']): 'success' | 'warning' | 'info' | 'muted' {\n switch (status) {\n case 'active':\n return 'success';\n case 'under_renovation':\n return 'warning';\n case 'pending_sale':\n return 'info';\n case 'sold':\n return 'muted';\n default:\n return 'muted';\n }\n}\n\nexport function AssetManagementPage() {\n const navigate = useNavigate();\n const pagination = useServerPagination(10);\n const [search, setSearch] = useState('');\n const [debouncedSearch, setDebouncedSearch] = useState('');\n\n // Debounce the raw search input into the value that feeds the server filter.\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedSearch(search), 300);\n return () => clearTimeout(timer);\n }, [search]);\n\n const { data: assets, loading } = useAssets({\n filter: { search: debouncedSearch.trim() || undefined },\n pagination: pagination.pagination,\n });\n const { createAsset, loading: creating } = useCreateAsset();\n\n const handleSearchChange = (value: string) => {\n setSearch(value);\n pagination.setPage(1);\n };\n\n // Add-asset dialog state\n const [createOpen, setCreateOpen] = useState(false);\n const [title, setTitle] = useState('');\n const [type, setType] = useState<PropertyType>('residential');\n const [category, setCategory] = useState<PropertyCategory>('apartment');\n const [location, setLocation] = useState('');\n const [currentValue, setCurrentValue] = useState('');\n const [purchasePrice, setPurchasePrice] = useState('');\n const [purchaseDate, setPurchaseDate] = useState('');\n const [rentalYield, setRentalYield] = useState('');\n const [imageUrl, setImageUrl] = useState('');\n const [status, setStatus] = useState<Asset['status']>('active');\n\n const resetForm = () => {\n setTitle('');\n setType('residential');\n setCategory('apartment');\n setLocation('');\n setCurrentValue('');\n setPurchasePrice('');\n setPurchaseDate('');\n setRentalYield('');\n setImageUrl('');\n setStatus('active');\n };\n\n const openCreate = () => {\n resetForm();\n setCreateOpen(true);\n };\n\n const handleCreate = async (e: FormEvent) => {\n e.preventDefault();\n if (!title.trim()) {\n notify.warning('Enter an asset title');\n return;\n }\n if (!location.trim()) {\n notify.warning('Enter a location');\n return;\n }\n const current = Number(currentValue);\n const purchase = Number(purchasePrice);\n if (!Number.isFinite(current) || current <= 0) {\n notify.warning('Enter a valid current value');\n return;\n }\n if (!Number.isFinite(purchase) || purchase <= 0) {\n notify.warning('Enter a valid purchase price');\n return;\n }\n const yieldValue = rentalYield.trim() ? Number(rentalYield) : undefined;\n if (yieldValue !== undefined && !Number.isFinite(yieldValue)) {\n notify.warning('Enter a valid rental yield');\n return;\n }\n // Appreciation is derived from the real purchase vs. current value, never fabricated.\n const appreciationPercent = purchase > 0 ? ((current - purchase) / purchase) * 100 : 0;\n try {\n await createAsset({\n title: title.trim(),\n type,\n category,\n location: location.trim(),\n currentValue: current,\n purchasePrice: purchase,\n purchaseDate: purchaseDate ? new Date(purchaseDate).toISOString() : undefined,\n appreciationPercent: Number(appreciationPercent.toFixed(2)),\n rentalYield: yieldValue,\n status,\n imageUrl: imageUrl.trim() || undefined,\n });\n notify.success('Asset added', `${title.trim()} was added to your portfolio.`);\n setCreateOpen(false);\n resetForm();\n } catch (err) {\n notify.error('Could not add asset', (err as Error).message);\n }\n };\n\n const handleExport = () => {\n if (assets.length === 0) {\n notify.warning('Nothing to export', 'There are no assets to export yet.');\n return;\n }\n const escape = (value: string) => `\"${value.replace(/\"/g, '\"\"')}\"`;\n const headers = [\n 'Title',\n 'Type',\n 'Category',\n 'Location',\n 'Purchase Price',\n 'Current Value',\n 'Appreciation %',\n 'Rental Yield %',\n 'Status',\n ];\n const rows = assets.map((a) =>\n [\n a.title,\n a.type,\n a.category,\n a.location,\n String(a.purchasePrice),\n String(a.currentValue),\n String(a.appreciationPercent),\n a.rentalYield != null ? String(a.rentalYield) : '',\n a.status,\n ]\n .map((cell) => escape(String(cell)))\n .join(',')\n );\n const csv = [headers.map(escape).join(','), ...rows].join('\\n');\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = `housingvista-assets-${new Date().toISOString().slice(0, 10)}.csv`;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n notify.success('Export ready', `${assets.length} assets exported to CSV.`);\n };\n\n const portfolioStats = useMemo(() => {\n const totalValue = assets.reduce((sum, a) => sum + a.currentValue, 0);\n const totalMonthlyIncome = assets\n .filter((a) => a.rentalYield)\n .reduce((sum, a) => sum + (a.currentValue * (a.rentalYield || 0)) / 100 / 12, 0);\n const avgAppreciation =\n assets.length > 0 ? assets.reduce((sum, a) => sum + a.appreciationPercent, 0) / assets.length : 0;\n const totalPurchasePrice = assets.reduce((sum, a) => sum + a.purchasePrice, 0);\n const totalROI = totalPurchasePrice > 0 ? ((totalValue - totalPurchasePrice) / totalPurchasePrice) * 100 : 0;\n return { totalValue, totalMonthlyIncome, avgAppreciation, totalROI };\n }, [assets]);\n\n // Portfolio value split by property type — real current values, never fabricated.\n const valueByType = useMemo(() => {\n const byType = new Map<string, number>();\n for (const asset of assets) {\n byType.set(asset.type, (byType.get(asset.type) ?? 0) + asset.currentValue);\n }\n const rows = Array.from(byType.entries())\n .filter(([, value]) => value > 0)\n .map(([type, value], idx) => ({\n type,\n label: type.charAt(0).toUpperCase() + type.slice(1).replace('_', ' '),\n value,\n color: CHART_COLORS[idx % CHART_COLORS.length],\n }))\n .sort((a, b) => b.value - a.value);\n const total = rows.reduce((s, r) => s + r.value, 0);\n return { rows, total };\n }, [assets]);\n\n const assetColumns: Column<Asset>[] = [\n {\n key: 'title',\n header: 'Property',\n render: (item) => (\n <div className=\"flex items-center gap-3\">\n <AssetImage src={item.imageUrl} alt={item.title} className=\"h-10 w-10 shrink-0 rounded-md\" />\n <div>\n <div className=\"font-medium text-text-primary\">{item.title}</div>\n <div className=\"flex items-center gap-1 text-xs text-text-secondary\">\n <MapPin className=\"h-3 w-3\" />\n {item.location}\n </div>\n </div>\n </div>\n ),\n },\n {\n key: 'type',\n header: 'Type',\n render: (item) => <span className=\"text-sm capitalize text-text-primary\">{item.type}</span>,\n },\n {\n key: 'purchasePrice',\n header: 'Purchase Price',\n render: (item) => <span className=\"text-sm text-text-primary\">{formatCurrency(item.purchasePrice)}</span>,\n },\n {\n key: 'currentValue',\n header: 'Current Value',\n render: (item) => (\n <span className=\"text-sm font-medium text-text-primary\">{formatCurrency(item.currentValue)}</span>\n ),\n },\n {\n key: 'appreciationPercent',\n header: 'Change',\n render: (item) => {\n const change = item.currentValue - item.purchasePrice;\n const isPositive = change >= 0;\n return (\n <div\n className={cn(\n 'flex items-center gap-1 text-sm font-medium',\n isPositive ? 'text-status-success-text' : 'text-status-error-text'\n )}\n >\n {isPositive ? <ArrowUpRight className=\"h-3 w-3\" /> : <ArrowDownRight className=\"h-3 w-3\" />}\n {formatCurrency(Math.abs(change))}\n <span className=\"text-xs\">({item.appreciationPercent}%)</span>\n </div>\n );\n },\n },\n {\n key: 'status',\n header: 'Status',\n render: (item) => <StatusBadge label={item.status.replace('_', ' ')} variant={getStatusVariant(item.status)} />,\n },\n {\n key: 'actions',\n header: '',\n sortable: false,\n render: (item) => (\n <button\n onClick={(e) => {\n e.stopPropagation();\n navigate(`../assets/${item.id}`);\n }}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-2 py-1 text-xs font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Eye className=\"h-3 w-3\" />\n View\n </button>\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"Asset Management\"\n description=\"Monitor and manage your property portfolio performance\"\n purpose=\"See how every property you own is performing as an investment — current value, appreciation, rental yield, and overall ROI in one portfolio view. Use it to decide what to hold, renovate, or sell.\"\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n onClick={handleExport}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Download className=\"h-4 w-4\" />\n Export\n </button>\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg-hover\"\n >\n <Plus className=\"h-4 w-4\" />\n Add Asset\n </button>\n </div>\n }\n >\n {/* Portfolio Value Stats — primary insight zone (one EmphasisPanel per page) */}\n <EmphasisPanel className=\"p-4\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n label=\"Total Value\"\n value={portfolioStats.totalValue}\n prefix=\"$\"\n icon={<DollarSign className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Monthly Income\"\n value={Math.round(portfolioStats.totalMonthlyIncome)}\n prefix=\"$\"\n icon={<TrendingUp className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Avg. Appreciation\"\n value={`${portfolioStats.avgAppreciation.toFixed(1)}%`}\n icon={<BarChart3 className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Overall ROI\"\n value={`${portfolioStats.totalROI.toFixed(1)}%`}\n icon={<Building className=\"h-4 w-4\" />}\n />\n </div>\n </EmphasisPanel>\n\n <NextSteps\n storageKey=\"housingvista-assets\"\n steps={[\n {\n id: 'open-asset',\n label: 'Open an asset to see full performance',\n description: 'Drill into value history, yield, and ROI for any property.',\n onClick: () => {\n const first = assets[0];\n if (first) navigate(`../assets/${first.id}`);\n },\n },\n {\n id: 'compare-market',\n label: 'Compare against market trends',\n description: 'Check market analytics to benchmark your portfolio.',\n onClick: () => navigate('../market-analytics'),\n },\n {\n id: 'add-asset',\n label: 'Add a property you already own',\n description: 'Bring an existing holding into your portfolio view.',\n onClick: openCreate,\n },\n ]}\n />\n\n {/* Portfolio overview: rich asset cards (left) + value-by-type donut (aside) */}\n {assets.length > 0 && (\n <div className=\"grid grid-cols-1 gap-6 xl:grid-cols-3\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:col-span-2\">\n {assets.map((asset) => {\n const change = asset.currentValue - asset.purchasePrice;\n const isPositive = change >= 0;\n const roi = asset.purchasePrice > 0 ? (change / asset.purchasePrice) * 100 : 0;\n return (\n <button\n key={asset.id}\n type=\"button\"\n onClick={() => navigate(`../assets/${asset.id}`)}\n className=\"group overflow-hidden rounded-card border border-border-seam bg-bg-surface text-left shadow-elevation-1 transition-[box-shadow,border-color] hover:border-border-strong hover:shadow-elevation-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <div className=\"relative aspect-[16/9] overflow-hidden\">\n <AssetImage src={asset.imageUrl} alt={asset.title} className=\"h-full w-full\" />\n <div className=\"absolute left-3 top-3\">\n <StatusBadge\n label={asset.status.replace('_', ' ')}\n variant={getStatusVariant(asset.status)}\n dot\n />\n </div>\n <div\n className={cn(\n 'absolute right-3 top-3 inline-flex items-center gap-0.5 rounded-full px-2 py-0.5 text-xs font-semibold backdrop-blur-sm',\n isPositive\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-status-error-bg-subtle text-status-error-text'\n )}\n >\n {isPositive ? <ArrowUpRight className=\"h-3 w-3\" /> : <ArrowDownRight className=\"h-3 w-3\" />}\n {asset.appreciationPercent}%\n </div>\n </div>\n <div className=\"p-4\">\n <h3 className=\"truncate font-medium text-text-primary\">{asset.title}</h3>\n <p className=\"mt-0.5 flex items-center gap-1 text-xs text-text-secondary\">\n <MapPin className=\"h-3 w-3 shrink-0\" />\n <span className=\"truncate\">{asset.location}</span>\n </p>\n <div className=\"mt-3 flex items-end justify-between\">\n <div>\n <p className=\"text-xs text-text-secondary\">Current Value</p>\n <p className=\"text-lg font-bold text-text-primary\">{formatCurrency(asset.currentValue)}</p>\n </div>\n <span className=\"rounded-md bg-bg-sunken px-2 py-1 text-xs font-medium capitalize text-text-secondary\">\n {asset.category}\n </span>\n </div>\n <div className=\"mt-3 grid grid-cols-2 gap-3 border-t border-border-subtle pt-3 text-xs\">\n <div className=\"flex items-center gap-1.5 text-text-secondary\">\n <Percent className=\"h-3.5 w-3.5\" />\n <span>Yield</span>\n <span className=\"ml-auto font-semibold text-text-primary\">\n {asset.rentalYield != null ? `${asset.rentalYield}%` : '—'}\n </span>\n </div>\n <div className=\"flex items-center gap-1.5 text-text-secondary\">\n <Target className=\"h-3.5 w-3.5\" />\n <span>ROI</span>\n <span\n className={cn(\n 'ml-auto font-semibold',\n roi >= 0 ? 'text-status-success-text' : 'text-status-error-text'\n )}\n >\n {roi.toFixed(1)}%\n </span>\n </div>\n </div>\n </div>\n </button>\n );\n })}\n </div>\n\n {valueByType.rows.length > 0 && (\n <aside className=\"xl:col-span-1\">\n <ChartCard title=\"Portfolio by Type\" description=\"Current value split across property types\">\n <div className=\"relative h-52 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <PieChart>\n <Pie\n data={valueByType.rows}\n dataKey=\"value\"\n nameKey=\"label\"\n innerRadius={56}\n outerRadius={82}\n paddingAngle={2}\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={2}\n >\n {valueByType.rows.map((entry) => (\n <Cell key={entry.type} fill={entry.color} />\n ))}\n </Pie>\n <Tooltip\n contentStyle={assetTooltipStyle}\n formatter={(value: number, _n, item) => [formatCurrency(value), item?.payload?.label]}\n />\n </PieChart>\n </ResponsiveContainer>\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center\">\n <span className=\"text-xs text-text-secondary\">Total value</span>\n <span className=\"text-lg font-bold text-text-primary\">{formatCurrency(valueByType.total)}</span>\n </div>\n </div>\n <ul className=\"mt-4 space-y-2.5\">\n {valueByType.rows.map((entry) => (\n <li key={entry.type} className=\"flex items-center justify-between text-sm\">\n <span className=\"flex items-center gap-2\">\n <span className=\"h-2.5 w-2.5 rounded-full\" style={{ background: entry.color }} />\n <span className=\"text-text-primary\">{entry.label}</span>\n </span>\n <span className=\"font-medium text-text-secondary\">\n {valueByType.total > 0 ? Math.round((entry.value / valueByType.total) * 100) : 0}%\n </span>\n </li>\n ))}\n </ul>\n </ChartCard>\n </aside>\n )}\n </div>\n )}\n\n {/* Asset Table */}\n <div>\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">All Assets</h2>\n <FilterBar\n searchValue={search}\n onSearchChange={handleSearchChange}\n searchPlaceholder=\"Search assets by name, location, or type...\"\n />\n <div className=\"mt-4\">\n {assets.length > 0 ? (\n <DataTable\n columns={assetColumns}\n data={assets}\n keyField=\"id\"\n onRowClick={(item) => navigate(`../assets/${item.id}`)}\n pageSize={10}\n serverPagination={{\n page: pagination.page,\n pageSize: pagination.pageSize,\n hasNextPage: assets.length >= pagination.pageSize,\n loading,\n onPageChange: pagination.setPage,\n onPageSizeChange: pagination.setPageSize,\n }}\n />\n ) : (\n <EmptyState\n illustration=\"empty-search\"\n icon={<Building className=\"h-12 w-12\" />}\n title=\"No assets found\"\n description=\"Try adjusting your search criteria or add a new asset.\"\n action={\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg-hover\"\n >\n <Plus className=\"h-4 w-4\" />\n Add Asset\n </button>\n }\n />\n )}\n </div>\n </div>\n\n {/* Add Asset dialog */}\n <ResponsiveDialog open={createOpen} onOpenChange={setCreateOpen}>\n <ResponsiveDialogContent className=\"sm:max-w-lg\">\n <form onSubmit={handleCreate}>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add asset</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Bring a property you own into your portfolio to track its value and performance.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3 py-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Title *</label>\n <input\n type=\"text\"\n required\n placeholder=\"e.g. Riverside Villa\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Location *</label>\n <input\n type=\"text\"\n required\n placeholder=\"City, State\"\n value={location}\n onChange={(e) => setLocation(e.target.value)}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Type</label>\n <select value={type} onChange={(e) => setType(e.target.value as PropertyType)} className={inputClass}>\n {assetTypeOptions.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Category</label>\n <select\n value={category}\n onChange={(e) => setCategory(e.target.value as PropertyCategory)}\n className={inputClass}\n >\n {assetCategoryOptions.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Purchase price (USD) *</label>\n <input\n type=\"number\"\n required\n min={1}\n step=\"any\"\n placeholder=\"0\"\n value={purchasePrice}\n onChange={(e) => setPurchasePrice(e.target.value)}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Current value (USD) *</label>\n <input\n type=\"number\"\n required\n min={1}\n step=\"any\"\n placeholder=\"0\"\n value={currentValue}\n onChange={(e) => setCurrentValue(e.target.value)}\n className={inputClass}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-3\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Purchase date</label>\n <input\n type=\"date\"\n value={purchaseDate}\n onChange={(e) => setPurchaseDate(e.target.value)}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Rental yield (%)</label>\n <input\n type=\"number\"\n min={0}\n step=\"any\"\n placeholder=\"0\"\n value={rentalYield}\n onChange={(e) => setRentalYield(e.target.value)}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Status</label>\n <select\n value={status}\n onChange={(e) => setStatus(e.target.value as Asset['status'])}\n className={inputClass}\n >\n {assetStatusOptions.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Image URL</label>\n <input\n type=\"url\"\n placeholder=\"https://…\"\n value={imageUrl}\n onChange={(e) => setImageUrl(e.target.value)}\n className={inputClass}\n />\n </div>\n </div>\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setCreateOpen(false)}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={creating || !title.trim() || !location.trim()}>\n {creating ? 'Adding…' : 'Add asset'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Messages — the real HousingVista inbox. A two-pane conversations UI (thread
|
|
3
|
+
* list + chat thread with bubbles) backed by wspace-conversations-svc:
|
|
4
|
+
* - `useHousingVistaConversations` lists the user's product threads,
|
|
5
|
+
* - `useConversationMessages` streams the selected thread,
|
|
6
|
+
* - `useSendMessage` posts new messages.
|
|
7
|
+
* Everything is persisted server-side; when there are no threads yet we show a
|
|
8
|
+
* tasteful empty state (never fabricated rows). Fully responsive: on mobile the
|
|
9
|
+
* list and the open thread swap places (back button), on desktop they sit
|
|
10
|
+
* side-by-side.
|
|
11
|
+
* @module housingvista
|
|
12
|
+
*/
|
|
13
|
+
export declare function ChatPage(): import("react").JSX.Element;
|
|
14
|
+
//# sourceMappingURL=ChatPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatPage.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/chat/ChatPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAuDH,wBAAgB,QAAQ,gCA4PvB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HousingVista Home Loan Detail / Apply Page
|
|
3
|
+
* Single loan offer backed by `housingvistaHomeLoanOffer(id)` — full terms plus
|
|
4
|
+
* a live EMI summary for the offer's headline rate.
|
|
5
|
+
*
|
|
6
|
+
* "Apply" opens the real `ContactOwnerDialog` — a persisted conversation with
|
|
7
|
+
* the bank. No fabricated approval; the EMI calculator stays for modelling.
|
|
8
|
+
* @module housingvista
|
|
9
|
+
*/
|
|
10
|
+
export declare function HomeLoanDetailPage(): import("react").JSX.Element;
|
|
11
|
+
//# sourceMappingURL=HomeLoanDetailPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HomeLoanDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/finance/HomeLoanDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA6CH,wBAAgB,kBAAkB,gCAqQjC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HomeLoansPage.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/finance/HomeLoansPage.tsx"],"names":[],"mappings":"AAmIA,wBAAgB,aAAa,gCA0N5B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InvestmentDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/investments/InvestmentDetailPage.tsx"],"names":[],"mappings":"AAwIA,wBAAgB,oBAAoB,gCA8kBnC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InvestmentsPage.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/investments/InvestmentsPage.tsx"],"names":[],"mappings":"AA+JA,wBAAgB,eAAe,gCAgiB9B"}
|