@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.
@@ -1 +1 @@
1
- {"version":3,"file":"HomePage.js","names":[],"sources":["../../../src/housingvista/pages/HomePage.tsx"],"sourcesContent":["/**\n * HousingVista Dashboard / Home Page\n * Migrated from legacy housingvista-frontend dashboard.tsx\n * @module housingvista\n */\n\nimport { useMemo } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n AuroraBackground,\n GradientText,\n Button,\n GlassCard,\n EmphasisPanel,\n IllustratedEmptyState,\n PagePurpose,\n NextSteps,\n} from '@burdenoff/fe-libs/ui';\nimport { Building, TrendingUp, Eye, DollarSign, Plus, BarChart3, FileText, Home, ArrowRight, Clock, Zap } from 'lucide-react';\nimport { StatCard, PropertyCard } from '../components';\nimport { useDashboardStats, useRecentActivities, useProperties } from '../hooks';\nimport { cn, getRelativeTime } from '../utils/helpers';\nimport type { RecentActivity } from '../types';\n\n// ---- Activity icon mapping ----\nconst activityIconMap: Record<RecentActivity['type'], typeof Eye> = {\n property_listed: Home,\n transaction_update: FileText,\n payment_received: DollarSign,\n maintenance_request: Building,\n viewing_scheduled: Eye,\n document_uploaded: FileText,\n offer_received: TrendingUp,\n lease_signed: FileText,\n};\n\nconst activityColorMap: Record<RecentActivity['type'], string> = {\n property_listed: 'text-accent-purple',\n transaction_update: 'text-status-info-text',\n payment_received: 'text-status-success-text',\n maintenance_request: 'text-status-warning-text',\n viewing_scheduled: 'text-status-info-text',\n document_uploaded: 'text-status-info-text',\n offer_received: 'text-status-success-text',\n lease_signed: 'text-accent-teal',\n};\n\nexport const HomePage = () => {\n const navigate = useNavigate();\n const { t } = useI18n();\n const { data: stats } = useDashboardStats();\n const { data: activities } = useRecentActivities();\n const { data: properties } = useProperties();\n\n const tr = (key: string, fallback: string) => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const quickActions = [\n {\n title: tr('housingvista.home.quickActions.addProperty.title', 'Add Property'),\n description: tr('housingvista.home.quickActions.addProperty.description', 'List a new property'),\n icon: Plus,\n path: 'properties/new',\n },\n {\n title: tr('housingvista.home.quickActions.viewAnalytics.title', 'View Analytics'),\n description: tr('housingvista.home.quickActions.viewAnalytics.description', 'Check performance metrics'),\n icon: BarChart3,\n path: 'analytics',\n },\n {\n title: tr('housingvista.home.quickActions.manageTransactions.title', 'Manage Transactions'),\n description: tr('housingvista.home.quickActions.manageTransactions.description', 'Handle ongoing deals'),\n icon: FileText,\n path: 'transactions',\n },\n {\n title: tr('housingvista.home.quickActions.browseProperties.title', 'Browse Properties'),\n description: tr('housingvista.home.quickActions.browseProperties.description', 'Explore listings'),\n icon: Building,\n path: 'properties',\n },\n ];\n\n const recentProperties = useMemo(() => properties.slice(0, 6), [properties]);\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Welcome Header — Boff UI 2.0 aurora hero (one per MFE) */}\n <div className=\"relative overflow-hidden rounded-card border border-border-seam bg-gloss-card p-6 shadow-pop\">\n <AuroraBackground intensity=\"subtle\" />\n <div className=\"relative flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n <div>\n <h1 className=\"text-3xl font-bold text-text-primary\">\n <GradientText>{tr('housingvista.home.header.title', 'Welcome back!')}</GradientText>\n </h1>\n <p className=\"mt-1 text-text-secondary\">\n {tr(\n 'housingvista.home.header.description',\n \"Here's what's happening with your real estate portfolio today.\"\n )}\n </p>\n </div>\n <Button onClick={() => navigate('properties/new')} className=\"gap-2 self-start lg:self-auto\">\n <Plus className=\"h-4 w-4\" />\n {tr('housingvista.home.header.quickAdd', 'Quick Add')}\n </Button>\n </div>\n </div>\n\n <PagePurpose className=\"max-w-3xl\">\n {tr(\n 'housingvista.home.purpose',\n 'Your real-estate command center. See your portfolio at a glance, jump to the things you do most, and pick up where you left off — from listing a property to tracking deals and analytics.'\n )}\n </PagePurpose>\n\n <NextSteps\n storageKey=\"housingvista-home\"\n steps={[\n {\n id: 'add-property',\n label: tr('housingvista.home.nextSteps.addProperty', 'List your first property'),\n description: tr(\n 'housingvista.home.nextSteps.addPropertyDesc',\n 'Create a market-ready listing in a few guided steps.'\n ),\n onClick: () => navigate('properties/new'),\n },\n {\n id: 'browse',\n label: tr('housingvista.home.nextSteps.browse', 'Browse properties'),\n description: tr(\n 'housingvista.home.nextSteps.browseDesc',\n 'Explore listings and open one for full details.'\n ),\n onClick: () => navigate('properties'),\n },\n {\n id: 'analytics',\n label: tr('housingvista.home.nextSteps.analytics', 'Review your analytics'),\n description: tr(\n 'housingvista.home.nextSteps.analyticsDesc',\n 'Check portfolio performance and market insights.'\n ),\n onClick: () => navigate('analytics'),\n },\n ]}\n />\n\n {/* Stats Grid */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n label={tr('housingvista.home.stats.totalProperties', 'Total Properties')}\n value={stats.totalProperties}\n icon={<Building className=\"h-5 w-5\" />}\n featured\n />\n <StatCard\n label={tr('housingvista.home.stats.activeListings', 'Active Listings')}\n value={stats.activeTransactions}\n icon={<TrendingUp className=\"h-5 w-5\" />}\n />\n <StatCard\n label={tr('housingvista.home.stats.upcomingViewings', 'Upcoming Viewings')}\n value={stats.upcomingViewings}\n icon={<Eye className=\"h-5 w-5\" />}\n />\n <StatCard\n label={tr('housingvista.home.stats.monthlyRevenue', 'Monthly Revenue')}\n value={stats.monthlyRevenue}\n prefix=\"$\"\n icon={<DollarSign className=\"h-5 w-5\" />}\n />\n </div>\n\n {/* Recent Activity + Quick Actions */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Recent Activity — primary insight zone (one EmphasisPanel per page) */}\n <div className=\"lg:col-span-2\">\n <EmphasisPanel className=\"p-0\">\n <div className=\"flex items-center gap-2 border-b border-border-seam px-5 py-4\">\n <Clock className=\"h-4 w-4 text-text-secondary\" />\n <h2 className=\"text-base font-semibold text-text-primary\">\n {tr('housingvista.home.recentActivity.title', 'Recent Activity')}\n </h2>\n </div>\n <div className=\"p-5\">\n {activities.length === 0 ? (\n <div className=\"flex flex-col items-center gap-2 py-8 text-center\">\n <div className=\"rounded-full bg-bg-sunken p-3\">\n <Clock className=\"h-6 w-6 text-text-secondary\" />\n </div>\n <p className=\"text-sm font-medium text-text-primary\">No activity yet</p>\n <p className=\"max-w-xs text-xs text-text-secondary\">\n As you list properties, book viewings, and close deals, your latest updates appear here.\n </p>\n </div>\n ) : (\n <ol className=\"relative space-y-5\">\n {/* Connecting rail */}\n <span\n aria-hidden\n className=\"absolute bottom-2 left-[18px] top-2 w-px bg-border-seam\"\n />\n {activities.slice(0, 6).map((activity) => {\n const Icon = activityIconMap[activity.type] ?? Eye;\n const colorClass = activityColorMap[activity.type] ?? 'text-text-secondary';\n return (\n <li key={activity.id} className=\"relative flex items-start gap-4\">\n <span\n className={cn(\n 'relative z-10 flex h-9 w-9 shrink-0 items-center justify-center rounded-full border border-border-seam bg-bg-surface shadow-elevation-1',\n colorClass\n )}\n >\n <Icon className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0 flex-1 pt-0.5\">\n <div className=\"flex flex-wrap items-baseline justify-between gap-x-3 gap-y-0.5\">\n <p className=\"text-sm font-medium text-text-primary\">{activity.title}</p>\n <p className=\"shrink-0 text-xs text-text-secondary\">\n {getRelativeTime(activity.timestamp)}\n </p>\n </div>\n <p className=\"mt-0.5 text-xs text-text-secondary\">{activity.description}</p>\n {activity.actorName && (\n <p className=\"mt-0.5 text-xs text-text-secondary\">{activity.actorName}</p>\n )}\n </div>\n </li>\n );\n })}\n </ol>\n )}\n </div>\n </EmphasisPanel>\n </div>\n\n {/* Quick Actions */}\n <div className=\"lg:col-span-1\">\n <GlassCard treatment=\"plain\" className=\"h-full border-border-seam\">\n <div className=\"flex items-center gap-2 border-b border-border-seam px-5 py-4\">\n <Zap className=\"h-4 w-4 text-text-secondary\" />\n <h2 className=\"text-base font-semibold text-text-primary\">\n {tr('housingvista.home.quickActions.title', 'Quick Actions')}\n </h2>\n </div>\n <div className=\"space-y-2 p-4\">\n {quickActions.map((action, index) => {\n const Icon = action.icon;\n const accent = index % 2 === 0 ? 'text-accent-teal' : 'text-accent-blue';\n return (\n <button\n key={action.title}\n onClick={() => navigate(action.path)}\n className=\"group/action flex min-h-[44px] w-full items-center gap-3 rounded-lg border border-border-seam p-3 text-left transition-colors hover:bg-bg-sunken/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <div className={cn('rounded-lg bg-bg-sunken p-2', accent)}>\n <Icon className=\"h-5 w-5\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"text-sm font-medium text-text-primary\">{action.title}</div>\n <div className=\"text-xs text-text-secondary\">{action.description}</div>\n </div>\n <ArrowRight className=\"h-4 w-4 shrink-0 text-text-secondary opacity-0 transition-opacity group-hover/action:opacity-100\" />\n </button>\n );\n })}\n </div>\n </GlassCard>\n </div>\n </div>\n\n {/* Recent Properties */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <div className=\"flex items-center justify-between border-b border-border-seam px-5 py-4\">\n <h2 className=\"text-base font-semibold text-text-primary\">\n {tr('housingvista.home.recentProperties.title', 'Recent Properties')}\n </h2>\n <button\n onClick={() => navigate('properties')}\n className=\"inline-flex min-h-[44px] items-center gap-1 text-sm font-medium text-text-secondary transition-colors hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] rounded-md px-1\"\n >\n {tr('housingvista.home.recentProperties.viewAll', 'View All')}\n <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n {recentProperties.length === 0 ? (\n <div className=\"p-4\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr('housingvista.home.recentProperties.empty.title', 'No properties yet')}\n description={tr(\n 'housingvista.home.recentProperties.empty.description',\n 'List your first property to see it appear here.'\n )}\n action={\n <Button onClick={() => navigate('properties/new')} className=\"gap-2\">\n <Plus className=\"h-4 w-4\" />\n {tr('housingvista.home.header.quickAdd', 'Quick Add')}\n </Button>\n }\n />\n </div>\n ) : (\n <div className=\"grid grid-cols-1 gap-4 p-4 sm:grid-cols-2 lg:grid-cols-3\">\n {recentProperties.map((property) => (\n <PropertyCard\n key={property.id}\n id={property.id}\n title={property.title}\n price={property.price.amount}\n priceType={property.price.type === 'rent' ? 'rent' : 'sale'}\n pricePeriod={property.price.period}\n currency={property.price.currency}\n location={`${property.location.neighborhood}, ${property.location.city}`}\n bedrooms={property.specifications.bedrooms}\n bathrooms={property.specifications.bathrooms}\n area={property.specifications.totalArea}\n imageUrl={property.images[0]?.url}\n status={property.status.replace('_', ' ')}\n type={property.category}\n onClick={() => navigate(`properties/${property.id}`)}\n />\n ))}\n </div>\n )}\n </GlassCard>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"HomePage.js","names":[],"sources":["../../../src/housingvista/pages/HomePage.tsx"],"sourcesContent":["/**\n * HousingVista Dashboard / Home Page\n * Migrated from legacy housingvista-frontend dashboard.tsx\n * @module housingvista\n */\n\nimport { useMemo } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n AuroraBackground,\n GradientText,\n Button,\n GlassCard,\n EmphasisPanel,\n IllustratedEmptyState,\n PagePurpose,\n NextSteps,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Building,\n TrendingUp,\n Eye,\n DollarSign,\n Plus,\n BarChart3,\n FileText,\n Home,\n ArrowRight,\n Clock,\n Zap,\n} from 'lucide-react';\nimport { StatCard, PropertyCard } from '../components';\nimport { useDashboardStats, useRecentActivities, useProperties } from '../hooks';\nimport { cn, getRelativeTime } from '../utils/helpers';\nimport type { RecentActivity } from '../types';\n\n// ---- Activity icon mapping ----\nconst activityIconMap: Record<RecentActivity['type'], typeof Eye> = {\n property_listed: Home,\n transaction_update: FileText,\n payment_received: DollarSign,\n maintenance_request: Building,\n viewing_scheduled: Eye,\n document_uploaded: FileText,\n offer_received: TrendingUp,\n lease_signed: FileText,\n};\n\nconst activityColorMap: Record<RecentActivity['type'], string> = {\n property_listed: 'text-accent-purple',\n transaction_update: 'text-status-info-text',\n payment_received: 'text-status-success-text',\n maintenance_request: 'text-status-warning-text',\n viewing_scheduled: 'text-status-info-text',\n document_uploaded: 'text-status-info-text',\n offer_received: 'text-status-success-text',\n lease_signed: 'text-accent-teal',\n};\n\nexport const HomePage = () => {\n const navigate = useNavigate();\n const { t } = useI18n();\n const { data: stats } = useDashboardStats();\n const { data: activities } = useRecentActivities();\n const { data: properties } = useProperties();\n\n const tr = (key: string, fallback: string) => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const quickActions = [\n {\n title: tr('housingvista.home.quickActions.addProperty.title', 'Add Property'),\n description: tr('housingvista.home.quickActions.addProperty.description', 'List a new property'),\n icon: Plus,\n path: 'properties/new',\n },\n {\n title: tr('housingvista.home.quickActions.viewAnalytics.title', 'View Analytics'),\n description: tr('housingvista.home.quickActions.viewAnalytics.description', 'Check performance metrics'),\n icon: BarChart3,\n path: 'analytics',\n },\n {\n title: tr('housingvista.home.quickActions.manageTransactions.title', 'Manage Transactions'),\n description: tr('housingvista.home.quickActions.manageTransactions.description', 'Handle ongoing deals'),\n icon: FileText,\n path: 'transactions',\n },\n {\n title: tr('housingvista.home.quickActions.browseProperties.title', 'Browse Properties'),\n description: tr('housingvista.home.quickActions.browseProperties.description', 'Explore listings'),\n icon: Building,\n path: 'properties',\n },\n ];\n\n const recentProperties = useMemo(() => properties.slice(0, 6), [properties]);\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Welcome Header — Boff UI 2.0 aurora hero (one per MFE) */}\n <div className=\"relative overflow-hidden rounded-card border border-border-seam bg-gloss-card p-6 shadow-pop\">\n <AuroraBackground intensity=\"subtle\" />\n <div className=\"relative flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n <div>\n <h1 className=\"text-3xl font-bold text-text-primary\">\n <GradientText>{tr('housingvista.home.header.title', 'Welcome back!')}</GradientText>\n </h1>\n <p className=\"mt-1 text-text-secondary\">\n {tr(\n 'housingvista.home.header.description',\n \"Here's what's happening with your real estate portfolio today.\"\n )}\n </p>\n </div>\n <Button onClick={() => navigate('properties/new')} className=\"gap-2 self-start lg:self-auto\">\n <Plus className=\"h-4 w-4\" />\n {tr('housingvista.home.header.quickAdd', 'Quick Add')}\n </Button>\n </div>\n </div>\n\n <PagePurpose className=\"max-w-3xl\">\n {tr(\n 'housingvista.home.purpose',\n 'Your real-estate command center. See your portfolio at a glance, jump to the things you do most, and pick up where you left off — from listing a property to tracking deals and analytics.'\n )}\n </PagePurpose>\n\n <NextSteps\n storageKey=\"housingvista-home\"\n steps={[\n {\n id: 'add-property',\n label: tr('housingvista.home.nextSteps.addProperty', 'List your first property'),\n description: tr(\n 'housingvista.home.nextSteps.addPropertyDesc',\n 'Create a market-ready listing in a few guided steps.'\n ),\n onClick: () => navigate('properties/new'),\n },\n {\n id: 'browse',\n label: tr('housingvista.home.nextSteps.browse', 'Browse properties'),\n description: tr(\n 'housingvista.home.nextSteps.browseDesc',\n 'Explore listings and open one for full details.'\n ),\n onClick: () => navigate('properties'),\n },\n {\n id: 'analytics',\n label: tr('housingvista.home.nextSteps.analytics', 'Review your analytics'),\n description: tr(\n 'housingvista.home.nextSteps.analyticsDesc',\n 'Check portfolio performance and market insights.'\n ),\n onClick: () => navigate('analytics'),\n },\n ]}\n />\n\n {/* Stats Grid */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n label={tr('housingvista.home.stats.totalProperties', 'Total Properties')}\n value={stats.totalProperties}\n icon={<Building className=\"h-5 w-5\" />}\n featured\n />\n <StatCard\n label={tr('housingvista.home.stats.activeListings', 'Active Listings')}\n value={stats.activeTransactions}\n icon={<TrendingUp className=\"h-5 w-5\" />}\n />\n <StatCard\n label={tr('housingvista.home.stats.upcomingViewings', 'Upcoming Viewings')}\n value={stats.upcomingViewings}\n icon={<Eye className=\"h-5 w-5\" />}\n />\n <StatCard\n label={tr('housingvista.home.stats.monthlyRevenue', 'Monthly Revenue')}\n value={stats.monthlyRevenue}\n prefix=\"$\"\n icon={<DollarSign className=\"h-5 w-5\" />}\n />\n </div>\n\n {/* Recent Activity + Quick Actions */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Recent Activity — primary insight zone (one EmphasisPanel per page) */}\n <div className=\"lg:col-span-2\">\n <EmphasisPanel className=\"p-0\">\n <div className=\"flex items-center gap-2 border-b border-border-seam px-5 py-4\">\n <Clock className=\"h-4 w-4 text-text-secondary\" />\n <h2 className=\"text-base font-semibold text-text-primary\">\n {tr('housingvista.home.recentActivity.title', 'Recent Activity')}\n </h2>\n </div>\n <div className=\"p-5\">\n {activities.length === 0 ? (\n <div className=\"flex flex-col items-center gap-2 py-8 text-center\">\n <div className=\"rounded-full bg-bg-sunken p-3\">\n <Clock className=\"h-6 w-6 text-text-secondary\" />\n </div>\n <p className=\"text-sm font-medium text-text-primary\">No activity yet</p>\n <p className=\"max-w-xs text-xs text-text-secondary\">\n As you list properties, book viewings, and close deals, your latest updates appear here.\n </p>\n </div>\n ) : (\n <ol className=\"relative space-y-5\">\n {/* Connecting rail */}\n <span aria-hidden className=\"absolute bottom-2 left-[18px] top-2 w-px bg-border-seam\" />\n {activities.slice(0, 6).map((activity) => {\n const Icon = activityIconMap[activity.type] ?? Eye;\n const colorClass = activityColorMap[activity.type] ?? 'text-text-secondary';\n return (\n <li key={activity.id} className=\"relative flex items-start gap-4\">\n <span\n className={cn(\n 'relative z-10 flex h-9 w-9 shrink-0 items-center justify-center rounded-full border border-border-seam bg-bg-surface shadow-elevation-1',\n colorClass\n )}\n >\n <Icon className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0 flex-1 pt-0.5\">\n <div className=\"flex flex-wrap items-baseline justify-between gap-x-3 gap-y-0.5\">\n <p className=\"text-sm font-medium text-text-primary\">{activity.title}</p>\n <p className=\"shrink-0 text-xs text-text-secondary\">\n {getRelativeTime(activity.timestamp)}\n </p>\n </div>\n <p className=\"mt-0.5 text-xs text-text-secondary\">{activity.description}</p>\n {activity.actorName && (\n <p className=\"mt-0.5 text-xs text-text-secondary\">{activity.actorName}</p>\n )}\n </div>\n </li>\n );\n })}\n </ol>\n )}\n </div>\n </EmphasisPanel>\n </div>\n\n {/* Quick Actions */}\n <div className=\"lg:col-span-1\">\n <GlassCard treatment=\"plain\" className=\"h-full border-border-seam\">\n <div className=\"flex items-center gap-2 border-b border-border-seam px-5 py-4\">\n <Zap className=\"h-4 w-4 text-text-secondary\" />\n <h2 className=\"text-base font-semibold text-text-primary\">\n {tr('housingvista.home.quickActions.title', 'Quick Actions')}\n </h2>\n </div>\n <div className=\"space-y-2 p-4\">\n {quickActions.map((action, index) => {\n const Icon = action.icon;\n const accent = index % 2 === 0 ? 'text-accent-teal' : 'text-accent-blue';\n return (\n <button\n key={action.title}\n onClick={() => navigate(action.path)}\n className=\"group/action flex min-h-[44px] w-full items-center gap-3 rounded-lg border border-border-seam p-3 text-left transition-colors hover:bg-bg-sunken/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <div className={cn('rounded-lg bg-bg-sunken p-2', accent)}>\n <Icon className=\"h-5 w-5\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"text-sm font-medium text-text-primary\">{action.title}</div>\n <div className=\"text-xs text-text-secondary\">{action.description}</div>\n </div>\n <ArrowRight className=\"h-4 w-4 shrink-0 text-text-secondary opacity-0 transition-opacity group-hover/action:opacity-100\" />\n </button>\n );\n })}\n </div>\n </GlassCard>\n </div>\n </div>\n\n {/* Recent Properties */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <div className=\"flex items-center justify-between border-b border-border-seam px-5 py-4\">\n <h2 className=\"text-base font-semibold text-text-primary\">\n {tr('housingvista.home.recentProperties.title', 'Recent Properties')}\n </h2>\n <button\n onClick={() => navigate('properties')}\n className=\"inline-flex min-h-[44px] items-center gap-1 text-sm font-medium text-text-secondary transition-colors hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] rounded-md px-1\"\n >\n {tr('housingvista.home.recentProperties.viewAll', 'View All')}\n <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n {recentProperties.length === 0 ? (\n <div className=\"p-4\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr('housingvista.home.recentProperties.empty.title', 'No properties yet')}\n description={tr(\n 'housingvista.home.recentProperties.empty.description',\n 'List your first property to see it appear here.'\n )}\n action={\n <Button onClick={() => navigate('properties/new')} className=\"gap-2\">\n <Plus className=\"h-4 w-4\" />\n {tr('housingvista.home.header.quickAdd', 'Quick Add')}\n </Button>\n }\n />\n </div>\n ) : (\n <div className=\"grid grid-cols-1 gap-4 p-4 sm:grid-cols-2 lg:grid-cols-3\">\n {recentProperties.map((property) => (\n <PropertyCard\n key={property.id}\n id={property.id}\n title={property.title}\n price={property.price.amount}\n priceType={property.price.type === 'rent' ? 'rent' : 'sale'}\n pricePeriod={property.price.period}\n currency={property.price.currency}\n location={`${property.location.neighborhood}, ${property.location.city}`}\n bedrooms={property.specifications.bedrooms}\n bathrooms={property.specifications.bathrooms}\n area={property.specifications.totalArea}\n imageUrl={property.images[0]?.url}\n status={property.status.replace('_', ' ')}\n type={property.category}\n onClick={() => navigate(`properties/${property.id}`)}\n />\n ))}\n </div>\n )}\n </GlassCard>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"AMCManagementPage.js","names":[],"sources":["../../../../src/housingvista/pages/amc/AMCManagementPage.tsx"],"sourcesContent":["import { useState, useMemo, useEffect, type FormEvent } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n Shield,\n DollarSign,\n Calendar,\n Plus,\n Download,\n Clock,\n AlertTriangle,\n Wrench,\n RotateCcw,\n MapPin,\n} from 'lucide-react';\nimport {\n Button,\n EmphasisPanel,\n NextSteps,\n Pagination,\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 { StatusBadge } from '../../components';\nimport { FilterBar } from '../../components';\nimport { EmptyState } from '../../components';\nimport { useAMCContracts, useCreateAmcContract, useMaintenanceRequests } from '../../hooks';\nimport { useServerPagination } from '../../hooks/useServerPagination';\nimport { cn, formatCurrency, formatDate } from '../../utils/helpers';\nimport { notify } from '../../lib/toast';\nimport type { AMCContract } 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\ntype AmcStatus = 'active' | 'expiring_soon' | 'expired' | 'cancelled';\n\nfunction toIso(value: string): string {\n return value ? new Date(value).toISOString() : '';\n}\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 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: amcContracts } = useAMCContracts({\n filter: { search: debouncedSearch.trim() || undefined },\n pagination: pagination.pagination,\n });\n\n const { createAmcContract, loading: creating } = useCreateAmcContract();\n\n // Open maintenance requests across the portfolio — real count for the sidebar stat.\n const { data: maintenanceRequests } = useMaintenanceRequests({\n filter: { status: 'open' },\n pagination: { limit: 100, offset: 0 },\n });\n const openMaintenanceCount = maintenanceRequests.length;\n\n const handleSearchChange = (value: string) => {\n setSearch(value);\n pagination.setPage(1);\n };\n\n const handleExport = () => {\n if (amcContracts.length === 0) {\n notify.warning('Nothing to export', 'There are no contracts to export yet.');\n return;\n }\n const escape = (value: string) => `\"${value.replace(/\"/g, '\"\"')}\"`;\n const headers = ['Package', 'Property', 'Provider', 'Monthly Cost', 'Start', 'End', 'Status', 'Services'];\n const rows = amcContracts.map((c) =>\n [\n c.packageName,\n c.propertyTitle,\n c.providerName,\n String(c.monthlyCost),\n c.startDate,\n c.endDate,\n c.status,\n c.servicesIncluded.join('; '),\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-amc-contracts-${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', `${amcContracts.length} contracts exported to CSV.`);\n };\n\n const [createOpen, setCreateOpen] = useState(false);\n const [propertyTitle, setPropertyTitle] = useState('');\n const [packageName, setPackageName] = useState('');\n const [providerName, setProviderName] = useState('');\n const [monthlyCost, setMonthlyCost] = useState('');\n const [startDate, setStartDate] = useState('');\n const [endDate, setEndDate] = useState('');\n const [status, setStatus] = useState<AmcStatus>('active');\n const [servicesIncluded, setServicesIncluded] = useState('');\n\n const resetForm = () => {\n setPropertyTitle('');\n setPackageName('');\n setProviderName('');\n setMonthlyCost('');\n setStartDate('');\n setEndDate('');\n setStatus('active');\n setServicesIncluded('');\n };\n\n const openCreate = () => {\n resetForm();\n setCreateOpen(true);\n };\n\n const handleCreate = async (e: FormEvent) => {\n e.preventDefault();\n const cost = Number(monthlyCost);\n if (!propertyTitle.trim() || !packageName.trim() || !providerName.trim()) {\n notify.warning('Missing details', 'Property, package and provider are required.');\n return;\n }\n if (!Number.isFinite(cost) || cost <= 0) {\n notify.warning('Enter a valid monthly cost');\n return;\n }\n const services = servicesIncluded\n .split(',')\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n try {\n await createAmcContract({\n propertyTitle: propertyTitle.trim(),\n packageName: packageName.trim(),\n providerName: providerName.trim(),\n monthlyCost: cost,\n startDate: toIso(startDate),\n endDate: toIso(endDate),\n status,\n servicesIncluded: services.length > 0 ? services : undefined,\n });\n notify.success('Contract created', `${packageName.trim()} added for ${propertyTitle.trim()}.`);\n setCreateOpen(false);\n resetForm();\n } catch (err) {\n notify.error('Could not create contract', (err as Error).message);\n }\n };\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 // Accent styling per contract status for the card treatment.\n const statusAccent: Record<AMCContract['status'], string> = {\n active: 'border-l-4 border-l-status-success-border',\n expiring_soon: 'border-l-4 border-l-status-warning-border bg-status-warning-bg-subtle/30',\n expired: 'border-l-4 border-l-status-error-border',\n cancelled: 'border-l-4 border-l-border-strong',\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\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 type=\"button\" onClick={openCreate} className=\"gap-2\">\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 label=\"Pending Renewals\" value={stats.pendingRenewals} icon={<RotateCcw className=\"h-4 w-4\" />} />\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 <NextSteps\n storageKey=\"housingvista-amc\"\n steps={[\n {\n id: 'pick-property',\n label: 'Add coverage for a property',\n description: 'Choose a property to set up a new maintenance contract.',\n onClick: () => navigate('../properties'),\n },\n {\n id: 'compare-providers',\n label: 'Compare your providers',\n description: 'Search by provider name to weigh coverage and monthly cost.',\n onClick: () => {\n const firstProvider = amcContracts[0]?.providerName;\n if (firstProvider) {\n setSearch(firstProvider);\n pagination.setPage(1);\n }\n },\n },\n {\n id: 'check-legal',\n label: 'Review contract paperwork',\n description: 'Open the legal hub to manage the underlying documents.',\n onClick: () => navigate('../legal'),\n },\n ]}\n />\n\n {/* Contract cards (left) + service schedule & open-maintenance sidebar (right) */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"space-y-4 lg:col-span-2\">\n <div className=\"flex flex-col gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">All Contracts</h2>\n <FilterBar\n searchValue={search}\n onSearchChange={handleSearchChange}\n searchPlaceholder=\"Search contracts, providers, properties...\"\n />\n </div>\n {amcContracts.length > 0 ? (\n <>\n <div className=\"space-y-3\">\n {amcContracts.map((contract) => (\n <div\n key={contract.id}\n className={cn(\n 'rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm',\n statusAccent[contract.status]\n )}\n >\n <div className=\"flex flex-wrap items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <p className=\"flex items-center gap-1 text-xs text-text-secondary\">\n <MapPin className=\"h-3 w-3\" />\n Property\n </p>\n <h3 className=\"truncate text-base font-semibold text-text-primary\">\n {contract.propertyTitle}\n </h3>\n <p className=\"text-xs text-text-secondary\">{contract.packageName}</p>\n </div>\n <StatusBadge\n label={contract.status.replace('_', ' ')}\n variant={getStatusVariant(contract.status)}\n dot\n />\n </div>\n <div className=\"mt-4 grid grid-cols-2 gap-4 border-t border-border-subtle pt-4 sm:grid-cols-4\">\n <div>\n <p className=\"text-xs text-text-secondary\">Provider</p>\n <p className=\"mt-0.5 text-sm font-medium text-text-primary\">{contract.providerName}</p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Coverage</p>\n <p className=\"mt-0.5 truncate text-sm font-medium text-text-primary\">\n {contract.servicesIncluded.length > 0\n ? contract.servicesIncluded.slice(0, 2).join(', ')\n : '—'}\n {contract.servicesIncluded.length > 2 ? ` +${contract.servicesIncluded.length - 2}` : ''}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Monthly Cost</p>\n <p className=\"mt-0.5 text-sm font-semibold text-text-primary\">\n {formatCurrency(contract.monthlyCost)}\n <span className=\"text-xs font-normal text-text-secondary\">/mo</span>\n </p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Renewal Date</p>\n <p className=\"mt-0.5 text-sm font-medium text-text-primary\">{formatDate(contract.endDate)}</p>\n </div>\n </div>\n </div>\n ))}\n </div>\n <Pagination\n page={pagination.page}\n pageSize={pagination.pageSize}\n hasNextPage={amcContracts.length >= pagination.pageSize}\n hasPreviousPage={pagination.page > 1}\n onPageChange={pagination.setPage}\n onPageSizeChange={pagination.setPageSize}\n />\n </>\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 type=\"button\" onClick={openCreate} className=\"gap-2\">\n <Plus className=\"h-4 w-4\" />\n New Contract\n </Button>\n }\n />\n )}\n </div>\n\n {/* Sidebar */}\n <aside className=\"space-y-6 lg:col-span-1\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm\">\n <h3 className=\"mb-4 text-base 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=\"Upcoming service dates from your active contracts will appear here.\"\n className=\"border-0 bg-transparent p-4\"\n />\n ) : (\n <ol className=\"space-y-4\">\n {scheduledServices.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 return (\n <li key={contract.id} className=\"flex gap-3\">\n <div\n className={cn(\n 'flex h-9 w-9 shrink-0 items-center justify-center rounded-full',\n isPast\n ? 'bg-status-error-bg-subtle'\n : isUrgent\n ? 'bg-status-warning-bg-subtle'\n : 'bg-status-info-bg-subtle'\n )}\n >\n {isPast ? (\n <AlertTriangle className=\"h-4 w-4 text-status-error-text\" />\n ) : isUrgent ? (\n <Clock className=\"h-4 w-4 text-status-warning-text\" />\n ) : (\n <Wrench className=\"h-4 w-4 text-status-info-text\" />\n )}\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary\">\n {formatDate(contract.nextServiceDate)}\n </p>\n <p className=\"truncate text-sm text-text-primary\">\n {contract.packageName} — {contract.propertyTitle}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">{contract.providerName}</p>\n <p\n className={cn(\n 'text-xs font-medium',\n isPast\n ? 'text-status-error-text'\n : isUrgent\n ? 'text-status-warning-text'\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 </li>\n );\n })}\n </ol>\n )}\n </div>\n\n <button\n type=\"button\"\n onClick={() => navigate('../rental')}\n className=\"flex w-full items-center gap-3 rounded-lg border border-border-subtle bg-bg-surface p-4 text-left shadow-sm transition-colors hover:bg-bg-sunken focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <div className=\"rounded-lg bg-status-info-bg-subtle p-2.5\">\n <Wrench className=\"h-5 w-5 text-status-info-text\" />\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">Open maintenance requests</p>\n <p className=\"text-xl font-bold text-text-primary\">{openMaintenanceCount}</p>\n </div>\n </button>\n </aside>\n </div>\n\n {/* New Contract dialog */}\n <ResponsiveDialog open={createOpen} onOpenChange={setCreateOpen}>\n <ResponsiveDialogContent className=\"sm:max-w-lg\">\n <form onSubmit={handleCreate}>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New maintenance contract</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Set up an Annual Maintenance Contract to track coverage, cost and renewal dates.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3 py-2\">\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\">Property *</label>\n <input\n type=\"text\"\n required\n placeholder=\"e.g. Maple Court\"\n value={propertyTitle}\n onChange={(e) => setPropertyTitle(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\">Package *</label>\n <input\n type=\"text\"\n required\n placeholder=\"e.g. Premium Care\"\n value={packageName}\n onChange={(e) => setPackageName(e.target.value)}\n className={inputClass}\n />\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\">Provider *</label>\n <input\n type=\"text\"\n required\n placeholder=\"Service company\"\n value={providerName}\n onChange={(e) => setProviderName(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\">Monthly cost *</label>\n <input\n type=\"number\"\n required\n min={0}\n step=\"0.01\"\n placeholder=\"0.00\"\n value={monthlyCost}\n onChange={(e) => setMonthlyCost(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\">Start date</label>\n <input\n type=\"date\"\n value={startDate}\n onChange={(e) => setStartDate(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\">End date</label>\n <input\n type=\"date\"\n value={endDate}\n onChange={(e) => setEndDate(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 AmcStatus)}\n className={inputClass}\n >\n <option value=\"active\">Active</option>\n <option value=\"expiring_soon\">Expiring soon</option>\n <option value=\"expired\">Expired</option>\n <option value=\"cancelled\">Cancelled</option>\n </select>\n </div>\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Services included</label>\n <input\n type=\"text\"\n placeholder=\"Comma-separated, e.g. Plumbing, Electrical, HVAC\"\n value={servicesIncluded}\n onChange={(e) => setServicesIncluded(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}>\n {creating ? 'Saving…' : 'Create contract'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\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, useEffect, type FormEvent } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n Shield,\n DollarSign,\n Calendar,\n Plus,\n Download,\n Clock,\n AlertTriangle,\n Wrench,\n RotateCcw,\n MapPin,\n} from 'lucide-react';\nimport {\n Button,\n EmphasisPanel,\n NextSteps,\n Pagination,\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 { StatusBadge } from '../../components';\nimport { FilterBar } from '../../components';\nimport { EmptyState } from '../../components';\nimport { useAMCContracts, useCreateAmcContract, useMaintenanceRequests } from '../../hooks';\nimport { useServerPagination } from '../../hooks/useServerPagination';\nimport { cn, formatCurrency, formatDate } from '../../utils/helpers';\nimport { notify } from '../../lib/toast';\nimport type { AMCContract } 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\ntype AmcStatus = 'active' | 'expiring_soon' | 'expired' | 'cancelled';\n\nfunction toIso(value: string): string {\n return value ? new Date(value).toISOString() : '';\n}\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 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: amcContracts } = useAMCContracts({\n filter: { search: debouncedSearch.trim() || undefined },\n pagination: pagination.pagination,\n });\n\n const { createAmcContract, loading: creating } = useCreateAmcContract();\n\n // Open maintenance requests across the portfolio — real count for the sidebar stat.\n const { data: maintenanceRequests } = useMaintenanceRequests({\n filter: { status: 'open' },\n pagination: { limit: 100, offset: 0 },\n });\n const openMaintenanceCount = maintenanceRequests.length;\n\n const handleSearchChange = (value: string) => {\n setSearch(value);\n pagination.setPage(1);\n };\n\n const handleExport = () => {\n if (amcContracts.length === 0) {\n notify.warning('Nothing to export', 'There are no contracts to export yet.');\n return;\n }\n const escape = (value: string) => `\"${value.replace(/\"/g, '\"\"')}\"`;\n const headers = ['Package', 'Property', 'Provider', 'Monthly Cost', 'Start', 'End', 'Status', 'Services'];\n const rows = amcContracts.map((c) =>\n [\n c.packageName,\n c.propertyTitle,\n c.providerName,\n String(c.monthlyCost),\n c.startDate,\n c.endDate,\n c.status,\n c.servicesIncluded.join('; '),\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-amc-contracts-${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', `${amcContracts.length} contracts exported to CSV.`);\n };\n\n const [createOpen, setCreateOpen] = useState(false);\n const [propertyTitle, setPropertyTitle] = useState('');\n const [packageName, setPackageName] = useState('');\n const [providerName, setProviderName] = useState('');\n const [monthlyCost, setMonthlyCost] = useState('');\n const [startDate, setStartDate] = useState('');\n const [endDate, setEndDate] = useState('');\n const [status, setStatus] = useState<AmcStatus>('active');\n const [servicesIncluded, setServicesIncluded] = useState('');\n\n const resetForm = () => {\n setPropertyTitle('');\n setPackageName('');\n setProviderName('');\n setMonthlyCost('');\n setStartDate('');\n setEndDate('');\n setStatus('active');\n setServicesIncluded('');\n };\n\n const openCreate = () => {\n resetForm();\n setCreateOpen(true);\n };\n\n const handleCreate = async (e: FormEvent) => {\n e.preventDefault();\n const cost = Number(monthlyCost);\n if (!propertyTitle.trim() || !packageName.trim() || !providerName.trim()) {\n notify.warning('Missing details', 'Property, package and provider are required.');\n return;\n }\n if (!Number.isFinite(cost) || cost <= 0) {\n notify.warning('Enter a valid monthly cost');\n return;\n }\n const services = servicesIncluded\n .split(',')\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n try {\n await createAmcContract({\n propertyTitle: propertyTitle.trim(),\n packageName: packageName.trim(),\n providerName: providerName.trim(),\n monthlyCost: cost,\n startDate: toIso(startDate),\n endDate: toIso(endDate),\n status,\n servicesIncluded: services.length > 0 ? services : undefined,\n });\n notify.success('Contract created', `${packageName.trim()} added for ${propertyTitle.trim()}.`);\n setCreateOpen(false);\n resetForm();\n } catch (err) {\n notify.error('Could not create contract', (err as Error).message);\n }\n };\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 // Accent styling per contract status for the card treatment.\n const statusAccent: Record<AMCContract['status'], string> = {\n active: 'border-l-4 border-l-status-success-border',\n expiring_soon: 'border-l-4 border-l-status-warning-border bg-status-warning-bg-subtle/30',\n expired: 'border-l-4 border-l-status-error-border',\n cancelled: 'border-l-4 border-l-border-strong',\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\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 type=\"button\" onClick={openCreate} className=\"gap-2\">\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 label=\"Pending Renewals\" value={stats.pendingRenewals} icon={<RotateCcw className=\"h-4 w-4\" />} />\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 <NextSteps\n storageKey=\"housingvista-amc\"\n steps={[\n {\n id: 'pick-property',\n label: 'Add coverage for a property',\n description: 'Choose a property to set up a new maintenance contract.',\n onClick: () => navigate('../properties'),\n },\n {\n id: 'compare-providers',\n label: 'Compare your providers',\n description: 'Search by provider name to weigh coverage and monthly cost.',\n onClick: () => {\n const firstProvider = amcContracts[0]?.providerName;\n if (firstProvider) {\n setSearch(firstProvider);\n pagination.setPage(1);\n }\n },\n },\n {\n id: 'check-legal',\n label: 'Review contract paperwork',\n description: 'Open the legal hub to manage the underlying documents.',\n onClick: () => navigate('../legal'),\n },\n ]}\n />\n\n {/* Contract cards (left) + service schedule & open-maintenance sidebar (right) */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"space-y-4 lg:col-span-2\">\n <div className=\"flex flex-col gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">All Contracts</h2>\n <FilterBar\n searchValue={search}\n onSearchChange={handleSearchChange}\n searchPlaceholder=\"Search contracts, providers, properties...\"\n />\n </div>\n {amcContracts.length > 0 ? (\n <>\n <div className=\"space-y-3\">\n {amcContracts.map((contract) => (\n <div\n key={contract.id}\n className={cn(\n 'rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm',\n statusAccent[contract.status]\n )}\n >\n <div className=\"flex flex-wrap items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <p className=\"flex items-center gap-1 text-xs text-text-secondary\">\n <MapPin className=\"h-3 w-3\" />\n Property\n </p>\n <h3 className=\"truncate text-base font-semibold text-text-primary\">{contract.propertyTitle}</h3>\n <p className=\"text-xs text-text-secondary\">{contract.packageName}</p>\n </div>\n <StatusBadge\n label={contract.status.replace('_', ' ')}\n variant={getStatusVariant(contract.status)}\n dot\n />\n </div>\n <div className=\"mt-4 grid grid-cols-2 gap-4 border-t border-border-subtle pt-4 sm:grid-cols-4\">\n <div>\n <p className=\"text-xs text-text-secondary\">Provider</p>\n <p className=\"mt-0.5 text-sm font-medium text-text-primary\">{contract.providerName}</p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Coverage</p>\n <p className=\"mt-0.5 truncate text-sm font-medium text-text-primary\">\n {contract.servicesIncluded.length > 0\n ? contract.servicesIncluded.slice(0, 2).join(', ')\n : '—'}\n {contract.servicesIncluded.length > 2 ? ` +${contract.servicesIncluded.length - 2}` : ''}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Monthly Cost</p>\n <p className=\"mt-0.5 text-sm font-semibold text-text-primary\">\n {formatCurrency(contract.monthlyCost)}\n <span className=\"text-xs font-normal text-text-secondary\">/mo</span>\n </p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Renewal Date</p>\n <p className=\"mt-0.5 text-sm font-medium text-text-primary\">{formatDate(contract.endDate)}</p>\n </div>\n </div>\n </div>\n ))}\n </div>\n <Pagination\n page={pagination.page}\n pageSize={pagination.pageSize}\n hasNextPage={amcContracts.length >= pagination.pageSize}\n hasPreviousPage={pagination.page > 1}\n onPageChange={pagination.setPage}\n onPageSizeChange={pagination.setPageSize}\n />\n </>\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 type=\"button\" onClick={openCreate} className=\"gap-2\">\n <Plus className=\"h-4 w-4\" />\n New Contract\n </Button>\n }\n />\n )}\n </div>\n\n {/* Sidebar */}\n <aside className=\"space-y-6 lg:col-span-1\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm\">\n <h3 className=\"mb-4 text-base 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=\"Upcoming service dates from your active contracts will appear here.\"\n className=\"border-0 bg-transparent p-4\"\n />\n ) : (\n <ol className=\"space-y-4\">\n {scheduledServices.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 return (\n <li key={contract.id} className=\"flex gap-3\">\n <div\n className={cn(\n 'flex h-9 w-9 shrink-0 items-center justify-center rounded-full',\n isPast\n ? 'bg-status-error-bg-subtle'\n : isUrgent\n ? 'bg-status-warning-bg-subtle'\n : 'bg-status-info-bg-subtle'\n )}\n >\n {isPast ? (\n <AlertTriangle className=\"h-4 w-4 text-status-error-text\" />\n ) : isUrgent ? (\n <Clock className=\"h-4 w-4 text-status-warning-text\" />\n ) : (\n <Wrench className=\"h-4 w-4 text-status-info-text\" />\n )}\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary\">{formatDate(contract.nextServiceDate)}</p>\n <p className=\"truncate text-sm text-text-primary\">\n {contract.packageName} — {contract.propertyTitle}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">{contract.providerName}</p>\n <p\n className={cn(\n 'text-xs font-medium',\n isPast\n ? 'text-status-error-text'\n : isUrgent\n ? 'text-status-warning-text'\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 </li>\n );\n })}\n </ol>\n )}\n </div>\n\n <button\n type=\"button\"\n onClick={() => navigate('../rental')}\n className=\"flex w-full items-center gap-3 rounded-lg border border-border-subtle bg-bg-surface p-4 text-left shadow-sm transition-colors hover:bg-bg-sunken focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <div className=\"rounded-lg bg-status-info-bg-subtle p-2.5\">\n <Wrench className=\"h-5 w-5 text-status-info-text\" />\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">Open maintenance requests</p>\n <p className=\"text-xl font-bold text-text-primary\">{openMaintenanceCount}</p>\n </div>\n </button>\n </aside>\n </div>\n\n {/* New Contract dialog */}\n <ResponsiveDialog open={createOpen} onOpenChange={setCreateOpen}>\n <ResponsiveDialogContent className=\"sm:max-w-lg\">\n <form onSubmit={handleCreate}>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New maintenance contract</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Set up an Annual Maintenance Contract to track coverage, cost and renewal dates.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3 py-2\">\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\">Property *</label>\n <input\n type=\"text\"\n required\n placeholder=\"e.g. Maple Court\"\n value={propertyTitle}\n onChange={(e) => setPropertyTitle(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\">Package *</label>\n <input\n type=\"text\"\n required\n placeholder=\"e.g. Premium Care\"\n value={packageName}\n onChange={(e) => setPackageName(e.target.value)}\n className={inputClass}\n />\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\">Provider *</label>\n <input\n type=\"text\"\n required\n placeholder=\"Service company\"\n value={providerName}\n onChange={(e) => setProviderName(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\">Monthly cost *</label>\n <input\n type=\"number\"\n required\n min={0}\n step=\"0.01\"\n placeholder=\"0.00\"\n value={monthlyCost}\n onChange={(e) => setMonthlyCost(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\">Start date</label>\n <input\n type=\"date\"\n value={startDate}\n onChange={(e) => setStartDate(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\">End date</label>\n <input\n type=\"date\"\n value={endDate}\n onChange={(e) => setEndDate(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 AmcStatus)}\n className={inputClass}\n >\n <option value=\"active\">Active</option>\n <option value=\"expiring_soon\">Expiring soon</option>\n <option value=\"expired\">Expired</option>\n <option value=\"cancelled\">Cancelled</option>\n </select>\n </div>\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Services included</label>\n <input\n type=\"text\"\n placeholder=\"Comma-separated, e.g. Plumbing, Electrical, HVAC\"\n value={servicesIncluded}\n onChange={(e) => setServicesIncluded(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}>\n {creating ? 'Saving…' : 'Create contract'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -26,12 +26,12 @@ import { jsx as D, jsxs as O } from "react/jsx-runtime";
26
26
  import { EmphasisPanel as k, NextSteps as ie } from "@burdenoff/fe-libs/ui";
27
27
  //#region src/housingvista/pages/analytics/AnalyticsPage.tsx
28
28
  var A = [
29
- "var(--chart-1)",
30
- "var(--chart-2)",
31
- "var(--chart-3)",
32
- "var(--chart-4)",
33
- "var(--chart-5)"
34
- ], j = "var(--chart-2)", M = "var(--chart-1)", N = {
29
+ "var(--color-chart-1)",
30
+ "var(--color-chart-2)",
31
+ "var(--color-chart-3)",
32
+ "var(--color-chart-4)",
33
+ "var(--color-chart-5)"
34
+ ], j = "var(--color-chart-2)", M = "var(--color-chart-1)", N = {
35
35
  background: "var(--color-bg-surface)",
36
36
  border: "1px solid var(--color-border-subtle)",
37
37
  borderRadius: 10,
@@ -433,11 +433,11 @@ function L() {
433
433
  /* @__PURE__ */ D(g, {
434
434
  type: "monotone",
435
435
  dataKey: "rate",
436
- stroke: "var(--chart-2)",
436
+ stroke: "var(--color-chart-2)",
437
437
  strokeWidth: 2.5,
438
438
  dot: {
439
439
  r: 3,
440
- fill: "var(--chart-2)",
440
+ fill: "var(--color-chart-2)",
441
441
  strokeWidth: 0
442
442
  },
443
443
  activeDot: { r: 5 }
@@ -1 +1 @@
1
- {"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../../src/housingvista/pages/analytics/AnalyticsPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n TrendingUp,\n DollarSign,\n Building,\n Activity,\n Download,\n Clock,\n BarChart3,\n} from 'lucide-react';\nimport {\n AreaChart,\n Area,\n LineChart,\n Line,\n PieChart,\n Pie,\n Cell,\n XAxis,\n YAxis,\n CartesianGrid,\n Tooltip,\n ResponsiveContainer,\n} from 'recharts';\nimport { EmphasisPanel, NextSteps } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components';\nimport { StatCard } from '../../components';\nimport { ChartCard } from '../../components';\nimport { DataTable, type Column } from '../../components';\nimport { EmptyState } from '../../components';\nimport { LoadingSkeleton } from '../../components';\nimport { useAnalyticsData } from '../../hooks';\nimport { notify } from '../../lib/toast';\nimport { cn, formatCurrency, formatNumber } from '../../utils/helpers';\n\n// Distribution slice colours drawn from the semantic chart palette (full oklch tokens).\nconst CHART_COLORS = ['var(--chart-1)', 'var(--chart-2)', 'var(--chart-3)', 'var(--chart-4)', 'var(--chart-5)'];\nconst REVENUE_COLOR = 'var(--chart-2)';\nconst EXPENSE_COLOR = 'var(--chart-1)';\n\n// Shared, theme-aware tooltip styling for every recharts surface on this page.\nconst tooltipContentStyle: React.CSSProperties = {\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-subtle)',\n borderRadius: 10,\n fontSize: 12,\n boxShadow: '0 8px 24px rgb(0 0 0 / 0.12)',\n};\nconst tooltipLabelStyle: React.CSSProperties = { color: 'var(--color-text-primary)', fontWeight: 600 };\nconst axisTick = { fontSize: 11, fill: 'var(--color-text-secondary)' } as const;\n\n// The backend series is monthly; this range control slices to the most recent N\n// months so the same real data can be viewed at different horizons (0 = all).\nconst rangeOptions: { value: number; label: string }[] = [\n { value: 3, label: '3M' },\n { value: 6, label: '6M' },\n { value: 12, label: '12M' },\n { value: 0, label: 'All' },\n];\n\nexport function AnalyticsPage() {\n const { data: analyticsData, loading } = useAnalyticsData();\n const [range, setRange] = useState<number>(12);\n\n // Client-side CSV export of the loaded analytics data (KPIs + monthly revenue).\n const handleExportCsv = () => {\n if (!analyticsData) return;\n\n const escapeCsv = (val: string | number) => {\n const s = String(val);\n return /[\",\\n]/.test(s) ? `\"${s.replace(/\"/g, '\"\"')}\"` : s;\n };\n\n const lines: string[] = [];\n lines.push('KPI,Value,Unit,Change %');\n for (const kpi of analyticsData.kpis) {\n lines.push([kpi.label, kpi.value, kpi.unit, kpi.changePercent].map(escapeCsv).join(','));\n }\n lines.push('');\n lines.push('Month,Revenue,Expenses,Net Income');\n for (const row of analyticsData.revenueByMonth) {\n lines.push([row.month, row.revenue, row.expenses, row.revenue - row.expenses].map(escapeCsv).join(','));\n }\n\n const blob = new Blob([lines.join('\\n')], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = `housingvista-analytics-${new Date().toISOString().slice(0, 10)}.csv`;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n\n notify.success('Report exported', 'Your analytics data was downloaded as a CSV file.');\n };\n\n const revenueColumns: Column<{ month: string; revenue: number; expenses: number }>[] = [\n {\n key: 'month',\n header: 'Month',\n render: (item) => <span className=\"font-medium text-text-primary\">{item.month}</span>,\n },\n {\n key: 'revenue',\n header: 'Revenue',\n render: (item) => <span className=\"font-medium text-status-success-text\">{formatCurrency(item.revenue)}</span>,\n },\n {\n key: 'expenses',\n header: 'Expenses',\n render: (item) => <span className=\"font-medium text-status-error-text\">{formatCurrency(item.expenses)}</span>,\n },\n {\n key: 'net',\n header: 'Net Income',\n sortable: false,\n render: (item) => {\n const net = item.revenue - item.expenses;\n return (\n <span className={cn('font-medium', net >= 0 ? 'text-status-success-text' : 'text-status-error-text')}>\n {formatCurrency(net)}\n </span>\n );\n },\n },\n {\n key: 'margin',\n header: 'Margin',\n sortable: false,\n render: (item) => {\n const margin = item.revenue > 0 ? ((item.revenue - item.expenses) / item.revenue) * 100 : 0;\n return (\n <span className={cn('font-medium', margin >= 0 ? 'text-status-success-text' : 'text-status-error-text')}>\n {margin.toFixed(1)}%\n </span>\n );\n },\n },\n ];\n\n // ---- Loading ----\n if (loading) {\n return (\n <PageLayout title=\"Analytics\" description=\"Track your property portfolio performance and market insights\">\n <LoadingSkeleton className=\"h-28\" />\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <LoadingSkeleton className=\"h-64\" />\n <LoadingSkeleton className=\"h-64\" />\n </div>\n </PageLayout>\n );\n }\n\n // ---- Empty / not-yet-computed ----\n const hasData =\n analyticsData &&\n (analyticsData.kpis.length > 0 ||\n analyticsData.revenueByMonth.length > 0 ||\n analyticsData.propertyTypeDistribution.length > 0);\n\n if (!hasData) {\n return (\n <PageLayout title=\"Analytics\" description=\"Track your property portfolio performance and market insights\">\n <EmptyState\n illustration=\"empty-generic\"\n icon={<BarChart3 className=\"h-12 w-12\" />}\n title=\"No analytics yet\"\n description=\"Once you have properties, transactions, and rentals recorded, revenue, occupancy, and portfolio insights will appear here.\"\n />\n </PageLayout>\n );\n }\n\n const kpis = analyticsData.kpis;\n const propertyTypeDistribution = analyticsData.propertyTypeDistribution;\n\n // Range control slices the real monthly series to its most recent N entries.\n const sliceRange = <T,>(rows: T[]): T[] => (range > 0 ? rows.slice(-range) : rows);\n const revenueByMonth = sliceRange(analyticsData.revenueByMonth);\n const occupancyRate = sliceRange(analyticsData.occupancyRate);\n\n const revenueChartData = revenueByMonth.map((r) => ({\n month: r.month,\n revenue: r.revenue,\n expenses: r.expenses,\n net: r.revenue - r.expenses,\n }));\n const distributionTotal = propertyTypeDistribution.reduce((s, d) => s + d.value, 0);\n const distributionData = propertyTypeDistribution.map((d, idx) => ({\n name: d.type.charAt(0).toUpperCase() + d.type.slice(1),\n value: d.value,\n count: d.count,\n color: CHART_COLORS[idx % CHART_COLORS.length],\n }));\n\n const totalRevenue = revenueByMonth.reduce((s, r) => s + r.revenue, 0);\n const totalExpenses = revenueByMonth.reduce((s, r) => s + r.expenses, 0);\n const netIncome = totalRevenue - totalExpenses;\n const profitMargin = totalRevenue > 0 ? (netIncome / totalRevenue) * 100 : 0;\n\n return (\n <PageLayout\n title=\"Analytics\"\n description=\"Track your property portfolio performance and market insights\"\n purpose=\"Understand how your portfolio is actually performing — revenue versus expenses, occupancy, property-type mix, and profitability — so you can spot trends, compare periods, and decide where to buy, sell, or raise rents.\"\n actions={\n <button\n onClick={handleExportCsv}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Download className=\"h-4 w-4\" />\n Export Report\n </button>\n }\n >\n <NextSteps\n storageKey=\"housingvista-analytics\"\n steps={[\n {\n id: 'performance',\n label: 'Drill into monthly performance',\n description: 'Review revenue, expenses, net income, and margin month by month.',\n href: '#analytics-performance',\n },\n ]}\n />\n\n {/* Period range control — slices the real monthly series to a horizon */}\n <div className=\"flex flex-wrap items-center gap-3\">\n <span className=\"text-sm font-medium text-text-secondary\">Period</span>\n <div\n role=\"group\"\n aria-label=\"Select analytics period\"\n className=\"inline-flex rounded-lg border border-border-subtle bg-bg-sunken/60 p-1\"\n >\n {rangeOptions.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n aria-pressed={range === option.value}\n onClick={() => setRange(option.value)}\n className={cn(\n 'rounded-md px-3 py-1.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n range === option.value\n ? 'bg-action-primary-bg text-action-primary-text shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n )}\n >\n {option.label}\n </button>\n ))}\n </div>\n </div>\n\n {/* Stats Overview Row — primary insight zone (one EmphasisPanel per page) */}\n {kpis.length > 0 && (\n <EmphasisPanel className=\"p-4\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {kpis.map((kpi) => {\n const icons: Record<string, typeof DollarSign> = {\n 'Portfolio Value': DollarSign,\n 'Monthly Revenue': TrendingUp,\n 'Occupancy Rate': Building,\n 'Avg. Days to Rent': Clock,\n };\n const IconComponent = icons[kpi.label] || Activity;\n\n return (\n <StatCard\n key={kpi.id}\n label={kpi.label}\n value={\n kpi.unit === 'USD'\n ? kpi.value\n : `${formatNumber(kpi.value)}${kpi.unit === '%' ? '%' : ` ${kpi.unit}`}`\n }\n prefix={kpi.unit === 'USD' ? '$' : undefined}\n icon={<IconComponent className=\"h-4 w-4\" />}\n trend={kpi.trend === 'up' ? 'up' : kpi.trend === 'down' ? 'down' : 'stable'}\n change={kpi.changePercent}\n />\n );\n })}\n </div>\n </EmphasisPanel>\n )}\n\n {/* Charts Section — revenue area chart (wide) + property-type donut (aside) */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {revenueChartData.length > 0 && (\n <ChartCard\n title=\"Revenue vs Expenses\"\n description=\"Real income against costs across the selected period\"\n className=\"lg:col-span-2\"\n actions={\n <div className=\"flex items-center gap-4 text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1.5\">\n <span className=\"h-2.5 w-2.5 rounded-full\" style={{ background: REVENUE_COLOR }} />\n Revenue\n </span>\n <span className=\"flex items-center gap-1.5\">\n <span className=\"h-2.5 w-2.5 rounded-full\" style={{ background: EXPENSE_COLOR }} />\n Expenses\n </span>\n </div>\n }\n >\n <div className=\"h-72 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <AreaChart data={revenueChartData} margin={{ top: 8, right: 8, left: 0, bottom: 0 }}>\n <defs>\n <linearGradient id=\"revGrad\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"5%\" stopColor={REVENUE_COLOR} stopOpacity={0.35} />\n <stop offset=\"95%\" stopColor={REVENUE_COLOR} stopOpacity={0.02} />\n </linearGradient>\n <linearGradient id=\"expGrad\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"5%\" stopColor={EXPENSE_COLOR} stopOpacity={0.3} />\n <stop offset=\"95%\" stopColor={EXPENSE_COLOR} stopOpacity={0.02} />\n </linearGradient>\n </defs>\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--color-border-subtle)\" vertical={false} />\n <XAxis dataKey=\"month\" tick={axisTick} tickLine={false} axisLine={false} />\n <YAxis\n tick={axisTick}\n tickLine={false}\n axisLine={false}\n width={52}\n tickFormatter={(v: number) => formatCurrency(v)}\n />\n <Tooltip\n contentStyle={tooltipContentStyle}\n labelStyle={tooltipLabelStyle}\n formatter={(value: number, name) => [formatCurrency(value), name]}\n />\n <Area\n type=\"monotone\"\n dataKey=\"revenue\"\n name=\"Revenue\"\n stroke={REVENUE_COLOR}\n strokeWidth={2.5}\n fill=\"url(#revGrad)\"\n />\n <Area\n type=\"monotone\"\n dataKey=\"expenses\"\n name=\"Expenses\"\n stroke={EXPENSE_COLOR}\n strokeWidth={2.5}\n fill=\"url(#expGrad)\"\n />\n </AreaChart>\n </ResponsiveContainer>\n </div>\n </ChartCard>\n )}\n\n {distributionData.length > 0 && (\n <ChartCard title=\"Property-Type Distribution\" description=\"Portfolio value split by type\">\n <div className=\"relative h-56 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <PieChart>\n <Pie\n data={distributionData}\n dataKey=\"value\"\n nameKey=\"name\"\n innerRadius={58}\n outerRadius={84}\n paddingAngle={2}\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={2}\n >\n {distributionData.map((entry) => (\n <Cell key={entry.name} fill={entry.color} />\n ))}\n </Pie>\n <Tooltip\n contentStyle={tooltipContentStyle}\n labelStyle={tooltipLabelStyle}\n formatter={(value: number, _n, item) => [\n `${formatCurrency(value)} · ${item?.payload?.count ?? 0} properties`,\n item?.payload?.name,\n ]}\n />\n </PieChart>\n </ResponsiveContainer>\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center\">\n <span className=\"text-xs text-text-secondary\">Total value</span>\n <span className=\"text-lg font-bold text-text-primary\">{formatCurrency(distributionTotal)}</span>\n </div>\n </div>\n <ul className=\"mt-3 space-y-2\">\n {distributionData.map((entry) => (\n <li key={entry.name} className=\"flex items-center justify-between text-sm\">\n <span className=\"flex items-center gap-2\">\n <span className=\"h-2.5 w-2.5 rounded-full\" style={{ background: entry.color }} />\n <span className=\"text-text-primary\">{entry.name}</span>\n </span>\n <span className=\"text-text-secondary\">\n {distributionTotal > 0 ? Math.round((entry.value / distributionTotal) * 100) : 0}%\n </span>\n </li>\n ))}\n </ul>\n </ChartCard>\n )}\n </div>\n\n {/* Occupancy trend (wide) + quick stats (aside) */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {occupancyRate.length > 0 && (\n <ChartCard\n title=\"Occupancy Trend\"\n description=\"Share of units occupied over the selected period\"\n className=\"lg:col-span-2\"\n >\n <div className=\"h-56 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <LineChart data={occupancyRate} margin={{ top: 8, right: 12, left: 0, bottom: 0 }}>\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--color-border-subtle)\" vertical={false} />\n <XAxis dataKey=\"month\" tick={axisTick} tickLine={false} axisLine={false} />\n <YAxis\n tick={axisTick}\n tickLine={false}\n axisLine={false}\n width={40}\n domain={[0, 100]}\n tickFormatter={(v: number) => `${v}%`}\n />\n <Tooltip\n contentStyle={tooltipContentStyle}\n labelStyle={tooltipLabelStyle}\n formatter={(value: number) => [`${value}%`, 'Occupancy']}\n />\n <Line\n type=\"monotone\"\n dataKey=\"rate\"\n stroke=\"var(--chart-2)\"\n strokeWidth={2.5}\n dot={{ r: 3, fill: 'var(--chart-2)', strokeWidth: 0 }}\n activeDot={{ r: 5 }}\n />\n </LineChart>\n </ResponsiveContainer>\n </div>\n </ChartCard>\n )}\n\n {revenueByMonth.length > 0 && (\n <ChartCard title=\"Period Summary\" description=\"Totals across the selected range\">\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between rounded-lg bg-bg-sunken/50 p-3\">\n <div>\n <p className=\"text-sm text-text-secondary\">Total Revenue</p>\n <p className=\"text-lg font-bold text-text-primary\">{formatCurrency(totalRevenue)}</p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm text-text-secondary\">Total Expenses</p>\n <p className=\"text-lg font-bold text-text-primary\">{formatCurrency(totalExpenses)}</p>\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"rounded-lg bg-status-success-bg-subtle p-3 text-center\">\n <p className=\"text-xl font-bold text-status-success-text\">{formatCurrency(netIncome)}</p>\n <p className=\"text-xs text-text-secondary\">Net Income</p>\n </div>\n <div className=\"rounded-lg bg-status-info-bg-subtle p-3 text-center\">\n <p className=\"text-xl font-bold text-status-info-text\">{profitMargin.toFixed(1)}%</p>\n <p className=\"text-xs text-text-secondary\">Profit Margin</p>\n </div>\n </div>\n </div>\n </ChartCard>\n )}\n </div>\n\n {/* Performance Metrics Table */}\n {revenueByMonth.length > 0 && (\n <div id=\"analytics-performance\" className=\"scroll-mt-6\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Monthly Performance</h2>\n <DataTable columns={revenueColumns} data={revenueByMonth} keyField=\"month\" pageSize={12} />\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../../src/housingvista/pages/analytics/AnalyticsPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { TrendingUp, DollarSign, Building, Activity, Download, Clock, BarChart3 } from 'lucide-react';\nimport {\n AreaChart,\n Area,\n LineChart,\n Line,\n PieChart,\n Pie,\n Cell,\n XAxis,\n YAxis,\n CartesianGrid,\n Tooltip,\n ResponsiveContainer,\n} from 'recharts';\nimport { EmphasisPanel, NextSteps } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components';\nimport { StatCard } from '../../components';\nimport { ChartCard } from '../../components';\nimport { DataTable, type Column } from '../../components';\nimport { EmptyState } from '../../components';\nimport { LoadingSkeleton } from '../../components';\nimport { useAnalyticsData } from '../../hooks';\nimport { notify } from '../../lib/toast';\nimport { cn, formatCurrency, formatNumber } from '../../utils/helpers';\n\n// Distribution slice colours drawn from the semantic chart palette (full oklch tokens).\nconst CHART_COLORS = ['var(--color-chart-1)', 'var(--color-chart-2)', 'var(--color-chart-3)', 'var(--color-chart-4)', 'var(--color-chart-5)'];\nconst REVENUE_COLOR = 'var(--color-chart-2)';\nconst EXPENSE_COLOR = 'var(--color-chart-1)';\n\n// Shared, theme-aware tooltip styling for every recharts surface on this page.\nconst tooltipContentStyle: React.CSSProperties = {\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-subtle)',\n borderRadius: 10,\n fontSize: 12,\n boxShadow: '0 8px 24px rgb(0 0 0 / 0.12)',\n};\nconst tooltipLabelStyle: React.CSSProperties = { color: 'var(--color-text-primary)', fontWeight: 600 };\nconst axisTick = { fontSize: 11, fill: 'var(--color-text-secondary)' } as const;\n\n// The backend series is monthly; this range control slices to the most recent N\n// months so the same real data can be viewed at different horizons (0 = all).\nconst rangeOptions: { value: number; label: string }[] = [\n { value: 3, label: '3M' },\n { value: 6, label: '6M' },\n { value: 12, label: '12M' },\n { value: 0, label: 'All' },\n];\n\nexport function AnalyticsPage() {\n const { data: analyticsData, loading } = useAnalyticsData();\n const [range, setRange] = useState<number>(12);\n\n // Client-side CSV export of the loaded analytics data (KPIs + monthly revenue).\n const handleExportCsv = () => {\n if (!analyticsData) return;\n\n const escapeCsv = (val: string | number) => {\n const s = String(val);\n return /[\",\\n]/.test(s) ? `\"${s.replace(/\"/g, '\"\"')}\"` : s;\n };\n\n const lines: string[] = [];\n lines.push('KPI,Value,Unit,Change %');\n for (const kpi of analyticsData.kpis) {\n lines.push([kpi.label, kpi.value, kpi.unit, kpi.changePercent].map(escapeCsv).join(','));\n }\n lines.push('');\n lines.push('Month,Revenue,Expenses,Net Income');\n for (const row of analyticsData.revenueByMonth) {\n lines.push([row.month, row.revenue, row.expenses, row.revenue - row.expenses].map(escapeCsv).join(','));\n }\n\n const blob = new Blob([lines.join('\\n')], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = `housingvista-analytics-${new Date().toISOString().slice(0, 10)}.csv`;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n\n notify.success('Report exported', 'Your analytics data was downloaded as a CSV file.');\n };\n\n const revenueColumns: Column<{ month: string; revenue: number; expenses: number }>[] = [\n {\n key: 'month',\n header: 'Month',\n render: (item) => <span className=\"font-medium text-text-primary\">{item.month}</span>,\n },\n {\n key: 'revenue',\n header: 'Revenue',\n render: (item) => <span className=\"font-medium text-status-success-text\">{formatCurrency(item.revenue)}</span>,\n },\n {\n key: 'expenses',\n header: 'Expenses',\n render: (item) => <span className=\"font-medium text-status-error-text\">{formatCurrency(item.expenses)}</span>,\n },\n {\n key: 'net',\n header: 'Net Income',\n sortable: false,\n render: (item) => {\n const net = item.revenue - item.expenses;\n return (\n <span className={cn('font-medium', net >= 0 ? 'text-status-success-text' : 'text-status-error-text')}>\n {formatCurrency(net)}\n </span>\n );\n },\n },\n {\n key: 'margin',\n header: 'Margin',\n sortable: false,\n render: (item) => {\n const margin = item.revenue > 0 ? ((item.revenue - item.expenses) / item.revenue) * 100 : 0;\n return (\n <span className={cn('font-medium', margin >= 0 ? 'text-status-success-text' : 'text-status-error-text')}>\n {margin.toFixed(1)}%\n </span>\n );\n },\n },\n ];\n\n // ---- Loading ----\n if (loading) {\n return (\n <PageLayout title=\"Analytics\" description=\"Track your property portfolio performance and market insights\">\n <LoadingSkeleton className=\"h-28\" />\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <LoadingSkeleton className=\"h-64\" />\n <LoadingSkeleton className=\"h-64\" />\n </div>\n </PageLayout>\n );\n }\n\n // ---- Empty / not-yet-computed ----\n const hasData =\n analyticsData &&\n (analyticsData.kpis.length > 0 ||\n analyticsData.revenueByMonth.length > 0 ||\n analyticsData.propertyTypeDistribution.length > 0);\n\n if (!hasData) {\n return (\n <PageLayout title=\"Analytics\" description=\"Track your property portfolio performance and market insights\">\n <EmptyState\n illustration=\"empty-generic\"\n icon={<BarChart3 className=\"h-12 w-12\" />}\n title=\"No analytics yet\"\n description=\"Once you have properties, transactions, and rentals recorded, revenue, occupancy, and portfolio insights will appear here.\"\n />\n </PageLayout>\n );\n }\n\n const kpis = analyticsData.kpis;\n const propertyTypeDistribution = analyticsData.propertyTypeDistribution;\n\n // Range control slices the real monthly series to its most recent N entries.\n const sliceRange = <T,>(rows: T[]): T[] => (range > 0 ? rows.slice(-range) : rows);\n const revenueByMonth = sliceRange(analyticsData.revenueByMonth);\n const occupancyRate = sliceRange(analyticsData.occupancyRate);\n\n const revenueChartData = revenueByMonth.map((r) => ({\n month: r.month,\n revenue: r.revenue,\n expenses: r.expenses,\n net: r.revenue - r.expenses,\n }));\n const distributionTotal = propertyTypeDistribution.reduce((s, d) => s + d.value, 0);\n const distributionData = propertyTypeDistribution.map((d, idx) => ({\n name: d.type.charAt(0).toUpperCase() + d.type.slice(1),\n value: d.value,\n count: d.count,\n color: CHART_COLORS[idx % CHART_COLORS.length],\n }));\n\n const totalRevenue = revenueByMonth.reduce((s, r) => s + r.revenue, 0);\n const totalExpenses = revenueByMonth.reduce((s, r) => s + r.expenses, 0);\n const netIncome = totalRevenue - totalExpenses;\n const profitMargin = totalRevenue > 0 ? (netIncome / totalRevenue) * 100 : 0;\n\n return (\n <PageLayout\n title=\"Analytics\"\n description=\"Track your property portfolio performance and market insights\"\n purpose=\"Understand how your portfolio is actually performing — revenue versus expenses, occupancy, property-type mix, and profitability — so you can spot trends, compare periods, and decide where to buy, sell, or raise rents.\"\n actions={\n <button\n onClick={handleExportCsv}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Download className=\"h-4 w-4\" />\n Export Report\n </button>\n }\n >\n <NextSteps\n storageKey=\"housingvista-analytics\"\n steps={[\n {\n id: 'performance',\n label: 'Drill into monthly performance',\n description: 'Review revenue, expenses, net income, and margin month by month.',\n href: '#analytics-performance',\n },\n ]}\n />\n\n {/* Period range control — slices the real monthly series to a horizon */}\n <div className=\"flex flex-wrap items-center gap-3\">\n <span className=\"text-sm font-medium text-text-secondary\">Period</span>\n <div\n role=\"group\"\n aria-label=\"Select analytics period\"\n className=\"inline-flex rounded-lg border border-border-subtle bg-bg-sunken/60 p-1\"\n >\n {rangeOptions.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n aria-pressed={range === option.value}\n onClick={() => setRange(option.value)}\n className={cn(\n 'rounded-md px-3 py-1.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n range === option.value\n ? 'bg-action-primary-bg text-action-primary-text shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n )}\n >\n {option.label}\n </button>\n ))}\n </div>\n </div>\n\n {/* Stats Overview Row — primary insight zone (one EmphasisPanel per page) */}\n {kpis.length > 0 && (\n <EmphasisPanel className=\"p-4\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {kpis.map((kpi) => {\n const icons: Record<string, typeof DollarSign> = {\n 'Portfolio Value': DollarSign,\n 'Monthly Revenue': TrendingUp,\n 'Occupancy Rate': Building,\n 'Avg. Days to Rent': Clock,\n };\n const IconComponent = icons[kpi.label] || Activity;\n\n return (\n <StatCard\n key={kpi.id}\n label={kpi.label}\n value={\n kpi.unit === 'USD'\n ? kpi.value\n : `${formatNumber(kpi.value)}${kpi.unit === '%' ? '%' : ` ${kpi.unit}`}`\n }\n prefix={kpi.unit === 'USD' ? '$' : undefined}\n icon={<IconComponent className=\"h-4 w-4\" />}\n trend={kpi.trend === 'up' ? 'up' : kpi.trend === 'down' ? 'down' : 'stable'}\n change={kpi.changePercent}\n />\n );\n })}\n </div>\n </EmphasisPanel>\n )}\n\n {/* Charts Section — revenue area chart (wide) + property-type donut (aside) */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {revenueChartData.length > 0 && (\n <ChartCard\n title=\"Revenue vs Expenses\"\n description=\"Real income against costs across the selected period\"\n className=\"lg:col-span-2\"\n actions={\n <div className=\"flex items-center gap-4 text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1.5\">\n <span className=\"h-2.5 w-2.5 rounded-full\" style={{ background: REVENUE_COLOR }} />\n Revenue\n </span>\n <span className=\"flex items-center gap-1.5\">\n <span className=\"h-2.5 w-2.5 rounded-full\" style={{ background: EXPENSE_COLOR }} />\n Expenses\n </span>\n </div>\n }\n >\n <div className=\"h-72 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <AreaChart data={revenueChartData} margin={{ top: 8, right: 8, left: 0, bottom: 0 }}>\n <defs>\n <linearGradient id=\"revGrad\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"5%\" stopColor={REVENUE_COLOR} stopOpacity={0.35} />\n <stop offset=\"95%\" stopColor={REVENUE_COLOR} stopOpacity={0.02} />\n </linearGradient>\n <linearGradient id=\"expGrad\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"5%\" stopColor={EXPENSE_COLOR} stopOpacity={0.3} />\n <stop offset=\"95%\" stopColor={EXPENSE_COLOR} stopOpacity={0.02} />\n </linearGradient>\n </defs>\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--color-border-subtle)\" vertical={false} />\n <XAxis dataKey=\"month\" tick={axisTick} tickLine={false} axisLine={false} />\n <YAxis\n tick={axisTick}\n tickLine={false}\n axisLine={false}\n width={52}\n tickFormatter={(v: number) => formatCurrency(v)}\n />\n <Tooltip\n contentStyle={tooltipContentStyle}\n labelStyle={tooltipLabelStyle}\n formatter={(value: number, name) => [formatCurrency(value), name]}\n />\n <Area\n type=\"monotone\"\n dataKey=\"revenue\"\n name=\"Revenue\"\n stroke={REVENUE_COLOR}\n strokeWidth={2.5}\n fill=\"url(#revGrad)\"\n />\n <Area\n type=\"monotone\"\n dataKey=\"expenses\"\n name=\"Expenses\"\n stroke={EXPENSE_COLOR}\n strokeWidth={2.5}\n fill=\"url(#expGrad)\"\n />\n </AreaChart>\n </ResponsiveContainer>\n </div>\n </ChartCard>\n )}\n\n {distributionData.length > 0 && (\n <ChartCard title=\"Property-Type Distribution\" description=\"Portfolio value split by type\">\n <div className=\"relative h-56 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <PieChart>\n <Pie\n data={distributionData}\n dataKey=\"value\"\n nameKey=\"name\"\n innerRadius={58}\n outerRadius={84}\n paddingAngle={2}\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={2}\n >\n {distributionData.map((entry) => (\n <Cell key={entry.name} fill={entry.color} />\n ))}\n </Pie>\n <Tooltip\n contentStyle={tooltipContentStyle}\n labelStyle={tooltipLabelStyle}\n formatter={(value: number, _n, item) => [\n `${formatCurrency(value)} · ${item?.payload?.count ?? 0} properties`,\n item?.payload?.name,\n ]}\n />\n </PieChart>\n </ResponsiveContainer>\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center\">\n <span className=\"text-xs text-text-secondary\">Total value</span>\n <span className=\"text-lg font-bold text-text-primary\">{formatCurrency(distributionTotal)}</span>\n </div>\n </div>\n <ul className=\"mt-3 space-y-2\">\n {distributionData.map((entry) => (\n <li key={entry.name} className=\"flex items-center justify-between text-sm\">\n <span className=\"flex items-center gap-2\">\n <span className=\"h-2.5 w-2.5 rounded-full\" style={{ background: entry.color }} />\n <span className=\"text-text-primary\">{entry.name}</span>\n </span>\n <span className=\"text-text-secondary\">\n {distributionTotal > 0 ? Math.round((entry.value / distributionTotal) * 100) : 0}%\n </span>\n </li>\n ))}\n </ul>\n </ChartCard>\n )}\n </div>\n\n {/* Occupancy trend (wide) + quick stats (aside) */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {occupancyRate.length > 0 && (\n <ChartCard\n title=\"Occupancy Trend\"\n description=\"Share of units occupied over the selected period\"\n className=\"lg:col-span-2\"\n >\n <div className=\"h-56 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <LineChart data={occupancyRate} margin={{ top: 8, right: 12, left: 0, bottom: 0 }}>\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--color-border-subtle)\" vertical={false} />\n <XAxis dataKey=\"month\" tick={axisTick} tickLine={false} axisLine={false} />\n <YAxis\n tick={axisTick}\n tickLine={false}\n axisLine={false}\n width={40}\n domain={[0, 100]}\n tickFormatter={(v: number) => `${v}%`}\n />\n <Tooltip\n contentStyle={tooltipContentStyle}\n labelStyle={tooltipLabelStyle}\n formatter={(value: number) => [`${value}%`, 'Occupancy']}\n />\n <Line\n type=\"monotone\"\n dataKey=\"rate\"\n stroke=\"var(--color-chart-2)\"\n strokeWidth={2.5}\n dot={{ r: 3, fill: 'var(--color-chart-2)', strokeWidth: 0 }}\n activeDot={{ r: 5 }}\n />\n </LineChart>\n </ResponsiveContainer>\n </div>\n </ChartCard>\n )}\n\n {revenueByMonth.length > 0 && (\n <ChartCard title=\"Period Summary\" description=\"Totals across the selected range\">\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between rounded-lg bg-bg-sunken/50 p-3\">\n <div>\n <p className=\"text-sm text-text-secondary\">Total Revenue</p>\n <p className=\"text-lg font-bold text-text-primary\">{formatCurrency(totalRevenue)}</p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm text-text-secondary\">Total Expenses</p>\n <p className=\"text-lg font-bold text-text-primary\">{formatCurrency(totalExpenses)}</p>\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"rounded-lg bg-status-success-bg-subtle p-3 text-center\">\n <p className=\"text-xl font-bold text-status-success-text\">{formatCurrency(netIncome)}</p>\n <p className=\"text-xs text-text-secondary\">Net Income</p>\n </div>\n <div className=\"rounded-lg bg-status-info-bg-subtle p-3 text-center\">\n <p className=\"text-xl font-bold text-status-info-text\">{profitMargin.toFixed(1)}%</p>\n <p className=\"text-xs text-text-secondary\">Profit Margin</p>\n </div>\n </div>\n </div>\n </ChartCard>\n )}\n </div>\n\n {/* Performance Metrics Table */}\n {revenueByMonth.length > 0 && (\n <div id=\"analytics-performance\" className=\"scroll-mt-6\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Monthly Performance</h2>\n <DataTable columns={revenueColumns} data={revenueByMonth} keyField=\"month\" pageSize={12} />\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -230,11 +230,11 @@ function R() {
230
230
  data: [{
231
231
  name: "Purchase",
232
232
  value: B.purchasePrice,
233
- fill: "var(--chart-3)"
233
+ fill: "var(--color-chart-3)"
234
234
  }, {
235
235
  name: "Current",
236
236
  value: B.currentValue,
237
- fill: U ? "var(--chart-2)" : "var(--chart-5)"
237
+ fill: U ? "var(--color-chart-2)" : "var(--color-chart-5)"
238
238
  }],
239
239
  margin: {
240
240
  top: 8,
@@ -286,7 +286,7 @@ function R() {
286
286
  0
287
287
  ],
288
288
  maxBarSize: 72,
289
- children: [/* @__PURE__ */ j(d, { fill: "var(--chart-3)" }), /* @__PURE__ */ j(d, { fill: U ? "var(--chart-2)" : "var(--chart-5)" })]
289
+ children: [/* @__PURE__ */ j(d, { fill: "var(--color-chart-3)" }), /* @__PURE__ */ j(d, { fill: U ? "var(--color-chart-2)" : "var(--color-chart-5)" })]
290
290
  })
291
291
  ]
292
292
  })