@burdenoff/microfe-movethewheels 2026.1010.2 → 2026.1011.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/MoveTheWheelsRoot.js +12 -1
- package/dist/MoveTheWheelsRoutes.js +57 -49
- package/dist/generated/shipment-status-authority-operations.d.ts +4473 -0
- package/dist/generated/tracking-quality-operations.d.ts +4302 -0
- package/dist/hooks/useEpod.js +85 -77
- package/dist/hooks/useShipmentStatusAuthority.js +127 -0
- package/dist/hooks/useTracking.js +15 -14
- package/dist/hooks/useTrackingEventQuality.js +60 -0
- package/dist/movethewheels/hooks/useEpod.d.ts +23 -0
- package/dist/movethewheels/hooks/useShipmentStatusAuthority.d.ts +23 -0
- package/dist/movethewheels/hooks/useTrackingEventQuality.d.ts +18 -0
- package/dist/movethewheels/pages/ShipmentStatusAuthorityPage.d.ts +1 -0
- package/dist/movethewheels/pages/TrackingEventQualityPage.d.ts +1 -0
- package/dist/movethewheels/utils/shipmentStatusDraft.d.ts +45 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/radio-tower.js +36 -0
- package/dist/pages/DeliveryCapturePage.js +181 -116
- package/dist/pages/ShipmentStatusAuthorityPage.js +563 -0
- package/dist/pages/TrackingEventQualityPage.js +224 -0
- package/dist/src/generated/shipment-status-authority-operations.js +86 -0
- package/dist/src/generated/tracking-quality-operations.js +44 -0
- package/dist/utils/shipmentStatusDraft.js +41 -0
- package/package.json +16 -7
- package/patches/README.md +25 -0
- package/patches/braces@3.0.3.patch +87 -0
- package/src/generated/shipment-status-authority-operations.ts +5503 -0
- package/src/generated/tracking-quality-operations.ts +5323 -0
- package/src/movethewheels/MoveTheWheelsRoot.tsx +14 -0
- package/src/movethewheels/MoveTheWheelsRoutes.tsx +4 -0
- package/src/movethewheels/hooks/useEpod.ts +47 -12
- package/src/movethewheels/hooks/useShipmentStatusAuthority.ts +179 -0
- package/src/movethewheels/hooks/useTracking.ts +27 -12
- package/src/movethewheels/hooks/useTrackingEventQuality.ts +110 -0
- package/src/movethewheels/pages/DeliveryCapturePage.tsx +113 -6
- package/src/movethewheels/pages/ShipmentStatusAuthorityPage.tsx +639 -0
- package/src/movethewheels/pages/TrackingEventQualityPage.tsx +250 -0
- package/src/movethewheels/utils/shipmentStatusDraft.ts +118 -0
- package/src/operations/shipment-status-authority/status.graphql +80 -0
- package/src/operations/tracking-quality/quality.graphql +15 -0
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* driver app (BOFF-3991) — this page is the capture core, reachable from the
|
|
16
16
|
* existing web MFE.
|
|
17
17
|
*/
|
|
18
|
-
import { useRef, useState } from 'react';
|
|
18
|
+
import { useEffect, useRef, useState } from 'react';
|
|
19
19
|
import {
|
|
20
20
|
CheckCircle2,
|
|
21
21
|
Loader2,
|
|
@@ -52,6 +52,10 @@ import {
|
|
|
52
52
|
} from '../hooks/useEpod';
|
|
53
53
|
import { formatAddress, formatDate } from '../utils/formatters';
|
|
54
54
|
import type { SignatureType } from '../types';
|
|
55
|
+
import { useShipmentStatusQuery } from '../hooks/useShipmentStatusAuthority';
|
|
56
|
+
import { StatusAuthorityOwnedDeliveryDocument } from '../../generated/shipment-status-authority-operations';
|
|
57
|
+
import type { StatusAuthorityOwnedDeliveryQuery } from '../../generated/shipment-status-authority-operations';
|
|
58
|
+
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
55
59
|
|
|
56
60
|
/** Resolves the device's current position, if the browser grants it. Never blocks submission. */
|
|
57
61
|
function getCurrentPosition(): Promise<GeolocationPosition | null> {
|
|
@@ -77,7 +81,25 @@ function CaptureForm({
|
|
|
77
81
|
onCaptured: () => void;
|
|
78
82
|
onCancel: () => void;
|
|
79
83
|
}) {
|
|
80
|
-
const { captureEpod, isSubmitting, error: captureError } = useCaptureEpod();
|
|
84
|
+
const { captureEpod, isSubmitting, error: captureError, captureFeedback } = useCaptureEpod();
|
|
85
|
+
const [statusRefresh, setStatusRefresh] = useState(0);
|
|
86
|
+
const ownedStatus = useShipmentStatusQuery<StatusAuthorityOwnedDeliveryQuery>(
|
|
87
|
+
StatusAuthorityOwnedDeliveryDocument,
|
|
88
|
+
{ deliveryJobId: job.id },
|
|
89
|
+
statusRefresh
|
|
90
|
+
);
|
|
91
|
+
const [completionAttestation, setCompletionAttestation] = useState(false);
|
|
92
|
+
const uploadedPhoto = useRef<{ file: File; id: string } | null>(null);
|
|
93
|
+
const submissionLock = useRef(false);
|
|
94
|
+
const active = useRef(true);
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
active.current = true;
|
|
97
|
+
return () => {
|
|
98
|
+
active.current = false;
|
|
99
|
+
};
|
|
100
|
+
}, []);
|
|
101
|
+
const [preparing, setPreparing] = useState(false);
|
|
102
|
+
const [requiredStatusRefresh, setRequiredStatusRefresh] = useState(0);
|
|
81
103
|
const { upload: uploadPhoto, isUploading, error: uploadError } = useEpodPhotoUpload();
|
|
82
104
|
const signaturePadRef = useRef<SignaturePadHandle>(null);
|
|
83
105
|
|
|
@@ -89,7 +111,21 @@ function CaptureForm({
|
|
|
89
111
|
const [validationError, setValidationError] = useState<string | null>(null);
|
|
90
112
|
|
|
91
113
|
const handleSubmit = async () => {
|
|
114
|
+
if (submissionLock.current) return;
|
|
92
115
|
setValidationError(null);
|
|
116
|
+
if (
|
|
117
|
+
ownedStatus.loading ||
|
|
118
|
+
ownedStatus.error ||
|
|
119
|
+
!ownedStatus.data ||
|
|
120
|
+
statusRefresh < requiredStatusRefresh ||
|
|
121
|
+
!completionAttestation ||
|
|
122
|
+
captureFeedback?.kind === 'UNKNOWN'
|
|
123
|
+
) {
|
|
124
|
+
setValidationError(
|
|
125
|
+
'Review the current linked shipment status and confirm completion before capture. Do not resend an uncertain capture.'
|
|
126
|
+
);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
93
129
|
|
|
94
130
|
const trimmedRecipient = recipientName.trim();
|
|
95
131
|
if (!trimmedRecipient) {
|
|
@@ -110,16 +146,27 @@ function CaptureForm({
|
|
|
110
146
|
return;
|
|
111
147
|
}
|
|
112
148
|
|
|
149
|
+
submissionLock.current = true;
|
|
150
|
+
setPreparing(true);
|
|
113
151
|
try {
|
|
114
152
|
let photoFileIds: string[] | undefined;
|
|
115
153
|
if (photoFile) {
|
|
116
|
-
|
|
117
|
-
|
|
154
|
+
if (uploadedPhoto.current?.file === photoFile) photoFileIds = [uploadedPhoto.current.id];
|
|
155
|
+
else {
|
|
156
|
+
const uploaded = await uploadPhoto(photoFile);
|
|
157
|
+
uploadedPhoto.current = { file: photoFile, id: uploaded.fileId };
|
|
158
|
+
photoFileIds = [uploaded.fileId];
|
|
159
|
+
}
|
|
118
160
|
}
|
|
119
161
|
|
|
162
|
+
if (!active.current) return;
|
|
120
163
|
const position = await getCurrentPosition();
|
|
164
|
+
if (!active.current) return;
|
|
121
165
|
|
|
122
166
|
await captureEpod({
|
|
167
|
+
expectedShipmentId: ownedStatus.data.myEpodShipmentStatus?.id ?? null,
|
|
168
|
+
expectedShipmentStatusRevision: ownedStatus.data.myEpodShipmentStatus?.statusRevision,
|
|
169
|
+
completionAttestation,
|
|
123
170
|
deliveryJobId: job.id,
|
|
124
171
|
recipientName: trimmedRecipient,
|
|
125
172
|
signatureType,
|
|
@@ -133,11 +180,16 @@ function CaptureForm({
|
|
|
133
180
|
|
|
134
181
|
onCaptured();
|
|
135
182
|
} catch {
|
|
183
|
+
setCompletionAttestation(false);
|
|
184
|
+
setRequiredStatusRefresh(statusRefresh + 1);
|
|
136
185
|
// captureError / uploadError already carry the message for display below.
|
|
186
|
+
} finally {
|
|
187
|
+
submissionLock.current = false;
|
|
188
|
+
setPreparing(false);
|
|
137
189
|
}
|
|
138
190
|
};
|
|
139
191
|
|
|
140
|
-
const submitting = isSubmitting || isUploading;
|
|
192
|
+
const submitting = isSubmitting || isUploading || preparing;
|
|
141
193
|
|
|
142
194
|
return (
|
|
143
195
|
<Card>
|
|
@@ -151,6 +203,47 @@ function CaptureForm({
|
|
|
151
203
|
{job.customerName && <p>Recipient on file: {job.customerName}</p>}
|
|
152
204
|
{job.deliveryAddress && <p>{formatAddress(job.deliveryAddress)}</p>}
|
|
153
205
|
</div>
|
|
206
|
+
<section aria-label="Linked shipment status" className="space-y-2">
|
|
207
|
+
<p>
|
|
208
|
+
Recorded shipment:{' '}
|
|
209
|
+
{ownedStatus.data?.myEpodShipmentStatus
|
|
210
|
+
? `${ownedStatus.data.myEpodShipmentStatus.status.toLowerCase().replaceAll('_', ' ')} · revision ${ownedStatus.data.myEpodShipmentStatus.statusRevision}`
|
|
211
|
+
: ownedStatus.data
|
|
212
|
+
? 'No linked shipment'
|
|
213
|
+
: 'Loading current record'}
|
|
214
|
+
</p>
|
|
215
|
+
{ownedStatus.error && <p role="alert">{ownedStatus.error}</p>}
|
|
216
|
+
<Button
|
|
217
|
+
variant="outline"
|
|
218
|
+
disabled={ownedStatus.loading || submitting}
|
|
219
|
+
onClick={() => {
|
|
220
|
+
setCompletionAttestation(false);
|
|
221
|
+
setStatusRefresh((value) => value + 1);
|
|
222
|
+
}}
|
|
223
|
+
>
|
|
224
|
+
Refresh linked status
|
|
225
|
+
</Button>
|
|
226
|
+
<label className="flex items-start gap-2">
|
|
227
|
+
<input
|
|
228
|
+
type="checkbox"
|
|
229
|
+
checked={completionAttestation}
|
|
230
|
+
disabled={
|
|
231
|
+
ownedStatus.loading ||
|
|
232
|
+
!!ownedStatus.error ||
|
|
233
|
+
submitting ||
|
|
234
|
+
statusRefresh < requiredStatusRefresh ||
|
|
235
|
+
captureFeedback?.kind === 'UNKNOWN'
|
|
236
|
+
}
|
|
237
|
+
onChange={(event) => setCompletionAttestation(event.target.checked)}
|
|
238
|
+
/>
|
|
239
|
+
I reviewed the recorded linked status and attest to this delivery completion.
|
|
240
|
+
</label>
|
|
241
|
+
<p>
|
|
242
|
+
This records an assigned driver's attestation; it does not independently verify physical
|
|
243
|
+
freight movement.
|
|
244
|
+
</p>
|
|
245
|
+
{captureFeedback && <p role="status">{captureFeedback.message}</p>}
|
|
246
|
+
</section>
|
|
154
247
|
|
|
155
248
|
<div className="space-y-1.5">
|
|
156
249
|
<Label htmlFor="epod-recipient-name">Recipient name</Label>
|
|
@@ -232,7 +325,19 @@ function CaptureForm({
|
|
|
232
325
|
<Button type="button" variant="outline" onClick={onCancel} disabled={submitting}>
|
|
233
326
|
Cancel
|
|
234
327
|
</Button>
|
|
235
|
-
<Button
|
|
328
|
+
<Button
|
|
329
|
+
type="button"
|
|
330
|
+
onClick={() => void handleSubmit()}
|
|
331
|
+
disabled={
|
|
332
|
+
submitting ||
|
|
333
|
+
ownedStatus.loading ||
|
|
334
|
+
!!ownedStatus.error ||
|
|
335
|
+
!ownedStatus.data ||
|
|
336
|
+
statusRefresh < requiredStatusRefresh ||
|
|
337
|
+
!completionAttestation ||
|
|
338
|
+
captureFeedback?.kind === 'UNKNOWN'
|
|
339
|
+
}
|
|
340
|
+
>
|
|
236
341
|
{submitting ? (
|
|
237
342
|
<>
|
|
238
343
|
<Loader2 size={14} className="animate-spin" /> Submitting…
|
|
@@ -250,6 +355,7 @@ function CaptureForm({
|
|
|
250
355
|
}
|
|
251
356
|
|
|
252
357
|
export default function DeliveryCapturePage() {
|
|
358
|
+
const { workspaceId, tenantId, currentUser, apiGatewayUrl } = useMoveTheWheels();
|
|
253
359
|
const [pageIndex, setPageIndex] = useState(0);
|
|
254
360
|
const pageSize = 20;
|
|
255
361
|
const { jobs, hasMore, isLoading, error, refetch } = useMyPendingEpodDeliveryJobs({
|
|
@@ -277,6 +383,7 @@ export default function DeliveryCapturePage() {
|
|
|
277
383
|
|
|
278
384
|
{selectedJob ? (
|
|
279
385
|
<CaptureForm
|
|
386
|
+
key={`${workspaceId}:${tenantId}:${currentUser?.id}:${apiGatewayUrl}:${selectedJob.id}`}
|
|
280
387
|
job={selectedJob}
|
|
281
388
|
onCaptured={handleCaptured}
|
|
282
389
|
onCancel={() => setSelectedJob(null)}
|