@burdenoff/microfe-assethandler 2026.625.2 → 2026.703.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assethandler/AssetHandlerRoot.js +0 -24
- package/dist/assethandler/AssetHandlerRoot.js.map +1 -1
- package/dist/assethandler/AssetHandlerRoutes.js +92 -160
- package/dist/assethandler/AssetHandlerRoutes.js.map +1 -1
- package/dist/assethandler/pages/AnalyticsPage.js +367 -402
- package/dist/assethandler/pages/AnalyticsPage.js.map +1 -1
- package/dist/assethandler/pages/AssetsAssetCategoriesPage.js +29 -26
- package/dist/assethandler/pages/AssetsAssetCategoriesPage.js.map +1 -1
- package/dist/assethandler/pages/AssetsAssetCheckInOutPage.js +278 -164
- package/dist/assethandler/pages/AssetsAssetCheckInOutPage.js.map +1 -1
- package/dist/assethandler/pages/AssetsAssetCreatePage.js +200 -242
- package/dist/assethandler/pages/AssetsAssetCreatePage.js.map +1 -1
- package/dist/assethandler/pages/AssetsAssetDetailPage.js +192 -286
- package/dist/assethandler/pages/AssetsAssetDetailPage.js.map +1 -1
- package/dist/assethandler/pages/AssetsAssetListPage.js +204 -296
- package/dist/assethandler/pages/AssetsAssetListPage.js.map +1 -1
- package/dist/assethandler/pages/AssetsAssetTrackingPage.js +38 -42
- package/dist/assethandler/pages/AssetsAssetTrackingPage.js.map +1 -1
- package/dist/assethandler/pages/DashboardPage.js +212 -236
- package/dist/assethandler/pages/DashboardPage.js.map +1 -1
- package/dist/assethandler/pages/ItilIncidentDetailPage.js +41 -85
- package/dist/assethandler/pages/ItilIncidentDetailPage.js.map +1 -1
- package/dist/assethandler/pages/MaintenanceMaintenanceCalendarPage.js +1 -1
- package/dist/assethandler/pages/MaintenanceMaintenanceCalendarPage.js.map +1 -1
- package/dist/assethandler/pages/MaintenanceMaintenanceSchedulerPage.js +37 -86
- package/dist/assethandler/pages/MaintenanceMaintenanceSchedulerPage.js.map +1 -1
- package/dist/assethandler/pages/OperationsMaintenanceDetailPage.js +82 -98
- package/dist/assethandler/pages/OperationsMaintenanceDetailPage.js.map +1 -1
- package/dist/assethandler/pages/OperationsOperationsHubPage.js +6 -22
- package/dist/assethandler/pages/OperationsOperationsHubPage.js.map +1 -1
- package/dist/assethandler/pages/OperationsWorkOrderDetailPage.js +4 -34
- package/dist/assethandler/pages/OperationsWorkOrderDetailPage.js.map +1 -1
- package/dist/assethandler/pages/PortfolioPortfolioOverviewPage.js +105 -150
- package/dist/assethandler/pages/PortfolioPortfolioOverviewPage.js.map +1 -1
- package/dist/assethandler/pages/TransactionsTransactionDetailPage.js +116 -152
- package/dist/assethandler/pages/TransactionsTransactionDetailPage.js.map +1 -1
- package/dist/assethandler/providers/AssetHandlerProvider.js.map +1 -1
- package/dist/assethandler/store/assethandlerStore.js +68 -2206
- package/dist/assethandler/store/assethandlerStore.js.map +1 -1
- package/dist/assethandler/utils/toast.js +33 -0
- package/dist/assethandler/utils/toast.js.map +1 -0
- package/package.json +5 -4
- package/dist/assethandler/pages/AssetsAssetEditPage.js +0 -353
- package/dist/assethandler/pages/AssetsAssetEditPage.js.map +0 -1
- package/dist/assethandler/pages/AuditTrailPage.js +0 -215
- package/dist/assethandler/pages/AuditTrailPage.js.map +0 -1
- package/dist/assethandler/pages/MarketplaceMarketplaceDetailPage.js +0 -177
- package/dist/assethandler/pages/MarketplaceMarketplaceDetailPage.js.map +0 -1
- package/dist/assethandler/pages/MarketplaceMarketplaceListPage.js +0 -162
- package/dist/assethandler/pages/MarketplaceMarketplaceListPage.js.map +0 -1
- package/dist/assethandler/pages/MarketplaceMarketplaceOrdersPage.js +0 -143
- package/dist/assethandler/pages/MarketplaceMarketplaceOrdersPage.js.map +0 -1
- package/dist/assethandler/pages/MarketplaceMarketplaceSellPage.js +0 -163
- package/dist/assethandler/pages/MarketplaceMarketplaceSellPage.js.map +0 -1
- package/dist/assethandler/pages/ReportsReportBuilderPage.js +0 -208
- package/dist/assethandler/pages/ReportsReportBuilderPage.js.map +0 -1
- package/dist/assethandler/pages/ReportsReportsDashboardPage.js +0 -236
- package/dist/assethandler/pages/ReportsReportsDashboardPage.js.map +0 -1
- package/dist/assethandler/pages/ReportsReportsListPage.js +0 -144
- package/dist/assethandler/pages/ReportsReportsListPage.js.map +0 -1
- package/dist/assethandler/pages/SecurityScheduleBackupPage.js +0 -149
- package/dist/assethandler/pages/SecurityScheduleBackupPage.js.map +0 -1
- package/dist/assethandler/pages/UsersPage.js +0 -208
- package/dist/assethandler/pages/UsersPage.js.map +0 -1
- package/dist/assethandler/pages/UsersUserCreatePage.js +0 -220
- package/dist/assethandler/pages/UsersUserCreatePage.js.map +0 -1
- package/dist/assethandler/pages/UsersUserManagementHubPage.js +0 -373
- package/dist/assethandler/pages/UsersUserManagementHubPage.js.map +0 -1
- package/dist/assethandler/pages/WorkflowsWorkflowBuilderPage.js +0 -264
- package/dist/assethandler/pages/WorkflowsWorkflowBuilderPage.js.map +0 -1
- package/dist/assethandler/pages/WorkflowsWorkflowExecutionsPage.js +0 -206
- package/dist/assethandler/pages/WorkflowsWorkflowExecutionsPage.js.map +0 -1
- package/dist/assethandler/pages/WorkflowsWorkflowListPage.js +0 -171
- package/dist/assethandler/pages/WorkflowsWorkflowListPage.js.map +0 -1
- package/dist/assethandler/pages/WorkflowsWorkflowTemplatesPage.js +0 -173
- package/dist/assethandler/pages/WorkflowsWorkflowTemplatesPage.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../src/assethandler/pages/AnalyticsPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n Badge,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n Separator,\n GradientText,\n GlassCard,\n CTAOverflowMenu,\n EmphasisPanel,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport {\n BarChart3,\n TrendingUp,\n TrendingDown,\n DollarSign,\n Box,\n PieChart,\n Activity,\n Download,\n ArrowUpRight,\n ArrowDownRight,\n} from 'lucide-react';\nimport { useAssetHandlerStore } from '../store/assethandlerStore';\n\nexport function AnalyticsPage() {\n const { assets, portfolio, transactions, incidents, maintenanceRecords, workflows } =\n useAssetHandlerStore();\n const [timeRange, setTimeRange] = useState<'7d' | '30d' | '90d' | '1y'>('30d');\n\n const totalValue = assets.reduce((sum, a) => sum + a.currentValue, 0);\n const totalOriginal = assets.reduce((sum, a) => sum + a.value, 0);\n const depreciationTotal = totalOriginal - totalValue;\n const depreciationPct =\n totalOriginal > 0 ? ((depreciationTotal / totalOriginal) * 100).toFixed(1) : '0';\n const avgAssetValue = assets.length > 0 ? Math.round(totalValue / assets.length) : 0;\n\n const categoryBreakdown = assets.reduce<Record<string, { count: number; value: number }>>(\n (acc, a) => {\n const cat = a.category.replace('_', ' ');\n if (!acc[cat]) acc[cat] = { count: 0, value: 0 };\n acc[cat].count++;\n acc[cat].value += a.currentValue;\n return acc;\n },\n {}\n );\n\n const statusBreakdown = assets.reduce<Record<string, number>>((acc, a) => {\n acc[a.status] = (acc[a.status] || 0) + 1;\n return acc;\n }, {});\n\n const departmentBreakdown = assets.reduce<Record<string, { count: number; value: number }>>(\n (acc, a) => {\n if (!acc[a.department]) acc[a.department] = { count: 0, value: 0 };\n acc[a.department].count++;\n acc[a.department].value += a.currentValue;\n return acc;\n },\n {}\n );\n\n const resolvedIncidents = incidents.filter(\n (i) => i.status === 'resolved' || i.status === 'closed'\n );\n const slaBreached = incidents.filter((i) => i.slaBreached);\n const completedMaintenance = maintenanceRecords.filter((m) => m.status === 'completed');\n const totalMaintenanceCost = maintenanceRecords.reduce((sum, m) => sum + m.cost, 0);\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-3xl font-bold\">\n <GradientText>Analytics & Business Intelligence</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n Comprehensive asset management analytics and insights\n </p>\n </div>\n <div className=\"flex gap-2\">\n <select\n value={timeRange}\n onChange={(e) => setTimeRange(e.target.value as typeof timeRange)}\n className=\"rounded-md border border-border-default bg-bg-surface px-3 py-1.5 text-sm\"\n >\n <option value=\"7d\">Last 7 days</option>\n <option value=\"30d\">Last 30 days</option>\n <option value=\"90d\">Last 90 days</option>\n <option value=\"1y\">Last year</option>\n </select>\n <CTAOverflowMenu\n primary={{ label: 'Generate Report', onSelect: () => {} }}\n actions={[\n { label: 'Export', onSelect: () => {}, icon: <Download className=\"h-4 w-4\" /> },\n ]}\n />\n </div>\n </div>\n\n <PagePurpose>\n Turn raw asset data into decisions. See where value is concentrated, how it is depreciating,\n which categories cost the most to run, and how operations are trending — so you can plan\n budgets, justify replacements and spot risk before it shows up as downtime.\n </PagePurpose>\n\n <Tabs defaultValue=\"overview\">\n <TabsList>\n <TabsTrigger value=\"overview\">Overview</TabsTrigger>\n <TabsTrigger value=\"financial\">Financial</TabsTrigger>\n <TabsTrigger value=\"operational\">Operational</TabsTrigger>\n <TabsTrigger value=\"insights\">Insights</TabsTrigger>\n </TabsList>\n\n {/* Overview Tab */}\n <TabsContent value=\"overview\" className=\"space-y-6 mt-6\">\n {/* Key Metrics */}\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Total Portfolio Value</CardTitle>\n <DollarSign className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n ${portfolio.totalValue.toLocaleString()}\n </div>\n <p className=\"text-xs text-text-secondary flex items-center mt-1\">\n <ArrowUpRight className=\"mr-1 h-3 w-3 text-status-success-text\" />\n <span className=\"text-status-success-text\">+5.6%</span> from last quarter\n </p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Total Depreciation</CardTitle>\n <TrendingDown className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold text-status-error-text\">\n -${depreciationTotal.toLocaleString()}\n </div>\n <p className=\"text-xs text-text-secondary\">{depreciationPct}% of original value</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Total Assets</CardTitle>\n <Box className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{portfolio.totalAssets}</div>\n <p className=\"text-xs text-text-secondary\">\n {assets.filter((a) => a.status === 'active').length} currently active\n </p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Avg. Asset Value</CardTitle>\n <BarChart3 className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n ${avgAssetValue.toLocaleString()}\n </div>\n <p className=\"text-xs text-text-secondary\">Across all categories</p>\n </CardContent>\n </GlassCard>\n </div>\n\n {/* Category & Status Breakdown */}\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <PieChart className=\"h-4 w-4\" />\n Category Distribution\n </CardTitle>\n <CardDescription>Assets by category with values</CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {Object.entries(categoryBreakdown)\n .sort((a, b) => b[1].value - a[1].value)\n .map(([cat, data]) => {\n const pct = Math.round((data.count / assets.length) * 100);\n return (\n <div key={cat} className=\"space-y-1\">\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"font-medium capitalize\">{cat}</span>\n <span className=\"text-text-secondary\">\n {data.count} · ${(data.value / 1000).toFixed(1)}K\n </span>\n </div>\n <div className=\"h-2 rounded-full bg-bg-sunken overflow-hidden\">\n <div\n className=\"h-full rounded-full bg-action-primary-bg\"\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n );\n })}\n </div>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Activity className=\"h-4 w-4\" />\n Status Breakdown\n </CardTitle>\n <CardDescription>Current asset status distribution</CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {Object.entries(statusBreakdown).map(([status, count]) => {\n const pct = Math.round((count / assets.length) * 100);\n return (\n <div key={status} className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <Badge\n variant={\n status === 'active'\n ? 'default'\n : status === 'under_maintenance'\n ? 'secondary'\n : 'outline'\n }\n >\n {status.replace('_', ' ')}\n </Badge>\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"h-2 w-24 rounded-full bg-bg-sunken overflow-hidden\">\n <div\n className=\"h-full rounded-full bg-action-primary-bg\"\n style={{ width: `${pct}%` }}\n />\n </div>\n <span className=\"text-sm font-medium w-8 text-right\">{count}</span>\n </div>\n </div>\n );\n })}\n </div>\n\n <Separator className=\"my-4\" />\n\n <div>\n <h4 className=\"text-sm font-medium mb-3\">By Department</h4>\n <div className=\"space-y-2\">\n {Object.entries(departmentBreakdown)\n .sort((a, b) => b[1].count - a[1].count)\n .slice(0, 5)\n .map(([dept, data]) => (\n <div key={dept} className=\"flex items-center justify-between text-sm\">\n <span>{dept}</span>\n <span className=\"text-text-secondary\">{data.count} assets</span>\n </div>\n ))}\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n\n {/* Depreciation Trend */}\n <Card>\n <CardHeader>\n <CardTitle>Depreciation Trend</CardTitle>\n <CardDescription>Portfolio depreciation over the last 6 months</CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"flex items-end gap-2 h-40\">\n {portfolio.depreciationTrend.map((point) => {\n const maxVal = Math.max(...portfolio.depreciationTrend.map((p) => p.value));\n const height = maxVal > 0 ? (point.value / maxVal) * 100 : 0;\n return (\n <div key={point.month} className=\"flex-1 flex flex-col items-center gap-1\">\n <span className=\"text-[10px] text-text-secondary\">\n ${(point.value / 1000000).toFixed(2)}M\n </span>\n <div\n className=\"w-full bg-action-primary-bg/80 rounded-t\"\n style={{ height: `${height}%` }}\n />\n <span className=\"text-[10px] text-text-secondary\">\n {point.month.replace(' 20', \"'\")}\n </span>\n </div>\n );\n })}\n </div>\n </CardContent>\n </Card>\n </TabsContent>\n\n {/* Financial Tab */}\n <TabsContent value=\"financial\" className=\"space-y-6 mt-6\">\n <div className=\"grid gap-4 md:grid-cols-3\">\n <Card>\n <CardHeader className=\"pb-2\">\n <CardTitle className=\"text-sm font-medium\">Total Transactions</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{transactions.length}</div>\n <p className=\"text-xs text-text-secondary\">\n ${transactions.reduce((s, t) => s + Math.abs(t.amount), 0).toLocaleString()} total\n value\n </p>\n </CardContent>\n </Card>\n <Card>\n <CardHeader className=\"pb-2\">\n <CardTitle className=\"text-sm font-medium\">Maintenance Cost</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n ${totalMaintenanceCost.toLocaleString()}\n </div>\n <p className=\"text-xs text-text-secondary\">\n {maintenanceRecords.length} maintenance records\n </p>\n </CardContent>\n </Card>\n <Card>\n <CardHeader className=\"pb-2\">\n <CardTitle className=\"text-sm font-medium\">Acquisitions (6mo)</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n {portfolio.acquisitionTrend.reduce((s, a) => s + a.count, 0)}\n </div>\n <p className=\"text-xs text-text-secondary\">\n ${(portfolio.acquisitionTrend.reduce((s, a) => s + a.value, 0) / 1000).toFixed(0)}\n K total spend\n </p>\n </CardContent>\n </Card>\n </div>\n\n {/* Acquisition Trend */}\n <Card>\n <CardHeader>\n <CardTitle>Acquisition Trend</CardTitle>\n <CardDescription>New assets acquired over the last 6 months</CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"flex items-end gap-2 h-40\">\n {portfolio.acquisitionTrend.map((point) => {\n const maxVal = Math.max(...portfolio.acquisitionTrend.map((p) => p.count));\n const height = maxVal > 0 ? (point.count / maxVal) * 100 : 0;\n return (\n <div key={point.month} className=\"flex-1 flex flex-col items-center gap-1\">\n <span className=\"text-[10px] text-text-secondary\">{point.count} units</span>\n <div\n className=\"w-full bg-status-success-bg rounded-t\"\n style={{ height: `${height}%` }}\n />\n <span className=\"text-[10px] text-text-secondary\">\n {point.month.replace(' 20', \"'\")}\n </span>\n </div>\n );\n })}\n </div>\n </CardContent>\n </Card>\n\n {/* Recent Transactions */}\n <Card>\n <CardHeader>\n <CardTitle>Recent Transactions</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b\">\n <th className=\"text-left py-2 font-medium\">ID</th>\n <th className=\"text-left py-2 font-medium\">Type</th>\n <th className=\"text-left py-2 font-medium\">Description</th>\n <th className=\"text-right py-2 font-medium\">Amount</th>\n <th className=\"text-left py-2 font-medium\">Status</th>\n </tr>\n </thead>\n <tbody>\n {transactions.slice(0, 5).map((txn) => (\n <tr key={txn.id} className=\"border-b hover:bg-bg-sunken/50\">\n <td className=\"py-2 font-mono text-xs\">{txn.id}</td>\n <td className=\"py-2 capitalize\">{txn.type}</td>\n <td className=\"py-2 truncate max-w-xs\">{txn.description}</td>\n <td\n className={`py-2 text-right font-medium ${txn.amount < 0 ? 'text-status-error-text' : ''}`}\n >\n ${Math.abs(txn.amount).toLocaleString()}\n </td>\n <td className=\"py-2\">\n <Badge variant=\"outline\">{txn.status}</Badge>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </CardContent>\n </Card>\n </TabsContent>\n\n {/* Operational Tab */}\n <TabsContent value=\"operational\" className=\"space-y-6 mt-6\">\n <div className=\"grid gap-4 md:grid-cols-4\">\n <Card>\n <CardHeader className=\"pb-2\">\n <CardTitle className=\"text-sm font-medium\">Incidents</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{incidents.length}</div>\n <p className=\"text-xs text-text-secondary\">{resolvedIncidents.length} resolved</p>\n </CardContent>\n </Card>\n <Card>\n <CardHeader className=\"pb-2\">\n <CardTitle className=\"text-sm font-medium\">SLA Breaches</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold text-status-error-text\">\n {slaBreached.length}\n </div>\n <p className=\"text-xs text-text-secondary\">\n {((1 - slaBreached.length / Math.max(incidents.length, 1)) * 100).toFixed(1)}%\n compliance\n </p>\n </CardContent>\n </Card>\n <Card>\n <CardHeader className=\"pb-2\">\n <CardTitle className=\"text-sm font-medium\">Maintenance Done</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{completedMaintenance.length}</div>\n <p className=\"text-xs text-text-secondary\">of {maintenanceRecords.length} total</p>\n </CardContent>\n </Card>\n <Card>\n <CardHeader className=\"pb-2\">\n <CardTitle className=\"text-sm font-medium\">Workflow Success</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n {(\n workflows.reduce((s, w) => s + w.successRate, 0) / Math.max(workflows.length, 1)\n ).toFixed(1)}\n %\n </div>\n <p className=\"text-xs text-text-secondary\">{workflows.length} active workflows</p>\n </CardContent>\n </Card>\n </div>\n </TabsContent>\n\n {/* Insights Tab */}\n <TabsContent value=\"insights\" className=\"space-y-6 mt-6\">\n <div className=\"grid gap-4 md:grid-cols-3\">\n <Card className=\"border-status-info-border bg-status-info-bg-subtle\">\n <CardHeader>\n <CardTitle className=\"text-status-info-text text-base\">Diversification</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-status-info-text\">\n Your portfolio spans {Object.keys(categoryBreakdown).length} categories across{' '}\n {Object.keys(departmentBreakdown).length} departments. IT Hardware represents the\n largest segment at {portfolio.byCategory[0]?.count || 0} assets.\n </p>\n </CardContent>\n </Card>\n <Card className=\"border-status-success-border bg-status-success-bg-subtle\">\n <CardHeader>\n <CardTitle className=\"text-status-success-text text-base\">Performance</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-status-success-text\">\n Portfolio depreciated value stands at $\n {(portfolio.depreciatedValue / 1000000).toFixed(2)}M. Depreciation rate is within\n expected range at {depreciationPct}% of original cost.\n </p>\n </CardContent>\n </Card>\n <Card className=\"border-status-warning-border bg-status-warning-bg-subtle\">\n <CardHeader>\n <CardTitle className=\"text-status-warning-text text-base\">Maintenance</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-status-warning-text\">\n {maintenanceRecords.filter((m) => m.status === 'scheduled').length} maintenance\n tasks are scheduled. Total maintenance spend is $\n {totalMaintenanceCost.toLocaleString()}. Stay on top of preventive maintenance.\n </p>\n </CardContent>\n </Card>\n </div>\n\n {/* Top Assets by Value */}\n <EmphasisPanel>\n <CardHeader className=\"px-0 pt-0\">\n <CardTitle>Top Assets by Value</CardTitle>\n <CardDescription>Highest value assets in your portfolio</CardDescription>\n </CardHeader>\n <CardContent className=\"px-0 pb-0\">\n <div className=\"space-y-3\">\n {[...assets]\n .sort((a, b) => b.currentValue - a.currentValue)\n .slice(0, 5)\n .map((asset, i) => (\n <div\n key={asset.id}\n className=\"flex items-center justify-between p-3 rounded-lg border border-border-seam bg-gloss-card\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"w-8 h-8 rounded-full bg-action-primary-bg/10 flex items-center justify-center text-xs font-medium text-primary\">\n {i + 1}\n </div>\n <div>\n <p className=\"text-sm font-medium\">{asset.name}</p>\n <p className=\"text-xs text-text-secondary\">\n {asset.department} · {asset.category.replace('_', ' ')}\n </p>\n </div>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-medium\">\n ${asset.currentValue.toLocaleString()}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {asset.currentValue < asset.value ? (\n <span className=\"text-status-error-text\">\n -{((1 - asset.currentValue / asset.value) * 100).toFixed(1)}%\n </span>\n ) : (\n <span className=\"text-status-success-text\">No depreciation</span>\n )}\n </p>\n </div>\n </div>\n ))}\n </div>\n </CardContent>\n </EmphasisPanel>\n </TabsContent>\n </Tabs>\n </div>\n );\n}\n"],"mappings":";;;;;;AAiCA,SAAgB,IAAgB;CAC9B,IAAM,EAAE,WAAQ,cAAW,iBAAc,cAAW,uBAAoB,iBACtE,GAAsB,EAClB,CAAC,GAAW,KAAgB,EAAsC,MAAM,EAExE,IAAa,EAAO,QAAQ,GAAK,MAAM,IAAM,EAAE,cAAc,EAAE,EAC/D,IAAgB,EAAO,QAAQ,GAAK,MAAM,IAAM,EAAE,OAAO,EAAE,EAC3D,IAAoB,IAAgB,GACpC,IACJ,IAAgB,KAAM,IAAoB,IAAiB,KAAK,QAAQ,EAAE,GAAG,KACzE,IAAgB,EAAO,SAAS,IAAI,KAAK,MAAM,IAAa,EAAO,OAAO,GAAG,GAE7E,IAAoB,EAAO,QAC9B,GAAK,MAAM;EACV,IAAM,IAAM,EAAE,SAAS,QAAQ,KAAK,IAAI;AAIxC,SAHK,EAAI,OAAM,EAAI,KAAO;GAAE,OAAO;GAAG,OAAO;GAAG,GAChD,EAAI,GAAK,SACT,EAAI,GAAK,SAAS,EAAE,cACb;IAET,EAAE,CACH,EAEK,IAAkB,EAAO,QAAgC,GAAK,OAClE,EAAI,EAAE,WAAW,EAAI,EAAE,WAAW,KAAK,GAChC,IACN,EAAE,CAAC,EAEA,IAAsB,EAAO,QAChC,GAAK,OACC,EAAI,EAAE,gBAAa,EAAI,EAAE,cAAc;EAAE,OAAO;EAAG,OAAO;EAAG,GAClE,EAAI,EAAE,YAAY,SAClB,EAAI,EAAE,YAAY,SAAS,EAAE,cACtB,IAET,EAAE,CACH,EAEK,IAAoB,EAAU,QACjC,MAAM,EAAE,WAAW,cAAc,EAAE,WAAW,SAChD,EACK,IAAc,EAAU,QAAQ,MAAM,EAAE,YAAY,EACpD,IAAuB,EAAmB,QAAQ,MAAM,EAAE,WAAW,YAAY,EACjF,IAAuB,EAAmB,QAAQ,GAAK,MAAM,IAAM,EAAE,MAAM,EAAE;AAEnF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eACZ,kBAAC,GAAD,EAAA,UAAc,qCAAgD,CAAA;KAC3D,CAAA,EACL,kBAAC,KAAD;KAAG,WAAU;eAAmC;KAE5C,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,OAAO;MACP,WAAW,MAAM,EAAa,EAAE,OAAO,MAA0B;MACjE,WAAU;gBAHZ;OAKE,kBAAC,UAAD;QAAQ,OAAM;kBAAK;QAAoB,CAAA;OACvC,kBAAC,UAAD;QAAQ,OAAM;kBAAM;QAAqB,CAAA;OACzC,kBAAC,UAAD;QAAQ,OAAM;kBAAM;QAAqB,CAAA;OACzC,kBAAC,UAAD;QAAQ,OAAM;kBAAK;QAAkB,CAAA;OAC9B;SACT,kBAAC,GAAD;MACE,SAAS;OAAE,OAAO;OAAmB,gBAAgB;OAAI;MACzD,SAAS,CACP;OAAE,OAAO;OAAU,gBAAgB;OAAI,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;OAAE,CAChF;MACD,CAAA,CACE;OACF;;GAEN,kBAAC,GAAD,EAAA,UAAa,qQAIC,CAAA;GAEd,kBAAC,GAAD;IAAM,cAAa;cAAnB;KACE,kBAAC,GAAD,EAAA,UAAA;MACE,kBAAC,GAAD;OAAa,OAAM;iBAAW;OAAsB,CAAA;MACpD,kBAAC,GAAD;OAAa,OAAM;iBAAY;OAAuB,CAAA;MACtD,kBAAC,GAAD;OAAa,OAAM;iBAAc;OAAyB,CAAA;MAC1D,kBAAC,GAAD;OAAa,OAAM;iBAAW;OAAsB,CAAA;MAC3C,EAAA,CAAA;KAGX,kBAAC,GAAD;MAAa,OAAM;MAAW,WAAU;gBAAxC;OAEE,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,GAAD;UAAW,WAAU;UAAS,WAAU;oBAAxC,CACE,kBAAC,GAAD;WAAY,WAAU;qBAAtB,CACE,kBAAC,GAAD;YAAW,WAAU;sBAAsB;YAAiC,CAAA,EAC5E,kBAAC,GAAD,EAAY,WAAU,+BAAgC,CAAA,CAC3C;cACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CAAiD,KAC7C,EAAU,WAAW,gBAAgB,CACnC;cACN,kBAAC,KAAD;WAAG,WAAU;qBAAb;YACE,kBAAC,GAAD,EAAc,WAAU,yCAA0C,CAAA;YAClE,kBAAC,QAAD;aAAM,WAAU;uBAA2B;aAAY,CAAA;;YACrD;aACQ,EAAA,CAAA,CACJ;;SACZ,kBAAC,GAAD;UAAW,WAAU;UAAS,WAAU;oBAAxC,CACE,kBAAC,GAAD;WAAY,WAAU;qBAAtB,CACE,kBAAC,GAAD;YAAW,WAAU;sBAAsB;YAA8B,CAAA,EACzE,kBAAC,GAAD,EAAc,WAAU,+BAAgC,CAAA,CAC7C;cACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CAA2D,MACtD,EAAkB,gBAAgB,CACjC;cACN,kBAAC,KAAD;WAAG,WAAU;qBAAb,CAA4C,GAAgB,sBAAuB;aACvE,EAAA,CAAA,CACJ;;SACZ,kBAAC,GAAD;UAAW,WAAU;UAAS,WAAU;oBAAxC,CACE,kBAAC,GAAD;WAAY,WAAU;qBAAtB,CACE,kBAAC,GAAD;YAAW,WAAU;sBAAsB;YAAwB,CAAA,EACnE,kBAAC,GAAD,EAAK,WAAU,+BAAgC,CAAA,CACpC;cACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;WAAK,WAAU;qBAAmC,EAAU;WAAkB,CAAA,EAC9E,kBAAC,KAAD;WAAG,WAAU;qBAAb,CACG,EAAO,QAAQ,MAAM,EAAE,WAAW,SAAS,CAAC,QAAO,oBAClD;aACQ,EAAA,CAAA,CACJ;;SACZ,kBAAC,GAAD;UAAW,WAAU;UAAS,WAAU;oBAAxC,CACE,kBAAC,GAAD;WAAY,WAAU;qBAAtB,CACE,kBAAC,GAAD;YAAW,WAAU;sBAAsB;YAA4B,CAAA,EACvE,kBAAC,GAAD,EAAW,WAAU,+BAAgC,CAAA,CAC1C;cACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CAAiD,KAC7C,EAAc,gBAAgB,CAC5B;cACN,kBAAC,KAAD;WAAG,WAAU;qBAA8B;WAAyB,CAAA,CACxD,EAAA,CAAA,CACJ;;SACR;;OAGN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;SAAW,WAAU;mBAArB,CACE,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA,EAAA,wBAEtB;YACZ,kBAAC,GAAD,EAAA,UAAiB,kCAAgD,CAAA,CACtD,EAAA,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;SAAK,WAAU;mBACZ,OAAO,QAAQ,EAAkB,CAC/B,MAAM,GAAG,MAAM,EAAE,GAAG,QAAQ,EAAE,GAAG,MAAM,CACvC,KAAK,CAAC,GAAK,OAAU;UACpB,IAAM,IAAM,KAAK,MAAO,EAAK,QAAQ,EAAO,SAAU,IAAI;AAC1D,iBACE,kBAAC,OAAD;WAAe,WAAU;qBAAzB,CACE,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,QAAD;aAAM,WAAU;uBAA0B;aAAW,CAAA,EACrD,kBAAC,QAAD;aAAM,WAAU;uBAAhB;cACG,EAAK;cAAM;eAAa,EAAK,QAAQ,KAAM,QAAQ,EAAE;cAAC;cAClD;eACH;eACN,kBAAC,OAAD;YAAK,WAAU;sBACb,kBAAC,OAAD;aACE,WAAU;aACV,OAAO,EAAE,OAAO,GAAG,EAAI,IAAI;aAC3B,CAAA;YACE,CAAA,CACF;aAbI,EAaJ;WAER;SACA,CAAA,EACM,CAAA,CACT,EAAA,CAAA,EAEP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;SAAW,WAAU;mBAArB,CACE,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA,EAAA,mBAEtB;YACZ,kBAAC,GAAD,EAAA,UAAiB,qCAAmD,CAAA,CACzD,EAAA,CAAA,EACb,kBAAC,GAAD,EAAA,UAAA;SACE,kBAAC,OAAD;UAAK,WAAU;oBACZ,OAAO,QAAQ,EAAgB,CAAC,KAAK,CAAC,GAAQ,OAAW;WACxD,IAAM,IAAM,KAAK,MAAO,IAAQ,EAAO,SAAU,IAAI;AACrD,kBACE,kBAAC,OAAD;YAAkB,WAAU;sBAA5B,CACE,kBAAC,OAAD;aAAK,WAAU;uBACb,kBAAC,GAAD;cACE,SACE,MAAW,WACP,YACA,MAAW,sBACT,cACA;wBAGP,EAAO,QAAQ,KAAK,IAAI;cACnB,CAAA;aACJ,CAAA,EACN,kBAAC,OAAD;aAAK,WAAU;uBAAf,CACE,kBAAC,OAAD;cAAK,WAAU;wBACb,kBAAC,OAAD;eACE,WAAU;eACV,OAAO,EAAE,OAAO,GAAG,EAAI,IAAI;eAC3B,CAAA;cACE,CAAA,EACN,kBAAC,QAAD;cAAM,WAAU;wBAAsC;cAAa,CAAA,CAC/D;eACF;cAvBI,EAuBJ;YAER;UACE,CAAA;SAEN,kBAAC,GAAD,EAAW,WAAU,QAAS,CAAA;SAE9B,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;UAAI,WAAU;oBAA2B;UAAkB,CAAA,EAC3D,kBAAC,OAAD;UAAK,WAAU;oBACZ,OAAO,QAAQ,EAAoB,CACjC,MAAM,GAAG,MAAM,EAAE,GAAG,QAAQ,EAAE,GAAG,MAAM,CACvC,MAAM,GAAG,EAAE,CACX,KAAK,CAAC,GAAM,OACX,kBAAC,OAAD;WAAgB,WAAU;qBAA1B,CACE,kBAAC,QAAD,EAAA,UAAO,GAAY,CAAA,EACnB,kBAAC,QAAD;YAAM,WAAU;sBAAhB,CAAuC,EAAK,OAAM,UAAc;cAC5D;aAHI,EAGJ,CACN;UACA,CAAA,CACF,EAAA,CAAA;SACM,EAAA,CAAA,CACT,EAAA,CAAA,CACH;;OAGN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAW,sBAA8B,CAAA,EACzC,kBAAC,GAAD,EAAA,UAAiB,iDAA+D,CAAA,CACrE,EAAA,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;QAAK,WAAU;kBACZ,EAAU,kBAAkB,KAAK,MAAU;SAC1C,IAAM,IAAS,KAAK,IAAI,GAAG,EAAU,kBAAkB,KAAK,MAAM,EAAE,MAAM,CAAC,EACrE,IAAS,IAAS,IAAK,EAAM,QAAQ,IAAU,MAAM;AAC3D,gBACE,kBAAC,OAAD;UAAuB,WAAU;oBAAjC;WACE,kBAAC,QAAD;YAAM,WAAU;sBAAhB;aAAkD;cAC7C,EAAM,QAAQ,KAAS,QAAQ,EAAE;aAAC;aAChC;;WACP,kBAAC,OAAD;YACE,WAAU;YACV,OAAO,EAAE,QAAQ,GAAG,EAAO,IAAI;YAC/B,CAAA;WACF,kBAAC,QAAD;YAAM,WAAU;sBACb,EAAM,MAAM,QAAQ,OAAO,IAAI;YAC3B,CAAA;WACH;YAXI,EAAM,MAWV;UAER;QACE,CAAA,EACM,CAAA,CACT,EAAA,CAAA;OACK;;KAGd,kBAAC,GAAD;MAAa,OAAM;MAAY,WAAU;gBAAzC;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;UAAY,WAAU;oBACpB,kBAAC,GAAD;WAAW,WAAU;qBAAsB;WAA8B,CAAA;UAC9D,CAAA,EACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;UAAK,WAAU;oBAAmC,EAAa;UAAa,CAAA,EAC5E,kBAAC,KAAD;UAAG,WAAU;oBAAb;WAA2C;WACvC,EAAa,QAAQ,GAAG,MAAM,IAAI,KAAK,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,gBAAgB;WAAC;WAE1E;YACQ,EAAA,CAAA,CACT,EAAA,CAAA;SACP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;UAAY,WAAU;oBACpB,kBAAC,GAAD;WAAW,WAAU;qBAAsB;WAA4B,CAAA;UAC5D,CAAA,EACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CAAiD,KAC7C,EAAqB,gBAAgB,CACnC;aACN,kBAAC,KAAD;UAAG,WAAU;oBAAb,CACG,EAAmB,QAAO,uBACzB;YACQ,EAAA,CAAA,CACT,EAAA,CAAA;SACP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;UAAY,WAAU;oBACpB,kBAAC,GAAD;WAAW,WAAU;qBAAsB;WAA8B,CAAA;UAC9D,CAAA,EACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;UAAK,WAAU;oBACZ,EAAU,iBAAiB,QAAQ,GAAG,MAAM,IAAI,EAAE,OAAO,EAAE;UACxD,CAAA,EACN,kBAAC,KAAD;UAAG,WAAU;oBAAb;WAA2C;YACtC,EAAU,iBAAiB,QAAQ,GAAG,MAAM,IAAI,EAAE,OAAO,EAAE,GAAG,KAAM,QAAQ,EAAE;WAAC;WAEhF;YACQ,EAAA,CAAA,CACT,EAAA,CAAA;SACH;;OAGN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAW,qBAA6B,CAAA,EACxC,kBAAC,GAAD,EAAA,UAAiB,8CAA4D,CAAA,CAClE,EAAA,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;QAAK,WAAU;kBACZ,EAAU,iBAAiB,KAAK,MAAU;SACzC,IAAM,IAAS,KAAK,IAAI,GAAG,EAAU,iBAAiB,KAAK,MAAM,EAAE,MAAM,CAAC,EACpE,IAAS,IAAS,IAAK,EAAM,QAAQ,IAAU,MAAM;AAC3D,gBACE,kBAAC,OAAD;UAAuB,WAAU;oBAAjC;WACE,kBAAC,QAAD;YAAM,WAAU;sBAAhB,CAAmD,EAAM,OAAM,SAAa;;WAC5E,kBAAC,OAAD;YACE,WAAU;YACV,OAAO,EAAE,QAAQ,GAAG,EAAO,IAAI;YAC/B,CAAA;WACF,kBAAC,QAAD;YAAM,WAAU;sBACb,EAAM,MAAM,QAAQ,OAAO,IAAI;YAC3B,CAAA;WACH;YATI,EAAM,MASV;UAER;QACE,CAAA,EACM,CAAA,CACT,EAAA,CAAA;OAGP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,uBAA+B,CAAA,EAC/B,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,SAAD;SAAO,WAAU;mBAAjB,CACE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD;UAAI,WAAU;oBAAd;WACE,kBAAC,MAAD;YAAI,WAAU;sBAA6B;YAAO,CAAA;WAClD,kBAAC,MAAD;YAAI,WAAU;sBAA6B;YAAS,CAAA;WACpD,kBAAC,MAAD;YAAI,WAAU;sBAA6B;YAAgB,CAAA;WAC3D,kBAAC,MAAD;YAAI,WAAU;sBAA8B;YAAW,CAAA;WACvD,kBAAC,MAAD;YAAI,WAAU;sBAA6B;YAAW,CAAA;WACnD;aACC,CAAA,EACR,kBAAC,SAAD,EAAA,UACG,EAAa,MAAM,GAAG,EAAE,CAAC,KAAK,MAC7B,kBAAC,MAAD;UAAiB,WAAU;oBAA3B;WACE,kBAAC,MAAD;YAAI,WAAU;sBAA0B,EAAI;YAAQ,CAAA;WACpD,kBAAC,MAAD;YAAI,WAAU;sBAAmB,EAAI;YAAU,CAAA;WAC/C,kBAAC,MAAD;YAAI,WAAU;sBAA0B,EAAI;YAAiB,CAAA;WAC7D,kBAAC,MAAD;YACE,WAAW,+BAA+B,EAAI,SAAS,IAAI,2BAA2B;sBADxF,CAEC,KACG,KAAK,IAAI,EAAI,OAAO,CAAC,gBAAgB,CACpC;;WACL,kBAAC,MAAD;YAAI,WAAU;sBACZ,kBAAC,GAAD;aAAO,SAAQ;uBAAW,EAAI;aAAe,CAAA;YAC1C,CAAA;WACF;YAZI,EAAI,GAYR,CACL,EACI,CAAA,CACF;;QACJ,CAAA,EACM,CAAA,CACT,EAAA,CAAA;OACK;;KAGd,kBAAC,GAAD;MAAa,OAAM;MAAc,WAAU;gBACzC,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;SAAY,WAAU;mBACpB,kBAAC,GAAD;UAAW,WAAU;oBAAsB;UAAqB,CAAA;SACrD,CAAA,EACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAmC,EAAU;SAAa,CAAA,EACzE,kBAAC,KAAD;SAAG,WAAU;mBAAb,CAA4C,EAAkB,QAAO,YAAa;WACtE,EAAA,CAAA,CACT,EAAA,CAAA;QACP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;SAAY,WAAU;mBACpB,kBAAC,GAAD;UAAW,WAAU;oBAAsB;UAAwB,CAAA;SACxD,CAAA,EACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;SAAK,WAAU;mBACZ,EAAY;SACT,CAAA,EACN,kBAAC,KAAD;SAAG,WAAU;mBAAb,GACK,IAAI,EAAY,SAAS,KAAK,IAAI,EAAU,QAAQ,EAAE,IAAI,KAAK,QAAQ,EAAE,EAAC,eAE3E;WACQ,EAAA,CAAA,CACT,EAAA,CAAA;QACP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;SAAY,WAAU;mBACpB,kBAAC,GAAD;UAAW,WAAU;oBAAsB;UAA4B,CAAA;SAC5D,CAAA,EACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAmC,EAAqB;SAAa,CAAA,EACpF,kBAAC,KAAD;SAAG,WAAU;mBAAb;UAA2C;UAAI,EAAmB;UAAO;UAAU;WACvE,EAAA,CAAA,CACT,EAAA,CAAA;QACP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;SAAY,WAAU;mBACpB,kBAAC,GAAD;UAAW,WAAU;oBAAsB;UAA4B,CAAA;SAC5D,CAAA,EACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,EAEI,EAAU,QAAQ,GAAG,MAAM,IAAI,EAAE,aAAa,EAAE,GAAG,KAAK,IAAI,EAAU,QAAQ,EAAE,EAChF,QAAQ,EAAE,EAAC,IAET;YACN,kBAAC,KAAD;SAAG,WAAU;mBAAb,CAA4C,EAAU,QAAO,oBAAqB;WACtE,EAAA,CAAA,CACT,EAAA,CAAA;QACH;;MACM,CAAA;KAGd,kBAAC,GAAD;MAAa,OAAM;MAAW,WAAU;gBAAxC,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD;SAAM,WAAU;mBAAhB,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;UAAW,WAAU;oBAAkC;UAA2B,CAAA,EACvE,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,KAAD;UAAG,WAAU;oBAAb;WAA6C;WACrB,OAAO,KAAK,EAAkB,CAAC;WAAO;WAAmB;WAC9E,OAAO,KAAK,EAAoB,CAAC;WAAO;WACrB,EAAU,WAAW,IAAI,SAAS;WAAE;WACtD;aACQ,CAAA,CACT;;QACP,kBAAC,GAAD;SAAM,WAAU;mBAAhB,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;UAAW,WAAU;oBAAqC;UAAuB,CAAA,EACtE,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,KAAD;UAAG,WAAU;oBAAb;WAAgD;YAE5C,EAAU,mBAAmB,KAAS,QAAQ,EAAE;WAAC;WAChC;WAAgB;WACjC;aACQ,CAAA,CACT;;QACP,kBAAC,GAAD;SAAM,WAAU;mBAAhB,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;UAAW,WAAU;oBAAqC;UAAuB,CAAA,EACtE,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,KAAD;UAAG,WAAU;oBAAb;WACG,EAAmB,QAAQ,MAAM,EAAE,WAAW,YAAY,CAAC;WAAO;WAElE,EAAqB,gBAAgB;WAAC;WACrC;aACQ,CAAA,CACT;;QACH;UAGN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;OAAY,WAAU;iBAAtB,CACE,kBAAC,GAAD,EAAA,UAAW,uBAA+B,CAAA,EAC1C,kBAAC,GAAD,EAAA,UAAiB,0CAAwD,CAAA,CAC9D;UACb,kBAAC,GAAD;OAAa,WAAU;iBACrB,kBAAC,OAAD;QAAK,WAAU;kBACZ,CAAC,GAAG,EAAO,CACT,MAAM,GAAG,MAAM,EAAE,eAAe,EAAE,aAAa,CAC/C,MAAM,GAAG,EAAE,CACX,KAAK,GAAO,MACX,kBAAC,OAAD;SAEE,WAAU;mBAFZ,CAIE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,OAAD;WAAK,WAAU;qBACZ,IAAI;WACD,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;WAAG,WAAU;qBAAuB,EAAM;WAAS,CAAA,EACnD,kBAAC,KAAD;WAAG,WAAU;qBAAb;YACG,EAAM;YAAW;YAAW,EAAM,SAAS,QAAQ,KAAK,IAAI;YAC3D;aACA,EAAA,CAAA,CACF;aACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBAAb,CAAmC,KAC/B,EAAM,aAAa,gBAAgB,CACnC;cACJ,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAM,eAAe,EAAM,QAC1B,kBAAC,QAAD;YAAM,WAAU;sBAAhB;aAAyC;eACnC,IAAI,EAAM,eAAe,EAAM,SAAS,KAAK,QAAQ,EAAE;aAAC;aACvD;gBAEP,kBAAC,QAAD;YAAM,WAAU;sBAA2B;YAAsB,CAAA;WAEjE,CAAA,CACA;YACF;WA5BC,EAAM,GA4BP,CACN;QACA,CAAA;OACM,CAAA,CACA,EAAA,CAAA,CACJ;;KACT;;GACH"}
|
|
1
|
+
{"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../src/assethandler/pages/AnalyticsPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport {\n Badge,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n Separator,\n GradientText,\n GlassCard,\n EmphasisPanel,\n PagePurpose,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport { Box, PieChart, Activity, Wrench, AlertTriangle, Wallet } from 'lucide-react';\nimport { useAssetHandlerStore } from '../store/assethandlerStore';\n\nconst statusVariant = (status: string): 'default' | 'secondary' | 'outline' =>\n status === 'active' ? 'default' : status === 'maintenance' ? 'secondary' : 'outline';\n\nexport function AnalyticsPage() {\n const { assets, transactions, incidents, maintenanceRecords, budgets, invoices, workOrders } =\n useAssetHandlerStore();\n\n // ─── Asset aggregations (count-based only — no monetary asset value exists) ──\n const categoryBreakdown = useMemo(\n () =>\n assets.reduce<Record<string, number>>((acc, a) => {\n const cat = a.category.replace('_', ' ');\n acc[cat] = (acc[cat] || 0) + 1;\n return acc;\n }, {}),\n [assets]\n );\n\n const statusBreakdown = useMemo(\n () =>\n assets.reduce<Record<string, number>>((acc, a) => {\n acc[a.status] = (acc[a.status] || 0) + 1;\n return acc;\n }, {}),\n [assets]\n );\n\n const conditionBreakdown = useMemo(\n () =>\n assets.reduce<Record<string, number>>((acc, a) => {\n acc[a.condition] = (acc[a.condition] || 0) + 1;\n return acc;\n }, {}),\n [assets]\n );\n\n const activeAssets = assets.filter((a) => a.status === 'active').length;\n const attentionAssets = assets.filter(\n (a) => a.condition === 'poor' || a.condition === 'damaged' || a.status === 'maintenance'\n );\n\n // ─── Financial aggregations (from real transactions / budgets / invoices) ────\n const transactionsTotal = transactions.reduce((s, t) => s + Math.abs(t.amount), 0);\n const budgetAllocated = budgets.reduce((s, b) => s + b.allocated, 0);\n const budgetSpent = budgets.reduce((s, b) => s + b.spent, 0);\n const budgetRemaining = budgets.reduce((s, b) => s + b.remaining, 0);\n const maintenanceCost = maintenanceRecords.reduce(\n (s, m) => s + (m.actualCost ?? m.estimatedCost ?? 0),\n 0\n );\n const outstandingInvoices = invoices.filter((i) => i.status !== 'paid' && i.status !== 'cancelled');\n const outstandingInvoiceTotal = outstandingInvoices.reduce((s, i) => s + i.amount, 0);\n\n // ─── Operational aggregations ────────────────────────────────────────────────\n const resolvedIncidents = incidents.filter(\n (i) => i.status === 'resolved' || i.status === 'closed'\n );\n const slaBreached = incidents.filter((i) => i.slaBreached);\n const completedMaintenance = maintenanceRecords.filter((m) => m.status === 'completed');\n const scheduledMaintenance = maintenanceRecords.filter((m) => m.status === 'scheduled');\n const openWorkOrders = workOrders.filter(\n (w) => w.status !== 'completed' && w.status !== 'cancelled'\n );\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-3xl font-bold\">\n <GradientText>Analytics & Business Intelligence</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n Asset, financial and operational insights across your estate\n </p>\n </div>\n </div>\n\n <PagePurpose>\n Turn raw asset data into decisions. See how your estate is distributed by category, status\n and condition, where budget is being spent, and how operations are trending — so you can\n plan work, justify replacements and spot risk before it shows up as downtime.\n </PagePurpose>\n\n <Tabs defaultValue=\"overview\">\n <TabsList>\n <TabsTrigger value=\"overview\">Overview</TabsTrigger>\n <TabsTrigger value=\"financial\">Financial</TabsTrigger>\n <TabsTrigger value=\"operational\">Operational</TabsTrigger>\n <TabsTrigger value=\"insights\">Insights</TabsTrigger>\n </TabsList>\n\n {/* Overview Tab */}\n <TabsContent value=\"overview\" className=\"space-y-6 mt-6\">\n {/* Key Metrics */}\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Total Assets</CardTitle>\n <Box className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{assets.length}</div>\n <p className=\"text-xs text-text-secondary\">{activeAssets} currently active</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Categories</CardTitle>\n <PieChart className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n {Object.keys(categoryBreakdown).length}\n </div>\n <p className=\"text-xs text-text-secondary\">Distinct asset categories</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Needs Attention</CardTitle>\n <AlertTriangle className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{attentionAssets.length}</div>\n <p className=\"text-xs text-text-secondary\">Poor / damaged / in maintenance</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Open Incidents</CardTitle>\n <Activity className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n {incidents.length - resolvedIncidents.length}\n </div>\n <p className=\"text-xs text-text-secondary\">{incidents.length} total logged</p>\n </CardContent>\n </GlassCard>\n </div>\n\n {/* Category & Status Breakdown */}\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <PieChart className=\"h-4 w-4\" />\n Category Distribution\n </CardTitle>\n <CardDescription>Assets grouped by category</CardDescription>\n </CardHeader>\n <CardContent>\n {assets.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No assets yet\"\n description=\"Add assets to see how your estate is distributed.\"\n />\n ) : (\n <div className=\"space-y-3\">\n {Object.entries(categoryBreakdown)\n .sort((a, b) => b[1] - a[1])\n .map(([cat, count]) => {\n const pct = Math.round((count / assets.length) * 100);\n return (\n <div key={cat} className=\"space-y-1\">\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"font-medium capitalize\">{cat}</span>\n <span className=\"text-text-secondary\">\n {count} · {pct}%\n </span>\n </div>\n <div className=\"h-2 rounded-full bg-bg-sunken overflow-hidden\">\n <div\n className=\"h-full rounded-full bg-action-primary-bg\"\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n );\n })}\n </div>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Activity className=\"h-4 w-4\" />\n Status & Condition\n </CardTitle>\n <CardDescription>Current asset status and condition mix</CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {Object.entries(statusBreakdown).map(([status, count]) => {\n const pct = assets.length > 0 ? Math.round((count / assets.length) * 100) : 0;\n return (\n <div key={status} className=\"flex items-center justify-between\">\n <Badge variant={statusVariant(status)}>{status.replace('_', ' ')}</Badge>\n <div className=\"flex items-center gap-3\">\n <div className=\"h-2 w-24 rounded-full bg-bg-sunken overflow-hidden\">\n <div\n className=\"h-full rounded-full bg-action-primary-bg\"\n style={{ width: `${pct}%` }}\n />\n </div>\n <span className=\"text-sm font-medium w-8 text-right\">{count}</span>\n </div>\n </div>\n );\n })}\n </div>\n\n <Separator className=\"my-4\" />\n\n <div>\n <h4 className=\"text-sm font-medium mb-3\">By Condition</h4>\n <div className=\"flex flex-wrap gap-2\">\n {Object.entries(conditionBreakdown).map(([condition, count]) => (\n <Badge key={condition} variant=\"outline\" className=\"capitalize\">\n {condition}: {count}\n </Badge>\n ))}\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n </TabsContent>\n\n {/* Financial Tab */}\n <TabsContent value=\"financial\" className=\"space-y-6 mt-6\">\n <div className=\"grid gap-4 md:grid-cols-4\">\n <Card>\n <CardHeader className=\"pb-2\">\n <CardTitle className=\"text-sm font-medium\">Transactions</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{transactions.length}</div>\n <p className=\"text-xs text-text-secondary\">\n ${transactionsTotal.toLocaleString()} total value\n </p>\n </CardContent>\n </Card>\n <Card>\n <CardHeader className=\"pb-2\">\n <CardTitle className=\"text-sm font-medium\">Budget Allocated</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n ${budgetAllocated.toLocaleString()}\n </div>\n <p className=\"text-xs text-text-secondary\">\n ${budgetSpent.toLocaleString()} spent\n </p>\n </CardContent>\n </Card>\n <Card>\n <CardHeader className=\"pb-2\">\n <CardTitle className=\"text-sm font-medium\">Maintenance Cost</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n ${maintenanceCost.toLocaleString()}\n </div>\n <p className=\"text-xs text-text-secondary\">\n {maintenanceRecords.length} maintenance records\n </p>\n </CardContent>\n </Card>\n <Card>\n <CardHeader className=\"pb-2\">\n <CardTitle className=\"text-sm font-medium\">Outstanding Invoices</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n ${outstandingInvoiceTotal.toLocaleString()}\n </div>\n <p className=\"text-xs text-text-secondary\">\n {outstandingInvoices.length} of {invoices.length} unpaid\n </p>\n </CardContent>\n </Card>\n </div>\n\n {/* Budget Utilization */}\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Wallet className=\"h-4 w-4\" />\n Budget Utilization\n </CardTitle>\n <CardDescription>Allocated vs. spent per budget</CardDescription>\n </CardHeader>\n <CardContent>\n {budgets.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No budgets yet\"\n description=\"Create a budget to track allocation and spend here.\"\n />\n ) : (\n <div className=\"space-y-3\">\n {budgets.map((budget) => {\n const pct =\n budget.allocated > 0\n ? Math.min(100, Math.round((budget.spent / budget.allocated) * 100))\n : 0;\n return (\n <div key={budget.id} className=\"space-y-1\">\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"font-medium\">{budget.name}</span>\n <span className=\"text-text-secondary\">\n ${budget.spent.toLocaleString()} / ${budget.allocated.toLocaleString()}\n </span>\n </div>\n <div className=\"h-2 rounded-full bg-bg-sunken overflow-hidden\">\n <div\n className={`h-full rounded-full ${pct >= 100 ? 'bg-status-error-text' : 'bg-action-primary-bg'}`}\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n );\n })}\n <Separator />\n <div className=\"flex items-center justify-between text-sm font-medium\">\n <span>Remaining across all budgets</span>\n <span>${budgetRemaining.toLocaleString()}</span>\n </div>\n </div>\n )}\n </CardContent>\n </Card>\n\n {/* Recent Transactions */}\n <Card>\n <CardHeader>\n <CardTitle>Recent Transactions</CardTitle>\n </CardHeader>\n <CardContent>\n {transactions.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No transactions\"\n description=\"Asset purchases, leases and disposals will appear here.\"\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b\">\n <th className=\"text-left py-2 font-medium\">Type</th>\n <th className=\"text-left py-2 font-medium\">Description</th>\n <th className=\"text-right py-2 font-medium\">Amount</th>\n <th className=\"text-left py-2 font-medium\">Status</th>\n </tr>\n </thead>\n <tbody>\n {transactions.slice(0, 5).map((txn) => (\n <tr key={txn.id} className=\"border-b hover:bg-bg-sunken/50\">\n <td className=\"py-2 capitalize\">{txn.type}</td>\n <td className=\"py-2 truncate max-w-xs\">{txn.description}</td>\n <td\n className={`py-2 text-right font-medium ${txn.amount < 0 ? 'text-status-error-text' : ''}`}\n >\n ${Math.abs(txn.amount).toLocaleString()}\n </td>\n <td className=\"py-2\">\n <Badge variant=\"outline\">{txn.status}</Badge>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </CardContent>\n </Card>\n </TabsContent>\n\n {/* Operational Tab */}\n <TabsContent value=\"operational\" className=\"space-y-6 mt-6\">\n <div className=\"grid gap-4 md:grid-cols-4\">\n <Card>\n <CardHeader className=\"pb-2\">\n <CardTitle className=\"text-sm font-medium\">Incidents</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{incidents.length}</div>\n <p className=\"text-xs text-text-secondary\">{resolvedIncidents.length} resolved</p>\n </CardContent>\n </Card>\n <Card>\n <CardHeader className=\"pb-2\">\n <CardTitle className=\"text-sm font-medium\">SLA Breaches</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold text-status-error-text\">\n {slaBreached.length}\n </div>\n <p className=\"text-xs text-text-secondary\">\n {((1 - slaBreached.length / Math.max(incidents.length, 1)) * 100).toFixed(1)}%\n within SLA\n </p>\n </CardContent>\n </Card>\n <Card>\n <CardHeader className=\"pb-2\">\n <CardTitle className=\"text-sm font-medium\">Maintenance Done</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{completedMaintenance.length}</div>\n <p className=\"text-xs text-text-secondary\">of {maintenanceRecords.length} total</p>\n </CardContent>\n </Card>\n <Card>\n <CardHeader className=\"pb-2\">\n <CardTitle className=\"text-sm font-medium\">Open Work Orders</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{openWorkOrders.length}</div>\n <p className=\"text-xs text-text-secondary\">of {workOrders.length} total</p>\n </CardContent>\n </Card>\n </div>\n </TabsContent>\n\n {/* Insights Tab */}\n <TabsContent value=\"insights\" className=\"space-y-6 mt-6\">\n <div className=\"grid gap-4 md:grid-cols-3\">\n <Card className=\"border-status-info-border bg-status-info-bg-subtle\">\n <CardHeader>\n <CardTitle className=\"text-status-info-text text-base\">Diversification</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-status-info-text\">\n Your estate spans {Object.keys(categoryBreakdown).length} categories across{' '}\n {assets.length} assets. {activeAssets} are currently active.\n </p>\n </CardContent>\n </Card>\n <Card className=\"border-status-success-border bg-status-success-bg-subtle\">\n <CardHeader>\n <CardTitle className=\"text-status-success-text text-base\">Budget</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-status-success-text\">\n ${budgetSpent.toLocaleString()} spent of ${budgetAllocated.toLocaleString()}{' '}\n allocated, leaving ${budgetRemaining.toLocaleString()} across {budgets.length}{' '}\n budgets.\n </p>\n </CardContent>\n </Card>\n <Card className=\"border-status-warning-border bg-status-warning-bg-subtle\">\n <CardHeader>\n <CardTitle className=\"text-status-warning-text text-base\">Maintenance</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-status-warning-text\">\n {scheduledMaintenance.length} maintenance tasks are scheduled. Total maintenance\n spend is ${maintenanceCost.toLocaleString()}. Stay on top of preventive\n maintenance.\n </p>\n </CardContent>\n </Card>\n </div>\n\n {/* Assets needing attention */}\n <EmphasisPanel>\n <CardHeader className=\"px-0 pt-0\">\n <CardTitle className=\"flex items-center gap-2\">\n <Wrench className=\"h-4 w-4\" />\n Assets Needing Attention\n </CardTitle>\n <CardDescription>\n Assets in poor or damaged condition, or currently under maintenance\n </CardDescription>\n </CardHeader>\n <CardContent className=\"px-0 pb-0\">\n {attentionAssets.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-success\"\n title=\"Everything looks healthy\"\n description=\"No assets are flagged as poor, damaged or in maintenance right now.\"\n />\n ) : (\n <div className=\"space-y-3\">\n {attentionAssets.slice(0, 6).map((asset, i) => (\n <div\n key={asset.id}\n className=\"flex items-center justify-between p-3 rounded-lg border border-border-seam bg-gloss-card\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"w-8 h-8 rounded-full bg-action-primary-bg/10 flex items-center justify-center text-xs font-medium text-primary\">\n {i + 1}\n </div>\n <div>\n <p className=\"text-sm font-medium\">{asset.name}</p>\n <p className=\"text-xs text-text-secondary capitalize\">\n {asset.category.replace('_', ' ')}\n {asset.location ? ` · ${asset.location}` : ''}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <Badge variant=\"outline\" className=\"capitalize\">\n {asset.condition}\n </Badge>\n <Badge variant={statusVariant(asset.status)}>\n {asset.status.replace('_', ' ')}\n </Badge>\n </div>\n </div>\n ))}\n </div>\n )}\n </CardContent>\n </EmphasisPanel>\n </TabsContent>\n </Tabs>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -29,17 +29,16 @@ function T() {
|
|
|
29
29
|
for (let t of r) {
|
|
30
30
|
e[t.category] || (e[t.category] = {
|
|
31
31
|
count: 0,
|
|
32
|
-
totalValue: 0,
|
|
33
|
-
currentValue: 0,
|
|
34
32
|
statuses: {},
|
|
33
|
+
conditions: {},
|
|
35
34
|
topAssets: []
|
|
36
35
|
});
|
|
37
36
|
let n = e[t.category];
|
|
38
|
-
n.count++, n.
|
|
37
|
+
n.count++, n.statuses[t.status] = (n.statuses[t.status] || 0) + 1, n.conditions[t.condition] = (n.conditions[t.condition] || 0) + 1, n.topAssets.push(t);
|
|
39
38
|
}
|
|
40
|
-
for (let t of Object.values(e)) t.topAssets.sort((e, t) => t.
|
|
39
|
+
for (let t of Object.values(e)) t.topAssets.sort((e, t) => e.name.localeCompare(t.name)), t.topAssets = t.topAssets.slice(0, 3);
|
|
41
40
|
return e;
|
|
42
|
-
}, [r]), o = r.length
|
|
41
|
+
}, [r]), o = r.length;
|
|
43
42
|
return /* @__PURE__ */ S("div", {
|
|
44
43
|
className: "space-y-6 p-6",
|
|
45
44
|
children: [
|
|
@@ -54,16 +53,14 @@ function T() {
|
|
|
54
53
|
Object.keys(i).length,
|
|
55
54
|
" categories · ",
|
|
56
55
|
o,
|
|
57
|
-
" total assets
|
|
58
|
-
s.toLocaleString(),
|
|
59
|
-
" total value"
|
|
56
|
+
" total assets"
|
|
60
57
|
]
|
|
61
58
|
})] }), /* @__PURE__ */ S(d, {
|
|
62
59
|
size: "sm",
|
|
63
60
|
children: [/* @__PURE__ */ x(a, { className: "mr-2 h-4 w-4" }), "Add Category"]
|
|
64
61
|
})]
|
|
65
62
|
}),
|
|
66
|
-
/* @__PURE__ */ x(y, { children: "Group and compare your assets by category to see where
|
|
63
|
+
/* @__PURE__ */ x(y, { children: "Group and compare your assets by category to see where they are concentrated. Each card rolls up how many assets you have in a category, their status and condition mix, and a few example items — so you can plan procurement and replacements by category, not one asset at a time." }),
|
|
67
64
|
Object.keys(i).length === 0 ? /* @__PURE__ */ x(v, {
|
|
68
65
|
illustration: "empty-data",
|
|
69
66
|
title: "No categories yet",
|
|
@@ -71,7 +68,7 @@ function T() {
|
|
|
71
68
|
}) : /* @__PURE__ */ x("div", {
|
|
72
69
|
className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
|
|
73
70
|
children: Object.entries(i).map(([e, t], r) => {
|
|
74
|
-
let i = o > 0 ? Math.round(t.count / o * 100) : 0
|
|
71
|
+
let i = o > 0 ? Math.round(t.count / o * 100) : 0;
|
|
75
72
|
return /* @__PURE__ */ S(g, {
|
|
76
73
|
treatment: r === 0 ? "glass" : "plain",
|
|
77
74
|
glow: r === 0,
|
|
@@ -102,20 +99,10 @@ function T() {
|
|
|
102
99
|
className: "flex justify-between text-sm",
|
|
103
100
|
children: [/* @__PURE__ */ x("span", {
|
|
104
101
|
className: "text-text-secondary",
|
|
105
|
-
children: "
|
|
102
|
+
children: "Share of estate"
|
|
106
103
|
}), /* @__PURE__ */ S("span", {
|
|
107
104
|
className: "font-medium",
|
|
108
|
-
children: ["
|
|
109
|
-
})]
|
|
110
|
-
}),
|
|
111
|
-
/* @__PURE__ */ S("div", {
|
|
112
|
-
className: "flex justify-between text-sm",
|
|
113
|
-
children: [/* @__PURE__ */ x("span", {
|
|
114
|
-
className: "text-text-secondary",
|
|
115
|
-
children: "Depreciation"
|
|
116
|
-
}), /* @__PURE__ */ S("span", {
|
|
117
|
-
className: "text-status-error-text",
|
|
118
|
-
children: ["-$", a.toLocaleString()]
|
|
105
|
+
children: [i, "%"]
|
|
119
106
|
})]
|
|
120
107
|
}),
|
|
121
108
|
/* @__PURE__ */ x("div", {
|
|
@@ -144,7 +131,23 @@ function T() {
|
|
|
144
131
|
/* @__PURE__ */ x(b, {}),
|
|
145
132
|
/* @__PURE__ */ S("div", { children: [/* @__PURE__ */ x("p", {
|
|
146
133
|
className: "text-xs font-medium text-text-secondary mb-2",
|
|
147
|
-
children: "
|
|
134
|
+
children: "Condition breakdown"
|
|
135
|
+
}), /* @__PURE__ */ x("div", {
|
|
136
|
+
className: "flex flex-wrap gap-1",
|
|
137
|
+
children: Object.entries(t.conditions).map(([e, t]) => /* @__PURE__ */ S(u, {
|
|
138
|
+
variant: "outline",
|
|
139
|
+
className: "text-[10px]",
|
|
140
|
+
children: [
|
|
141
|
+
e,
|
|
142
|
+
": ",
|
|
143
|
+
t
|
|
144
|
+
]
|
|
145
|
+
}, e))
|
|
146
|
+
})] }),
|
|
147
|
+
/* @__PURE__ */ x(b, {}),
|
|
148
|
+
/* @__PURE__ */ S("div", { children: [/* @__PURE__ */ x("p", {
|
|
149
|
+
className: "text-xs font-medium text-text-secondary mb-2",
|
|
150
|
+
children: "Example assets"
|
|
148
151
|
}), /* @__PURE__ */ x("div", {
|
|
149
152
|
className: "space-y-1.5",
|
|
150
153
|
children: t.topAssets.map((e) => /* @__PURE__ */ S("div", {
|
|
@@ -152,9 +155,9 @@ function T() {
|
|
|
152
155
|
children: [/* @__PURE__ */ x("span", {
|
|
153
156
|
className: "truncate flex-1",
|
|
154
157
|
children: e.name
|
|
155
|
-
}), /* @__PURE__ */
|
|
156
|
-
className: "font-medium ml-2",
|
|
157
|
-
children:
|
|
158
|
+
}), /* @__PURE__ */ x("span", {
|
|
159
|
+
className: "font-medium ml-2 capitalize text-text-secondary",
|
|
160
|
+
children: e.condition
|
|
158
161
|
})]
|
|
159
162
|
}, e.id))
|
|
160
163
|
})] })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssetsAssetCategoriesPage.js","names":[],"sources":["../../../src/assethandler/pages/AssetsAssetCategoriesPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport {\n Badge,\n Button,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n Separator,\n GradientText,\n GlassCard,\n PagePurpose,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport { Box, Monitor, Server, Wifi, Truck, Wrench, Code, Plus } from 'lucide-react';\nimport { useAssetHandlerStore, type Asset } from '../store/assethandlerStore';\n\nconst categoryIcons: Record<string, React.ReactNode> = {\n electronics: <Monitor className=\"h-5 w-5\" />,\n it_hardware: <Monitor className=\"h-5 w-5\" />,\n furniture: <Box className=\"h-5 w-5\" />,\n vehicles: <Truck className=\"h-5 w-5\" />,\n equipment: <Wrench className=\"h-5 w-5\" />,\n software: <Code className=\"h-5 w-5\" />,\n networking: <Wifi className=\"h-5 w-5\" />,\n servers: <Server className=\"h-5 w-5\" />,\n};\n\nconst categoryColors: Record<string, string> = {\n electronics: 'bg-status-info-bg-subtle text-status-info-text',\n it_hardware: 'bg-status-info-bg-subtle text-status-info-text',\n furniture: 'bg-status-warning-bg-subtle text-status-warning-text',\n vehicles: 'bg-status-success-bg-subtle text-status-success-text',\n equipment: 'bg-status-warning-bg-subtle text-status-warning-text',\n software: 'bg-action-primary-bg/10 text-action-primary-bg',\n networking: 'bg-status-info-bg-subtle text-status-info-text',\n servers: 'bg-status-error-bg-subtle text-status-error-text',\n};\n\nexport function AssetsAssetCategoriesPage() {\n const { assets } = useAssetHandlerStore();\n\n const categories = useMemo(() => {\n const catMap: Record<\n string,\n {\n count: number;\n totalValue: number;\n currentValue: number;\n statuses: Record<string, number>;\n topAssets: Asset[];\n }\n > = {};\n for (const asset of assets) {\n if (!catMap[asset.category])\n catMap[asset.category] = {\n count: 0,\n totalValue: 0,\n currentValue: 0,\n statuses: {},\n topAssets: [],\n };\n const cat = catMap[asset.category];\n cat.count++;\n cat.totalValue += asset.value;\n cat.currentValue += asset.currentValue;\n cat.statuses[asset.status] = (cat.statuses[asset.status] || 0) + 1;\n cat.topAssets.push(asset);\n }\n for (const cat of Object.values(catMap)) {\n cat.topAssets.sort((a, b) => b.currentValue - a.currentValue);\n cat.topAssets = cat.topAssets.slice(0, 3);\n }\n return catMap;\n }, [assets]);\n\n const totalAssets = assets.length;\n const totalValue = assets.reduce((s, a) => s + a.currentValue, 0);\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold\">\n <GradientText>Asset Categories</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {Object.keys(categories).length} categories · {totalAssets} total assets ·\n ${totalValue.toLocaleString()} total value\n </p>\n </div>\n <Button size=\"sm\">\n <Plus className=\"mr-2 h-4 w-4\" />\n Add Category\n </Button>\n </div>\n\n <PagePurpose>\n Group and compare your assets by category to see where value and risk are concentrated.\n Each card rolls up how many assets you have in a category, their current value,\n depreciation, status mix and top items — so you can plan procurement and replacements by\n category, not one asset at a time.\n </PagePurpose>\n\n {Object.keys(categories).length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No categories yet\"\n description=\"Add assets and they will be grouped into categories here automatically.\"\n />\n ) : (\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {Object.entries(categories).map(([key, data], idx) => {\n const pct = totalAssets > 0 ? Math.round((data.count / totalAssets) * 100) : 0;\n const depreciation = data.totalValue - data.currentValue;\n return (\n <GlassCard\n key={key}\n treatment={idx === 0 ? 'glass' : 'plain'}\n glow={idx === 0}\n className=\"border-border-seam\"\n >\n <CardHeader className=\"pb-3\">\n <div className=\"flex items-center justify-between\">\n <div className={`p-2 rounded-lg ${categoryColors[key] || 'bg-bg-sunken'}`}>\n {categoryIcons[key] || <Box className=\"h-5 w-5\" />}\n </div>\n <Badge variant=\"outline\">{pct}%</Badge>\n </div>\n <CardTitle className=\"capitalize mt-3\">{key.replace('_', ' ')}</CardTitle>\n <CardDescription>{data.count} assets</CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-secondary\">Current Value</span>\n <span className=\"font-medium\">${data.currentValue.toLocaleString()}</span>\n </div>\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-secondary\">Depreciation</span>\n <span className=\"text-status-error-text\">\n -${depreciation.toLocaleString()}\n </span>\n </div>\n <div className=\"h-2 rounded-full bg-bg-sunken overflow-hidden\">\n <div\n className=\"h-full rounded-full bg-action-primary-bg\"\n style={{ width: `${pct}%` }}\n />\n </div>\n <Separator />\n <div>\n <p className=\"text-xs font-medium text-text-secondary mb-2\">Status breakdown</p>\n <div className=\"flex flex-wrap gap-1\">\n {Object.entries(data.statuses).map(([status, count]) => (\n <Badge key={status} variant=\"secondary\" className=\"text-[10px]\">\n {status.replace('_', ' ')}: {count}\n </Badge>\n ))}\n </div>\n </div>\n <Separator />\n <div>\n <p className=\"text-xs font-medium text-text-secondary mb-2\">Top Assets</p>\n <div className=\"space-y-1.5\">\n {data.topAssets.map((a) => (\n <div key={a.id} className=\"flex items-center justify-between text-xs\">\n <span className=\"truncate flex-1\">{a.name}</span>\n <span className=\"font-medium ml-2\">\n ${a.currentValue.toLocaleString()}\n </span>\n </div>\n ))}\n </div>\n </div>\n </div>\n </CardContent>\n </GlassCard>\n );\n })}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AssetsAssetCategoriesPage.js","names":[],"sources":["../../../src/assethandler/pages/AssetsAssetCategoriesPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport {\n Badge,\n Button,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n Separator,\n GradientText,\n GlassCard,\n PagePurpose,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport { Box, Monitor, Server, Wifi, Truck, Wrench, Code, Plus } from 'lucide-react';\nimport { useAssetHandlerStore, type Asset } from '../store/assethandlerStore';\n\nconst categoryIcons: Record<string, React.ReactNode> = {\n electronics: <Monitor className=\"h-5 w-5\" />,\n it_hardware: <Monitor className=\"h-5 w-5\" />,\n furniture: <Box className=\"h-5 w-5\" />,\n vehicles: <Truck className=\"h-5 w-5\" />,\n equipment: <Wrench className=\"h-5 w-5\" />,\n software: <Code className=\"h-5 w-5\" />,\n networking: <Wifi className=\"h-5 w-5\" />,\n servers: <Server className=\"h-5 w-5\" />,\n};\n\nconst categoryColors: Record<string, string> = {\n electronics: 'bg-status-info-bg-subtle text-status-info-text',\n it_hardware: 'bg-status-info-bg-subtle text-status-info-text',\n furniture: 'bg-status-warning-bg-subtle text-status-warning-text',\n vehicles: 'bg-status-success-bg-subtle text-status-success-text',\n equipment: 'bg-status-warning-bg-subtle text-status-warning-text',\n software: 'bg-action-primary-bg/10 text-action-primary-bg',\n networking: 'bg-status-info-bg-subtle text-status-info-text',\n servers: 'bg-status-error-bg-subtle text-status-error-text',\n};\n\nexport function AssetsAssetCategoriesPage() {\n const { assets } = useAssetHandlerStore();\n\n const categories = useMemo(() => {\n const catMap: Record<\n string,\n {\n count: number;\n statuses: Record<string, number>;\n conditions: Record<string, number>;\n topAssets: Asset[];\n }\n > = {};\n for (const asset of assets) {\n if (!catMap[asset.category])\n catMap[asset.category] = {\n count: 0,\n statuses: {},\n conditions: {},\n topAssets: [],\n };\n const cat = catMap[asset.category];\n cat.count++;\n cat.statuses[asset.status] = (cat.statuses[asset.status] || 0) + 1;\n cat.conditions[asset.condition] = (cat.conditions[asset.condition] || 0) + 1;\n cat.topAssets.push(asset);\n }\n for (const cat of Object.values(catMap)) {\n cat.topAssets.sort((a, b) => a.name.localeCompare(b.name));\n cat.topAssets = cat.topAssets.slice(0, 3);\n }\n return catMap;\n }, [assets]);\n\n const totalAssets = assets.length;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold\">\n <GradientText>Asset Categories</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {Object.keys(categories).length} categories · {totalAssets} total assets\n </p>\n </div>\n <Button size=\"sm\">\n <Plus className=\"mr-2 h-4 w-4\" />\n Add Category\n </Button>\n </div>\n\n <PagePurpose>\n Group and compare your assets by category to see where they are concentrated. Each card\n rolls up how many assets you have in a category, their status and condition mix, and a few\n example items — so you can plan procurement and replacements by category, not one asset at a\n time.\n </PagePurpose>\n\n {Object.keys(categories).length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No categories yet\"\n description=\"Add assets and they will be grouped into categories here automatically.\"\n />\n ) : (\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {Object.entries(categories).map(([key, data], idx) => {\n const pct = totalAssets > 0 ? Math.round((data.count / totalAssets) * 100) : 0;\n return (\n <GlassCard\n key={key}\n treatment={idx === 0 ? 'glass' : 'plain'}\n glow={idx === 0}\n className=\"border-border-seam\"\n >\n <CardHeader className=\"pb-3\">\n <div className=\"flex items-center justify-between\">\n <div className={`p-2 rounded-lg ${categoryColors[key] || 'bg-bg-sunken'}`}>\n {categoryIcons[key] || <Box className=\"h-5 w-5\" />}\n </div>\n <Badge variant=\"outline\">{pct}%</Badge>\n </div>\n <CardTitle className=\"capitalize mt-3\">{key.replace('_', ' ')}</CardTitle>\n <CardDescription>{data.count} assets</CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-secondary\">Share of estate</span>\n <span className=\"font-medium\">{pct}%</span>\n </div>\n <div className=\"h-2 rounded-full bg-bg-sunken overflow-hidden\">\n <div\n className=\"h-full rounded-full bg-action-primary-bg\"\n style={{ width: `${pct}%` }}\n />\n </div>\n <Separator />\n <div>\n <p className=\"text-xs font-medium text-text-secondary mb-2\">Status breakdown</p>\n <div className=\"flex flex-wrap gap-1\">\n {Object.entries(data.statuses).map(([status, count]) => (\n <Badge key={status} variant=\"secondary\" className=\"text-[10px]\">\n {status.replace('_', ' ')}: {count}\n </Badge>\n ))}\n </div>\n </div>\n <Separator />\n <div>\n <p className=\"text-xs font-medium text-text-secondary mb-2\">\n Condition breakdown\n </p>\n <div className=\"flex flex-wrap gap-1\">\n {Object.entries(data.conditions).map(([condition, count]) => (\n <Badge key={condition} variant=\"outline\" className=\"text-[10px]\">\n {condition}: {count}\n </Badge>\n ))}\n </div>\n </div>\n <Separator />\n <div>\n <p className=\"text-xs font-medium text-text-secondary mb-2\">Example assets</p>\n <div className=\"space-y-1.5\">\n {data.topAssets.map((a) => (\n <div key={a.id} className=\"flex items-center justify-between text-xs\">\n <span className=\"truncate flex-1\">{a.name}</span>\n <span className=\"font-medium ml-2 capitalize text-text-secondary\">\n {a.condition}\n </span>\n </div>\n ))}\n </div>\n </div>\n </div>\n </CardContent>\n </GlassCard>\n );\n })}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|