@burdenoff/microfe-housingvista 2026.703.12 → 2026.703.13
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/pages/HomePage.js.map +1 -1
- package/dist/housingvista/pages/amc/AMCManagementPage.js.map +1 -1
- package/dist/housingvista/pages/analytics/AnalyticsPage.js +8 -8
- package/dist/housingvista/pages/analytics/AnalyticsPage.js.map +1 -1
- package/dist/housingvista/pages/assets/AssetDetailPage.js +3 -3
- package/dist/housingvista/pages/assets/AssetDetailPage.js.map +1 -1
- package/dist/housingvista/pages/assets/AssetManagementPage.js +5 -5
- package/dist/housingvista/pages/assets/AssetManagementPage.js.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentDetailPage.js +3 -3
- package/dist/housingvista/pages/investments/InvestmentDetailPage.js.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentsPage.js +5 -5
- package/dist/housingvista/pages/investments/InvestmentsPage.js.map +1 -1
- package/dist/housingvista/pages/properties/PropertyDetailPage.js.map +1 -1
- package/dist/housingvista/pages/rental/RentalManagementPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssetDetailPage.js","names":[],"sources":["../../../../src/housingvista/pages/assets/AssetDetailPage.tsx"],"sourcesContent":["/**\n * HousingVista Asset Detail Page\n * Full performance view for a single owned asset backed by the single-item\n * `housingvistaAsset(id)` query — current value, appreciation, rental yield,\n * ROI, and purchase history. Read-only: no fabricated actions.\n * @module housingvista\n */\n\nimport { useNavigate, useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n ArrowUpRight,\n ArrowDownRight,\n Building,\n DollarSign,\n TrendingUp,\n Percent,\n Target,\n MapPin,\n Calendar,\n BarChart3,\n ImageOff,\n} from 'lucide-react';\nimport { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Cell } from 'recharts';\nimport { EmphasisPanel, GlassCard, PagePurpose, NextSteps } from '@burdenoff/fe-libs/ui';\nimport { Breadcrumbs, StatCard, StatusBadge, EmptyState, LoadingSkeleton } from '../../components';\nimport type { BreadcrumbItem } from '../../components';\nimport { useAsset } from '../../hooks';\nimport { cn, formatCurrency, formatDate } from '../../utils/helpers';\nimport type { Asset } from '../../types';\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 AssetDetailPage() {\n const { id } = useParams<{ id: string }>();\n const navigate = useNavigate();\n const { data: asset, loading } = useAsset(id);\n\n if (loading) {\n return (\n <div className=\"flex flex-col gap-6 p-6\">\n <LoadingSkeleton variant=\"detail\" />\n </div>\n );\n }\n\n if (!asset) {\n return (\n <div className=\"p-6\">\n <EmptyState\n illustration=\"empty-search\"\n icon={<Building className=\"h-12 w-12\" />}\n title=\"Asset Not Found\"\n description=\"The asset you're looking for doesn't exist or has been removed.\"\n action={\n <button\n onClick={() => navigate('../assets')}\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 <ArrowLeft className=\"h-4 w-4\" />\n Back to Assets\n </button>\n }\n />\n </div>\n );\n }\n\n const gain = asset.currentValue - asset.purchasePrice;\n const isPositive = gain >= 0;\n const roi = asset.purchasePrice > 0 ? (gain / asset.purchasePrice) * 100 : 0;\n const monthlyRentalIncome = asset.rentalYield ? (asset.currentValue * asset.rentalYield) / 100 / 12 : 0;\n\n const breadcrumbs: BreadcrumbItem[] = [{ label: 'Assets', href: '../' }, { label: asset.title }];\n\n return (\n <div className=\"flex flex-col gap-6 p-6\">\n <Breadcrumbs items={breadcrumbs} />\n\n {/* Header */}\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n <div>\n <button\n onClick={() => navigate('../assets')}\n className=\"mb-2 inline-flex items-center gap-2 rounded-md px-2 py-1 text-sm text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Assets\n </button>\n <h1 className=\"text-2xl font-semibold text-text-primary\">{asset.title}</h1>\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <StatusBadge label={asset.status.replace('_', ' ')} variant={getStatusVariant(asset.status)} dot />\n <StatusBadge label={asset.type} variant=\"muted\" />\n <span className=\"inline-flex items-center gap-1 text-sm text-text-secondary\">\n <MapPin className=\"h-3.5 w-3.5\" />\n {asset.location}\n </span>\n </div>\n </div>\n {asset.imageUrl ? (\n <img src={asset.imageUrl} alt={asset.title} className=\"h-24 w-full rounded-lg object-cover sm:w-40 lg:w-48\" />\n ) : (\n <div\n className=\"flex h-24 w-full items-center justify-center rounded-lg bg-gradient-to-br from-bg-sunken to-bg-sunken/50 sm:w-40 lg:w-48\"\n role=\"img\"\n aria-label={`${asset.title} — no image`}\n >\n <ImageOff className=\"h-8 w-8 text-text-secondary/40\" />\n </div>\n )}\n </div>\n\n <PagePurpose className=\"max-w-3xl\">\n A full performance breakdown for one property you own — what you paid, what it is worth today, how much it has\n appreciated, and the rental yield it generates. Use it to judge whether to hold, renovate, or sell.\n </PagePurpose>\n\n <NextSteps\n storageKey=\"housingvista-asset-detail\"\n steps={[\n {\n id: 'benchmark',\n label: 'Benchmark against the market',\n description: 'Compare this asset against area and market trends.',\n onClick: () => navigate('../market-analytics'),\n },\n {\n id: 'manage-rental',\n label: 'Manage the rental',\n description: 'Review tenants, leases, and rent collection for your units.',\n onClick: () => navigate('../rental'),\n },\n {\n id: 'back',\n label: 'Back to all assets',\n description: 'Return to your full portfolio view.',\n onClick: () => navigate('../assets'),\n },\n ]}\n />\n\n {/* Performance metrics — 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=\"Current Value\"\n value={asset.currentValue}\n prefix=\"$\"\n icon={<DollarSign className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Appreciation\"\n value={`${asset.appreciationPercent.toFixed(1)}%`}\n icon={<TrendingUp className=\"h-4 w-4\" />}\n />\n <StatCard label=\"Overall ROI\" value={`${roi.toFixed(1)}%`} icon={<Target className=\"h-4 w-4\" />} />\n <StatCard\n label=\"Rental Yield\"\n value={asset.rentalYield != null ? `${asset.rentalYield.toFixed(1)}%` : '—'}\n icon={<Percent className=\"h-4 w-4\" />}\n />\n </div>\n </EmphasisPanel>\n\n {/* Detail grid */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Value & performance */}\n <div className=\"space-y-6 lg:col-span-2\">\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-6\">\n <h2 className=\"mb-4 flex items-center gap-2 text-base font-semibold text-text-primary\">\n <BarChart3 className=\"h-5 w-5\" />\n Value & Performance\n </h2>\n <dl className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div className=\"rounded-lg bg-bg-sunken/50 p-4\">\n <dt className=\"text-xs text-text-secondary\">Purchase Price</dt>\n <dd className=\"mt-1 text-lg font-semibold text-text-primary\">{formatCurrency(asset.purchasePrice)}</dd>\n </div>\n <div className=\"rounded-lg bg-bg-sunken/50 p-4\">\n <dt className=\"text-xs text-text-secondary\">Current Value</dt>\n <dd className=\"mt-1 text-lg font-semibold text-text-primary\">{formatCurrency(asset.currentValue)}</dd>\n </div>\n <div className=\"rounded-lg bg-bg-sunken/50 p-4\">\n <dt className=\"text-xs text-text-secondary\">Total Gain / Loss</dt>\n <dd\n className={cn(\n 'mt-1 flex items-center gap-1 text-lg font-semibold',\n isPositive ? 'text-status-success-text' : 'text-status-error-text'\n )}\n >\n {isPositive ? <ArrowUpRight className=\"h-4 w-4\" /> : <ArrowDownRight className=\"h-4 w-4\" />}\n {formatCurrency(Math.abs(gain))} ({roi.toFixed(1)}%)\n </dd>\n </div>\n <div className=\"rounded-lg bg-bg-sunken/50 p-4\">\n <dt className=\"text-xs text-text-secondary\">Est. Monthly Rental Income</dt>\n <dd className=\"mt-1 text-lg font-semibold text-text-primary\">\n {asset.rentalYield ? formatCurrency(Math.round(monthlyRentalIncome)) : '—'}\n </dd>\n </div>\n </dl>\n\n {/* Purchase vs current value — real two-point comparison */}\n <div className=\"mt-6\">\n <p className=\"mb-2 text-xs font-medium text-text-secondary\">Purchase vs current value</p>\n <div className=\"h-44 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <BarChart\n data={[\n { name: 'Purchase', value: asset.purchasePrice, fill: 'var(--chart-3)' },\n { name: 'Current', value: asset.currentValue, fill: isPositive ? 'var(--chart-2)' : 'var(--chart-5)' },\n ]}\n margin={{ top: 8, right: 8, left: 0, bottom: 0 }}\n >\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--color-border-subtle)\" vertical={false} />\n <XAxis dataKey=\"name\" tick={{ fontSize: 11, fill: 'var(--color-text-secondary)' }} tickLine={false} axisLine={false} />\n <YAxis\n tick={{ fontSize: 11, fill: 'var(--color-text-secondary)' }}\n tickLine={false}\n axisLine={false}\n width={56}\n tickFormatter={(v: number) => formatCurrency(v)}\n />\n <Tooltip\n cursor={{ fill: 'var(--color-bg-sunken)' }}\n contentStyle={{\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-subtle)',\n borderRadius: 10,\n fontSize: 12,\n }}\n formatter={(value: number) => [formatCurrency(value), 'Value']}\n />\n <Bar dataKey=\"value\" radius={[6, 6, 0, 0]} maxBarSize={72}>\n <Cell fill=\"var(--chart-3)\" />\n <Cell fill={isPositive ? 'var(--chart-2)' : 'var(--chart-5)'} />\n </Bar>\n </BarChart>\n </ResponsiveContainer>\n </div>\n </div>\n </GlassCard>\n </div>\n\n {/* Purchase & holding info */}\n <div className=\"space-y-6\">\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <h3 className=\"mb-4 flex items-center gap-2 text-base font-semibold text-text-primary\">\n <Calendar className=\"h-5 w-5\" />\n Purchase & Holding\n </h3>\n <dl className=\"space-y-3 text-sm\">\n <div className=\"flex items-center justify-between\">\n <dt className=\"text-text-secondary\">Category</dt>\n <dd className=\"font-medium capitalize text-text-primary\">{asset.category}</dd>\n </div>\n <div className=\"flex items-center justify-between\">\n <dt className=\"text-text-secondary\">Type</dt>\n <dd className=\"font-medium capitalize text-text-primary\">{asset.type.replace('_', ' ')}</dd>\n </div>\n <div className=\"flex items-center justify-between\">\n <dt className=\"text-text-secondary\">Location</dt>\n <dd className=\"font-medium text-text-primary\">{asset.location}</dd>\n </div>\n <div className=\"flex items-center justify-between\">\n <dt className=\"text-text-secondary\">Purchase Date</dt>\n <dd className=\"font-medium text-text-primary\">\n {asset.purchaseDate ? formatDate(asset.purchaseDate) : '—'}\n </dd>\n </div>\n <div className=\"flex items-center justify-between\">\n <dt className=\"text-text-secondary\">Last Valuation</dt>\n <dd className=\"font-medium text-text-primary\">\n {asset.lastValuationDate ? formatDate(asset.lastValuationDate) : '—'}\n </dd>\n </div>\n <div className=\"flex items-center justify-between border-t border-border-subtle pt-3\">\n <dt className=\"text-text-secondary\">Status</dt>\n <dd>\n <StatusBadge label={asset.status.replace('_', ' ')} variant={getStatusVariant(asset.status)} />\n </dd>\n </div>\n </dl>\n </GlassCard>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AssetDetailPage.js","names":[],"sources":["../../../../src/housingvista/pages/assets/AssetDetailPage.tsx"],"sourcesContent":["/**\n * HousingVista Asset Detail Page\n * Full performance view for a single owned asset backed by the single-item\n * `housingvistaAsset(id)` query — current value, appreciation, rental yield,\n * ROI, and purchase history. Read-only: no fabricated actions.\n * @module housingvista\n */\n\nimport { useNavigate, useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n ArrowUpRight,\n ArrowDownRight,\n Building,\n DollarSign,\n TrendingUp,\n Percent,\n Target,\n MapPin,\n Calendar,\n BarChart3,\n ImageOff,\n} from 'lucide-react';\nimport { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Cell } from 'recharts';\nimport { EmphasisPanel, GlassCard, PagePurpose, NextSteps } from '@burdenoff/fe-libs/ui';\nimport { Breadcrumbs, StatCard, StatusBadge, EmptyState, LoadingSkeleton } from '../../components';\nimport type { BreadcrumbItem } from '../../components';\nimport { useAsset } from '../../hooks';\nimport { cn, formatCurrency, formatDate } from '../../utils/helpers';\nimport type { Asset } from '../../types';\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 AssetDetailPage() {\n const { id } = useParams<{ id: string }>();\n const navigate = useNavigate();\n const { data: asset, loading } = useAsset(id);\n\n if (loading) {\n return (\n <div className=\"flex flex-col gap-6 p-6\">\n <LoadingSkeleton variant=\"detail\" />\n </div>\n );\n }\n\n if (!asset) {\n return (\n <div className=\"p-6\">\n <EmptyState\n illustration=\"empty-search\"\n icon={<Building className=\"h-12 w-12\" />}\n title=\"Asset Not Found\"\n description=\"The asset you're looking for doesn't exist or has been removed.\"\n action={\n <button\n onClick={() => navigate('../assets')}\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 <ArrowLeft className=\"h-4 w-4\" />\n Back to Assets\n </button>\n }\n />\n </div>\n );\n }\n\n const gain = asset.currentValue - asset.purchasePrice;\n const isPositive = gain >= 0;\n const roi = asset.purchasePrice > 0 ? (gain / asset.purchasePrice) * 100 : 0;\n const monthlyRentalIncome = asset.rentalYield ? (asset.currentValue * asset.rentalYield) / 100 / 12 : 0;\n\n const breadcrumbs: BreadcrumbItem[] = [{ label: 'Assets', href: '../' }, { label: asset.title }];\n\n return (\n <div className=\"flex flex-col gap-6 p-6\">\n <Breadcrumbs items={breadcrumbs} />\n\n {/* Header */}\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n <div>\n <button\n onClick={() => navigate('../assets')}\n className=\"mb-2 inline-flex items-center gap-2 rounded-md px-2 py-1 text-sm text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Assets\n </button>\n <h1 className=\"text-2xl font-semibold text-text-primary\">{asset.title}</h1>\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <StatusBadge label={asset.status.replace('_', ' ')} variant={getStatusVariant(asset.status)} dot />\n <StatusBadge label={asset.type} variant=\"muted\" />\n <span className=\"inline-flex items-center gap-1 text-sm text-text-secondary\">\n <MapPin className=\"h-3.5 w-3.5\" />\n {asset.location}\n </span>\n </div>\n </div>\n {asset.imageUrl ? (\n <img src={asset.imageUrl} alt={asset.title} className=\"h-24 w-full rounded-lg object-cover sm:w-40 lg:w-48\" />\n ) : (\n <div\n className=\"flex h-24 w-full items-center justify-center rounded-lg bg-gradient-to-br from-bg-sunken to-bg-sunken/50 sm:w-40 lg:w-48\"\n role=\"img\"\n aria-label={`${asset.title} — no image`}\n >\n <ImageOff className=\"h-8 w-8 text-text-secondary/40\" />\n </div>\n )}\n </div>\n\n <PagePurpose className=\"max-w-3xl\">\n A full performance breakdown for one property you own — what you paid, what it is worth today, how much it has\n appreciated, and the rental yield it generates. Use it to judge whether to hold, renovate, or sell.\n </PagePurpose>\n\n <NextSteps\n storageKey=\"housingvista-asset-detail\"\n steps={[\n {\n id: 'benchmark',\n label: 'Benchmark against the market',\n description: 'Compare this asset against area and market trends.',\n onClick: () => navigate('../market-analytics'),\n },\n {\n id: 'manage-rental',\n label: 'Manage the rental',\n description: 'Review tenants, leases, and rent collection for your units.',\n onClick: () => navigate('../rental'),\n },\n {\n id: 'back',\n label: 'Back to all assets',\n description: 'Return to your full portfolio view.',\n onClick: () => navigate('../assets'),\n },\n ]}\n />\n\n {/* Performance metrics — 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=\"Current Value\"\n value={asset.currentValue}\n prefix=\"$\"\n icon={<DollarSign className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Appreciation\"\n value={`${asset.appreciationPercent.toFixed(1)}%`}\n icon={<TrendingUp className=\"h-4 w-4\" />}\n />\n <StatCard label=\"Overall ROI\" value={`${roi.toFixed(1)}%`} icon={<Target className=\"h-4 w-4\" />} />\n <StatCard\n label=\"Rental Yield\"\n value={asset.rentalYield != null ? `${asset.rentalYield.toFixed(1)}%` : '—'}\n icon={<Percent className=\"h-4 w-4\" />}\n />\n </div>\n </EmphasisPanel>\n\n {/* Detail grid */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Value & performance */}\n <div className=\"space-y-6 lg:col-span-2\">\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-6\">\n <h2 className=\"mb-4 flex items-center gap-2 text-base font-semibold text-text-primary\">\n <BarChart3 className=\"h-5 w-5\" />\n Value & Performance\n </h2>\n <dl className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div className=\"rounded-lg bg-bg-sunken/50 p-4\">\n <dt className=\"text-xs text-text-secondary\">Purchase Price</dt>\n <dd className=\"mt-1 text-lg font-semibold text-text-primary\">{formatCurrency(asset.purchasePrice)}</dd>\n </div>\n <div className=\"rounded-lg bg-bg-sunken/50 p-4\">\n <dt className=\"text-xs text-text-secondary\">Current Value</dt>\n <dd className=\"mt-1 text-lg font-semibold text-text-primary\">{formatCurrency(asset.currentValue)}</dd>\n </div>\n <div className=\"rounded-lg bg-bg-sunken/50 p-4\">\n <dt className=\"text-xs text-text-secondary\">Total Gain / Loss</dt>\n <dd\n className={cn(\n 'mt-1 flex items-center gap-1 text-lg font-semibold',\n isPositive ? 'text-status-success-text' : 'text-status-error-text'\n )}\n >\n {isPositive ? <ArrowUpRight className=\"h-4 w-4\" /> : <ArrowDownRight className=\"h-4 w-4\" />}\n {formatCurrency(Math.abs(gain))} ({roi.toFixed(1)}%)\n </dd>\n </div>\n <div className=\"rounded-lg bg-bg-sunken/50 p-4\">\n <dt className=\"text-xs text-text-secondary\">Est. Monthly Rental Income</dt>\n <dd className=\"mt-1 text-lg font-semibold text-text-primary\">\n {asset.rentalYield ? formatCurrency(Math.round(monthlyRentalIncome)) : '—'}\n </dd>\n </div>\n </dl>\n\n {/* Purchase vs current value — real two-point comparison */}\n <div className=\"mt-6\">\n <p className=\"mb-2 text-xs font-medium text-text-secondary\">Purchase vs current value</p>\n <div className=\"h-44 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <BarChart\n data={[\n { name: 'Purchase', value: asset.purchasePrice, fill: 'var(--color-chart-3)' },\n {\n name: 'Current',\n value: asset.currentValue,\n fill: isPositive ? 'var(--color-chart-2)' : 'var(--color-chart-5)',\n },\n ]}\n margin={{ top: 8, right: 8, left: 0, bottom: 0 }}\n >\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--color-border-subtle)\" vertical={false} />\n <XAxis\n dataKey=\"name\"\n tick={{ fontSize: 11, fill: 'var(--color-text-secondary)' }}\n tickLine={false}\n axisLine={false}\n />\n <YAxis\n tick={{ fontSize: 11, fill: 'var(--color-text-secondary)' }}\n tickLine={false}\n axisLine={false}\n width={56}\n tickFormatter={(v: number) => formatCurrency(v)}\n />\n <Tooltip\n cursor={{ fill: 'var(--color-bg-sunken)' }}\n contentStyle={{\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-subtle)',\n borderRadius: 10,\n fontSize: 12,\n }}\n formatter={(value: number) => [formatCurrency(value), 'Value']}\n />\n <Bar dataKey=\"value\" radius={[6, 6, 0, 0]} maxBarSize={72}>\n <Cell fill=\"var(--color-chart-3)\" />\n <Cell fill={isPositive ? 'var(--color-chart-2)' : 'var(--color-chart-5)'} />\n </Bar>\n </BarChart>\n </ResponsiveContainer>\n </div>\n </div>\n </GlassCard>\n </div>\n\n {/* Purchase & holding info */}\n <div className=\"space-y-6\">\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <h3 className=\"mb-4 flex items-center gap-2 text-base font-semibold text-text-primary\">\n <Calendar className=\"h-5 w-5\" />\n Purchase & Holding\n </h3>\n <dl className=\"space-y-3 text-sm\">\n <div className=\"flex items-center justify-between\">\n <dt className=\"text-text-secondary\">Category</dt>\n <dd className=\"font-medium capitalize text-text-primary\">{asset.category}</dd>\n </div>\n <div className=\"flex items-center justify-between\">\n <dt className=\"text-text-secondary\">Type</dt>\n <dd className=\"font-medium capitalize text-text-primary\">{asset.type.replace('_', ' ')}</dd>\n </div>\n <div className=\"flex items-center justify-between\">\n <dt className=\"text-text-secondary\">Location</dt>\n <dd className=\"font-medium text-text-primary\">{asset.location}</dd>\n </div>\n <div className=\"flex items-center justify-between\">\n <dt className=\"text-text-secondary\">Purchase Date</dt>\n <dd className=\"font-medium text-text-primary\">\n {asset.purchaseDate ? formatDate(asset.purchaseDate) : '—'}\n </dd>\n </div>\n <div className=\"flex items-center justify-between\">\n <dt className=\"text-text-secondary\">Last Valuation</dt>\n <dd className=\"font-medium text-text-primary\">\n {asset.lastValuationDate ? formatDate(asset.lastValuationDate) : '—'}\n </dd>\n </div>\n <div className=\"flex items-center justify-between border-t border-border-subtle pt-3\">\n <dt className=\"text-text-secondary\">Status</dt>\n <dd>\n <StatusBadge label={asset.status.replace('_', ' ')} variant={getStatusVariant(asset.status)} />\n </dd>\n </div>\n </dl>\n </GlassCard>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -22,11 +22,11 @@ import { jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
|
22
22
|
import { Button as y, EmphasisPanel as ve, NextSteps as ye, ResponsiveDialog as be, ResponsiveDialogContent as xe, ResponsiveDialogDescription as Se, ResponsiveDialogFooter as Ce, ResponsiveDialogHeader as we, ResponsiveDialogTitle as Te } from "@burdenoff/fe-libs/ui";
|
|
23
23
|
//#region src/housingvista/pages/assets/AssetManagementPage.tsx
|
|
24
24
|
var b = "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)]", x = [
|
|
25
|
-
"var(--chart-1)",
|
|
26
|
-
"var(--chart-2)",
|
|
27
|
-
"var(--chart-3)",
|
|
28
|
-
"var(--chart-4)",
|
|
29
|
-
"var(--chart-5)"
|
|
25
|
+
"var(--color-chart-1)",
|
|
26
|
+
"var(--color-chart-2)",
|
|
27
|
+
"var(--color-chart-3)",
|
|
28
|
+
"var(--color-chart-4)",
|
|
29
|
+
"var(--color-chart-5)"
|
|
30
30
|
], Ee = {
|
|
31
31
|
background: "var(--color-bg-surface)",
|
|
32
32
|
border: "1px solid var(--color-border-subtle)",
|
|
@@ -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(--chart-1)', 'var(--chart-2)', 'var(--chart-3)', 'var(--chart-4)', 'var(--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(\n 'flex items-center justify-center bg-gradient-to-br from-bg-sunken to-bg-sunken/50',\n className\n )}\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 label={asset.status.replace('_', ' ')} variant={getStatusVariant(asset.status)} dot />\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 = ['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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"}
|
|
@@ -520,11 +520,11 @@ function k() {
|
|
|
520
520
|
y2: "1",
|
|
521
521
|
children: [/* @__PURE__ */ u("stop", {
|
|
522
522
|
offset: "5%",
|
|
523
|
-
stopColor: "var(--chart-1)",
|
|
523
|
+
stopColor: "var(--color-chart-1)",
|
|
524
524
|
stopOpacity: .3
|
|
525
525
|
}), /* @__PURE__ */ u("stop", {
|
|
526
526
|
offset: "95%",
|
|
527
|
-
stopColor: "var(--chart-1)",
|
|
527
|
+
stopColor: "var(--color-chart-1)",
|
|
528
528
|
stopOpacity: 0
|
|
529
529
|
})]
|
|
530
530
|
}) }),
|
|
@@ -549,7 +549,7 @@ function k() {
|
|
|
549
549
|
/* @__PURE__ */ u(le, {
|
|
550
550
|
type: "monotone",
|
|
551
551
|
dataKey: "cumulative",
|
|
552
|
-
stroke: "var(--chart-1)",
|
|
552
|
+
stroke: "var(--color-chart-1)",
|
|
553
553
|
fill: "url(#returnsGrad)",
|
|
554
554
|
strokeWidth: 2
|
|
555
555
|
})
|