@burdenoff/microfe-assethandler 2026.801.1 → 2026.810.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/dist/assethandler/pages/AssetsAssetCategoriesPage.d.ts.map +1 -1
  2. package/dist/assethandler/pages/AssetsAssetCategoriesPage.js +148 -147
  3. package/dist/assethandler/pages/AssetsAssetCategoriesPage.js.map +1 -1
  4. package/dist/assethandler/pages/AssetsAssetCreatePage.d.ts.map +1 -1
  5. package/dist/assethandler/pages/AssetsAssetCreatePage.js +229 -224
  6. package/dist/assethandler/pages/AssetsAssetCreatePage.js.map +1 -1
  7. package/dist/assethandler/pages/AssetsAssetDetailPage.d.ts.map +1 -1
  8. package/dist/assethandler/pages/AssetsAssetDetailPage.js +295 -293
  9. package/dist/assethandler/pages/AssetsAssetDetailPage.js.map +1 -1
  10. package/dist/assethandler/pages/AssetsAssetListPage.d.ts.map +1 -1
  11. package/dist/assethandler/pages/AssetsAssetListPage.js +97 -95
  12. package/dist/assethandler/pages/AssetsAssetListPage.js.map +1 -1
  13. package/dist/assethandler/providers/AssetHandlerProvider.d.ts +5 -0
  14. package/dist/assethandler/providers/AssetHandlerProvider.d.ts.map +1 -1
  15. package/dist/assethandler/providers/AssetHandlerProvider.js +9 -2
  16. package/dist/assethandler/providers/AssetHandlerProvider.js.map +1 -1
  17. package/dist/assethandler/types/index.d.ts +8 -0
  18. package/dist/assethandler/types/index.d.ts.map +1 -1
  19. package/dist/assethandler/utils/categoryPresentation.d.ts +2 -0
  20. package/dist/assethandler/utils/categoryPresentation.d.ts.map +1 -0
  21. package/dist/assethandler/utils/categoryPresentation.js +8 -0
  22. package/dist/assethandler/utils/categoryPresentation.js.map +1 -0
  23. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"AssetsAssetCategoriesPage.d.ts","sourceRoot":"","sources":["../../../src/assethandler/pages/AssetsAssetCategoriesPage.tsx"],"names":[],"mappings":"AAyGA,wBAAgB,yBAAyB,gCAsVxC"}
