@burdenoff/microfe-assethandler 2026.713.2 → 2026.714.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/AttachmentsPanel.d.ts.map +1 -1
- package/dist/assethandler/components/AttachmentsPanel.js.map +1 -1
- package/dist/assethandler/hooks/useCopy.d.ts +28 -0
- package/dist/assethandler/hooks/useCopy.d.ts.map +1 -0
- package/dist/assethandler/hooks/useCopy.js +33 -0
- package/dist/assethandler/hooks/useCopy.js.map +1 -0
- package/dist/assethandler/index.d.ts +1 -1
- package/dist/assethandler/index.d.ts.map +1 -1
- package/dist/assethandler/pages/AnalyticsPage.d.ts.map +1 -1
- package/dist/assethandler/pages/AnalyticsPage.js +259 -258
- 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 +1 -2
- package/dist/assethandler/pages/AssetsAssetCategoriesPage.js.map +1 -1
- package/dist/assethandler/pages/AssetsAssetDetailPage.d.ts.map +1 -1
- package/dist/assethandler/pages/AssetsAssetDetailPage.js +376 -365
- package/dist/assethandler/pages/AssetsAssetDetailPage.js.map +1 -1
- package/dist/assethandler/pages/AssetsAssetListPage.d.ts.map +1 -1
- package/dist/assethandler/pages/AssetsAssetListPage.js +47 -48
- package/dist/assethandler/pages/AssetsAssetListPage.js.map +1 -1
- package/dist/assethandler/pages/DashboardPage.d.ts.map +1 -1
- package/dist/assethandler/pages/DashboardPage.js +236 -236
- package/dist/assethandler/pages/DashboardPage.js.map +1 -1
- package/dist/assethandler/pages/PortfolioPortfolioOverviewPage.d.ts.map +1 -1
- package/dist/assethandler/pages/PortfolioPortfolioOverviewPage.js +126 -125
- package/dist/assethandler/pages/PortfolioPortfolioOverviewPage.js.map +1 -1
- package/dist/assethandler/pages/TransactionsTransactionListPage.js +127 -127
- package/dist/assethandler/pages/TransactionsTransactionListPage.js.map +1 -1
- package/dist/assethandler/store/assethandlerStore.d.ts.map +1 -1
- package/dist/assethandler/store/assethandlerStore.js.map +1 -1
- package/dist/assethandler/types/index.d.ts +20 -0
- package/dist/assethandler/types/index.d.ts.map +1 -1
- package/dist/types/assistant.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { useAssetHandlerStore as e } from "../store/assethandlerStore.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
2
|
+
import { useCopy as t } from "../hooks/useCopy.js";
|
|
3
|
+
import { cn as n } from "../utils/cn.js";
|
|
4
|
+
import { formatCompact as r } from "../components/charts/chartTheme.js";
|
|
5
|
+
import { BarsChart as i } from "../components/charts/BarsChart.js";
|
|
6
|
+
import { DonutChart as a } from "../components/charts/DonutChart.js";
|
|
7
|
+
import { StatTile as o } from "../components/StatTile.js";
|
|
8
|
+
import { ChartCard as s } from "../components/ChartCard.js";
|
|
8
9
|
import "../components/index.js";
|
|
9
|
-
import { useMemo as
|
|
10
|
-
import { Activity as
|
|
11
|
-
import { gql as
|
|
12
|
-
import { useQuery as
|
|
13
|
-
import { Fragment as
|
|
14
|
-
import { GradientText as
|
|
10
|
+
import { useMemo as c } from "react";
|
|
11
|
+
import { Activity as l, Building2 as u, CheckCircle2 as d, DollarSign as f, Layers as p, MapPin as m, Package as h, PieChart as g, TrendingDown as _, Wallet as v, Wrench as y } from "lucide-react";
|
|
12
|
+
import { gql as b } from "@apollo/client";
|
|
13
|
+
import { useQuery as x } from "@apollo/client/react";
|
|
14
|
+
import { Fragment as S, jsx as C, jsxs as w } from "react/jsx-runtime";
|
|
15
|
+
import { GradientText as T, IllustratedEmptyState as E, PagePurpose as D, Table as O, TableBody as k, TableCell as A, TableHead as j, TableHeader as M, TableRow as N } from "@burdenoff/fe-libs/ui";
|
|
15
16
|
//#region src/assethandler/pages/PortfolioPortfolioOverviewPage.tsx
|
|
16
|
-
var
|
|
17
|
+
var P = b`
|
|
17
18
|
query AssetHandlerFinancialSummaryOverview {
|
|
18
19
|
financialSummary {
|
|
19
20
|
totalAssetValue
|
|
@@ -26,36 +27,36 @@ var N = y`
|
|
|
26
27
|
transactionExpense
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
|
-
`,
|
|
30
|
-
function
|
|
30
|
+
`, F = (e) => `$${Math.round(e).toLocaleString()}`, I = (e) => e === void 0 ? "—" : e;
|
|
31
|
+
function L(e) {
|
|
31
32
|
return e.replace(/_/g, " ").replace(/\b\w/g, (e) => e.toUpperCase());
|
|
32
33
|
}
|
|
33
|
-
function
|
|
34
|
+
function R(e, t) {
|
|
34
35
|
let n = e.reduce((e, n) => {
|
|
35
36
|
let r = t(n);
|
|
36
37
|
return e[r] = (e[r] || 0) + 1, e;
|
|
37
38
|
}, {});
|
|
38
39
|
return Object.entries(n).sort((e, t) => t[1] - e[1]);
|
|
39
40
|
}
|
|
40
|
-
var
|
|
41
|
+
var z = {
|
|
41
42
|
success: "bg-status-success-bg-subtle text-status-success-text",
|
|
42
43
|
warning: "bg-status-warning-bg-subtle text-status-warning-text",
|
|
43
44
|
error: "bg-status-error-bg-subtle text-status-error-text",
|
|
44
45
|
info: "bg-status-info-bg-subtle text-status-info-text",
|
|
45
46
|
neutral: "bg-bg-sunken text-text-secondary"
|
|
46
|
-
},
|
|
47
|
-
function
|
|
48
|
-
return /* @__PURE__ */
|
|
49
|
-
className:
|
|
50
|
-
children:
|
|
47
|
+
}, B = (e) => e === "active" ? "success" : e === "maintenance" ? "warning" : e === "disposed" ? "error" : "neutral";
|
|
48
|
+
function V({ tone: e, children: t }) {
|
|
49
|
+
return /* @__PURE__ */ C("span", {
|
|
50
|
+
className: n("inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium capitalize whitespace-nowrap", z[e]),
|
|
51
|
+
children: t
|
|
51
52
|
});
|
|
52
53
|
}
|
|
53
|
-
function
|
|
54
|
-
let { assets:
|
|
54
|
+
function H() {
|
|
55
|
+
let n = t(), { assets: b } = e(), { data: z } = x(P, {
|
|
55
56
|
fetchPolicy: "cache-and-network",
|
|
56
57
|
errorPolicy: "all"
|
|
57
|
-
}),
|
|
58
|
-
let e =
|
|
58
|
+
}), H = z?.financialSummary, U = c(() => b.reduce((e, t) => e + (t.totalCostOfOwnership ?? 0), 0), [b]), W = c(() => {
|
|
59
|
+
let e = b.reduce((e, t) => {
|
|
59
60
|
let n = t.department?.trim() || "Unassigned", r = e[n] ?? {
|
|
60
61
|
count: 0,
|
|
61
62
|
value: 0
|
|
@@ -63,16 +64,16 @@ function V() {
|
|
|
63
64
|
return r.count += 1, r.value += t.currentValue ?? 0, e[n] = r, e;
|
|
64
65
|
}, {});
|
|
65
66
|
return Object.entries(e).sort((e, t) => t[1].value - e[1].value);
|
|
66
|
-
}, [
|
|
67
|
+
}, [b]), G = c(() => W.filter(([, { value: e }]) => e > 0).slice(0, 8).map(([e, { value: t }]) => ({
|
|
67
68
|
department: e,
|
|
68
69
|
value: Math.round(t)
|
|
69
|
-
})), [
|
|
70
|
+
})), [W]), K = c(() => R(b, (e) => L(e.category)), [b]), q = c(() => R(b, (e) => e.status), [b]), J = c(() => R(b, (e) => e.condition), [b]), Y = c(() => R(b, (e) => e.location.trim() || "Unlocated"), [b]), X = c(() => K.map(([e, t]) => ({
|
|
70
71
|
name: e,
|
|
71
72
|
value: t
|
|
72
|
-
})), [
|
|
73
|
-
status:
|
|
73
|
+
})), [K]), Z = c(() => q.map(([e, t]) => ({
|
|
74
|
+
status: L(e),
|
|
74
75
|
count: t
|
|
75
|
-
})), [
|
|
76
|
+
})), [q]), Q = c(() => {
|
|
76
77
|
let e = [
|
|
77
78
|
"excellent",
|
|
78
79
|
"good",
|
|
@@ -80,113 +81,113 @@ function V() {
|
|
|
80
81
|
"poor",
|
|
81
82
|
"damaged"
|
|
82
83
|
];
|
|
83
|
-
return [...
|
|
84
|
-
condition:
|
|
84
|
+
return [...J].sort((t, n) => e.indexOf(t[0]) - e.indexOf(n[0])).map(([e, t]) => ({
|
|
85
|
+
condition: L(e),
|
|
85
86
|
count: t
|
|
86
87
|
}));
|
|
87
|
-
}, [
|
|
88
|
-
return /* @__PURE__ */
|
|
88
|
+
}, [J]), $ = q.find(([e]) => e === "active")?.[1] ?? 0, ee = q.find(([e]) => e === "maintenance")?.[1] ?? 0, te = Y.filter(([e]) => e !== "Unlocated").length;
|
|
89
|
+
return /* @__PURE__ */ w("div", {
|
|
89
90
|
className: "space-y-6 p-4 sm:p-6",
|
|
90
91
|
children: [
|
|
91
|
-
/* @__PURE__ */
|
|
92
|
-
/* @__PURE__ */
|
|
92
|
+
/* @__PURE__ */ w("div", { children: [
|
|
93
|
+
/* @__PURE__ */ C("p", {
|
|
93
94
|
className: "text-xs font-semibold uppercase tracking-wide text-text-muted",
|
|
94
95
|
children: "Portfolio Overview"
|
|
95
96
|
}),
|
|
96
|
-
/* @__PURE__ */
|
|
97
|
+
/* @__PURE__ */ C("h1", {
|
|
97
98
|
className: "mt-1 text-2xl font-bold sm:text-3xl",
|
|
98
|
-
children: /* @__PURE__ */
|
|
99
|
+
children: /* @__PURE__ */ C(T, { children: "Portfolio Overview" })
|
|
99
100
|
}),
|
|
100
|
-
/* @__PURE__ */
|
|
101
|
+
/* @__PURE__ */ C("p", {
|
|
101
102
|
className: "mt-1 text-sm text-text-secondary",
|
|
102
103
|
children: "Management-level rollup of the entire estate — how it is distributed and what it is worth."
|
|
103
104
|
})
|
|
104
105
|
] }),
|
|
105
|
-
/* @__PURE__ */
|
|
106
|
-
|
|
106
|
+
/* @__PURE__ */ C(D, { children: "The big-picture view of everything you own: how many assets you have and how they break down by category, status, condition and location. Use it to inform replacement planning and audit." }),
|
|
107
|
+
b.length === 0 ? /* @__PURE__ */ C(E, {
|
|
107
108
|
illustration: "empty-data",
|
|
108
109
|
title: "No assets in your portfolio yet",
|
|
109
110
|
description: "Once you add assets, this overview will summarise them by category, status, condition and location."
|
|
110
|
-
}) : /* @__PURE__ */
|
|
111
|
-
/* @__PURE__ */
|
|
111
|
+
}) : /* @__PURE__ */ w(S, { children: [
|
|
112
|
+
/* @__PURE__ */ w("div", {
|
|
112
113
|
className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-4",
|
|
113
114
|
children: [
|
|
114
|
-
/* @__PURE__ */
|
|
115
|
+
/* @__PURE__ */ C(o, {
|
|
115
116
|
hero: !0,
|
|
116
117
|
label: "Total Asset Value",
|
|
117
|
-
value:
|
|
118
|
-
format:
|
|
119
|
-
icon:
|
|
118
|
+
value: I(H?.totalAssetValue),
|
|
119
|
+
format: F,
|
|
120
|
+
icon: f
|
|
120
121
|
}),
|
|
121
|
-
/* @__PURE__ */
|
|
122
|
+
/* @__PURE__ */ C(o, {
|
|
122
123
|
label: "Total Depreciation",
|
|
123
|
-
value:
|
|
124
|
-
format:
|
|
125
|
-
icon:
|
|
124
|
+
value: I(H?.totalDepreciation),
|
|
125
|
+
format: F,
|
|
126
|
+
icon: _
|
|
126
127
|
}),
|
|
127
|
-
/* @__PURE__ */
|
|
128
|
+
/* @__PURE__ */ C(o, {
|
|
128
129
|
label: "Total Cost of Ownership",
|
|
129
|
-
value:
|
|
130
|
-
format:
|
|
131
|
-
icon:
|
|
130
|
+
value: U,
|
|
131
|
+
format: F,
|
|
132
|
+
icon: v
|
|
132
133
|
}),
|
|
133
|
-
/* @__PURE__ */
|
|
134
|
-
label: "Total
|
|
135
|
-
value:
|
|
136
|
-
format:
|
|
137
|
-
icon:
|
|
134
|
+
/* @__PURE__ */ C(o, {
|
|
135
|
+
label: n("assethandler.portfolio.metrics.totalAssets", "Total {{assets}}"),
|
|
136
|
+
value: b.length,
|
|
137
|
+
format: r,
|
|
138
|
+
icon: h
|
|
138
139
|
})
|
|
139
140
|
]
|
|
140
141
|
}),
|
|
141
|
-
/* @__PURE__ */
|
|
142
|
+
/* @__PURE__ */ w("div", {
|
|
142
143
|
className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-4",
|
|
143
144
|
children: [
|
|
144
|
-
/* @__PURE__ */
|
|
145
|
+
/* @__PURE__ */ C(o, {
|
|
145
146
|
label: "Active",
|
|
146
|
-
value:
|
|
147
|
-
icon:
|
|
148
|
-
delta: `${
|
|
147
|
+
value: $,
|
|
148
|
+
icon: d,
|
|
149
|
+
delta: `${b.length > 0 ? Math.round($ / b.length * 100) : 0}% of estate`,
|
|
149
150
|
deltaTone: "neutral"
|
|
150
151
|
}),
|
|
151
|
-
/* @__PURE__ */
|
|
152
|
+
/* @__PURE__ */ C(o, {
|
|
152
153
|
label: "In Maintenance",
|
|
153
|
-
value:
|
|
154
|
-
icon:
|
|
154
|
+
value: ee,
|
|
155
|
+
icon: y
|
|
155
156
|
}),
|
|
156
|
-
/* @__PURE__ */
|
|
157
|
+
/* @__PURE__ */ C(o, {
|
|
157
158
|
label: "Locations",
|
|
158
|
-
value:
|
|
159
|
-
icon:
|
|
159
|
+
value: te,
|
|
160
|
+
icon: m
|
|
160
161
|
}),
|
|
161
|
-
/* @__PURE__ */
|
|
162
|
+
/* @__PURE__ */ C(o, {
|
|
162
163
|
label: "Categories",
|
|
163
|
-
value:
|
|
164
|
-
icon:
|
|
164
|
+
value: K.length,
|
|
165
|
+
icon: p
|
|
165
166
|
})
|
|
166
167
|
]
|
|
167
168
|
}),
|
|
168
|
-
/* @__PURE__ */
|
|
169
|
+
/* @__PURE__ */ w("div", {
|
|
169
170
|
className: "grid gap-6 lg:grid-cols-2",
|
|
170
|
-
children: [/* @__PURE__ */
|
|
171
|
+
children: [/* @__PURE__ */ C(s, {
|
|
171
172
|
title: "Assets by Category",
|
|
172
|
-
icon:
|
|
173
|
-
children: /* @__PURE__ */
|
|
174
|
-
data:
|
|
175
|
-
centerLabel: "
|
|
176
|
-
centerValue:
|
|
173
|
+
icon: g,
|
|
174
|
+
children: /* @__PURE__ */ C(a, {
|
|
175
|
+
data: X,
|
|
176
|
+
centerLabel: n("assethandler.portfolio.donut.centerLabel", "{{assets}}"),
|
|
177
|
+
centerValue: b.length,
|
|
177
178
|
height: 280,
|
|
178
179
|
ariaLabel: "Assets by category"
|
|
179
180
|
})
|
|
180
|
-
}), /* @__PURE__ */
|
|
181
|
+
}), /* @__PURE__ */ C(s, {
|
|
181
182
|
title: "Value by Department",
|
|
182
183
|
subtitle: "Summed current book value",
|
|
183
|
-
icon:
|
|
184
|
-
children:
|
|
184
|
+
icon: u,
|
|
185
|
+
children: G.length === 0 ? /* @__PURE__ */ C(E, {
|
|
185
186
|
illustration: "empty-data",
|
|
186
187
|
title: "No department values yet",
|
|
187
188
|
description: "Assign departments and record current values to see this breakdown."
|
|
188
|
-
}) : /* @__PURE__ */
|
|
189
|
-
data:
|
|
189
|
+
}) : /* @__PURE__ */ C(i, {
|
|
190
|
+
data: G,
|
|
190
191
|
xKey: "department",
|
|
191
192
|
series: [{
|
|
192
193
|
key: "value",
|
|
@@ -194,19 +195,19 @@ function V() {
|
|
|
194
195
|
}],
|
|
195
196
|
orientation: "horizontal",
|
|
196
197
|
categoryWidth: 120,
|
|
197
|
-
height: Math.max(220,
|
|
198
|
-
valueFormat:
|
|
198
|
+
height: Math.max(220, G.length * 44),
|
|
199
|
+
valueFormat: F,
|
|
199
200
|
ariaLabel: "Current value by department"
|
|
200
201
|
})
|
|
201
202
|
})]
|
|
202
203
|
}),
|
|
203
|
-
/* @__PURE__ */
|
|
204
|
+
/* @__PURE__ */ w("div", {
|
|
204
205
|
className: "grid gap-6 lg:grid-cols-2",
|
|
205
|
-
children: [/* @__PURE__ */
|
|
206
|
+
children: [/* @__PURE__ */ C(s, {
|
|
206
207
|
title: "Assets by Status",
|
|
207
|
-
icon:
|
|
208
|
-
children: /* @__PURE__ */
|
|
209
|
-
data:
|
|
208
|
+
icon: l,
|
|
209
|
+
children: /* @__PURE__ */ C(i, {
|
|
210
|
+
data: Z,
|
|
210
211
|
xKey: "status",
|
|
211
212
|
series: [{
|
|
212
213
|
key: "count",
|
|
@@ -215,31 +216,31 @@ function V() {
|
|
|
215
216
|
height: 260,
|
|
216
217
|
ariaLabel: "Assets by status"
|
|
217
218
|
})
|
|
218
|
-
}), /* @__PURE__ */
|
|
219
|
+
}), /* @__PURE__ */ C(s, {
|
|
219
220
|
title: "Assets by Location",
|
|
220
|
-
icon:
|
|
221
|
-
children: /* @__PURE__ */
|
|
221
|
+
icon: m,
|
|
222
|
+
children: /* @__PURE__ */ C("div", {
|
|
222
223
|
className: "overflow-x-auto",
|
|
223
|
-
children: /* @__PURE__ */
|
|
224
|
+
children: /* @__PURE__ */ w(O, { children: [/* @__PURE__ */ C(M, { children: /* @__PURE__ */ w(N, { children: [/* @__PURE__ */ C(j, { children: "Location" }), /* @__PURE__ */ C(j, {
|
|
224
225
|
className: "text-right",
|
|
225
226
|
children: "Assets"
|
|
226
|
-
})] }) }), /* @__PURE__ */
|
|
227
|
+
})] }) }), /* @__PURE__ */ C(k, { children: Y.map(([e, t]) => /* @__PURE__ */ w(N, { children: [/* @__PURE__ */ C(A, {
|
|
227
228
|
className: "font-medium",
|
|
228
229
|
children: e
|
|
229
|
-
}), /* @__PURE__ */
|
|
230
|
+
}), /* @__PURE__ */ C(A, {
|
|
230
231
|
className: "text-right tabular-nums",
|
|
231
232
|
children: t
|
|
232
233
|
})] }, e)) })] })
|
|
233
234
|
})
|
|
234
235
|
})]
|
|
235
236
|
}),
|
|
236
|
-
/* @__PURE__ */
|
|
237
|
+
/* @__PURE__ */ w("div", {
|
|
237
238
|
className: "grid gap-6 lg:grid-cols-2",
|
|
238
|
-
children: [/* @__PURE__ */
|
|
239
|
+
children: [/* @__PURE__ */ C(s, {
|
|
239
240
|
title: "Asset Condition Mix",
|
|
240
|
-
icon:
|
|
241
|
-
children: /* @__PURE__ */
|
|
242
|
-
data:
|
|
241
|
+
icon: y,
|
|
242
|
+
children: /* @__PURE__ */ C(i, {
|
|
243
|
+
data: Q,
|
|
243
244
|
xKey: "condition",
|
|
244
245
|
series: [{
|
|
245
246
|
key: "count",
|
|
@@ -248,50 +249,50 @@ function V() {
|
|
|
248
249
|
height: 260,
|
|
249
250
|
ariaLabel: "Assets by condition"
|
|
250
251
|
})
|
|
251
|
-
}), /* @__PURE__ */
|
|
252
|
+
}), /* @__PURE__ */ C(s, {
|
|
252
253
|
title: "Department Breakdown",
|
|
253
254
|
subtitle: "Assets and current value",
|
|
254
|
-
icon:
|
|
255
|
-
children: /* @__PURE__ */
|
|
255
|
+
icon: v,
|
|
256
|
+
children: /* @__PURE__ */ C("div", {
|
|
256
257
|
className: "overflow-x-auto",
|
|
257
|
-
children: /* @__PURE__ */
|
|
258
|
-
/* @__PURE__ */
|
|
259
|
-
/* @__PURE__ */
|
|
258
|
+
children: /* @__PURE__ */ w(O, { children: [/* @__PURE__ */ C(M, { children: /* @__PURE__ */ w(N, { children: [
|
|
259
|
+
/* @__PURE__ */ C(j, { children: "Department" }),
|
|
260
|
+
/* @__PURE__ */ C(j, {
|
|
260
261
|
className: "text-right",
|
|
261
262
|
children: "Assets"
|
|
262
263
|
}),
|
|
263
|
-
/* @__PURE__ */
|
|
264
|
+
/* @__PURE__ */ C(j, {
|
|
264
265
|
className: "text-right",
|
|
265
266
|
children: "Current Value"
|
|
266
267
|
})
|
|
267
|
-
] }) }), /* @__PURE__ */
|
|
268
|
-
/* @__PURE__ */
|
|
268
|
+
] }) }), /* @__PURE__ */ C(k, { children: W.map(([e, { count: t, value: n }]) => /* @__PURE__ */ w(N, { children: [
|
|
269
|
+
/* @__PURE__ */ C(A, {
|
|
269
270
|
className: "font-medium",
|
|
270
271
|
children: e
|
|
271
272
|
}),
|
|
272
|
-
/* @__PURE__ */
|
|
273
|
+
/* @__PURE__ */ C(A, {
|
|
273
274
|
className: "text-right tabular-nums",
|
|
274
275
|
children: t
|
|
275
276
|
}),
|
|
276
|
-
/* @__PURE__ */
|
|
277
|
+
/* @__PURE__ */ C(A, {
|
|
277
278
|
className: "text-right tabular-nums",
|
|
278
|
-
children:
|
|
279
|
+
children: F(n)
|
|
279
280
|
})
|
|
280
281
|
] }, e)) })] })
|
|
281
282
|
})
|
|
282
283
|
})]
|
|
283
284
|
}),
|
|
284
|
-
/* @__PURE__ */
|
|
285
|
+
/* @__PURE__ */ C(s, {
|
|
285
286
|
title: "Status Breakdown",
|
|
286
|
-
icon:
|
|
287
|
-
children: /* @__PURE__ */
|
|
287
|
+
icon: l,
|
|
288
|
+
children: /* @__PURE__ */ C("div", {
|
|
288
289
|
className: "flex flex-wrap gap-3",
|
|
289
|
-
children:
|
|
290
|
+
children: q.map(([e, t]) => /* @__PURE__ */ w("div", {
|
|
290
291
|
className: "flex items-center gap-2 rounded-lg border border-border-seam bg-gloss-card p-3",
|
|
291
|
-
children: [/* @__PURE__ */
|
|
292
|
-
tone:
|
|
292
|
+
children: [/* @__PURE__ */ C(V, {
|
|
293
|
+
tone: B(e),
|
|
293
294
|
children: e.replace(/_/g, " ")
|
|
294
|
-
}), /* @__PURE__ */
|
|
295
|
+
}), /* @__PURE__ */ C("span", {
|
|
295
296
|
className: "text-lg font-bold tabular-nums",
|
|
296
297
|
children: t
|
|
297
298
|
})]
|
|
@@ -303,6 +304,6 @@ function V() {
|
|
|
303
304
|
});
|
|
304
305
|
}
|
|
305
306
|
//#endregion
|
|
306
|
-
export {
|
|
307
|
+
export { H as PortfolioPortfolioOverviewPage };
|
|
307
308
|
|
|
308
309
|
//# sourceMappingURL=PortfolioPortfolioOverviewPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PortfolioPortfolioOverviewPage.js","names":[],"sources":["../../../src/assethandler/pages/PortfolioPortfolioOverviewPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n GradientText,\n PagePurpose,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Package,\n CheckCircle2,\n Wrench,\n MapPin,\n DollarSign,\n TrendingDown,\n Wallet,\n PieChart,\n Building2,\n Activity,\n Layers,\n} from 'lucide-react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { useAssetHandlerStore, type Asset } from '../store/assethandlerStore';\nimport { cn } from '../utils/cn';\nimport {\n StatTile,\n ChartCard,\n DonutChart,\n BarsChart,\n formatCompact,\n type ChartDatum,\n type DonutSlice,\n} from '../components';\n\n// ─── Backend financial rollup (wspace-assethandler-svc) ──────────────────────\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\nconst FINANCIAL_SUMMARY_QUERY = gql`\n query AssetHandlerFinancialSummaryOverview {\n financialSummary {\n totalAssetValue\n totalDepreciation\n totalMaintenanceSpend\n budgetAllocated\n budgetSpent\n invoicesOutstanding\n invoicesOutstandingCount\n transactionExpense\n }\n }\n`;\n\nconst moneyFull = (value: number): string => `$${Math.round(value).toLocaleString()}`;\nconst moneyOrDash = (value: number | undefined): number | string =>\n value === undefined ? '—' : value;\n\nfunction titleCase(value: string): string {\n return value.replace(/_/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\nfunction countBy(assets: Asset[], key: (a: Asset) => string): Array<[string, number]> {\n const map = assets.reduce<Record<string, number>>((acc, a) => {\n const k = key(a);\n acc[k] = (acc[k] || 0) + 1;\n return acc;\n }, {});\n return Object.entries(map).sort((a, b) => b[1] - a[1]);\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};\nconst statusTone = (status: string): PillTone =>\n status === 'active'\n ? 'success'\n : status === 'maintenance'\n ? 'warning'\n : status === 'disposed'\n ? 'error'\n : 'neutral';\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}\n\nexport function PortfolioPortfolioOverviewPage() {\n const { assets } = useAssetHandlerStore();\n\n const { data: financialData } = useQuery<{ financialSummary: AssetHandlerFinancialSummary }>(\n FINANCIAL_SUMMARY_QUERY,\n { fetchPolicy: 'cache-and-network', errorPolicy: 'all' }\n );\n const financialSummary = financialData?.financialSummary;\n\n // TCO summed from store assets (undefined per-asset figures treated as 0).\n const totalCostOfOwnership = useMemo(\n () => assets.reduce((sum, a) => sum + (a.totalCostOfOwnership ?? 0), 0),\n [assets]\n );\n\n // Per-department count + summed current value, sorted by value desc.\n const byDepartment = useMemo(() => {\n const map = assets.reduce<Record<string, { count: number; value: number }>>((acc, a) => {\n const dept = a.department?.trim() || 'Unassigned';\n const entry = acc[dept] ?? { count: 0, value: 0 };\n entry.count += 1;\n entry.value += a.currentValue ?? 0;\n acc[dept] = entry;\n return acc;\n }, {});\n return Object.entries(map).sort((a, b) => b[1].value - a[1].value);\n }, [assets]);\n\n const departmentBars = useMemo<ChartDatum[]>(\n () =>\n byDepartment\n .filter(([, { value }]) => value > 0)\n .slice(0, 8)\n .map(([dept, { value }]) => ({ department: dept, value: Math.round(value) })),\n [byDepartment]\n );\n\n const byCategory = useMemo(() => countBy(assets, (a) => titleCase(a.category)), [assets]);\n const byStatus = useMemo(() => countBy(assets, (a) => a.status), [assets]);\n const byCondition = useMemo(() => countBy(assets, (a) => a.condition), [assets]);\n const byLocation = useMemo(\n () => countBy(assets, (a) => a.location.trim() || 'Unlocated'),\n [assets]\n );\n\n const categoryDonut = useMemo<DonutSlice[]>(\n () => byCategory.map(([name, value]) => ({ name, value })),\n [byCategory]\n );\n const statusBars = useMemo<ChartDatum[]>(\n () => byStatus.map(([status, count]) => ({ status: titleCase(status), count })),\n [byStatus]\n );\n const conditionBars = useMemo<ChartDatum[]>(() => {\n const order = ['excellent', 'good', 'fair', 'poor', 'damaged'];\n return [...byCondition]\n .sort((a, b) => order.indexOf(a[0]) - order.indexOf(b[0]))\n .map(([condition, count]) => ({ condition: titleCase(condition), count }));\n }, [byCondition]);\n\n const activeCount = byStatus.find(([s]) => s === 'active')?.[1] ?? 0;\n const maintenanceCount = byStatus.find(([s]) => s === 'maintenance')?.[1] ?? 0;\n const locationCount = byLocation.filter(([l]) => l !== 'Unlocated').length;\n\n return (\n <div className=\"space-y-6 p-4 sm:p-6\">\n <div>\n <p className=\"text-xs font-semibold uppercase tracking-wide text-text-muted\">\n Portfolio Overview\n </p>\n <h1 className=\"mt-1 text-2xl font-bold sm:text-3xl\">\n <GradientText>Portfolio Overview</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Management-level rollup of the entire estate — how it is distributed and what it is worth.\n </p>\n </div>\n\n <PagePurpose>\n The big-picture view of everything you own: how many assets you have and how they break down\n by category, status, condition and location. Use it to inform replacement planning and\n audit.\n </PagePurpose>\n\n {assets.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No assets in your portfolio yet\"\n description=\"Once you add assets, this overview will summarise them by category, status, condition and location.\"\n />\n ) : (\n <>\n {/* Primary value KPIs */}\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatTile\n hero\n label=\"Total Asset Value\"\n value={moneyOrDash(financialSummary?.totalAssetValue)}\n format={moneyFull}\n icon={DollarSign}\n />\n <StatTile\n label=\"Total Depreciation\"\n value={moneyOrDash(financialSummary?.totalDepreciation)}\n format={moneyFull}\n icon={TrendingDown}\n />\n <StatTile\n label=\"Total Cost of Ownership\"\n value={totalCostOfOwnership}\n format={moneyFull}\n icon={Wallet}\n />\n <StatTile\n label=\"Total Assets\"\n value={assets.length}\n format={formatCompact}\n icon={Package}\n />\n </div>\n\n {/* Secondary count KPIs */}\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatTile\n label=\"Active\"\n value={activeCount}\n icon={CheckCircle2}\n delta={`${assets.length > 0 ? Math.round((activeCount / assets.length) * 100) : 0}% of estate`}\n deltaTone=\"neutral\"\n />\n <StatTile label=\"In Maintenance\" value={maintenanceCount} icon={Wrench} />\n <StatTile label=\"Locations\" value={locationCount} icon={MapPin} />\n <StatTile label=\"Categories\" value={byCategory.length} icon={Layers} />\n </div>\n\n {/* Category donut + Value by department */}\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <ChartCard title=\"Assets by Category\" icon={PieChart}>\n <DonutChart\n data={categoryDonut}\n centerLabel=\"Assets\"\n centerValue={assets.length}\n height={280}\n ariaLabel=\"Assets by category\"\n />\n </ChartCard>\n\n <ChartCard\n title=\"Value by Department\"\n subtitle=\"Summed current book value\"\n icon={Building2}\n >\n {departmentBars.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No department values yet\"\n description=\"Assign departments and record current values to see this breakdown.\"\n />\n ) : (\n <BarsChart\n data={departmentBars}\n xKey=\"department\"\n series={[{ key: 'value', label: 'Current value' }]}\n orientation=\"horizontal\"\n categoryWidth={120}\n height={Math.max(220, departmentBars.length * 44)}\n valueFormat={moneyFull}\n ariaLabel=\"Current value by department\"\n />\n )}\n </ChartCard>\n </div>\n\n {/* Status bars + Location table */}\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <ChartCard title=\"Assets by Status\" icon={Activity}>\n <BarsChart\n data={statusBars}\n xKey=\"status\"\n series={[{ key: 'count', label: 'Assets' }]}\n height={260}\n ariaLabel=\"Assets by status\"\n />\n </ChartCard>\n\n <ChartCard title=\"Assets by Location\" icon={MapPin}>\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Location</TableHead>\n <TableHead className=\"text-right\">Assets</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {byLocation.map(([location, count]) => (\n <TableRow key={location}>\n <TableCell className=\"font-medium\">{location}</TableCell>\n <TableCell className=\"text-right tabular-nums\">{count}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n </ChartCard>\n </div>\n\n {/* Condition mix + Department value table */}\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <ChartCard title=\"Asset Condition Mix\" icon={Wrench}>\n <BarsChart\n data={conditionBars}\n xKey=\"condition\"\n series={[{ key: 'count', label: 'Assets' }]}\n height={260}\n ariaLabel=\"Assets by condition\"\n />\n </ChartCard>\n\n <ChartCard\n title=\"Department Breakdown\"\n subtitle=\"Assets and current value\"\n icon={Wallet}\n >\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Department</TableHead>\n <TableHead className=\"text-right\">Assets</TableHead>\n <TableHead className=\"text-right\">Current Value</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {byDepartment.map(([dept, { count, value }]) => (\n <TableRow key={dept}>\n <TableCell className=\"font-medium\">{dept}</TableCell>\n <TableCell className=\"text-right tabular-nums\">{count}</TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {moneyFull(value)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n </ChartCard>\n </div>\n\n {/* Status legend chips */}\n <ChartCard title=\"Status Breakdown\" icon={Activity}>\n <div className=\"flex flex-wrap gap-3\">\n {byStatus.map(([status, count]) => (\n <div\n key={status}\n className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-gloss-card p-3\"\n >\n <StatusPill tone={statusTone(status)}>{status.replace(/_/g, ' ')}</StatusPill>\n <span className=\"text-lg font-bold tabular-nums\">{count}</span>\n </div>\n ))}\n </div>\n </ChartCard>\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PortfolioPortfolioOverviewPage.js","names":[],"sources":["../../../src/assethandler/pages/PortfolioPortfolioOverviewPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n GradientText,\n PagePurpose,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Package,\n CheckCircle2,\n Wrench,\n MapPin,\n DollarSign,\n TrendingDown,\n Wallet,\n PieChart,\n Building2,\n Activity,\n Layers,\n} from 'lucide-react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { useAssetHandlerStore, type Asset } from '../store/assethandlerStore';\nimport { useCopy } from '../hooks/useCopy';\nimport { cn } from '../utils/cn';\nimport {\n StatTile,\n ChartCard,\n DonutChart,\n BarsChart,\n formatCompact,\n type ChartDatum,\n type DonutSlice,\n} from '../components';\n\n// ─── Backend financial rollup (wspace-assethandler-svc) ──────────────────────\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\nconst FINANCIAL_SUMMARY_QUERY = gql`\n query AssetHandlerFinancialSummaryOverview {\n financialSummary {\n totalAssetValue\n totalDepreciation\n totalMaintenanceSpend\n budgetAllocated\n budgetSpent\n invoicesOutstanding\n invoicesOutstandingCount\n transactionExpense\n }\n }\n`;\n\nconst moneyFull = (value: number): string => `$${Math.round(value).toLocaleString()}`;\nconst moneyOrDash = (value: number | undefined): number | string =>\n value === undefined ? '—' : value;\n\nfunction titleCase(value: string): string {\n return value.replace(/_/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\nfunction countBy(assets: Asset[], key: (a: Asset) => string): Array<[string, number]> {\n const map = assets.reduce<Record<string, number>>((acc, a) => {\n const k = key(a);\n acc[k] = (acc[k] || 0) + 1;\n return acc;\n }, {});\n return Object.entries(map).sort((a, b) => b[1] - a[1]);\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};\nconst statusTone = (status: string): PillTone =>\n status === 'active'\n ? 'success'\n : status === 'maintenance'\n ? 'warning'\n : status === 'disposed'\n ? 'error'\n : 'neutral';\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}\n\nexport function PortfolioPortfolioOverviewPage() {\n const tc = useCopy();\n const { assets } = useAssetHandlerStore();\n\n const { data: financialData } = useQuery<{ financialSummary: AssetHandlerFinancialSummary }>(\n FINANCIAL_SUMMARY_QUERY,\n { fetchPolicy: 'cache-and-network', errorPolicy: 'all' }\n );\n const financialSummary = financialData?.financialSummary;\n\n // TCO summed from store assets (undefined per-asset figures treated as 0).\n const totalCostOfOwnership = useMemo(\n () => assets.reduce((sum, a) => sum + (a.totalCostOfOwnership ?? 0), 0),\n [assets]\n );\n\n // Per-department count + summed current value, sorted by value desc.\n const byDepartment = useMemo(() => {\n const map = assets.reduce<Record<string, { count: number; value: number }>>((acc, a) => {\n const dept = a.department?.trim() || 'Unassigned';\n const entry = acc[dept] ?? { count: 0, value: 0 };\n entry.count += 1;\n entry.value += a.currentValue ?? 0;\n acc[dept] = entry;\n return acc;\n }, {});\n return Object.entries(map).sort((a, b) => b[1].value - a[1].value);\n }, [assets]);\n\n const departmentBars = useMemo<ChartDatum[]>(\n () =>\n byDepartment\n .filter(([, { value }]) => value > 0)\n .slice(0, 8)\n .map(([dept, { value }]) => ({ department: dept, value: Math.round(value) })),\n [byDepartment]\n );\n\n const byCategory = useMemo(() => countBy(assets, (a) => titleCase(a.category)), [assets]);\n const byStatus = useMemo(() => countBy(assets, (a) => a.status), [assets]);\n const byCondition = useMemo(() => countBy(assets, (a) => a.condition), [assets]);\n const byLocation = useMemo(\n () => countBy(assets, (a) => a.location.trim() || 'Unlocated'),\n [assets]\n );\n\n const categoryDonut = useMemo<DonutSlice[]>(\n () => byCategory.map(([name, value]) => ({ name, value })),\n [byCategory]\n );\n const statusBars = useMemo<ChartDatum[]>(\n () => byStatus.map(([status, count]) => ({ status: titleCase(status), count })),\n [byStatus]\n );\n const conditionBars = useMemo<ChartDatum[]>(() => {\n const order = ['excellent', 'good', 'fair', 'poor', 'damaged'];\n return [...byCondition]\n .sort((a, b) => order.indexOf(a[0]) - order.indexOf(b[0]))\n .map(([condition, count]) => ({ condition: titleCase(condition), count }));\n }, [byCondition]);\n\n const activeCount = byStatus.find(([s]) => s === 'active')?.[1] ?? 0;\n const maintenanceCount = byStatus.find(([s]) => s === 'maintenance')?.[1] ?? 0;\n const locationCount = byLocation.filter(([l]) => l !== 'Unlocated').length;\n\n return (\n <div className=\"space-y-6 p-4 sm:p-6\">\n <div>\n <p className=\"text-xs font-semibold uppercase tracking-wide text-text-muted\">\n Portfolio Overview\n </p>\n <h1 className=\"mt-1 text-2xl font-bold sm:text-3xl\">\n <GradientText>Portfolio Overview</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Management-level rollup of the entire estate — how it is distributed and what it is worth.\n </p>\n </div>\n\n <PagePurpose>\n The big-picture view of everything you own: how many assets you have and how they break down\n by category, status, condition and location. Use it to inform replacement planning and\n audit.\n </PagePurpose>\n\n {assets.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No assets in your portfolio yet\"\n description=\"Once you add assets, this overview will summarise them by category, status, condition and location.\"\n />\n ) : (\n <>\n {/* Primary value KPIs */}\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatTile\n hero\n label=\"Total Asset Value\"\n value={moneyOrDash(financialSummary?.totalAssetValue)}\n format={moneyFull}\n icon={DollarSign}\n />\n <StatTile\n label=\"Total Depreciation\"\n value={moneyOrDash(financialSummary?.totalDepreciation)}\n format={moneyFull}\n icon={TrendingDown}\n />\n <StatTile\n label=\"Total Cost of Ownership\"\n value={totalCostOfOwnership}\n format={moneyFull}\n icon={Wallet}\n />\n <StatTile\n label={tc('assethandler.portfolio.metrics.totalAssets', 'Total {{assets}}')}\n value={assets.length}\n format={formatCompact}\n icon={Package}\n />\n </div>\n\n {/* Secondary count KPIs */}\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatTile\n label=\"Active\"\n value={activeCount}\n icon={CheckCircle2}\n delta={`${assets.length > 0 ? Math.round((activeCount / assets.length) * 100) : 0}% of estate`}\n deltaTone=\"neutral\"\n />\n <StatTile label=\"In Maintenance\" value={maintenanceCount} icon={Wrench} />\n <StatTile label=\"Locations\" value={locationCount} icon={MapPin} />\n <StatTile label=\"Categories\" value={byCategory.length} icon={Layers} />\n </div>\n\n {/* Category donut + Value by department */}\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <ChartCard title=\"Assets by Category\" icon={PieChart}>\n <DonutChart\n data={categoryDonut}\n centerLabel={tc('assethandler.portfolio.donut.centerLabel', '{{assets}}')}\n centerValue={assets.length}\n height={280}\n ariaLabel=\"Assets by category\"\n />\n </ChartCard>\n\n <ChartCard\n title=\"Value by Department\"\n subtitle=\"Summed current book value\"\n icon={Building2}\n >\n {departmentBars.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No department values yet\"\n description=\"Assign departments and record current values to see this breakdown.\"\n />\n ) : (\n <BarsChart\n data={departmentBars}\n xKey=\"department\"\n series={[{ key: 'value', label: 'Current value' }]}\n orientation=\"horizontal\"\n categoryWidth={120}\n height={Math.max(220, departmentBars.length * 44)}\n valueFormat={moneyFull}\n ariaLabel=\"Current value by department\"\n />\n )}\n </ChartCard>\n </div>\n\n {/* Status bars + Location table */}\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <ChartCard title=\"Assets by Status\" icon={Activity}>\n <BarsChart\n data={statusBars}\n xKey=\"status\"\n series={[{ key: 'count', label: 'Assets' }]}\n height={260}\n ariaLabel=\"Assets by status\"\n />\n </ChartCard>\n\n <ChartCard title=\"Assets by Location\" icon={MapPin}>\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Location</TableHead>\n <TableHead className=\"text-right\">Assets</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {byLocation.map(([location, count]) => (\n <TableRow key={location}>\n <TableCell className=\"font-medium\">{location}</TableCell>\n <TableCell className=\"text-right tabular-nums\">{count}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n </ChartCard>\n </div>\n\n {/* Condition mix + Department value table */}\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <ChartCard title=\"Asset Condition Mix\" icon={Wrench}>\n <BarsChart\n data={conditionBars}\n xKey=\"condition\"\n series={[{ key: 'count', label: 'Assets' }]}\n height={260}\n ariaLabel=\"Assets by condition\"\n />\n </ChartCard>\n\n <ChartCard\n title=\"Department Breakdown\"\n subtitle=\"Assets and current value\"\n icon={Wallet}\n >\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Department</TableHead>\n <TableHead className=\"text-right\">Assets</TableHead>\n <TableHead className=\"text-right\">Current Value</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {byDepartment.map(([dept, { count, value }]) => (\n <TableRow key={dept}>\n <TableCell className=\"font-medium\">{dept}</TableCell>\n <TableCell className=\"text-right tabular-nums\">{count}</TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {moneyFull(value)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n </ChartCard>\n </div>\n\n {/* Status legend chips */}\n <ChartCard title=\"Status Breakdown\" icon={Activity}>\n <div className=\"flex flex-wrap gap-3\">\n {byStatus.map(([status, count]) => (\n <div\n key={status}\n className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-gloss-card p-3\"\n >\n <StatusPill tone={statusTone(status)}>{status.replace(/_/g, ' ')}</StatusPill>\n <span className=\"text-lg font-bold tabular-nums\">{count}</span>\n </div>\n ))}\n </div>\n </ChartCard>\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|