@burdenoff/microfe-housingvista 2026.624.2 → 2026.625.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/housingvista/components/ChartCard.js +11 -9
  2. package/dist/housingvista/components/ChartCard.js.map +1 -1
  3. package/dist/housingvista/components/DataTable.js +63 -60
  4. package/dist/housingvista/components/DataTable.js.map +1 -1
  5. package/dist/housingvista/components/EmptyState.js +16 -9
  6. package/dist/housingvista/components/EmptyState.js.map +1 -1
  7. package/dist/housingvista/components/PageLayout.js +21 -14
  8. package/dist/housingvista/components/PageLayout.js.map +1 -1
  9. package/dist/housingvista/components/PropertyCard.js +2 -2
  10. package/dist/housingvista/components/PropertyCard.js.map +1 -1
  11. package/dist/housingvista/components/StatCard.js +20 -17
  12. package/dist/housingvista/components/StatCard.js.map +1 -1
  13. package/dist/housingvista/components/StatusBadge.js +1 -1
  14. package/dist/housingvista/components/StatusBadge.js.map +1 -1
  15. package/dist/housingvista/pages/HomePage.js +101 -58
  16. package/dist/housingvista/pages/HomePage.js.map +1 -1
  17. package/dist/housingvista/pages/amc/AMCManagementPage.js +59 -48
  18. package/dist/housingvista/pages/amc/AMCManagementPage.js.map +1 -1
  19. package/dist/housingvista/pages/analytics/AnalyticsPage.js +113 -90
  20. package/dist/housingvista/pages/analytics/AnalyticsPage.js.map +1 -1
  21. package/dist/housingvista/pages/assets/AssetManagementPage.js +66 -60
  22. package/dist/housingvista/pages/assets/AssetManagementPage.js.map +1 -1
  23. package/dist/housingvista/pages/chat/ChatPage.js +60 -58
  24. package/dist/housingvista/pages/chat/ChatPage.js.map +1 -1
  25. package/dist/housingvista/pages/finance/HomeLoansPage.js +23 -20
  26. package/dist/housingvista/pages/finance/HomeLoansPage.js.map +1 -1
  27. package/dist/housingvista/pages/investments/InvestmentDetailPage.js +114 -81
  28. package/dist/housingvista/pages/investments/InvestmentDetailPage.js.map +1 -1
  29. package/dist/housingvista/pages/investments/InvestmentsPage.js +65 -59
  30. package/dist/housingvista/pages/investments/InvestmentsPage.js.map +1 -1
  31. package/dist/housingvista/pages/legal/LegalPage.js +17 -14
  32. package/dist/housingvista/pages/legal/LegalPage.js.map +1 -1
  33. package/dist/housingvista/pages/market/AreaInsightsPage.js +13 -10
  34. package/dist/housingvista/pages/market/AreaInsightsPage.js.map +1 -1
  35. package/dist/housingvista/pages/market/MarketAnalyticsPage.js +155 -123
  36. package/dist/housingvista/pages/market/MarketAnalyticsPage.js.map +1 -1
  37. package/dist/housingvista/pages/marketplace/MarketplacePage.js +15 -12
  38. package/dist/housingvista/pages/marketplace/MarketplacePage.js.map +1 -1
  39. package/dist/housingvista/pages/notifications/NotificationsPage.js +24 -21
  40. package/dist/housingvista/pages/notifications/NotificationsPage.js.map +1 -1
  41. package/dist/housingvista/pages/properties/AddPropertyPage.js +73 -71
  42. package/dist/housingvista/pages/properties/AddPropertyPage.js.map +1 -1
  43. package/dist/housingvista/pages/properties/PropertiesPage.js +141 -135
  44. package/dist/housingvista/pages/properties/PropertiesPage.js.map +1 -1
  45. package/dist/housingvista/pages/properties/PropertyDetailPage.js +192 -154
  46. package/dist/housingvista/pages/properties/PropertyDetailPage.js.map +1 -1
  47. package/dist/housingvista/pages/rental/RentalManagementPage.js +185 -160
  48. package/dist/housingvista/pages/rental/RentalManagementPage.js.map +1 -1
  49. package/dist/housingvista/pages/settings/ProfilePage.js +51 -49
  50. package/dist/housingvista/pages/settings/ProfilePage.js.map +1 -1
  51. package/dist/housingvista/pages/settings/SettingsPage.js +56 -54
  52. package/dist/housingvista/pages/settings/SettingsPage.js.map +1 -1
  53. package/dist/housingvista/pages/transactions/TransactionDetailPage.js +183 -142
  54. package/dist/housingvista/pages/transactions/TransactionDetailPage.js.map +1 -1
  55. package/dist/housingvista/pages/transactions/TransactionsPage.js +90 -84
  56. package/dist/housingvista/pages/transactions/TransactionsPage.js.map +1 -1
  57. package/dist/housingvista/pages/verification/VerificationPage.js +49 -42
  58. package/dist/housingvista/pages/verification/VerificationPage.js.map +1 -1
  59. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"AMCManagementPage.js","names":[],"sources":["../../../../src/housingvista/pages/amc/AMCManagementPage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport {\n Shield,\n FileText,\n DollarSign,\n Calendar,\n Plus,\n Download,\n CheckCircle,\n Clock,\n AlertTriangle,\n Wrench,\n RotateCcw,\n Eye,\n} from 'lucide-react';\nimport { PageLayout } from '../../components';\nimport { StatCard } from '../../components';\nimport { DataTable, type Column } from '../../components';\nimport { StatusBadge } from '../../components';\nimport { FilterBar } from '../../components';\nimport { EmptyState } from '../../components';\nimport { useAMCContracts } from '../../hooks';\nimport { cn, formatCurrency, formatDate } from '../../utils/helpers';\nimport type { AMCContract } from '../../types';\n\nfunction getStatusVariant(status: AMCContract['status']): 'success' | 'warning' | 'error' | 'muted' {\n switch (status) {\n case 'active':\n return 'success';\n case 'expiring_soon':\n return 'warning';\n case 'expired':\n return 'error';\n case 'cancelled':\n return 'muted';\n default:\n return 'muted';\n }\n}\n\nexport function AMCManagementPage() {\n const { data: amcContracts } = useAMCContracts();\n const [search, setSearch] = useState('');\n\n const filtered = useMemo(() => {\n if (!search) return amcContracts;\n const q = search.toLowerCase();\n return amcContracts.filter(\n (c) =>\n c.propertyTitle.toLowerCase().includes(q) ||\n c.providerName.toLowerCase().includes(q) ||\n c.packageName.toLowerCase().includes(q)\n );\n }, [amcContracts, search]);\n\n const stats = useMemo(() => {\n const activeContracts = amcContracts.filter((c) => c.status === 'active' || c.status === 'expiring_soon').length;\n const pendingRenewals = amcContracts.filter((c) => c.status === 'expiring_soon').length;\n const totalMonthlySpent = amcContracts\n .filter((c) => c.status === 'active' || c.status === 'expiring_soon')\n .reduce((sum, c) => sum + c.monthlyCost, 0);\n const upcomingServices = amcContracts.filter((c) => {\n if (!c.nextServiceDate) return false;\n const serviceDate = new Date(c.nextServiceDate);\n const now = new Date();\n const daysUntil = (serviceDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24);\n return daysUntil >= 0 && daysUntil <= 30;\n }).length;\n return { activeContracts, pendingRenewals, totalMonthlySpent, upcomingServices };\n }, [amcContracts]);\n\n const contractColumns: Column<AMCContract>[] = [\n {\n key: 'packageName',\n header: 'Contract',\n render: (item) => (\n <div>\n <div className=\"font-medium text-text-primary\">{item.packageName}</div>\n <div className=\"text-xs text-text-secondary\">{item.propertyTitle}</div>\n </div>\n ),\n },\n {\n key: 'providerName',\n header: 'Provider',\n render: (item) => <span className=\"text-sm text-text-primary\">{item.providerName}</span>,\n },\n {\n key: 'servicesIncluded',\n header: 'Coverage',\n render: (item) => (\n <div className=\"max-w-[200px]\">\n <span className=\"text-sm text-text-primary\">{item.servicesIncluded.length} services</span>\n <p className=\"truncate text-xs text-text-secondary\">\n {item.servicesIncluded.slice(0, 2).join(', ')}\n {item.servicesIncluded.length > 2 && ` +${item.servicesIncluded.length - 2} more`}\n </p>\n </div>\n ),\n },\n {\n key: 'monthlyCost',\n header: 'Cost',\n render: (item) => (\n <div>\n <span className=\"font-medium text-text-primary\">{formatCurrency(item.monthlyCost)}</span>\n <span className=\"text-xs text-text-secondary\">/mo</span>\n </div>\n ),\n },\n {\n key: 'startDate',\n header: 'Start',\n render: (item) => <span className=\"text-sm text-text-primary\">{formatDate(item.startDate)}</span>,\n },\n {\n key: 'endDate',\n header: 'End',\n render: (item) => <span className=\"text-sm text-text-primary\">{formatDate(item.endDate)}</span>,\n },\n {\n key: 'status',\n header: 'Status',\n render: (item) => (\n <StatusBadge label={item.status.replace('_', ' ')} variant={getStatusVariant(item.status)} dot />\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"AMC Management\"\n description=\"Manage Annual Maintenance Contracts and service packages\"\n actions={\n <div className=\"flex items-center gap-2\">\n <button 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 <Download className=\"h-4 w-4\" />\n Export\n </button>\n <button className=\"inline-flex items-center gap-2 rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors hover:bg-foreground/90\">\n <Plus className=\"h-4 w-4\" />\n New Contract\n </button>\n </div>\n }\n >\n {/* Stats */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard label=\"Active Contracts\" value={stats.activeContracts} icon={<Shield className=\"h-4 w-4\" />} />\n <StatCard\n label=\"Pending Renewals\"\n value={stats.pendingRenewals}\n icon={<RotateCcw className=\"h-4 w-4\" />}\n trend={stats.pendingRenewals > 0 ? 'up' : 'stable'}\n change={stats.pendingRenewals > 0 ? stats.pendingRenewals : 0}\n />\n <StatCard\n label=\"Total Monthly Spent\"\n value={stats.totalMonthlySpent}\n prefix=\"$\"\n icon={<DollarSign className=\"h-4 w-4\" />}\n />\n <StatCard label=\"Upcoming Services\" value={stats.upcomingServices} icon={<Calendar className=\"h-4 w-4\" />} />\n </div>\n\n {/* Service Schedule Section */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm\">\n <h3 className=\"mb-4 text-lg font-semibold text-text-primary\">Upcoming Service Schedule</h3>\n <div className=\"space-y-3\">\n {amcContracts\n .filter((c) => c.nextServiceDate && (c.status === 'active' || c.status === 'expiring_soon'))\n .sort((a, b) => new Date(a.nextServiceDate).getTime() - new Date(b.nextServiceDate).getTime())\n .map((contract) => {\n const serviceDate = new Date(contract.nextServiceDate);\n const now = new Date();\n const daysUntil = Math.ceil((serviceDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));\n const isUrgent = daysUntil <= 7;\n const isPast = daysUntil < 0;\n\n return (\n <div\n key={contract.id}\n className=\"flex flex-col gap-3 rounded-lg bg-bg-sunken/50 p-4 sm:flex-row sm:items-center sm:justify-between\"\n >\n <div className=\"flex items-start gap-3\">\n <div\n className={cn(\n 'rounded-lg p-2',\n isPast\n ? 'bg-red-100 dark:bg-red-900/30'\n : isUrgent\n ? 'bg-amber-100 dark:bg-amber-900/30'\n : 'bg-blue-100 dark:bg-blue-900/30'\n )}\n >\n {isPast ? (\n <AlertTriangle className=\"h-4 w-4 text-red-600 dark:text-red-400\" />\n ) : isUrgent ? (\n <Clock className=\"h-4 w-4 text-amber-600 dark:text-amber-400\" />\n ) : (\n <Wrench className=\"h-4 w-4 text-blue-600 dark:text-blue-400\" />\n )}\n </div>\n <div>\n <p className=\"font-medium text-text-primary\">{contract.propertyTitle}</p>\n <p className=\"text-sm text-text-secondary\">\n {contract.packageName} &middot; {contract.providerName}\n </p>\n <div className=\"mt-1 flex flex-wrap gap-1\">\n {contract.servicesIncluded.slice(0, 3).map((service, idx) => (\n <span\n key={idx}\n className=\"inline-block rounded-md bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {service}\n </span>\n ))}\n {contract.servicesIncluded.length > 3 && (\n <span className=\"inline-block rounded-md bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n +{contract.servicesIncluded.length - 3} more\n </span>\n )}\n </div>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"text-right\">\n <p className=\"text-sm font-medium text-text-primary\">{formatDate(contract.nextServiceDate)}</p>\n <p\n className={cn(\n 'text-xs',\n isPast\n ? 'text-red-600 dark:text-red-400'\n : isUrgent\n ? 'text-amber-600 dark:text-amber-400'\n : 'text-text-secondary'\n )}\n >\n {isPast\n ? `${Math.abs(daysUntil)} days overdue`\n : daysUntil === 0\n ? 'Today'\n : `In ${daysUntil} days`}\n </p>\n </div>\n <StatusBadge\n label={contract.status.replace('_', ' ')}\n variant={getStatusVariant(contract.status)}\n />\n </div>\n </div>\n );\n })}\n </div>\n </div>\n\n {/* Contracts Table */}\n <div>\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">All Contracts</h2>\n <FilterBar\n searchValue={search}\n onSearchChange={setSearch}\n searchPlaceholder=\"Search contracts, providers, properties...\"\n />\n <div className=\"mt-4\">\n {filtered.length > 0 ? (\n <DataTable columns={contractColumns} data={filtered} keyField=\"id\" pageSize={10} />\n ) : (\n <EmptyState\n icon={<Shield className=\"h-12 w-12\" />}\n title=\"No contracts found\"\n description=\"Try adjusting your search criteria or create a new contract.\"\n action={\n <button className=\"inline-flex items-center gap-2 rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors hover:bg-foreground/90\">\n <Plus className=\"h-4 w-4\" />\n New Contract\n </button>\n }\n />\n )}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AMCManagementPage.js","names":[],"sources":["../../../../src/housingvista/pages/amc/AMCManagementPage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport {\n Shield,\n FileText,\n DollarSign,\n Calendar,\n Plus,\n Download,\n CheckCircle,\n Clock,\n AlertTriangle,\n Wrench,\n RotateCcw,\n Eye,\n} from 'lucide-react';\nimport { EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components';\nimport { StatCard } from '../../components';\nimport { DataTable, type Column } from '../../components';\nimport { StatusBadge } from '../../components';\nimport { FilterBar } from '../../components';\nimport { EmptyState } from '../../components';\nimport { useAMCContracts } from '../../hooks';\nimport { cn, formatCurrency, formatDate } from '../../utils/helpers';\nimport type { AMCContract } from '../../types';\n\nfunction getStatusVariant(status: AMCContract['status']): 'success' | 'warning' | 'error' | 'muted' {\n switch (status) {\n case 'active':\n return 'success';\n case 'expiring_soon':\n return 'warning';\n case 'expired':\n return 'error';\n case 'cancelled':\n return 'muted';\n default:\n return 'muted';\n }\n}\n\nexport function AMCManagementPage() {\n const { data: amcContracts } = useAMCContracts();\n const [search, setSearch] = useState('');\n\n const filtered = useMemo(() => {\n if (!search) return amcContracts;\n const q = search.toLowerCase();\n return amcContracts.filter(\n (c) =>\n c.propertyTitle.toLowerCase().includes(q) ||\n c.providerName.toLowerCase().includes(q) ||\n c.packageName.toLowerCase().includes(q)\n );\n }, [amcContracts, search]);\n\n const stats = useMemo(() => {\n const activeContracts = amcContracts.filter((c) => c.status === 'active' || c.status === 'expiring_soon').length;\n const pendingRenewals = amcContracts.filter((c) => c.status === 'expiring_soon').length;\n const totalMonthlySpent = amcContracts\n .filter((c) => c.status === 'active' || c.status === 'expiring_soon')\n .reduce((sum, c) => sum + c.monthlyCost, 0);\n const upcomingServices = amcContracts.filter((c) => {\n if (!c.nextServiceDate) return false;\n const serviceDate = new Date(c.nextServiceDate);\n const now = new Date();\n const daysUntil = (serviceDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24);\n return daysUntil >= 0 && daysUntil <= 30;\n }).length;\n return { activeContracts, pendingRenewals, totalMonthlySpent, upcomingServices };\n }, [amcContracts]);\n\n const scheduledServices = useMemo(\n () =>\n amcContracts\n .filter((c) => c.nextServiceDate && (c.status === 'active' || c.status === 'expiring_soon'))\n .sort((a, b) => new Date(a.nextServiceDate).getTime() - new Date(b.nextServiceDate).getTime()),\n [amcContracts]\n );\n\n const contractColumns: Column<AMCContract>[] = [\n {\n key: 'packageName',\n header: 'Contract',\n render: (item) => (\n <div>\n <div className=\"font-medium text-text-primary\">{item.packageName}</div>\n <div className=\"text-xs text-text-secondary\">{item.propertyTitle}</div>\n </div>\n ),\n },\n {\n key: 'providerName',\n header: 'Provider',\n render: (item) => <span className=\"text-sm text-text-primary\">{item.providerName}</span>,\n },\n {\n key: 'servicesIncluded',\n header: 'Coverage',\n render: (item) => (\n <div className=\"max-w-[200px]\">\n <span className=\"text-sm text-text-primary\">{item.servicesIncluded.length} services</span>\n <p className=\"truncate text-xs text-text-secondary\">\n {item.servicesIncluded.slice(0, 2).join(', ')}\n {item.servicesIncluded.length > 2 && ` +${item.servicesIncluded.length - 2} more`}\n </p>\n </div>\n ),\n },\n {\n key: 'monthlyCost',\n header: 'Cost',\n render: (item) => (\n <div>\n <span className=\"font-medium text-text-primary\">{formatCurrency(item.monthlyCost)}</span>\n <span className=\"text-xs text-text-secondary\">/mo</span>\n </div>\n ),\n },\n {\n key: 'startDate',\n header: 'Start',\n render: (item) => <span className=\"text-sm text-text-primary\">{formatDate(item.startDate)}</span>,\n },\n {\n key: 'endDate',\n header: 'End',\n render: (item) => <span className=\"text-sm text-text-primary\">{formatDate(item.endDate)}</span>,\n },\n {\n key: 'status',\n header: 'Status',\n render: (item) => (\n <StatusBadge label={item.status.replace('_', ' ')} variant={getStatusVariant(item.status)} dot />\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"AMC Management\"\n description=\"Manage Annual Maintenance Contracts and service packages\"\n purpose=\"Keep your properties maintained without surprises. Track every Annual Maintenance Contract, see what each covers and costs, get ahead of renewals, and know which services are due in the next 30 days so nothing slips.\"\n actions={\n <div className=\"flex items-center gap-2\">\n <button 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 <Download className=\"h-4 w-4\" />\n Export\n </button>\n <button className=\"inline-flex items-center gap-2 rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors hover:bg-foreground/90\">\n <Plus className=\"h-4 w-4\" />\n New Contract\n </button>\n </div>\n }\n >\n {/* 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 label=\"Active Contracts\" value={stats.activeContracts} icon={<Shield className=\"h-4 w-4\" />} />\n <StatCard\n label=\"Pending Renewals\"\n value={stats.pendingRenewals}\n icon={<RotateCcw className=\"h-4 w-4\" />}\n trend={stats.pendingRenewals > 0 ? 'up' : 'stable'}\n change={stats.pendingRenewals > 0 ? stats.pendingRenewals : 0}\n />\n <StatCard\n label=\"Total Monthly Spent\"\n value={stats.totalMonthlySpent}\n prefix=\"$\"\n icon={<DollarSign className=\"h-4 w-4\" />}\n />\n <StatCard label=\"Upcoming Services\" value={stats.upcomingServices} icon={<Calendar className=\"h-4 w-4\" />} />\n </div>\n </EmphasisPanel>\n\n {/* Service Schedule Section */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm\">\n <h3 className=\"mb-4 text-lg font-semibold text-text-primary\">Upcoming Service Schedule</h3>\n {scheduledServices.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No services scheduled\"\n description=\"Once your active contracts have upcoming service dates, they'll be listed here in order of priority.\"\n className=\"border-0 bg-transparent p-6\"\n />\n ) : (\n <div className=\"space-y-3\">\n {scheduledServices\n .map((contract) => {\n const serviceDate = new Date(contract.nextServiceDate);\n const now = new Date();\n const daysUntil = Math.ceil((serviceDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));\n const isUrgent = daysUntil <= 7;\n const isPast = daysUntil < 0;\n\n return (\n <div\n key={contract.id}\n className=\"flex flex-col gap-3 rounded-lg bg-bg-sunken/50 p-4 sm:flex-row sm:items-center sm:justify-between\"\n >\n <div className=\"flex items-start gap-3\">\n <div\n className={cn(\n 'rounded-lg p-2',\n isPast\n ? 'bg-red-100 dark:bg-red-900/30'\n : isUrgent\n ? 'bg-amber-100 dark:bg-amber-900/30'\n : 'bg-blue-100 dark:bg-blue-900/30'\n )}\n >\n {isPast ? (\n <AlertTriangle className=\"h-4 w-4 text-red-600 dark:text-red-400\" />\n ) : isUrgent ? (\n <Clock className=\"h-4 w-4 text-amber-600 dark:text-amber-400\" />\n ) : (\n <Wrench className=\"h-4 w-4 text-blue-600 dark:text-blue-400\" />\n )}\n </div>\n <div>\n <p className=\"font-medium text-text-primary\">{contract.propertyTitle}</p>\n <p className=\"text-sm text-text-secondary\">\n {contract.packageName} &middot; {contract.providerName}\n </p>\n <div className=\"mt-1 flex flex-wrap gap-1\">\n {contract.servicesIncluded.slice(0, 3).map((service, idx) => (\n <span\n key={idx}\n className=\"inline-block rounded-md bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {service}\n </span>\n ))}\n {contract.servicesIncluded.length > 3 && (\n <span className=\"inline-block rounded-md bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n +{contract.servicesIncluded.length - 3} more\n </span>\n )}\n </div>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"text-right\">\n <p className=\"text-sm font-medium text-text-primary\">{formatDate(contract.nextServiceDate)}</p>\n <p\n className={cn(\n 'text-xs',\n isPast\n ? 'text-red-600 dark:text-red-400'\n : isUrgent\n ? 'text-amber-600 dark:text-amber-400'\n : 'text-text-secondary'\n )}\n >\n {isPast\n ? `${Math.abs(daysUntil)} days overdue`\n : daysUntil === 0\n ? 'Today'\n : `In ${daysUntil} days`}\n </p>\n </div>\n <StatusBadge\n label={contract.status.replace('_', ' ')}\n variant={getStatusVariant(contract.status)}\n />\n </div>\n </div>\n );\n })}\n </div>\n )}\n </div>\n\n {/* Contracts Table */}\n <div>\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">All Contracts</h2>\n <FilterBar\n searchValue={search}\n onSearchChange={setSearch}\n searchPlaceholder=\"Search contracts, providers, properties...\"\n />\n <div className=\"mt-4\">\n {filtered.length > 0 ? (\n <DataTable columns={contractColumns} data={filtered} keyField=\"id\" pageSize={10} />\n ) : (\n <EmptyState\n illustration=\"empty-search\"\n icon={<Shield className=\"h-12 w-12\" />}\n title=\"No contracts found\"\n description=\"Try adjusting your search criteria or create a new contract.\"\n action={\n <button className=\"inline-flex items-center gap-2 rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors hover:bg-foreground/90\">\n <Plus className=\"h-4 w-4\" />\n New Contract\n </button>\n }\n />\n )}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -8,8 +8,9 @@ import { useAnalyticsData as s } from "../../hooks/index.js";
8
8
  import { useState as c } from "react";
9
9
  import { Activity as l, ArrowDownRight as u, ArrowUpRight as d, Building as f, Calendar as p, Clock as m, DollarSign as h, Download as g, TrendingUp as _ } from "lucide-react";
10
10
  import { jsx as v, jsxs as y } from "react/jsx-runtime";
11
+ import { EmphasisPanel as b, NextSteps as x } from "@burdenoff/fe-libs/ui";
11
12
  //#region src/housingvista/pages/analytics/AnalyticsPage.tsx
12
- var b = [
13
+ var S = [
13
14
  {
14
15
  value: "week",
15
16
  label: "Week"
@@ -27,16 +28,31 @@ var b = [
27
28
  label: "Year"
28
29
  }
29
30
  ];
30
- function x() {
31
- let { data: x } = s(), [S, C] = c("month");
31
+ function C() {
32
+ let { data: C } = s(), [w, T] = c("month");
32
33
  return /* @__PURE__ */ y(r, {
33
34
  title: "Analytics",
34
35
  description: "Track your property portfolio performance and market insights",
36
+ purpose: "Understand how your portfolio is actually performing — revenue versus expenses, occupancy, property-type mix, and profitability — so you can spot trends, compare periods, and decide where to buy, sell, or raise rents.",
35
37
  actions: /* @__PURE__ */ y("button", {
36
38
  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",
37
39
  children: [/* @__PURE__ */ v(g, { className: "h-4 w-4" }), "Export Report"]
38
40
  }),
39
41
  children: [
42
+ /* @__PURE__ */ v(x, {
43
+ storageKey: "housingvista-analytics",
44
+ steps: [{
45
+ id: "period-year",
46
+ label: "See the full-year picture",
47
+ description: "Switch the period to Year to view long-term trends.",
48
+ onClick: () => T("year")
49
+ }, {
50
+ id: "performance",
51
+ label: "Drill into monthly performance",
52
+ description: "Review revenue, expenses, net income, and margin month by month.",
53
+ href: "#analytics-performance"
54
+ }]
55
+ }),
40
56
  /* @__PURE__ */ y("div", {
41
57
  className: "flex flex-wrap items-center gap-2",
42
58
  children: [
@@ -45,30 +61,33 @@ function x() {
45
61
  className: "text-sm font-medium text-text-secondary",
46
62
  children: "Period:"
47
63
  }),
48
- b.map((t) => /* @__PURE__ */ v("button", {
49
- onClick: () => C(t.value),
50
- className: e("rounded-md px-3 py-1.5 text-sm font-medium transition-colors", S === t.value ? "bg-foreground text-background" : "bg-bg-sunken text-text-secondary hover:text-text-primary"),
64
+ S.map((t) => /* @__PURE__ */ v("button", {
65
+ onClick: () => T(t.value),
66
+ className: e("rounded-md px-3 py-1.5 text-sm font-medium transition-colors", w === t.value ? "bg-foreground text-background" : "bg-bg-sunken text-text-secondary hover:text-text-primary"),
51
67
  children: t.label
52
68
  }, t.value))
53
69
  ]
54
70
  }),
55
- /* @__PURE__ */ v("div", {
56
- className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
57
- children: x.kpis.map((e) => {
58
- let t = {
59
- "Portfolio Value": h,
60
- "Monthly Revenue": _,
61
- "Occupancy Rate": f,
62
- "Avg. Days to Rent": m
63
- }[e.label] || l;
64
- return /* @__PURE__ */ v(i, {
65
- label: e.label,
66
- value: e.unit === "USD" ? e.value : `${n(e.value)}${e.unit === "%" ? "%" : ` ${e.unit}`}`,
67
- prefix: e.unit === "USD" ? "$" : void 0,
68
- icon: /* @__PURE__ */ v(t, { className: "h-4 w-4" }),
69
- trend: e.trend === "up" ? "up" : e.trend === "down" ? "down" : "stable",
70
- change: e.changePercent
71
- }, e.id);
71
+ /* @__PURE__ */ v(b, {
72
+ className: "p-4",
73
+ children: /* @__PURE__ */ v("div", {
74
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
75
+ children: C.kpis.map((e) => {
76
+ let t = {
77
+ "Portfolio Value": h,
78
+ "Monthly Revenue": _,
79
+ "Occupancy Rate": f,
80
+ "Avg. Days to Rent": m
81
+ }[e.label] || l;
82
+ return /* @__PURE__ */ v(i, {
83
+ label: e.label,
84
+ value: e.unit === "USD" ? e.value : `${n(e.value)}${e.unit === "%" ? "%" : ` ${e.unit}`}`,
85
+ prefix: e.unit === "USD" ? "$" : void 0,
86
+ icon: /* @__PURE__ */ v(t, { className: "h-4 w-4" }),
87
+ trend: e.trend === "up" ? "up" : e.trend === "down" ? "down" : "stable",
88
+ change: e.changePercent
89
+ }, e.id);
90
+ })
72
91
  })
73
92
  }),
74
93
  /* @__PURE__ */ y("div", {
@@ -79,8 +98,8 @@ function x() {
79
98
  description: "Monthly revenue and expense trends",
80
99
  children: /* @__PURE__ */ y("div", {
81
100
  className: "space-y-3",
82
- children: [x.revenueByMonth.slice(-6).map((e) => {
83
- let n = Math.max(...x.revenueByMonth.map((e) => e.revenue)), r = e.revenue / n * 100, i = e.expenses / n * 100;
101
+ children: [C.revenueByMonth.slice(-6).map((e) => {
102
+ let n = Math.max(...C.revenueByMonth.map((e) => e.revenue)), r = e.revenue / n * 100, i = e.expenses / n * 100;
84
103
  return /* @__PURE__ */ y("div", {
85
104
  className: "space-y-1",
86
105
  children: [
@@ -131,8 +150,8 @@ function x() {
131
150
  description: "Portfolio breakdown by property type",
132
151
  children: /* @__PURE__ */ v("div", {
133
152
  className: "space-y-4",
134
- children: x.propertyTypeDistribution.map((n) => {
135
- let r = x.propertyTypeDistribution.reduce((e, t) => e + t.value, 0), i = r > 0 ? n.value / r * 100 : 0, a = {
153
+ children: C.propertyTypeDistribution.map((n) => {
154
+ let r = C.propertyTypeDistribution.reduce((e, t) => e + t.value, 0), i = r > 0 ? n.value / r * 100 : 0, a = {
136
155
  Residential: "bg-blue-500",
137
156
  Commercial: "bg-purple-500",
138
157
  Agricultural: "bg-emerald-500"
@@ -173,7 +192,7 @@ function x() {
173
192
  description: "Monthly occupancy rate percentage",
174
193
  children: /* @__PURE__ */ v("div", {
175
194
  className: "space-y-3",
176
- children: x.occupancyRate.slice(-6).map((t, n, r) => {
195
+ children: C.occupancyRate.slice(-6).map((t, n, r) => {
177
196
  let i = n > 0 ? r[n - 1].rate : t.rate, a = t.rate - i;
178
197
  return /* @__PURE__ */ y("div", {
179
198
  className: "flex items-center justify-between",
@@ -216,7 +235,7 @@ function x() {
216
235
  children: "Total Revenue (YTD)"
217
236
  }), /* @__PURE__ */ v("p", {
218
237
  className: "text-lg font-bold text-text-primary",
219
- children: t(x.revenueByMonth.reduce((e, t) => e + t.revenue, 0))
238
+ children: t(C.revenueByMonth.reduce((e, t) => e + t.revenue, 0))
220
239
  })] }), /* @__PURE__ */ y("div", {
221
240
  className: "text-right",
222
241
  children: [/* @__PURE__ */ v("p", {
@@ -224,7 +243,7 @@ function x() {
224
243
  children: "Total Expenses (YTD)"
225
244
  }), /* @__PURE__ */ v("p", {
226
245
  className: "text-lg font-bold text-text-primary",
227
- children: t(x.revenueByMonth.reduce((e, t) => e + t.expenses, 0))
246
+ children: t(C.revenueByMonth.reduce((e, t) => e + t.expenses, 0))
228
247
  })]
229
248
  })]
230
249
  }), /* @__PURE__ */ y("div", {
@@ -233,7 +252,7 @@ function x() {
233
252
  className: "rounded-lg bg-emerald-50 p-3 text-center dark:bg-emerald-900/20",
234
253
  children: [/* @__PURE__ */ v("p", {
235
254
  className: "text-xl font-bold text-emerald-600 dark:text-emerald-400",
236
- children: t(x.revenueByMonth.reduce((e, t) => e + t.revenue, 0) - x.revenueByMonth.reduce((e, t) => e + t.expenses, 0))
255
+ children: t(C.revenueByMonth.reduce((e, t) => e + t.revenue, 0) - C.revenueByMonth.reduce((e, t) => e + t.expenses, 0))
237
256
  }), /* @__PURE__ */ v("p", {
238
257
  className: "text-xs text-text-secondary",
239
258
  children: "Net Income"
@@ -242,7 +261,7 @@ function x() {
242
261
  className: "rounded-lg bg-blue-50 p-3 text-center dark:bg-blue-900/20",
243
262
  children: [/* @__PURE__ */ y("p", {
244
263
  className: "text-xl font-bold text-blue-600 dark:text-blue-400",
245
- children: [((x.revenueByMonth.reduce((e, t) => e + t.revenue, 0) - x.revenueByMonth.reduce((e, t) => e + t.expenses, 0)) / x.revenueByMonth.reduce((e, t) => e + t.revenue, 0) * 100).toFixed(1), "%"]
264
+ children: [((C.revenueByMonth.reduce((e, t) => e + t.revenue, 0) - C.revenueByMonth.reduce((e, t) => e + t.expenses, 0)) / C.revenueByMonth.reduce((e, t) => e + t.revenue, 0) * 100).toFixed(1), "%"]
246
265
  }), /* @__PURE__ */ v("p", {
247
266
  className: "text-xs text-text-secondary",
248
267
  children: "Profit Margin"
@@ -253,68 +272,72 @@ function x() {
253
272
  })
254
273
  ]
255
274
  }),
256
- /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("h2", {
257
- className: "mb-4 text-lg font-semibold text-text-primary",
258
- children: "Monthly Performance"
259
- }), /* @__PURE__ */ v(a, {
260
- columns: [
261
- {
262
- key: "month",
263
- header: "Month",
264
- render: (e) => /* @__PURE__ */ v("span", {
265
- className: "font-medium text-text-primary",
266
- children: e.month
267
- })
268
- },
269
- {
270
- key: "revenue",
271
- header: "Revenue",
272
- render: (e) => /* @__PURE__ */ v("span", {
273
- className: "font-medium text-emerald-600 dark:text-emerald-400",
274
- children: t(e.revenue)
275
- })
276
- },
277
- {
278
- key: "expenses",
279
- header: "Expenses",
280
- render: (e) => /* @__PURE__ */ v("span", {
281
- className: "font-medium text-red-600 dark:text-red-400",
282
- children: t(e.expenses)
283
- })
284
- },
285
- {
286
- key: "net",
287
- header: "Net Income",
288
- sortable: !1,
289
- render: (n) => {
290
- let r = n.revenue - n.expenses;
291
- return /* @__PURE__ */ v("span", {
292
- className: e("font-medium", r >= 0 ? "text-emerald-600 dark:text-emerald-400" : "text-red-600 dark:text-red-400"),
293
- children: t(r)
294
- });
295
- }
296
- },
297
- {
298
- key: "margin",
299
- header: "Margin",
300
- sortable: !1,
301
- render: (t) => {
302
- let n = t.revenue > 0 ? (t.revenue - t.expenses) / t.revenue * 100 : 0;
303
- return /* @__PURE__ */ y("span", {
304
- className: e("font-medium", n >= 0 ? "text-emerald-600 dark:text-emerald-400" : "text-red-600 dark:text-red-400"),
305
- children: [n.toFixed(1), "%"]
306
- });
275
+ /* @__PURE__ */ y("div", {
276
+ id: "analytics-performance",
277
+ className: "scroll-mt-6",
278
+ children: [/* @__PURE__ */ v("h2", {
279
+ className: "mb-4 text-lg font-semibold text-text-primary",
280
+ children: "Monthly Performance"
281
+ }), /* @__PURE__ */ v(a, {
282
+ columns: [
283
+ {
284
+ key: "month",
285
+ header: "Month",
286
+ render: (e) => /* @__PURE__ */ v("span", {
287
+ className: "font-medium text-text-primary",
288
+ children: e.month
289
+ })
290
+ },
291
+ {
292
+ key: "revenue",
293
+ header: "Revenue",
294
+ render: (e) => /* @__PURE__ */ v("span", {
295
+ className: "font-medium text-emerald-600 dark:text-emerald-400",
296
+ children: t(e.revenue)
297
+ })
298
+ },
299
+ {
300
+ key: "expenses",
301
+ header: "Expenses",
302
+ render: (e) => /* @__PURE__ */ v("span", {
303
+ className: "font-medium text-red-600 dark:text-red-400",
304
+ children: t(e.expenses)
305
+ })
306
+ },
307
+ {
308
+ key: "net",
309
+ header: "Net Income",
310
+ sortable: !1,
311
+ render: (n) => {
312
+ let r = n.revenue - n.expenses;
313
+ return /* @__PURE__ */ v("span", {
314
+ className: e("font-medium", r >= 0 ? "text-emerald-600 dark:text-emerald-400" : "text-red-600 dark:text-red-400"),
315
+ children: t(r)
316
+ });
317
+ }
318
+ },
319
+ {
320
+ key: "margin",
321
+ header: "Margin",
322
+ sortable: !1,
323
+ render: (t) => {
324
+ let n = t.revenue > 0 ? (t.revenue - t.expenses) / t.revenue * 100 : 0;
325
+ return /* @__PURE__ */ y("span", {
326
+ className: e("font-medium", n >= 0 ? "text-emerald-600 dark:text-emerald-400" : "text-red-600 dark:text-red-400"),
327
+ children: [n.toFixed(1), "%"]
328
+ });
329
+ }
307
330
  }
308
- }
309
- ],
310
- data: x.revenueByMonth,
311
- keyField: "month",
312
- pageSize: 12
313
- })] })
331
+ ],
332
+ data: C.revenueByMonth,
333
+ keyField: "month",
334
+ pageSize: 12
335
+ })]
336
+ })
314
337
  ]
315
338
  });
316
339
  }
317
340
  //#endregion
318
- export { x as AnalyticsPage };
341
+ export { C as AnalyticsPage };
319
342
 
320
343
  //# sourceMappingURL=AnalyticsPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../../src/housingvista/pages/analytics/AnalyticsPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n BarChart3,\n TrendingUp,\n DollarSign,\n Building,\n Activity,\n Calendar,\n Download,\n PieChart,\n ArrowUpRight,\n ArrowDownRight,\n Clock,\n} from 'lucide-react';\nimport { PageLayout } from '../../components';\nimport { StatCard } from '../../components';\nimport { ChartCard } from '../../components';\nimport { DataTable, type Column } from '../../components';\nimport { useAnalyticsData } from '../../hooks';\nimport { cn, formatCurrency, formatNumber } from '../../utils/helpers';\n\ntype TimePeriod = 'week' | 'month' | 'quarter' | 'year';\n\nconst periodTabs: { value: TimePeriod; label: string }[] = [\n { value: 'week', label: 'Week' },\n { value: 'month', label: 'Month' },\n { value: 'quarter', label: 'Quarter' },\n { value: 'year', label: 'Year' },\n];\n\nexport function AnalyticsPage() {\n const { data: analyticsData } = useAnalyticsData();\n const [selectedPeriod, setSelectedPeriod] = useState<TimePeriod>('month');\n\n const revenueColumns: Column<{ month: string; revenue: number; expenses: number }>[] = [\n {\n key: 'month',\n header: 'Month',\n render: (item) => <span className=\"font-medium text-text-primary\">{item.month}</span>,\n },\n {\n key: 'revenue',\n header: 'Revenue',\n render: (item) => (\n <span className=\"font-medium text-emerald-600 dark:text-emerald-400\">{formatCurrency(item.revenue)}</span>\n ),\n },\n {\n key: 'expenses',\n header: 'Expenses',\n render: (item) => (\n <span className=\"font-medium text-red-600 dark:text-red-400\">{formatCurrency(item.expenses)}</span>\n ),\n },\n {\n key: 'net',\n header: 'Net Income',\n sortable: false,\n render: (item) => {\n const net = item.revenue - item.expenses;\n return (\n <span\n className={cn(\n 'font-medium',\n net >= 0 ? 'text-emerald-600 dark:text-emerald-400' : 'text-red-600 dark:text-red-400'\n )}\n >\n {formatCurrency(net)}\n </span>\n );\n },\n },\n {\n key: 'margin',\n header: 'Margin',\n sortable: false,\n render: (item) => {\n const margin = item.revenue > 0 ? ((item.revenue - item.expenses) / item.revenue) * 100 : 0;\n return (\n <span\n className={cn(\n 'font-medium',\n margin >= 0 ? 'text-emerald-600 dark:text-emerald-400' : 'text-red-600 dark:text-red-400'\n )}\n >\n {margin.toFixed(1)}%\n </span>\n );\n },\n },\n ];\n\n return (\n <PageLayout\n title=\"Analytics\"\n description=\"Track your property portfolio performance and market insights\"\n actions={\n <button 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 <Download className=\"h-4 w-4\" />\n Export Report\n </button>\n }\n >\n {/* Time Period Selector */}\n <div className=\"flex flex-wrap items-center gap-2\">\n <Calendar className=\"h-4 w-4 text-text-secondary\" />\n <span className=\"text-sm font-medium text-text-secondary\">Period:</span>\n {periodTabs.map((tab) => (\n <button\n key={tab.value}\n onClick={() => setSelectedPeriod(tab.value)}\n className={cn(\n 'rounded-md px-3 py-1.5 text-sm font-medium transition-colors',\n selectedPeriod === tab.value\n ? 'bg-foreground text-background'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {tab.label}\n </button>\n ))}\n </div>\n\n {/* Stats Overview Row */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {analyticsData.kpis.map((kpi) => {\n const icons: Record<string, typeof DollarSign> = {\n 'Portfolio Value': DollarSign,\n 'Monthly Revenue': TrendingUp,\n 'Occupancy Rate': Building,\n 'Avg. Days to Rent': Clock,\n };\n const IconComponent = icons[kpi.label] || Activity;\n\n return (\n <StatCard\n key={kpi.id}\n label={kpi.label}\n value={\n kpi.unit === 'USD' ? kpi.value : `${formatNumber(kpi.value)}${kpi.unit === '%' ? '%' : ` ${kpi.unit}`}`\n }\n prefix={kpi.unit === 'USD' ? '$' : undefined}\n icon={<IconComponent className=\"h-4 w-4\" />}\n trend={kpi.trend === 'up' ? 'up' : kpi.trend === 'down' ? 'down' : 'stable'}\n change={kpi.changePercent}\n />\n );\n })}\n </div>\n\n {/* Charts Section */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n {/* Revenue Chart */}\n <ChartCard title=\"Revenue vs Expenses\" description=\"Monthly revenue and expense trends\">\n <div className=\"space-y-3\">\n {/* Simple bar representation */}\n {analyticsData.revenueByMonth.slice(-6).map((item) => {\n const maxVal = Math.max(...analyticsData.revenueByMonth.map((r) => r.revenue));\n const revWidth = (item.revenue / maxVal) * 100;\n const expWidth = (item.expenses / maxVal) * 100;\n return (\n <div key={item.month} className=\"space-y-1\">\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"font-medium text-text-primary\">{item.month}</span>\n <span className=\"text-text-secondary\">\n {formatCurrency(item.revenue)} / {formatCurrency(item.expenses)}\n </span>\n </div>\n <div className=\"flex gap-1\">\n <div className=\"h-2 rounded-full bg-emerald-500\" style={{ width: `${revWidth}%` }} />\n </div>\n <div className=\"flex gap-1\">\n <div className=\"h-2 rounded-full bg-red-400\" style={{ width: `${expWidth}%` }} />\n </div>\n </div>\n );\n })}\n <div className=\"flex items-center gap-4 pt-2 text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <span className=\"h-2 w-2 rounded-full bg-emerald-500\" />\n Revenue\n </span>\n <span className=\"flex items-center gap-1\">\n <span className=\"h-2 w-2 rounded-full bg-red-400\" />\n Expenses\n </span>\n </div>\n </div>\n </ChartCard>\n\n {/* Property Distribution */}\n <ChartCard title=\"Property Type Distribution\" description=\"Portfolio breakdown by property type\">\n <div className=\"space-y-4\">\n {analyticsData.propertyTypeDistribution.map((item) => {\n const totalValue = analyticsData.propertyTypeDistribution.reduce((s, d) => s + d.value, 0);\n const pct = totalValue > 0 ? (item.value / totalValue) * 100 : 0;\n const colors: Record<string, string> = {\n Residential: 'bg-blue-500',\n Commercial: 'bg-purple-500',\n Agricultural: 'bg-emerald-500',\n };\n return (\n <div key={item.type} className=\"space-y-1\">\n <div className=\"flex items-center justify-between text-sm\">\n <div className=\"flex items-center gap-2\">\n <span className={cn('h-3 w-3 rounded-full', colors[item.type] || 'bg-muted-foreground')} />\n <span className=\"font-medium text-text-primary\">{item.type}</span>\n </div>\n <div className=\"flex items-center gap-3\">\n <span className=\"text-text-secondary\">{item.count} properties</span>\n <span className=\"font-medium text-text-primary\">{formatCurrency(item.value)}</span>\n </div>\n </div>\n <div className=\"h-2 w-full rounded-full bg-bg-sunken\">\n <div\n className={cn('h-2 rounded-full', colors[item.type] || 'bg-muted-foreground')}\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n );\n })}\n </div>\n </ChartCard>\n\n {/* Occupancy Rate */}\n <ChartCard title=\"Occupancy Rate Trend\" description=\"Monthly occupancy rate percentage\">\n <div className=\"space-y-3\">\n {analyticsData.occupancyRate.slice(-6).map((item, idx, arr) => {\n const prev = idx > 0 ? arr[idx - 1].rate : item.rate;\n const change = item.rate - prev;\n return (\n <div key={item.month} className=\"flex items-center justify-between\">\n <span className=\"text-sm font-medium text-text-primary\">{item.month}</span>\n <div className=\"flex items-center gap-2\">\n <div className=\"h-2 w-24 rounded-full bg-bg-sunken\">\n <div className=\"h-2 rounded-full bg-blue-500\" style={{ width: `${item.rate}%` }} />\n </div>\n <span className=\"w-12 text-right text-sm font-semibold text-text-primary\">{item.rate}%</span>\n {change !== 0 && (\n <span\n className={cn('flex items-center text-xs', change > 0 ? 'text-emerald-600' : 'text-red-600')}\n >\n {change > 0 ? <ArrowUpRight className=\"h-3 w-3\" /> : <ArrowDownRight className=\"h-3 w-3\" />}\n {Math.abs(change)}\n </span>\n )}\n </div>\n </div>\n );\n })}\n </div>\n </ChartCard>\n\n {/* Recent Activity */}\n <ChartCard title=\"Quick Stats\" description=\"Key performance indicators at a glance\">\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between rounded-lg bg-bg-sunken/50 p-3\">\n <div>\n <p className=\"text-sm text-text-secondary\">Total Revenue (YTD)</p>\n <p className=\"text-lg font-bold text-text-primary\">\n {formatCurrency(analyticsData.revenueByMonth.reduce((s, r) => s + r.revenue, 0))}\n </p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm text-text-secondary\">Total Expenses (YTD)</p>\n <p className=\"text-lg font-bold text-text-primary\">\n {formatCurrency(analyticsData.revenueByMonth.reduce((s, r) => s + r.expenses, 0))}\n </p>\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"rounded-lg bg-emerald-50 p-3 text-center dark:bg-emerald-900/20\">\n <p className=\"text-xl font-bold text-emerald-600 dark:text-emerald-400\">\n {formatCurrency(\n analyticsData.revenueByMonth.reduce((s, r) => s + r.revenue, 0) -\n analyticsData.revenueByMonth.reduce((s, r) => s + r.expenses, 0)\n )}\n </p>\n <p className=\"text-xs text-text-secondary\">Net Income</p>\n </div>\n <div className=\"rounded-lg bg-blue-50 p-3 text-center dark:bg-blue-900/20\">\n <p className=\"text-xl font-bold text-blue-600 dark:text-blue-400\">\n {(\n ((analyticsData.revenueByMonth.reduce((s, r) => s + r.revenue, 0) -\n analyticsData.revenueByMonth.reduce((s, r) => s + r.expenses, 0)) /\n analyticsData.revenueByMonth.reduce((s, r) => s + r.revenue, 0)) *\n 100\n ).toFixed(1)}\n %\n </p>\n <p className=\"text-xs text-text-secondary\">Profit Margin</p>\n </div>\n </div>\n </div>\n </ChartCard>\n </div>\n\n {/* Performance Metrics Table */}\n <div>\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Monthly Performance</h2>\n <DataTable columns={revenueColumns} data={analyticsData.revenueByMonth} keyField=\"month\" pageSize={12} />\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../../src/housingvista/pages/analytics/AnalyticsPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n BarChart3,\n TrendingUp,\n DollarSign,\n Building,\n Activity,\n Calendar,\n Download,\n PieChart,\n ArrowUpRight,\n ArrowDownRight,\n Clock,\n} from 'lucide-react';\nimport { EmphasisPanel, NextSteps } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components';\nimport { StatCard } from '../../components';\nimport { ChartCard } from '../../components';\nimport { DataTable, type Column } from '../../components';\nimport { useAnalyticsData } from '../../hooks';\nimport { cn, formatCurrency, formatNumber } from '../../utils/helpers';\n\ntype TimePeriod = 'week' | 'month' | 'quarter' | 'year';\n\nconst periodTabs: { value: TimePeriod; label: string }[] = [\n { value: 'week', label: 'Week' },\n { value: 'month', label: 'Month' },\n { value: 'quarter', label: 'Quarter' },\n { value: 'year', label: 'Year' },\n];\n\nexport function AnalyticsPage() {\n const { data: analyticsData } = useAnalyticsData();\n const [selectedPeriod, setSelectedPeriod] = useState<TimePeriod>('month');\n\n const revenueColumns: Column<{ month: string; revenue: number; expenses: number }>[] = [\n {\n key: 'month',\n header: 'Month',\n render: (item) => <span className=\"font-medium text-text-primary\">{item.month}</span>,\n },\n {\n key: 'revenue',\n header: 'Revenue',\n render: (item) => (\n <span className=\"font-medium text-emerald-600 dark:text-emerald-400\">{formatCurrency(item.revenue)}</span>\n ),\n },\n {\n key: 'expenses',\n header: 'Expenses',\n render: (item) => (\n <span className=\"font-medium text-red-600 dark:text-red-400\">{formatCurrency(item.expenses)}</span>\n ),\n },\n {\n key: 'net',\n header: 'Net Income',\n sortable: false,\n render: (item) => {\n const net = item.revenue - item.expenses;\n return (\n <span\n className={cn(\n 'font-medium',\n net >= 0 ? 'text-emerald-600 dark:text-emerald-400' : 'text-red-600 dark:text-red-400'\n )}\n >\n {formatCurrency(net)}\n </span>\n );\n },\n },\n {\n key: 'margin',\n header: 'Margin',\n sortable: false,\n render: (item) => {\n const margin = item.revenue > 0 ? ((item.revenue - item.expenses) / item.revenue) * 100 : 0;\n return (\n <span\n className={cn(\n 'font-medium',\n margin >= 0 ? 'text-emerald-600 dark:text-emerald-400' : 'text-red-600 dark:text-red-400'\n )}\n >\n {margin.toFixed(1)}%\n </span>\n );\n },\n },\n ];\n\n return (\n <PageLayout\n title=\"Analytics\"\n description=\"Track your property portfolio performance and market insights\"\n purpose=\"Understand how your portfolio is actually performing — revenue versus expenses, occupancy, property-type mix, and profitability — so you can spot trends, compare periods, and decide where to buy, sell, or raise rents.\"\n actions={\n <button 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 <Download className=\"h-4 w-4\" />\n Export Report\n </button>\n }\n >\n <NextSteps\n storageKey=\"housingvista-analytics\"\n steps={[\n {\n id: 'period-year',\n label: 'See the full-year picture',\n description: 'Switch the period to Year to view long-term trends.',\n onClick: () => setSelectedPeriod('year'),\n },\n {\n id: 'performance',\n label: 'Drill into monthly performance',\n description: 'Review revenue, expenses, net income, and margin month by month.',\n href: '#analytics-performance',\n },\n ]}\n />\n\n {/* Time Period Selector */}\n <div className=\"flex flex-wrap items-center gap-2\">\n <Calendar className=\"h-4 w-4 text-text-secondary\" />\n <span className=\"text-sm font-medium text-text-secondary\">Period:</span>\n {periodTabs.map((tab) => (\n <button\n key={tab.value}\n onClick={() => setSelectedPeriod(tab.value)}\n className={cn(\n 'rounded-md px-3 py-1.5 text-sm font-medium transition-colors',\n selectedPeriod === tab.value\n ? 'bg-foreground text-background'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {tab.label}\n </button>\n ))}\n </div>\n\n {/* Stats Overview Row — 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 {analyticsData.kpis.map((kpi) => {\n const icons: Record<string, typeof DollarSign> = {\n 'Portfolio Value': DollarSign,\n 'Monthly Revenue': TrendingUp,\n 'Occupancy Rate': Building,\n 'Avg. Days to Rent': Clock,\n };\n const IconComponent = icons[kpi.label] || Activity;\n\n return (\n <StatCard\n key={kpi.id}\n label={kpi.label}\n value={\n kpi.unit === 'USD' ? kpi.value : `${formatNumber(kpi.value)}${kpi.unit === '%' ? '%' : ` ${kpi.unit}`}`\n }\n prefix={kpi.unit === 'USD' ? '$' : undefined}\n icon={<IconComponent className=\"h-4 w-4\" />}\n trend={kpi.trend === 'up' ? 'up' : kpi.trend === 'down' ? 'down' : 'stable'}\n change={kpi.changePercent}\n />\n );\n })}\n </div>\n </EmphasisPanel>\n\n {/* Charts Section */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n {/* Revenue Chart */}\n <ChartCard title=\"Revenue vs Expenses\" description=\"Monthly revenue and expense trends\">\n <div className=\"space-y-3\">\n {/* Simple bar representation */}\n {analyticsData.revenueByMonth.slice(-6).map((item) => {\n const maxVal = Math.max(...analyticsData.revenueByMonth.map((r) => r.revenue));\n const revWidth = (item.revenue / maxVal) * 100;\n const expWidth = (item.expenses / maxVal) * 100;\n return (\n <div key={item.month} className=\"space-y-1\">\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"font-medium text-text-primary\">{item.month}</span>\n <span className=\"text-text-secondary\">\n {formatCurrency(item.revenue)} / {formatCurrency(item.expenses)}\n </span>\n </div>\n <div className=\"flex gap-1\">\n <div className=\"h-2 rounded-full bg-emerald-500\" style={{ width: `${revWidth}%` }} />\n </div>\n <div className=\"flex gap-1\">\n <div className=\"h-2 rounded-full bg-red-400\" style={{ width: `${expWidth}%` }} />\n </div>\n </div>\n );\n })}\n <div className=\"flex items-center gap-4 pt-2 text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <span className=\"h-2 w-2 rounded-full bg-emerald-500\" />\n Revenue\n </span>\n <span className=\"flex items-center gap-1\">\n <span className=\"h-2 w-2 rounded-full bg-red-400\" />\n Expenses\n </span>\n </div>\n </div>\n </ChartCard>\n\n {/* Property Distribution */}\n <ChartCard title=\"Property Type Distribution\" description=\"Portfolio breakdown by property type\">\n <div className=\"space-y-4\">\n {analyticsData.propertyTypeDistribution.map((item) => {\n const totalValue = analyticsData.propertyTypeDistribution.reduce((s, d) => s + d.value, 0);\n const pct = totalValue > 0 ? (item.value / totalValue) * 100 : 0;\n const colors: Record<string, string> = {\n Residential: 'bg-blue-500',\n Commercial: 'bg-purple-500',\n Agricultural: 'bg-emerald-500',\n };\n return (\n <div key={item.type} className=\"space-y-1\">\n <div className=\"flex items-center justify-between text-sm\">\n <div className=\"flex items-center gap-2\">\n <span className={cn('h-3 w-3 rounded-full', colors[item.type] || 'bg-muted-foreground')} />\n <span className=\"font-medium text-text-primary\">{item.type}</span>\n </div>\n <div className=\"flex items-center gap-3\">\n <span className=\"text-text-secondary\">{item.count} properties</span>\n <span className=\"font-medium text-text-primary\">{formatCurrency(item.value)}</span>\n </div>\n </div>\n <div className=\"h-2 w-full rounded-full bg-bg-sunken\">\n <div\n className={cn('h-2 rounded-full', colors[item.type] || 'bg-muted-foreground')}\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n );\n })}\n </div>\n </ChartCard>\n\n {/* Occupancy Rate */}\n <ChartCard title=\"Occupancy Rate Trend\" description=\"Monthly occupancy rate percentage\">\n <div className=\"space-y-3\">\n {analyticsData.occupancyRate.slice(-6).map((item, idx, arr) => {\n const prev = idx > 0 ? arr[idx - 1].rate : item.rate;\n const change = item.rate - prev;\n return (\n <div key={item.month} className=\"flex items-center justify-between\">\n <span className=\"text-sm font-medium text-text-primary\">{item.month}</span>\n <div className=\"flex items-center gap-2\">\n <div className=\"h-2 w-24 rounded-full bg-bg-sunken\">\n <div className=\"h-2 rounded-full bg-blue-500\" style={{ width: `${item.rate}%` }} />\n </div>\n <span className=\"w-12 text-right text-sm font-semibold text-text-primary\">{item.rate}%</span>\n {change !== 0 && (\n <span\n className={cn('flex items-center text-xs', change > 0 ? 'text-emerald-600' : 'text-red-600')}\n >\n {change > 0 ? <ArrowUpRight className=\"h-3 w-3\" /> : <ArrowDownRight className=\"h-3 w-3\" />}\n {Math.abs(change)}\n </span>\n )}\n </div>\n </div>\n );\n })}\n </div>\n </ChartCard>\n\n {/* Recent Activity */}\n <ChartCard title=\"Quick Stats\" description=\"Key performance indicators at a glance\">\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between rounded-lg bg-bg-sunken/50 p-3\">\n <div>\n <p className=\"text-sm text-text-secondary\">Total Revenue (YTD)</p>\n <p className=\"text-lg font-bold text-text-primary\">\n {formatCurrency(analyticsData.revenueByMonth.reduce((s, r) => s + r.revenue, 0))}\n </p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm text-text-secondary\">Total Expenses (YTD)</p>\n <p className=\"text-lg font-bold text-text-primary\">\n {formatCurrency(analyticsData.revenueByMonth.reduce((s, r) => s + r.expenses, 0))}\n </p>\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"rounded-lg bg-emerald-50 p-3 text-center dark:bg-emerald-900/20\">\n <p className=\"text-xl font-bold text-emerald-600 dark:text-emerald-400\">\n {formatCurrency(\n analyticsData.revenueByMonth.reduce((s, r) => s + r.revenue, 0) -\n analyticsData.revenueByMonth.reduce((s, r) => s + r.expenses, 0)\n )}\n </p>\n <p className=\"text-xs text-text-secondary\">Net Income</p>\n </div>\n <div className=\"rounded-lg bg-blue-50 p-3 text-center dark:bg-blue-900/20\">\n <p className=\"text-xl font-bold text-blue-600 dark:text-blue-400\">\n {(\n ((analyticsData.revenueByMonth.reduce((s, r) => s + r.revenue, 0) -\n analyticsData.revenueByMonth.reduce((s, r) => s + r.expenses, 0)) /\n analyticsData.revenueByMonth.reduce((s, r) => s + r.revenue, 0)) *\n 100\n ).toFixed(1)}\n %\n </p>\n <p className=\"text-xs text-text-secondary\">Profit Margin</p>\n </div>\n </div>\n </div>\n </ChartCard>\n </div>\n\n {/* Performance Metrics Table */}\n <div id=\"analytics-performance\" className=\"scroll-mt-6\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Monthly Performance</h2>\n <DataTable columns={revenueColumns} data={analyticsData.revenueByMonth} keyField=\"month\" pageSize={12} />\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}