1
+ {"version":3,"file":"AssetsAssetCategoriesPage.d.ts","sourceRoot":"","sources":["../../../src/assethandler/pages/AssetsAssetCategoriesPage.tsx"],"names":[],"mappings":"AAyGA,wBAAgB,yBAAyB,gCAuVxC"}
@@ -1,27 +1,28 @@
1
1
  import { useAssetHandlerStore as e } from "../store/assethandlerStore.js";
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";
2
+ import { formatAssetCategory as t } from "../utils/categoryPresentation.js";
3
+ import { useAssetCategoryPresentation as n, useAssetTerminology as r, useHiddenCategoryIds as i } from "../providers/AssetHandlerProvider.js";
4
+ import { useAssetHandlerNavigate as a } from "../hooks/useAssetHandlerNavigate.js";
5
+ import { notify as o } from "../utils/toast.js";
6
+ import { formatCompact as s, formatCurrencyCompact as c } from "../components/charts/chartTheme.js";
7
+ import { BarsChart as l } from "../components/charts/BarsChart.js";
8
+ import { StatTile as u } from "../components/StatTile.js";
9
+ import { ChartCard as d } from "../components/ChartCard.js";
9
10
  import "../components/index.js";
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";
11
+ import { useMemo as f, useState as p } from "react";
12
+ import { ArrowUpDown as m, Box as h, Code as g, Copy as _, ExternalLink as v, Layers as y, Monitor as b, MoreHorizontal as x, Server as S, Truck as C, Wifi as w, Wrench as T } from "lucide-react";
13
+ import { Fragment as E, jsx as D, jsxs as O } from "react/jsx-runtime";
14
+ import { Badge as k, Button as A, DropdownMenu as j, DropdownMenuContent as M, DropdownMenuItem as N, DropdownMenuTrigger as P, GlassCard as F, GradientText as I, IllustratedEmptyState as L, PagePurpose as R } from "@burdenoff/fe-libs/ui";
14
15
  //#region src/assethandler/pages/AssetsAssetCategoriesPage.tsx
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 = {
16
+ var z = {
17
+ electronics: /* @__PURE__ */ D(b, { className: "h-5 w-5" }),
18
+ it_hardware: /* @__PURE__ */ D(b, { className: "h-5 w-5" }),
19
+ furniture: /* @__PURE__ */ D(h, { className: "h-5 w-5" }),
20
+ vehicles: /* @__PURE__ */ D(C, { className: "h-5 w-5" }),
21
+ equipment: /* @__PURE__ */ D(T, { className: "h-5 w-5" }),
22
+ software: /* @__PURE__ */ D(g, { className: "h-5 w-5" }),
23
+ networking: /* @__PURE__ */ D(w, { className: "h-5 w-5" }),
24
+ servers: /* @__PURE__ */ D(S, { className: "h-5 w-5" })
25
+ }, B = {
25
26
  electronics: "bg-status-info-bg-subtle text-status-info-text",
26
27
  it_hardware: "bg-status-info-bg-subtle text-status-info-text",
27
28
  furniture: "bg-status-warning-bg-subtle text-status-warning-text",
@@ -30,20 +31,20 @@ var L = {
30
31
  software: "bg-action-primary-bg/10 text-action-primary-bg",
31
32
  networking: "bg-status-info-bg-subtle text-status-info-text",
32
33
  servers: "bg-status-error-bg-subtle text-status-error-text"
33
- }, z = [
34
+ }, V = [
34
35
  "bg-status-info-bg-subtle text-status-info-text",
35
36
  "bg-status-success-bg-subtle text-status-success-text",
36
37
  "bg-status-warning-bg-subtle text-status-warning-text",
37
38
  "bg-status-error-bg-subtle text-status-error-text",
38
39
  "bg-action-primary-bg/10 text-action-primary-bg"
39
40
  ];
40
- function B(e) {
41
- if (R[e]) return R[e];
41
+ function H(e) {
42
+ if (B[e]) return B[e];
42
43
  let t = 0;
43
44
  for (let n = 0; n < e.length; n += 1) t = t * 31 + e.charCodeAt(n) >>> 0;
44
- return z[t % z.length];
45
+ return V[t % V.length];
45
46
  }
46
- var V = [
47
+ var U = [
47
48
  {
48
49
  key: "active",
49
50
  label: "Active",
@@ -69,17 +70,17 @@ var V = [
69
70
  label: "Disposed",
70
71
  varName: "var(--color-text-muted)"
71
72
  }
72
- ], H = {
73
+ ], W = {
73
74
  excellent: 5,
74
75
  good: 4,
75
76
  fair: 3,
76
77
  poor: 2,
77
78
  damaged: 1
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(() => {
79
+ }, G = (e) => `$${e.toLocaleString()}`;
80
+ function K() {
81
+ let g = a(), b = r(), S = i(), { labels: C } = n(), { assets: w } = e(), T = f(() => w.filter((e) => !S.has(e.category)), [w, S]), [B, V] = p("count"), [K, q] = p("count"), J = f(() => {
81
82
  let e = {};
82
- for (let t of S) {
83
+ for (let t of T) {
83
84
  e[t.category] || (e[t.category] = {
84
85
  key: t.category,
85
86
  count: 0,
@@ -94,170 +95,170 @@ function W() {
94
95
  for (let t of Object.values(e)) {
95
96
  let e = 0, n = 0;
96
97
  for (let [r, i] of Object.entries(t.conditions)) {
97
- let t = H[r];
98
+ let t = W[r];
98
99
  t && (e += t * i, n += i);
99
100
  }
100
101
  t.avgCondition = n > 0 ? e / n : 0;
101
102
  }
102
103
  return Object.values(e).sort((e, t) => t.count - e.count);
103
- }, [S]), K = S.length, q = u(() => S.reduce((e, t) => e + (t.currentValue ?? t.purchaseValue ?? 0), 0), [S]), J = u(() => G.map((e) => ({
104
- category: e.key.replace(/_/g, " "),
104
+ }, [T]), Y = T.length, X = f(() => T.reduce((e, t) => e + (t.currentValue ?? t.purchaseValue ?? 0), 0), [T]), Z = f(() => J.map((e) => ({
105
+ category: t(e.key, C),
105
106
  count: e.count,
106
107
  value: e.totalValue
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
+ })), [J, C]), Q = f(() => [...J].sort((e, t) => K === "count" ? t.count - e.count : t.totalValue - e.totalValue), [J, K]), $ = (e) => {
109
+ navigator.clipboard?.writeText(e), o.success("Category copied", `"${t(e, C)}" copied to clipboard.`);
109
110
  };
110
- return /* @__PURE__ */ E("div", {
111
+ return /* @__PURE__ */ O("div", {
111
112
  className: "space-y-6 p-4 sm:p-6",
112
113
  children: [
113
- /* @__PURE__ */ E("div", { children: [/* @__PURE__ */ T("h1", {
114
+ /* @__PURE__ */ O("div", { children: [/* @__PURE__ */ D("h1", {
114
115
  className: "text-2xl font-bold",
115
- children: /* @__PURE__ */ E(P, { children: [v.asset, " Categories"] })
116
- }), /* @__PURE__ */ E("p", {
116
+ children: /* @__PURE__ */ O(I, { children: [b.asset, " Categories"] })
117
+ }), /* @__PURE__ */ O("p", {
117
118
  className: "text-sm text-text-secondary",
118
119
  children: [
119
- G.length,
120
+ J.length,
120
121
  " categories · ",
121
- K,
122
+ Y,
122
123
  " total ",
123
- v.assets.toLowerCase()
124
+ b.assets.toLowerCase()
124
125
  ]
125
126
  })] }),
126
- /* @__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." }),
127
- G.length === 0 ? /* @__PURE__ */ T(F, {
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
+ J.length === 0 ? /* @__PURE__ */ D(L, {
128
129
  illustration: "empty-data",
129
130
  title: "No categories yet",
130
- description: `Add ${v.assets.toLowerCase()} and they will be grouped into categories here automatically.`
131
- }) : /* @__PURE__ */ E(w, { children: [
132
- /* @__PURE__ */ E("div", {
131
+ description: `Add ${b.assets.toLowerCase()} and they will be grouped into categories here automatically.`
132
+ }) : /* @__PURE__ */ O(E, { children: [
133
+ /* @__PURE__ */ O("div", {
133
134
  className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3",
134
135
  children: [
135
- /* @__PURE__ */ T(c, {
136
+ /* @__PURE__ */ D(u, {
136
137
  label: "Categories",
137
- value: G.length,
138
- format: a,
139
- icon: _,
138
+ value: J.length,
139
+ format: s,
140
+ icon: y,
140
141
  hero: !0
141
142
  }),
142
- /* @__PURE__ */ T(c, {
143
- label: `${v.assets} tracked`,
144
- value: K,
145
- format: a,
146
- icon: p
143
+ /* @__PURE__ */ D(u, {
144
+ label: `${b.assets} tracked`,
145
+ value: Y,
146
+ format: s,
147
+ icon: h
147
148
  }),
148
- /* @__PURE__ */ T(c, {
149
+ /* @__PURE__ */ D(u, {
149
150
  label: "Total book value",
150
- value: q,
151
- format: o("USD"),
152
- icon: p
151
+ value: X,
152
+ format: c("USD"),
153
+ icon: h
153
154
  })
154
155
  ]
155
156
  }),
156
- /* @__PURE__ */ T("div", {
157
+ /* @__PURE__ */ D("div", {
157
158
  className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
158
- children: G.map((e, t) => {
159
- let n = K > 0 ? Math.round(e.count / K * 100) : 0, r = V.filter((t) => (e.statuses[t.key] || 0) > 0);
160
- return /* @__PURE__ */ T(N, {
161
- treatment: t === 0 ? "glass" : "plain",
162
- glow: t === 0,
159
+ children: J.map((e, n) => {
160
+ let r = Y > 0 ? Math.round(e.count / Y * 100) : 0, i = U.filter((t) => (e.statuses[t.key] || 0) > 0);
161
+ return /* @__PURE__ */ D(F, {
162
+ treatment: n === 0 ? "glass" : "plain",
163
+ glow: n === 0,
163
164
  className: "border-border-seam",
164
- children: /* @__PURE__ */ E("div", {
165
+ children: /* @__PURE__ */ O("div", {
165
166
  className: "space-y-4 p-5",
166
167
  children: [
167
- /* @__PURE__ */ E("div", {
168
+ /* @__PURE__ */ O("div", {
168
169
  className: "flex items-start justify-between gap-2",
169
- children: [/* @__PURE__ */ E("div", {
170
+ children: [/* @__PURE__ */ O("div", {
170
171
  className: "flex min-w-0 items-center gap-3",
171
- children: [/* @__PURE__ */ T("div", {
172
- className: `shrink-0 rounded-lg p-2 ${B(e.key)}`,
173
- children: L[e.key] || /* @__PURE__ */ T(p, { className: "h-5 w-5" })
174
- }), /* @__PURE__ */ E("div", {
172
+ children: [/* @__PURE__ */ D("div", {
173
+ className: `shrink-0 rounded-lg p-2 ${H(e.key)}`,
174
+ children: z[e.key] || /* @__PURE__ */ D(h, { className: "h-5 w-5" })
175
+ }), /* @__PURE__ */ O("div", {
175
176
  className: "min-w-0",
176
- children: [/* @__PURE__ */ T("p", {
177
+ children: [/* @__PURE__ */ D("p", {
177
178
  className: "truncate text-base font-semibold capitalize",
178
- children: e.key.replace(/_/g, " ")
179
- }), /* @__PURE__ */ E(D, {
179
+ children: t(e.key, C)
180
+ }), /* @__PURE__ */ O(k, {
180
181
  variant: "outline",
181
182
  className: "mt-0.5 text-xs",
182
- children: [n, "% of estate"]
183
+ children: [r, "% of estate"]
183
184
  })]
184
185
  })]
185
- }), /* @__PURE__ */ E(k, { children: [/* @__PURE__ */ T(M, {
186
+ }), /* @__PURE__ */ O(j, { children: [/* @__PURE__ */ D(P, {
186
187
  asChild: !0,
187
- children: /* @__PURE__ */ T(O, {
188
+ children: /* @__PURE__ */ D(A, {
188
189
  variant: "ghost",
189
190
  size: "icon",
190
191
  className: "h-8 w-8 shrink-0",
191
- "aria-label": `Actions for ${e.key.replace(/_/g, " ")}`,
192
- children: /* @__PURE__ */ T(y, { className: "h-4 w-4" })
192
+ "aria-label": `Actions for ${t(e.key, C)}`,
193
+ children: /* @__PURE__ */ D(x, { className: "h-4 w-4" })
193
194
  })
194
- }), /* @__PURE__ */ E(A, {
195
+ }), /* @__PURE__ */ O(M, {
195
196
  align: "end",
196
- children: [/* @__PURE__ */ E(j, {
197
- onClick: () => m("/assets/asset-list"),
197
+ children: [/* @__PURE__ */ O(N, {
198
+ onClick: () => g("/assets/asset-list"),
198
199
  children: [
199
- /* @__PURE__ */ T(g, { className: "mr-2 h-4 w-4" }),
200
+ /* @__PURE__ */ D(v, { className: "mr-2 h-4 w-4" }),
200
201
  "View ",
201
- v.assets.toLowerCase()
202
+ b.assets.toLowerCase()
202
203
  ]
203
- }), /* @__PURE__ */ E(j, {
204
- onClick: () => X(e.key),
205
- children: [/* @__PURE__ */ T(h, { className: "mr-2 h-4 w-4" }), "Copy category ID"]
204
+ }), /* @__PURE__ */ O(N, {
205
+ onClick: () => $(e.key),
206
+ children: [/* @__PURE__ */ D(_, { className: "mr-2 h-4 w-4" }), "Copy category ID"]
206
207
  })]
207
208
  })] })]
208
209
  }),
209
- /* @__PURE__ */ E("div", {
210
+ /* @__PURE__ */ O("div", {
210
211
  className: "flex flex-wrap items-end justify-between gap-3",
211
- children: [/* @__PURE__ */ E("div", { children: [/* @__PURE__ */ E("p", {
212
+ children: [/* @__PURE__ */ O("div", { children: [/* @__PURE__ */ O("p", {
212
213
  className: "text-xs text-text-secondary",
213
- children: [v.asset, " count"]
214
- }), /* @__PURE__ */ T("p", {
214
+ children: [b.asset, " count"]
215
+ }), /* @__PURE__ */ D("p", {
215
216
  className: "text-2xl font-bold tabular-nums",
216
217
  children: e.count
217
- })] }), /* @__PURE__ */ E("div", {
218
+ })] }), /* @__PURE__ */ O("div", {
218
219
  className: "text-right",
219
- children: [/* @__PURE__ */ T("p", {
220
+ children: [/* @__PURE__ */ D("p", {
220
221
  className: "text-xs text-text-secondary",
221
222
  children: "Total value"
222
- }), /* @__PURE__ */ T("p", {
223
+ }), /* @__PURE__ */ D("p", {
223
224
  className: "text-2xl font-bold tabular-nums",
224
- children: o("USD")(e.totalValue)
225
+ children: c("USD")(e.totalValue)
225
226
  })]
226
227
  })]
227
228
  }),
228
- /* @__PURE__ */ E("div", { children: [/* @__PURE__ */ T("div", {
229
+ /* @__PURE__ */ O("div", { children: [/* @__PURE__ */ D("div", {
229
230
  className: "flex h-2.5 w-full overflow-hidden rounded-full bg-bg-sunken",
230
231
  role: "img",
231
- "aria-label": r.map((t) => `${t.label}: ${e.statuses[t.key]}`).join(", "),
232
- children: r.map((t) => /* @__PURE__ */ T("div", { style: {
232
+ "aria-label": i.map((t) => `${t.label}: ${e.statuses[t.key]}`).join(", "),
233
+ children: i.map((t) => /* @__PURE__ */ D("div", { style: {
233
234
  width: `${(e.statuses[t.key] || 0) / e.count * 100}%`,
234
235
  backgroundColor: t.varName
235
236
  } }, t.key))
236
- }), /* @__PURE__ */ T("div", {
237
+ }), /* @__PURE__ */ D("div", {
237
238
  className: "mt-2 flex flex-wrap gap-x-3 gap-y-1",
238
- children: r.map((t) => /* @__PURE__ */ E("span", {
239
+ children: i.map((t) => /* @__PURE__ */ O("span", {
239
240
  className: "inline-flex items-center gap-1.5 text-xs text-text-secondary",
240
241
  children: [
241
- /* @__PURE__ */ T("span", {
242
+ /* @__PURE__ */ D("span", {
242
243
  className: "h-2.5 w-2.5 rounded-[3px]",
243
244
  style: { backgroundColor: t.varName }
244
245
  }),
245
246
  t.label,
246
- /* @__PURE__ */ T("span", {
247
+ /* @__PURE__ */ D("span", {
247
248
  className: "tabular-nums text-text-muted",
248
249
  children: e.statuses[t.key]
249
250
  })
250
251
  ]
251
252
  }, t.key))
252
253
  })] }),
253
- /* @__PURE__ */ E("div", {
254
+ /* @__PURE__ */ O("div", {
254
255
  className: "flex items-center justify-between border-t border-border-seam pt-3 text-sm",
255
- children: [/* @__PURE__ */ T("span", {
256
+ children: [/* @__PURE__ */ D("span", {
256
257
  className: "text-text-secondary",
257
258
  children: "Avg. condition"
258
- }), /* @__PURE__ */ E("span", {
259
+ }), /* @__PURE__ */ O("span", {
259
260
  className: "font-semibold tabular-nums",
260
- children: [e.avgCondition > 0 ? e.avgCondition.toFixed(1) : "—", /* @__PURE__ */ T("span", {
261
+ children: [e.avgCondition > 0 ? e.avgCondition.toFixed(1) : "—", /* @__PURE__ */ D("span", {
261
262
  className: "text-xs font-normal text-text-muted",
262
263
  children: " / 5"
263
264
  })]
@@ -268,92 +269,92 @@ function W() {
268
269
  }, e.key);
269
270
  })
270
271
  }),
271
- /* @__PURE__ */ T(l, {
272
+ /* @__PURE__ */ D(d, {
272
273
  title: "Category comparison",
273
- subtitle: C === "count" ? `${v.assets} per category` : "Book value per category",
274
- actions: /* @__PURE__ */ E("div", {
274
+ subtitle: B === "count" ? `${b.assets} per category` : "Book value per category",
275
+ actions: /* @__PURE__ */ O("div", {
275
276
  className: "flex gap-1 rounded-md border border-border-seam p-0.5",
276
- children: [/* @__PURE__ */ T(O, {
277
- variant: C === "count" ? "default" : "ghost",
277
+ children: [/* @__PURE__ */ D(A, {
278
+ variant: B === "count" ? "default" : "ghost",
278
279
  size: "sm",
279
280
  className: "h-7 px-2 text-xs",
280
- onClick: () => R("count"),
281
+ onClick: () => V("count"),
281
282
  children: "Count"
282
- }), /* @__PURE__ */ T(O, {
283
- variant: C === "value" ? "default" : "ghost",
283
+ }), /* @__PURE__ */ D(A, {
284
+ variant: B === "value" ? "default" : "ghost",
284
285
  size: "sm",
285
286
  className: "h-7 px-2 text-xs",
286
- onClick: () => R("value"),
287
+ onClick: () => V("value"),
287
288
  children: "Value"
288
289
  })]
289
290
  }),
290
- children: /* @__PURE__ */ T(s, {
291
- data: J,
291
+ children: /* @__PURE__ */ D(l, {
292
+ data: Z,
292
293
  xKey: "category",
293
294
  orientation: "horizontal",
294
- height: Math.max(200, J.length * 44),
295
+ height: Math.max(200, Z.length * 44),
295
296
  categoryWidth: 120,
296
- valueFormat: C === "value" ? o("USD") : a,
297
+ valueFormat: B === "value" ? c("USD") : s,
297
298
  series: [{
298
- key: C,
299
- label: C === "count" ? v.assets : "Book value"
299
+ key: B,
300
+ label: B === "count" ? b.assets : "Book value"
300
301
  }]
301
302
  })
302
303
  }),
303
- /* @__PURE__ */ T(l, {
304
+ /* @__PURE__ */ D(d, {
304
305
  title: "Category summary",
305
306
  subtitle: "Sortable roll-up across every category",
306
- icon: _,
307
- children: /* @__PURE__ */ T("div", {
307
+ icon: y,
308
+ children: /* @__PURE__ */ D("div", {
308
309
  className: "overflow-x-auto",
309
- children: /* @__PURE__ */ E("table", {
310
+ children: /* @__PURE__ */ O("table", {
310
311
  className: "w-full min-w-[520px] text-sm",
311
- children: [/* @__PURE__ */ T("thead", { children: /* @__PURE__ */ E("tr", {
312
+ children: [/* @__PURE__ */ D("thead", { children: /* @__PURE__ */ O("tr", {
312
313
  className: "border-b border-border-seam text-left text-text-secondary",
313
314
  children: [
314
- /* @__PURE__ */ T("th", {
315
+ /* @__PURE__ */ D("th", {
315
316
  className: "py-2 pr-4 font-medium",
316
317
  children: "Category"
317
318
  }),
318
- /* @__PURE__ */ T("th", {
319
+ /* @__PURE__ */ D("th", {
319
320
  className: "py-2 pr-4 font-medium",
320
- children: /* @__PURE__ */ E("button", {
321
+ children: /* @__PURE__ */ O("button", {
321
322
  type: "button",
322
323
  className: "inline-flex items-center gap-1 hover:text-text-primary",
323
- onClick: () => W("count"),
324
- children: ["Count", /* @__PURE__ */ T(f, { className: `h-3 w-3 ${z === "count" ? "text-action-primary-bg" : ""}` })]
324
+ onClick: () => q("count"),
325
+ children: ["Count", /* @__PURE__ */ D(m, { className: `h-3 w-3 ${K === "count" ? "text-action-primary-bg" : ""}` })]
325
326
  })
326
327
  }),
327
- /* @__PURE__ */ T("th", {
328
+ /* @__PURE__ */ D("th", {
328
329
  className: "py-2 pr-4 font-medium",
329
- children: /* @__PURE__ */ E("button", {
330
+ children: /* @__PURE__ */ O("button", {
330
331
  type: "button",
331
332
  className: "inline-flex items-center gap-1 hover:text-text-primary",
332
- onClick: () => W("value"),
333
- children: ["Value", /* @__PURE__ */ T(f, { className: `h-3 w-3 ${z === "value" ? "text-action-primary-bg" : ""}` })]
333
+ onClick: () => q("value"),
334
+ children: ["Value", /* @__PURE__ */ D(m, { className: `h-3 w-3 ${K === "value" ? "text-action-primary-bg" : ""}` })]
334
335
  })
335
336
  }),
336
- /* @__PURE__ */ T("th", {
337
+ /* @__PURE__ */ D("th", {
337
338
  className: "py-2 font-medium",
338
339
  children: "Avg. condition"
339
340
  })
340
341
  ]
341
- }) }), /* @__PURE__ */ T("tbody", { children: Y.map((e) => /* @__PURE__ */ E("tr", {
342
+ }) }), /* @__PURE__ */ D("tbody", { children: Q.map((e) => /* @__PURE__ */ O("tr", {
342
343
  className: "border-b border-border-seam/60 last:border-0",
343
344
  children: [
344
- /* @__PURE__ */ T("td", {
345
+ /* @__PURE__ */ D("td", {
345
346
  className: "py-2.5 pr-4 font-medium capitalize",
346
- children: e.key.replace(/_/g, " ")
347
+ children: t(e.key, C)
347
348
  }),
348
- /* @__PURE__ */ T("td", {
349
+ /* @__PURE__ */ D("td", {
349
350
  className: "py-2.5 pr-4 tabular-nums",
350
351
  children: e.count
351
352
  }),
352
- /* @__PURE__ */ T("td", {
353
+ /* @__PURE__ */ D("td", {
353
354
  className: "py-2.5 pr-4 tabular-nums",
354
- children: U(e.totalValue)
355
+ children: G(e.totalValue)
355
356
  }),
356
- /* @__PURE__ */ T("td", {
357
+ /* @__PURE__ */ D("td", {
357
358
  className: "py-2.5 tabular-nums",
358
359
  children: e.avgCondition > 0 ? e.avgCondition.toFixed(1) : "—"
359
360
  })
@@ -367,6 +368,6 @@ function W() {
367
368
  });
368
369
  }
369
370
  //#endregion
370
- export { W as AssetsAssetCategoriesPage };
371
+ export { K as AssetsAssetCategoriesPage };
371
372
 
372
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 { 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 &middot; {totalAssets} total {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' ? `${term.assets} per category` : '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
+ {"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 { formatAssetCategory, useAssetCategoryPresentation, 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 { labels: categoryLabels } = useAssetCategoryPresentation();\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: formatAssetCategory(cat.key, categoryLabels),\n count: cat.count,\n value: cat.totalValue,\n })),\n [aggregates, categoryLabels]\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', `\"${formatAssetCategory(key, categoryLabels)}\" 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 &middot; {totalAssets} total {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 {formatAssetCategory(cat.key, categoryLabels)}\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 ${formatAssetCategory(cat.key, categoryLabels)}`}\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' ? `${term.assets} per category` : '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 {formatAssetCategory(cat.key, categoryLabels)}\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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"}