@burdenoff/microfe-movethewheels 2026.1011.4 → 2026.1012.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/hooks/useEpod.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import { useMoveTheWheels as e } from "../providers/MoveTheWheelsProvider.js";
|
|
2
2
|
import { executeGraphQL as t } from "../utils/graphqlClient.js";
|
|
3
3
|
import { useShipmentStatusCommand as n } from "./useShipmentStatusAuthority.js";
|
|
4
|
-
import { useCallback as r, useEffect as i, useMemo as a,
|
|
5
|
-
import { parse as
|
|
4
|
+
import { useCallback as r, useEffect as i, useMemo as a, useRef as o, useState as s } from "react";
|
|
5
|
+
import { parse as c } from "graphql";
|
|
6
6
|
//#region src/movethewheels/hooks/useEpod.ts
|
|
7
|
-
var
|
|
7
|
+
var l = "\n id\n deliveryJobId\n shipmentId\n recipientName\n signatureType\n signatureData\n photoFileIds\n latitude\n longitude\n locationAccuracy\n deliveredAt\n capturedByDriverId\n notes\n", u = `
|
|
8
8
|
query GetEpod($deliveryJobId: ID!) {
|
|
9
9
|
epod(deliveryJobId: $deliveryJobId) {
|
|
10
|
-
${
|
|
10
|
+
${l}
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
-
`,
|
|
13
|
+
`, d = "\n query MyPendingEpodDeliveryJobs($pagination: PaginationInput) {\n myPendingEpodDeliveryJobs(pagination: $pagination) {\n id\n deliveryJobNumber\n status\n priority\n deliveryAddress\n scheduledDelivery\n customer {\n name\n phone\n }\n }\n myPendingEpodDeliveryJobsCount\n }\n", f = c(`
|
|
14
14
|
mutation CaptureEpod($input: CaptureEpodInput!) {
|
|
15
15
|
captureEpod(input: $input) {
|
|
16
|
-
${
|
|
16
|
+
${l}
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
-
`),
|
|
20
|
-
function
|
|
19
|
+
`), p = "\n mutation EpodInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }\n", m = "\n mutation EpodCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n }\n }\n", h = "\n query EpodFileUrl($id: ID!, $expiresIn: Int) {\n getFileDownloadUrl(id: $id, expiresIn: $expiresIn)\n }\n", g = 21600;
|
|
20
|
+
function _(e) {
|
|
21
21
|
return e === "typed" ? "TYPED" : "DRAWN";
|
|
22
22
|
}
|
|
23
|
-
function
|
|
23
|
+
function v(e) {
|
|
24
24
|
return {
|
|
25
25
|
id: e.id,
|
|
26
26
|
deliveryJobId: e.deliveryJobId,
|
|
@@ -37,7 +37,7 @@ function _(e) {
|
|
|
37
37
|
notes: e.notes ?? void 0
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
-
function
|
|
40
|
+
function y() {
|
|
41
41
|
let { apiGatewayUrl: t, authToken: n, workspaceToken: r, workspaceId: i, tenantId: o, currentUser: s } = e();
|
|
42
42
|
return a(() => ({
|
|
43
43
|
apiGatewayUrl: t,
|
|
@@ -55,20 +55,20 @@ function v() {
|
|
|
55
55
|
s?.id
|
|
56
56
|
]);
|
|
57
57
|
}
|
|
58
|
-
function
|
|
59
|
-
let n =
|
|
58
|
+
function b(e) {
|
|
59
|
+
let n = y(), [a, o] = s(null), [c, l] = s(!1), [d, f] = s(null), p = r(async () => {
|
|
60
60
|
if (!e) {
|
|
61
|
-
|
|
61
|
+
o(null);
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
|
-
|
|
64
|
+
l(!0), f(null);
|
|
65
65
|
try {
|
|
66
|
-
let r = await t(n,
|
|
67
|
-
|
|
66
|
+
let r = await t(n, u, { deliveryJobId: e });
|
|
67
|
+
o(r.epod ? v(r.epod) : null);
|
|
68
68
|
} catch (e) {
|
|
69
|
-
f(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to load proof of delivery")),
|
|
69
|
+
f(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to load proof of delivery")), o(null);
|
|
70
70
|
} finally {
|
|
71
|
-
|
|
71
|
+
l(!1);
|
|
72
72
|
}
|
|
73
73
|
}, [e, n]);
|
|
74
74
|
return i(() => {
|
|
@@ -80,69 +80,133 @@ function y(e) {
|
|
|
80
80
|
refetch: p
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
|
-
function
|
|
84
|
-
let { limit: n = 20, offset: a = 0 } = e,
|
|
85
|
-
|
|
83
|
+
function x(e = {}) {
|
|
84
|
+
let { limit: n = 20, offset: a = 0 } = e, c = y(), l = JSON.stringify([
|
|
85
|
+
c.apiGatewayUrl,
|
|
86
|
+
c.workspaceId,
|
|
87
|
+
c.tenantId,
|
|
88
|
+
c.actorId
|
|
89
|
+
]), u = JSON.stringify([
|
|
90
|
+
l,
|
|
91
|
+
n,
|
|
92
|
+
a
|
|
93
|
+
]), f = o({
|
|
94
|
+
scopeKey: l,
|
|
95
|
+
ids: /* @__PURE__ */ new Set()
|
|
96
|
+
}), p = o(0), [m, h] = s({
|
|
97
|
+
scopeKey: l,
|
|
98
|
+
readKey: u,
|
|
99
|
+
jobs: [],
|
|
100
|
+
totalJobs: 0,
|
|
101
|
+
hasMore: !1,
|
|
102
|
+
isLoading: !0,
|
|
103
|
+
error: null
|
|
104
|
+
}), g = r(async () => {
|
|
105
|
+
let e = ++p.current;
|
|
106
|
+
f.current.scopeKey !== l && (f.current = {
|
|
107
|
+
scopeKey: l,
|
|
108
|
+
ids: /* @__PURE__ */ new Set()
|
|
109
|
+
}), h((e) => ({
|
|
110
|
+
scopeKey: l,
|
|
111
|
+
readKey: u,
|
|
112
|
+
jobs: e.readKey === u ? e.jobs : [],
|
|
113
|
+
totalJobs: e.readKey === u ? e.totalJobs : 0,
|
|
114
|
+
hasMore: e.readKey === u ? e.hasMore : !1,
|
|
115
|
+
isLoading: !0,
|
|
116
|
+
error: null
|
|
117
|
+
}));
|
|
86
118
|
try {
|
|
87
|
-
let
|
|
119
|
+
let r = await t(c, d, { pagination: {
|
|
88
120
|
limit: n,
|
|
89
121
|
offset: a
|
|
90
122
|
} });
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
123
|
+
if (e !== p.current) return;
|
|
124
|
+
let i = r.myPendingEpodDeliveryJobs, o = r.myPendingEpodDeliveryJobsCount ?? 0, s = i.filter((e) => f.current.ids.has(e.id)).length;
|
|
125
|
+
h({
|
|
126
|
+
scopeKey: l,
|
|
127
|
+
readKey: u,
|
|
128
|
+
totalJobs: Math.max(0, o - s),
|
|
129
|
+
hasMore: a + i.length < o,
|
|
130
|
+
jobs: i.filter((e) => !f.current.ids.has(e.id)).map((e) => ({
|
|
131
|
+
id: e.id,
|
|
132
|
+
deliveryJobNumber: e.deliveryJobNumber,
|
|
133
|
+
status: e.status,
|
|
134
|
+
priority: e.priority,
|
|
135
|
+
deliveryAddress: e.deliveryAddress,
|
|
136
|
+
scheduledDelivery: e.scheduledDelivery ? new Date(e.scheduledDelivery) : void 0,
|
|
137
|
+
customerName: e.customer?.name ?? void 0,
|
|
138
|
+
customerPhone: e.customer?.phone ?? void 0
|
|
139
|
+
})),
|
|
140
|
+
isLoading: !1,
|
|
141
|
+
error: null
|
|
142
|
+
});
|
|
143
|
+
} catch (t) {
|
|
144
|
+
if (e !== p.current) return;
|
|
145
|
+
h({
|
|
146
|
+
scopeKey: l,
|
|
147
|
+
readKey: u,
|
|
148
|
+
jobs: [],
|
|
149
|
+
totalJobs: 0,
|
|
150
|
+
hasMore: !1,
|
|
151
|
+
isLoading: !1,
|
|
152
|
+
error: t instanceof Error ? t : /* @__PURE__ */ Error("Failed to load pending deliveries")
|
|
153
|
+
});
|
|
105
154
|
}
|
|
106
155
|
}, [
|
|
107
|
-
|
|
156
|
+
c,
|
|
108
157
|
n,
|
|
109
|
-
a
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
158
|
+
a,
|
|
159
|
+
l,
|
|
160
|
+
u
|
|
161
|
+
]), _ = r(() => {
|
|
162
|
+
p.current++;
|
|
163
|
+
}, []);
|
|
164
|
+
i(() => (g(), _), [g, _]);
|
|
165
|
+
let v = r((e) => {
|
|
166
|
+
f.current.scopeKey !== l || f.current.ids.has(e) || (f.current.ids.add(e), h((t) => {
|
|
167
|
+
if (t.scopeKey !== l) return t;
|
|
168
|
+
let n = t.jobs.some((t) => t.id === e);
|
|
169
|
+
return {
|
|
170
|
+
...t,
|
|
171
|
+
jobs: t.jobs.filter((t) => t.id !== e),
|
|
172
|
+
totalJobs: Math.max(0, t.totalJobs - +!!n)
|
|
173
|
+
};
|
|
174
|
+
}));
|
|
175
|
+
}, [l]), b = m.readKey === u;
|
|
176
|
+
return {
|
|
177
|
+
jobs: b ? m.jobs : [],
|
|
178
|
+
totalJobs: b ? m.totalJobs : 0,
|
|
179
|
+
hasMore: b && m.hasMore,
|
|
180
|
+
isLoading: !b || m.isLoading,
|
|
181
|
+
error: b ? m.error : null,
|
|
182
|
+
refetch: g,
|
|
183
|
+
retireAcknowledgedJob: v
|
|
120
184
|
};
|
|
121
185
|
}
|
|
122
|
-
function
|
|
123
|
-
return !!e && typeof e.id == "string" && e.id.trim().length > 0 && e.deliveryJobId === t.deliveryJobId && e.shipmentId === t.expectedShipmentId && e.recipientName === t.recipientName && e.signatureType ===
|
|
186
|
+
function S(e, t) {
|
|
187
|
+
return !!e && typeof e.id == "string" && e.id.trim().length > 0 && e.deliveryJobId === t.deliveryJobId && e.shipmentId === t.expectedShipmentId && e.recipientName === t.recipientName && e.signatureType === _(t.signatureType) && e.signatureData === t.signatureData && typeof e.capturedByDriverId == "string" && e.capturedByDriverId.trim().length > 0 && typeof e.deliveredAt == "string" && Number.isFinite(Date.parse(e.deliveredAt)) && (e.photoFileIds === null ? (t.photoFileIds ?? []).length === 0 : Array.isArray(e.photoFileIds) && e.photoFileIds.length === (t.photoFileIds ?? []).length && e.photoFileIds.every((e, n) => e === (t.photoFileIds ?? [])[n])) && e.latitude === (t.latitude ?? null) && e.longitude === (t.longitude ?? null) && e.locationAccuracy === (t.locationAccuracy ?? null) && e.notes === (t.notes ?? null);
|
|
124
188
|
}
|
|
125
|
-
function
|
|
126
|
-
let e = n(), [t, i] =
|
|
189
|
+
function C() {
|
|
190
|
+
let e = n(), [t, i] = s(null);
|
|
127
191
|
return {
|
|
128
192
|
captureEpod: r(async (t) => {
|
|
129
193
|
i(null);
|
|
130
194
|
try {
|
|
131
|
-
let n = await e.run(
|
|
195
|
+
let n = await e.run(f, { input: {
|
|
132
196
|
expectedShipmentStatusRevision: t.expectedShipmentStatusRevision,
|
|
133
197
|
completionAttestation: t.completionAttestation,
|
|
134
198
|
deliveryJobId: t.deliveryJobId,
|
|
135
199
|
recipientName: t.recipientName,
|
|
136
|
-
signatureType:
|
|
200
|
+
signatureType: _(t.signatureType),
|
|
137
201
|
signatureData: t.signatureData,
|
|
138
202
|
photoFileIds: t.photoFileIds,
|
|
139
203
|
latitude: t.latitude,
|
|
140
204
|
longitude: t.longitude,
|
|
141
205
|
locationAccuracy: t.locationAccuracy,
|
|
142
206
|
notes: t.notes
|
|
143
|
-
} }, (e) =>
|
|
207
|
+
} }, (e) => S(e.captureEpod, t));
|
|
144
208
|
if (!n) throw Error("Capture was not acknowledged. Review the recorded status and the capture message before taking further action.");
|
|
145
|
-
return
|
|
209
|
+
return v(n.captureEpod);
|
|
146
210
|
} catch (e) {
|
|
147
211
|
let t = e instanceof Error ? e : /* @__PURE__ */ Error("Failed to capture proof of delivery");
|
|
148
212
|
throw i(t), t;
|
|
@@ -153,32 +217,32 @@ function S() {
|
|
|
153
217
|
captureFeedback: e.feedback
|
|
154
218
|
};
|
|
155
219
|
}
|
|
156
|
-
function
|
|
157
|
-
let e =
|
|
220
|
+
function w() {
|
|
221
|
+
let e = y(), [n, i] = s(!1), [a, o] = s(0), [c, l] = s(null);
|
|
158
222
|
return {
|
|
159
223
|
upload: r(async (n) => {
|
|
160
|
-
i(!0),
|
|
224
|
+
i(!0), o(0), l(null);
|
|
161
225
|
try {
|
|
162
|
-
let r = n.name.lastIndexOf("."), i = r > -1 ? n.name.slice(r + 1) : "", a = n.type || "application/octet-stream",
|
|
226
|
+
let r = n.name.lastIndexOf("."), i = r > -1 ? n.name.slice(r + 1) : "", a = n.type || "application/octet-stream", s = await t(e, p, { input: {
|
|
163
227
|
fileName: n.name,
|
|
164
228
|
extension: i,
|
|
165
229
|
mimeType: a,
|
|
166
230
|
fileSize: String(n.size),
|
|
167
231
|
visibility: "WORKSPACE"
|
|
168
|
-
} }), { uploadUrl: c } =
|
|
232
|
+
} }), { uploadUrl: c } = s.initiateUpload, l = s.initiateUpload.session.id;
|
|
169
233
|
if (!c) throw Error("The file service did not return an upload URL.");
|
|
170
|
-
|
|
234
|
+
o(50);
|
|
171
235
|
let u = await fetch(c, {
|
|
172
236
|
method: "PUT",
|
|
173
237
|
headers: { "Content-Type": a },
|
|
174
238
|
body: n
|
|
175
239
|
});
|
|
176
240
|
if (!u.ok) throw Error(`File storage rejected the upload (HTTP ${u.status}).`);
|
|
177
|
-
let d = await t(e,
|
|
241
|
+
let d = await t(e, m, { input: {
|
|
178
242
|
sessionId: l,
|
|
179
243
|
fileSize: String(n.size)
|
|
180
244
|
} });
|
|
181
|
-
return
|
|
245
|
+
return o(100), {
|
|
182
246
|
fileId: d.completeUpload.fileId,
|
|
183
247
|
fileName: n.name
|
|
184
248
|
};
|
|
@@ -194,17 +258,17 @@ function C() {
|
|
|
194
258
|
error: c
|
|
195
259
|
};
|
|
196
260
|
}
|
|
197
|
-
function
|
|
198
|
-
let n =
|
|
261
|
+
function T(e) {
|
|
262
|
+
let n = y(), [r, a] = s(null), [o, c] = s(!1);
|
|
199
263
|
return i(() => {
|
|
200
264
|
if (!e) {
|
|
201
265
|
a(null);
|
|
202
266
|
return;
|
|
203
267
|
}
|
|
204
268
|
let r = !1;
|
|
205
|
-
return c(!0), t(n,
|
|
269
|
+
return c(!0), t(n, h, {
|
|
206
270
|
id: e,
|
|
207
|
-
expiresIn:
|
|
271
|
+
expiresIn: g
|
|
208
272
|
}).then((e) => {
|
|
209
273
|
r || a(e.getFileDownloadUrl ?? null);
|
|
210
274
|
}).catch(() => {
|
|
@@ -216,8 +280,8 @@ function w(e) {
|
|
|
216
280
|
};
|
|
217
281
|
}, [e, n]), {
|
|
218
282
|
url: r,
|
|
219
|
-
isLoading:
|
|
283
|
+
isLoading: o
|
|
220
284
|
};
|
|
221
285
|
}
|
|
222
286
|
//#endregion
|
|
223
|
-
export {
|
|
287
|
+
export { C as useCaptureEpod, b as useEpod, w as useEpodPhotoUpload, T as useEpodPhotoUrl, x as useMyPendingEpodDeliveryJobs };
|
|
@@ -43,6 +43,7 @@ export declare function useMyPendingEpodDeliveryJobs(options?: {
|
|
|
43
43
|
isLoading: boolean;
|
|
44
44
|
error: Error | null;
|
|
45
45
|
refetch: () => Promise<void>;
|
|
46
|
+
retireAcknowledgedJob: (deliveryJobId: string) => void;
|
|
46
47
|
};
|
|
47
48
|
export interface CaptureEpodArgs {
|
|
48
49
|
expectedShipmentId: string | null;
|
|
@@ -6,21 +6,21 @@ import { MapPin as i } from "../node_modules/lucide-react/dist/esm/icons/map-pin
|
|
|
6
6
|
import { Package as a } from "../node_modules/lucide-react/dist/esm/icons/package.js";
|
|
7
7
|
import { PenLine as o } from "../node_modules/lucide-react/dist/esm/icons/pen-line.js";
|
|
8
8
|
import { Truck as s } from "../node_modules/lucide-react/dist/esm/icons/truck.js";
|
|
9
|
-
import { Type as
|
|
10
|
-
import { Upload as
|
|
11
|
-
import { useMoveTheWheels as
|
|
12
|
-
import { mapOrderPriority as
|
|
13
|
-
import { useShipmentStatusQuery as
|
|
14
|
-
import { useCaptureEpod as
|
|
15
|
-
import { Button as
|
|
16
|
-
import { PageLayout as
|
|
17
|
-
import { PriorityBadge as
|
|
18
|
-
import { FormError as
|
|
19
|
-
import { Notice as
|
|
20
|
-
import { formatAddress as
|
|
21
|
-
import { SignaturePad as
|
|
22
|
-
import { StatusAuthorityOwnedDeliveryDocument as
|
|
23
|
-
import { useEffect as
|
|
9
|
+
import { Type as ee } from "../node_modules/lucide-react/dist/esm/icons/type.js";
|
|
10
|
+
import { Upload as c } from "../node_modules/lucide-react/dist/esm/icons/upload.js";
|
|
11
|
+
import { useMoveTheWheels as l } from "../providers/MoveTheWheelsProvider.js";
|
|
12
|
+
import { mapOrderPriority as u, mapOrderStatus as d } from "../hooks/useOrders.js";
|
|
13
|
+
import { useShipmentStatusQuery as f } from "../hooks/useShipmentStatusAuthority.js";
|
|
14
|
+
import { useCaptureEpod as p, useEpodPhotoUpload as m, useMyPendingEpodDeliveryJobs as h } from "../hooks/useEpod.js";
|
|
15
|
+
import { Button as g, Card as _, CardContent as v, CardHeader as y, CardTitle as b, Input as x, Label as S, Textarea as C } from "../components/ui.js";
|
|
16
|
+
import { PageLayout as w } from "../components/PageLayout.js";
|
|
17
|
+
import { PriorityBadge as T, StatusBadge as E } from "../components/StatusBadge.js";
|
|
18
|
+
import { FormError as D } from "../components/FormError.js";
|
|
19
|
+
import { Notice as O, useNotice as k } from "../components/Notice.js";
|
|
20
|
+
import { formatAddress as A, formatDate as j } from "../utils/formatters.js";
|
|
21
|
+
import { SignaturePad as M } from "../components/SignaturePad.js";
|
|
22
|
+
import { StatusAuthorityOwnedDeliveryDocument as N } from "../src/generated/shipment-status-authority-operations.js";
|
|
23
|
+
import { useEffect as P, useRef as F, useState as I } from "react";
|
|
24
24
|
import { Fragment as te, jsx as L, jsxs as R } from "react/jsx-runtime";
|
|
25
25
|
//#region src/movethewheels/pages/DeliveryCapturePage.tsx
|
|
26
26
|
function ne() {
|
|
@@ -37,13 +37,13 @@ function ne() {
|
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
function z({ job: e, onCaptured: t, onCancel: a }) {
|
|
40
|
-
let { captureEpod:
|
|
41
|
-
|
|
40
|
+
let { captureEpod: l, isSubmitting: u, error: d, captureFeedback: h } = p(), [w, T] = I(0), E = f(N, { deliveryJobId: e.id }, w), [O, k] = I(!1), j = F(null), z = F(!1), B = F(!0);
|
|
41
|
+
P(() => (B.current = !0, () => {
|
|
42
42
|
B.current = !1;
|
|
43
43
|
}), []);
|
|
44
|
-
let [re, V] = I(!1), [H, ie] = I(0), { upload: ae, isUploading: oe, error: U } =
|
|
44
|
+
let [re, V] = I(!1), [H, ie] = I(0), { upload: ae, isUploading: oe, error: U } = m(), W = F(null), [G, se] = I(""), [K, q] = I("drawn"), [J, ce] = I(""), [Y, le] = I(null), [X, ue] = I(""), [Z, Q] = I(null), de = async () => {
|
|
45
45
|
if (z.current) return;
|
|
46
|
-
if (Q(null),
|
|
46
|
+
if (Q(null), E.loading || E.error || !E.data || w < H || !O || h?.kind === "UNKNOWN") {
|
|
47
47
|
Q("Review the current linked shipment status and confirm completion before capture. Do not resend an uncertain capture.");
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
@@ -60,10 +60,10 @@ function z({ job: e, onCaptured: t, onCancel: a }) {
|
|
|
60
60
|
z.current = !0, V(!0);
|
|
61
61
|
try {
|
|
62
62
|
let i;
|
|
63
|
-
if (Y) if (
|
|
63
|
+
if (Y) if (j.current?.file === Y) i = [j.current.id];
|
|
64
64
|
else {
|
|
65
65
|
let e = await ae(Y);
|
|
66
|
-
|
|
66
|
+
j.current = {
|
|
67
67
|
file: Y,
|
|
68
68
|
id: e.fileId
|
|
69
69
|
}, i = [e.fileId];
|
|
@@ -71,10 +71,10 @@ function z({ job: e, onCaptured: t, onCancel: a }) {
|
|
|
71
71
|
if (!B.current) return;
|
|
72
72
|
let a = await ne();
|
|
73
73
|
if (!B.current) return;
|
|
74
|
-
await
|
|
75
|
-
expectedShipmentId:
|
|
76
|
-
expectedShipmentStatusRevision:
|
|
77
|
-
completionAttestation:
|
|
74
|
+
let o = await l({
|
|
75
|
+
expectedShipmentId: E.data.myEpodShipmentStatus?.id ?? null,
|
|
76
|
+
expectedShipmentStatusRevision: E.data.myEpodShipmentStatus?.statusRevision,
|
|
77
|
+
completionAttestation: O,
|
|
78
78
|
deliveryJobId: e.id,
|
|
79
79
|
recipientName: n,
|
|
80
80
|
signatureType: K,
|
|
@@ -84,26 +84,28 @@ function z({ job: e, onCaptured: t, onCancel: a }) {
|
|
|
84
84
|
longitude: a?.coords.longitude,
|
|
85
85
|
locationAccuracy: a?.coords.accuracy,
|
|
86
86
|
notes: X.trim() || void 0
|
|
87
|
-
})
|
|
87
|
+
});
|
|
88
|
+
if (!B.current) return;
|
|
89
|
+
t(o.deliveryJobId);
|
|
88
90
|
} catch {
|
|
89
|
-
|
|
91
|
+
k(!1), ie(w + 1);
|
|
90
92
|
} finally {
|
|
91
93
|
z.current = !1, V(!1);
|
|
92
94
|
}
|
|
93
|
-
}, $ =
|
|
94
|
-
return /* @__PURE__ */ R(
|
|
95
|
+
}, $ = u || oe || re;
|
|
96
|
+
return /* @__PURE__ */ R(_, { children: [/* @__PURE__ */ L(y, { children: /* @__PURE__ */ R(b, {
|
|
95
97
|
className: "flex items-center gap-2",
|
|
96
98
|
children: [
|
|
97
99
|
/* @__PURE__ */ L(s, { size: 18 }),
|
|
98
100
|
" Capture delivery for ",
|
|
99
101
|
e.deliveryJobNumber
|
|
100
102
|
]
|
|
101
|
-
}) }), /* @__PURE__ */ R(
|
|
103
|
+
}) }), /* @__PURE__ */ R(v, {
|
|
102
104
|
className: "space-y-4",
|
|
103
105
|
children: [
|
|
104
106
|
/* @__PURE__ */ R("div", {
|
|
105
107
|
className: "text-sm text-text-secondary space-y-1",
|
|
106
|
-
children: [e.customerName && /* @__PURE__ */ R("p", { children: ["Recipient on file: ", e.customerName] }), e.deliveryAddress && /* @__PURE__ */ L("p", { children:
|
|
108
|
+
children: [e.customerName && /* @__PURE__ */ R("p", { children: ["Recipient on file: ", e.customerName] }), e.deliveryAddress && /* @__PURE__ */ L("p", { children: A(e.deliveryAddress) })]
|
|
107
109
|
}),
|
|
108
110
|
/* @__PURE__ */ R("section", {
|
|
109
111
|
"aria-label": "Linked shipment status",
|
|
@@ -112,17 +114,17 @@ function z({ job: e, onCaptured: t, onCancel: a }) {
|
|
|
112
114
|
/* @__PURE__ */ R("p", { children: [
|
|
113
115
|
"Recorded shipment:",
|
|
114
116
|
" ",
|
|
115
|
-
|
|
117
|
+
E.data?.myEpodShipmentStatus ? `${E.data.myEpodShipmentStatus.status.toLowerCase().replaceAll("_", " ")} · revision ${E.data.myEpodShipmentStatus.statusRevision}` : E.data ? "No linked shipment" : "Loading current record"
|
|
116
118
|
] }),
|
|
117
|
-
|
|
119
|
+
E.error && /* @__PURE__ */ L("p", {
|
|
118
120
|
role: "alert",
|
|
119
|
-
children:
|
|
121
|
+
children: E.error
|
|
120
122
|
}),
|
|
121
|
-
/* @__PURE__ */ L(
|
|
123
|
+
/* @__PURE__ */ L(g, {
|
|
122
124
|
variant: "outline",
|
|
123
|
-
disabled:
|
|
125
|
+
disabled: E.loading || $,
|
|
124
126
|
onClick: () => {
|
|
125
|
-
|
|
127
|
+
k(!1), T((e) => e + 1);
|
|
126
128
|
},
|
|
127
129
|
children: "Refresh linked status"
|
|
128
130
|
}),
|
|
@@ -130,24 +132,24 @@ function z({ job: e, onCaptured: t, onCancel: a }) {
|
|
|
130
132
|
className: "flex items-start gap-2",
|
|
131
133
|
children: [/* @__PURE__ */ L("input", {
|
|
132
134
|
type: "checkbox",
|
|
133
|
-
checked:
|
|
134
|
-
disabled:
|
|
135
|
-
onChange: (e) =>
|
|
135
|
+
checked: O,
|
|
136
|
+
disabled: E.loading || !!E.error || $ || w < H || h?.kind === "UNKNOWN",
|
|
137
|
+
onChange: (e) => k(e.target.checked)
|
|
136
138
|
}), "I reviewed the recorded linked status and attest to this delivery completion."]
|
|
137
139
|
}),
|
|
138
140
|
/* @__PURE__ */ L("p", { children: "This records an assigned driver's attestation; it does not independently verify physical freight movement." }),
|
|
139
|
-
|
|
141
|
+
h && /* @__PURE__ */ L("p", {
|
|
140
142
|
role: "status",
|
|
141
|
-
children:
|
|
143
|
+
children: h.message
|
|
142
144
|
})
|
|
143
145
|
]
|
|
144
146
|
}),
|
|
145
147
|
/* @__PURE__ */ R("div", {
|
|
146
148
|
className: "space-y-1.5",
|
|
147
|
-
children: [/* @__PURE__ */ L(
|
|
149
|
+
children: [/* @__PURE__ */ L(S, {
|
|
148
150
|
htmlFor: "epod-recipient-name",
|
|
149
151
|
children: "Recipient name"
|
|
150
|
-
}), /* @__PURE__ */ L(
|
|
152
|
+
}), /* @__PURE__ */ L(x, {
|
|
151
153
|
id: "epod-recipient-name",
|
|
152
154
|
value: G,
|
|
153
155
|
onChange: (e) => se(e.target.value),
|
|
@@ -157,24 +159,24 @@ function z({ job: e, onCaptured: t, onCancel: a }) {
|
|
|
157
159
|
/* @__PURE__ */ R("div", {
|
|
158
160
|
className: "space-y-1.5",
|
|
159
161
|
children: [
|
|
160
|
-
/* @__PURE__ */ L(
|
|
162
|
+
/* @__PURE__ */ L(S, { children: "Signature" }),
|
|
161
163
|
/* @__PURE__ */ R("div", {
|
|
162
164
|
className: "flex gap-2",
|
|
163
|
-
children: [/* @__PURE__ */ R(
|
|
165
|
+
children: [/* @__PURE__ */ R(g, {
|
|
164
166
|
type: "button",
|
|
165
167
|
variant: K === "drawn" ? "default" : "outline",
|
|
166
168
|
size: "sm",
|
|
167
169
|
onClick: () => q("drawn"),
|
|
168
170
|
children: [/* @__PURE__ */ L(o, { size: 14 }), " Draw"]
|
|
169
|
-
}), /* @__PURE__ */ R(
|
|
171
|
+
}), /* @__PURE__ */ R(g, {
|
|
170
172
|
type: "button",
|
|
171
173
|
variant: K === "typed" ? "default" : "outline",
|
|
172
174
|
size: "sm",
|
|
173
175
|
onClick: () => q("typed"),
|
|
174
|
-
children: [/* @__PURE__ */ L(
|
|
176
|
+
children: [/* @__PURE__ */ L(ee, { size: 14 }), " Type name instead"]
|
|
175
177
|
})]
|
|
176
178
|
}),
|
|
177
|
-
K === "drawn" ? /* @__PURE__ */ L(
|
|
179
|
+
K === "drawn" ? /* @__PURE__ */ L(M, { ref: W }) : /* @__PURE__ */ L(x, {
|
|
178
180
|
value: J,
|
|
179
181
|
onChange: (e) => ce(e.target.value),
|
|
180
182
|
placeholder: "Type full name as signature"
|
|
@@ -184,7 +186,7 @@ function z({ job: e, onCaptured: t, onCancel: a }) {
|
|
|
184
186
|
/* @__PURE__ */ R("div", {
|
|
185
187
|
className: "space-y-1.5",
|
|
186
188
|
children: [
|
|
187
|
-
/* @__PURE__ */ L(
|
|
189
|
+
/* @__PURE__ */ L(S, {
|
|
188
190
|
htmlFor: "epod-photo",
|
|
189
191
|
children: "Delivery photo (optional)"
|
|
190
192
|
}),
|
|
@@ -197,7 +199,7 @@ function z({ job: e, onCaptured: t, onCancel: a }) {
|
|
|
197
199
|
capture: "environment",
|
|
198
200
|
onChange: (e) => le(e.target.files?.[0] ?? null),
|
|
199
201
|
className: "text-sm"
|
|
200
|
-
}), Y && /* @__PURE__ */ L(
|
|
202
|
+
}), Y && /* @__PURE__ */ L(c, {
|
|
201
203
|
size: 14,
|
|
202
204
|
className: "text-text-secondary"
|
|
203
205
|
})]
|
|
@@ -210,10 +212,10 @@ function z({ job: e, onCaptured: t, onCancel: a }) {
|
|
|
210
212
|
}),
|
|
211
213
|
/* @__PURE__ */ R("div", {
|
|
212
214
|
className: "space-y-1.5",
|
|
213
|
-
children: [/* @__PURE__ */ L(
|
|
215
|
+
children: [/* @__PURE__ */ L(S, {
|
|
214
216
|
htmlFor: "epod-notes",
|
|
215
217
|
children: "Notes (optional)"
|
|
216
|
-
}), /* @__PURE__ */ L(
|
|
218
|
+
}), /* @__PURE__ */ L(C, {
|
|
217
219
|
id: "epod-notes",
|
|
218
220
|
value: X,
|
|
219
221
|
onChange: (e) => ue(e.target.value),
|
|
@@ -225,21 +227,21 @@ function z({ job: e, onCaptured: t, onCancel: a }) {
|
|
|
225
227
|
className: "flex items-center gap-1.5 text-xs text-text-secondary",
|
|
226
228
|
children: [/* @__PURE__ */ L(i, { size: 12 }), " Device GPS is captured automatically when permitted by the browser."]
|
|
227
229
|
}),
|
|
228
|
-
Z && /* @__PURE__ */ L(
|
|
229
|
-
|
|
230
|
-
U && /* @__PURE__ */ L(
|
|
230
|
+
Z && /* @__PURE__ */ L(D, { message: Z }),
|
|
231
|
+
d && /* @__PURE__ */ L(D, { message: d.message }),
|
|
232
|
+
U && /* @__PURE__ */ L(D, { message: U.message }),
|
|
231
233
|
/* @__PURE__ */ R("div", {
|
|
232
234
|
className: "flex gap-2 justify-end",
|
|
233
|
-
children: [/* @__PURE__ */ L(
|
|
235
|
+
children: [/* @__PURE__ */ L(g, {
|
|
234
236
|
type: "button",
|
|
235
237
|
variant: "outline",
|
|
236
238
|
onClick: a,
|
|
237
239
|
disabled: $,
|
|
238
240
|
children: "Cancel"
|
|
239
|
-
}), /* @__PURE__ */ L(
|
|
241
|
+
}), /* @__PURE__ */ L(g, {
|
|
240
242
|
type: "button",
|
|
241
243
|
onClick: () => void de(),
|
|
242
|
-
disabled: $ ||
|
|
244
|
+
disabled: $ || E.loading || !!E.error || !E.data || w < H || !O || h?.kind === "UNKNOWN",
|
|
243
245
|
children: $ ? /* @__PURE__ */ R(te, { children: [/* @__PURE__ */ L(r, {
|
|
244
246
|
size: 14,
|
|
245
247
|
className: "animate-spin"
|
|
@@ -250,28 +252,28 @@ function z({ job: e, onCaptured: t, onCancel: a }) {
|
|
|
250
252
|
})] });
|
|
251
253
|
}
|
|
252
254
|
function B() {
|
|
253
|
-
let { workspaceId: r, tenantId: i, currentUser: o, apiGatewayUrl:
|
|
255
|
+
let { workspaceId: r, tenantId: i, currentUser: o, apiGatewayUrl: ee } = l(), [c, f] = I(0), { jobs: p, hasMore: m, isLoading: y, error: b, refetch: x, retireAcknowledgedJob: S } = h({
|
|
254
256
|
limit: 20,
|
|
255
|
-
offset:
|
|
256
|
-
}), [C,
|
|
257
|
-
return /* @__PURE__ */ L(
|
|
257
|
+
offset: c * 20
|
|
258
|
+
}), [C, M] = I(null), { message: N, notify: P } = k();
|
|
259
|
+
return /* @__PURE__ */ L(w, {
|
|
258
260
|
title: "My Deliveries",
|
|
259
261
|
subtitle: "Capture proof of delivery for your assigned stops",
|
|
260
262
|
icon: /* @__PURE__ */ L(s, { size: 24 }),
|
|
261
263
|
children: /* @__PURE__ */ R("div", {
|
|
262
264
|
className: "space-y-4",
|
|
263
|
-
children: [/* @__PURE__ */ L(
|
|
265
|
+
children: [/* @__PURE__ */ L(O, { message: N }), C ? /* @__PURE__ */ L(z, {
|
|
264
266
|
job: C,
|
|
265
|
-
onCaptured: () => {
|
|
266
|
-
P("Proof of delivery captured — delivery marked complete."),
|
|
267
|
+
onCaptured: (e) => {
|
|
268
|
+
S(e), P("Proof of delivery captured — delivery marked complete."), M(null), f(0), x();
|
|
267
269
|
},
|
|
268
|
-
onCancel: () =>
|
|
269
|
-
}, `${r}:${i}:${o?.id}:${
|
|
270
|
+
onCancel: () => M(null)
|
|
271
|
+
}, `${r}:${i}:${o?.id}:${ee}:${C.id}`) : y ? /* @__PURE__ */ L("p", {
|
|
270
272
|
className: "text-text-secondary text-center py-8",
|
|
271
273
|
children: "Loading your deliveries…"
|
|
272
|
-
}) :
|
|
274
|
+
}) : b ? /* @__PURE__ */ L(D, { message: b.message }) : p.length > 0 ? /* @__PURE__ */ R("div", {
|
|
273
275
|
className: "space-y-3",
|
|
274
|
-
children: [
|
|
276
|
+
children: [p.map((e) => /* @__PURE__ */ L(_, { children: /* @__PURE__ */ R(v, {
|
|
275
277
|
className: "flex items-center justify-between gap-3 py-4",
|
|
276
278
|
children: [/* @__PURE__ */ R("div", {
|
|
277
279
|
className: "min-w-0",
|
|
@@ -286,45 +288,45 @@ function B() {
|
|
|
286
288
|
}),
|
|
287
289
|
/* @__PURE__ */ R("div", {
|
|
288
290
|
className: "flex flex-wrap items-center gap-1.5 py-1",
|
|
289
|
-
children: [/* @__PURE__ */ L(
|
|
290
|
-
status:
|
|
291
|
+
children: [/* @__PURE__ */ L(E, {
|
|
292
|
+
status: d(e.status),
|
|
291
293
|
type: "order",
|
|
292
294
|
size: "sm"
|
|
293
|
-
}), /* @__PURE__ */ L(
|
|
294
|
-
priority:
|
|
295
|
+
}), /* @__PURE__ */ L(T, {
|
|
296
|
+
priority: u(e.priority),
|
|
295
297
|
size: "sm"
|
|
296
298
|
})]
|
|
297
299
|
}),
|
|
298
300
|
e.deliveryAddress && /* @__PURE__ */ L("p", {
|
|
299
301
|
className: "text-sm text-text-secondary truncate",
|
|
300
|
-
children:
|
|
302
|
+
children: A(e.deliveryAddress)
|
|
301
303
|
}),
|
|
302
304
|
e.scheduledDelivery && /* @__PURE__ */ R("p", {
|
|
303
305
|
className: "text-xs text-text-secondary",
|
|
304
|
-
children: ["Scheduled ",
|
|
306
|
+
children: ["Scheduled ", j(e.scheduledDelivery, "short")]
|
|
305
307
|
})
|
|
306
308
|
]
|
|
307
|
-
}), /* @__PURE__ */ L(
|
|
309
|
+
}), /* @__PURE__ */ L(g, {
|
|
308
310
|
type: "button",
|
|
309
311
|
size: "sm",
|
|
310
|
-
onClick: () =>
|
|
312
|
+
onClick: () => M(e),
|
|
311
313
|
children: "Capture"
|
|
312
314
|
})]
|
|
313
|
-
}) }, e.id)), (
|
|
315
|
+
}) }, e.id)), (c > 0 || m) && /* @__PURE__ */ R("div", {
|
|
314
316
|
className: "flex justify-end gap-2 pt-2",
|
|
315
|
-
children: [/* @__PURE__ */ R(
|
|
317
|
+
children: [/* @__PURE__ */ R(g, {
|
|
316
318
|
type: "button",
|
|
317
319
|
variant: "outline",
|
|
318
320
|
size: "sm",
|
|
319
|
-
disabled:
|
|
320
|
-
onClick: () =>
|
|
321
|
+
disabled: c === 0 || y,
|
|
322
|
+
onClick: () => f((e) => Math.max(0, e - 1)),
|
|
321
323
|
children: [/* @__PURE__ */ L(e, { size: 16 }), " Previous"]
|
|
322
|
-
}), /* @__PURE__ */ R(
|
|
324
|
+
}), /* @__PURE__ */ R(g, {
|
|
323
325
|
type: "button",
|
|
324
326
|
variant: "outline",
|
|
325
327
|
size: "sm",
|
|
326
|
-
disabled: !
|
|
327
|
-
onClick: () =>
|
|
328
|
+
disabled: !m || y,
|
|
329
|
+
onClick: () => f((e) => e + 1),
|
|
328
330
|
children: ["Next ", /* @__PURE__ */ L(t, { size: 16 })]
|
|
329
331
|
})]
|
|
330
332
|
})]
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* Deferred (see BOFF-4399 follow-up notes): barcode/RFID capture and
|
|
12
12
|
* multi-language delivery instructions.
|
|
13
13
|
*/
|
|
14
|
-
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
14
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
15
15
|
import { parse } from 'graphql';
|
|
16
16
|
import { useShipmentStatusCommand } from './useShipmentStatusAuthority';
|
|
17
17
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
@@ -212,51 +212,127 @@ interface ApiPendingJob {
|
|
|
212
212
|
export function useMyPendingEpodDeliveryJobs(options: { limit?: number; offset?: number } = {}) {
|
|
213
213
|
const { limit = 20, offset = 0 } = options;
|
|
214
214
|
const graphQLOptions = useGraphQLOptions();
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
215
|
+
const scopeKey = JSON.stringify([
|
|
216
|
+
graphQLOptions.apiGatewayUrl,
|
|
217
|
+
graphQLOptions.workspaceId,
|
|
218
|
+
graphQLOptions.tenantId,
|
|
219
|
+
graphQLOptions.actorId,
|
|
220
|
+
]);
|
|
221
|
+
const readKey = JSON.stringify([scopeKey, limit, offset]);
|
|
222
|
+
// A validated capture ACK makes this stop ineligible for capture immediately.
|
|
223
|
+
// Keep that fact for this actor/scope while a pending-list refresh is delayed.
|
|
224
|
+
// This is an in-memory UI exclusion, never a fabricated server job update.
|
|
225
|
+
const acknowledgedJobs = useRef({ scopeKey, ids: new Set<string>() });
|
|
226
|
+
const requestId = useRef(0);
|
|
227
|
+
const [snapshot, setSnapshot] = useState<{
|
|
228
|
+
scopeKey: string;
|
|
229
|
+
readKey: string;
|
|
230
|
+
jobs: PendingEpodDeliveryJob[];
|
|
231
|
+
totalJobs: number;
|
|
232
|
+
hasMore: boolean;
|
|
233
|
+
isLoading: boolean;
|
|
234
|
+
error: Error | null;
|
|
235
|
+
}>({ scopeKey, readKey, jobs: [], totalJobs: 0, hasMore: false, isLoading: true, error: null });
|
|
220
236
|
|
|
221
237
|
const load = useCallback(async () => {
|
|
222
|
-
|
|
223
|
-
|
|
238
|
+
const request = ++requestId.current;
|
|
239
|
+
if (acknowledgedJobs.current.scopeKey !== scopeKey) {
|
|
240
|
+
acknowledgedJobs.current = { scopeKey, ids: new Set<string>() };
|
|
241
|
+
}
|
|
242
|
+
setSnapshot((previous) => ({
|
|
243
|
+
scopeKey,
|
|
244
|
+
readKey,
|
|
245
|
+
jobs: previous.readKey === readKey ? previous.jobs : [],
|
|
246
|
+
totalJobs: previous.readKey === readKey ? previous.totalJobs : 0,
|
|
247
|
+
hasMore: previous.readKey === readKey ? previous.hasMore : false,
|
|
248
|
+
isLoading: true,
|
|
249
|
+
error: null,
|
|
250
|
+
}));
|
|
224
251
|
try {
|
|
225
252
|
const data = await executeGraphQL<{
|
|
226
253
|
myPendingEpodDeliveryJobs: ApiPendingJob[];
|
|
227
254
|
myPendingEpodDeliveryJobsCount: number;
|
|
228
255
|
}>(graphQLOptions, MY_PENDING_EPOD_JOBS_QUERY, { pagination: { limit, offset } });
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
256
|
+
if (request !== requestId.current) return;
|
|
257
|
+
const rows = data.myPendingEpodDeliveryJobs;
|
|
258
|
+
const total = data.myPendingEpodDeliveryJobsCount ?? 0;
|
|
259
|
+
const excluded = rows.filter((job) => acknowledgedJobs.current.ids.has(job.id)).length;
|
|
260
|
+
setSnapshot({
|
|
261
|
+
scopeKey,
|
|
262
|
+
readKey,
|
|
263
|
+
totalJobs: Math.max(0, total - excluded),
|
|
264
|
+
// Pagination still follows the server page, including any excluded row.
|
|
265
|
+
hasMore: offset + rows.length < total,
|
|
266
|
+
jobs: rows
|
|
267
|
+
.filter((job) => !acknowledgedJobs.current.ids.has(job.id))
|
|
268
|
+
.map((job) => ({
|
|
269
|
+
id: job.id,
|
|
270
|
+
deliveryJobNumber: job.deliveryJobNumber,
|
|
271
|
+
status: job.status,
|
|
272
|
+
priority: job.priority,
|
|
273
|
+
deliveryAddress: job.deliveryAddress,
|
|
274
|
+
scheduledDelivery: job.scheduledDelivery ? new Date(job.scheduledDelivery) : undefined,
|
|
275
|
+
customerName: job.customer?.name ?? undefined,
|
|
276
|
+
customerPhone: job.customer?.phone ?? undefined,
|
|
277
|
+
})),
|
|
278
|
+
isLoading: false,
|
|
279
|
+
error: null,
|
|
280
|
+
});
|
|
245
281
|
} catch (fetchError) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
282
|
+
if (request !== requestId.current) return;
|
|
283
|
+
setSnapshot({
|
|
284
|
+
scopeKey,
|
|
285
|
+
readKey,
|
|
286
|
+
jobs: [],
|
|
287
|
+
totalJobs: 0,
|
|
288
|
+
hasMore: false,
|
|
289
|
+
isLoading: false,
|
|
290
|
+
error:
|
|
291
|
+
fetchError instanceof Error ? fetchError : new Error('Failed to load pending deliveries'),
|
|
292
|
+
});
|
|
252
293
|
}
|
|
253
|
-
}, [graphQLOptions, limit, offset]);
|
|
294
|
+
}, [graphQLOptions, limit, offset, scopeKey, readKey]);
|
|
295
|
+
|
|
296
|
+
const cancelPendingRead = useCallback(() => {
|
|
297
|
+
requestId.current++;
|
|
298
|
+
}, []);
|
|
254
299
|
|
|
255
300
|
useEffect(() => {
|
|
256
301
|
void load();
|
|
257
|
-
|
|
302
|
+
return cancelPendingRead;
|
|
303
|
+
}, [load, cancelPendingRead]);
|
|
304
|
+
|
|
305
|
+
const retireAcknowledgedJob = useCallback(
|
|
306
|
+
(deliveryJobId: string) => {
|
|
307
|
+
if (
|
|
308
|
+
acknowledgedJobs.current.scopeKey !== scopeKey ||
|
|
309
|
+
acknowledgedJobs.current.ids.has(deliveryJobId)
|
|
310
|
+
)
|
|
311
|
+
return;
|
|
312
|
+
acknowledgedJobs.current.ids.add(deliveryJobId);
|
|
313
|
+
setSnapshot((previous) => {
|
|
314
|
+
if (previous.scopeKey !== scopeKey) return previous;
|
|
315
|
+
const removed = previous.jobs.some((job) => job.id === deliveryJobId);
|
|
316
|
+
return {
|
|
317
|
+
...previous,
|
|
318
|
+
jobs: previous.jobs.filter((job) => job.id !== deliveryJobId),
|
|
319
|
+
totalJobs: Math.max(0, previous.totalJobs - (removed ? 1 : 0)),
|
|
320
|
+
};
|
|
321
|
+
});
|
|
322
|
+
},
|
|
323
|
+
[scopeKey]
|
|
324
|
+
);
|
|
258
325
|
|
|
259
|
-
|
|
326
|
+
const current = snapshot.readKey === readKey;
|
|
327
|
+
return {
|
|
328
|
+
jobs: current ? snapshot.jobs : [],
|
|
329
|
+
totalJobs: current ? snapshot.totalJobs : 0,
|
|
330
|
+
hasMore: current && snapshot.hasMore,
|
|
331
|
+
isLoading: !current || snapshot.isLoading,
|
|
332
|
+
error: current ? snapshot.error : null,
|
|
333
|
+
refetch: load,
|
|
334
|
+
retireAcknowledgedJob,
|
|
335
|
+
};
|
|
260
336
|
}
|
|
261
337
|
|
|
262
338
|
export interface CaptureEpodArgs {
|
|
@@ -291,9 +367,11 @@ export function epodAcknowledged(
|
|
|
291
367
|
actual.capturedByDriverId.trim().length > 0 &&
|
|
292
368
|
typeof actual.deliveredAt === 'string' &&
|
|
293
369
|
Number.isFinite(Date.parse(actual.deliveredAt)) &&
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
370
|
+
(actual.photoFileIds === null
|
|
371
|
+
? (expected.photoFileIds ?? []).length === 0
|
|
372
|
+
: Array.isArray(actual.photoFileIds) &&
|
|
373
|
+
actual.photoFileIds.length === (expected.photoFileIds ?? []).length &&
|
|
374
|
+
actual.photoFileIds.every((id, index) => id === (expected.photoFileIds ?? [])[index])) &&
|
|
297
375
|
actual.latitude === (expected.latitude ?? null) &&
|
|
298
376
|
actual.longitude === (expected.longitude ?? null) &&
|
|
299
377
|
actual.locationAccuracy === (expected.locationAccuracy ?? null) &&
|
|
@@ -78,7 +78,7 @@ function CaptureForm({
|
|
|
78
78
|
onCancel,
|
|
79
79
|
}: {
|
|
80
80
|
job: PendingEpodDeliveryJob;
|
|
81
|
-
onCaptured: () => void;
|
|
81
|
+
onCaptured: (deliveryJobId: string) => void;
|
|
82
82
|
onCancel: () => void;
|
|
83
83
|
}) {
|
|
84
84
|
const { captureEpod, isSubmitting, error: captureError, captureFeedback } = useCaptureEpod();
|
|
@@ -163,7 +163,7 @@ function CaptureForm({
|
|
|
163
163
|
const position = await getCurrentPosition();
|
|
164
164
|
if (!active.current) return;
|
|
165
165
|
|
|
166
|
-
await captureEpod({
|
|
166
|
+
const proof = await captureEpod({
|
|
167
167
|
expectedShipmentId: ownedStatus.data.myEpodShipmentStatus?.id ?? null,
|
|
168
168
|
expectedShipmentStatusRevision: ownedStatus.data.myEpodShipmentStatus?.statusRevision,
|
|
169
169
|
completionAttestation,
|
|
@@ -178,7 +178,8 @@ function CaptureForm({
|
|
|
178
178
|
notes: notes.trim() || undefined,
|
|
179
179
|
});
|
|
180
180
|
|
|
181
|
-
|
|
181
|
+
if (!active.current) return;
|
|
182
|
+
onCaptured(proof.deliveryJobId);
|
|
182
183
|
} catch {
|
|
183
184
|
setCompletionAttestation(false);
|
|
184
185
|
setRequiredStatusRefresh(statusRefresh + 1);
|
|
@@ -358,14 +359,16 @@ export default function DeliveryCapturePage() {
|
|
|
358
359
|
const { workspaceId, tenantId, currentUser, apiGatewayUrl } = useMoveTheWheels();
|
|
359
360
|
const [pageIndex, setPageIndex] = useState(0);
|
|
360
361
|
const pageSize = 20;
|
|
361
|
-
const { jobs, hasMore, isLoading, error, refetch } =
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
362
|
+
const { jobs, hasMore, isLoading, error, refetch, retireAcknowledgedJob } =
|
|
363
|
+
useMyPendingEpodDeliveryJobs({
|
|
364
|
+
limit: pageSize,
|
|
365
|
+
offset: pageIndex * pageSize,
|
|
366
|
+
});
|
|
365
367
|
const [selectedJob, setSelectedJob] = useState<PendingEpodDeliveryJob | null>(null);
|
|
366
368
|
const { message, notify } = useNotice();
|
|
367
369
|
|
|
368
|
-
const handleCaptured = () => {
|
|
370
|
+
const handleCaptured = (deliveryJobId: string) => {
|
|
371
|
+
retireAcknowledgedJob(deliveryJobId);
|
|
369
372
|
notify('Proof of delivery captured — delivery marked complete.');
|
|
370
373
|
setSelectedJob(null);
|
|
371
374
|
setPageIndex(0);
|