@burdenoff/microfe-assethandler 2026.813.1 → 2026.813.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assethandler/components/charts/BarsChart.js +25 -25
- package/dist/assethandler/components/charts/ChartParts.js +27 -27
- package/dist/assethandler/components/charts/DonutChart.js +17 -17
- package/dist/assethandler/components/charts/ProgressMeter.js +5 -5
- package/dist/assethandler/components/charts/RadialGauge.js +13 -13
- package/dist/assethandler/components/charts/Sparkline.js +27 -27
- package/dist/assethandler/components/charts/TrendChart.js +47 -47
- package/dist/assethandler/components/index.js +1 -1
- package/dist/assethandler/pages/AiPredictionsPage.js +20 -20
- package/dist/assethandler/pages/AnalyticsPage.d.ts.map +1 -1
- package/dist/assethandler/pages/AnalyticsPage.js +133 -133
- package/dist/assethandler/pages/AnalyticsPage.js.map +1 -1
- package/dist/assethandler/pages/AssetsAssetCategoriesPage.d.ts.map +1 -1
- package/dist/assethandler/pages/AssetsAssetCategoriesPage.js +55 -55
- package/dist/assethandler/pages/AssetsAssetCategoriesPage.js.map +1 -1
- package/dist/assethandler/pages/AssetsAssetCreatePage.d.ts.map +1 -1
- package/dist/assethandler/pages/AssetsAssetCreatePage.js +77 -78
- package/dist/assethandler/pages/AssetsAssetCreatePage.js.map +1 -1
- package/dist/assethandler/pages/AssetsAssetDetailPage.d.ts.map +1 -1
- package/dist/assethandler/pages/AssetsAssetDetailPage.js +140 -141
- package/dist/assethandler/pages/AssetsAssetDetailPage.js.map +1 -1
- package/dist/assethandler/pages/AssetsAssetListPage.js +249 -249
- package/dist/assethandler/pages/AssetsAssetListPage.js.map +1 -1
- package/dist/assethandler/pages/AssetsAssetTrackingPage.js +8 -8
- package/dist/assethandler/pages/CmdbBrowserPage.js +34 -34
- package/dist/assethandler/pages/DashboardPage.d.ts.map +1 -1
- package/dist/assethandler/pages/DashboardPage.js +256 -255
- package/dist/assethandler/pages/DashboardPage.js.map +1 -1
- package/dist/assethandler/pages/DiscoveryJobsPage.js +27 -27
- package/dist/assethandler/pages/EsgDashboardPage.js +53 -53
- package/dist/assethandler/pages/FinanceBudgetDetailPage.d.ts.map +1 -1
- package/dist/assethandler/pages/FinanceBudgetDetailPage.js +156 -155
- package/dist/assethandler/pages/FinanceBudgetDetailPage.js.map +1 -1
- package/dist/assethandler/pages/FinanceFinanceHubPage.d.ts.map +1 -1
- package/dist/assethandler/pages/FinanceFinanceHubPage.js +475 -475
- package/dist/assethandler/pages/FinanceFinanceHubPage.js.map +1 -1
- package/dist/assethandler/pages/FinanceInvoiceDetailPage.d.ts.map +1 -1
- package/dist/assethandler/pages/FinanceInvoiceDetailPage.js +141 -140
- package/dist/assethandler/pages/FinanceInvoiceDetailPage.js.map +1 -1
- package/dist/assethandler/pages/ItilChangeManagementPage.js +21 -21
- package/dist/assethandler/pages/ItilIncidentManagementPage.js +34 -34
- package/dist/assethandler/pages/ItilProblemManagementPage.js +17 -17
- package/dist/assethandler/pages/KnowledgeBasePage.js +39 -39
- package/dist/assethandler/pages/MaintenanceMaintenanceSchedulerPage.d.ts.map +1 -1
- package/dist/assethandler/pages/MaintenanceMaintenanceSchedulerPage.js +114 -114
- package/dist/assethandler/pages/MaintenanceMaintenanceSchedulerPage.js.map +1 -1
- package/dist/assethandler/pages/OperationsMaintenanceDetailPage.d.ts.map +1 -1
- package/dist/assethandler/pages/OperationsMaintenanceDetailPage.js +158 -157
- package/dist/assethandler/pages/OperationsMaintenanceDetailPage.js.map +1 -1
- package/dist/assethandler/pages/OperationsOperationsHubPage.d.ts.map +1 -1
- package/dist/assethandler/pages/OperationsOperationsHubPage.js +253 -252
- package/dist/assethandler/pages/OperationsOperationsHubPage.js.map +1 -1
- package/dist/assethandler/pages/PortfolioPortfolioOverviewPage.d.ts.map +1 -1
- package/dist/assethandler/pages/PortfolioPortfolioOverviewPage.js +96 -95
- package/dist/assethandler/pages/PortfolioPortfolioOverviewPage.js.map +1 -1
- package/dist/assethandler/pages/ProvenancePage.js +21 -21
- package/dist/assethandler/pages/ReportsDashboardPage.d.ts.map +1 -1
- package/dist/assethandler/pages/ReportsDashboardPage.js +301 -301
- package/dist/assethandler/pages/ReportsDashboardPage.js.map +1 -1
- package/dist/assethandler/pages/SecuritySecurityHubPage.js +126 -126
- package/dist/assethandler/pages/SecuritySecurityHubPage.js.map +1 -1
- package/dist/assethandler/pages/TransactionsTransactionDetailPage.d.ts.map +1 -1
- package/dist/assethandler/pages/TransactionsTransactionDetailPage.js +112 -113
- package/dist/assethandler/pages/TransactionsTransactionDetailPage.js.map +1 -1
- package/dist/assethandler/pages/TransactionsTransactionListPage.d.ts.map +1 -1
- package/dist/assethandler/pages/TransactionsTransactionListPage.js +136 -137
- package/dist/assethandler/pages/TransactionsTransactionListPage.js.map +1 -1
- package/dist/assethandler/providers/AssetHandlerProvider.d.ts +14 -0
- package/dist/assethandler/providers/AssetHandlerProvider.d.ts.map +1 -1
- package/dist/assethandler/providers/AssetHandlerProvider.js +44 -34
- package/dist/assethandler/providers/AssetHandlerProvider.js.map +1 -1
- package/dist/assethandler/types/index.d.ts +10 -0
- package/dist/assethandler/types/index.d.ts.map +1 -1
- package/dist/assethandler/utils/money.d.ts +34 -0
- package/dist/assethandler/utils/money.d.ts.map +1 -0
- package/dist/assethandler/utils/money.js +27 -0
- package/dist/assethandler/utils/money.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../src/assethandler/pages/AnalyticsPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n Separator,\n GradientText,\n EmphasisPanel,\n PagePurpose,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Box,\n PieChart,\n Activity,\n Wrench,\n AlertTriangle,\n Wallet,\n DollarSign,\n TrendingDown,\n Users,\n Gauge,\n Receipt,\n ClipboardList,\n ShieldCheck,\n} from 'lucide-react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { useAssetHandlerStore } from '../store/assethandlerStore';\nimport { useCopy } from '../hooks/useCopy';\nimport { cn } from '../utils/cn';\nimport {\n StatTile,\n ChartCard,\n DonutChart,\n BarsChart,\n TrendChart,\n RadialGauge,\n ProgressMeter,\n formatCompact,\n formatCurrencyCompact,\n seriesColor,\n type ChartDatum,\n type DonutSlice,\n} from '../components';\n\n// ─── Backend rollups (wspace-assethandler-svc, @rbac-guarded, no args) ────────\ninterface AssetHandlerFinancialSummary {\n totalAssetValue: number;\n totalDepreciation: number;\n totalMaintenanceSpend: number;\n budgetAllocated: number;\n budgetSpent: number;\n invoicesOutstanding: number;\n invoicesOutstandingCount: number;\n transactionExpense: number;\n}\n\ninterface AssetHandlerStatusCount {\n status: string;\n count: number;\n}\n\ninterface AssetHandlerUtilizationReport {\n totalAssets: number;\n assignedAssets: number;\n unassignedAssets: number;\n idleAssets: number;\n overdueAssignments: number;\n byStatus: AssetHandlerStatusCount[];\n}\n\nconst ANALYTICS_ROLLUPS_QUERY = gql`\n query AssetHandlerAnalyticsRollups {\n financialSummary {\n totalAssetValue\n totalDepreciation\n totalMaintenanceSpend\n budgetAllocated\n budgetSpent\n invoicesOutstanding\n invoicesOutstandingCount\n transactionExpense\n }\n utilizationReport {\n totalAssets\n assignedAssets\n unassignedAssets\n idleAssets\n overdueAssignments\n byStatus {\n status\n count\n }\n }\n }\n`;\n\nconst usd = formatCurrencyCompact('USD');\n\nfunction titleCase(value: string): string {\n return value.replace(/_/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\nfunction parseDate(value: string | undefined | null): Date | null {\n if (!value) return null;\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n}\nfunction monthKey(date: Date): string {\n return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;\n}\nfunction monthLabel(key: string): string {\n const [year, month] = key.split('-').map(Number);\n return new Date(year, month - 1, 1).toLocaleString(undefined, { month: 'short' });\n}\n\ntype PillTone = 'success' | 'warning' | 'error' | 'info' | 'neutral';\nconst PILL_CLASS: Record<PillTone, string> = {\n success: 'bg-status-success-bg-subtle text-status-success-text',\n warning: 'bg-status-warning-bg-subtle text-status-warning-text',\n error: 'bg-status-error-bg-subtle text-status-error-text',\n info: 'bg-status-info-bg-subtle text-status-info-text',\n neutral: 'bg-bg-sunken text-text-secondary',\n};\nfunction StatusPill({ tone, children }: { tone: PillTone; children: React.ReactNode }) {\n return (\n <span\n className={cn(\n 'inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium capitalize whitespace-nowrap',\n PILL_CLASS[tone]\n )}\n >\n {children}\n </span>\n );\n}\nconst txStatusTone = (status: string): PillTone =>\n status === 'completed' || status === 'approved'\n ? 'success'\n : status === 'rejected'\n ? 'error'\n : status === 'pending'\n ? 'warning'\n : 'neutral';\n\nexport function AnalyticsPage() {\n const tc = useCopy();\n const { assets, transactions, incidents, maintenanceRecords, budgets, invoices, workOrders } =\n useAssetHandlerStore();\n\n const { data: rollupData } = useQuery<{\n financialSummary: AssetHandlerFinancialSummary;\n utilizationReport: AssetHandlerUtilizationReport;\n }>(ANALYTICS_ROLLUPS_QUERY, { fetchPolicy: 'cache-and-network', errorPolicy: 'all' });\n const financialSummary = rollupData?.financialSummary;\n const utilizationReport = rollupData?.utilizationReport;\n\n // ─── Asset aggregations (count-based) ────────────────────────────────────────\n const categoryDonut = useMemo<DonutSlice[]>(() => {\n const counts = assets.reduce<Record<string, number>>((acc, a) => {\n const cat = titleCase(a.category);\n acc[cat] = (acc[cat] || 0) + 1;\n return acc;\n }, {});\n return Object.entries(counts)\n .sort((a, b) => b[1] - a[1])\n .map(([name, value]) => ({ name, value }));\n }, [assets]);\n\n const categoryBars = useMemo<ChartDatum[]>(\n () => categoryDonut.map((s) => ({ category: s.name, count: s.value })),\n [categoryDonut]\n );\n\n const statusDonut = useMemo<DonutSlice[]>(() => {\n const counts = assets.reduce<Record<string, number>>((acc, a) => {\n acc[a.status] = (acc[a.status] || 0) + 1;\n return acc;\n }, {});\n const toneFor: Record<string, PillTone> = {\n active: 'success',\n maintenance: 'warning',\n retired: 'neutral',\n inactive: 'neutral',\n disposed: 'error',\n };\n const toneToken: Record<PillTone, string> = {\n success: 'var(--color-status-success-text)',\n warning: 'var(--color-status-warning-text)',\n error: 'var(--color-status-error-text)',\n info: 'var(--color-status-info-text)',\n neutral: 'var(--color-text-muted)',\n };\n return Object.entries(counts)\n .sort((a, b) => b[1] - a[1])\n .map(([status, value]) => ({\n name: titleCase(status),\n value,\n color: toneToken[toneFor[status] ?? 'neutral'],\n }));\n }, [assets]);\n\n const conditionBars = useMemo<ChartDatum[]>(() => {\n const order = ['excellent', 'good', 'fair', 'poor', 'damaged'];\n const counts = assets.reduce<Record<string, number>>((acc, a) => {\n acc[a.condition] = (acc[a.condition] || 0) + 1;\n return acc;\n }, {});\n return order\n .filter((c) => counts[c])\n .map((c) => ({ condition: titleCase(c), count: counts[c] }))\n .concat(\n Object.entries(counts)\n .filter(([c]) => !order.includes(c))\n .map(([c, count]) => ({ condition: titleCase(c), count }))\n );\n }, [assets]);\n\n const totalAssets = assets.length;\n const activeAssets = assets.filter((a) => a.status === 'active').length;\n const distinctCategories = categoryDonut.length;\n const attentionAssets = assets.filter(\n (a) => a.condition === 'poor' || a.condition === 'damaged' || a.status === 'maintenance'\n );\n\n // ─── Financial aggregations ──────────────────────────────────────────────────\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(\n (i) => i.status !== 'paid' && i.status !== 'cancelled'\n );\n const outstandingInvoiceTotal = outstandingInvoices.reduce((s, i) => s + i.amount, 0);\n\n const budgetBars = useMemo<ChartDatum[]>(\n () =>\n budgets.map((b) => ({\n name: b.name,\n allocated: b.allocated,\n spent: b.spent,\n })),\n [budgets]\n );\n\n const spendTrend = useMemo<ChartDatum[]>(() => {\n const byMonth = new Map<string, number>();\n for (const tx of transactions) {\n const d = parseDate(tx.date);\n if (!d) continue;\n const key = monthKey(d);\n byMonth.set(key, (byMonth.get(key) ?? 0) + Math.abs(tx.amount));\n }\n return [...byMonth.keys()]\n .sort()\n .map((k) => ({ month: monthLabel(k), spend: Math.round(byMonth.get(k) ?? 0) }));\n }, [transactions]);\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 slaCompliancePct =\n incidents.length > 0 ? Math.round((1 - slaBreached.length / incidents.length) * 100) : 100;\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 const utilStatusBars = useMemo<ChartDatum[]>(\n () =>\n (utilizationReport?.byStatus ?? []).map((s) => ({\n status: titleCase(s.status),\n count: s.count,\n })),\n [utilizationReport]\n );\n\n const workOrderDonut = useMemo<DonutSlice[]>(() => {\n const counts = workOrders.reduce<Record<string, number>>((acc, w) => {\n acc[w.status] = (acc[w.status] || 0) + 1;\n return acc;\n }, {});\n return Object.entries(counts)\n .sort((a, b) => b[1] - a[1])\n .map(([name, value]) => ({ name: titleCase(name), value }));\n }, [workOrders]);\n\n const maintenanceCostTrend = useMemo<ChartDatum[]>(() => {\n const byMonth = new Map<string, number>();\n for (const m of maintenanceRecords) {\n const d = parseDate(m.completedDate ?? m.scheduledDate);\n const cost = m.actualCost ?? m.estimatedCost;\n if (!d || cost === undefined) continue;\n const key = monthKey(d);\n byMonth.set(key, (byMonth.get(key) ?? 0) + cost);\n }\n return [...byMonth.keys()]\n .sort()\n .map((k) => ({ month: monthLabel(k), cost: Math.round(byMonth.get(k) ?? 0) }));\n }, [maintenanceRecords]);\n\n const financialTiles = financialSummary\n ? [\n { label: 'Total Asset Value', value: financialSummary.totalAssetValue, icon: DollarSign },\n {\n label: 'Total Depreciation',\n value: financialSummary.totalDepreciation,\n icon: TrendingDown,\n },\n {\n label: 'Maintenance Spend',\n value: financialSummary.totalMaintenanceSpend,\n icon: Wrench,\n },\n {\n label: 'Transaction Expense',\n value: financialSummary.transactionExpense,\n icon: Receipt,\n },\n { label: 'Budget Allocated', value: financialSummary.budgetAllocated, icon: Wallet },\n { label: 'Budget Spent', value: financialSummary.budgetSpent, icon: Wallet },\n {\n label: 'Invoices Outstanding',\n value: financialSummary.invoicesOutstanding,\n icon: AlertTriangle,\n },\n ]\n : [];\n\n return (\n <div className=\"space-y-6 p-4 sm:p-6\">\n {/* Header */}\n <div>\n <h1 className=\"text-2xl font-bold sm:text-3xl\">\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\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 className=\"max-w-full justify-start overflow-x-auto\">\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 <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <StatTile\n hero\n label={tc('assethandler.analytics.metrics.totalAssets', 'Total {{assets}}')}\n value={totalAssets}\n format={formatCompact}\n icon={Box}\n />\n <StatTile label=\"Categories\" value={distinctCategories} icon={PieChart} />\n <StatTile label=\"Needs Attention\" value={attentionAssets.length} icon={AlertTriangle} />\n <StatTile\n label=\"Open Incidents\"\n value={incidents.length - resolvedIncidents.length}\n icon={Activity}\n />\n </div>\n\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Assets by Category\"\n subtitle=\"Computed from real data\"\n icon={PieChart}\n >\n <BarsChart\n data={categoryBars}\n xKey=\"category\"\n series={[{ key: 'count', label: 'Assets' }]}\n orientation=\"horizontal\"\n height={Math.max(220, categoryBars.length * 40)}\n categoryWidth={120}\n emptyLabel=\"No assets yet\"\n ariaLabel=\"Assets by category\"\n />\n </ChartCard>\n\n <ChartCard title=\"Assets by Status\" subtitle=\"Computed from real data\" icon={Activity}>\n <DonutChart\n data={statusDonut}\n centerLabel={tc('assethandler.analytics.donut.centerLabel', '{{assets}}')}\n centerValue={totalAssets}\n height={260}\n emptyLabel=\"No assets yet\"\n ariaLabel=\"Assets by status\"\n />\n </ChartCard>\n </div>\n\n <ChartCard title=\"Asset Condition Mix\" subtitle=\"Computed from real data\" icon={Gauge}>\n <BarsChart\n data={conditionBars}\n xKey=\"condition\"\n series={[{ key: 'count', label: 'Assets' }]}\n height={260}\n emptyLabel=\"No assets yet\"\n ariaLabel=\"Assets by condition\"\n />\n </ChartCard>\n </TabsContent>\n\n {/* ─── Financial Tab ────────────────────────────────────────────────── */}\n <TabsContent value=\"financial\" className=\"space-y-6 mt-6\">\n {financialSummary ? (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {financialTiles.slice(0, 4).map((tile, i) => (\n <StatTile\n key={tile.label}\n hero={i === 0}\n label={tile.label}\n value={tile.value}\n format={usd}\n icon={tile.icon}\n />\n ))}\n </div>\n ) : (\n <Card>\n <CardContent className=\"py-8\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"Financial summary unavailable\"\n description=\"Portfolio valuation and budget rollups will appear once financial data is recorded.\"\n />\n </CardContent>\n </Card>\n )}\n\n {financialSummary ? (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {financialTiles.slice(4).map((tile) => (\n <StatTile\n key={tile.label}\n label={tile.label}\n value={tile.value}\n format={usd}\n icon={tile.icon}\n delta={\n tile.label === 'Invoices Outstanding'\n ? `${financialSummary.invoicesOutstandingCount} unpaid`\n : undefined\n }\n deltaTone=\"neutral\"\n />\n ))}\n </div>\n ) : null}\n\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Budget: Allocated vs Spent\"\n subtitle=\"Per budget, from real data\"\n icon={Wallet}\n >\n {budgetBars.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 <BarsChart\n data={budgetBars}\n xKey=\"name\"\n series={[\n { key: 'allocated', label: 'Allocated', color: seriesColor(1) },\n { key: 'spent', label: 'Spent' },\n ]}\n height={Math.max(220, budgetBars.length * 56)}\n orientation=\"horizontal\"\n categoryWidth={120}\n valueFormat={usd}\n ariaLabel=\"Budget allocated versus spent\"\n />\n )}\n </ChartCard>\n\n <ChartCard title=\"Spend Over Time\" subtitle=\"Monthly transaction spend\" icon={Receipt}>\n {spendTrend.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No transactions yet\"\n description=\"Recorded purchases, leases and disposals will chart here.\"\n />\n ) : (\n <TrendChart\n data={spendTrend}\n xKey=\"month\"\n series={[{ key: 'spend', label: 'Spend' }]}\n valueFormat={usd}\n height={260}\n ariaLabel=\"Monthly transaction spend\"\n />\n )}\n </ChartCard>\n </div>\n\n {/* Budget utilization meters */}\n <ChartCard\n title=\"Budget Utilization\"\n subtitle=\"Allocated vs. spent per budget\"\n icon={Wallet}\n >\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-4\">\n {budgets.map((budget) => (\n <ProgressMeter\n key={budget.id}\n label={budget.name}\n value={budget.spent}\n max={budget.allocated}\n format={(v) => `$${Math.round(v).toLocaleString()}`}\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 className=\"tabular-nums\">${budgetRemaining.toLocaleString()}</span>\n </div>\n </div>\n )}\n </ChartCard>\n\n {/* Financial quick stats */}\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatTile\n label=\"Transactions\"\n value={transactions.length}\n icon={Receipt}\n delta={`$${transactionsTotal.toLocaleString()} value`}\n deltaTone=\"neutral\"\n />\n <StatTile\n label=\"Budget Allocated\"\n value={budgetAllocated}\n format={usd}\n icon={Wallet}\n delta={`$${budgetSpent.toLocaleString()} spent`}\n deltaTone=\"neutral\"\n />\n <StatTile label=\"Maintenance Cost\" value={maintenanceCost} format={usd} icon={Wrench} />\n <StatTile\n label=\"Outstanding Invoices\"\n value={outstandingInvoiceTotal}\n format={usd}\n icon={AlertTriangle}\n delta={`${outstandingInvoices.length} of ${invoices.length}`}\n deltaTone=\"neutral\"\n />\n </div>\n\n {/* Recent transactions */}\n <ChartCard title=\"Recent Transactions\" icon={Receipt}>\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 border-border-seam text-text-secondary\">\n <th className=\"py-2 text-left font-medium\">Type</th>\n <th className=\"py-2 text-left font-medium\">Description</th>\n <th className=\"py-2 text-right font-medium\">Amount</th>\n <th className=\"py-2 text-left font-medium\">Status</th>\n </tr>\n </thead>\n <tbody>\n {transactions.slice(0, 6).map((txn) => (\n <tr\n key={txn.id}\n className=\"border-b border-border-seam hover:bg-bg-sunken/50\"\n >\n <td className=\"py-2.5 capitalize\">{txn.type}</td>\n <td className=\"max-w-xs truncate py-2.5\">{txn.description}</td>\n <td\n className={cn(\n 'py-2.5 text-right font-medium tabular-nums',\n txn.amount < 0 && 'text-status-error-text'\n )}\n >\n ${Math.abs(txn.amount).toLocaleString()}\n </td>\n <td className=\"py-2.5\">\n <StatusPill tone={txStatusTone(txn.status)}>{txn.status}</StatusPill>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </ChartCard>\n </TabsContent>\n\n {/* ─── Operational Tab ──────────────────────────────────────────────── */}\n <TabsContent value=\"operational\" className=\"space-y-6 mt-6\">\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-5\">\n <StatTile\n label={tc('assethandler.analytics.metrics.totalAssets', 'Total {{assets}}')}\n value={utilizationReport?.totalAssets ?? totalAssets}\n icon={Box}\n />\n <StatTile\n label=\"Assigned\"\n value={utilizationReport?.assignedAssets ?? '—'}\n icon={Users}\n />\n <StatTile\n label=\"Unassigned\"\n value={utilizationReport?.unassignedAssets ?? '—'}\n icon={Box}\n />\n <StatTile label=\"Idle\" value={utilizationReport?.idleAssets ?? '—'} icon={Activity} />\n <StatTile\n label=\"Overdue\"\n value={utilizationReport?.overdueAssignments ?? '—'}\n icon={AlertTriangle}\n delta={\n utilizationReport && utilizationReport.overdueAssignments > 0\n ? 'action needed'\n : undefined\n }\n deltaTone=\"negative\"\n deltaDirection=\"up\"\n />\n </div>\n\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <ChartCard title=\"Assets by Status\" subtitle=\"Assignment coverage\" icon={Gauge}>\n <BarsChart\n data={utilStatusBars}\n xKey=\"status\"\n series={[{ key: 'count', label: 'Assets' }]}\n orientation=\"horizontal\"\n height={Math.max(200, utilStatusBars.length * 40)}\n categoryWidth={120}\n emptyLabel=\"No utilization data\"\n ariaLabel=\"Assets by status\"\n />\n </ChartCard>\n\n <ChartCard title=\"Work Orders by Status\" subtitle=\"From real data\" icon={ClipboardList}>\n <DonutChart\n data={workOrderDonut}\n centerLabel=\"Work orders\"\n centerValue={workOrders.length}\n height={260}\n emptyLabel=\"No work orders\"\n ariaLabel=\"Work orders by status\"\n />\n </ChartCard>\n </div>\n\n <ChartCard\n title=\"Maintenance Cost Over Time\"\n subtitle=\"Monthly recorded maintenance cost\"\n icon={Wrench}\n >\n {maintenanceCostTrend.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No maintenance cost yet\"\n description=\"Once maintenance records carry cost, monthly spend will chart here.\"\n />\n ) : (\n <TrendChart\n data={maintenanceCostTrend}\n xKey=\"month\"\n series={[{ key: 'cost', label: 'Cost' }]}\n valueFormat={usd}\n height={260}\n ariaLabel=\"Monthly maintenance cost\"\n />\n )}\n </ChartCard>\n\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatTile\n label=\"Incidents\"\n value={incidents.length}\n icon={Activity}\n delta={`${resolvedIncidents.length} resolved`}\n deltaTone=\"neutral\"\n />\n <StatTile\n label=\"SLA Breaches\"\n value={slaBreached.length}\n icon={AlertTriangle}\n delta={slaBreached.length > 0 ? 'over SLA' : 'all within SLA'}\n deltaTone={slaBreached.length > 0 ? 'negative' : 'positive'}\n deltaDirection={slaBreached.length > 0 ? 'up' : 'neutral'}\n />\n <StatTile\n label=\"Maintenance Done\"\n value={completedMaintenance.length}\n icon={Wrench}\n delta={`of ${maintenanceRecords.length}`}\n deltaTone=\"neutral\"\n />\n <StatTile\n label=\"Open Work Orders\"\n value={openWorkOrders.length}\n icon={ClipboardList}\n delta={`of ${workOrders.length}`}\n deltaTone=\"neutral\"\n />\n </div>\n </TabsContent>\n\n {/* ─── Insights Tab ─────────────────────────────────────────────────── */}\n <TabsContent value=\"insights\" className=\"space-y-6 mt-6\">\n <div className=\"grid gap-6 lg:grid-cols-3\">\n <ChartCard\n title=\"SLA Compliance\"\n subtitle=\"Incidents resolved within SLA\"\n icon={ShieldCheck}\n >\n <RadialGauge\n value={slaCompliancePct}\n label=\"within SLA\"\n sublabel={`${slaBreached.length} of ${incidents.length} breached`}\n format={(v) => `${Math.round(v)}%`}\n height={220}\n ariaLabel=\"SLA compliance percentage\"\n />\n </ChartCard>\n\n <div className=\"lg:col-span-2 grid gap-4 sm: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 {distinctCategories} categories across {totalAssets} assets.{' '}\n {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} tasks scheduled. Total maintenance spend is $\n {maintenanceCost.toLocaleString()}. Stay on top of preventive maintenance.\n </p>\n </CardContent>\n </Card>\n </div>\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 </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 gap-3 rounded-lg border border-border-seam bg-gloss-card p-3\"\n >\n <div className=\"flex min-w-0 items-center gap-3\">\n <div className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/10 text-xs font-medium text-primary\">\n {i + 1}\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium\">{asset.name}</p>\n <p className=\"truncate 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 shrink-0 items-center gap-2\">\n <StatusPill tone={asset.condition === 'damaged' ? 'error' : 'warning'}>\n {asset.condition}\n </StatusPill>\n <StatusPill tone={asset.status === 'active' ? 'success' : 'warning'}>\n {asset.status.replace('_', ' ')}\n </StatusPill>\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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"}
|
|
1
|
+
{"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../src/assethandler/pages/AnalyticsPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { useAssetMoney } from '../providers/AssetHandlerProvider';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n Separator,\n GradientText,\n EmphasisPanel,\n PagePurpose,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Box,\n PieChart,\n Activity,\n Wrench,\n AlertTriangle,\n Wallet,\n DollarSign,\n TrendingDown,\n Users,\n Gauge,\n Receipt,\n ClipboardList,\n ShieldCheck,\n} from 'lucide-react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { useAssetHandlerStore } from '../store/assethandlerStore';\nimport { useCopy } from '../hooks/useCopy';\nimport { cn } from '../utils/cn';\nimport {\n StatTile,\n ChartCard,\n DonutChart,\n BarsChart,\n TrendChart,\n RadialGauge,\n ProgressMeter,\n formatCompact,\n seriesColor,\n type ChartDatum,\n type DonutSlice,\n} from '../components';\n\n// ─── Backend rollups (wspace-assethandler-svc, @rbac-guarded, no args) ────────\ninterface AssetHandlerFinancialSummary {\n totalAssetValue: number;\n totalDepreciation: number;\n totalMaintenanceSpend: number;\n budgetAllocated: number;\n budgetSpent: number;\n invoicesOutstanding: number;\n invoicesOutstandingCount: number;\n transactionExpense: number;\n}\n\ninterface AssetHandlerStatusCount {\n status: string;\n count: number;\n}\n\ninterface AssetHandlerUtilizationReport {\n totalAssets: number;\n assignedAssets: number;\n unassignedAssets: number;\n idleAssets: number;\n overdueAssignments: number;\n byStatus: AssetHandlerStatusCount[];\n}\n\nconst ANALYTICS_ROLLUPS_QUERY = gql`\n query AssetHandlerAnalyticsRollups {\n financialSummary {\n totalAssetValue\n totalDepreciation\n totalMaintenanceSpend\n budgetAllocated\n budgetSpent\n invoicesOutstanding\n invoicesOutstandingCount\n transactionExpense\n }\n utilizationReport {\n totalAssets\n assignedAssets\n unassignedAssets\n idleAssets\n overdueAssignments\n byStatus {\n status\n count\n }\n }\n }\n`;\n\nfunction titleCase(value: string): string {\n return value.replace(/_/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\nfunction parseDate(value: string | undefined | null): Date | null {\n if (!value) return null;\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n}\nfunction monthKey(date: Date): string {\n return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;\n}\nfunction monthLabel(key: string): string {\n const [year, month] = key.split('-').map(Number);\n return new Date(year, month - 1, 1).toLocaleString(undefined, { month: 'short' });\n}\n\ntype PillTone = 'success' | 'warning' | 'error' | 'info' | 'neutral';\nconst PILL_CLASS: Record<PillTone, string> = {\n success: 'bg-status-success-bg-subtle text-status-success-text',\n warning: 'bg-status-warning-bg-subtle text-status-warning-text',\n error: 'bg-status-error-bg-subtle text-status-error-text',\n info: 'bg-status-info-bg-subtle text-status-info-text',\n neutral: 'bg-bg-sunken text-text-secondary',\n};\nfunction StatusPill({ tone, children }: { tone: PillTone; children: React.ReactNode }) {\n return (\n <span\n className={cn(\n 'inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium capitalize whitespace-nowrap',\n PILL_CLASS[tone]\n )}\n >\n {children}\n </span>\n );\n}\nconst txStatusTone = (status: string): PillTone =>\n status === 'completed' || status === 'approved'\n ? 'success'\n : status === 'rejected'\n ? 'error'\n : status === 'pending'\n ? 'warning'\n : 'neutral';\n\nexport function AnalyticsPage() {\n const { compact: usd, format: money } = useAssetMoney();\n const tc = useCopy();\n const { assets, transactions, incidents, maintenanceRecords, budgets, invoices, workOrders } =\n useAssetHandlerStore();\n\n const { data: rollupData } = useQuery<{\n financialSummary: AssetHandlerFinancialSummary;\n utilizationReport: AssetHandlerUtilizationReport;\n }>(ANALYTICS_ROLLUPS_QUERY, { fetchPolicy: 'cache-and-network', errorPolicy: 'all' });\n const financialSummary = rollupData?.financialSummary;\n const utilizationReport = rollupData?.utilizationReport;\n\n // ─── Asset aggregations (count-based) ────────────────────────────────────────\n const categoryDonut = useMemo<DonutSlice[]>(() => {\n const counts = assets.reduce<Record<string, number>>((acc, a) => {\n const cat = titleCase(a.category);\n acc[cat] = (acc[cat] || 0) + 1;\n return acc;\n }, {});\n return Object.entries(counts)\n .sort((a, b) => b[1] - a[1])\n .map(([name, value]) => ({ name, value }));\n }, [assets]);\n\n const categoryBars = useMemo<ChartDatum[]>(\n () => categoryDonut.map((s) => ({ category: s.name, count: s.value })),\n [categoryDonut]\n );\n\n const statusDonut = useMemo<DonutSlice[]>(() => {\n const counts = assets.reduce<Record<string, number>>((acc, a) => {\n acc[a.status] = (acc[a.status] || 0) + 1;\n return acc;\n }, {});\n const toneFor: Record<string, PillTone> = {\n active: 'success',\n maintenance: 'warning',\n retired: 'neutral',\n inactive: 'neutral',\n disposed: 'error',\n };\n const toneToken: Record<PillTone, string> = {\n success: 'var(--color-status-success-text)',\n warning: 'var(--color-status-warning-text)',\n error: 'var(--color-status-error-text)',\n info: 'var(--color-status-info-text)',\n neutral: 'var(--color-text-muted)',\n };\n return Object.entries(counts)\n .sort((a, b) => b[1] - a[1])\n .map(([status, value]) => ({\n name: titleCase(status),\n value,\n color: toneToken[toneFor[status] ?? 'neutral'],\n }));\n }, [assets]);\n\n const conditionBars = useMemo<ChartDatum[]>(() => {\n const order = ['excellent', 'good', 'fair', 'poor', 'damaged'];\n const counts = assets.reduce<Record<string, number>>((acc, a) => {\n acc[a.condition] = (acc[a.condition] || 0) + 1;\n return acc;\n }, {});\n return order\n .filter((c) => counts[c])\n .map((c) => ({ condition: titleCase(c), count: counts[c] }))\n .concat(\n Object.entries(counts)\n .filter(([c]) => !order.includes(c))\n .map(([c, count]) => ({ condition: titleCase(c), count }))\n );\n }, [assets]);\n\n const totalAssets = assets.length;\n const activeAssets = assets.filter((a) => a.status === 'active').length;\n const distinctCategories = categoryDonut.length;\n const attentionAssets = assets.filter(\n (a) => a.condition === 'poor' || a.condition === 'damaged' || a.status === 'maintenance'\n );\n\n // ─── Financial aggregations ──────────────────────────────────────────────────\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(\n (i) => i.status !== 'paid' && i.status !== 'cancelled'\n );\n const outstandingInvoiceTotal = outstandingInvoices.reduce((s, i) => s + i.amount, 0);\n\n const budgetBars = useMemo<ChartDatum[]>(\n () =>\n budgets.map((b) => ({\n name: b.name,\n allocated: b.allocated,\n spent: b.spent,\n })),\n [budgets]\n );\n\n const spendTrend = useMemo<ChartDatum[]>(() => {\n const byMonth = new Map<string, number>();\n for (const tx of transactions) {\n const d = parseDate(tx.date);\n if (!d) continue;\n const key = monthKey(d);\n byMonth.set(key, (byMonth.get(key) ?? 0) + Math.abs(tx.amount));\n }\n return [...byMonth.keys()]\n .sort()\n .map((k) => ({ month: monthLabel(k), spend: Math.round(byMonth.get(k) ?? 0) }));\n }, [transactions]);\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 slaCompliancePct =\n incidents.length > 0 ? Math.round((1 - slaBreached.length / incidents.length) * 100) : 100;\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 const utilStatusBars = useMemo<ChartDatum[]>(\n () =>\n (utilizationReport?.byStatus ?? []).map((s) => ({\n status: titleCase(s.status),\n count: s.count,\n })),\n [utilizationReport]\n );\n\n const workOrderDonut = useMemo<DonutSlice[]>(() => {\n const counts = workOrders.reduce<Record<string, number>>((acc, w) => {\n acc[w.status] = (acc[w.status] || 0) + 1;\n return acc;\n }, {});\n return Object.entries(counts)\n .sort((a, b) => b[1] - a[1])\n .map(([name, value]) => ({ name: titleCase(name), value }));\n }, [workOrders]);\n\n const maintenanceCostTrend = useMemo<ChartDatum[]>(() => {\n const byMonth = new Map<string, number>();\n for (const m of maintenanceRecords) {\n const d = parseDate(m.completedDate ?? m.scheduledDate);\n const cost = m.actualCost ?? m.estimatedCost;\n if (!d || cost === undefined) continue;\n const key = monthKey(d);\n byMonth.set(key, (byMonth.get(key) ?? 0) + cost);\n }\n return [...byMonth.keys()]\n .sort()\n .map((k) => ({ month: monthLabel(k), cost: Math.round(byMonth.get(k) ?? 0) }));\n }, [maintenanceRecords]);\n\n const financialTiles = financialSummary\n ? [\n { label: 'Total Asset Value', value: financialSummary.totalAssetValue, icon: DollarSign },\n {\n label: 'Total Depreciation',\n value: financialSummary.totalDepreciation,\n icon: TrendingDown,\n },\n {\n label: 'Maintenance Spend',\n value: financialSummary.totalMaintenanceSpend,\n icon: Wrench,\n },\n {\n label: 'Transaction Expense',\n value: financialSummary.transactionExpense,\n icon: Receipt,\n },\n { label: 'Budget Allocated', value: financialSummary.budgetAllocated, icon: Wallet },\n { label: 'Budget Spent', value: financialSummary.budgetSpent, icon: Wallet },\n {\n label: 'Invoices Outstanding',\n value: financialSummary.invoicesOutstanding,\n icon: AlertTriangle,\n },\n ]\n : [];\n\n return (\n <div className=\"space-y-6 p-4 sm:p-6\">\n {/* Header */}\n <div>\n <h1 className=\"text-2xl font-bold sm:text-3xl\">\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\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 className=\"max-w-full justify-start overflow-x-auto\">\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 <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <StatTile\n hero\n label={tc('assethandler.analytics.metrics.totalAssets', 'Total {{assets}}')}\n value={totalAssets}\n format={formatCompact}\n icon={Box}\n />\n <StatTile label=\"Categories\" value={distinctCategories} icon={PieChart} />\n <StatTile label=\"Needs Attention\" value={attentionAssets.length} icon={AlertTriangle} />\n <StatTile\n label=\"Open Incidents\"\n value={incidents.length - resolvedIncidents.length}\n icon={Activity}\n />\n </div>\n\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Assets by Category\"\n subtitle=\"Computed from real data\"\n icon={PieChart}\n >\n <BarsChart\n data={categoryBars}\n xKey=\"category\"\n series={[{ key: 'count', label: 'Assets' }]}\n orientation=\"horizontal\"\n height={Math.max(220, categoryBars.length * 40)}\n categoryWidth={120}\n emptyLabel=\"No assets yet\"\n ariaLabel=\"Assets by category\"\n />\n </ChartCard>\n\n <ChartCard title=\"Assets by Status\" subtitle=\"Computed from real data\" icon={Activity}>\n <DonutChart\n data={statusDonut}\n centerLabel={tc('assethandler.analytics.donut.centerLabel', '{{assets}}')}\n centerValue={totalAssets}\n height={260}\n emptyLabel=\"No assets yet\"\n ariaLabel=\"Assets by status\"\n />\n </ChartCard>\n </div>\n\n <ChartCard title=\"Asset Condition Mix\" subtitle=\"Computed from real data\" icon={Gauge}>\n <BarsChart\n data={conditionBars}\n xKey=\"condition\"\n series={[{ key: 'count', label: 'Assets' }]}\n height={260}\n emptyLabel=\"No assets yet\"\n ariaLabel=\"Assets by condition\"\n />\n </ChartCard>\n </TabsContent>\n\n {/* ─── Financial Tab ────────────────────────────────────────────────── */}\n <TabsContent value=\"financial\" className=\"space-y-6 mt-6\">\n {financialSummary ? (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {financialTiles.slice(0, 4).map((tile, i) => (\n <StatTile\n key={tile.label}\n hero={i === 0}\n label={tile.label}\n value={tile.value}\n format={usd}\n icon={tile.icon}\n />\n ))}\n </div>\n ) : (\n <Card>\n <CardContent className=\"py-8\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"Financial summary unavailable\"\n description=\"Portfolio valuation and budget rollups will appear once financial data is recorded.\"\n />\n </CardContent>\n </Card>\n )}\n\n {financialSummary ? (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {financialTiles.slice(4).map((tile) => (\n <StatTile\n key={tile.label}\n label={tile.label}\n value={tile.value}\n format={usd}\n icon={tile.icon}\n delta={\n tile.label === 'Invoices Outstanding'\n ? `${financialSummary.invoicesOutstandingCount} unpaid`\n : undefined\n }\n deltaTone=\"neutral\"\n />\n ))}\n </div>\n ) : null}\n\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Budget: Allocated vs Spent\"\n subtitle=\"Per budget, from real data\"\n icon={Wallet}\n >\n {budgetBars.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 <BarsChart\n data={budgetBars}\n xKey=\"name\"\n series={[\n { key: 'allocated', label: 'Allocated', color: seriesColor(1) },\n { key: 'spent', label: 'Spent' },\n ]}\n height={Math.max(220, budgetBars.length * 56)}\n orientation=\"horizontal\"\n categoryWidth={120}\n valueFormat={usd}\n ariaLabel=\"Budget allocated versus spent\"\n />\n )}\n </ChartCard>\n\n <ChartCard title=\"Spend Over Time\" subtitle=\"Monthly transaction spend\" icon={Receipt}>\n {spendTrend.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No transactions yet\"\n description=\"Recorded purchases, leases and disposals will chart here.\"\n />\n ) : (\n <TrendChart\n data={spendTrend}\n xKey=\"month\"\n series={[{ key: 'spend', label: 'Spend' }]}\n valueFormat={usd}\n height={260}\n ariaLabel=\"Monthly transaction spend\"\n />\n )}\n </ChartCard>\n </div>\n\n {/* Budget utilization meters */}\n <ChartCard\n title=\"Budget Utilization\"\n subtitle=\"Allocated vs. spent per budget\"\n icon={Wallet}\n >\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-4\">\n {budgets.map((budget) => (\n <ProgressMeter\n key={budget.id}\n label={budget.name}\n value={budget.spent}\n max={budget.allocated}\n format={(v) => money(Math.round(v))}\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 className=\"tabular-nums\">{money(budgetRemaining)}</span>\n </div>\n </div>\n )}\n </ChartCard>\n\n {/* Financial quick stats */}\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatTile\n label=\"Transactions\"\n value={transactions.length}\n icon={Receipt}\n delta={`${money(transactionsTotal)} value`}\n deltaTone=\"neutral\"\n />\n <StatTile\n label=\"Budget Allocated\"\n value={budgetAllocated}\n format={usd}\n icon={Wallet}\n delta={`${money(budgetSpent)} spent`}\n deltaTone=\"neutral\"\n />\n <StatTile label=\"Maintenance Cost\" value={maintenanceCost} format={usd} icon={Wrench} />\n <StatTile\n label=\"Outstanding Invoices\"\n value={outstandingInvoiceTotal}\n format={usd}\n icon={AlertTriangle}\n delta={`${outstandingInvoices.length} of ${invoices.length}`}\n deltaTone=\"neutral\"\n />\n </div>\n\n {/* Recent transactions */}\n <ChartCard title=\"Recent Transactions\" icon={Receipt}>\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 border-border-seam text-text-secondary\">\n <th className=\"py-2 text-left font-medium\">Type</th>\n <th className=\"py-2 text-left font-medium\">Description</th>\n <th className=\"py-2 text-right font-medium\">Amount</th>\n <th className=\"py-2 text-left font-medium\">Status</th>\n </tr>\n </thead>\n <tbody>\n {transactions.slice(0, 6).map((txn) => (\n <tr\n key={txn.id}\n className=\"border-b border-border-seam hover:bg-bg-sunken/50\"\n >\n <td className=\"py-2.5 capitalize\">{txn.type}</td>\n <td className=\"max-w-xs truncate py-2.5\">{txn.description}</td>\n <td\n className={cn(\n 'py-2.5 text-right font-medium tabular-nums',\n txn.amount < 0 && 'text-status-error-text'\n )}\n >\n {money(Math.abs(txn.amount))}\n </td>\n <td className=\"py-2.5\">\n <StatusPill tone={txStatusTone(txn.status)}>{txn.status}</StatusPill>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </ChartCard>\n </TabsContent>\n\n {/* ─── Operational Tab ──────────────────────────────────────────────── */}\n <TabsContent value=\"operational\" className=\"space-y-6 mt-6\">\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-5\">\n <StatTile\n label={tc('assethandler.analytics.metrics.totalAssets', 'Total {{assets}}')}\n value={utilizationReport?.totalAssets ?? totalAssets}\n icon={Box}\n />\n <StatTile\n label=\"Assigned\"\n value={utilizationReport?.assignedAssets ?? '—'}\n icon={Users}\n />\n <StatTile\n label=\"Unassigned\"\n value={utilizationReport?.unassignedAssets ?? '—'}\n icon={Box}\n />\n <StatTile label=\"Idle\" value={utilizationReport?.idleAssets ?? '—'} icon={Activity} />\n <StatTile\n label=\"Overdue\"\n value={utilizationReport?.overdueAssignments ?? '—'}\n icon={AlertTriangle}\n delta={\n utilizationReport && utilizationReport.overdueAssignments > 0\n ? 'action needed'\n : undefined\n }\n deltaTone=\"negative\"\n deltaDirection=\"up\"\n />\n </div>\n\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <ChartCard title=\"Assets by Status\" subtitle=\"Assignment coverage\" icon={Gauge}>\n <BarsChart\n data={utilStatusBars}\n xKey=\"status\"\n series={[{ key: 'count', label: 'Assets' }]}\n orientation=\"horizontal\"\n height={Math.max(200, utilStatusBars.length * 40)}\n categoryWidth={120}\n emptyLabel=\"No utilization data\"\n ariaLabel=\"Assets by status\"\n />\n </ChartCard>\n\n <ChartCard title=\"Work Orders by Status\" subtitle=\"From real data\" icon={ClipboardList}>\n <DonutChart\n data={workOrderDonut}\n centerLabel=\"Work orders\"\n centerValue={workOrders.length}\n height={260}\n emptyLabel=\"No work orders\"\n ariaLabel=\"Work orders by status\"\n />\n </ChartCard>\n </div>\n\n <ChartCard\n title=\"Maintenance Cost Over Time\"\n subtitle=\"Monthly recorded maintenance cost\"\n icon={Wrench}\n >\n {maintenanceCostTrend.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No maintenance cost yet\"\n description=\"Once maintenance records carry cost, monthly spend will chart here.\"\n />\n ) : (\n <TrendChart\n data={maintenanceCostTrend}\n xKey=\"month\"\n series={[{ key: 'cost', label: 'Cost' }]}\n valueFormat={usd}\n height={260}\n ariaLabel=\"Monthly maintenance cost\"\n />\n )}\n </ChartCard>\n\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatTile\n label=\"Incidents\"\n value={incidents.length}\n icon={Activity}\n delta={`${resolvedIncidents.length} resolved`}\n deltaTone=\"neutral\"\n />\n <StatTile\n label=\"SLA Breaches\"\n value={slaBreached.length}\n icon={AlertTriangle}\n delta={slaBreached.length > 0 ? 'over SLA' : 'all within SLA'}\n deltaTone={slaBreached.length > 0 ? 'negative' : 'positive'}\n deltaDirection={slaBreached.length > 0 ? 'up' : 'neutral'}\n />\n <StatTile\n label=\"Maintenance Done\"\n value={completedMaintenance.length}\n icon={Wrench}\n delta={`of ${maintenanceRecords.length}`}\n deltaTone=\"neutral\"\n />\n <StatTile\n label=\"Open Work Orders\"\n value={openWorkOrders.length}\n icon={ClipboardList}\n delta={`of ${workOrders.length}`}\n deltaTone=\"neutral\"\n />\n </div>\n </TabsContent>\n\n {/* ─── Insights Tab ─────────────────────────────────────────────────── */}\n <TabsContent value=\"insights\" className=\"space-y-6 mt-6\">\n <div className=\"grid gap-6 lg:grid-cols-3\">\n <ChartCard\n title=\"SLA Compliance\"\n subtitle=\"Incidents resolved within SLA\"\n icon={ShieldCheck}\n >\n <RadialGauge\n value={slaCompliancePct}\n label=\"within SLA\"\n sublabel={`${slaBreached.length} of ${incidents.length} breached`}\n format={(v) => `${Math.round(v)}%`}\n height={220}\n ariaLabel=\"SLA compliance percentage\"\n />\n </ChartCard>\n\n <div className=\"lg:col-span-2 grid gap-4 sm: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 {distinctCategories} categories across {totalAssets} assets.{' '}\n {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 {money(budgetSpent)} spent of {money(budgetAllocated)} allocated, leaving{' '}\n {money(budgetRemaining)} across {budgets.length} 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} tasks scheduled. Total maintenance spend is{' '}\n {money(maintenanceCost)}. Stay on top of preventive maintenance.\n </p>\n </CardContent>\n </Card>\n </div>\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 </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 gap-3 rounded-lg border border-border-seam bg-gloss-card p-3\"\n >\n <div className=\"flex min-w-0 items-center gap-3\">\n <div className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/10 text-xs font-medium text-primary\">\n {i + 1}\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium\">{asset.name}</p>\n <p className=\"truncate 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 shrink-0 items-center gap-2\">\n <StatusPill tone={asset.condition === 'damaged' ? 'error' : 'warning'}>\n {asset.condition}\n </StatusPill>\n <StatusPill tone={asset.status === 'active' ? 'success' : 'warning'}>\n {asset.status.replace('_', ' ')}\n </StatusPill>\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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssetsAssetCategoriesPage.d.ts","sourceRoot":"","sources":["../../../src/assethandler/pages/AssetsAssetCategoriesPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AssetsAssetCategoriesPage.d.ts","sourceRoot":"","sources":["../../../src/assethandler/pages/AssetsAssetCategoriesPage.tsx"],"names":[],"mappings":"AAuGA,wBAAgB,yBAAyB,gCA2VxC"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useAssetHandlerStore as e } from "../store/assethandlerStore.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
import { formatCompact as t } from "../components/charts/chartTheme.js";
|
|
3
|
+
import { formatAssetCategory as n } from "../utils/categoryPresentation.js";
|
|
4
|
+
import { useAssetCategoryPresentation as r, useAssetMoney as i, useAssetTerminology as a, useHiddenCategoryIds as o } from "../providers/AssetHandlerProvider.js";
|
|
5
|
+
import { useAssetHandlerNavigate as s } from "../hooks/useAssetHandlerNavigate.js";
|
|
6
|
+
import { notify as c } from "../utils/toast.js";
|
|
7
7
|
import { BarsChart as l } from "../components/charts/BarsChart.js";
|
|
8
8
|
import { StatTile as u } from "../components/StatTile.js";
|
|
9
9
|
import { ChartCard as d } from "../components/ChartCard.js";
|
|
@@ -76,11 +76,11 @@ var U = [
|
|
|
76
76
|
fair: 3,
|
|
77
77
|
poor: 2,
|
|
78
78
|
damaged: 1
|
|
79
|
-
}
|
|
80
|
-
function
|
|
81
|
-
let g =
|
|
79
|
+
};
|
|
80
|
+
function G() {
|
|
81
|
+
let { format: g, compact: b } = i(), S = s(), C = a(), w = o(), { labels: T } = r(), { assets: B } = e(), V = f(() => B.filter((e) => !w.has(e.category)), [B, w]), [G, K] = p("count"), [q, J] = p("count"), Y = f(() => {
|
|
82
82
|
let e = {};
|
|
83
|
-
for (let t of
|
|
83
|
+
for (let t of V) {
|
|
84
84
|
e[t.category] || (e[t.category] = {
|
|
85
85
|
key: t.category,
|
|
86
86
|
count: 0,
|
|
@@ -101,66 +101,66 @@ function K() {
|
|
|
101
101
|
t.avgCondition = n > 0 ? e / n : 0;
|
|
102
102
|
}
|
|
103
103
|
return Object.values(e).sort((e, t) => t.count - e.count);
|
|
104
|
-
}, [
|
|
105
|
-
category:
|
|
104
|
+
}, [V]), X = V.length, Z = f(() => V.reduce((e, t) => e + (t.currentValue ?? t.purchaseValue ?? 0), 0), [V]), Q = f(() => Y.map((e) => ({
|
|
105
|
+
category: n(e.key, T),
|
|
106
106
|
count: e.count,
|
|
107
107
|
value: e.totalValue
|
|
108
|
-
})), [
|
|
109
|
-
navigator.clipboard?.writeText(e),
|
|
108
|
+
})), [Y, T]), $ = f(() => [...Y].sort((e, t) => q === "count" ? t.count - e.count : t.totalValue - e.totalValue), [Y, q]), ee = (e) => {
|
|
109
|
+
navigator.clipboard?.writeText(e), c.success("Category copied", `"${n(e, T)}" copied to clipboard.`);
|
|
110
110
|
};
|
|
111
111
|
return /* @__PURE__ */ O("div", {
|
|
112
112
|
className: "space-y-6 p-4 sm:p-6",
|
|
113
113
|
children: [
|
|
114
114
|
/* @__PURE__ */ O("div", { children: [/* @__PURE__ */ D("h1", {
|
|
115
115
|
className: "text-2xl font-bold",
|
|
116
|
-
children: /* @__PURE__ */ O(I, { children: [
|
|
116
|
+
children: /* @__PURE__ */ O(I, { children: [C.asset, " Categories"] })
|
|
117
117
|
}), /* @__PURE__ */ O("p", {
|
|
118
118
|
className: "text-sm text-text-secondary",
|
|
119
119
|
children: [
|
|
120
|
-
|
|
120
|
+
Y.length,
|
|
121
121
|
" categories · ",
|
|
122
|
-
|
|
122
|
+
X,
|
|
123
123
|
" total ",
|
|
124
|
-
|
|
124
|
+
C.assets.toLowerCase()
|
|
125
125
|
]
|
|
126
126
|
})] }),
|
|
127
127
|
/* @__PURE__ */ D(R, { children: "Group and compare your assets by category to see where value and volume are concentrated. Each card rolls up how many assets you hold, their total book value and a proportional status mix — so you can plan procurement and replacements by category, not one asset at a time." }),
|
|
128
|
-
|
|
128
|
+
Y.length === 0 ? /* @__PURE__ */ D(L, {
|
|
129
129
|
illustration: "empty-data",
|
|
130
130
|
title: "No categories yet",
|
|
131
|
-
description: `Add ${
|
|
131
|
+
description: `Add ${C.assets.toLowerCase()} and they will be grouped into categories here automatically.`
|
|
132
132
|
}) : /* @__PURE__ */ O(E, { children: [
|
|
133
133
|
/* @__PURE__ */ O("div", {
|
|
134
134
|
className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
135
135
|
children: [
|
|
136
136
|
/* @__PURE__ */ D(u, {
|
|
137
137
|
label: "Categories",
|
|
138
|
-
value:
|
|
139
|
-
format:
|
|
138
|
+
value: Y.length,
|
|
139
|
+
format: t,
|
|
140
140
|
icon: y,
|
|
141
141
|
hero: !0
|
|
142
142
|
}),
|
|
143
143
|
/* @__PURE__ */ D(u, {
|
|
144
|
-
label: `${
|
|
145
|
-
value:
|
|
146
|
-
format:
|
|
144
|
+
label: `${C.assets} tracked`,
|
|
145
|
+
value: X,
|
|
146
|
+
format: t,
|
|
147
147
|
icon: h
|
|
148
148
|
}),
|
|
149
149
|
/* @__PURE__ */ D(u, {
|
|
150
150
|
label: "Total book value",
|
|
151
|
-
value:
|
|
152
|
-
format:
|
|
151
|
+
value: Z,
|
|
152
|
+
format: b,
|
|
153
153
|
icon: h
|
|
154
154
|
})
|
|
155
155
|
]
|
|
156
156
|
}),
|
|
157
157
|
/* @__PURE__ */ D("div", {
|
|
158
158
|
className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
|
|
159
|
-
children:
|
|
160
|
-
let r =
|
|
159
|
+
children: Y.map((e, t) => {
|
|
160
|
+
let r = X > 0 ? Math.round(e.count / X * 100) : 0, i = U.filter((t) => (e.statuses[t.key] || 0) > 0);
|
|
161
161
|
return /* @__PURE__ */ D(F, {
|
|
162
|
-
treatment:
|
|
163
|
-
glow:
|
|
162
|
+
treatment: t === 0 ? "glass" : "plain",
|
|
163
|
+
glow: t === 0,
|
|
164
164
|
className: "border-border-seam",
|
|
165
165
|
children: /* @__PURE__ */ O("div", {
|
|
166
166
|
className: "space-y-4 p-5",
|
|
@@ -176,7 +176,7 @@ function K() {
|
|
|
176
176
|
className: "min-w-0",
|
|
177
177
|
children: [/* @__PURE__ */ D("p", {
|
|
178
178
|
className: "truncate text-base font-semibold capitalize",
|
|
179
|
-
children:
|
|
179
|
+
children: n(e.key, T)
|
|
180
180
|
}), /* @__PURE__ */ O(k, {
|
|
181
181
|
variant: "outline",
|
|
182
182
|
className: "mt-0.5 text-xs",
|
|
@@ -189,20 +189,20 @@ function K() {
|
|
|
189
189
|
variant: "ghost",
|
|
190
190
|
size: "icon",
|
|
191
191
|
className: "h-8 w-8 shrink-0",
|
|
192
|
-
"aria-label": `Actions for ${
|
|
192
|
+
"aria-label": `Actions for ${n(e.key, T)}`,
|
|
193
193
|
children: /* @__PURE__ */ D(x, { className: "h-4 w-4" })
|
|
194
194
|
})
|
|
195
195
|
}), /* @__PURE__ */ O(M, {
|
|
196
196
|
align: "end",
|
|
197
197
|
children: [/* @__PURE__ */ O(N, {
|
|
198
|
-
onClick: () =>
|
|
198
|
+
onClick: () => S("/assets/asset-list"),
|
|
199
199
|
children: [
|
|
200
200
|
/* @__PURE__ */ D(v, { className: "mr-2 h-4 w-4" }),
|
|
201
201
|
"View ",
|
|
202
|
-
|
|
202
|
+
C.assets.toLowerCase()
|
|
203
203
|
]
|
|
204
204
|
}), /* @__PURE__ */ O(N, {
|
|
205
|
-
onClick: () =>
|
|
205
|
+
onClick: () => ee(e.key),
|
|
206
206
|
children: [/* @__PURE__ */ D(_, { className: "mr-2 h-4 w-4" }), "Copy category ID"]
|
|
207
207
|
})]
|
|
208
208
|
})] })]
|
|
@@ -211,7 +211,7 @@ function K() {
|
|
|
211
211
|
className: "flex flex-wrap items-end justify-between gap-3",
|
|
212
212
|
children: [/* @__PURE__ */ O("div", { children: [/* @__PURE__ */ O("p", {
|
|
213
213
|
className: "text-xs text-text-secondary",
|
|
214
|
-
children: [
|
|
214
|
+
children: [C.asset, " count"]
|
|
215
215
|
}), /* @__PURE__ */ D("p", {
|
|
216
216
|
className: "text-2xl font-bold tabular-nums",
|
|
217
217
|
children: e.count
|
|
@@ -222,7 +222,7 @@ function K() {
|
|
|
222
222
|
children: "Total value"
|
|
223
223
|
}), /* @__PURE__ */ D("p", {
|
|
224
224
|
className: "text-2xl font-bold tabular-nums",
|
|
225
|
-
children:
|
|
225
|
+
children: b(e.totalValue)
|
|
226
226
|
})]
|
|
227
227
|
})]
|
|
228
228
|
}),
|
|
@@ -271,33 +271,33 @@ function K() {
|
|
|
271
271
|
}),
|
|
272
272
|
/* @__PURE__ */ D(d, {
|
|
273
273
|
title: "Category comparison",
|
|
274
|
-
subtitle:
|
|
274
|
+
subtitle: G === "count" ? `${C.assets} per category` : "Book value per category",
|
|
275
275
|
actions: /* @__PURE__ */ O("div", {
|
|
276
276
|
className: "flex gap-1 rounded-md border border-border-seam p-0.5",
|
|
277
277
|
children: [/* @__PURE__ */ D(A, {
|
|
278
|
-
variant:
|
|
278
|
+
variant: G === "count" ? "default" : "ghost",
|
|
279
279
|
size: "sm",
|
|
280
280
|
className: "h-7 px-2 text-xs",
|
|
281
|
-
onClick: () =>
|
|
281
|
+
onClick: () => K("count"),
|
|
282
282
|
children: "Count"
|
|
283
283
|
}), /* @__PURE__ */ D(A, {
|
|
284
|
-
variant:
|
|
284
|
+
variant: G === "value" ? "default" : "ghost",
|
|
285
285
|
size: "sm",
|
|
286
286
|
className: "h-7 px-2 text-xs",
|
|
287
|
-
onClick: () =>
|
|
287
|
+
onClick: () => K("value"),
|
|
288
288
|
children: "Value"
|
|
289
289
|
})]
|
|
290
290
|
}),
|
|
291
291
|
children: /* @__PURE__ */ D(l, {
|
|
292
|
-
data:
|
|
292
|
+
data: Q,
|
|
293
293
|
xKey: "category",
|
|
294
294
|
orientation: "horizontal",
|
|
295
|
-
height: Math.max(200,
|
|
295
|
+
height: Math.max(200, Q.length * 44),
|
|
296
296
|
categoryWidth: 120,
|
|
297
|
-
valueFormat:
|
|
297
|
+
valueFormat: G === "value" ? b : t,
|
|
298
298
|
series: [{
|
|
299
|
-
key:
|
|
300
|
-
label:
|
|
299
|
+
key: G,
|
|
300
|
+
label: G === "count" ? C.assets : "Book value"
|
|
301
301
|
}]
|
|
302
302
|
})
|
|
303
303
|
}),
|
|
@@ -321,8 +321,8 @@ function K() {
|
|
|
321
321
|
children: /* @__PURE__ */ O("button", {
|
|
322
322
|
type: "button",
|
|
323
323
|
className: "inline-flex items-center gap-1 hover:text-text-primary",
|
|
324
|
-
onClick: () =>
|
|
325
|
-
children: ["Count", /* @__PURE__ */ D(m, { className: `h-3 w-3 ${
|
|
324
|
+
onClick: () => J("count"),
|
|
325
|
+
children: ["Count", /* @__PURE__ */ D(m, { className: `h-3 w-3 ${q === "count" ? "text-action-primary-bg" : ""}` })]
|
|
326
326
|
})
|
|
327
327
|
}),
|
|
328
328
|
/* @__PURE__ */ D("th", {
|
|
@@ -330,8 +330,8 @@ function K() {
|
|
|
330
330
|
children: /* @__PURE__ */ O("button", {
|
|
331
331
|
type: "button",
|
|
332
332
|
className: "inline-flex items-center gap-1 hover:text-text-primary",
|
|
333
|
-
onClick: () =>
|
|
334
|
-
children: ["Value", /* @__PURE__ */ D(m, { className: `h-3 w-3 ${
|
|
333
|
+
onClick: () => J("value"),
|
|
334
|
+
children: ["Value", /* @__PURE__ */ D(m, { className: `h-3 w-3 ${q === "value" ? "text-action-primary-bg" : ""}` })]
|
|
335
335
|
})
|
|
336
336
|
}),
|
|
337
337
|
/* @__PURE__ */ D("th", {
|
|
@@ -339,12 +339,12 @@ function K() {
|
|
|
339
339
|
children: "Avg. condition"
|
|
340
340
|
})
|
|
341
341
|
]
|
|
342
|
-
}) }), /* @__PURE__ */ D("tbody", { children:
|
|
342
|
+
}) }), /* @__PURE__ */ D("tbody", { children: $.map((e) => /* @__PURE__ */ O("tr", {
|
|
343
343
|
className: "border-b border-border-seam/60 last:border-0",
|
|
344
344
|
children: [
|
|
345
345
|
/* @__PURE__ */ D("td", {
|
|
346
346
|
className: "py-2.5 pr-4 font-medium capitalize",
|
|
347
|
-
children:
|
|
347
|
+
children: n(e.key, T)
|
|
348
348
|
}),
|
|
349
349
|
/* @__PURE__ */ D("td", {
|
|
350
350
|
className: "py-2.5 pr-4 tabular-nums",
|
|
@@ -352,7 +352,7 @@ function K() {
|
|
|
352
352
|
}),
|
|
353
353
|
/* @__PURE__ */ D("td", {
|
|
354
354
|
className: "py-2.5 pr-4 tabular-nums",
|
|
355
|
-
children:
|
|
355
|
+
children: g(e.totalValue)
|
|
356
356
|
}),
|
|
357
357
|
/* @__PURE__ */ D("td", {
|
|
358
358
|
className: "py-2.5 tabular-nums",
|
|
@@ -368,6 +368,6 @@ function K() {
|
|
|
368
368
|
});
|
|
369
369
|
}
|
|
370
370
|
//#endregion
|
|
371
|
-
export {
|
|
371
|
+
export { G as AssetsAssetCategoriesPage };
|
|
372
372
|
|
|
373
373
|
//# sourceMappingURL=AssetsAssetCategoriesPage.js.map
|