@burdenoff/microfe-assethandler 2026.1103.3 → 2026.1107.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.
@@ -13,5 +13,7 @@
13
13
  * would let an offline scan resolve against another workspace's registry.
14
14
  */
15
15
  export declare const FIELD_ASSETS: import('graphql').DocumentNode;
16
+ /** Online lookup of one asset by its printed tag (scan or deep link). */
17
+ export declare const FIELD_ASSET_BY_TAG: import('graphql').DocumentNode;
16
18
  export declare function FieldModePage(): import("react").JSX.Element;
17
19
  //# sourceMappingURL=FieldModePage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FieldModePage.d.ts","sourceRoot":"","sources":["../../../src/assethandler/pages/FieldModePage.tsx"],"names":[],"mappings":"AAyCA;;;;;;GAMG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,gCAYxB,CAAC;AA0FF,wBAAgB,aAAa,gCAK5B"}
1
+ {"version":3,"file":"FieldModePage.d.ts","sourceRoot":"","sources":["../../../src/assethandler/pages/FieldModePage.tsx"],"names":[],"mappings":"AA0CA;;;;;;GAMG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,gCAYxB,CAAC;AAwBF,yEAAyE;AACzE,eAAO,MAAM,kBAAkB,gCAY9B,CAAC;AAsDF,wBAAgB,aAAa,gCAK5B"}
@@ -11,12 +11,13 @@ import { browserStorage as o, useFieldQueue as ie } from "../hooks/useFieldQueue
11
11
  import { fieldAssetCacheKey as ae, filterFieldAssets as oe, findFieldAsset as se, loadFieldAssets as ce, normalizeScannedTag as le, saveFieldAssets as ue, toFieldAsset as s } from "../utils/fieldAssetCache.js";
12
12
  import { useCallback as c, useEffect as l, useMemo as de, useRef as u, useState as d } from "react";
13
13
  import { ArrowDownToLine as fe, ArrowUpFromLine as pe, Camera as me, CloudOff as he, RefreshCw as ge, ScanLine as _e, Smartphone as ve, Trash2 as ye, Wifi as be } from "lucide-react";
14
+ import { useSearchParams as xe } from "react-router";
14
15
  import { gql as f } from "@apollo/client";
15
- import { useApolloClient as xe } from "@apollo/client/react";
16
+ import { useApolloClient as Se } from "@apollo/client/react";
16
17
  import { jsx as p, jsxs as m } from "react/jsx-runtime";
17
- import { Button as h, GlassCard as g, Input as _, Label as v, PagePurpose as Se } from "@burdenoff/fe-libs/ui";
18
+ import { Button as h, GlassCard as g, Input as _, Label as v, PagePurpose as Ce } from "@burdenoff/fe-libs/ui";
18
19
  //#region src/assethandler/pages/FieldModePage.tsx
19
- var Ce = f`
20
+ var we = f`
20
21
  query AssetHandlerFieldAssets {
21
22
  assets {
22
23
  id
@@ -40,7 +41,7 @@ function y(e) {
40
41
  status: e.status
41
42
  });
42
43
  }
43
- var we = f`
44
+ var Te = f`
44
45
  query AssetHandlerFieldAssetByTag($tagId: String!) {
45
46
  assetByTag(tagId: $tagId) {
46
47
  id
@@ -52,7 +53,7 @@ var we = f`
52
53
  assignedTo
53
54
  }
54
55
  }
55
- `, Te = f`
56
+ `, Ee = f`
56
57
  query AssetHandlerFieldLatestAssignment($assetId: ID!) {
57
58
  assetAssignments(assetId: $assetId, pagination: { limit: 1 }) {
58
59
  id
@@ -60,15 +61,15 @@ var we = f`
60
61
  assignedTo
61
62
  }
62
63
  }
63
- `, b = "assethandler.field.lastAssignee", Ee = 50;
64
- function De() {
64
+ `, b = "assethandler.field.lastAssignee", De = 50;
65
+ function Oe() {
65
66
  try {
66
67
  return o()?.getItem(b) ?? "";
67
68
  } catch {
68
69
  return "";
69
70
  }
70
71
  }
