@burdenoff/microfe-assethandler 2026.710.1 → 2026.713.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/AssetHandlerRoot.d.ts.map +1 -1
- package/dist/assethandler/AssetHandlerRoot.js +16 -8
- package/dist/assethandler/AssetHandlerRoot.js.map +1 -1
- package/dist/assethandler/components/AttachmentsPanel.d.ts.map +1 -1
- package/dist/assethandler/components/AttachmentsPanel.js.map +1 -1
- package/dist/assethandler/pages/AssetsAssetCategoriesPage.d.ts.map +1 -1
- package/dist/assethandler/pages/AssetsAssetCategoriesPage.js +144 -137
- 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 +202 -201
- package/dist/assethandler/pages/AssetsAssetCreatePage.js.map +1 -1
- package/dist/assethandler/pages/AssetsAssetListPage.d.ts.map +1 -1
- package/dist/assethandler/pages/AssetsAssetListPage.js +135 -132
- package/dist/assethandler/pages/AssetsAssetListPage.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 +14 -2
- package/dist/assethandler/providers/AssetHandlerProvider.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 +32 -0
- package/dist/assethandler/types/index.d.ts.map +1 -1
- package/dist/services/createSandboxAssistantTransport.d.ts +8 -26
- package/dist/services/createSandboxAssistantTransport.d.ts.map +1 -1
- package/dist/services/createSandboxAssistantTransport.js +8 -114
- package/dist/services/createSandboxAssistantTransport.js.map +1 -1
- package/dist/types/assistant.d.ts.map +1 -1
- package/dist/utils/pageContext.d.ts.map +1 -1
- package/dist/utils/pageContext.js.map +1 -1
- package/package.json +2 -2
- package/dist/generated/wspace-operations.js +0 -255
- package/dist/generated/wspace-operations.js.map +0 -1
- package/dist/services/assistantApi.d.ts +0 -71
- package/dist/services/assistantApi.d.ts.map +0 -1
- package/dist/services/assistantApi.js +0 -215
- package/dist/services/assistantApi.js.map +0 -1
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
import { useAssetHandlerStore as e } from "../store/assethandlerStore.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
2
|
+
import { useAssetTerminology as t, useHiddenCategoryIds as n } from "../providers/AssetHandlerProvider.js";
|
|
3
|
+
import { useAssetHandlerNavigate as r } from "../hooks/useAssetHandlerNavigate.js";
|
|
4
|
+
import { notify as i } from "../utils/toast.js";
|
|
5
|
+
import { formatCompact as a, formatCurrencyCompact as o } from "../components/charts/chartTheme.js";
|
|
6
|
+
import { BarsChart as s } from "../components/charts/BarsChart.js";
|
|
7
|
+
import { StatTile as c } from "../components/StatTile.js";
|
|
8
|
+
import { ChartCard as l } from "../components/ChartCard.js";
|
|
8
9
|
import "../components/index.js";
|
|
9
|
-
import { useMemo as
|
|
10
|
-
import { ArrowUpDown as
|
|
11
|
-
import { Fragment as
|
|
12
|
-
import { Badge as
|
|
10
|
+
import { useMemo as u, useState as d } from "react";
|
|
11
|
+
import { ArrowUpDown as f, Box as p, Code as m, Copy as h, ExternalLink as g, Layers as _, Monitor as v, MoreHorizontal as y, Server as b, Truck as x, Wifi as S, Wrench as C } from "lucide-react";
|
|
12
|
+
import { Fragment as w, jsx as T, jsxs as E } from "react/jsx-runtime";
|
|
13
|
+
import { Badge as D, Button as O, DropdownMenu as k, DropdownMenuContent as A, DropdownMenuItem as j, DropdownMenuTrigger as M, GlassCard as N, GradientText as P, IllustratedEmptyState as F, PagePurpose as I } from "@burdenoff/fe-libs/ui";
|
|
13
14
|
//#region src/assethandler/pages/AssetsAssetCategoriesPage.tsx
|
|
14
|
-
var
|
|
15
|
-
electronics: /* @__PURE__ */
|
|
16
|
-
it_hardware: /* @__PURE__ */
|
|
17
|
-
furniture: /* @__PURE__ */
|
|
18
|
-
vehicles: /* @__PURE__ */
|
|
19
|
-
equipment: /* @__PURE__ */ C
|
|
20
|
-
software: /* @__PURE__ */
|
|
21
|
-
networking: /* @__PURE__ */
|
|
22
|
-
servers: /* @__PURE__ */
|
|
23
|
-
},
|
|
15
|
+
var L = {
|
|
16
|
+
electronics: /* @__PURE__ */ T(v, { className: "h-5 w-5" }),
|
|
17
|
+
it_hardware: /* @__PURE__ */ T(v, { className: "h-5 w-5" }),
|
|
18
|
+
furniture: /* @__PURE__ */ T(p, { className: "h-5 w-5" }),
|
|
19
|
+
vehicles: /* @__PURE__ */ T(x, { className: "h-5 w-5" }),
|
|
20
|
+
equipment: /* @__PURE__ */ T(C, { className: "h-5 w-5" }),
|
|
21
|
+
software: /* @__PURE__ */ T(m, { className: "h-5 w-5" }),
|
|
22
|
+
networking: /* @__PURE__ */ T(S, { className: "h-5 w-5" }),
|
|
23
|
+
servers: /* @__PURE__ */ T(b, { className: "h-5 w-5" })
|
|
24
|
+
}, R = {
|
|
24
25
|
electronics: "bg-status-info-bg-subtle text-status-info-text",
|
|
25
26
|
it_hardware: "bg-status-info-bg-subtle text-status-info-text",
|
|
26
27
|
furniture: "bg-status-warning-bg-subtle text-status-warning-text",
|
|
@@ -29,20 +30,20 @@ var F = {
|
|
|
29
30
|
software: "bg-action-primary-bg/10 text-action-primary-bg",
|
|
30
31
|
networking: "bg-status-info-bg-subtle text-status-info-text",
|
|
31
32
|
servers: "bg-status-error-bg-subtle text-status-error-text"
|
|
32
|
-
},
|
|
33
|
+
}, z = [
|
|
33
34
|
"bg-status-info-bg-subtle text-status-info-text",
|
|
34
35
|
"bg-status-success-bg-subtle text-status-success-text",
|
|
35
36
|
"bg-status-warning-bg-subtle text-status-warning-text",
|
|
36
37
|
"bg-status-error-bg-subtle text-status-error-text",
|
|
37
38
|
"bg-action-primary-bg/10 text-action-primary-bg"
|
|
38
39
|
];
|
|
39
|
-
function
|
|
40
|
-
if (
|
|
40
|
+
function B(e) {
|
|
41
|
+
if (R[e]) return R[e];
|
|
41
42
|
let t = 0;
|
|
42
43
|
for (let n = 0; n < e.length; n += 1) t = t * 31 + e.charCodeAt(n) >>> 0;
|
|
43
|
-
return
|
|
44
|
+
return z[t % z.length];
|
|
44
45
|
}
|
|
45
|
-
var
|
|
46
|
+
var V = [
|
|
46
47
|
{
|
|
47
48
|
key: "active",
|
|
48
49
|
label: "Active",
|
|
@@ -68,17 +69,17 @@ var z = [
|
|
|
68
69
|
label: "Disposed",
|
|
69
70
|
varName: "var(--color-text-muted)"
|
|
70
71
|
}
|
|
71
|
-
],
|
|
72
|
+
], H = {
|
|
72
73
|
excellent: 5,
|
|
73
74
|
good: 4,
|
|
74
75
|
fair: 3,
|
|
75
76
|
poor: 2,
|
|
76
77
|
damaged: 1
|
|
77
|
-
},
|
|
78
|
-
function
|
|
79
|
-
let
|
|
78
|
+
}, U = (e) => `$${e.toLocaleString()}`;
|
|
79
|
+
function W() {
|
|
80
|
+
let m = r(), v = t(), b = n(), { assets: x } = e(), S = u(() => x.filter((e) => !b.has(e.category)), [x, b]), [C, R] = d("count"), [z, W] = d("count"), G = u(() => {
|
|
80
81
|
let e = {};
|
|
81
|
-
for (let t of
|
|
82
|
+
for (let t of S) {
|
|
82
83
|
e[t.category] || (e[t.category] = {
|
|
83
84
|
key: t.category,
|
|
84
85
|
count: 0,
|
|
@@ -93,165 +94,171 @@ function H() {
|
|
|
93
94
|
for (let t of Object.values(e)) {
|
|
94
95
|
let e = 0, n = 0;
|
|
95
96
|
for (let [r, i] of Object.entries(t.conditions)) {
|
|
96
|
-
let t =
|
|
97
|
+
let t = H[r];
|
|
97
98
|
t && (e += t * i, n += i);
|
|
98
99
|
}
|
|
99
100
|
t.avgCondition = n > 0 ? e / n : 0;
|
|
100
101
|
}
|
|
101
102
|
return Object.values(e).sort((e, t) => t.count - e.count);
|
|
102
|
-
}, [
|
|
103
|
+
}, [S]), K = S.length, q = u(() => S.reduce((e, t) => e + (t.currentValue ?? t.purchaseValue ?? 0), 0), [S]), J = u(() => G.map((e) => ({
|
|
103
104
|
category: e.key.replace(/_/g, " "),
|
|
104
105
|
count: e.count,
|
|
105
106
|
value: e.totalValue
|
|
106
|
-
})), [
|
|
107
|
-
navigator.clipboard?.writeText(e),
|
|
107
|
+
})), [G]), Y = u(() => [...G].sort((e, t) => z === "count" ? t.count - e.count : t.totalValue - e.totalValue), [G, z]), X = (e) => {
|
|
108
|
+
navigator.clipboard?.writeText(e), i.success("Category copied", `"${e.replace(/_/g, " ")}" copied to clipboard.`);
|
|
108
109
|
};
|
|
109
|
-
return /* @__PURE__ */
|
|
110
|
+
return /* @__PURE__ */ E("div", {
|
|
110
111
|
className: "space-y-6 p-4 sm:p-6",
|
|
111
112
|
children: [
|
|
112
|
-
/* @__PURE__ */
|
|
113
|
+
/* @__PURE__ */ E("div", { children: [/* @__PURE__ */ T("h1", {
|
|
113
114
|
className: "text-2xl font-bold",
|
|
114
|
-
children: /* @__PURE__ */
|
|
115
|
-
}), /* @__PURE__ */
|
|
115
|
+
children: /* @__PURE__ */ E(P, { children: [v.asset, " Categories"] })
|
|
116
|
+
}), /* @__PURE__ */ E("p", {
|
|
116
117
|
className: "text-sm text-text-secondary",
|
|
117
118
|
children: [
|
|
118
|
-
|
|
119
|
+
G.length,
|
|
119
120
|
" categories · ",
|
|
120
|
-
|
|
121
|
-
" total
|
|
121
|
+
K,
|
|
122
|
+
" total",
|
|
123
|
+
" ",
|
|
124
|
+
v.assets.toLowerCase()
|
|
122
125
|
]
|
|
123
126
|
})] }),
|
|
124
|
-
/* @__PURE__ */
|
|
125
|
-
|
|
127
|
+
/* @__PURE__ */ T(I, { 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
|
+
G.length === 0 ? /* @__PURE__ */ T(F, {
|
|
126
129
|
illustration: "empty-data",
|
|
127
130
|
title: "No categories yet",
|
|
128
|
-
description:
|
|
129
|
-
}) : /* @__PURE__ */ w
|
|
130
|
-
/* @__PURE__ */
|
|
131
|
+
description: `Add ${v.assets.toLowerCase()} and they will be grouped into categories here automatically.`
|
|
132
|
+
}) : /* @__PURE__ */ E(w, { children: [
|
|
133
|
+
/* @__PURE__ */ E("div", {
|
|
131
134
|
className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
132
135
|
children: [
|
|
133
|
-
/* @__PURE__ */
|
|
136
|
+
/* @__PURE__ */ T(c, {
|
|
134
137
|
label: "Categories",
|
|
135
|
-
value:
|
|
136
|
-
format:
|
|
137
|
-
icon:
|
|
138
|
+
value: G.length,
|
|
139
|
+
format: a,
|
|
140
|
+
icon: _,
|
|
138
141
|
hero: !0
|
|
139
142
|
}),
|
|
140
|
-
/* @__PURE__ */
|
|
141
|
-
label:
|
|
142
|
-
value:
|
|
143
|
-
format:
|
|
144
|
-
icon:
|
|
143
|
+
/* @__PURE__ */ T(c, {
|
|
144
|
+
label: `${v.assets} tracked`,
|
|
145
|
+
value: K,
|
|
146
|
+
format: a,
|
|
147
|
+
icon: p
|
|
145
148
|
}),
|
|
146
|
-
/* @__PURE__ */
|
|
149
|
+
/* @__PURE__ */ T(c, {
|
|
147
150
|
label: "Total book value",
|
|
148
|
-
value:
|
|
149
|
-
format:
|
|
150
|
-
icon:
|
|
151
|
+
value: q,
|
|
152
|
+
format: o("USD"),
|
|
153
|
+
icon: p
|
|
151
154
|
})
|
|
152
155
|
]
|
|
153
156
|
}),
|
|
154
|
-
/* @__PURE__ */
|
|
157
|
+
/* @__PURE__ */ T("div", {
|
|
155
158
|
className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
|
|
156
|
-
children:
|
|
157
|
-
let n =
|
|
158
|
-
return /* @__PURE__ */
|
|
159
|
+
children: G.map((e, t) => {
|
|
160
|
+
let n = K > 0 ? Math.round(e.count / K * 100) : 0, r = V.filter((t) => (e.statuses[t.key] || 0) > 0);
|
|
161
|
+
return /* @__PURE__ */ T(N, {
|
|
159
162
|
treatment: t === 0 ? "glass" : "plain",
|
|
160
163
|
glow: t === 0,
|
|
161
164
|
className: "border-border-seam",
|
|
162
|
-
children: /* @__PURE__ */
|
|
165
|
+
children: /* @__PURE__ */ E("div", {
|
|
163
166
|
className: "space-y-4 p-5",
|
|
164
167
|
children: [
|
|
165
|
-
/* @__PURE__ */
|
|
168
|
+
/* @__PURE__ */ E("div", {
|
|
166
169
|
className: "flex items-start justify-between gap-2",
|
|
167
|
-
children: [/* @__PURE__ */
|
|
170
|
+
children: [/* @__PURE__ */ E("div", {
|
|
168
171
|
className: "flex min-w-0 items-center gap-3",
|
|
169
|
-
children: [/* @__PURE__ */
|
|
170
|
-
className: `shrink-0 rounded-lg p-2 ${
|
|
171
|
-
children:
|
|
172
|
-
}), /* @__PURE__ */
|
|
172
|
+
children: [/* @__PURE__ */ T("div", {
|
|
173
|
+
className: `shrink-0 rounded-lg p-2 ${B(e.key)}`,
|
|
174
|
+
children: L[e.key] || /* @__PURE__ */ T(p, { className: "h-5 w-5" })
|
|
175
|
+
}), /* @__PURE__ */ E("div", {
|
|
173
176
|
className: "min-w-0",
|
|
174
|
-
children: [/* @__PURE__ */
|
|
177
|
+
children: [/* @__PURE__ */ T("p", {
|
|
175
178
|
className: "truncate text-base font-semibold capitalize",
|
|
176
179
|
children: e.key.replace(/_/g, " ")
|
|
177
|
-
}), /* @__PURE__ */
|
|
180
|
+
}), /* @__PURE__ */ E(D, {
|
|
178
181
|
variant: "outline",
|
|
179
182
|
className: "mt-0.5 text-xs",
|
|
180
183
|
children: [n, "% of estate"]
|
|
181
184
|
})]
|
|
182
185
|
})]
|
|
183
|
-
}), /* @__PURE__ */
|
|
186
|
+
}), /* @__PURE__ */ E(k, { children: [/* @__PURE__ */ T(M, {
|
|
184
187
|
asChild: !0,
|
|
185
|
-
children: /* @__PURE__ */
|
|
188
|
+
children: /* @__PURE__ */ T(O, {
|
|
186
189
|
variant: "ghost",
|
|
187
190
|
size: "icon",
|
|
188
191
|
className: "h-8 w-8 shrink-0",
|
|
189
192
|
"aria-label": `Actions for ${e.key.replace(/_/g, " ")}`,
|
|
190
|
-
children: /* @__PURE__ */
|
|
193
|
+
children: /* @__PURE__ */ T(y, { className: "h-4 w-4" })
|
|
191
194
|
})
|
|
192
|
-
}), /* @__PURE__ */
|
|
195
|
+
}), /* @__PURE__ */ E(A, {
|
|
193
196
|
align: "end",
|
|
194
|
-
children: [/* @__PURE__ */
|
|
195
|
-
onClick: () =>
|
|
196
|
-
children: [
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
children: [/* @__PURE__ */ E(j, {
|
|
198
|
+
onClick: () => m("/assets/asset-list"),
|
|
199
|
+
children: [
|
|
200
|
+
/* @__PURE__ */ T(g, { className: "mr-2 h-4 w-4" }),
|
|
201
|
+
"View ",
|
|
202
|
+
v.assets.toLowerCase()
|
|
203
|
+
]
|
|
204
|
+
}), /* @__PURE__ */ E(j, {
|
|
205
|
+
onClick: () => X(e.key),
|
|
206
|
+
children: [/* @__PURE__ */ T(h, { className: "mr-2 h-4 w-4" }), "Copy category ID"]
|
|
200
207
|
})]
|
|
201
208
|
})] })]
|
|
202
209
|
}),
|
|
203
|
-
/* @__PURE__ */
|
|
210
|
+
/* @__PURE__ */ E("div", {
|
|
204
211
|
className: "flex flex-wrap items-end justify-between gap-3",
|
|
205
|
-
children: [/* @__PURE__ */
|
|
212
|
+
children: [/* @__PURE__ */ E("div", { children: [/* @__PURE__ */ E("p", {
|
|
206
213
|
className: "text-xs text-text-secondary",
|
|
207
|
-
children: "
|
|
208
|
-
}), /* @__PURE__ */
|
|
214
|
+
children: [v.asset, " count"]
|
|
215
|
+
}), /* @__PURE__ */ T("p", {
|
|
209
216
|
className: "text-2xl font-bold tabular-nums",
|
|
210
217
|
children: e.count
|
|
211
|
-
})] }), /* @__PURE__ */
|
|
218
|
+
})] }), /* @__PURE__ */ E("div", {
|
|
212
219
|
className: "text-right",
|
|
213
|
-
children: [/* @__PURE__ */
|
|
220
|
+
children: [/* @__PURE__ */ T("p", {
|
|
214
221
|
className: "text-xs text-text-secondary",
|
|
215
222
|
children: "Total value"
|
|
216
|
-
}), /* @__PURE__ */
|
|
223
|
+
}), /* @__PURE__ */ T("p", {
|
|
217
224
|
className: "text-2xl font-bold tabular-nums",
|
|
218
|
-
children:
|
|
225
|
+
children: o("USD")(e.totalValue)
|
|
219
226
|
})]
|
|
220
227
|
})]
|
|
221
228
|
}),
|
|
222
|
-
/* @__PURE__ */
|
|
229
|
+
/* @__PURE__ */ E("div", { children: [/* @__PURE__ */ T("div", {
|
|
223
230
|
className: "flex h-2.5 w-full overflow-hidden rounded-full bg-bg-sunken",
|
|
224
231
|
role: "img",
|
|
225
232
|
"aria-label": r.map((t) => `${t.label}: ${e.statuses[t.key]}`).join(", "),
|
|
226
|
-
children: r.map((t) => /* @__PURE__ */
|
|
233
|
+
children: r.map((t) => /* @__PURE__ */ T("div", { style: {
|
|
227
234
|
width: `${(e.statuses[t.key] || 0) / e.count * 100}%`,
|
|
228
235
|
backgroundColor: t.varName
|
|
229
236
|
} }, t.key))
|
|
230
|
-
}), /* @__PURE__ */
|
|
237
|
+
}), /* @__PURE__ */ T("div", {
|
|
231
238
|
className: "mt-2 flex flex-wrap gap-x-3 gap-y-1",
|
|
232
|
-
children: r.map((t) => /* @__PURE__ */
|
|
239
|
+
children: r.map((t) => /* @__PURE__ */ E("span", {
|
|
233
240
|
className: "inline-flex items-center gap-1.5 text-xs text-text-secondary",
|
|
234
241
|
children: [
|
|
235
|
-
/* @__PURE__ */
|
|
242
|
+
/* @__PURE__ */ T("span", {
|
|
236
243
|
className: "h-2.5 w-2.5 rounded-[3px]",
|
|
237
244
|
style: { backgroundColor: t.varName }
|
|
238
245
|
}),
|
|
239
246
|
t.label,
|
|
240
|
-
/* @__PURE__ */
|
|
247
|
+
/* @__PURE__ */ T("span", {
|
|
241
248
|
className: "tabular-nums text-text-muted",
|
|
242
249
|
children: e.statuses[t.key]
|
|
243
250
|
})
|
|
244
251
|
]
|
|
245
252
|
}, t.key))
|
|
246
253
|
})] }),
|
|
247
|
-
/* @__PURE__ */
|
|
254
|
+
/* @__PURE__ */ E("div", {
|
|
248
255
|
className: "flex items-center justify-between border-t border-border-seam pt-3 text-sm",
|
|
249
|
-
children: [/* @__PURE__ */
|
|
256
|
+
children: [/* @__PURE__ */ T("span", {
|
|
250
257
|
className: "text-text-secondary",
|
|
251
258
|
children: "Avg. condition"
|
|
252
|
-
}), /* @__PURE__ */
|
|
259
|
+
}), /* @__PURE__ */ E("span", {
|
|
253
260
|
className: "font-semibold tabular-nums",
|
|
254
|
-
children: [e.avgCondition > 0 ? e.avgCondition.toFixed(1) : "—", /* @__PURE__ */
|
|
261
|
+
children: [e.avgCondition > 0 ? e.avgCondition.toFixed(1) : "—", /* @__PURE__ */ T("span", {
|
|
255
262
|
className: "text-xs font-normal text-text-muted",
|
|
256
263
|
children: " / 5"
|
|
257
264
|
})]
|
|
@@ -262,92 +269,92 @@ function H() {
|
|
|
262
269
|
}, e.key);
|
|
263
270
|
})
|
|
264
271
|
}),
|
|
265
|
-
/* @__PURE__ */
|
|
272
|
+
/* @__PURE__ */ T(l, {
|
|
266
273
|
title: "Category comparison",
|
|
267
|
-
subtitle:
|
|
268
|
-
actions: /* @__PURE__ */
|
|
274
|
+
subtitle: C === "count" ? `${v.assets} per category` : "Book value per category",
|
|
275
|
+
actions: /* @__PURE__ */ E("div", {
|
|
269
276
|
className: "flex gap-1 rounded-md border border-border-seam p-0.5",
|
|
270
|
-
children: [/* @__PURE__ */
|
|
271
|
-
variant:
|
|
277
|
+
children: [/* @__PURE__ */ T(O, {
|
|
278
|
+
variant: C === "count" ? "default" : "ghost",
|
|
272
279
|
size: "sm",
|
|
273
280
|
className: "h-7 px-2 text-xs",
|
|
274
|
-
onClick: () =>
|
|
281
|
+
onClick: () => R("count"),
|
|
275
282
|
children: "Count"
|
|
276
|
-
}), /* @__PURE__ */
|
|
277
|
-
variant:
|
|
283
|
+
}), /* @__PURE__ */ T(O, {
|
|
284
|
+
variant: C === "value" ? "default" : "ghost",
|
|
278
285
|
size: "sm",
|
|
279
286
|
className: "h-7 px-2 text-xs",
|
|
280
|
-
onClick: () =>
|
|
287
|
+
onClick: () => R("value"),
|
|
281
288
|
children: "Value"
|
|
282
289
|
})]
|
|
283
290
|
}),
|
|
284
|
-
children: /* @__PURE__ */
|
|
285
|
-
data:
|
|
291
|
+
children: /* @__PURE__ */ T(s, {
|
|
292
|
+
data: J,
|
|
286
293
|
xKey: "category",
|
|
287
294
|
orientation: "horizontal",
|
|
288
|
-
height: Math.max(200,
|
|
295
|
+
height: Math.max(200, J.length * 44),
|
|
289
296
|
categoryWidth: 120,
|
|
290
|
-
valueFormat:
|
|
297
|
+
valueFormat: C === "value" ? o("USD") : a,
|
|
291
298
|
series: [{
|
|
292
|
-
key:
|
|
293
|
-
label:
|
|
299
|
+
key: C,
|
|
300
|
+
label: C === "count" ? v.assets : "Book value"
|
|
294
301
|
}]
|
|
295
302
|
})
|
|
296
303
|
}),
|
|
297
|
-
/* @__PURE__ */
|
|
304
|
+
/* @__PURE__ */ T(l, {
|
|
298
305
|
title: "Category summary",
|
|
299
306
|
subtitle: "Sortable roll-up across every category",
|
|
300
|
-
icon:
|
|
301
|
-
children: /* @__PURE__ */
|
|
307
|
+
icon: _,
|
|
308
|
+
children: /* @__PURE__ */ T("div", {
|
|
302
309
|
className: "overflow-x-auto",
|
|
303
|
-
children: /* @__PURE__ */
|
|
310
|
+
children: /* @__PURE__ */ E("table", {
|
|
304
311
|
className: "w-full min-w-[520px] text-sm",
|
|
305
|
-
children: [/* @__PURE__ */
|
|
312
|
+
children: [/* @__PURE__ */ T("thead", { children: /* @__PURE__ */ E("tr", {
|
|
306
313
|
className: "border-b border-border-seam text-left text-text-secondary",
|
|
307
314
|
children: [
|
|
308
|
-
/* @__PURE__ */
|
|
315
|
+
/* @__PURE__ */ T("th", {
|
|
309
316
|
className: "py-2 pr-4 font-medium",
|
|
310
317
|
children: "Category"
|
|
311
318
|
}),
|
|
312
|
-
/* @__PURE__ */
|
|
319
|
+
/* @__PURE__ */ T("th", {
|
|
313
320
|
className: "py-2 pr-4 font-medium",
|
|
314
|
-
children: /* @__PURE__ */
|
|
321
|
+
children: /* @__PURE__ */ E("button", {
|
|
315
322
|
type: "button",
|
|
316
323
|
className: "inline-flex items-center gap-1 hover:text-text-primary",
|
|
317
|
-
onClick: () =>
|
|
318
|
-
children: ["Count", /* @__PURE__ */
|
|
324
|
+
onClick: () => W("count"),
|
|
325
|
+
children: ["Count", /* @__PURE__ */ T(f, { className: `h-3 w-3 ${z === "count" ? "text-action-primary-bg" : ""}` })]
|
|
319
326
|
})
|
|
320
327
|
}),
|
|
321
|
-
/* @__PURE__ */
|
|
328
|
+
/* @__PURE__ */ T("th", {
|
|
322
329
|
className: "py-2 pr-4 font-medium",
|
|
323
|
-
children: /* @__PURE__ */
|
|
330
|
+
children: /* @__PURE__ */ E("button", {
|
|
324
331
|
type: "button",
|
|
325
332
|
className: "inline-flex items-center gap-1 hover:text-text-primary",
|
|
326
|
-
onClick: () =>
|
|
327
|
-
children: ["Value", /* @__PURE__ */
|
|
333
|
+
onClick: () => W("value"),
|
|
334
|
+
children: ["Value", /* @__PURE__ */ T(f, { className: `h-3 w-3 ${z === "value" ? "text-action-primary-bg" : ""}` })]
|
|
328
335
|
})
|
|
329
336
|
}),
|
|
330
|
-
/* @__PURE__ */
|
|
337
|
+
/* @__PURE__ */ T("th", {
|
|
331
338
|
className: "py-2 font-medium",
|
|
332
339
|
children: "Avg. condition"
|
|
333
340
|
})
|
|
334
341
|
]
|
|
335
|
-
}) }), /* @__PURE__ */
|
|
342
|
+
}) }), /* @__PURE__ */ T("tbody", { children: Y.map((e) => /* @__PURE__ */ E("tr", {
|
|
336
343
|
className: "border-b border-border-seam/60 last:border-0",
|
|
337
344
|
children: [
|
|
338
|
-
/* @__PURE__ */
|
|
345
|
+
/* @__PURE__ */ T("td", {
|
|
339
346
|
className: "py-2.5 pr-4 font-medium capitalize",
|
|
340
347
|
children: e.key.replace(/_/g, " ")
|
|
341
348
|
}),
|
|
342
|
-
/* @__PURE__ */
|
|
349
|
+
/* @__PURE__ */ T("td", {
|
|
343
350
|
className: "py-2.5 pr-4 tabular-nums",
|
|
344
351
|
children: e.count
|
|
345
352
|
}),
|
|
346
|
-
/* @__PURE__ */
|
|
353
|
+
/* @__PURE__ */ T("td", {
|
|
347
354
|
className: "py-2.5 pr-4 tabular-nums",
|
|
348
|
-
children:
|
|
355
|
+
children: U(e.totalValue)
|
|
349
356
|
}),
|
|
350
|
-
/* @__PURE__ */
|
|
357
|
+
/* @__PURE__ */ T("td", {
|
|
351
358
|
className: "py-2.5 tabular-nums",
|
|
352
359
|
children: e.avgCondition > 0 ? e.avgCondition.toFixed(1) : "—"
|
|
353
360
|
})
|
|
@@ -361,6 +368,6 @@ function H() {
|
|
|
361
368
|
});
|
|
362
369
|
}
|
|
363
370
|
//#endregion
|
|
364
|
-
export {
|
|
371
|
+
export { W as AssetsAssetCategoriesPage };
|
|
365
372
|
|
|
366
373
|
//# sourceMappingURL=AssetsAssetCategoriesPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssetsAssetCategoriesPage.js","names":[],"sources":["../../../src/assethandler/pages/AssetsAssetCategoriesPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Badge,\n GradientText,\n GlassCard,\n PagePurpose,\n IllustratedEmptyState,\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Box,\n Monitor,\n Server,\n Wifi,\n Truck,\n Wrench,\n Code,\n MoreHorizontal,\n Copy,\n ExternalLink,\n ArrowUpDown,\n Layers,\n} from 'lucide-react';\nimport { useAssetHandlerNavigate } from '../hooks/useAssetHandlerNavigate';\nimport { useAssetHandlerStore, type AssetStatus } from '../store/assethandlerStore';\nimport { notify } from '../utils/toast';\nimport {\n ChartCard,\n StatTile,\n BarsChart,\n formatCompact,\n formatCurrencyCompact,\n} from '../components';\n\nconst categoryIcons: Record<string, React.ReactNode> = {\n electronics: <Monitor className=\"h-5 w-5\" />,\n it_hardware: <Monitor className=\"h-5 w-5\" />,\n furniture: <Box className=\"h-5 w-5\" />,\n vehicles: <Truck className=\"h-5 w-5\" />,\n equipment: <Wrench className=\"h-5 w-5\" />,\n software: <Code className=\"h-5 w-5\" />,\n networking: <Wifi className=\"h-5 w-5\" />,\n servers: <Server className=\"h-5 w-5\" />,\n};\n\nconst categoryColors: Record<string, string> = {\n electronics: 'bg-status-info-bg-subtle text-status-info-text',\n it_hardware: 'bg-status-info-bg-subtle text-status-info-text',\n furniture: 'bg-status-warning-bg-subtle text-status-warning-text',\n vehicles: 'bg-status-success-bg-subtle text-status-success-text',\n equipment: 'bg-status-warning-bg-subtle text-status-warning-text',\n software: 'bg-action-primary-bg/10 text-action-primary-bg',\n networking: 'bg-status-info-bg-subtle text-status-info-text',\n servers: 'bg-status-error-bg-subtle text-status-error-text',\n};\n\n// Stable fallback tint for any category outside the known slug map — hashes the\n// key into the semantic palette so every real category is visually differentiated.\nconst FALLBACK_TINTS = [\n 'bg-status-info-bg-subtle text-status-info-text',\n 'bg-status-success-bg-subtle text-status-success-text',\n 'bg-status-warning-bg-subtle text-status-warning-text',\n 'bg-status-error-bg-subtle text-status-error-text',\n 'bg-action-primary-bg/10 text-action-primary-bg',\n];\nfunction tintFor(key: string): string {\n if (categoryColors[key]) return categoryColors[key];\n let hash = 0;\n for (let i = 0; i < key.length; i += 1) hash = (hash * 31 + key.charCodeAt(i)) >>> 0;\n return FALLBACK_TINTS[hash % FALLBACK_TINTS.length];\n}\n\n// Proportional status-bar segments — semantic tokens (CSS vars), theme-aware.\nconst STATUS_SEGMENTS: { key: AssetStatus; label: string; varName: string }[] = [\n { key: 'active', label: 'Active', varName: 'var(--color-status-success-bg)' },\n { key: 'maintenance', label: 'Maintenance', varName: 'var(--color-status-warning-bg)' },\n { key: 'retired', label: 'Retired', varName: 'var(--color-status-error-bg)' },\n { key: 'inactive', label: 'Inactive', varName: 'var(--color-status-info-bg)' },\n { key: 'disposed', label: 'Disposed', varName: 'var(--color-text-muted)' },\n];\n\nconst CONDITION_SCORE: Record<string, number> = {\n excellent: 5,\n good: 4,\n fair: 3,\n poor: 2,\n damaged: 1,\n};\n\ninterface CategoryAggregate {\n key: string;\n count: number;\n totalValue: number;\n statuses: Record<string, number>;\n conditions: Record<string, number>;\n avgCondition: number;\n}\n\nconst money = (value: number): string => `$${value.toLocaleString()}`;\n\nexport function AssetsAssetCategoriesPage() {\n const navigate = useAssetHandlerNavigate();\n const { assets } = useAssetHandlerStore();\n const [chartMetric, setChartMetric] = useState<'count' | 'value'>('count');\n const [sortKey, setSortKey] = useState<'count' | 'value'>('count');\n\n const aggregates = useMemo<CategoryAggregate[]>(() => {\n const map: Record<string, CategoryAggregate> = {};\n for (const asset of assets) {\n if (!map[asset.category]) {\n map[asset.category] = {\n key: asset.category,\n count: 0,\n totalValue: 0,\n statuses: {},\n conditions: {},\n avgCondition: 0,\n };\n }\n const cat = map[asset.category];\n cat.count += 1;\n cat.totalValue += asset.currentValue ?? asset.purchaseValue ?? 0;\n cat.statuses[asset.status] = (cat.statuses[asset.status] || 0) + 1;\n cat.conditions[asset.condition] = (cat.conditions[asset.condition] || 0) + 1;\n }\n for (const cat of Object.values(map)) {\n let scoreSum = 0;\n let scoreN = 0;\n for (const [cond, n] of Object.entries(cat.conditions)) {\n const score = CONDITION_SCORE[cond];\n if (score) {\n scoreSum += score * n;\n scoreN += n;\n }\n }\n cat.avgCondition = scoreN > 0 ? scoreSum / scoreN : 0;\n }\n return Object.values(map).sort((a, b) => b.count - a.count);\n }, [assets]);\n\n const totalAssets = assets.length;\n const totalValue = useMemo(\n () => assets.reduce((sum, a) => sum + (a.currentValue ?? a.purchaseValue ?? 0), 0),\n [assets]\n );\n\n const chartData = useMemo(\n () =>\n aggregates.map((cat) => ({\n category: cat.key.replace(/_/g, ' '),\n count: cat.count,\n value: cat.totalValue,\n })),\n [aggregates]\n );\n\n const sortedRows = useMemo(\n () =>\n [...aggregates].sort((a, b) =>\n sortKey === 'count' ? b.count - a.count : b.totalValue - a.totalValue\n ),\n [aggregates, sortKey]\n );\n\n const copyId = (key: string) => {\n void navigator.clipboard?.writeText(key);\n notify.success('Category copied', `\"${key.replace(/_/g, ' ')}\" copied to clipboard.`);\n };\n\n return (\n <div className=\"space-y-6 p-4 sm:p-6\">\n <div>\n <h1 className=\"text-2xl font-bold\">\n <GradientText>Asset Categories</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {aggregates.length} categories · {totalAssets} total assets\n </p>\n </div>\n\n <PagePurpose>\n Group and compare your assets by category to see where value and volume are concentrated.\n Each card rolls up how many assets you hold, their total book value and a proportional\n status mix — so you can plan procurement and replacements by category, not one asset at a\n time.\n </PagePurpose>\n\n {aggregates.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No categories yet\"\n description=\"Add assets and they will be grouped into categories here automatically.\"\n />\n ) : (\n <>\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n <StatTile\n label=\"Categories\"\n value={aggregates.length}\n format={formatCompact}\n icon={Layers}\n hero\n />\n <StatTile\n label=\"Assets tracked\"\n value={totalAssets}\n format={formatCompact}\n icon={Box}\n />\n <StatTile\n label=\"Total book value\"\n value={totalValue}\n format={formatCurrencyCompact('USD')}\n icon={Box}\n />\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {aggregates.map((cat, idx) => {\n const pct = totalAssets > 0 ? Math.round((cat.count / totalAssets) * 100) : 0;\n const segments = STATUS_SEGMENTS.filter((s) => (cat.statuses[s.key] || 0) > 0);\n return (\n <GlassCard\n key={cat.key}\n treatment={idx === 0 ? 'glass' : 'plain'}\n glow={idx === 0}\n className=\"border-border-seam\"\n >\n <div className=\"space-y-4 p-5\">\n {/* Header: icon + title + overflow */}\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex min-w-0 items-center gap-3\">\n <div className={`shrink-0 rounded-lg p-2 ${tintFor(cat.key)}`}>\n {categoryIcons[cat.key] || <Box className=\"h-5 w-5\" />}\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate text-base font-semibold capitalize\">\n {cat.key.replace(/_/g, ' ')}\n </p>\n <Badge variant=\"outline\" className=\"mt-0.5 text-xs\">\n {pct}% of estate\n </Badge>\n </div>\n </div>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8 shrink-0\"\n aria-label={`Actions for ${cat.key.replace(/_/g, ' ')}`}\n >\n <MoreHorizontal className=\"h-4 w-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => navigate('/assets/asset-list')}>\n <ExternalLink className=\"mr-2 h-4 w-4\" />\n View assets\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => copyId(cat.key)}>\n <Copy className=\"mr-2 h-4 w-4\" />\n Copy category ID\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n\n {/* Dual metric row */}\n <div className=\"flex flex-wrap items-end justify-between gap-3\">\n <div>\n <p className=\"text-xs text-text-secondary\">Asset count</p>\n <p className=\"text-2xl font-bold tabular-nums\">{cat.count}</p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-xs text-text-secondary\">Total value</p>\n <p className=\"text-2xl font-bold tabular-nums\">\n {formatCurrencyCompact('USD')(cat.totalValue)}\n </p>\n </div>\n </div>\n\n {/* Proportional status bar */}\n <div>\n <div\n className=\"flex h-2.5 w-full overflow-hidden rounded-full bg-bg-sunken\"\n role=\"img\"\n aria-label={segments\n .map((s) => `${s.label}: ${cat.statuses[s.key]}`)\n .join(', ')}\n >\n {segments.map((s) => (\n <div\n key={s.key}\n style={{\n width: `${((cat.statuses[s.key] || 0) / cat.count) * 100}%`,\n backgroundColor: s.varName,\n }}\n />\n ))}\n </div>\n <div className=\"mt-2 flex flex-wrap gap-x-3 gap-y-1\">\n {segments.map((s) => (\n <span\n key={s.key}\n className=\"inline-flex items-center gap-1.5 text-xs text-text-secondary\"\n >\n <span\n className=\"h-2.5 w-2.5 rounded-[3px]\"\n style={{ backgroundColor: s.varName }}\n />\n {s.label}\n <span className=\"tabular-nums text-text-muted\">\n {cat.statuses[s.key]}\n </span>\n </span>\n ))}\n </div>\n </div>\n\n {/* Avg condition */}\n <div className=\"flex items-center justify-between border-t border-border-seam pt-3 text-sm\">\n <span className=\"text-text-secondary\">Avg. condition</span>\n <span className=\"font-semibold tabular-nums\">\n {cat.avgCondition > 0 ? cat.avgCondition.toFixed(1) : '—'}\n <span className=\"text-xs font-normal text-text-muted\"> / 5</span>\n </span>\n </div>\n </div>\n </GlassCard>\n );\n })}\n </div>\n\n {/* Cross-category comparison chart */}\n <ChartCard\n title=\"Category comparison\"\n subtitle={chartMetric === 'count' ? 'Assets per category' : 'Book value per category'}\n actions={\n <div className=\"flex gap-1 rounded-md border border-border-seam p-0.5\">\n <Button\n variant={chartMetric === 'count' ? 'default' : 'ghost'}\n size=\"sm\"\n className=\"h-7 px-2 text-xs\"\n onClick={() => setChartMetric('count')}\n >\n Count\n </Button>\n <Button\n variant={chartMetric === 'value' ? 'default' : 'ghost'}\n size=\"sm\"\n className=\"h-7 px-2 text-xs\"\n onClick={() => setChartMetric('value')}\n >\n Value\n </Button>\n </div>\n }\n >\n <BarsChart\n data={chartData}\n xKey=\"category\"\n orientation=\"horizontal\"\n height={Math.max(200, chartData.length * 44)}\n categoryWidth={120}\n valueFormat={chartMetric === 'value' ? formatCurrencyCompact('USD') : formatCompact}\n series={[\n {\n key: chartMetric,\n label: chartMetric === 'count' ? 'Assets' : 'Book value',\n },\n ]}\n />\n </ChartCard>\n\n {/* Summary comparison table */}\n <ChartCard\n title=\"Category summary\"\n subtitle=\"Sortable roll-up across every category\"\n icon={Layers}\n >\n <div className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[520px] text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left text-text-secondary\">\n <th className=\"py-2 pr-4 font-medium\">Category</th>\n <th className=\"py-2 pr-4 font-medium\">\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1 hover:text-text-primary\"\n onClick={() => setSortKey('count')}\n >\n Count\n <ArrowUpDown\n className={`h-3 w-3 ${sortKey === 'count' ? 'text-action-primary-bg' : ''}`}\n />\n </button>\n </th>\n <th className=\"py-2 pr-4 font-medium\">\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1 hover:text-text-primary\"\n onClick={() => setSortKey('value')}\n >\n Value\n <ArrowUpDown\n className={`h-3 w-3 ${sortKey === 'value' ? 'text-action-primary-bg' : ''}`}\n />\n </button>\n </th>\n <th className=\"py-2 font-medium\">Avg. condition</th>\n </tr>\n </thead>\n <tbody>\n {sortedRows.map((cat) => (\n <tr key={cat.key} className=\"border-b border-border-seam/60 last:border-0\">\n <td className=\"py-2.5 pr-4 font-medium capitalize\">\n {cat.key.replace(/_/g, ' ')}\n </td>\n <td className=\"py-2.5 pr-4 tabular-nums\">{cat.count}</td>\n <td className=\"py-2.5 pr-4 tabular-nums\">{money(cat.totalValue)}</td>\n <td className=\"py-2.5 tabular-nums\">\n {cat.avgCondition > 0 ? cat.avgCondition.toFixed(1) : '—'}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </ChartCard>\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AssetsAssetCategoriesPage.js","names":[],"sources":["../../../src/assethandler/pages/AssetsAssetCategoriesPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Badge,\n GradientText,\n GlassCard,\n PagePurpose,\n IllustratedEmptyState,\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Box,\n Monitor,\n Server,\n Wifi,\n Truck,\n Wrench,\n Code,\n MoreHorizontal,\n Copy,\n ExternalLink,\n ArrowUpDown,\n Layers,\n} from 'lucide-react';\nimport { useAssetHandlerNavigate } from '../hooks/useAssetHandlerNavigate';\nimport { useAssetTerminology, useHiddenCategoryIds } from '../providers/AssetHandlerProvider';\nimport { useAssetHandlerStore, type AssetStatus } from '../store/assethandlerStore';\nimport { notify } from '../utils/toast';\nimport {\n ChartCard,\n StatTile,\n BarsChart,\n formatCompact,\n formatCurrencyCompact,\n} from '../components';\n\nconst categoryIcons: Record<string, React.ReactNode> = {\n electronics: <Monitor className=\"h-5 w-5\" />,\n it_hardware: <Monitor className=\"h-5 w-5\" />,\n furniture: <Box className=\"h-5 w-5\" />,\n vehicles: <Truck className=\"h-5 w-5\" />,\n equipment: <Wrench className=\"h-5 w-5\" />,\n software: <Code className=\"h-5 w-5\" />,\n networking: <Wifi className=\"h-5 w-5\" />,\n servers: <Server className=\"h-5 w-5\" />,\n};\n\nconst categoryColors: Record<string, string> = {\n electronics: 'bg-status-info-bg-subtle text-status-info-text',\n it_hardware: 'bg-status-info-bg-subtle text-status-info-text',\n furniture: 'bg-status-warning-bg-subtle text-status-warning-text',\n vehicles: 'bg-status-success-bg-subtle text-status-success-text',\n equipment: 'bg-status-warning-bg-subtle text-status-warning-text',\n software: 'bg-action-primary-bg/10 text-action-primary-bg',\n networking: 'bg-status-info-bg-subtle text-status-info-text',\n servers: 'bg-status-error-bg-subtle text-status-error-text',\n};\n\n// Stable fallback tint for any category outside the known slug map — hashes the\n// key into the semantic palette so every real category is visually differentiated.\nconst FALLBACK_TINTS = [\n 'bg-status-info-bg-subtle text-status-info-text',\n 'bg-status-success-bg-subtle text-status-success-text',\n 'bg-status-warning-bg-subtle text-status-warning-text',\n 'bg-status-error-bg-subtle text-status-error-text',\n 'bg-action-primary-bg/10 text-action-primary-bg',\n];\nfunction tintFor(key: string): string {\n if (categoryColors[key]) return categoryColors[key];\n let hash = 0;\n for (let i = 0; i < key.length; i += 1) hash = (hash * 31 + key.charCodeAt(i)) >>> 0;\n return FALLBACK_TINTS[hash % FALLBACK_TINTS.length];\n}\n\n// Proportional status-bar segments — semantic tokens (CSS vars), theme-aware.\nconst STATUS_SEGMENTS: { key: AssetStatus; label: string; varName: string }[] = [\n { key: 'active', label: 'Active', varName: 'var(--color-status-success-bg)' },\n { key: 'maintenance', label: 'Maintenance', varName: 'var(--color-status-warning-bg)' },\n { key: 'retired', label: 'Retired', varName: 'var(--color-status-error-bg)' },\n { key: 'inactive', label: 'Inactive', varName: 'var(--color-status-info-bg)' },\n { key: 'disposed', label: 'Disposed', varName: 'var(--color-text-muted)' },\n];\n\nconst CONDITION_SCORE: Record<string, number> = {\n excellent: 5,\n good: 4,\n fair: 3,\n poor: 2,\n damaged: 1,\n};\n\ninterface CategoryAggregate {\n key: string;\n count: number;\n totalValue: number;\n statuses: Record<string, number>;\n conditions: Record<string, number>;\n avgCondition: number;\n}\n\nconst money = (value: number): string => `$${value.toLocaleString()}`;\n\nexport function AssetsAssetCategoriesPage() {\n const navigate = useAssetHandlerNavigate();\n const term = useAssetTerminology();\n const hiddenCategories = useHiddenCategoryIds();\n const { assets: allAssets } = useAssetHandlerStore();\n const assets = useMemo(\n () => allAssets.filter((a) => !hiddenCategories.has(a.category)),\n [allAssets, hiddenCategories]\n );\n const [chartMetric, setChartMetric] = useState<'count' | 'value'>('count');\n const [sortKey, setSortKey] = useState<'count' | 'value'>('count');\n\n const aggregates = useMemo<CategoryAggregate[]>(() => {\n const map: Record<string, CategoryAggregate> = {};\n for (const asset of assets) {\n if (!map[asset.category]) {\n map[asset.category] = {\n key: asset.category,\n count: 0,\n totalValue: 0,\n statuses: {},\n conditions: {},\n avgCondition: 0,\n };\n }\n const cat = map[asset.category];\n cat.count += 1;\n cat.totalValue += asset.currentValue ?? asset.purchaseValue ?? 0;\n cat.statuses[asset.status] = (cat.statuses[asset.status] || 0) + 1;\n cat.conditions[asset.condition] = (cat.conditions[asset.condition] || 0) + 1;\n }\n for (const cat of Object.values(map)) {\n let scoreSum = 0;\n let scoreN = 0;\n for (const [cond, n] of Object.entries(cat.conditions)) {\n const score = CONDITION_SCORE[cond];\n if (score) {\n scoreSum += score * n;\n scoreN += n;\n }\n }\n cat.avgCondition = scoreN > 0 ? scoreSum / scoreN : 0;\n }\n return Object.values(map).sort((a, b) => b.count - a.count);\n }, [assets]);\n\n const totalAssets = assets.length;\n const totalValue = useMemo(\n () => assets.reduce((sum, a) => sum + (a.currentValue ?? a.purchaseValue ?? 0), 0),\n [assets]\n );\n\n const chartData = useMemo(\n () =>\n aggregates.map((cat) => ({\n category: cat.key.replace(/_/g, ' '),\n count: cat.count,\n value: cat.totalValue,\n })),\n [aggregates]\n );\n\n const sortedRows = useMemo(\n () =>\n [...aggregates].sort((a, b) =>\n sortKey === 'count' ? b.count - a.count : b.totalValue - a.totalValue\n ),\n [aggregates, sortKey]\n );\n\n const copyId = (key: string) => {\n void navigator.clipboard?.writeText(key);\n notify.success('Category copied', `\"${key.replace(/_/g, ' ')}\" copied to clipboard.`);\n };\n\n return (\n <div className=\"space-y-6 p-4 sm:p-6\">\n <div>\n <h1 className=\"text-2xl font-bold\">\n <GradientText>{term.asset} Categories</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {aggregates.length} categories · {totalAssets} total{' '}\n {term.assets.toLowerCase()}\n </p>\n </div>\n\n <PagePurpose>\n Group and compare your assets by category to see where value and volume are concentrated.\n Each card rolls up how many assets you hold, their total book value and a proportional\n status mix — so you can plan procurement and replacements by category, not one asset at a\n time.\n </PagePurpose>\n\n {aggregates.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No categories yet\"\n description={`Add ${term.assets.toLowerCase()} and they will be grouped into categories here automatically.`}\n />\n ) : (\n <>\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n <StatTile\n label=\"Categories\"\n value={aggregates.length}\n format={formatCompact}\n icon={Layers}\n hero\n />\n <StatTile\n label={`${term.assets} tracked`}\n value={totalAssets}\n format={formatCompact}\n icon={Box}\n />\n <StatTile\n label=\"Total book value\"\n value={totalValue}\n format={formatCurrencyCompact('USD')}\n icon={Box}\n />\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {aggregates.map((cat, idx) => {\n const pct = totalAssets > 0 ? Math.round((cat.count / totalAssets) * 100) : 0;\n const segments = STATUS_SEGMENTS.filter((s) => (cat.statuses[s.key] || 0) > 0);\n return (\n <GlassCard\n key={cat.key}\n treatment={idx === 0 ? 'glass' : 'plain'}\n glow={idx === 0}\n className=\"border-border-seam\"\n >\n <div className=\"space-y-4 p-5\">\n {/* Header: icon + title + overflow */}\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex min-w-0 items-center gap-3\">\n <div className={`shrink-0 rounded-lg p-2 ${tintFor(cat.key)}`}>\n {categoryIcons[cat.key] || <Box className=\"h-5 w-5\" />}\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate text-base font-semibold capitalize\">\n {cat.key.replace(/_/g, ' ')}\n </p>\n <Badge variant=\"outline\" className=\"mt-0.5 text-xs\">\n {pct}% of estate\n </Badge>\n </div>\n </div>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8 shrink-0\"\n aria-label={`Actions for ${cat.key.replace(/_/g, ' ')}`}\n >\n <MoreHorizontal className=\"h-4 w-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => navigate('/assets/asset-list')}>\n <ExternalLink className=\"mr-2 h-4 w-4\" />\n View {term.assets.toLowerCase()}\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => copyId(cat.key)}>\n <Copy className=\"mr-2 h-4 w-4\" />\n Copy category ID\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n\n {/* Dual metric row */}\n <div className=\"flex flex-wrap items-end justify-between gap-3\">\n <div>\n <p className=\"text-xs text-text-secondary\">{term.asset} count</p>\n <p className=\"text-2xl font-bold tabular-nums\">{cat.count}</p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-xs text-text-secondary\">Total value</p>\n <p className=\"text-2xl font-bold tabular-nums\">\n {formatCurrencyCompact('USD')(cat.totalValue)}\n </p>\n </div>\n </div>\n\n {/* Proportional status bar */}\n <div>\n <div\n className=\"flex h-2.5 w-full overflow-hidden rounded-full bg-bg-sunken\"\n role=\"img\"\n aria-label={segments\n .map((s) => `${s.label}: ${cat.statuses[s.key]}`)\n .join(', ')}\n >\n {segments.map((s) => (\n <div\n key={s.key}\n style={{\n width: `${((cat.statuses[s.key] || 0) / cat.count) * 100}%`,\n backgroundColor: s.varName,\n }}\n />\n ))}\n </div>\n <div className=\"mt-2 flex flex-wrap gap-x-3 gap-y-1\">\n {segments.map((s) => (\n <span\n key={s.key}\n className=\"inline-flex items-center gap-1.5 text-xs text-text-secondary\"\n >\n <span\n className=\"h-2.5 w-2.5 rounded-[3px]\"\n style={{ backgroundColor: s.varName }}\n />\n {s.label}\n <span className=\"tabular-nums text-text-muted\">\n {cat.statuses[s.key]}\n </span>\n </span>\n ))}\n </div>\n </div>\n\n {/* Avg condition */}\n <div className=\"flex items-center justify-between border-t border-border-seam pt-3 text-sm\">\n <span className=\"text-text-secondary\">Avg. condition</span>\n <span className=\"font-semibold tabular-nums\">\n {cat.avgCondition > 0 ? cat.avgCondition.toFixed(1) : '—'}\n <span className=\"text-xs font-normal text-text-muted\"> / 5</span>\n </span>\n </div>\n </div>\n </GlassCard>\n );\n })}\n </div>\n\n {/* Cross-category comparison chart */}\n <ChartCard\n title=\"Category comparison\"\n subtitle={\n chartMetric === 'count'\n ? `${term.assets} per category`\n : 'Book value per category'\n }\n actions={\n <div className=\"flex gap-1 rounded-md border border-border-seam p-0.5\">\n <Button\n variant={chartMetric === 'count' ? 'default' : 'ghost'}\n size=\"sm\"\n className=\"h-7 px-2 text-xs\"\n onClick={() => setChartMetric('count')}\n >\n Count\n </Button>\n <Button\n variant={chartMetric === 'value' ? 'default' : 'ghost'}\n size=\"sm\"\n className=\"h-7 px-2 text-xs\"\n onClick={() => setChartMetric('value')}\n >\n Value\n </Button>\n </div>\n }\n >\n <BarsChart\n data={chartData}\n xKey=\"category\"\n orientation=\"horizontal\"\n height={Math.max(200, chartData.length * 44)}\n categoryWidth={120}\n valueFormat={chartMetric === 'value' ? formatCurrencyCompact('USD') : formatCompact}\n series={[\n {\n key: chartMetric,\n label: chartMetric === 'count' ? term.assets : 'Book value',\n },\n ]}\n />\n </ChartCard>\n\n {/* Summary comparison table */}\n <ChartCard\n title=\"Category summary\"\n subtitle=\"Sortable roll-up across every category\"\n icon={Layers}\n >\n <div className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[520px] text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left text-text-secondary\">\n <th className=\"py-2 pr-4 font-medium\">Category</th>\n <th className=\"py-2 pr-4 font-medium\">\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1 hover:text-text-primary\"\n onClick={() => setSortKey('count')}\n >\n Count\n <ArrowUpDown\n className={`h-3 w-3 ${sortKey === 'count' ? 'text-action-primary-bg' : ''}`}\n />\n </button>\n </th>\n <th className=\"py-2 pr-4 font-medium\">\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1 hover:text-text-primary\"\n onClick={() => setSortKey('value')}\n >\n Value\n <ArrowUpDown\n className={`h-3 w-3 ${sortKey === 'value' ? 'text-action-primary-bg' : ''}`}\n />\n </button>\n </th>\n <th className=\"py-2 font-medium\">Avg. condition</th>\n </tr>\n </thead>\n <tbody>\n {sortedRows.map((cat) => (\n <tr key={cat.key} className=\"border-b border-border-seam/60 last:border-0\">\n <td className=\"py-2.5 pr-4 font-medium capitalize\">\n {cat.key.replace(/_/g, ' ')}\n </td>\n <td className=\"py-2.5 pr-4 tabular-nums\">{cat.count}</td>\n <td className=\"py-2.5 pr-4 tabular-nums\">{money(cat.totalValue)}</td>\n <td className=\"py-2.5 tabular-nums\">\n {cat.avgCondition > 0 ? cat.avgCondition.toFixed(1) : '—'}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </ChartCard>\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssetsAssetCreatePage.d.ts","sourceRoot":"","sources":["../../../src/assethandler/pages/AssetsAssetCreatePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AssetsAssetCreatePage.d.ts","sourceRoot":"","sources":["../../../src/assethandler/pages/AssetsAssetCreatePage.tsx"],"names":[],"mappings":"AA0IA,wBAAgB,qBAAqB,gCAufpC"}
|