@asteby/metacore-runtime-react 37.1.2 → 37.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/dynamic-table.d.ts.map +1 -1
- package/dist/dynamic-table.js +4 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/use-edge-device.d.ts +56 -0
- package/dist/use-edge-device.d.ts.map +1 -0
- package/dist/use-edge-device.js +93 -0
- package/dist/use-resource.d.ts +28 -0
- package/dist/use-resource.d.ts.map +1 -0
- package/dist/use-resource.js +109 -0
- package/package.json +3 -3
- package/src/__tests__/use-edge-device.test.ts +44 -0
- package/src/__tests__/use-resource.test.ts +100 -0
- package/src/dynamic-table.tsx +4 -1
- package/src/index.ts +7 -0
- package/src/use-edge-device.ts +148 -0
- package/src/use-resource.ts +143 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 37.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 677580a: Add `useEdgeDevice`/`resolveEdgeDevice`/`sendEdgeDeviceCommand` generic primitives for resolving a paired edge device (printer, scale, payment terminal…) by capability, replacing per-addon lookups like `pos-edge-print`'s local `findEdgePrinter`.
|
|
8
|
+
|
|
9
|
+
## 37.2.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 362ffda: Add `useResource`/`useMutation` — a tanstack-free polling GET/mutation hook for federated addons, with a `stuck` field on `useResource` that flips true once `isLoading` stays on past a configurable threshold (default 8s) without landing. Lets addon UIs offer a retry instead of an indefinite skeleton when a fetch run gets lost to a remount/abort race. Extracted from the pattern independently reimplemented in the `pos` addon (PRs #1455, #1460).
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 1e35269: Preserve `?action=` in DynamicTable URL sync so notification deep-links can open ActionModalDispatcher on list routes.
|
|
18
|
+
|
|
3
19
|
## 37.1.2
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAgC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAkGnF,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;;;;;;;;OASG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC/B,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,UAAU,CAAA;IACjC;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,aAAoB,EACpB,UAAU,EACV,UAAU,EACV,aAAkB,EAClB,iBAAiB,EACjB,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,EAC5C,QAAQ,EACR,QAAQ,EACR,UAAU,EAAE,cAAc,EAC1B,cAAc,EAAE,kBAA0B,GAC7C,EAAE,iBAAiB,+
|
|
1
|
+
{"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAgC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAkGnF,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;;;;;;;;OASG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC/B,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,UAAU,CAAA;IACjC;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,aAAoB,EACpB,UAAU,EACV,UAAU,EACV,aAAkB,EAClB,iBAAiB,EACjB,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,EAC5C,QAAQ,EACR,QAAQ,EACR,UAAU,EAAE,cAAc,EAC1B,cAAc,EAAE,kBAA0B,GAC7C,EAAE,iBAAiB,+BA62CnB"}
|
package/dist/dynamic-table.js
CHANGED
|
@@ -340,7 +340,10 @@ export function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync
|
|
|
340
340
|
// the URL goes bare, and the sidebar falls back to the model default
|
|
341
341
|
// (the "click twice to move the active to the right entry" bug).
|
|
342
342
|
const current = new URLSearchParams(window.location.search);
|
|
343
|
-
|
|
343
|
+
// `action` is host-owned (?action=stamp_fiscal deep-links from
|
|
344
|
+
// notifications). Without carrying it, the first URL rewrite drops it
|
|
345
|
+
// before the list route can open ActionModalDispatcher.
|
|
346
|
+
for (const key of ['view', 'group_by', 'action']) {
|
|
344
347
|
const v = current.get(key);
|
|
345
348
|
if (v)
|
|
346
349
|
params.set(key, v);
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export { useStageOverrides, type StageOverridePatch, type UseStageOverridesResul
|
|
|
14
14
|
export { DynamicView, resolveViewRenderer, readViewFromSearch, resolveActiveView, type DynamicViewProps, } from './dynamic-view';
|
|
15
15
|
export { useDynamicFilters, type UseDynamicFiltersOptions, type UseDynamicFiltersResult, } from './use-dynamic-filters';
|
|
16
16
|
export { useDebouncedValue, SEARCH_DEBOUNCE_MS } from './use-debounced-value';
|
|
17
|
+
export { useResource, useMutation, type ResourceState, type MutationState, } from './use-resource';
|
|
17
18
|
export * from './dynamic-form';
|
|
18
19
|
export { FilePickButton, type FilePickButtonProps, } from './file-pick-button';
|
|
19
20
|
export * from './barcode-scanner';
|
|
@@ -37,6 +38,7 @@ export * from './navigation-builder';
|
|
|
37
38
|
export * from './i18n-provider';
|
|
38
39
|
export * from './api-context';
|
|
39
40
|
export * from './use-addon-settings';
|
|
41
|
+
export * from './use-edge-device';
|
|
40
42
|
export * from './use-print-document';
|
|
41
43
|
export * from './print-document-button';
|
|
42
44
|
export * from './use-org-document-templates';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,7 @@ export { useStageOverrides, } from './stage-overrides';
|
|
|
19
19
|
export { DynamicView, resolveViewRenderer, readViewFromSearch, resolveActiveView, } from './dynamic-view';
|
|
20
20
|
export { useDynamicFilters, } from './use-dynamic-filters';
|
|
21
21
|
export { useDebouncedValue, SEARCH_DEBOUNCE_MS } from './use-debounced-value';
|
|
22
|
+
export { useResource, useMutation, } from './use-resource';
|
|
22
23
|
export * from './dynamic-form';
|
|
23
24
|
export { FilePickButton, } from './file-pick-button';
|
|
24
25
|
export * from './barcode-scanner';
|
|
@@ -42,6 +43,7 @@ export * from './navigation-builder';
|
|
|
42
43
|
export * from './i18n-provider';
|
|
43
44
|
export * from './api-context';
|
|
44
45
|
export * from './use-addon-settings';
|
|
46
|
+
export * from './use-edge-device';
|
|
45
47
|
export * from './use-print-document';
|
|
46
48
|
export * from './print-document-button';
|
|
47
49
|
export * from './use-org-document-templates';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** A physical device paired to an org (and usually a branch) via ops-edge-agent. */
|
|
2
|
+
export interface EdgeDevice {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
kind: string;
|
|
6
|
+
status: 'online' | 'offline';
|
|
7
|
+
capabilities: string[];
|
|
8
|
+
sucursalId?: string | number | null;
|
|
9
|
+
}
|
|
10
|
+
/** Stable react-query key for the org's edge device list. */
|
|
11
|
+
export declare function edgeDevicesKey(): readonly ["edge-devices"];
|
|
12
|
+
/**
|
|
13
|
+
* Pick the best device for `capability`: online devices only, preferring one
|
|
14
|
+
* paired to `sucursalId` and falling back to an org-level device (no
|
|
15
|
+
* `sucursalId`) when the branch has none. Pure + exported for unit testing.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveEdgeDevice(devices: EdgeDevice[], capability: string, sucursalId?: string | number | null): EdgeDevice | undefined;
|
|
18
|
+
export interface UseEdgeDeviceOptions {
|
|
19
|
+
/** Override staleTime (default 15s — device pairing/online status can change). */
|
|
20
|
+
staleTime?: number;
|
|
21
|
+
/** Poll interval; devices go on/offline without any local event to react to. */
|
|
22
|
+
refetchInterval?: number;
|
|
23
|
+
/** Defer fetching. */
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface UseEdgeDeviceResult {
|
|
27
|
+
device: EdgeDevice | undefined;
|
|
28
|
+
isLoading: boolean;
|
|
29
|
+
error: unknown;
|
|
30
|
+
refetch: () => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Resolve the best online device offering `capability` for the active branch.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const { device, isLoading } = useEdgeDevice('print')
|
|
37
|
+
* if (device) await sendEdgeDeviceCommand(device.id, { type: 'print', payload })
|
|
38
|
+
*/
|
|
39
|
+
export declare function useEdgeDevice(capability: string, opts?: UseEdgeDeviceOptions): UseEdgeDeviceResult;
|
|
40
|
+
/**
|
|
41
|
+
* Send a command to a paired edge device (e.g. a print job, an open-drawer
|
|
42
|
+
* pulse, a weigh request). Generic over the command payload shape — each
|
|
43
|
+
* addon defines its own command contract with the local agent.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* const send = useSendEdgeDeviceCommand()
|
|
47
|
+
* await send.mutateAsync({ deviceId: device.id, command: { type: 'print', payload } })
|
|
48
|
+
*/
|
|
49
|
+
export declare function sendEdgeDeviceCommand(api: Pick<import('./api-context').ApiClient, 'post'>, deviceId: string, command: Record<string, unknown>): Promise<unknown>;
|
|
50
|
+
export interface SendEdgeDeviceCommandArgs {
|
|
51
|
+
deviceId: string;
|
|
52
|
+
command: Record<string, unknown>;
|
|
53
|
+
}
|
|
54
|
+
/** Mutation wrapper over {@link sendEdgeDeviceCommand} bound to the host's ApiClient. */
|
|
55
|
+
export declare function useSendEdgeDeviceCommand(): import("@tanstack/react-query").UseMutationResult<unknown, Error, SendEdgeDeviceCommandArgs, unknown>;
|
|
56
|
+
//# sourceMappingURL=use-edge-device.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-edge-device.d.ts","sourceRoot":"","sources":["../src/use-edge-device.ts"],"names":[],"mappings":"AAkBA,oFAAoF;AACpF,MAAM,WAAW,UAAU;IACvB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAA;IAC5B,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CACtC;AAED,6DAA6D;AAC7D,wBAAgB,cAAc,8BAE7B;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC7B,OAAO,EAAE,UAAU,EAAE,EACrB,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GACpC,UAAU,GAAG,SAAS,CASxB;AAED,MAAM,WAAW,oBAAoB;IACjC,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gFAAgF;IAChF,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,sBAAsB;IACtB,OAAO,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,MAAM,WAAW,mBAAmB;IAChC,MAAM,EAAE,UAAU,GAAG,SAAS,CAAA;IAC9B,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,OAAO,CAAA;IACd,OAAO,EAAE,MAAM,IAAI,CAAA;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CACzB,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,oBAAyB,GAChC,mBAAmB,CA+BrB;AAED;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CACvC,GAAG,EAAE,IAAI,CAAC,OAAO,eAAe,EAAE,SAAS,EAAE,MAAM,CAAC,EACpD,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,OAAO,CAAC,CAIlB;AAED,MAAM,WAAW,yBAAyB;IACtC,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED,yFAAyF;AACzF,wBAAgB,wBAAwB,0GAUvC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// useEdgeDevice — THE standard primitive for resolving a paired edge device
|
|
2
|
+
// (printer, scale, payment terminal, cashdro…) by capability, without every
|
|
3
|
+
// addon re-implementing its own lookup against `GET /edge/devices`.
|
|
4
|
+
//
|
|
5
|
+
// `pos-edge-print` implemented this locally as `findEdgePrinter` (first
|
|
6
|
+
// paired+online device with `capabilities.includes('print')`, filtered by
|
|
7
|
+
// active branch). Any addon that needs a device by capability hits the same
|
|
8
|
+
// shape, so the lookup belongs here, not copied per addon.
|
|
9
|
+
//
|
|
10
|
+
// react-query is a PEER dependency of runtime-react: `useQuery` resolves to
|
|
11
|
+
// the HOST's singleton QueryClient (the MF shared singleton), so this hook
|
|
12
|
+
// never constructs its own client and stays cache-coherent with the rest of
|
|
13
|
+
// the app. Do not add a QueryClientProvider here.
|
|
14
|
+
import { useMemo } from 'react';
|
|
15
|
+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
16
|
+
import { useApi } from './api-context';
|
|
17
|
+
import { useCurrentBranch } from './api-context';
|
|
18
|
+
/** Stable react-query key for the org's edge device list. */
|
|
19
|
+
export function edgeDevicesKey() {
|
|
20
|
+
return ['edge-devices'];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Pick the best device for `capability`: online devices only, preferring one
|
|
24
|
+
* paired to `sucursalId` and falling back to an org-level device (no
|
|
25
|
+
* `sucursalId`) when the branch has none. Pure + exported for unit testing.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveEdgeDevice(devices, capability, sucursalId) {
|
|
28
|
+
const eligible = devices.filter((d) => d.status === 'online' && d.capabilities.includes(capability));
|
|
29
|
+
if (sucursalId != null) {
|
|
30
|
+
const branchMatch = eligible.find((d) => String(d.sucursalId ?? '') === String(sucursalId));
|
|
31
|
+
if (branchMatch)
|
|
32
|
+
return branchMatch;
|
|
33
|
+
}
|
|
34
|
+
return eligible.find((d) => d.sucursalId == null) ?? eligible[0];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the best online device offering `capability` for the active branch.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* const { device, isLoading } = useEdgeDevice('print')
|
|
41
|
+
* if (device) await sendEdgeDeviceCommand(device.id, { type: 'print', payload })
|
|
42
|
+
*/
|
|
43
|
+
export function useEdgeDevice(capability, opts = {}) {
|
|
44
|
+
const api = useApi();
|
|
45
|
+
const branch = useCurrentBranch();
|
|
46
|
+
const { staleTime, refetchInterval, enabled } = opts;
|
|
47
|
+
const query = useQuery({
|
|
48
|
+
queryKey: edgeDevicesKey(),
|
|
49
|
+
queryFn: async () => {
|
|
50
|
+
const res = await api.get('/edge/devices');
|
|
51
|
+
const body = res.data;
|
|
52
|
+
const devices = body?.success ? body.data : body;
|
|
53
|
+
return (devices ?? []);
|
|
54
|
+
},
|
|
55
|
+
staleTime: staleTime ?? 15_000,
|
|
56
|
+
refetchInterval,
|
|
57
|
+
enabled: (enabled ?? true) && !!capability,
|
|
58
|
+
});
|
|
59
|
+
const device = useMemo(() => resolveEdgeDevice(query.data ?? [], capability, branch.id), [query.data, capability, branch.id]);
|
|
60
|
+
return {
|
|
61
|
+
device,
|
|
62
|
+
isLoading: query.isLoading,
|
|
63
|
+
error: query.error,
|
|
64
|
+
refetch: () => {
|
|
65
|
+
void query.refetch();
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Send a command to a paired edge device (e.g. a print job, an open-drawer
|
|
71
|
+
* pulse, a weigh request). Generic over the command payload shape — each
|
|
72
|
+
* addon defines its own command contract with the local agent.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* const send = useSendEdgeDeviceCommand()
|
|
76
|
+
* await send.mutateAsync({ deviceId: device.id, command: { type: 'print', payload } })
|
|
77
|
+
*/
|
|
78
|
+
export async function sendEdgeDeviceCommand(api, deviceId, command) {
|
|
79
|
+
const res = await api.post(`/edge/devices/${deviceId}/commands`, command);
|
|
80
|
+
const body = res.data;
|
|
81
|
+
return body?.success ? body.data : body;
|
|
82
|
+
}
|
|
83
|
+
/** Mutation wrapper over {@link sendEdgeDeviceCommand} bound to the host's ApiClient. */
|
|
84
|
+
export function useSendEdgeDeviceCommand() {
|
|
85
|
+
const api = useApi();
|
|
86
|
+
const qc = useQueryClient();
|
|
87
|
+
return useMutation({
|
|
88
|
+
mutationFn: ({ deviceId, command }) => sendEdgeDeviceCommand(api, deviceId, command),
|
|
89
|
+
onSuccess: () => {
|
|
90
|
+
void qc.invalidateQueries({ queryKey: edgeDevicesKey() });
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface ResourceState<T> {
|
|
2
|
+
data: T | undefined;
|
|
3
|
+
isLoading: boolean;
|
|
4
|
+
error: Error | null;
|
|
5
|
+
refetch: () => void;
|
|
6
|
+
/**
|
|
7
|
+
* True once `isLoading` has stayed on for `stuckAfterMs` without landing.
|
|
8
|
+
* Whatever the exact timing race (an aborted fetch racing a remount, a
|
|
9
|
+
* slow/hung request), a caller needs a way OUT — a spinner with no escape
|
|
10
|
+
* hatch is a dead end. This never claims to know WHY the fetch is stuck;
|
|
11
|
+
* it only tells the caller long enough has passed to offer a retry
|
|
12
|
+
* instead of an indefinite loading state.
|
|
13
|
+
*/
|
|
14
|
+
stuck: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function useResource<T>(fetcher: (signal: AbortSignal) => Promise<T>, deps: ReadonlyArray<unknown>, options?: {
|
|
17
|
+
refetchInterval?: number;
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
/** Threshold in ms before `stuck` flips true. Default 8000. */
|
|
20
|
+
stuckAfterMs?: number;
|
|
21
|
+
}): ResourceState<T>;
|
|
22
|
+
export interface MutationState<TArgs, TResult> {
|
|
23
|
+
mutateAsync: (args: TArgs) => Promise<TResult>;
|
|
24
|
+
isPending: boolean;
|
|
25
|
+
error: Error | null;
|
|
26
|
+
}
|
|
27
|
+
export declare function useMutation<TArgs, TResult>(fn: (args: TArgs) => Promise<TResult>): MutationState<TArgs, TResult>;
|
|
28
|
+
//# sourceMappingURL=use-resource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-resource.d.ts","sourceRoot":"","sources":["../src/use-resource.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,IAAI,EAAE,CAAC,GAAG,SAAS,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB;;;;;;;OAOG;IACH,KAAK,EAAE,OAAO,CAAC;CAChB;AAID,wBAAgB,WAAW,CAAC,CAAC,EAC3B,OAAO,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EAC5C,IAAI,EAAE,aAAa,CAAC,OAAO,CAAC,EAC5B,OAAO,CAAC,EAAE;IACR,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GACA,aAAa,CAAC,CAAC,CAAC,CAuElB;AAED,MAAM,WAAW,aAAa,CAAC,KAAK,EAAE,OAAO;IAC3C,WAAW,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EACxC,EAAE,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,OAAO,CAAC,GACpC,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAqB/B"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny data-fetching hooks for federated addons.
|
|
3
|
+
*
|
|
4
|
+
* Addon-safe replacement for `@tanstack/react-query`: pulling tanstack into a
|
|
5
|
+
* federated remote risks a duplicate query-client / version clash with the
|
|
6
|
+
* host and bloats the MF bundle (see @asteby/metacore-runtime-react's
|
|
7
|
+
* singleton react-query for HOST-side code, which does not apply inside a
|
|
8
|
+
* federated addon bundle). These hooks cover exactly what an addon panel
|
|
9
|
+
* needs — a polling GET resource and an imperative mutation — over any
|
|
10
|
+
* fetch-like client.
|
|
11
|
+
*/
|
|
12
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
13
|
+
const DEFAULT_STUCK_AFTER_MS = 8_000;
|
|
14
|
+
export function useResource(fetcher, deps, options) {
|
|
15
|
+
const enabled = options?.enabled ?? true;
|
|
16
|
+
const stuckAfterMs = options?.stuckAfterMs ?? DEFAULT_STUCK_AFTER_MS;
|
|
17
|
+
const [data, setData] = useState(undefined);
|
|
18
|
+
const [isLoading, setIsLoading] = useState(enabled);
|
|
19
|
+
const [error, setError] = useState(null);
|
|
20
|
+
const fetcherRef = useRef(fetcher);
|
|
21
|
+
fetcherRef.current = fetcher;
|
|
22
|
+
const hasDataRef = useRef(false);
|
|
23
|
+
const [tick, setTick] = useState(0);
|
|
24
|
+
const [stuck, setStuck] = useState(false);
|
|
25
|
+
const refetch = useCallback(() => {
|
|
26
|
+
setStuck(false);
|
|
27
|
+
setTick((t) => t + 1);
|
|
28
|
+
}, []);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (!enabled) {
|
|
31
|
+
setIsLoading(false);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
let active = true;
|
|
36
|
+
// Stale-while-revalidate: once we have data, refetches/polls revalidate in
|
|
37
|
+
// the background without flipping isLoading — the UI keeps showing the last
|
|
38
|
+
// values instead of collapsing to a "loading" blank every poll.
|
|
39
|
+
if (!hasDataRef.current)
|
|
40
|
+
setIsLoading(true);
|
|
41
|
+
setStuck(false);
|
|
42
|
+
const stuckTimer = !hasDataRef.current
|
|
43
|
+
? setTimeout(() => {
|
|
44
|
+
if (active)
|
|
45
|
+
setStuck(true);
|
|
46
|
+
}, stuckAfterMs)
|
|
47
|
+
: undefined;
|
|
48
|
+
fetcherRef
|
|
49
|
+
.current(controller.signal)
|
|
50
|
+
.then((res) => {
|
|
51
|
+
if (!active)
|
|
52
|
+
return;
|
|
53
|
+
hasDataRef.current = true;
|
|
54
|
+
setData(res);
|
|
55
|
+
setError(null);
|
|
56
|
+
})
|
|
57
|
+
.catch((e) => {
|
|
58
|
+
if (controller.signal.aborted)
|
|
59
|
+
return;
|
|
60
|
+
if (!active)
|
|
61
|
+
return;
|
|
62
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
63
|
+
})
|
|
64
|
+
.finally(() => {
|
|
65
|
+
clearTimeout(stuckTimer);
|
|
66
|
+
if (active && !controller.signal.aborted) {
|
|
67
|
+
setIsLoading(false);
|
|
68
|
+
setStuck(false);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
return () => {
|
|
72
|
+
active = false;
|
|
73
|
+
clearTimeout(stuckTimer);
|
|
74
|
+
controller.abort();
|
|
75
|
+
};
|
|
76
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
77
|
+
}, [enabled, tick, stuckAfterMs, ...deps]);
|
|
78
|
+
// Polling
|
|
79
|
+
const interval = options?.refetchInterval;
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (!enabled || !interval)
|
|
82
|
+
return;
|
|
83
|
+
const id = setInterval(() => setTick((t) => t + 1), interval);
|
|
84
|
+
return () => clearInterval(id);
|
|
85
|
+
}, [enabled, interval]);
|
|
86
|
+
return { data, isLoading, error, refetch, stuck: stuck && isLoading };
|
|
87
|
+
}
|
|
88
|
+
export function useMutation(fn) {
|
|
89
|
+
const [isPending, setIsPending] = useState(false);
|
|
90
|
+
const [error, setError] = useState(null);
|
|
91
|
+
const fnRef = useRef(fn);
|
|
92
|
+
fnRef.current = fn;
|
|
93
|
+
const mutateAsync = useCallback(async (args) => {
|
|
94
|
+
setIsPending(true);
|
|
95
|
+
setError(null);
|
|
96
|
+
try {
|
|
97
|
+
return await fnRef.current(args);
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
101
|
+
setError(err);
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
setIsPending(false);
|
|
106
|
+
}
|
|
107
|
+
}, []);
|
|
108
|
+
return { mutateAsync, isPending, error };
|
|
109
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asteby/metacore-runtime-react",
|
|
3
|
-
"version": "37.
|
|
3
|
+
"version": "37.3.0",
|
|
4
4
|
"description": "React runtime for metacore hosts — renders addon contributions dynamically",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
"typescript": "^6.0.0",
|
|
68
68
|
"vitest": "^4.0.0",
|
|
69
69
|
"zustand": "^5.0.0",
|
|
70
|
-
"@asteby/metacore-
|
|
71
|
-
"@asteby/metacore-
|
|
70
|
+
"@asteby/metacore-sdk": "3.7.0",
|
|
71
|
+
"@asteby/metacore-ui": "2.17.3"
|
|
72
72
|
},
|
|
73
73
|
"scripts": {
|
|
74
74
|
"build": "tsc -p tsconfig.json",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Locks resolveEdgeDevice's selection rules: branch match wins, org-level
|
|
2
|
+
// device (no sucursalId) is the fallback, offline devices are never picked.
|
|
3
|
+
import { describe, it, expect } from 'vitest'
|
|
4
|
+
import { resolveEdgeDevice, type EdgeDevice } from '../use-edge-device'
|
|
5
|
+
|
|
6
|
+
function device(overrides: Partial<EdgeDevice>): EdgeDevice {
|
|
7
|
+
return {
|
|
8
|
+
id: 'd1',
|
|
9
|
+
label: 'Device',
|
|
10
|
+
kind: 'printer',
|
|
11
|
+
status: 'online',
|
|
12
|
+
capabilities: ['print'],
|
|
13
|
+
...overrides,
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('resolveEdgeDevice', () => {
|
|
18
|
+
it('prefers a device paired to the active branch', () => {
|
|
19
|
+
const branch = device({ id: 'branch', sucursalId: 'suc-1' })
|
|
20
|
+
const other = device({ id: 'other', sucursalId: 'suc-2' })
|
|
21
|
+
expect(resolveEdgeDevice([other, branch], 'print', 'suc-1')).toEqual(branch)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('falls back to an org-level device when the branch has none', () => {
|
|
25
|
+
const org = device({ id: 'org', sucursalId: null })
|
|
26
|
+
const other = device({ id: 'other', sucursalId: 'suc-2' })
|
|
27
|
+
expect(resolveEdgeDevice([other, org], 'print', 'suc-1')).toEqual(org)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('returns undefined when no device offers the capability', () => {
|
|
31
|
+
const weigher = device({ id: 'w1', capabilities: ['weigh'] })
|
|
32
|
+
expect(resolveEdgeDevice([weigher], 'print', 'suc-1')).toBeUndefined()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('ignores offline devices even if capability matches', () => {
|
|
36
|
+
const offline = device({ id: 'off', status: 'offline', sucursalId: 'suc-1' })
|
|
37
|
+
expect(resolveEdgeDevice([offline], 'print', 'suc-1')).toBeUndefined()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('falls back to any eligible device when no sucursalId is given', () => {
|
|
41
|
+
const d = device({ id: 'any', sucursalId: 'suc-9' })
|
|
42
|
+
expect(resolveEdgeDevice([d], 'print')).toEqual(d)
|
|
43
|
+
})
|
|
44
|
+
})
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
|
|
3
|
+
import { renderHook, act } from '@testing-library/react'
|
|
4
|
+
import { useResource } from '../use-resource'
|
|
5
|
+
|
|
6
|
+
describe('useResource stuck flag', () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
vi.useFakeTimers()
|
|
9
|
+
})
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
vi.useRealTimers()
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('is not stuck before the threshold elapses', () => {
|
|
15
|
+
const fetcher = vi.fn(() => new Promise<string>(() => {}))
|
|
16
|
+
const { result } = renderHook(() =>
|
|
17
|
+
useResource(fetcher, [], { stuckAfterMs: 8_000 }),
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
expect(result.current.isLoading).toBe(true)
|
|
21
|
+
expect(result.current.stuck).toBe(false)
|
|
22
|
+
|
|
23
|
+
act(() => {
|
|
24
|
+
vi.advanceTimersByTime(7_999)
|
|
25
|
+
})
|
|
26
|
+
expect(result.current.stuck).toBe(false)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('flips stuck once loading is sustained past the threshold', () => {
|
|
30
|
+
const fetcher = vi.fn(() => new Promise<string>(() => {}))
|
|
31
|
+
const { result } = renderHook(() =>
|
|
32
|
+
useResource(fetcher, [], { stuckAfterMs: 8_000 }),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
act(() => {
|
|
36
|
+
vi.advanceTimersByTime(8_000)
|
|
37
|
+
})
|
|
38
|
+
expect(result.current.isLoading).toBe(true)
|
|
39
|
+
expect(result.current.stuck).toBe(true)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('never gets stuck when the fetch resolves before the threshold', async () => {
|
|
43
|
+
let resolveFetch: (value: string) => void = () => {}
|
|
44
|
+
const fetcher = vi.fn(
|
|
45
|
+
() =>
|
|
46
|
+
new Promise<string>((resolve) => {
|
|
47
|
+
resolveFetch = resolve
|
|
48
|
+
}),
|
|
49
|
+
)
|
|
50
|
+
const { result } = renderHook(() =>
|
|
51
|
+
useResource(fetcher, [], { stuckAfterMs: 8_000 }),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
act(() => {
|
|
55
|
+
vi.advanceTimersByTime(1_000)
|
|
56
|
+
})
|
|
57
|
+
expect(result.current.stuck).toBe(false)
|
|
58
|
+
|
|
59
|
+
await act(async () => {
|
|
60
|
+
resolveFetch('data')
|
|
61
|
+
await Promise.resolve()
|
|
62
|
+
await Promise.resolve()
|
|
63
|
+
})
|
|
64
|
+
expect(result.current.isLoading).toBe(false)
|
|
65
|
+
expect(result.current.data).toBe('data')
|
|
66
|
+
expect(result.current.stuck).toBe(false)
|
|
67
|
+
|
|
68
|
+
act(() => {
|
|
69
|
+
vi.advanceTimersByTime(8_000)
|
|
70
|
+
})
|
|
71
|
+
expect(result.current.stuck).toBe(false)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('clears stuck and restarts the timer on refetch', () => {
|
|
75
|
+
const fetcher = vi.fn(() => new Promise<string>(() => {}))
|
|
76
|
+
const { result } = renderHook(() =>
|
|
77
|
+
useResource(fetcher, [], { stuckAfterMs: 8_000 }),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
act(() => {
|
|
81
|
+
vi.advanceTimersByTime(8_000)
|
|
82
|
+
})
|
|
83
|
+
expect(result.current.stuck).toBe(true)
|
|
84
|
+
|
|
85
|
+
act(() => {
|
|
86
|
+
result.current.refetch()
|
|
87
|
+
})
|
|
88
|
+
expect(result.current.stuck).toBe(false)
|
|
89
|
+
|
|
90
|
+
act(() => {
|
|
91
|
+
vi.advanceTimersByTime(7_999)
|
|
92
|
+
})
|
|
93
|
+
expect(result.current.stuck).toBe(false)
|
|
94
|
+
|
|
95
|
+
act(() => {
|
|
96
|
+
vi.advanceTimersByTime(1)
|
|
97
|
+
})
|
|
98
|
+
expect(result.current.stuck).toBe(true)
|
|
99
|
+
})
|
|
100
|
+
})
|
package/src/dynamic-table.tsx
CHANGED
|
@@ -511,7 +511,10 @@ export function DynamicTable({
|
|
|
511
511
|
// the URL goes bare, and the sidebar falls back to the model default
|
|
512
512
|
// (the "click twice to move the active to the right entry" bug).
|
|
513
513
|
const current = new URLSearchParams(window.location.search)
|
|
514
|
-
|
|
514
|
+
// `action` is host-owned (?action=stamp_fiscal deep-links from
|
|
515
|
+
// notifications). Without carrying it, the first URL rewrite drops it
|
|
516
|
+
// before the list route can open ActionModalDispatcher.
|
|
517
|
+
for (const key of ['view', 'group_by', 'action']) {
|
|
515
518
|
const v = current.get(key)
|
|
516
519
|
if (v) params.set(key, v)
|
|
517
520
|
}
|
package/src/index.ts
CHANGED
|
@@ -127,6 +127,12 @@ export {
|
|
|
127
127
|
type UseDynamicFiltersResult,
|
|
128
128
|
} from './use-dynamic-filters'
|
|
129
129
|
export { useDebouncedValue, SEARCH_DEBOUNCE_MS } from './use-debounced-value'
|
|
130
|
+
export {
|
|
131
|
+
useResource,
|
|
132
|
+
useMutation,
|
|
133
|
+
type ResourceState,
|
|
134
|
+
type MutationState,
|
|
135
|
+
} from './use-resource'
|
|
130
136
|
export * from './dynamic-form'
|
|
131
137
|
export {
|
|
132
138
|
FilePickButton,
|
|
@@ -215,6 +221,7 @@ export * from './navigation-builder'
|
|
|
215
221
|
export * from './i18n-provider'
|
|
216
222
|
export * from './api-context'
|
|
217
223
|
export * from './use-addon-settings'
|
|
224
|
+
export * from './use-edge-device'
|
|
218
225
|
export * from './use-print-document'
|
|
219
226
|
export * from './print-document-button'
|
|
220
227
|
export * from './use-org-document-templates'
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// useEdgeDevice — THE standard primitive for resolving a paired edge device
|
|
2
|
+
// (printer, scale, payment terminal, cashdro…) by capability, without every
|
|
3
|
+
// addon re-implementing its own lookup against `GET /edge/devices`.
|
|
4
|
+
//
|
|
5
|
+
// `pos-edge-print` implemented this locally as `findEdgePrinter` (first
|
|
6
|
+
// paired+online device with `capabilities.includes('print')`, filtered by
|
|
7
|
+
// active branch). Any addon that needs a device by capability hits the same
|
|
8
|
+
// shape, so the lookup belongs here, not copied per addon.
|
|
9
|
+
//
|
|
10
|
+
// react-query is a PEER dependency of runtime-react: `useQuery` resolves to
|
|
11
|
+
// the HOST's singleton QueryClient (the MF shared singleton), so this hook
|
|
12
|
+
// never constructs its own client and stays cache-coherent with the rest of
|
|
13
|
+
// the app. Do not add a QueryClientProvider here.
|
|
14
|
+
import { useMemo } from 'react'
|
|
15
|
+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
|
16
|
+
import { useApi } from './api-context'
|
|
17
|
+
import { useCurrentBranch } from './api-context'
|
|
18
|
+
|
|
19
|
+
/** A physical device paired to an org (and usually a branch) via ops-edge-agent. */
|
|
20
|
+
export interface EdgeDevice {
|
|
21
|
+
id: string
|
|
22
|
+
label: string
|
|
23
|
+
kind: string
|
|
24
|
+
status: 'online' | 'offline'
|
|
25
|
+
capabilities: string[]
|
|
26
|
+
sucursalId?: string | number | null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Stable react-query key for the org's edge device list. */
|
|
30
|
+
export function edgeDevicesKey() {
|
|
31
|
+
return ['edge-devices'] as const
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Pick the best device for `capability`: online devices only, preferring one
|
|
36
|
+
* paired to `sucursalId` and falling back to an org-level device (no
|
|
37
|
+
* `sucursalId`) when the branch has none. Pure + exported for unit testing.
|
|
38
|
+
*/
|
|
39
|
+
export function resolveEdgeDevice(
|
|
40
|
+
devices: EdgeDevice[],
|
|
41
|
+
capability: string,
|
|
42
|
+
sucursalId?: string | number | null,
|
|
43
|
+
): EdgeDevice | undefined {
|
|
44
|
+
const eligible = devices.filter(
|
|
45
|
+
(d) => d.status === 'online' && d.capabilities.includes(capability),
|
|
46
|
+
)
|
|
47
|
+
if (sucursalId != null) {
|
|
48
|
+
const branchMatch = eligible.find((d) => String(d.sucursalId ?? '') === String(sucursalId))
|
|
49
|
+
if (branchMatch) return branchMatch
|
|
50
|
+
}
|
|
51
|
+
return eligible.find((d) => d.sucursalId == null) ?? eligible[0]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface UseEdgeDeviceOptions {
|
|
55
|
+
/** Override staleTime (default 15s — device pairing/online status can change). */
|
|
56
|
+
staleTime?: number
|
|
57
|
+
/** Poll interval; devices go on/offline without any local event to react to. */
|
|
58
|
+
refetchInterval?: number
|
|
59
|
+
/** Defer fetching. */
|
|
60
|
+
enabled?: boolean
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface UseEdgeDeviceResult {
|
|
64
|
+
device: EdgeDevice | undefined
|
|
65
|
+
isLoading: boolean
|
|
66
|
+
error: unknown
|
|
67
|
+
refetch: () => void
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Resolve the best online device offering `capability` for the active branch.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* const { device, isLoading } = useEdgeDevice('print')
|
|
75
|
+
* if (device) await sendEdgeDeviceCommand(device.id, { type: 'print', payload })
|
|
76
|
+
*/
|
|
77
|
+
export function useEdgeDevice(
|
|
78
|
+
capability: string,
|
|
79
|
+
opts: UseEdgeDeviceOptions = {},
|
|
80
|
+
): UseEdgeDeviceResult {
|
|
81
|
+
const api = useApi()
|
|
82
|
+
const branch = useCurrentBranch()
|
|
83
|
+
const { staleTime, refetchInterval, enabled } = opts
|
|
84
|
+
|
|
85
|
+
const query = useQuery<EdgeDevice[]>({
|
|
86
|
+
queryKey: edgeDevicesKey(),
|
|
87
|
+
queryFn: async () => {
|
|
88
|
+
const res = await api.get('/edge/devices')
|
|
89
|
+
const body = (res as { data: any }).data
|
|
90
|
+
const devices = body?.success ? body.data : body
|
|
91
|
+
return (devices ?? []) as EdgeDevice[]
|
|
92
|
+
},
|
|
93
|
+
staleTime: staleTime ?? 15_000,
|
|
94
|
+
refetchInterval,
|
|
95
|
+
enabled: (enabled ?? true) && !!capability,
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
const device = useMemo(
|
|
99
|
+
() => resolveEdgeDevice(query.data ?? [], capability, branch.id),
|
|
100
|
+
[query.data, capability, branch.id],
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
device,
|
|
105
|
+
isLoading: query.isLoading,
|
|
106
|
+
error: query.error,
|
|
107
|
+
refetch: () => {
|
|
108
|
+
void query.refetch()
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Send a command to a paired edge device (e.g. a print job, an open-drawer
|
|
115
|
+
* pulse, a weigh request). Generic over the command payload shape — each
|
|
116
|
+
* addon defines its own command contract with the local agent.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* const send = useSendEdgeDeviceCommand()
|
|
120
|
+
* await send.mutateAsync({ deviceId: device.id, command: { type: 'print', payload } })
|
|
121
|
+
*/
|
|
122
|
+
export async function sendEdgeDeviceCommand(
|
|
123
|
+
api: Pick<import('./api-context').ApiClient, 'post'>,
|
|
124
|
+
deviceId: string,
|
|
125
|
+
command: Record<string, unknown>,
|
|
126
|
+
): Promise<unknown> {
|
|
127
|
+
const res = await api.post(`/edge/devices/${deviceId}/commands`, command)
|
|
128
|
+
const body = (res as { data: any }).data
|
|
129
|
+
return body?.success ? body.data : body
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface SendEdgeDeviceCommandArgs {
|
|
133
|
+
deviceId: string
|
|
134
|
+
command: Record<string, unknown>
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Mutation wrapper over {@link sendEdgeDeviceCommand} bound to the host's ApiClient. */
|
|
138
|
+
export function useSendEdgeDeviceCommand() {
|
|
139
|
+
const api = useApi()
|
|
140
|
+
const qc = useQueryClient()
|
|
141
|
+
|
|
142
|
+
return useMutation<unknown, Error, SendEdgeDeviceCommandArgs>({
|
|
143
|
+
mutationFn: ({ deviceId, command }) => sendEdgeDeviceCommand(api, deviceId, command),
|
|
144
|
+
onSuccess: () => {
|
|
145
|
+
void qc.invalidateQueries({ queryKey: edgeDevicesKey() })
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny data-fetching hooks for federated addons.
|
|
3
|
+
*
|
|
4
|
+
* Addon-safe replacement for `@tanstack/react-query`: pulling tanstack into a
|
|
5
|
+
* federated remote risks a duplicate query-client / version clash with the
|
|
6
|
+
* host and bloats the MF bundle (see @asteby/metacore-runtime-react's
|
|
7
|
+
* singleton react-query for HOST-side code, which does not apply inside a
|
|
8
|
+
* federated addon bundle). These hooks cover exactly what an addon panel
|
|
9
|
+
* needs — a polling GET resource and an imperative mutation — over any
|
|
10
|
+
* fetch-like client.
|
|
11
|
+
*/
|
|
12
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
13
|
+
|
|
14
|
+
export interface ResourceState<T> {
|
|
15
|
+
data: T | undefined;
|
|
16
|
+
isLoading: boolean;
|
|
17
|
+
error: Error | null;
|
|
18
|
+
refetch: () => void;
|
|
19
|
+
/**
|
|
20
|
+
* True once `isLoading` has stayed on for `stuckAfterMs` without landing.
|
|
21
|
+
* Whatever the exact timing race (an aborted fetch racing a remount, a
|
|
22
|
+
* slow/hung request), a caller needs a way OUT — a spinner with no escape
|
|
23
|
+
* hatch is a dead end. This never claims to know WHY the fetch is stuck;
|
|
24
|
+
* it only tells the caller long enough has passed to offer a retry
|
|
25
|
+
* instead of an indefinite loading state.
|
|
26
|
+
*/
|
|
27
|
+
stuck: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const DEFAULT_STUCK_AFTER_MS = 8_000;
|
|
31
|
+
|
|
32
|
+
export function useResource<T>(
|
|
33
|
+
fetcher: (signal: AbortSignal) => Promise<T>,
|
|
34
|
+
deps: ReadonlyArray<unknown>,
|
|
35
|
+
options?: {
|
|
36
|
+
refetchInterval?: number;
|
|
37
|
+
enabled?: boolean;
|
|
38
|
+
/** Threshold in ms before `stuck` flips true. Default 8000. */
|
|
39
|
+
stuckAfterMs?: number;
|
|
40
|
+
},
|
|
41
|
+
): ResourceState<T> {
|
|
42
|
+
const enabled = options?.enabled ?? true;
|
|
43
|
+
const stuckAfterMs = options?.stuckAfterMs ?? DEFAULT_STUCK_AFTER_MS;
|
|
44
|
+
const [data, setData] = useState<T | undefined>(undefined);
|
|
45
|
+
const [isLoading, setIsLoading] = useState<boolean>(enabled);
|
|
46
|
+
const [error, setError] = useState<Error | null>(null);
|
|
47
|
+
const fetcherRef = useRef(fetcher);
|
|
48
|
+
fetcherRef.current = fetcher;
|
|
49
|
+
const hasDataRef = useRef(false);
|
|
50
|
+
const [tick, setTick] = useState(0);
|
|
51
|
+
const [stuck, setStuck] = useState(false);
|
|
52
|
+
|
|
53
|
+
const refetch = useCallback(() => {
|
|
54
|
+
setStuck(false);
|
|
55
|
+
setTick((t) => t + 1);
|
|
56
|
+
}, []);
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (!enabled) {
|
|
60
|
+
setIsLoading(false);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const controller = new AbortController();
|
|
64
|
+
let active = true;
|
|
65
|
+
// Stale-while-revalidate: once we have data, refetches/polls revalidate in
|
|
66
|
+
// the background without flipping isLoading — the UI keeps showing the last
|
|
67
|
+
// values instead of collapsing to a "loading" blank every poll.
|
|
68
|
+
if (!hasDataRef.current) setIsLoading(true);
|
|
69
|
+
setStuck(false);
|
|
70
|
+
const stuckTimer = !hasDataRef.current
|
|
71
|
+
? setTimeout(() => {
|
|
72
|
+
if (active) setStuck(true);
|
|
73
|
+
}, stuckAfterMs)
|
|
74
|
+
: undefined;
|
|
75
|
+
fetcherRef
|
|
76
|
+
.current(controller.signal)
|
|
77
|
+
.then((res) => {
|
|
78
|
+
if (!active) return;
|
|
79
|
+
hasDataRef.current = true;
|
|
80
|
+
setData(res);
|
|
81
|
+
setError(null);
|
|
82
|
+
})
|
|
83
|
+
.catch((e: unknown) => {
|
|
84
|
+
if (controller.signal.aborted) return;
|
|
85
|
+
if (!active) return;
|
|
86
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
87
|
+
})
|
|
88
|
+
.finally(() => {
|
|
89
|
+
clearTimeout(stuckTimer);
|
|
90
|
+
if (active && !controller.signal.aborted) {
|
|
91
|
+
setIsLoading(false);
|
|
92
|
+
setStuck(false);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return () => {
|
|
96
|
+
active = false;
|
|
97
|
+
clearTimeout(stuckTimer);
|
|
98
|
+
controller.abort();
|
|
99
|
+
};
|
|
100
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
101
|
+
}, [enabled, tick, stuckAfterMs, ...deps]);
|
|
102
|
+
|
|
103
|
+
// Polling
|
|
104
|
+
const interval = options?.refetchInterval;
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (!enabled || !interval) return;
|
|
107
|
+
const id = setInterval(() => setTick((t) => t + 1), interval);
|
|
108
|
+
return () => clearInterval(id);
|
|
109
|
+
}, [enabled, interval]);
|
|
110
|
+
|
|
111
|
+
return { data, isLoading, error, refetch, stuck: stuck && isLoading };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface MutationState<TArgs, TResult> {
|
|
115
|
+
mutateAsync: (args: TArgs) => Promise<TResult>;
|
|
116
|
+
isPending: boolean;
|
|
117
|
+
error: Error | null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function useMutation<TArgs, TResult>(
|
|
121
|
+
fn: (args: TArgs) => Promise<TResult>,
|
|
122
|
+
): MutationState<TArgs, TResult> {
|
|
123
|
+
const [isPending, setIsPending] = useState(false);
|
|
124
|
+
const [error, setError] = useState<Error | null>(null);
|
|
125
|
+
const fnRef = useRef(fn);
|
|
126
|
+
fnRef.current = fn;
|
|
127
|
+
|
|
128
|
+
const mutateAsync = useCallback(async (args: TArgs) => {
|
|
129
|
+
setIsPending(true);
|
|
130
|
+
setError(null);
|
|
131
|
+
try {
|
|
132
|
+
return await fnRef.current(args);
|
|
133
|
+
} catch (e) {
|
|
134
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
135
|
+
setError(err);
|
|
136
|
+
throw err;
|
|
137
|
+
} finally {
|
|
138
|
+
setIsPending(false);
|
|
139
|
+
}
|
|
140
|
+
}, []);
|
|
141
|
+
|
|
142
|
+
return { mutateAsync, isPending, error };
|
|
143
|
+
}
|