71
- function Oe(e) {
72
+ function ke(e) {
72
73
  try {
73
74
  o()?.setItem(b, e);
74
75
  } catch {}
@@ -78,7 +79,7 @@ function x() {
78
79
  return /* @__PURE__ */ p(S, { workspaceId: e }, e || "default");
79
80
  }
80
81
  function S({ workspaceId: s }) {
81
- let f = a(), b = xe(), { currentUser: x } = t(), S = e((e) => e.refetchBootstrap), C = ae(s), [ke] = d(() => ce(o(), C).assets), [Ae, w] = d(!1), [T, E] = d(""), [D, je] = d(""), [O, Me] = d(null), [k, A] = d(null), [j, Ne] = d(De), [M, N] = d(""), [Pe, P] = d(!1), F = u(null), I = u(null), L = u(0), [Fe, Ie] = d(null), R = u(!1), z = u(0);
82
+ let f = a(), b = Se(), { currentUser: x } = t(), S = e((e) => e.refetchBootstrap), C = ae(s), [Ae] = d(() => ce(o(), C).assets), [je, w] = d(!1), [T, E] = d(""), [D, Me] = d(""), [O, Ne] = d(null), [k, A] = d(null), [j, Pe] = d(Oe), [M, N] = d(""), [Fe, P] = d(!1), F = u(null), I = u(null), L = u(0), [Ie, Le] = d(null), R = u(!1), z = u(0);
82
83
  l(() => (R.current = !0, () => {
83
84
  R.current = !1, z.current += 1;
84
85
  }), []);
@@ -87,12 +88,12 @@ function S({ workspaceId: s }) {
87
88
  z.current += 1;
88
89
  let e = z.current;
89
90
  b.query({
90
- query: Ce,
91
+ query: we,
91
92
  fetchPolicy: "network-only"
92
93
  }).then((t) => {
93
94
  if (!R.current || z.current !== e || !t.data?.assets) return;
94
95
  let n = t.data.assets.map(y);
95
- Ie(n), ue(o(), C, n);
96
+ Le(n), ue(o(), C, n);
96
97
  }, () => {});
97
98
  }, [b, C]), V = c(async (e, t) => {
98
99
  let n = {
@@ -102,7 +103,7 @@ function S({ workspaceId: s }) {
102
103
  };
103
104
  if (I.current = e.id, A(n), t) try {
104
105
  let t = await b.query({
105
- query: Te,
106
+ query: Ee,
106
107
  variables: { assetId: e.id },
107
108
  fetchPolicy: "network-only"
108
109
  });
@@ -114,7 +115,7 @@ function S({ workspaceId: s }) {
114
115
  confirmed: !0
115
116
  });
116
117
  } catch {}
117
- }, [b]), { queue: H, pending: U, online: W, syncing: G, sendingId: K, persisted: Le, enqueue: Re, syncNow: ze, remove: Be, retry: Ve } = ie({
118
+ }, [b]), { queue: H, pending: U, online: W, syncing: G, sendingId: K, persisted: Re, enqueue: ze, syncNow: Be, remove: Ve, retry: He } = ie({
118
119
  workspaceId: s,
119
120
  client: b,
120
121
  onFlushed: (e) => {
@@ -126,8 +127,8 @@ function S({ workspaceId: s }) {
126
127
  l(() => {
127
128
  W && B();
128
129
  }, [W, B]);
129
- let q = Fe ?? ke, J = c((e) => {
130
- L.current += 1, P(!1), Me(e), N(""), V(e, W);
130
+ let q = Ie ?? Ae, J = c((e) => {
131
+ L.current += 1, P(!1), Ne(e), N(""), V(e, W);
131
132
  }, [V, W]), Y = c(async (e) => {
132
133
  let t = le(e);
133
134
  if (!t) {
@@ -148,7 +149,7 @@ function S({ workspaceId: s }) {
148
149
  P(!0);
149
150
  try {
150
151
  let e = await b.query({
151
- query: we,
152
+ query: Te,
152
153
  variables: { tagId: t },
153
154
  fetchPolicy: "network-only"
154
155
  });
@@ -171,9 +172,13 @@ function S({ workspaceId: s }) {
171
172
  W,
172
173
  J,
173
174
  f
174
- ]), He = c((e) => {
175
+ ]), [Ue] = xe(), X = Ue.get("tag")?.trim() ?? "", We = u("");
176
+ l(() => {
177
+ !X || We.current === X || (We.current = X, Y(X));
178
+ }, [X, Y]);
179
+ let Ge = c((e) => {
175
180
  w(!1), Y(e);
176
- }, [Y]), X = O ? ne(H, O.tagId, k?.checkedOut ?? !1) : !1, Z = O ? re(H, O.tagId, k?.holder ?? "") : "", Ue = (e) => {
181
+ }, [Y]), Z = O ? ne(H, O.tagId, k?.checkedOut ?? !1) : !1, Ke = O ? re(H, O.tagId, k?.holder ?? "") : "", qe = (e) => {
177
182
  if (!O) return;
178
183
  let t = j.trim();
179
184
  if (e === "CHECK_OUT" && !t) {
@@ -181,7 +186,7 @@ function S({ workspaceId: s }) {
181
186
  return;
182
187
  }
183
188
  try {
184
- Re({
189
+ ze({
185
190
  kind: e,
186
191
  tagId: O.tagId,
187
192
  assetName: O.name,
@@ -195,8 +200,8 @@ function S({ workspaceId: s }) {
195
200
  }
196
201
  throw e;
197
202
  }
198
- e === "CHECK_OUT" && Oe(t), N(""), W || r.info(f("assethandler.field.savedOffline", "Saved on this device"), f("assethandler.field.savedOfflineHint", "It will sync automatically when you are back online.")), F.current?.focus();
199
- }, Q = de(() => oe(q, D).slice(0, Ee), [q, D]), $ = (e) => e === "CHECK_OUT" ? f("assethandler.field.kind.checkOut", "Check out") : f("assethandler.field.kind.checkIn", "Check in");
203
+ e === "CHECK_OUT" && ke(t), N(""), W || r.info(f("assethandler.field.savedOffline", "Saved on this device"), f("assethandler.field.savedOfflineHint", "It will sync automatically when you are back online.")), F.current?.focus();
204
+ }, Q = de(() => oe(q, D).slice(0, De), [q, D]), $ = (e) => e === "CHECK_OUT" ? f("assethandler.field.kind.checkOut", "Check out") : f("assethandler.field.kind.checkIn", "Check in");
200
205
  return /* @__PURE__ */ m("div", {
201
206
  className: "mx-auto max-w-2xl space-y-4 p-3 sm:p-6",
202
207
  children: [
@@ -206,7 +211,7 @@ function S({ workspaceId: s }) {
206
211
  title: f("assethandler.field.title", "Field Mode"),
207
212
  description: f("assethandler.field.description", "Scan, check out and check in assets on site, with or without signal")
208
213
  }),
209
- /* @__PURE__ */ p(Se, { children: f("assethandler.field.purpose", "Built for the phone in your hand. Scan a tag to open the asset, then check it out or in with one tap. Actions are saved on this device first and sync automatically, so a dead spot never loses a record.") }),
214
+ /* @__PURE__ */ p(Ce, { children: f("assethandler.field.purpose", "Built for the phone in your hand. Scan a tag to open the asset, then check it out or in with one tap. Actions are saved on this device first and sync automatically, so a dead spot never loses a record.") }),
210
215
  /* @__PURE__ */ m("div", {
211
216
  role: "status",
212
217
  "data-testid": "field-connectivity",
@@ -226,7 +231,7 @@ function S({ workspaceId: s }) {
226
231
  type: "button",
227
232
  size: "sm",
228
233
  variant: "outline",
229
- onClick: () => void ze(),
234
+ onClick: () => void Be(),
230
235
  disabled: G,
231
236
  children: [/* @__PURE__ */ p(ge, {
232
237
  className: n("h-4 w-4", G && "animate-spin"),
@@ -235,7 +240,7 @@ function S({ workspaceId: s }) {
235
240
  }) : null]
236
241
  })]
237
242
  }),
238
- Le ? null : /* @__PURE__ */ p("p", {
243
+ Re ? null : /* @__PURE__ */ p("p", {
239
244
  role: "alert",
240
245
  className: "text-sm text-status-error-text",
241
246
  children: f("assethandler.field.notPersisted", "This browser is not letting the app save data, so queued actions will be lost if you close the page. Stay on this page until they sync.")
@@ -250,8 +255,8 @@ function S({ workspaceId: s }) {
250
255
  "aria-hidden": "true"
251
256
  }), f("assethandler.field.scanTitle", "Scan a tag")]
252
257
  }),
253
- Ae ? /* @__PURE__ */ p(ee, {
254
- onDetected: He,
258
+ je ? /* @__PURE__ */ p(ee, {
259
+ onDetected: Ge,
255
260
  onClose: () => w(!1)
256
261
  }) : /* @__PURE__ */ m(h, {
257
262
  type: "button",
@@ -288,7 +293,7 @@ function S({ workspaceId: s }) {
288
293
  type: "submit",
289
294
  variant: "outline",
290
295
  className: "h-12",
291
- disabled: Pe,
296
+ disabled: Fe,
292
297
  children: f("assethandler.field.find", "Find")
293
298
  })
294
299
  ]
@@ -312,19 +317,19 @@ function S({ workspaceId: s }) {
312
317
  })]
313
318
  }), /* @__PURE__ */ p("span", {
314
319
  "data-testid": "field-custody",
315
- className: n("shrink-0 rounded-full px-2.5 py-0.5 text-xs font-medium", X ? "bg-status-warning-subtle text-status-warning-text" : "bg-status-success-subtle text-status-success-text"),
316
- children: X ? f("assethandler.field.checkedOut", "Checked out") : f("assethandler.field.available", "Available")
320
+ className: n("shrink-0 rounded-full px-2.5 py-0.5 text-xs font-medium", Z ? "bg-status-warning-subtle text-status-warning-text" : "bg-status-success-subtle text-status-success-text"),
321
+ children: Z ? f("assethandler.field.checkedOut", "Checked out") : f("assethandler.field.available", "Available")
317
322
  })]
318
323
  }),
319
- Z && X ? /* @__PURE__ */ p("p", {
324
+ Ke && Z ? /* @__PURE__ */ p("p", {
320
325
  className: "text-sm text-text-secondary",
321
- children: f("assethandler.field.heldBy", "Held by {{holder}}", { holder: Z })
326
+ children: f("assethandler.field.heldBy", "Held by {{holder}}", { holder: Ke })
322
327
  }) : null,
323
328
  k && !k.confirmed ? /* @__PURE__ */ p("p", {
324
329
  className: "text-xs text-text-secondary",
325
330
  children: f("assethandler.field.unconfirmed", "Custody shown from this device’s last sync; the server confirms it when the action syncs.")
326
331
  }) : null,
327
- X ? null : /* @__PURE__ */ m("div", {
332
+ Z ? null : /* @__PURE__ */ m("div", {
328
333
  className: "space-y-1",
329
334
  children: [/* @__PURE__ */ p(v, {
330
335
  htmlFor: "field-assignee",
@@ -332,7 +337,7 @@ function S({ workspaceId: s }) {
332
337
  }), /* @__PURE__ */ p(_, {
333
338
  id: "field-assignee",
334
339
  value: j,
335
- onChange: (e) => Ne(e.target.value),
340
+ onChange: (e) => Pe(e.target.value),
336
341
  placeholder: x?.email ?? "name@company.com",
337
342
  autoComplete: "email",
338
343
  className: "h-12"
@@ -356,8 +361,8 @@ function S({ workspaceId: s }) {
356
361
  children: [/* @__PURE__ */ m(h, {
357
362
  type: "button",
358
363
  className: "h-14 text-base",
359
- variant: X ? "outline" : "default",
360
- onClick: () => Ue("CHECK_OUT"),
364
+ variant: Z ? "outline" : "default",
365
+ onClick: () => qe("CHECK_OUT"),
361
366
  children: [/* @__PURE__ */ p(pe, {
362
367
  className: "h-5 w-5",
363
368
  "aria-hidden": "true"
@@ -365,8 +370,8 @@ function S({ workspaceId: s }) {
365
370
  }), /* @__PURE__ */ m(h, {
366
371
  type: "button",
367
372
  className: "h-14 text-base",
368
- variant: X ? "default" : "outline",
369
- onClick: () => Ue("CHECK_IN"),
373
+ variant: Z ? "default" : "outline",
374
+ onClick: () => qe("CHECK_IN"),
370
375
  children: [/* @__PURE__ */ p(fe, {
371
376
  className: "h-5 w-5",
372
377
  "aria-hidden": "true"
@@ -404,13 +409,13 @@ function S({ workspaceId: s }) {
404
409
  type: "button",
405
410
  size: "sm",
406
411
  variant: "outline",
407
- onClick: () => Ve(e.id),
412
+ onClick: () => He(e.id),
408
413
  children: f("assethandler.field.retry", "Retry")
409
414
  }) : null, /* @__PURE__ */ p(h, {
410
415
  type: "button",
411
416
  size: "icon",
412
417
  variant: "ghost",
413
- onClick: () => Be(e.id),
418
+ onClick: () => Ve(e.id),
414
419
  disabled: e.id === K,
415
420
  "aria-label": f("assethandler.field.discard", "Discard queued action"),
416
421
  children: /* @__PURE__ */ p(ye, {
@@ -433,7 +438,7 @@ function S({ workspaceId: s }) {
433
438
  /* @__PURE__ */ p(_, {
434
439
  id: "field-search",
435
440
  value: D,
436
- onChange: (e) => je(e.target.value),
441
+ onChange: (e) => Me(e.target.value),
437
442
  placeholder: f("assethandler.field.searchPlaceholder", "Name, tag, location or holder"),
438
443
  className: "h-12"
439
444
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"FieldModePage.js","names":[],"sources":["../../../src/assethandler/pages/FieldModePage.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useApolloClient } from '@apollo/client/react';\nimport { Button, Input, Label, GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport {\n ArrowDownToLine,\n ArrowUpFromLine,\n Camera,\n CloudOff,\n RefreshCw,\n ScanLine,\n Smartphone,\n Trash2,\n Wifi,\n} from 'lucide-react';\nimport { SectionHeader } from '../components';\nimport { CameraTagScanner } from '../components/CameraTagScanner';\nimport { useAssetHandler } from '../providers/AssetHandlerProvider';\nimport { useAssetHandlerStore } from '../store/assethandlerStore';\nimport { browserStorage, useFieldQueue } from '../hooks/useFieldQueue';\nimport { useTr } from '../hooks/useTr';\nimport { notify } from '../utils/toast';\nimport { cn } from '../utils/cn';\nimport {\n FieldQueueFullError,\n projectedCustody,\n projectedHolder,\n type FieldAction,\n type FieldActionKind,\n} from '../utils/fieldQueue';\nimport {\n fieldAssetCacheKey,\n filterFieldAssets,\n findFieldAsset,\n loadFieldAssets,\n normalizeScannedTag,\n saveFieldAssets,\n toFieldAsset,\n type FieldAsset,\n} from '../utils/fieldAssetCache';\n\n/**\n * Field mode (BOFF-3952): a phone-first surface for technicians on site.\n * Scan a tag with the camera (or a handheld scanner / typed tag), see the\n * asset and who holds it, and check it out or in with one thumb. Actions are\n * queued on the device and replayed idempotently, so the page keeps working\n * without network and syncs when the connection returns.\n */\n\n/**\n * The registry the device keeps for offline lookups. Field mode fetches it\n * itself (network-only) rather than reading the shared bootstrap store: the\n * store can still hold the previous workspace's assets for a moment after a\n * workspace switch, and persisting those under the new workspace's cache key\n * would let an offline scan resolve against another workspace's registry.\n */\nexport const FIELD_ASSETS = gql`\n query AssetHandlerFieldAssets {\n assets {\n id\n name\n tagId\n assetNumber\n status\n locationName\n assignedTo\n }\n }\n`;\n\ninterface FieldAssetRow {\n id: string;\n name: string;\n tagId?: string | null;\n assetNumber?: string | null;\n status?: string | null;\n locationName?: string | null;\n assignedTo?: string | null;\n}\n\nfunction rowToFieldAsset(row: FieldAssetRow): FieldAsset {\n return toFieldAsset({\n id: row.id,\n name: row.name,\n tagId: row.tagId,\n assetTag: row.assetNumber,\n location: row.locationName,\n assignedTo: row.assignedTo,\n status: row.status,\n });\n}\n\nconst FIELD_ASSET_BY_TAG = gql`\n query AssetHandlerFieldAssetByTag($tagId: String!) {\n assetByTag(tagId: $tagId) {\n id\n name\n tagId\n assetNumber\n status\n locationName\n assignedTo\n }\n }\n`;\n\nconst FIELD_LATEST_ASSIGNMENT = gql`\n query AssetHandlerFieldLatestAssignment($assetId: ID!) {\n assetAssignments(assetId: $assetId, pagination: { limit: 1 }) {\n id\n status\n assignedTo\n }\n }\n`;\n\ninterface AssetByTagResult {\n assetByTag: {\n id: string;\n name: string;\n tagId: string;\n assetNumber: string;\n status: string;\n locationName: string | null;\n assignedTo: string | null;\n } | null;\n}\n\ninterface LatestAssignmentResult {\n assetAssignments: { id: string; status: string; assignedTo: string }[];\n}\n\n/** Custody as last confirmed by the server, or a hint when offline. */\ninterface CustodyView {\n checkedOut: boolean;\n holder: string;\n confirmed: boolean;\n}\n\nconst LAST_ASSIGNEE_KEY = 'assethandler.field.lastAssignee';\nconst LIST_LIMIT = 50;\n\nfunction readLastAssignee(): string {\n try {\n return browserStorage()?.getItem(LAST_ASSIGNEE_KEY) ?? '';\n } catch {\n return '';\n }\n}\n\nfunction rememberAssignee(value: string): void {\n try {\n browserStorage()?.setItem(LAST_ASSIGNEE_KEY, value);\n } catch {\n // Remembering the last recipient is a convenience only.\n }\n}\n\nexport function FieldModePage() {\n const { workspaceId } = useAssetHandler();\n // Remount per workspace so the queue and offline registry never leak\n // across a workspace switch.\n return <FieldModeView key={workspaceId || 'default'} workspaceId={workspaceId} />;\n}\n\nfunction FieldModeView({ workspaceId }: { workspaceId?: string | null }) {\n const tr = useTr();\n const apollo = useApolloClient();\n const { currentUser } = useAssetHandler();\n const refetchBootstrap = useAssetHandlerStore((state) => state.refetchBootstrap);\n\n const cacheKey = fieldAssetCacheKey(workspaceId);\n const [cachedAssets] = useState<FieldAsset[]>(\n () => loadFieldAssets(browserStorage(), cacheKey).assets\n );\n const [scannerOpen, setScannerOpen] = useState(false);\n const [tagInput, setTagInput] = useState('');\n const [search, setSearch] = useState('');\n const [selected, setSelected] = useState<FieldAsset | null>(null);\n const [custody, setCustody] = useState<CustodyView | null>(null);\n const [assignee, setAssignee] = useState(readLastAssignee);\n const [notes, setNotes] = useState('');\n const [lookingUp, setLookingUp] = useState(false);\n const tagInputRef = useRef<HTMLInputElement>(null);\n // The asset whose custody the card currently shows. A slower custody\n // response for an asset the technician has since moved away from must not\n // overwrite the newer selection.\n const custodyAssetIdRef = useRef<string | null>(null);\n // Bumped by every selection and every online tag lookup. A lookup whose\n // response arrives after the technician picked something else (from Browse\n // or a newer scan) is stale and must not replace that newer choice.\n const selectionSeqRef = useRef(0);\n\n const [liveAssets, setLiveAssets] = useState<FieldAsset[] | null>(null);\n // This view is keyed by workspace, so every response it receives was\n // requested for this workspace. `registryActiveRef` drops responses that\n // land after unmount (a workspace switch); it is re-armed on every effect\n // setup, so a StrictMode (or Activity) cleanup/setup cycle keeps loading.\n // Each cleanup also bumps the request counter, which invalidates requests\n // issued during an earlier effect lifetime, and the counter itself drops\n // out-of-order responses from an older refresh.\n const registryActiveRef = useRef(false);\n const registryRequestRef = useRef(0);\n useEffect(() => {\n registryActiveRef.current = true;\n return () => {\n registryActiveRef.current = false;\n registryRequestRef.current += 1;\n };\n }, []);\n\n const refreshRegistry = useCallback(() => {\n if (!registryActiveRef.current) return;\n registryRequestRef.current += 1;\n const request = registryRequestRef.current;\n apollo\n .query<{ assets: FieldAssetRow[] | null }>({\n query: FIELD_ASSETS,\n fetchPolicy: 'network-only',\n })\n .then(\n (result) => {\n if (\n !registryActiveRef.current ||\n registryRequestRef.current !== request ||\n !result.data?.assets\n ) {\n return;\n }\n const next = result.data.assets.map(rowToFieldAsset);\n setLiveAssets(next);\n // Keep the offline snapshot in step with the live registry.\n saveFieldAssets(browserStorage(), cacheKey, next);\n },\n () => {\n // Offline or a transient failure: keep using the device snapshot.\n }\n );\n }, [apollo, cacheKey]);\n\n const loadCustody = useCallback(\n async (asset: FieldAsset, online: boolean) => {\n const hint: CustodyView = {\n checkedOut: asset.assignedTo !== '',\n holder: asset.assignedTo,\n confirmed: false,\n };\n custodyAssetIdRef.current = asset.id;\n setCustody(hint);\n if (!online) return;\n try {\n const result = await apollo.query<LatestAssignmentResult>({\n query: FIELD_LATEST_ASSIGNMENT,\n variables: { assetId: asset.id },\n fetchPolicy: 'network-only',\n });\n if (custodyAssetIdRef.current !== asset.id) return;\n const latest = result.data?.assetAssignments?.[0];\n setCustody({\n checkedOut: !!latest && latest.status !== 'RETURNED',\n holder: latest && latest.status !== 'RETURNED' ? latest.assignedTo : '',\n confirmed: true,\n });\n } catch {\n // Keep the hint; the server still validates the action on sync.\n }\n },\n [apollo]\n );\n\n const queueApi = useFieldQueue({\n workspaceId,\n client: apollo,\n onFlushed: (result) => {\n if (result.synced.length > 0) {\n notify.success(\n tr('assethandler.field.synced', '{{count}} field action(s) synced', {\n count: result.synced.length,\n })\n );\n refetchBootstrap();\n refreshRegistry();\n }\n for (const action of result.rejected) {\n notify.error(\n tr('assethandler.field.rejected', 'Could not apply action for {{asset}}', {\n asset: action.assetName,\n }),\n action.lastError\n );\n }\n if (selected) void loadCustody(selected, true);\n },\n });\n const { queue, pending, online, syncing, sendingId, persisted, enqueue, syncNow, remove, retry } =\n queueApi;\n\n // Load the registry on open and again whenever connectivity returns.\n useEffect(() => {\n if (online) refreshRegistry();\n }, [online, refreshRegistry]);\n\n const knownAssets = liveAssets ?? cachedAssets;\n\n const selectAsset = useCallback(\n (asset: FieldAsset) => {\n selectionSeqRef.current += 1;\n setLookingUp(false);\n setSelected(asset);\n setNotes('');\n void loadCustody(asset, online);\n },\n [loadCustody, online]\n );\n\n const resolveTag = useCallback(\n async (raw: string) => {\n const tag = normalizeScannedTag(raw);\n if (!tag) {\n notify.warning(\n tr('assethandler.field.enterTag', 'Enter a tag'),\n tr('assethandler.field.enterTagHint', 'Scan or type the asset tag to continue.')\n );\n return;\n }\n const local = findFieldAsset(knownAssets, tag);\n if (local) {\n selectAsset(local);\n setTagInput('');\n return;\n }\n if (!online) {\n notify.warning(\n tr('assethandler.field.unknownOffline', 'Tag not in the offline registry'),\n tr(\n 'assethandler.field.unknownOfflineHint',\n 'Reconnect once so this device can refresh its copy of the registry.'\n )\n );\n return;\n }\n selectionSeqRef.current += 1;\n const lookup = selectionSeqRef.current;\n const isCurrent = () => selectionSeqRef.current === lookup;\n setLookingUp(true);\n try {\n const result = await apollo.query<AssetByTagResult>({\n query: FIELD_ASSET_BY_TAG,\n variables: { tagId: tag },\n fetchPolicy: 'network-only',\n });\n if (!isCurrent()) return;\n const found = result.data?.assetByTag;\n if (!found) {\n notify.error(\n tr('assethandler.field.notFound', 'No asset found'),\n tr('assethandler.field.notFoundHint', 'No asset matches tag \"{{tag}}\".', { tag })\n );\n return;\n }\n selectAsset(rowToFieldAsset(found));\n setTagInput('');\n } catch (error) {\n if (!isCurrent()) return;\n notify.error(\n tr('assethandler.field.lookupFailed', 'Tag lookup failed'),\n error instanceof Error ? error.message : undefined\n );\n } finally {\n if (isCurrent()) setLookingUp(false);\n }\n },\n [apollo, knownAssets, online, selectAsset, tr]\n );\n\n const onDetected = useCallback(\n (value: string) => {\n setScannerOpen(false);\n void resolveTag(value);\n },\n [resolveTag]\n );\n\n const projectedOut = selected\n ? projectedCustody(queue, selected.tagId, custody?.checkedOut ?? false)\n : false;\n const projectedHolderName = selected\n ? projectedHolder(queue, selected.tagId, custody?.holder ?? '')\n : '';\n\n const record = (kind: FieldActionKind) => {\n if (!selected) return;\n const recipient = assignee.trim();\n if (kind === 'CHECK_OUT' && !recipient) {\n notify.warning(\n tr('assethandler.field.assigneeRequired', 'Recipient required'),\n tr('assethandler.field.assigneeRequiredHint', 'Enter who is receiving the asset.')\n );\n return;\n }\n try {\n enqueue({\n kind,\n tagId: selected.tagId,\n assetName: selected.name,\n ...(kind === 'CHECK_OUT' ? { assignedTo: recipient } : {}),\n ...(notes.trim() ? { notes: notes.trim() } : {}),\n });\n } catch (error) {\n if (error instanceof FieldQueueFullError) {\n notify.error(\n tr('assethandler.field.queueFull', 'Offline queue is full'),\n tr(\n 'assethandler.field.queueFullHint',\n 'Reconnect to sync the queued actions before recording more.'\n )\n );\n return;\n }\n throw error;\n }\n if (kind === 'CHECK_OUT') rememberAssignee(recipient);\n setNotes('');\n if (!online) {\n notify.info(\n tr('assethandler.field.savedOffline', 'Saved on this device'),\n tr(\n 'assethandler.field.savedOfflineHint',\n 'It will sync automatically when you are back online.'\n )\n );\n }\n tagInputRef.current?.focus();\n };\n\n const visibleAssets = useMemo(\n () => filterFieldAssets(knownAssets, search).slice(0, LIST_LIMIT),\n [knownAssets, search]\n );\n\n const kindLabel = (kind: FieldActionKind) =>\n kind === 'CHECK_OUT'\n ? tr('assethandler.field.kind.checkOut', 'Check out')\n : tr('assethandler.field.kind.checkIn', 'Check in');\n\n return (\n <div className=\"mx-auto max-w-2xl space-y-4 p-3 sm:p-6\">\n <SectionHeader\n hero\n icon={Smartphone}\n title={tr('assethandler.field.title', 'Field Mode')}\n description={tr(\n 'assethandler.field.description',\n 'Scan, check out and check in assets on site, with or without signal'\n )}\n />\n <PagePurpose>\n {tr(\n 'assethandler.field.purpose',\n 'Built for the phone in your hand. Scan a tag to open the asset, then check it out or in with one tap. Actions are saved on this device first and sync automatically, so a dead spot never loses a record.'\n )}\n </PagePurpose>\n\n <div\n role=\"status\"\n data-testid=\"field-connectivity\"\n className={cn(\n 'flex flex-wrap items-center justify-between gap-2 rounded-xl px-3 py-2 text-sm',\n online\n ? 'bg-status-success-subtle text-status-success-text'\n : 'bg-status-warning-subtle text-status-warning-text'\n )}\n >\n <span className=\"flex items-center gap-2 font-medium\">\n {online ? (\n <Wifi className=\"h-4 w-4\" aria-hidden=\"true\" />\n ) : (\n <CloudOff className=\"h-4 w-4\" aria-hidden=\"true\" />\n )}\n {online\n ? tr('assethandler.field.online', 'Online')\n : tr('assethandler.field.offline', 'Offline — actions are saved on this device')}\n </span>\n <span className=\"flex items-center gap-2\">\n <span data-testid=\"field-pending-count\">\n {tr('assethandler.field.pending', '{{count}} waiting to sync', { count: pending })}\n </span>\n {pending > 0 && online ? (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void syncNow()}\n disabled={syncing}\n >\n <RefreshCw className={cn('h-4 w-4', syncing && 'animate-spin')} aria-hidden=\"true\" />\n {tr('assethandler.field.syncNow', 'Sync now')}\n </Button>\n ) : null}\n </span>\n </div>\n {!persisted ? (\n <p role=\"alert\" className=\"text-sm text-status-error-text\">\n {tr(\n 'assethandler.field.notPersisted',\n 'This browser is not letting the app save data, so queued actions will be lost if you close the page. Stay on this page until they sync.'\n )}\n </p>\n ) : null}\n\n <GlassCard className=\"space-y-3 p-4\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <ScanLine className=\"h-4 w-4\" aria-hidden=\"true\" />\n {tr('assethandler.field.scanTitle', 'Scan a tag')}\n </div>\n {scannerOpen ? (\n <CameraTagScanner onDetected={onDetected} onClose={() => setScannerOpen(false)} />\n ) : (\n <Button\n type=\"button\"\n className=\"h-14 w-full text-base\"\n onClick={() => setScannerOpen(true)}\n >\n <Camera className=\"h-5 w-5\" aria-hidden=\"true\" />\n {tr('assethandler.field.openCamera', 'Scan with camera')}\n </Button>\n )}\n <form\n className=\"flex gap-2\"\n onSubmit={(event) => {\n event.preventDefault();\n void resolveTag(tagInput);\n }}\n >\n <Label htmlFor=\"field-tag-input\" className=\"sr-only\">\n {tr('assethandler.field.tagLabel', 'Asset tag')}\n </Label>\n <Input\n id=\"field-tag-input\"\n ref={tagInputRef}\n value={tagInput}\n onChange={(e) => setTagInput(e.target.value)}\n placeholder={tr('assethandler.field.tagPlaceholder', 'Type or scan a tag')}\n autoComplete=\"off\"\n autoCapitalize=\"off\"\n enterKeyHint=\"search\"\n className=\"h-12 flex-1\"\n />\n <Button type=\"submit\" variant=\"outline\" className=\"h-12\" disabled={lookingUp}>\n {tr('assethandler.field.find', 'Find')}\n </Button>\n </form>\n </GlassCard>\n\n {selected ? (\n <GlassCard className=\"space-y-4 p-4\" data-testid=\"field-selected-asset\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <h2 className=\"truncate text-lg font-semibold text-text-primary\">{selected.name}</h2>\n <p className=\"truncate text-xs text-text-secondary\">\n {tr('assethandler.field.tagValue', 'Tag {{tag}}', { tag: selected.tagId })}\n {selected.location ? ` · ${selected.location}` : ''}\n </p>\n </div>\n <span\n data-testid=\"field-custody\"\n className={cn(\n 'shrink-0 rounded-full px-2.5 py-0.5 text-xs font-medium',\n projectedOut\n ? 'bg-status-warning-subtle text-status-warning-text'\n : 'bg-status-success-subtle text-status-success-text'\n )}\n >\n {projectedOut\n ? tr('assethandler.field.checkedOut', 'Checked out')\n : tr('assethandler.field.available', 'Available')}\n </span>\n </div>\n {projectedHolderName && projectedOut ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('assethandler.field.heldBy', 'Held by {{holder}}', {\n holder: projectedHolderName,\n })}\n </p>\n ) : null}\n {custody && !custody.confirmed ? (\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'assethandler.field.unconfirmed',\n 'Custody shown from this device’s last sync; the server confirms it when the action syncs.'\n )}\n </p>\n ) : null}\n\n {!projectedOut ? (\n <div className=\"space-y-1\">\n <Label htmlFor=\"field-assignee\">\n {tr('assethandler.field.assignee', 'Checking out to')}\n </Label>\n <Input\n id=\"field-assignee\"\n value={assignee}\n onChange={(e) => setAssignee(e.target.value)}\n placeholder={currentUser?.email ?? 'name@company.com'}\n autoComplete=\"email\"\n className=\"h-12\"\n />\n </div>\n ) : null}\n <div className=\"space-y-1\">\n <Label htmlFor=\"field-notes\">\n {tr('assethandler.field.notes', 'Notes (optional)')}\n </Label>\n <Input\n id=\"field-notes\"\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n placeholder={tr('assethandler.field.notesPlaceholder', 'Condition, location, job…')}\n className=\"h-12\"\n />\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <Button\n type=\"button\"\n className=\"h-14 text-base\"\n variant={projectedOut ? 'outline' : 'default'}\n onClick={() => record('CHECK_OUT')}\n >\n <ArrowUpFromLine className=\"h-5 w-5\" aria-hidden=\"true\" />\n {kindLabel('CHECK_OUT')}\n </Button>\n <Button\n type=\"button\"\n className=\"h-14 text-base\"\n variant={projectedOut ? 'default' : 'outline'}\n onClick={() => record('CHECK_IN')}\n >\n <ArrowDownToLine className=\"h-5 w-5\" aria-hidden=\"true\" />\n {kindLabel('CHECK_IN')}\n </Button>\n </div>\n </GlassCard>\n ) : null}\n\n {queue.length > 0 ? (\n <GlassCard className=\"space-y-2 p-4\" data-testid=\"field-queue\">\n <h2 className=\"text-sm font-semibold text-text-primary\">\n {tr('assethandler.field.queueTitle', 'On this device')}\n </h2>\n <ul className=\"divide-y divide-border-seam\">\n {queue.map((action: FieldAction) => (\n <li key={action.id} className=\"flex items-center justify-between gap-3 py-2\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm text-text-primary\">\n {kindLabel(action.kind)} · {action.assetName}\n </p>\n <p\n className={cn(\n 'truncate text-xs',\n action.state === 'rejected' ? 'text-status-error-text' : 'text-text-secondary'\n )}\n >\n {action.state === 'rejected'\n ? (action.lastError ??\n tr('assethandler.field.refused', 'Refused by the server'))\n : action.id === sendingId\n ? tr('assethandler.field.sending', 'Sending…')\n : new Date(action.scannedAt).toLocaleString()}\n </p>\n </div>\n <div className=\"flex shrink-0 gap-1\">\n {action.state === 'rejected' ? (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => retry(action.id)}\n >\n {tr('assethandler.field.retry', 'Retry')}\n </Button>\n ) : null}\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"ghost\"\n onClick={() => remove(action.id)}\n disabled={action.id === sendingId}\n aria-label={tr('assethandler.field.discard', 'Discard queued action')}\n >\n <Trash2 className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n </div>\n </li>\n ))}\n </ul>\n </GlassCard>\n ) : null}\n\n <GlassCard className=\"space-y-3 p-4\">\n <Label htmlFor=\"field-search\" className=\"text-sm font-semibold\">\n {tr('assethandler.field.browse', 'Browse assets')}\n </Label>\n <Input\n id=\"field-search\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder={tr('assethandler.field.searchPlaceholder', 'Name, tag, location or holder')}\n className=\"h-12\"\n />\n {visibleAssets.length === 0 ? (\n <p className=\"py-4 text-center text-sm text-text-secondary\">\n {knownAssets.length === 0\n ? tr(\n 'assethandler.field.noAssets',\n 'No assets on this device yet. Open this page once while online to load the registry.'\n )\n : tr('assethandler.field.noMatches', 'No assets match your search.')}\n </p>\n ) : (\n <ul className=\"space-y-2\" data-testid=\"field-asset-list\">\n {visibleAssets.map((asset) => (\n <li key={asset.id}>\n <button\n type=\"button\"\n onClick={() => selectAsset(asset)}\n className={cn(\n 'flex min-h-14 w-full items-center justify-between gap-3 rounded-xl border border-border-seam px-3 py-2 text-left transition-colors hover:bg-bg-sunken',\n selected?.id === asset.id && 'border-action-primary-bg'\n )}\n >\n <span className=\"min-w-0\">\n <span className=\"block truncate text-sm font-medium text-text-primary\">\n {asset.name}\n </span>\n <span className=\"block truncate text-xs text-text-secondary\">\n {asset.assetTag || asset.tagId}\n {asset.location ? ` · ${asset.location}` : ''}\n </span>\n </span>\n {asset.assignedTo ? (\n <span className=\"max-w-[40%] shrink-0 truncate rounded-full bg-status-warning-subtle px-2 py-0.5 text-[11px] text-status-warning-text\">\n {asset.assignedTo}\n </span>\n ) : null}\n </button>\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"FieldModePage.js","names":[],"sources":["../../../src/assethandler/pages/FieldModePage.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useApolloClient } from '@apollo/client/react';\nimport { useSearchParams } from 'react-router';\nimport { Button, Input, Label, GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport {\n ArrowDownToLine,\n ArrowUpFromLine,\n Camera,\n CloudOff,\n RefreshCw,\n ScanLine,\n Smartphone,\n Trash2,\n Wifi,\n} from 'lucide-react';\nimport { SectionHeader } from '../components';\nimport { CameraTagScanner } from '../components/CameraTagScanner';\nimport { useAssetHandler } from '../providers/AssetHandlerProvider';\nimport { useAssetHandlerStore } from '../store/assethandlerStore';\nimport { browserStorage, useFieldQueue } from '../hooks/useFieldQueue';\nimport { useTr } from '../hooks/useTr';\nimport { notify } from '../utils/toast';\nimport { cn } from '../utils/cn';\nimport {\n FieldQueueFullError,\n projectedCustody,\n projectedHolder,\n type FieldAction,\n type FieldActionKind,\n} from '../utils/fieldQueue';\nimport {\n fieldAssetCacheKey,\n filterFieldAssets,\n findFieldAsset,\n loadFieldAssets,\n normalizeScannedTag,\n saveFieldAssets,\n toFieldAsset,\n type FieldAsset,\n} from '../utils/fieldAssetCache';\n\n/**\n * Field mode (BOFF-3952): a phone-first surface for technicians on site.\n * Scan a tag with the camera (or a handheld scanner / typed tag), see the\n * asset and who holds it, and check it out or in with one thumb. Actions are\n * queued on the device and replayed idempotently, so the page keeps working\n * without network and syncs when the connection returns.\n */\n\n/**\n * The registry the device keeps for offline lookups. Field mode fetches it\n * itself (network-only) rather than reading the shared bootstrap store: the\n * store can still hold the previous workspace's assets for a moment after a\n * workspace switch, and persisting those under the new workspace's cache key\n * would let an offline scan resolve against another workspace's registry.\n */\nexport const FIELD_ASSETS = gql`\n query AssetHandlerFieldAssets {\n assets {\n id\n name\n tagId\n assetNumber\n status\n locationName\n assignedTo\n }\n }\n`;\n\ninterface FieldAssetRow {\n id: string;\n name: string;\n tagId?: string | null;\n assetNumber?: string | null;\n status?: string | null;\n locationName?: string | null;\n assignedTo?: string | null;\n}\n\nfunction rowToFieldAsset(row: FieldAssetRow): FieldAsset {\n return toFieldAsset({\n id: row.id,\n name: row.name,\n tagId: row.tagId,\n assetTag: row.assetNumber,\n location: row.locationName,\n assignedTo: row.assignedTo,\n status: row.status,\n });\n}\n\n/** Online lookup of one asset by its printed tag (scan or deep link). */\nexport const FIELD_ASSET_BY_TAG = gql`\n query AssetHandlerFieldAssetByTag($tagId: String!) {\n assetByTag(tagId: $tagId) {\n id\n name\n tagId\n assetNumber\n status\n locationName\n assignedTo\n }\n }\n`;\n\nconst FIELD_LATEST_ASSIGNMENT = gql`\n query AssetHandlerFieldLatestAssignment($assetId: ID!) {\n assetAssignments(assetId: $assetId, pagination: { limit: 1 }) {\n id\n status\n assignedTo\n }\n }\n`;\n\ninterface AssetByTagResult {\n assetByTag: {\n id: string;\n name: string;\n tagId: string;\n assetNumber: string;\n status: string;\n locationName: string | null;\n assignedTo: string | null;\n } | null;\n}\n\ninterface LatestAssignmentResult {\n assetAssignments: { id: string; status: string; assignedTo: string }[];\n}\n\n/** Custody as last confirmed by the server, or a hint when offline. */\ninterface CustodyView {\n checkedOut: boolean;\n holder: string;\n confirmed: boolean;\n}\n\nconst LAST_ASSIGNEE_KEY = 'assethandler.field.lastAssignee';\nconst LIST_LIMIT = 50;\n\nfunction readLastAssignee(): string {\n try {\n return browserStorage()?.getItem(LAST_ASSIGNEE_KEY) ?? '';\n } catch {\n return '';\n }\n}\n\nfunction rememberAssignee(value: string): void {\n try {\n browserStorage()?.setItem(LAST_ASSIGNEE_KEY, value);\n } catch {\n // Remembering the last recipient is a convenience only.\n }\n}\n\nexport function FieldModePage() {\n const { workspaceId } = useAssetHandler();\n // Remount per workspace so the queue and offline registry never leak\n // across a workspace switch.\n return <FieldModeView key={workspaceId || 'default'} workspaceId={workspaceId} />;\n}\n\nfunction FieldModeView({ workspaceId }: { workspaceId?: string | null }) {\n const tr = useTr();\n const apollo = useApolloClient();\n const { currentUser } = useAssetHandler();\n const refetchBootstrap = useAssetHandlerStore((state) => state.refetchBootstrap);\n\n const cacheKey = fieldAssetCacheKey(workspaceId);\n const [cachedAssets] = useState<FieldAsset[]>(\n () => loadFieldAssets(browserStorage(), cacheKey).assets\n );\n const [scannerOpen, setScannerOpen] = useState(false);\n const [tagInput, setTagInput] = useState('');\n const [search, setSearch] = useState('');\n const [selected, setSelected] = useState<FieldAsset | null>(null);\n const [custody, setCustody] = useState<CustodyView | null>(null);\n const [assignee, setAssignee] = useState(readLastAssignee);\n const [notes, setNotes] = useState('');\n const [lookingUp, setLookingUp] = useState(false);\n const tagInputRef = useRef<HTMLInputElement>(null);\n // The asset whose custody the card currently shows. A slower custody\n // response for an asset the technician has since moved away from must not\n // overwrite the newer selection.\n const custodyAssetIdRef = useRef<string | null>(null);\n // Bumped by every selection and every online tag lookup. A lookup whose\n // response arrives after the technician picked something else (from Browse\n // or a newer scan) is stale and must not replace that newer choice.\n const selectionSeqRef = useRef(0);\n\n const [liveAssets, setLiveAssets] = useState<FieldAsset[] | null>(null);\n // This view is keyed by workspace, so every response it receives was\n // requested for this workspace. `registryActiveRef` drops responses that\n // land after unmount (a workspace switch); it is re-armed on every effect\n // setup, so a StrictMode (or Activity) cleanup/setup cycle keeps loading.\n // Each cleanup also bumps the request counter, which invalidates requests\n // issued during an earlier effect lifetime, and the counter itself drops\n // out-of-order responses from an older refresh.\n const registryActiveRef = useRef(false);\n const registryRequestRef = useRef(0);\n useEffect(() => {\n registryActiveRef.current = true;\n return () => {\n registryActiveRef.current = false;\n registryRequestRef.current += 1;\n };\n }, []);\n\n const refreshRegistry = useCallback(() => {\n if (!registryActiveRef.current) return;\n registryRequestRef.current += 1;\n const request = registryRequestRef.current;\n apollo\n .query<{ assets: FieldAssetRow[] | null }>({\n query: FIELD_ASSETS,\n fetchPolicy: 'network-only',\n })\n .then(\n (result) => {\n if (\n !registryActiveRef.current ||\n registryRequestRef.current !== request ||\n !result.data?.assets\n ) {\n return;\n }\n const next = result.data.assets.map(rowToFieldAsset);\n setLiveAssets(next);\n // Keep the offline snapshot in step with the live registry.\n saveFieldAssets(browserStorage(), cacheKey, next);\n },\n () => {\n // Offline or a transient failure: keep using the device snapshot.\n }\n );\n }, [apollo, cacheKey]);\n\n const loadCustody = useCallback(\n async (asset: FieldAsset, online: boolean) => {\n const hint: CustodyView = {\n checkedOut: asset.assignedTo !== '',\n holder: asset.assignedTo,\n confirmed: false,\n };\n custodyAssetIdRef.current = asset.id;\n setCustody(hint);\n if (!online) return;\n try {\n const result = await apollo.query<LatestAssignmentResult>({\n query: FIELD_LATEST_ASSIGNMENT,\n variables: { assetId: asset.id },\n fetchPolicy: 'network-only',\n });\n if (custodyAssetIdRef.current !== asset.id) return;\n const latest = result.data?.assetAssignments?.[0];\n setCustody({\n checkedOut: !!latest && latest.status !== 'RETURNED',\n holder: latest && latest.status !== 'RETURNED' ? latest.assignedTo : '',\n confirmed: true,\n });\n } catch {\n // Keep the hint; the server still validates the action on sync.\n }\n },\n [apollo]\n );\n\n const queueApi = useFieldQueue({\n workspaceId,\n client: apollo,\n onFlushed: (result) => {\n if (result.synced.length > 0) {\n notify.success(\n tr('assethandler.field.synced', '{{count}} field action(s) synced', {\n count: result.synced.length,\n })\n );\n refetchBootstrap();\n refreshRegistry();\n }\n for (const action of result.rejected) {\n notify.error(\n tr('assethandler.field.rejected', 'Could not apply action for {{asset}}', {\n asset: action.assetName,\n }),\n action.lastError\n );\n }\n if (selected) void loadCustody(selected, true);\n },\n });\n const { queue, pending, online, syncing, sendingId, persisted, enqueue, syncNow, remove, retry } =\n queueApi;\n\n // Load the registry on open and again whenever connectivity returns.\n useEffect(() => {\n if (online) refreshRegistry();\n }, [online, refreshRegistry]);\n\n const knownAssets = liveAssets ?? cachedAssets;\n\n const selectAsset = useCallback(\n (asset: FieldAsset) => {\n selectionSeqRef.current += 1;\n setLookingUp(false);\n setSelected(asset);\n setNotes('');\n void loadCustody(asset, online);\n },\n [loadCustody, online]\n );\n\n const resolveTag = useCallback(\n async (raw: string) => {\n const tag = normalizeScannedTag(raw);\n if (!tag) {\n notify.warning(\n tr('assethandler.field.enterTag', 'Enter a tag'),\n tr('assethandler.field.enterTagHint', 'Scan or type the asset tag to continue.')\n );\n return;\n }\n const local = findFieldAsset(knownAssets, tag);\n if (local) {\n selectAsset(local);\n setTagInput('');\n return;\n }\n if (!online) {\n notify.warning(\n tr('assethandler.field.unknownOffline', 'Tag not in the offline registry'),\n tr(\n 'assethandler.field.unknownOfflineHint',\n 'Reconnect once so this device can refresh its copy of the registry.'\n )\n );\n return;\n }\n selectionSeqRef.current += 1;\n const lookup = selectionSeqRef.current;\n const isCurrent = () => selectionSeqRef.current === lookup;\n setLookingUp(true);\n try {\n const result = await apollo.query<AssetByTagResult>({\n query: FIELD_ASSET_BY_TAG,\n variables: { tagId: tag },\n fetchPolicy: 'network-only',\n });\n if (!isCurrent()) return;\n const found = result.data?.assetByTag;\n if (!found) {\n notify.error(\n tr('assethandler.field.notFound', 'No asset found'),\n tr('assethandler.field.notFoundHint', 'No asset matches tag \"{{tag}}\".', { tag })\n );\n return;\n }\n selectAsset(rowToFieldAsset(found));\n setTagInput('');\n } catch (error) {\n if (!isCurrent()) return;\n notify.error(\n tr('assethandler.field.lookupFailed', 'Tag lookup failed'),\n error instanceof Error ? error.message : undefined\n );\n } finally {\n if (isCurrent()) setLookingUp(false);\n }\n },\n [apollo, knownAssets, online, selectAsset, tr]\n );\n\n // Notifications deep-link here as `/assethandler/field?tag=<tagId>` (a\n // maintenance-due push for an asset the technician holds, BOFF-10631).\n // Open that asset once per linked tag, through the same lookup a scan uses,\n // so an offline device still resolves it from its registry copy.\n const [searchParams] = useSearchParams();\n const linkedTag = searchParams.get('tag')?.trim() ?? '';\n const openedLinkRef = useRef('');\n useEffect(() => {\n if (!linkedTag || openedLinkRef.current === linkedTag) return;\n openedLinkRef.current = linkedTag;\n void resolveTag(linkedTag);\n }, [linkedTag, resolveTag]);\n\n const onDetected = useCallback(\n (value: string) => {\n setScannerOpen(false);\n void resolveTag(value);\n },\n [resolveTag]\n );\n\n const projectedOut = selected\n ? projectedCustody(queue, selected.tagId, custody?.checkedOut ?? false)\n : false;\n const projectedHolderName = selected\n ? projectedHolder(queue, selected.tagId, custody?.holder ?? '')\n : '';\n\n const record = (kind: FieldActionKind) => {\n if (!selected) return;\n const recipient = assignee.trim();\n if (kind === 'CHECK_OUT' && !recipient) {\n notify.warning(\n tr('assethandler.field.assigneeRequired', 'Recipient required'),\n tr('assethandler.field.assigneeRequiredHint', 'Enter who is receiving the asset.')\n );\n return;\n }\n try {\n enqueue({\n kind,\n tagId: selected.tagId,\n assetName: selected.name,\n ...(kind === 'CHECK_OUT' ? { assignedTo: recipient } : {}),\n ...(notes.trim() ? { notes: notes.trim() } : {}),\n });\n } catch (error) {\n if (error instanceof FieldQueueFullError) {\n notify.error(\n tr('assethandler.field.queueFull', 'Offline queue is full'),\n tr(\n 'assethandler.field.queueFullHint',\n 'Reconnect to sync the queued actions before recording more.'\n )\n );\n return;\n }\n throw error;\n }\n if (kind === 'CHECK_OUT') rememberAssignee(recipient);\n setNotes('');\n if (!online) {\n notify.info(\n tr('assethandler.field.savedOffline', 'Saved on this device'),\n tr(\n 'assethandler.field.savedOfflineHint',\n 'It will sync automatically when you are back online.'\n )\n );\n }\n tagInputRef.current?.focus();\n };\n\n const visibleAssets = useMemo(\n () => filterFieldAssets(knownAssets, search).slice(0, LIST_LIMIT),\n [knownAssets, search]\n );\n\n const kindLabel = (kind: FieldActionKind) =>\n kind === 'CHECK_OUT'\n ? tr('assethandler.field.kind.checkOut', 'Check out')\n : tr('assethandler.field.kind.checkIn', 'Check in');\n\n return (\n <div className=\"mx-auto max-w-2xl space-y-4 p-3 sm:p-6\">\n <SectionHeader\n hero\n icon={Smartphone}\n title={tr('assethandler.field.title', 'Field Mode')}\n description={tr(\n 'assethandler.field.description',\n 'Scan, check out and check in assets on site, with or without signal'\n )}\n />\n <PagePurpose>\n {tr(\n 'assethandler.field.purpose',\n 'Built for the phone in your hand. Scan a tag to open the asset, then check it out or in with one tap. Actions are saved on this device first and sync automatically, so a dead spot never loses a record.'\n )}\n </PagePurpose>\n\n <div\n role=\"status\"\n data-testid=\"field-connectivity\"\n className={cn(\n 'flex flex-wrap items-center justify-between gap-2 rounded-xl px-3 py-2 text-sm',\n online\n ? 'bg-status-success-subtle text-status-success-text'\n : 'bg-status-warning-subtle text-status-warning-text'\n )}\n >\n <span className=\"flex items-center gap-2 font-medium\">\n {online ? (\n <Wifi className=\"h-4 w-4\" aria-hidden=\"true\" />\n ) : (\n <CloudOff className=\"h-4 w-4\" aria-hidden=\"true\" />\n )}\n {online\n ? tr('assethandler.field.online', 'Online')\n : tr('assethandler.field.offline', 'Offline — actions are saved on this device')}\n </span>\n <span className=\"flex items-center gap-2\">\n <span data-testid=\"field-pending-count\">\n {tr('assethandler.field.pending', '{{count}} waiting to sync', { count: pending })}\n </span>\n {pending > 0 && online ? (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void syncNow()}\n disabled={syncing}\n >\n <RefreshCw className={cn('h-4 w-4', syncing && 'animate-spin')} aria-hidden=\"true\" />\n {tr('assethandler.field.syncNow', 'Sync now')}\n </Button>\n ) : null}\n </span>\n </div>\n {!persisted ? (\n <p role=\"alert\" className=\"text-sm text-status-error-text\">\n {tr(\n 'assethandler.field.notPersisted',\n 'This browser is not letting the app save data, so queued actions will be lost if you close the page. Stay on this page until they sync.'\n )}\n </p>\n ) : null}\n\n <GlassCard className=\"space-y-3 p-4\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <ScanLine className=\"h-4 w-4\" aria-hidden=\"true\" />\n {tr('assethandler.field.scanTitle', 'Scan a tag')}\n </div>\n {scannerOpen ? (\n <CameraTagScanner onDetected={onDetected} onClose={() => setScannerOpen(false)} />\n ) : (\n <Button\n type=\"button\"\n className=\"h-14 w-full text-base\"\n onClick={() => setScannerOpen(true)}\n >\n <Camera className=\"h-5 w-5\" aria-hidden=\"true\" />\n {tr('assethandler.field.openCamera', 'Scan with camera')}\n </Button>\n )}\n <form\n className=\"flex gap-2\"\n onSubmit={(event) => {\n event.preventDefault();\n void resolveTag(tagInput);\n }}\n >\n <Label htmlFor=\"field-tag-input\" className=\"sr-only\">\n {tr('assethandler.field.tagLabel', 'Asset tag')}\n </Label>\n <Input\n id=\"field-tag-input\"\n ref={tagInputRef}\n value={tagInput}\n onChange={(e) => setTagInput(e.target.value)}\n placeholder={tr('assethandler.field.tagPlaceholder', 'Type or scan a tag')}\n autoComplete=\"off\"\n autoCapitalize=\"off\"\n enterKeyHint=\"search\"\n className=\"h-12 flex-1\"\n />\n <Button type=\"submit\" variant=\"outline\" className=\"h-12\" disabled={lookingUp}>\n {tr('assethandler.field.find', 'Find')}\n </Button>\n </form>\n </GlassCard>\n\n {selected ? (\n <GlassCard className=\"space-y-4 p-4\" data-testid=\"field-selected-asset\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <h2 className=\"truncate text-lg font-semibold text-text-primary\">{selected.name}</h2>\n <p className=\"truncate text-xs text-text-secondary\">\n {tr('assethandler.field.tagValue', 'Tag {{tag}}', { tag: selected.tagId })}\n {selected.location ? ` · ${selected.location}` : ''}\n </p>\n </div>\n <span\n data-testid=\"field-custody\"\n className={cn(\n 'shrink-0 rounded-full px-2.5 py-0.5 text-xs font-medium',\n projectedOut\n ? 'bg-status-warning-subtle text-status-warning-text'\n : 'bg-status-success-subtle text-status-success-text'\n )}\n >\n {projectedOut\n ? tr('assethandler.field.checkedOut', 'Checked out')\n : tr('assethandler.field.available', 'Available')}\n </span>\n </div>\n {projectedHolderName && projectedOut ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('assethandler.field.heldBy', 'Held by {{holder}}', {\n holder: projectedHolderName,\n })}\n </p>\n ) : null}\n {custody && !custody.confirmed ? (\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'assethandler.field.unconfirmed',\n 'Custody shown from this device’s last sync; the server confirms it when the action syncs.'\n )}\n </p>\n ) : null}\n\n {!projectedOut ? (\n <div className=\"space-y-1\">\n <Label htmlFor=\"field-assignee\">\n {tr('assethandler.field.assignee', 'Checking out to')}\n </Label>\n <Input\n id=\"field-assignee\"\n value={assignee}\n onChange={(e) => setAssignee(e.target.value)}\n placeholder={currentUser?.email ?? 'name@company.com'}\n autoComplete=\"email\"\n className=\"h-12\"\n />\n </div>\n ) : null}\n <div className=\"space-y-1\">\n <Label htmlFor=\"field-notes\">\n {tr('assethandler.field.notes', 'Notes (optional)')}\n </Label>\n <Input\n id=\"field-notes\"\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n placeholder={tr('assethandler.field.notesPlaceholder', 'Condition, location, job…')}\n className=\"h-12\"\n />\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <Button\n type=\"button\"\n className=\"h-14 text-base\"\n variant={projectedOut ? 'outline' : 'default'}\n onClick={() => record('CHECK_OUT')}\n >\n <ArrowUpFromLine className=\"h-5 w-5\" aria-hidden=\"true\" />\n {kindLabel('CHECK_OUT')}\n </Button>\n <Button\n type=\"button\"\n className=\"h-14 text-base\"\n variant={projectedOut ? 'default' : 'outline'}\n onClick={() => record('CHECK_IN')}\n >\n <ArrowDownToLine className=\"h-5 w-5\" aria-hidden=\"true\" />\n {kindLabel('CHECK_IN')}\n </Button>\n </div>\n </GlassCard>\n ) : null}\n\n {queue.length > 0 ? (\n <GlassCard className=\"space-y-2 p-4\" data-testid=\"field-queue\">\n <h2 className=\"text-sm font-semibold text-text-primary\">\n {tr('assethandler.field.queueTitle', 'On this device')}\n </h2>\n <ul className=\"divide-y divide-border-seam\">\n {queue.map((action: FieldAction) => (\n <li key={action.id} className=\"flex items-center justify-between gap-3 py-2\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm text-text-primary\">\n {kindLabel(action.kind)} · {action.assetName}\n </p>\n <p\n className={cn(\n 'truncate text-xs',\n action.state === 'rejected' ? 'text-status-error-text' : 'text-text-secondary'\n )}\n >\n {action.state === 'rejected'\n ? (action.lastError ??\n tr('assethandler.field.refused', 'Refused by the server'))\n : action.id === sendingId\n ? tr('assethandler.field.sending', 'Sending…')\n : new Date(action.scannedAt).toLocaleString()}\n </p>\n </div>\n <div className=\"flex shrink-0 gap-1\">\n {action.state === 'rejected' ? (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => retry(action.id)}\n >\n {tr('assethandler.field.retry', 'Retry')}\n </Button>\n ) : null}\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"ghost\"\n onClick={() => remove(action.id)}\n disabled={action.id === sendingId}\n aria-label={tr('assethandler.field.discard', 'Discard queued action')}\n >\n <Trash2 className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n </div>\n </li>\n ))}\n </ul>\n </GlassCard>\n ) : null}\n\n <GlassCard className=\"space-y-3 p-4\">\n <Label htmlFor=\"field-search\" className=\"text-sm font-semibold\">\n {tr('assethandler.field.browse', 'Browse assets')}\n </Label>\n <Input\n id=\"field-search\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder={tr('assethandler.field.searchPlaceholder', 'Name, tag, location or holder')}\n className=\"h-12\"\n />\n {visibleAssets.length === 0 ? (\n <p className=\"py-4 text-center text-sm text-text-secondary\">\n {knownAssets.length === 0\n ? tr(\n 'assethandler.field.noAssets',\n 'No assets on this device yet. Open this page once while online to load the registry.'\n )\n : tr('assethandler.field.noMatches', 'No assets match your search.')}\n </p>\n ) : (\n <ul className=\"space-y-2\" data-testid=\"field-asset-list\">\n {visibleAssets.map((asset) => (\n <li key={asset.id}>\n <button\n type=\"button\"\n onClick={() => selectAsset(asset)}\n className={cn(\n 'flex min-h-14 w-full items-center justify-between gap-3 rounded-xl border border-border-seam px-3 py-2 text-left transition-colors hover:bg-bg-sunken',\n selected?.id === asset.id && 'border-action-primary-bg'\n )}\n >\n <span className=\"min-w-0\">\n <span className=\"block truncate text-sm font-medium text-text-primary\">\n {asset.name}\n </span>\n <span className=\"block truncate text-xs text-text-secondary\">\n {asset.assetTag || asset.tagId}\n {asset.location ? ` · ${asset.location}` : ''}\n </span>\n </span>\n {asset.assignedTo ? (\n <span className=\"max-w-[40%] shrink-0 truncate rounded-full bg-status-warning-subtle px-2 py-0.5 text-[11px] text-status-warning-text\">\n {asset.assignedTo}\n </span>\n ) : null}\n </button>\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n </div>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-assethandler",
3
- "version": "2026.1103.3",
3
+ "version": "2026.1107.1",
4
4
  "description": "Burdenoff AssetHandler Microfrontend",
5
5
  "type": "module",
6
6
  "files": [