@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.
Files changed (38) hide show
  1. package/dist/MoveTheWheelsRoot.js +12 -1
  2. package/dist/MoveTheWheelsRoutes.js +57 -49
  3. package/dist/generated/shipment-status-authority-operations.d.ts +4473 -0
  4. package/dist/generated/tracking-quality-operations.d.ts +4302 -0
  5. package/dist/hooks/useEpod.js +85 -77
  6. package/dist/hooks/useShipmentStatusAuthority.js +127 -0
  7. package/dist/hooks/useTracking.js +15 -14
  8. package/dist/hooks/useTrackingEventQuality.js +60 -0
  9. package/dist/movethewheels/hooks/useEpod.d.ts +23 -0
  10. package/dist/movethewheels/hooks/useShipmentStatusAuthority.d.ts +23 -0
  11. package/dist/movethewheels/hooks/useTrackingEventQuality.d.ts +18 -0
  12. package/dist/movethewheels/pages/ShipmentStatusAuthorityPage.d.ts +1 -0
  13. package/dist/movethewheels/pages/TrackingEventQualityPage.d.ts +1 -0
  14. package/dist/movethewheels/utils/shipmentStatusDraft.d.ts +45 -0
  15. package/dist/node_modules/lucide-react/dist/esm/icons/radio-tower.js +36 -0
  16. package/dist/pages/DeliveryCapturePage.js +181 -116
  17. package/dist/pages/ShipmentStatusAuthorityPage.js +563 -0
  18. package/dist/pages/TrackingEventQualityPage.js +224 -0
  19. package/dist/src/generated/shipment-status-authority-operations.js +86 -0
  20. package/dist/src/generated/tracking-quality-operations.js +44 -0
  21. package/dist/utils/shipmentStatusDraft.js +41 -0
  22. package/package.json +16 -7
  23. package/patches/README.md +25 -0
  24. package/patches/braces@3.0.3.patch +87 -0
  25. package/src/generated/shipment-status-authority-operations.ts +5503 -0
  26. package/src/generated/tracking-quality-operations.ts +5323 -0
  27. package/src/movethewheels/MoveTheWheelsRoot.tsx +14 -0
  28. package/src/movethewheels/MoveTheWheelsRoutes.tsx +4 -0
  29. package/src/movethewheels/hooks/useEpod.ts +47 -12
  30. package/src/movethewheels/hooks/useShipmentStatusAuthority.ts +179 -0
  31. package/src/movethewheels/hooks/useTracking.ts +27 -12
  32. package/src/movethewheels/hooks/useTrackingEventQuality.ts +110 -0
  33. package/src/movethewheels/pages/DeliveryCapturePage.tsx +113 -6
  34. package/src/movethewheels/pages/ShipmentStatusAuthorityPage.tsx +639 -0
  35. package/src/movethewheels/pages/TrackingEventQualityPage.tsx +250 -0
  36. package/src/movethewheels/utils/shipmentStatusDraft.ts +118 -0
  37. package/src/operations/shipment-status-authority/status.graphql +80 -0
  38. 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
- const uploaded = await uploadPhoto(photoFile);
117
- photoFileIds = [uploaded.fileId];
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 type="button" onClick={() => void handleSubmit()} disabled={submitting}>
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)}