@burdenoff/microfe-movethewheels 2026.1101.2 → 2026.1101.3
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/movethewheels/utils/driverNavigation.d.ts +6 -0
- package/dist/pages/DeliveryCapturePage.js +71 -71
- package/dist/utils/driverNavigation.js +11 -1
- package/package.json +1 -1
- package/src/movethewheels/pages/DeliveryCapturePage.tsx +2 -3
- package/src/movethewheels/utils/driverNavigation.ts +16 -0
|
@@ -29,3 +29,9 @@ export declare function timezoneOffsetFor(date: string): number;
|
|
|
29
29
|
* days. Null when the runtime cannot report one; the fixed offset is then used.
|
|
30
30
|
*/
|
|
31
31
|
export declare function deviceTimeZone(): string | null;
|
|
32
|
+
/**
|
|
33
|
+
* A calendar label for a YYYY-MM-DD day, e.g. "Sunday, November 1, 2026".
|
|
34
|
+
* Formatted as a pure calendar date (no time, no zone shift) so the header
|
|
35
|
+
* always names the day the driver picked.
|
|
36
|
+
*/
|
|
37
|
+
export declare function formatLocalDay(date: string): string;
|
|
@@ -10,20 +10,20 @@ import { Phone as c } from "../node_modules/lucide-react/dist/esm/icons/phone.js
|
|
|
10
10
|
import { RefreshCw as l } from "../node_modules/lucide-react/dist/esm/icons/refresh-cw.js";
|
|
11
11
|
import { Route as u } from "../node_modules/lucide-react/dist/esm/icons/route.js";
|
|
12
12
|
import { TriangleAlert as d } from "../node_modules/lucide-react/dist/esm/icons/triangle-alert.js";
|
|
13
|
-
import { Truck as
|
|
14
|
-
import { WifiOff as
|
|
15
|
-
import { useMoveTheWheels as
|
|
16
|
-
import { formatAddress as
|
|
17
|
-
import { mapOrderPriority as
|
|
18
|
-
import { localDateString as
|
|
19
|
-
import { useMyDeliveryDay as re, useOnlineStatus as
|
|
20
|
-
import { Badge as S, Button as C, Card as w, CardContent as T, Input as
|
|
21
|
-
import { PageLayout as
|
|
22
|
-
import { PriorityBadge as
|
|
23
|
-
import { FormError as
|
|
24
|
-
import { Notice as
|
|
13
|
+
import { Truck as f } from "../node_modules/lucide-react/dist/esm/icons/truck.js";
|
|
14
|
+
import { WifiOff as ee } from "../node_modules/lucide-react/dist/esm/icons/wifi-off.js";
|
|
15
|
+
import { useMoveTheWheels as te } from "../providers/MoveTheWheelsProvider.js";
|
|
16
|
+
import { formatAddress as p, formatDate as m, formatTime as h } from "../utils/formatters.js";
|
|
17
|
+
import { mapOrderPriority as g } from "../hooks/useOrders.js";
|
|
18
|
+
import { formatLocalDay as _, localDateString as v, phoneHref as y, routeNavigationUrl as ne, shiftLocalDate as b, stopNavigationUrl as x } from "../utils/driverNavigation.js";
|
|
19
|
+
import { useMyDeliveryDay as re, useOnlineStatus as ie } from "../hooks/useDriverDay.js";
|
|
20
|
+
import { Badge as S, Button as C, Card as w, CardContent as T, Input as E, Progress as D } from "../components/ui.js";
|
|
21
|
+
import { PageLayout as O } from "../components/PageLayout.js";
|
|
22
|
+
import { PriorityBadge as k } from "../components/StatusBadge.js";
|
|
23
|
+
import { FormError as A } from "../components/FormError.js";
|
|
24
|
+
import { Notice as j, useNotice as ae } from "../components/Notice.js";
|
|
25
25
|
import { EpodCaptureForm as oe } from "../components/EpodCaptureForm.js";
|
|
26
|
-
import { useEffect as se, useMemo as
|
|
26
|
+
import { useEffect as se, useMemo as M, useRef as ce, useState as N } from "react";
|
|
27
27
|
import { Fragment as P, jsx as F, jsxs as I } from "react/jsx-runtime";
|
|
28
28
|
//#region src/movethewheels/pages/DeliveryCapturePage.tsx
|
|
29
29
|
var L = {
|
|
@@ -96,7 +96,7 @@ function W({ stop: e, onOpen: t, onCapture: n, offline: r }) {
|
|
|
96
96
|
}),
|
|
97
97
|
e.deliveryAddress && /* @__PURE__ */ F("p", {
|
|
98
98
|
className: "mt-1 text-sm text-text-secondary",
|
|
99
|
-
children:
|
|
99
|
+
children: p(e.deliveryAddress)
|
|
100
100
|
}),
|
|
101
101
|
/* @__PURE__ */ I("p", {
|
|
102
102
|
className: "mt-1 flex flex-wrap items-center gap-x-3 gap-y-0.5 text-xs text-text-secondary",
|
|
@@ -106,7 +106,7 @@ function W({ stop: e, onOpen: t, onCapture: n, offline: r }) {
|
|
|
106
106
|
children: [
|
|
107
107
|
/* @__PURE__ */ F(a, { size: 12 }),
|
|
108
108
|
" ",
|
|
109
|
-
|
|
109
|
+
h(e.scheduledDelivery)
|
|
110
110
|
]
|
|
111
111
|
}),
|
|
112
112
|
/* @__PURE__ */ I("span", {
|
|
@@ -139,8 +139,8 @@ function W({ stop: e, onOpen: t, onCapture: n, offline: r }) {
|
|
|
139
139
|
]
|
|
140
140
|
}) });
|
|
141
141
|
}
|
|
142
|
-
function
|
|
143
|
-
let s =
|
|
142
|
+
function le({ stop: t, onBack: n, onCapture: r, offline: a }) {
|
|
143
|
+
let s = x(t.deliveryAddress), l = y(t.customerPhone);
|
|
144
144
|
return /* @__PURE__ */ I("div", {
|
|
145
145
|
className: "space-y-4",
|
|
146
146
|
children: [/* @__PURE__ */ I(C, {
|
|
@@ -161,8 +161,8 @@ function G({ stop: t, onBack: n, onCapture: r, offline: a }) {
|
|
|
161
161
|
children: ["Stop ", t.sequence]
|
|
162
162
|
}),
|
|
163
163
|
/* @__PURE__ */ F(H, { state: t.state }),
|
|
164
|
-
/* @__PURE__ */ F(
|
|
165
|
-
priority:
|
|
164
|
+
/* @__PURE__ */ F(k, {
|
|
165
|
+
priority: g(t.priority),
|
|
166
166
|
size: "sm"
|
|
167
167
|
}),
|
|
168
168
|
t.overdue && /* @__PURE__ */ F(S, {
|
|
@@ -179,7 +179,7 @@ function G({ stop: t, onBack: n, onCapture: r, offline: a }) {
|
|
|
179
179
|
children: "Deliver to"
|
|
180
180
|
}), /* @__PURE__ */ F("dd", {
|
|
181
181
|
className: "font-medium",
|
|
182
|
-
children: t.deliveryAddress ?
|
|
182
|
+
children: t.deliveryAddress ? p(t.deliveryAddress) : "No address on file"
|
|
183
183
|
})] }),
|
|
184
184
|
t.customerName && /* @__PURE__ */ I("div", { children: [/* @__PURE__ */ F("dt", {
|
|
185
185
|
className: "text-text-secondary",
|
|
@@ -200,7 +200,7 @@ function G({ stop: t, onBack: n, onCapture: r, offline: a }) {
|
|
|
200
200
|
children: "Scheduled"
|
|
201
201
|
}), /* @__PURE__ */ F("dd", {
|
|
202
202
|
className: "font-medium",
|
|
203
|
-
children:
|
|
203
|
+
children: m(t.scheduledDelivery, "full")
|
|
204
204
|
})] }),
|
|
205
205
|
t.routeName && /* @__PURE__ */ I("div", { children: [/* @__PURE__ */ F("dt", {
|
|
206
206
|
className: "text-text-secondary",
|
|
@@ -221,7 +221,7 @@ function G({ stop: t, onBack: n, onCapture: r, offline: a }) {
|
|
|
221
221
|
children: "Proof of delivery captured"
|
|
222
222
|
}), /* @__PURE__ */ F("dd", {
|
|
223
223
|
className: "font-medium",
|
|
224
|
-
children:
|
|
224
|
+
children: m(t.deliveredAt, "full")
|
|
225
225
|
})] })
|
|
226
226
|
]
|
|
227
227
|
}),
|
|
@@ -257,46 +257,46 @@ function G({ stop: t, onBack: n, onCapture: r, offline: a }) {
|
|
|
257
257
|
}) })]
|
|
258
258
|
});
|
|
259
259
|
}
|
|
260
|
-
function
|
|
261
|
-
let { workspaceId: e, tenantId: a, currentUser: o, apiGatewayUrl: s } =
|
|
260
|
+
function G() {
|
|
261
|
+
let { workspaceId: e, tenantId: a, currentUser: o, apiGatewayUrl: s } = te(), c = ie(), m = !c, [h, g] = N(() => v(/* @__PURE__ */ new Date())), [y, x] = N("all"), [S, k] = N({ kind: "list" }), { day: L, isLoading: R, error: H, refetch: U, markCaptured: G } = re(h), { message: ue, notify: K } = ae(), q = v(/* @__PURE__ */ new Date()), J = ce(!1);
|
|
262
262
|
se(() => {
|
|
263
|
-
c ? J.current && (J.current = !1,
|
|
264
|
-
}, [c,
|
|
265
|
-
let Y =
|
|
266
|
-
|
|
263
|
+
c ? J.current && (J.current = !1, U()) : J.current = !0;
|
|
264
|
+
}, [c, U]);
|
|
265
|
+
let Y = M(() => L?.stops ?? [], [L]), X = M(() => Y.filter((e) => y === "all" ? !0 : y === "completed" ? z(e) : !z(e)), [Y, y]), Z = M(() => ne(Y.filter((e) => !z(e)).map((e) => e.deliveryAddress)), [Y]), Q = S.kind === "list" ? null : Y.find((e) => e.jobId === S.jobId) ?? null, de = (e) => {
|
|
266
|
+
G(e), K("Proof of delivery captured — delivery marked complete."), k({ kind: "list" }), U();
|
|
267
267
|
}, $ = (e) => {
|
|
268
|
-
/^\d{4}-\d{2}-\d{2}$/.test(e) && (
|
|
269
|
-
}, fe =
|
|
270
|
-
return /* @__PURE__ */ F(
|
|
268
|
+
/^\d{4}-\d{2}-\d{2}$/.test(e) && (g(e), k({ kind: "list" }));
|
|
269
|
+
}, fe = L && L.totalStops > 0 ? L.completedStops / L.totalStops * 100 : 0;
|
|
270
|
+
return /* @__PURE__ */ F(O, {
|
|
271
271
|
title: "My Deliveries",
|
|
272
272
|
subtitle: "Your stops for the day, in order — navigate, then capture proof of delivery",
|
|
273
|
-
icon: /* @__PURE__ */ F(
|
|
273
|
+
icon: /* @__PURE__ */ F(f, { size: 24 }),
|
|
274
274
|
maxWidth: "2xl",
|
|
275
275
|
children: /* @__PURE__ */ I("div", {
|
|
276
276
|
className: "space-y-4",
|
|
277
277
|
children: [
|
|
278
|
-
/* @__PURE__ */ F(
|
|
278
|
+
/* @__PURE__ */ F(j, { message: ue }),
|
|
279
279
|
m && /* @__PURE__ */ I("div", {
|
|
280
280
|
role: "status",
|
|
281
281
|
className: "flex items-start gap-2 rounded-md border border-border bg-surface-secondary p-3 text-sm",
|
|
282
|
-
children: [/* @__PURE__ */ F(
|
|
282
|
+
children: [/* @__PURE__ */ F(ee, {
|
|
283
283
|
size: 16,
|
|
284
284
|
className: "mt-0.5 shrink-0"
|
|
285
285
|
}), /* @__PURE__ */ F("span", { children: "You are offline. Your last loaded stops stay available; reconnect to capture proof of delivery." })]
|
|
286
286
|
}),
|
|
287
|
-
Q &&
|
|
287
|
+
Q && S.kind === "capture" && Q.awaitingCapture ? /* @__PURE__ */ F(oe, {
|
|
288
288
|
job: B(Q),
|
|
289
289
|
offline: m,
|
|
290
290
|
onCaptured: de,
|
|
291
|
-
onCancel: () =>
|
|
291
|
+
onCancel: () => k({
|
|
292
292
|
kind: "stop",
|
|
293
293
|
jobId: Q.jobId
|
|
294
294
|
})
|
|
295
|
-
}, `${e}:${a}:${o?.id}:${s}:${Q.jobId}`) : Q ? /* @__PURE__ */ F(
|
|
295
|
+
}, `${e}:${a}:${o?.id}:${s}:${Q.jobId}`) : Q ? /* @__PURE__ */ F(le, {
|
|
296
296
|
stop: Q,
|
|
297
297
|
offline: m,
|
|
298
|
-
onBack: () =>
|
|
299
|
-
onCapture: () =>
|
|
298
|
+
onBack: () => k({ kind: "list" }),
|
|
299
|
+
onCapture: () => k({
|
|
300
300
|
kind: "capture",
|
|
301
301
|
jobId: Q.jobId
|
|
302
302
|
})
|
|
@@ -309,7 +309,7 @@ function K() {
|
|
|
309
309
|
variant: "outline",
|
|
310
310
|
size: "sm",
|
|
311
311
|
"aria-label": "Previous day",
|
|
312
|
-
onClick: () => $(
|
|
312
|
+
onClick: () => $(b(h, -1)),
|
|
313
313
|
children: /* @__PURE__ */ F(n, { size: 16 })
|
|
314
314
|
}),
|
|
315
315
|
/* @__PURE__ */ I(C, {
|
|
@@ -324,10 +324,10 @@ function K() {
|
|
|
324
324
|
variant: "outline",
|
|
325
325
|
size: "sm",
|
|
326
326
|
"aria-label": "Next day",
|
|
327
|
-
onClick: () => $(
|
|
327
|
+
onClick: () => $(b(h, 1)),
|
|
328
328
|
children: /* @__PURE__ */ F(r, { size: 16 })
|
|
329
329
|
}),
|
|
330
|
-
/* @__PURE__ */ F(
|
|
330
|
+
/* @__PURE__ */ F(E, {
|
|
331
331
|
type: "date",
|
|
332
332
|
"aria-label": "Delivery date",
|
|
333
333
|
value: h,
|
|
@@ -339,20 +339,20 @@ function K() {
|
|
|
339
339
|
variant: "ghost",
|
|
340
340
|
size: "sm",
|
|
341
341
|
"aria-label": "Refresh stops",
|
|
342
|
-
disabled:
|
|
343
|
-
onClick: () => void
|
|
342
|
+
disabled: R || m,
|
|
343
|
+
onClick: () => void U(),
|
|
344
344
|
children: /* @__PURE__ */ F(l, {
|
|
345
345
|
size: 16,
|
|
346
|
-
className:
|
|
346
|
+
className: R ? "animate-spin" : void 0
|
|
347
347
|
})
|
|
348
348
|
})
|
|
349
349
|
]
|
|
350
350
|
}),
|
|
351
|
-
|
|
352
|
-
|
|
351
|
+
H && /* @__PURE__ */ F(A, { message: H.message }),
|
|
352
|
+
R && !L ? /* @__PURE__ */ F("p", {
|
|
353
353
|
className: "py-8 text-center text-text-secondary",
|
|
354
354
|
children: "Loading your deliveries…"
|
|
355
|
-
}) :
|
|
355
|
+
}) : L && L.driverId === null ? /* @__PURE__ */ I("div", {
|
|
356
356
|
className: "py-12 text-center",
|
|
357
357
|
children: [/* @__PURE__ */ F(d, {
|
|
358
358
|
size: 40,
|
|
@@ -361,7 +361,7 @@ function K() {
|
|
|
361
361
|
className: "text-text-secondary",
|
|
362
362
|
children: "Your account is not linked to a driver in this workspace. Ask your dispatcher to link your user on the driver profile."
|
|
363
363
|
})]
|
|
364
|
-
}) :
|
|
364
|
+
}) : L ? /* @__PURE__ */ I(P, { children: [
|
|
365
365
|
/* @__PURE__ */ F(w, { children: /* @__PURE__ */ I(T, {
|
|
366
366
|
className: "space-y-3 py-4",
|
|
367
367
|
children: [
|
|
@@ -370,35 +370,35 @@ function K() {
|
|
|
370
370
|
children: [/* @__PURE__ */ I("p", {
|
|
371
371
|
className: "font-medium",
|
|
372
372
|
children: [
|
|
373
|
-
|
|
373
|
+
L.completedStops,
|
|
374
374
|
" of ",
|
|
375
|
-
|
|
375
|
+
L.totalStops,
|
|
376
376
|
" stops done",
|
|
377
|
-
|
|
377
|
+
L.driverName ? ` · ${L.driverName}` : ""
|
|
378
378
|
]
|
|
379
379
|
}), /* @__PURE__ */ F("p", {
|
|
380
380
|
className: "text-sm text-text-secondary",
|
|
381
|
-
children:
|
|
381
|
+
children: _(h)
|
|
382
382
|
})]
|
|
383
383
|
}),
|
|
384
|
-
/* @__PURE__ */ F(
|
|
384
|
+
/* @__PURE__ */ F(D, {
|
|
385
385
|
value: fe,
|
|
386
386
|
"aria-label": "Stops completed"
|
|
387
387
|
}),
|
|
388
|
-
|
|
388
|
+
L.nextStop && /* @__PURE__ */ I("div", {
|
|
389
389
|
className: "rounded-md border border-border p-3",
|
|
390
390
|
children: [
|
|
391
391
|
/* @__PURE__ */ I("p", {
|
|
392
392
|
className: "text-xs uppercase tracking-wide text-text-secondary",
|
|
393
|
-
children: ["Next stop · Stop ",
|
|
393
|
+
children: ["Next stop · Stop ", L.nextStop.sequence]
|
|
394
394
|
}),
|
|
395
395
|
/* @__PURE__ */ F("p", {
|
|
396
396
|
className: "mt-1 font-medium",
|
|
397
|
-
children:
|
|
397
|
+
children: L.nextStop.deliveryAddress ? p(L.nextStop.deliveryAddress) : L.nextStop.deliveryJobNumber
|
|
398
398
|
}),
|
|
399
|
-
|
|
399
|
+
L.nextStop.customerName && /* @__PURE__ */ I("p", {
|
|
400
400
|
className: "text-sm text-text-secondary",
|
|
401
|
-
children: ["Recipient: ",
|
|
401
|
+
children: ["Recipient: ", L.nextStop.customerName]
|
|
402
402
|
}),
|
|
403
403
|
/* @__PURE__ */ I("div", {
|
|
404
404
|
className: "mt-3 grid gap-2 sm:grid-cols-2",
|
|
@@ -411,8 +411,8 @@ function K() {
|
|
|
411
411
|
type: "button",
|
|
412
412
|
variant: "outline",
|
|
413
413
|
onClick: () => {
|
|
414
|
-
let e =
|
|
415
|
-
e &&
|
|
414
|
+
let e = L.nextStop;
|
|
415
|
+
e && k({
|
|
416
416
|
kind: "stop",
|
|
417
417
|
jobId: e.jobId
|
|
418
418
|
});
|
|
@@ -429,16 +429,16 @@ function K() {
|
|
|
429
429
|
"aria-label": "Filter stops",
|
|
430
430
|
className: "flex gap-2",
|
|
431
431
|
children: [
|
|
432
|
-
["all", `All stops (${
|
|
433
|
-
["remaining", `Remaining (${
|
|
434
|
-
["completed", `Done (${
|
|
432
|
+
["all", `All stops (${L.totalStops})`],
|
|
433
|
+
["remaining", `Remaining (${L.remainingStops})`],
|
|
434
|
+
["completed", `Done (${L.completedStops})`]
|
|
435
435
|
].map(([e, t]) => /* @__PURE__ */ F(C, {
|
|
436
436
|
type: "button",
|
|
437
437
|
role: "tab",
|
|
438
|
-
"aria-selected":
|
|
438
|
+
"aria-selected": y === e,
|
|
439
439
|
size: "sm",
|
|
440
|
-
variant:
|
|
441
|
-
onClick: () =>
|
|
440
|
+
variant: y === e ? "default" : "outline",
|
|
441
|
+
onClick: () => x(e),
|
|
442
442
|
children: t
|
|
443
443
|
}, e))
|
|
444
444
|
}),
|
|
@@ -447,11 +447,11 @@ function K() {
|
|
|
447
447
|
children: X.map((e) => /* @__PURE__ */ F(W, {
|
|
448
448
|
stop: e,
|
|
449
449
|
offline: m,
|
|
450
|
-
onOpen: () =>
|
|
450
|
+
onOpen: () => k({
|
|
451
451
|
kind: "stop",
|
|
452
452
|
jobId: e.jobId
|
|
453
453
|
}),
|
|
454
|
-
onCapture: () =>
|
|
454
|
+
onCapture: () => k({
|
|
455
455
|
kind: "capture",
|
|
456
456
|
jobId: e.jobId
|
|
457
457
|
})
|
|
@@ -463,7 +463,7 @@ function K() {
|
|
|
463
463
|
className: "mx-auto mb-3 text-text-secondary opacity-50"
|
|
464
464
|
}), /* @__PURE__ */ F("p", {
|
|
465
465
|
className: "text-text-secondary",
|
|
466
|
-
children:
|
|
466
|
+
children: L.totalStops === 0 ? "No deliveries scheduled for this day." : y === "completed" ? "No stops completed yet." : "All stops done for this day."
|
|
467
467
|
})]
|
|
468
468
|
})
|
|
469
469
|
] }) : null
|
|
@@ -473,4 +473,4 @@ function K() {
|
|
|
473
473
|
});
|
|
474
474
|
}
|
|
475
475
|
//#endregion
|
|
476
|
-
export {
|
|
476
|
+
export { G as default };
|
|
@@ -47,5 +47,15 @@ function l() {
|
|
|
47
47
|
return null;
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
+
function u(e) {
|
|
51
|
+
let [t, n, r] = e.split("-").map(Number);
|
|
52
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
53
|
+
weekday: "long",
|
|
54
|
+
year: "numeric",
|
|
55
|
+
month: "long",
|
|
56
|
+
day: "numeric",
|
|
57
|
+
timeZone: "UTC"
|
|
58
|
+
}).format(new Date(Date.UTC(t, n - 1, r)));
|
|
59
|
+
}
|
|
50
60
|
//#endregion
|
|
51
|
-
export { l as deviceTimeZone, o as localDateString, a as phoneHref, i as routeNavigationUrl, s as shiftLocalDate, r as stopNavigationUrl, c as timezoneOffsetFor };
|
|
61
|
+
export { l as deviceTimeZone, u as formatLocalDay, o as localDateString, a as phoneHref, i as routeNavigationUrl, s as shiftLocalDate, r as stopNavigationUrl, c as timezoneOffsetFor };
|
package/package.json
CHANGED
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
type DriverStopState,
|
|
49
49
|
} from '../hooks/useDriverDay';
|
|
50
50
|
import {
|
|
51
|
+
formatLocalDay,
|
|
51
52
|
localDateString,
|
|
52
53
|
phoneHref,
|
|
53
54
|
routeNavigationUrl,
|
|
@@ -445,9 +446,7 @@ export default function DeliveryCapturePage() {
|
|
|
445
446
|
{day.completedStops} of {day.totalStops} stops done
|
|
446
447
|
{day.driverName ? ` · ${day.driverName}` : ''}
|
|
447
448
|
</p>
|
|
448
|
-
<p className="text-sm text-text-secondary">
|
|
449
|
-
{formatDate(new Date(`${date}T12:00:00`), 'full')}
|
|
450
|
-
</p>
|
|
449
|
+
<p className="text-sm text-text-secondary">{formatLocalDay(date)}</p>
|
|
451
450
|
</div>
|
|
452
451
|
<Progress value={progress} aria-label="Stops completed" />
|
|
453
452
|
{day.nextStop && (
|
|
@@ -101,3 +101,19 @@ export function deviceTimeZone(): string | null {
|
|
|
101
101
|
return null;
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* A calendar label for a YYYY-MM-DD day, e.g. "Sunday, November 1, 2026".
|
|
107
|
+
* Formatted as a pure calendar date (no time, no zone shift) so the header
|
|
108
|
+
* always names the day the driver picked.
|
|
109
|
+
*/
|
|
110
|
+
export function formatLocalDay(date: string): string {
|
|
111
|
+
const [year, month, day] = date.split('-').map(Number);
|
|
112
|
+
return new Intl.DateTimeFormat('en-US', {
|
|
113
|
+
weekday: 'long',
|
|
114
|
+
year: 'numeric',
|
|
115
|
+
month: 'long',
|
|
116
|
+
day: 'numeric',
|
|
117
|
+
timeZone: 'UTC',
|
|
118
|
+
}).format(new Date(Date.UTC(year, month - 1, day)));
|
|
119
|
+
}
|