@burdenoff/microfe-housingvista 2026.709.2 → 2026.709.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/housingvista/HousingVistaRoutes.d.ts.map +1 -1
- package/dist/housingvista/HousingVistaRoutes.js +136 -37
- package/dist/housingvista/HousingVistaRoutes.js.map +1 -1
- package/dist/housingvista/hooks/useHousingVistaAnalytics.d.ts +1 -1
- package/dist/housingvista/hooks/useHousingVistaAnalytics.d.ts.map +1 -1
- package/dist/housingvista/hooks/useHousingVistaAnalytics.js +4 -4
- package/dist/housingvista/hooks/useHousingVistaAnalytics.js.map +1 -1
- package/dist/housingvista/pages/amc/AMCManagementPage.d.ts.map +1 -1
- package/dist/housingvista/pages/amc/AMCManagementPage.js +62 -60
- package/dist/housingvista/pages/amc/AMCManagementPage.js.map +1 -1
- package/dist/housingvista/pages/assets/AssetDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/assets/AssetDetailPage.js +141 -138
- package/dist/housingvista/pages/assets/AssetDetailPage.js.map +1 -1
- package/dist/housingvista/pages/assets/AssetManagementPage.d.ts.map +1 -1
- package/dist/housingvista/pages/assets/AssetManagementPage.js +193 -191
- package/dist/housingvista/pages/assets/AssetManagementPage.js.map +1 -1
- package/dist/housingvista/pages/chat/ChatPage.d.ts.map +1 -1
- package/dist/housingvista/pages/chat/ChatPage.js +115 -113
- package/dist/housingvista/pages/chat/ChatPage.js.map +1 -1
- package/dist/housingvista/pages/finance/HomeLoanDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/finance/HomeLoanDetailPage.js +152 -149
- package/dist/housingvista/pages/finance/HomeLoanDetailPage.js.map +1 -1
- package/dist/housingvista/pages/finance/HomeLoansPage.d.ts.map +1 -1
- package/dist/housingvista/pages/finance/HomeLoansPage.js +141 -139
- package/dist/housingvista/pages/finance/HomeLoansPage.js.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentDetailPage.js +290 -287
- package/dist/housingvista/pages/investments/InvestmentDetailPage.js.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentsPage.js +64 -62
- package/dist/housingvista/pages/investments/InvestmentsPage.js.map +1 -1
- package/dist/housingvista/pages/legal/LegalPage.d.ts.map +1 -1
- package/dist/housingvista/pages/legal/LegalPage.js +95 -93
- package/dist/housingvista/pages/legal/LegalPage.js.map +1 -1
- package/dist/housingvista/pages/legal/LegalServiceDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/legal/LegalServiceDetailPage.js +129 -126
- package/dist/housingvista/pages/legal/LegalServiceDetailPage.js.map +1 -1
- package/dist/housingvista/pages/market/AreaInsightsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/market/AreaInsightsPage.js +99 -97
- package/dist/housingvista/pages/market/AreaInsightsPage.js.map +1 -1
- package/dist/housingvista/pages/market/MarketAnalyticsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/market/MarketAnalyticsPage.js +138 -136
- package/dist/housingvista/pages/market/MarketAnalyticsPage.js.map +1 -1
- package/dist/housingvista/pages/marketplace/MarketplacePage.d.ts.map +1 -1
- package/dist/housingvista/pages/marketplace/MarketplacePage.js +99 -97
- package/dist/housingvista/pages/marketplace/MarketplacePage.js.map +1 -1
- package/dist/housingvista/pages/marketplace/ServiceDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/marketplace/ServiceDetailPage.js +137 -134
- package/dist/housingvista/pages/marketplace/ServiceDetailPage.js.map +1 -1
- package/dist/housingvista/pages/offers/OffersPage.d.ts.map +1 -1
- package/dist/housingvista/pages/offers/OffersPage.js +94 -92
- package/dist/housingvista/pages/offers/OffersPage.js.map +1 -1
- package/dist/housingvista/pages/properties/AddPropertyPage.d.ts.map +1 -1
- package/dist/housingvista/pages/properties/AddPropertyPage.js +280 -279
- package/dist/housingvista/pages/properties/AddPropertyPage.js.map +1 -1
- package/dist/housingvista/pages/rental/RentalManagementPage.d.ts.map +1 -1
- package/dist/housingvista/pages/rental/RentalManagementPage.js +153 -151
- package/dist/housingvista/pages/rental/RentalManagementPage.js.map +1 -1
- package/dist/housingvista/pages/settings/ProfilePage.d.ts.map +1 -1
- package/dist/housingvista/pages/settings/ProfilePage.js +52 -50
- package/dist/housingvista/pages/settings/ProfilePage.js.map +1 -1
- package/dist/housingvista/pages/settings/SettingsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/settings/SettingsPage.js +54 -52
- package/dist/housingvista/pages/settings/SettingsPage.js.map +1 -1
- package/dist/housingvista/pages/shortlist/ShortlistPage.d.ts.map +1 -1
- package/dist/housingvista/pages/shortlist/ShortlistPage.js +43 -41
- package/dist/housingvista/pages/shortlist/ShortlistPage.js.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionDetailPage.js +211 -208
- package/dist/housingvista/pages/transactions/TransactionDetailPage.js.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionsPage.js +85 -83
- package/dist/housingvista/pages/transactions/TransactionsPage.js.map +1 -1
- package/dist/housingvista/pages/verification/VerificationDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/verification/VerificationDetailPage.js +25 -22
- package/dist/housingvista/pages/verification/VerificationDetailPage.js.map +1 -1
- package/dist/housingvista/pages/verification/VerificationPage.d.ts.map +1 -1
- package/dist/housingvista/pages/verification/VerificationPage.js +123 -121
- package/dist/housingvista/pages/verification/VerificationPage.js.map +1 -1
- package/dist/housingvista/pages/viewings/ViewingsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/viewings/ViewingsPage.js +128 -126
- package/dist/housingvista/pages/viewings/ViewingsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"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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"}
|
|
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';\nimport { useTrackPageView } from '../../hooks/useHousingVistaAnalytics';\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 useTrackPageView('housingvista.asset.viewed');\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatPage.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/chat/ChatPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"ChatPage.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/chat/ChatPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAwDH,wBAAgB,QAAQ,gCA6PvB"}
|
|
@@ -4,154 +4,156 @@ import { PageLayout as r } from "../../components/PageLayout.js";
|
|
|
4
4
|
import { StatCard as i } from "../../components/StatCard.js";
|
|
5
5
|
import { EmptyState as a } from "../../components/EmptyState.js";
|
|
6
6
|
import { useConversationMessages as o, useSendMessage as s } from "../../hooks/platform.js";
|
|
7
|
+
import { useTrackPageView as c } from "../../hooks/useHousingVistaAnalytics.js";
|
|
7
8
|
import "../../hooks/index.js";
|
|
8
|
-
import { notify as
|
|
9
|
+
import { notify as l } from "../../lib/toast.js";
|
|
9
10
|
import "../../components/index.js";
|
|
10
|
-
import { useHousingVistaConversations as
|
|
11
|
-
import { useEffect as
|
|
12
|
-
import { ArrowLeft as
|
|
13
|
-
import { useNavigate as
|
|
14
|
-
import { Fragment as
|
|
15
|
-
import { Button as
|
|
11
|
+
import { useHousingVistaConversations as u } from "../../hooks/messages.js";
|
|
12
|
+
import { useEffect as d, useMemo as f, useRef as p, useState as m } from "react";
|
|
13
|
+
import { ArrowLeft as h, MapPin as g, MessageSquare as _, Search as v, Send as y } from "lucide-react";
|
|
14
|
+
import { useNavigate as b } from "react-router-dom";
|
|
15
|
+
import { Fragment as x, jsx as S, jsxs as C } from "react/jsx-runtime";
|
|
16
|
+
import { Button as w, EmphasisPanel as T, GlassCard as E } from "@burdenoff/fe-libs/ui";
|
|
16
17
|
//#region src/housingvista/pages/chat/ChatPage.tsx
|
|
17
|
-
var
|
|
18
|
+
var D = [
|
|
18
19
|
"bg-status-info-bg-subtle text-status-info-text",
|
|
19
20
|
"bg-status-success-bg-subtle text-status-success-text",
|
|
20
21
|
"bg-status-warning-bg-subtle text-status-warning-text",
|
|
21
22
|
"bg-action-primary-bg/15 text-primary"
|
|
22
23
|
];
|
|
23
|
-
function
|
|
24
|
+
function O(e) {
|
|
24
25
|
let t = e.trim().split(/\s+/).filter(Boolean);
|
|
25
26
|
return t.length === 0 ? "?" : t.length === 1 ? t[0].slice(0, 2).toUpperCase() : (t[0][0] + t[t.length - 1][0]).toUpperCase();
|
|
26
27
|
}
|
|
27
|
-
function
|
|
28
|
+
function k(e) {
|
|
28
29
|
let t = 0;
|
|
29
30
|
for (let n = 0; n < e.length; n += 1) t += e.charCodeAt(n);
|
|
30
|
-
return
|
|
31
|
+
return D[t % D.length];
|
|
31
32
|
}
|
|
32
|
-
function
|
|
33
|
+
function A(e) {
|
|
33
34
|
let t = new Date(e);
|
|
34
35
|
return Number.isNaN(t.getTime()) ? "" : t.toLocaleTimeString("en-US", {
|
|
35
36
|
hour: "numeric",
|
|
36
37
|
minute: "2-digit"
|
|
37
38
|
});
|
|
38
39
|
}
|
|
39
|
-
function
|
|
40
|
-
return /* @__PURE__ */
|
|
41
|
-
className: t("inline-flex shrink-0 items-center justify-center rounded-full font-semibold", r === "md" ? "h-10 w-10 text-sm" : "h-9 w-9 text-xs",
|
|
40
|
+
function j({ title: e, id: n, size: r = "md" }) {
|
|
41
|
+
return /* @__PURE__ */ S("span", {
|
|
42
|
+
className: t("inline-flex shrink-0 items-center justify-center rounded-full font-semibold", r === "md" ? "h-10 w-10 text-sm" : "h-9 w-9 text-xs", k(n)),
|
|
42
43
|
"aria-hidden": !0,
|
|
43
|
-
children:
|
|
44
|
+
children: O(e)
|
|
44
45
|
});
|
|
45
46
|
}
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
u(()
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
47
|
+
function M() {
|
|
48
|
+
c("housingvista.chat.viewed");
|
|
49
|
+
let D = b(), { currentUser: O } = e(), k = O?.id ?? "", { data: M, loading: N, refetch: P } = u(), { sendMessage: F, loading: I } = s(), [L, R] = m(), [z, B] = m(""), [V, H] = m(""), U = p(null);
|
|
50
|
+
d(() => {
|
|
51
|
+
!L && M.length > 0 && typeof window < "u" && window.innerWidth >= 1024 && R(M[0].id);
|
|
52
|
+
}, [M, L]);
|
|
53
|
+
let W = f(() => M.find((e) => e.id === L), [M, L]), { data: G, loading: K } = o(L);
|
|
54
|
+
d(() => {
|
|
55
|
+
U.current?.scrollTo({ top: U.current.scrollHeight });
|
|
56
|
+
}, [G, L]);
|
|
57
|
+
let q = f(() => {
|
|
58
|
+
let e = z.trim().toLowerCase();
|
|
59
|
+
return e ? M.filter((t) => t.title.toLowerCase().includes(e) || t.lastMessageText.toLowerCase().includes(e)) : M;
|
|
60
|
+
}, [M, z]), J = M.filter((e) => e.unreadCount > 0).length, Y = async () => {
|
|
61
|
+
let e = V.trim();
|
|
62
|
+
if (!(!e || !L)) try {
|
|
63
|
+
await F(L, e), H(""), P();
|
|
62
64
|
} catch (e) {
|
|
63
|
-
|
|
65
|
+
l.error("Message not sent", e.message);
|
|
64
66
|
}
|
|
65
|
-
},
|
|
66
|
-
return /* @__PURE__ */
|
|
67
|
+
}, X = !N && M.length === 0;
|
|
68
|
+
return /* @__PURE__ */ C(r, {
|
|
67
69
|
title: "Messages",
|
|
68
70
|
description: "Your conversations with property owners, agents, and service providers",
|
|
69
71
|
purpose: "Every thread you start from a property, listing, or service lands here — read the full history, pick up where you left off, and reply. Messages are delivered to the other party's inbox in real time.",
|
|
70
|
-
children: [/* @__PURE__ */
|
|
72
|
+
children: [/* @__PURE__ */ S(T, {
|
|
71
73
|
className: "p-4",
|
|
72
|
-
children: /* @__PURE__ */
|
|
74
|
+
children: /* @__PURE__ */ C("div", {
|
|
73
75
|
className: "grid grid-cols-2 gap-4 sm:grid-cols-3",
|
|
74
76
|
children: [
|
|
75
|
-
/* @__PURE__ */
|
|
77
|
+
/* @__PURE__ */ S(i, {
|
|
76
78
|
label: "Conversations",
|
|
77
|
-
value:
|
|
78
|
-
icon: /* @__PURE__ */
|
|
79
|
+
value: M.length,
|
|
80
|
+
icon: /* @__PURE__ */ S(_, { className: "h-4 w-4" })
|
|
79
81
|
}),
|
|
80
|
-
/* @__PURE__ */
|
|
82
|
+
/* @__PURE__ */ S(i, {
|
|
81
83
|
label: "Unread threads",
|
|
82
|
-
value:
|
|
83
|
-
icon: /* @__PURE__ */
|
|
84
|
+
value: J,
|
|
85
|
+
icon: /* @__PURE__ */ S(_, { className: "h-4 w-4" })
|
|
84
86
|
}),
|
|
85
|
-
/* @__PURE__ */
|
|
87
|
+
/* @__PURE__ */ S(i, {
|
|
86
88
|
label: "With updates today",
|
|
87
|
-
value:
|
|
88
|
-
icon: /* @__PURE__ */
|
|
89
|
+
value: M.filter((e) => e.lastMessageAt && n(e.lastMessageAt) === "Today").length,
|
|
90
|
+
icon: /* @__PURE__ */ S(y, { className: "h-4 w-4" })
|
|
89
91
|
})
|
|
90
92
|
]
|
|
91
93
|
})
|
|
92
|
-
}),
|
|
94
|
+
}), X ? /* @__PURE__ */ S(a, {
|
|
93
95
|
illustration: "empty-generic",
|
|
94
|
-
icon: /* @__PURE__ */
|
|
96
|
+
icon: /* @__PURE__ */ S(_, { className: "h-12 w-12" }),
|
|
95
97
|
title: "No messages yet",
|
|
96
98
|
description: "Start a conversation from any property or service — reach out to an owner or provider and your thread will show up here.",
|
|
97
|
-
action: /* @__PURE__ */
|
|
98
|
-
onClick: () =>
|
|
99
|
+
action: /* @__PURE__ */ S(w, {
|
|
100
|
+
onClick: () => D("../properties"),
|
|
99
101
|
children: "Browse properties"
|
|
100
102
|
})
|
|
101
|
-
}) : /* @__PURE__ */
|
|
103
|
+
}) : /* @__PURE__ */ S(E, {
|
|
102
104
|
treatment: "plain",
|
|
103
105
|
className: "overflow-hidden p-0",
|
|
104
|
-
children: /* @__PURE__ */
|
|
106
|
+
children: /* @__PURE__ */ C("div", {
|
|
105
107
|
className: "grid h-[68vh] min-h-[26rem] grid-cols-1 lg:grid-cols-[20rem_1fr]",
|
|
106
|
-
children: [/* @__PURE__ */
|
|
107
|
-
className: t("flex flex-col border-border-subtle lg:border-r",
|
|
108
|
-
children: [/* @__PURE__ */
|
|
108
|
+
children: [/* @__PURE__ */ C("aside", {
|
|
109
|
+
className: t("flex flex-col border-border-subtle lg:border-r", L ? "hidden lg:flex" : "flex"),
|
|
110
|
+
children: [/* @__PURE__ */ S("div", {
|
|
109
111
|
className: "border-b border-border-subtle p-3",
|
|
110
|
-
children: /* @__PURE__ */
|
|
112
|
+
children: /* @__PURE__ */ C("div", {
|
|
111
113
|
className: "relative",
|
|
112
|
-
children: [/* @__PURE__ */
|
|
114
|
+
children: [/* @__PURE__ */ S(v, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ S("input", {
|
|
113
115
|
type: "search",
|
|
114
|
-
value:
|
|
115
|
-
onChange: (e) =>
|
|
116
|
+
value: z,
|
|
117
|
+
onChange: (e) => B(e.target.value),
|
|
116
118
|
placeholder: "Search conversations",
|
|
117
119
|
"aria-label": "Search conversations",
|
|
118
120
|
className: "h-10 w-full rounded-md border border-border-subtle bg-bg-surface pl-9 pr-3 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]"
|
|
119
121
|
})]
|
|
120
122
|
})
|
|
121
|
-
}), /* @__PURE__ */
|
|
123
|
+
}), /* @__PURE__ */ S("div", {
|
|
122
124
|
className: "min-h-0 flex-1 overflow-y-auto",
|
|
123
|
-
children:
|
|
125
|
+
children: N && M.length === 0 ? /* @__PURE__ */ S("p", {
|
|
124
126
|
className: "p-6 text-center text-sm text-text-secondary",
|
|
125
127
|
children: "Loading conversations…"
|
|
126
|
-
}) :
|
|
128
|
+
}) : q.length === 0 ? /* @__PURE__ */ S("p", {
|
|
127
129
|
className: "p-6 text-center text-sm text-text-secondary",
|
|
128
130
|
children: "No conversations match your search."
|
|
129
|
-
}) : /* @__PURE__ */
|
|
131
|
+
}) : /* @__PURE__ */ S("ul", { children: q.map((e) => /* @__PURE__ */ S("li", { children: /* @__PURE__ */ C("button", {
|
|
130
132
|
type: "button",
|
|
131
133
|
onClick: () => {
|
|
132
|
-
|
|
134
|
+
R(e.id), H("");
|
|
133
135
|
},
|
|
134
|
-
className: t("flex w-full items-start gap-3 border-b border-border-subtle px-3 py-3 text-left transition-colors", e.id ===
|
|
135
|
-
children: [/* @__PURE__ */
|
|
136
|
+
className: t("flex w-full items-start gap-3 border-b border-border-subtle px-3 py-3 text-left transition-colors", e.id === L ? "bg-action-primary-bg/10" : "hover:bg-bg-sunken"),
|
|
137
|
+
children: [/* @__PURE__ */ S(j, {
|
|
136
138
|
title: e.title,
|
|
137
139
|
id: e.id
|
|
138
|
-
}), /* @__PURE__ */
|
|
140
|
+
}), /* @__PURE__ */ C("div", {
|
|
139
141
|
className: "min-w-0 flex-1",
|
|
140
|
-
children: [/* @__PURE__ */
|
|
142
|
+
children: [/* @__PURE__ */ C("div", {
|
|
141
143
|
className: "flex items-center justify-between gap-2",
|
|
142
|
-
children: [/* @__PURE__ */
|
|
144
|
+
children: [/* @__PURE__ */ S("span", {
|
|
143
145
|
className: "truncate text-sm font-semibold text-text-primary",
|
|
144
146
|
children: e.title
|
|
145
|
-
}), e.lastMessageAt && /* @__PURE__ */
|
|
147
|
+
}), e.lastMessageAt && /* @__PURE__ */ S("span", {
|
|
146
148
|
className: "shrink-0 text-xs text-text-secondary",
|
|
147
149
|
children: n(e.lastMessageAt)
|
|
148
150
|
})]
|
|
149
|
-
}), /* @__PURE__ */
|
|
151
|
+
}), /* @__PURE__ */ C("div", {
|
|
150
152
|
className: "mt-0.5 flex items-center gap-2",
|
|
151
|
-
children: [/* @__PURE__ */
|
|
153
|
+
children: [/* @__PURE__ */ S("span", {
|
|
152
154
|
className: "min-w-0 flex-1 truncate text-xs text-text-secondary",
|
|
153
155
|
children: e.lastMessageText || "No messages yet"
|
|
154
|
-
}), e.unreadCount > 0 && /* @__PURE__ */
|
|
156
|
+
}), e.unreadCount > 0 && /* @__PURE__ */ S("span", {
|
|
155
157
|
className: "inline-flex h-5 min-w-5 shrink-0 items-center justify-center rounded-full bg-action-primary-bg px-1.5 text-[0.65rem] font-semibold text-action-primary-text",
|
|
156
158
|
children: e.unreadCount
|
|
157
159
|
})]
|
|
@@ -159,91 +161,91 @@ function j() {
|
|
|
159
161
|
})]
|
|
160
162
|
}) }, e.id)) })
|
|
161
163
|
})]
|
|
162
|
-
}), /* @__PURE__ */
|
|
163
|
-
className: t("flex min-h-0 flex-col",
|
|
164
|
-
children:
|
|
165
|
-
/* @__PURE__ */
|
|
164
|
+
}), /* @__PURE__ */ S("section", {
|
|
165
|
+
className: t("flex min-h-0 flex-col", L ? "flex" : "hidden lg:flex"),
|
|
166
|
+
children: W ? /* @__PURE__ */ C(x, { children: [
|
|
167
|
+
/* @__PURE__ */ C("header", {
|
|
166
168
|
className: "flex items-center gap-3 border-b border-border-subtle p-3",
|
|
167
169
|
children: [
|
|
168
|
-
/* @__PURE__ */
|
|
170
|
+
/* @__PURE__ */ S("button", {
|
|
169
171
|
type: "button",
|
|
170
|
-
onClick: () =>
|
|
172
|
+
onClick: () => R(void 0),
|
|
171
173
|
className: "rounded-md p-1.5 text-text-secondary transition-colors hover:bg-bg-sunken lg:hidden",
|
|
172
174
|
"aria-label": "Back to conversations",
|
|
173
|
-
children: /* @__PURE__ */
|
|
175
|
+
children: /* @__PURE__ */ S(h, { className: "h-5 w-5" })
|
|
174
176
|
}),
|
|
175
|
-
/* @__PURE__ */
|
|
176
|
-
title:
|
|
177
|
-
id:
|
|
177
|
+
/* @__PURE__ */ S(j, {
|
|
178
|
+
title: W.title,
|
|
179
|
+
id: W.id,
|
|
178
180
|
size: "sm"
|
|
179
181
|
}),
|
|
180
|
-
/* @__PURE__ */
|
|
182
|
+
/* @__PURE__ */ C("div", {
|
|
181
183
|
className: "min-w-0 flex-1",
|
|
182
|
-
children: [/* @__PURE__ */
|
|
184
|
+
children: [/* @__PURE__ */ S("h2", {
|
|
183
185
|
className: "truncate text-sm font-semibold text-text-primary",
|
|
184
|
-
children:
|
|
185
|
-
}),
|
|
186
|
+
children: W.title
|
|
187
|
+
}), W.contextHref && /* @__PURE__ */ C("button", {
|
|
186
188
|
type: "button",
|
|
187
|
-
onClick: () =>
|
|
189
|
+
onClick: () => D(`..${W.contextHref}`),
|
|
188
190
|
className: "flex items-center gap-1 text-xs text-text-secondary transition-colors hover:text-primary",
|
|
189
|
-
children: [/* @__PURE__ */
|
|
191
|
+
children: [/* @__PURE__ */ S(g, { className: "h-3 w-3" }), "View listing"]
|
|
190
192
|
})]
|
|
191
193
|
})
|
|
192
194
|
]
|
|
193
195
|
}),
|
|
194
|
-
/* @__PURE__ */
|
|
195
|
-
ref:
|
|
196
|
+
/* @__PURE__ */ S("div", {
|
|
197
|
+
ref: U,
|
|
196
198
|
className: "min-h-0 flex-1 space-y-2 overflow-y-auto bg-bg-sunken/40 p-4",
|
|
197
|
-
children:
|
|
199
|
+
children: K && G.length === 0 ? /* @__PURE__ */ S("p", {
|
|
198
200
|
className: "py-10 text-center text-sm text-text-secondary",
|
|
199
201
|
children: "Loading messages…"
|
|
200
|
-
}) :
|
|
202
|
+
}) : G.length === 0 ? /* @__PURE__ */ S("p", {
|
|
201
203
|
className: "py-10 text-center text-sm text-text-secondary",
|
|
202
204
|
children: "No messages yet. Say hello to get the conversation started."
|
|
203
|
-
}) :
|
|
204
|
-
let n = e.authorId ===
|
|
205
|
-
return /* @__PURE__ */
|
|
205
|
+
}) : G.map((e) => {
|
|
206
|
+
let n = e.authorId === k;
|
|
207
|
+
return /* @__PURE__ */ S("div", {
|
|
206
208
|
className: t("flex", n ? "justify-end" : "justify-start"),
|
|
207
|
-
children: /* @__PURE__ */
|
|
209
|
+
children: /* @__PURE__ */ C("div", {
|
|
208
210
|
className: t("max-w-[78%]", n ? "items-end" : "items-start"),
|
|
209
|
-
children: [/* @__PURE__ */
|
|
211
|
+
children: [/* @__PURE__ */ S("div", {
|
|
210
212
|
className: t("rounded-2xl px-3.5 py-2 text-sm", n ? "rounded-br-sm bg-action-primary-bg text-action-primary-text" : "rounded-bl-sm border border-border-subtle bg-bg-surface text-text-primary"),
|
|
211
213
|
children: e.text
|
|
212
|
-
}), /* @__PURE__ */
|
|
214
|
+
}), /* @__PURE__ */ S("div", {
|
|
213
215
|
className: t("mt-1 px-1 text-[0.65rem] text-text-secondary", n && "text-right"),
|
|
214
|
-
children:
|
|
216
|
+
children: A(e.createdAt)
|
|
215
217
|
})]
|
|
216
218
|
})
|
|
217
219
|
}, e.id);
|
|
218
220
|
})
|
|
219
221
|
}),
|
|
220
|
-
/* @__PURE__ */
|
|
222
|
+
/* @__PURE__ */ C("div", {
|
|
221
223
|
className: "flex items-end gap-2 border-t border-border-subtle p-3",
|
|
222
|
-
children: [/* @__PURE__ */
|
|
223
|
-
value:
|
|
224
|
-
onChange: (e) =>
|
|
224
|
+
children: [/* @__PURE__ */ S("textarea", {
|
|
225
|
+
value: V,
|
|
226
|
+
onChange: (e) => H(e.target.value),
|
|
225
227
|
onKeyDown: (e) => {
|
|
226
|
-
e.key === "Enter" && !e.shiftKey && (e.preventDefault(),
|
|
228
|
+
e.key === "Enter" && !e.shiftKey && (e.preventDefault(), Y());
|
|
227
229
|
},
|
|
228
230
|
rows: 1,
|
|
229
231
|
placeholder: "Type a message…",
|
|
230
232
|
"aria-label": "Message",
|
|
231
233
|
className: "max-h-32 min-h-10 flex-1 resize-none rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]"
|
|
232
|
-
}), /* @__PURE__ */
|
|
234
|
+
}), /* @__PURE__ */ C(w, {
|
|
233
235
|
type: "button",
|
|
234
|
-
onClick:
|
|
235
|
-
disabled: !
|
|
236
|
-
children: [/* @__PURE__ */
|
|
236
|
+
onClick: Y,
|
|
237
|
+
disabled: !V.trim() || I,
|
|
238
|
+
children: [/* @__PURE__ */ S(y, { className: "h-4 w-4" }), /* @__PURE__ */ S("span", {
|
|
237
239
|
className: "ml-1 hidden sm:inline",
|
|
238
240
|
children: "Send"
|
|
239
241
|
})]
|
|
240
242
|
})]
|
|
241
243
|
})
|
|
242
|
-
] }) : /* @__PURE__ */
|
|
244
|
+
] }) : /* @__PURE__ */ S("div", {
|
|
243
245
|
className: "hidden flex-1 items-center justify-center p-8 lg:flex",
|
|
244
|
-
children: /* @__PURE__ */
|
|
246
|
+
children: /* @__PURE__ */ C("div", {
|
|
245
247
|
className: "text-center",
|
|
246
|
-
children: [/* @__PURE__ */
|
|
248
|
+
children: [/* @__PURE__ */ S(_, { className: "mx-auto h-10 w-10 text-text-secondary" }), /* @__PURE__ */ S("p", {
|
|
247
249
|
className: "mt-3 text-sm text-text-secondary",
|
|
248
250
|
children: "Select a conversation to read and reply."
|
|
249
251
|
})]
|
|
@@ -255,6 +257,6 @@ function j() {
|
|
|
255
257
|
});
|
|
256
258
|
}
|
|
257
259
|
//#endregion
|
|
258
|
-
export {
|
|
260
|
+
export { M as ChatPage };
|
|
259
261
|
|
|
260
262
|
//# sourceMappingURL=ChatPage.js.map
|