@burdenoff/microfe-movethewheels 2026.1031.3 → 2026.1031.4

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 (58) hide show
  1. package/dist/MoveTheWheelsRoot.js +136 -127
  2. package/dist/MoveTheWheelsRoutes.js +140 -135
  3. package/dist/components/DocumentsCard.js +11 -11
  4. package/dist/components/ProofOfDeliveryCard.js +13 -13
  5. package/dist/components/ShipmentRiskDialog.js +24 -24
  6. package/dist/components/ShippingQuoteResults.js +46 -46
  7. package/dist/components/TrackingAlertList.js +17 -17
  8. package/dist/generated/tracking-quality-operations.d.ts +56 -0
  9. package/dist/hooks/useOrderDispatch.js +17 -17
  10. package/dist/movethewheels/pages/SettingsPage.d.ts +1 -0
  11. package/dist/movethewheels/providers/ModuleSettingsProvider.d.ts +17 -0
  12. package/dist/movethewheels/utils/formatters.d.ts +23 -3
  13. package/dist/movethewheels/utils/moduleSettings.d.ts +52 -0
  14. package/dist/node_modules/lucide-react/dist/esm/icons/bell.js +11 -0
  15. package/dist/node_modules/lucide-react/dist/esm/icons/key-round.js +14 -0
  16. package/dist/node_modules/lucide-react/dist/esm/icons/plug.js +22 -0
  17. package/dist/node_modules/lucide-react/dist/esm/icons/rotate-ccw.js +11 -0
  18. package/dist/node_modules/lucide-react/dist/esm/icons/settings.js +13 -0
  19. package/dist/pages/AnalyticsPage.js +27 -27
  20. package/dist/pages/CreateOrderPage.js +340 -317
  21. package/dist/pages/CustomerDetailsPage.js +246 -246
  22. package/dist/pages/CustomersPage.js +19 -19
  23. package/dist/pages/DashboardPage.js +24 -24
  24. package/dist/pages/DeliveryCapturePage.js +74 -74
  25. package/dist/pages/DriverDetailsPage.js +337 -337
  26. package/dist/pages/DriversPage.js +26 -26
  27. package/dist/pages/FinancePage.js +26 -26
  28. package/dist/pages/InventoryPage.js +28 -28
  29. package/dist/pages/LiveTrackingPage.js +108 -108
  30. package/dist/pages/MarketplacePage.js +44 -44
  31. package/dist/pages/OrderDetailsPage.js +306 -306
  32. package/dist/pages/OrdersListPage.js +27 -27
  33. package/dist/pages/ProductDetailsPage.js +89 -89
  34. package/dist/pages/ProductsPage.js +73 -73
  35. package/dist/pages/RouteDetailsPage.js +282 -285
  36. package/dist/pages/RoutesPage.js +78 -78
  37. package/dist/pages/SettingsPage.js +459 -0
  38. package/dist/pages/TrackingPage.js +41 -41
  39. package/dist/pages/VehicleDetailsPage.js +207 -207
  40. package/dist/pages/VehiclesPage.js +17 -16
  41. package/dist/pages/WarehouseDetailsPage.js +132 -132
  42. package/dist/providers/ModuleSettingsProvider.js +74 -0
  43. package/dist/utils/formatters.js +53 -26
  44. package/dist/utils/moduleSettings.js +132 -0
  45. package/package.json +3 -2
  46. package/src/generated/tracking-quality-operations.ts +62 -0
  47. package/src/movethewheels/MoveTheWheelsRoot.tsx +14 -3
  48. package/src/movethewheels/MoveTheWheelsRoutes.tsx +9 -0
  49. package/src/movethewheels/pages/CreateOrderPage.tsx +38 -6
  50. package/src/movethewheels/pages/ProductsPage.tsx +2 -2
  51. package/src/movethewheels/pages/RouteDetailsPage.tsx +3 -6
  52. package/src/movethewheels/pages/RoutesPage.tsx +2 -2
  53. package/src/movethewheels/pages/SettingsPage.tsx +516 -0
  54. package/src/movethewheels/pages/VehicleDetailsPage.tsx +4 -2
  55. package/src/movethewheels/pages/VehiclesPage.tsx +2 -1
  56. package/src/movethewheels/providers/ModuleSettingsProvider.tsx +133 -0
  57. package/src/movethewheels/utils/formatters.ts +69 -10
  58. package/src/movethewheels/utils/moduleSettings.ts +224 -0
@@ -0,0 +1,74 @@
1
+ import { useMoveTheWheels as e } from "./MoveTheWheelsProvider.js";
2
+ import { executeGraphQL as t } from "../utils/graphqlClient.js";
3
+ import { setFormatPreferences as n } from "../utils/formatters.js";
4
+ import { MTW_MODULE_SETTINGS_QUERY as r, UPDATE_MTW_MODULE_SETTINGS_MUTATION as i, platformDefaults as a, toFormatPreferences as o } from "../utils/moduleSettings.js";
5
+ import { createContext as s, useCallback as c, useContext as l, useEffect as u, useMemo as d, useState as f } from "react";
6
+ import { jsx as p } from "react/jsx-runtime";
7
+ //#region src/movethewheels/providers/ModuleSettingsProvider.tsx
8
+ var m = s(null), h = ({ children: s }) => {
9
+ let { apiGatewayUrl: l, authToken: h, workspaceToken: g, workspaceId: _, tenantId: v, currentUser: y } = e(), b = d(() => ({
10
+ apiGatewayUrl: l,
11
+ authToken: h,
12
+ workspaceToken: g,
13
+ workspaceId: _,
14
+ tenantId: v,
15
+ actorId: y?.id
16
+ }), [
17
+ l,
18
+ h,
19
+ g,
20
+ _,
21
+ v,
22
+ y?.id
23
+ ]), [x, S] = f(() => a(_ ?? "")), [C, w] = f(!1), [T, E] = f(null), [D, O] = f(0), k = c((e) => {
24
+ n(o(e)), S(e);
25
+ }, []);
26
+ u(() => {
27
+ if (!b.workspaceId) return;
28
+ let e = !1;
29
+ return t(b, r).then((t) => {
30
+ e || (k(t.mtwModuleSettings), E(null));
31
+ }).catch((t) => {
32
+ e || (k(a(b.workspaceId ?? "")), E(t instanceof Error ? t : /* @__PURE__ */ Error("Could not load settings")));
33
+ }).finally(() => {
34
+ e || w(!0);
35
+ }), () => {
36
+ e = !0;
37
+ };
38
+ }, [
39
+ b,
40
+ D,
41
+ k
42
+ ]);
43
+ let A = c(async (e) => {
44
+ let n = await t(b, i, { input: e });
45
+ return k(n.updateMtwModuleSettings), E(null), n.updateMtwModuleSettings;
46
+ }, [b, k]), j = c(() => O((e) => e + 1), []), M = d(() => ({
47
+ settings: x,
48
+ isLoaded: C,
49
+ error: T,
50
+ save: A,
51
+ reload: j
52
+ }), [
53
+ x,
54
+ C,
55
+ T,
56
+ A,
57
+ j
58
+ ]);
59
+ return /* @__PURE__ */ p(m.Provider, {
60
+ value: M,
61
+ children: s
62
+ });
63
+ };
64
+ function g() {
65
+ return l(m) || {
66
+ settings: a(),
67
+ isLoaded: !0,
68
+ error: null,
69
+ save: () => Promise.reject(/* @__PURE__ */ Error("Module settings are not available here")),
70
+ reload: () => void 0
71
+ };
72
+ }
73
+ //#endregion
74
+ export { h as ModuleSettingsProvider, g as useModuleSettings };
@@ -1,5 +1,17 @@
1
- //#region src/movethewheels/utils/formatters.ts
2
- function e(e) {
1
+ var e = {
2
+ currency: "USD",
3
+ distanceUnit: "KM",
4
+ weightUnit: "KG",
5
+ timeZone: null
6
+ };
7
+ function t(t) {
8
+ e = { ...t };
9
+ }
10
+ function n() {
11
+ return e.timeZone ? { timeZone: e.timeZone } : {};
12
+ }
13
+ var r = 1.609344, i = .45359237;
14
+ function a(e) {
3
15
  return [
4
16
  e.street,
5
17
  e.city,
@@ -8,35 +20,38 @@ function e(e) {
8
20
  e.country
9
21
  ].filter(Boolean).join(", ");
10
22
  }
11
- function t(e, t = "USD") {
23
+ function o(t, n) {
12
24
  return new Intl.NumberFormat("en-US", {
13
25
  style: "currency",
14
- currency: t,
26
+ currency: n || e.currency,
15
27
  minimumFractionDigits: 2,
16
28
  maximumFractionDigits: 2
17
- }).format(e);
29
+ }).format(t);
18
30
  }
19
- function n(e, t = "short") {
20
- let n = typeof e == "string" ? new Date(e) : e;
21
- return t === "relative" ? i(n) : t === "time" ? new Intl.DateTimeFormat("en-US", {
31
+ function s(e, t = "short") {
32
+ let r = typeof e == "string" ? new Date(e) : e;
33
+ return t === "relative" ? l(r) : t === "time" ? new Intl.DateTimeFormat("en-US", {
22
34
  hour: "numeric",
23
35
  minute: "2-digit",
24
- hour12: !0
25
- }).format(n) : t === "full" ? new Intl.DateTimeFormat("en-US", {
36
+ hour12: !0,
37
+ ...n()
38
+ }).format(r) : t === "full" ? new Intl.DateTimeFormat("en-US", {
26
39
  weekday: "long",
27
40
  year: "numeric",
28
41
  month: "long",
29
42
  day: "numeric",
30
43
  hour: "numeric",
31
44
  minute: "2-digit",
32
- hour12: !0
33
- }).format(n) : new Intl.DateTimeFormat("en-US", {
45
+ hour12: !0,
46
+ ...n()
47
+ }).format(r) : new Intl.DateTimeFormat("en-US", {
34
48
  year: "numeric",
35
49
  month: "short",
36
- day: "numeric"
37
- }).format(n);
50
+ day: "numeric",
51
+ ...n()
52
+ }).format(r);
38
53
  }
39
- function r(e) {
54
+ function c(e) {
40
55
  let t = typeof e == "string" ? new Date(e) : e;
41
56
  return new Intl.DateTimeFormat("en-US", {
42
57
  year: "numeric",
@@ -44,21 +59,33 @@ function r(e) {
44
59
  day: "numeric",
45
60
  hour: "numeric",
46
61
  minute: "2-digit",
47
- hour12: !0
62
+ hour12: !0,
63
+ ...n()
48
64
  }).format(t);
49
65
  }
50
- function i(e) {
51
- let t = typeof e == "string" ? new Date(e) : e, r = Math.floor(((/* @__PURE__ */ new Date()).getTime() - t.getTime()) / 1e3);
52
- if (r < 60) return "Just now";
66
+ function l(e) {
67
+ let t = typeof e == "string" ? new Date(e) : e, n = Math.floor(((/* @__PURE__ */ new Date()).getTime() - t.getTime()) / 1e3);
68
+ if (n < 60) return "Just now";
69
+ let r = Math.floor(n / 60);
70
+ if (r < 60) return `${r} ${r === 1 ? "minute" : "minutes"} ago`;
53
71
  let i = Math.floor(r / 60);
54
- if (i < 60) return `${i} ${i === 1 ? "minute" : "minutes"} ago`;
55
- let a = Math.floor(i / 60);
56
- if (a < 24) return `${a} ${a === 1 ? "hour" : "hours"} ago`;
57
- let o = Math.floor(a / 24);
58
- return o < 7 ? `${o} ${o === 1 ? "day" : "days"} ago` : n(t);
72
+ if (i < 24) return `${i} ${i === 1 ? "hour" : "hours"} ago`;
73
+ let a = Math.floor(i / 24);
74
+ return a < 7 ? `${a} ${a === 1 ? "day" : "days"} ago` : s(t);
59
75
  }
60
- function a(e) {
76
+ var u = new Intl.NumberFormat("en-US", { maximumFractionDigits: 2 }), d = new Intl.NumberFormat("en-US", { maximumFractionDigits: 1 });
77
+ function f(t) {
78
+ return e.weightUnit === "LB" ? `${u.format(t / i)} lb` : Math.abs(t) >= 1e3 ? `${u.format(t / 1e3)} tonnes` : `${u.format(t)} kg`;
79
+ }
80
+ function p(t) {
81
+ if (e.distanceUnit === "MI") {
82
+ let e = t / r;
83
+ return Math.abs(e) < .1 ? `${Math.round(e * 5280)} ft` : `${d.format(e)} mi`;
84
+ }
85
+ return Math.abs(t) < 1 ? `${Math.round(t * 1e3)} m` : `${d.format(t)} km`;
86
+ }
87
+ function m(e) {
61
88
  return e.split(" ").map((e) => e[0]).join("").toUpperCase().slice(0, 2);
62
89
  }
63
90
  //#endregion
64
- export { e as formatAddress, t as formatCurrency, n as formatDate, r as formatDateTime, a as getInitials };
91
+ export { a as formatAddress, o as formatCurrency, s as formatDate, c as formatDateTime, p as formatDistance, f as formatWeight, m as getInitials, t as setFormatPreferences };
@@ -0,0 +1,132 @@
1
+ function e(e = "") {
2
+ return {
3
+ workspaceId: e,
4
+ defaultCurrency: "USD",
5
+ distanceUnit: "KM",
6
+ weightUnit: "KG",
7
+ timezone: null,
8
+ defaultOrderPriority: "MEDIUM",
9
+ defaultDeliveryWindowHours: 24,
10
+ isCustomized: !1,
11
+ updatedAt: null,
12
+ updatedBy: null
13
+ };
14
+ }
15
+ var t = "\n workspaceId\n defaultCurrency\n distanceUnit\n weightUnit\n timezone\n defaultOrderPriority\n defaultDeliveryWindowHours\n isCustomized\n updatedAt\n updatedBy\n", n = `
16
+ query MtwModuleSettings {
17
+ mtwModuleSettings {
18
+ ${t}
19
+ }
20
+ }
21
+ `, r = `
22
+ mutation UpdateMtwModuleSettings($input: UpdateMtwModuleSettingsInput!) {
23
+ updateMtwModuleSettings(input: $input) {
24
+ ${t}
25
+ }
26
+ }
27
+ `;
28
+ function i(e) {
29
+ return {
30
+ currency: e.defaultCurrency,
31
+ distanceUnit: e.distanceUnit,
32
+ weightUnit: e.weightUnit,
33
+ timeZone: e.timezone
34
+ };
35
+ }
36
+ var a = [{
37
+ value: "KM",
38
+ label: "Kilometres (km)"
39
+ }, {
40
+ value: "MI",
41
+ label: "Miles (mi)"
42
+ }], o = [{
43
+ value: "KG",
44
+ label: "Kilograms (kg)"
45
+ }, {
46
+ value: "LB",
47
+ label: "Pounds (lb)"
48
+ }], s = [
49
+ {
50
+ value: "LOW",
51
+ label: "Low"
52
+ },
53
+ {
54
+ value: "MEDIUM",
55
+ label: "Normal"
56
+ },
57
+ {
58
+ value: "HIGH",
59
+ label: "High"
60
+ },
61
+ {
62
+ value: "URGENT",
63
+ label: "Urgent"
64
+ },
65
+ {
66
+ value: "CRITICAL",
67
+ label: "Critical"
68
+ }
69
+ ];
70
+ function c(e) {
71
+ switch (e) {
72
+ case "LOW": return "low";
73
+ case "HIGH": return "high";
74
+ case "URGENT":
75
+ case "CRITICAL": return "urgent";
76
+ default: return "normal";
77
+ }
78
+ }
79
+ var l = [
80
+ "USD",
81
+ "EUR",
82
+ "GBP",
83
+ "INR",
84
+ "AED",
85
+ "SGD",
86
+ "AUD",
87
+ "CAD",
88
+ "JPY",
89
+ "CNY"
90
+ ];
91
+ function u(e) {
92
+ return typeof Intl.supportedValuesOf == "function" ? Intl.supportedValuesOf(e) : [];
93
+ }
94
+ function d(e) {
95
+ let t = new Set([...l, ...u("currency")]);
96
+ e && t.add(e);
97
+ let n = [...t].filter((e) => !l.includes(e)).sort();
98
+ return [...l, ...n];
99
+ }
100
+ function f(e) {
101
+ try {
102
+ let t = new Intl.DisplayNames(["en"], { type: "currency" }).of(e);
103
+ return t && t !== e ? `${e} — ${t}` : e;
104
+ } catch {
105
+ return e;
106
+ }
107
+ }
108
+ function p(e) {
109
+ let t = new Set(["UTC", ...u("timeZone")]);
110
+ return e && t.add(e), [...t].sort((e, t) => e === "UTC" ? -1 : t === "UTC" ? 1 : e.localeCompare(t));
111
+ }
112
+ function m() {
113
+ try {
114
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
115
+ } catch {
116
+ return "UTC";
117
+ }
118
+ }
119
+ function h(e) {
120
+ let t = Number(e);
121
+ return !e.trim() || !Number.isInteger(t) || t < 1 || t > 720 ? "Enter a whole number of hours between 1 and 720." : null;
122
+ }
123
+ function g(e, t) {
124
+ let n = {};
125
+ return t.defaultCurrency !== void 0 && t.defaultCurrency !== e.defaultCurrency && (n.defaultCurrency = t.defaultCurrency), t.distanceUnit !== void 0 && t.distanceUnit !== e.distanceUnit && (n.distanceUnit = t.distanceUnit), t.weightUnit !== void 0 && t.weightUnit !== e.weightUnit && (n.weightUnit = t.weightUnit), t.timezone !== void 0 && t.timezone !== e.timezone && (n.timezone = t.timezone), t.defaultOrderPriority !== void 0 && t.defaultOrderPriority !== e.defaultOrderPriority && (n.defaultOrderPriority = t.defaultOrderPriority), t.defaultDeliveryWindowHours !== void 0 && t.defaultDeliveryWindowHours !== e.defaultDeliveryWindowHours && (n.defaultDeliveryWindowHours = t.defaultDeliveryWindowHours), n;
126
+ }
127
+ function _(e, t = /* @__PURE__ */ new Date()) {
128
+ let n = new Date(t.getTime() + e * 3600 * 1e3), r = (e) => String(e).padStart(2, "0");
129
+ return `${n.getFullYear()}-${r(n.getMonth() + 1)}-${r(n.getDate())}T${r(n.getHours())}:${r(n.getMinutes())}`;
130
+ }
131
+ //#endregion
132
+ export { a as DISTANCE_UNIT_OPTIONS, n as MTW_MODULE_SETTINGS_QUERY, s as ORDER_PRIORITY_OPTIONS, r as UPDATE_MTW_MODULE_SETTINGS_MUTATION, o as WEIGHT_UNIT_OPTIONS, m as browserTimeZone, g as changedSettings, f as currencyLabel, d as currencyOptions, h as deliveryWindowError, _ as deliveryWindowValue, c as orderFormPriority, e as platformDefaults, p as timeZoneOptions, i as toFormatPreferences };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-movethewheels",
3
- "version": "2026.1031.3",
3
+ "version": "2026.1031.4",
4
4
  "description": "Burdenoff MoveTheWheels Microfrontend - Logistics & Supply Chain Management Platform",
5
5
  "type": "module",
6
6
  "files": [
@@ -37,7 +37,7 @@
37
37
  "format": "prettier --write \"src/**/*.{ts,tsx}\"",
38
38
  "type:check": "bash scripts/build-limits.sh tsc --noEmit",
39
39
  "validate:schema-drift": "graphql-schema-drift",
40
- "sanity": "bash scripts/build-limits.sh --gate bash -c 'bun run codegen && bun run validate:schema-drift && bun run lint:sanity && bun run format && bun run type:check && bun run verify:live-logistics-contract && bun run verify:empty-reuse-retry && bun run verify:empty-reuse-recovery && bun run verify:trip-utilization-input && bun run verify:trip-draft-context && bun run verify:stock-binding-input && bun run verify:stock-observation-input && bun run verify:pending-pick-input && bun run verify:pick-review-ack && bun run verify:driver-license-expiry && bun run verify:tracking-alerts && bun run verify:freight-input && bun run verify:freight-recovery && bun run verify:freight-commercial && bun run verify:freight-invoice && bun run verify:procedure-input && bun run verify:consolidation && bun run verify:multimodal-single-attempt && bun run verify:workspace-predispatch && bun run verify:tracking-quality-outcomes && bun run verify:shipment-status-authority && bun run verify:free-time-permissions && bun run verify:core-codegen && bun run verify:order-vehicle-hold && bun run verify:pallet-preview && bun run verify:entity-export && bun run verify:entity-import && bun run verify:consumed-fuel-preview && bun run build && bun run build-storybook'",
40
+ "sanity": "bash scripts/build-limits.sh --gate bash -c 'bun run codegen && bun run validate:schema-drift && bun run lint:sanity && bun run format && bun run type:check && bun run verify:live-logistics-contract && bun run verify:empty-reuse-retry && bun run verify:empty-reuse-recovery && bun run verify:trip-utilization-input && bun run verify:trip-draft-context && bun run verify:stock-binding-input && bun run verify:stock-observation-input && bun run verify:pending-pick-input && bun run verify:pick-review-ack && bun run verify:driver-license-expiry && bun run verify:tracking-alerts && bun run verify:freight-input && bun run verify:freight-recovery && bun run verify:freight-commercial && bun run verify:freight-invoice && bun run verify:procedure-input && bun run verify:consolidation && bun run verify:multimodal-single-attempt && bun run verify:workspace-predispatch && bun run verify:tracking-quality-outcomes && bun run verify:shipment-status-authority && bun run verify:free-time-permissions && bun run verify:core-codegen && bun run verify:order-vehicle-hold && bun run verify:pallet-preview && bun run verify:entity-export && bun run verify:entity-import && bun run verify:module-settings && bun run verify:consumed-fuel-preview && bun run build && bun run build-storybook'",
41
41
  "storybook": "storybook dev -p 6006",
42
42
  "build-storybook": "storybook build",
43
43
  "test:storybook": "vitest run --project=storybook",
@@ -74,6 +74,7 @@
74
74
  "verify:core-codegen": "bun test scripts/codegen.test.ts && node --test scripts/check-package-types.test.mjs",
75
75
  "verify:entity-export": "bun test scripts/check-entity-export.test.ts",
76
76
  "verify:entity-import": "bun test scripts/check-entity-import.test.ts",
77
+ "verify:module-settings": "bun test scripts/check-module-settings.test.ts",
77
78
  "verify:pallet-preview": "bun test scripts/check-pallet-preview.test.ts && bun scripts/verify-pallet-preview-hook.mjs",
78
79
  "verify:consumed-fuel-preview": "bun test scripts/check-consumed-fuel-preview.test.ts && bun scripts/verify-consumed-fuel-preview-hook.mjs && bun scripts/verify-consumed-fuel-preview-component.mjs"
79
80
  },
@@ -1259,6 +1259,13 @@ export type MtwDemandForecastVariance = {
1259
1259
  volumeVariance: Scalars['Int']['output'];
1260
1260
  };
1261
1261
 
1262
+ /** Unit MoveTheWheels shows distances in. */
1263
+ export type MtwDistanceUnit =
1264
+ /** Kilometres */
1265
+ | 'KM'
1266
+ /** Miles */
1267
+ | 'MI';
1268
+
1262
1269
  export type MtwDriver = {
1263
1270
  __typename?: 'MtwDriver';
1264
1271
  baseSalary?: Maybe<Scalars['Float']['output']>;
@@ -2700,6 +2707,31 @@ export type MtwMarketplaceStats = {
2700
2707
  verifiedProviders: Scalars['Int']['output'];
2701
2708
  };
2702
2709
 
2710
+ /**
2711
+ * Workspace operational defaults for MoveTheWheels (BOFF-10488): how amounts,
2712
+ * distances, weights and times are shown, and what new orders start from.
2713
+ * Company profile, notifications, two-factor authentication and integrations
2714
+ * are platform settings and are not part of this type.
2715
+ */
2716
+ export type MtwModuleSettings = {
2717
+ __typename?: 'MtwModuleSettings';
2718
+ /** ISO 4217 code amounts without their own currency are shown in. */
2719
+ defaultCurrency: Scalars['String']['output'];
2720
+ /** Hours from now a new order's suggested delivery time falls (1 to 720). */
2721
+ defaultDeliveryWindowHours: Scalars['Int']['output'];
2722
+ /** Priority a new order starts with. */
2723
+ defaultOrderPriority: DeliveryPriority;
2724
+ distanceUnit: MtwDistanceUnit;
2725
+ /** False until the workspace saves its settings; the values are then the platform defaults. */
2726
+ isCustomized: Scalars['Boolean']['output'];
2727
+ /** IANA time zone dates and times are shown in. Null shows each viewer their own local time. */
2728
+ timezone?: Maybe<Scalars['String']['output']>;
2729
+ updatedAt?: Maybe<Scalars['DateTime']['output']>;
2730
+ updatedBy?: Maybe<Scalars['ID']['output']>;
2731
+ weightUnit: MtwWeightUnit;
2732
+ workspaceId: Scalars['ID']['output'];
2733
+ };
2734
+
2703
2735
  export type MtwMoveReviewSource = {
2704
2736
  __typename?: 'MtwMoveReviewSource';
2705
2737
  reference: Scalars['String']['output'];
@@ -4552,6 +4584,13 @@ export type MtwWarehouseStats = {
4552
4584
  total: Scalars['Int']['output'];
4553
4585
  };
4554
4586
 
4587
+ /** Unit MoveTheWheels shows weights in. */
4588
+ export type MtwWeightUnit =
4589
+ /** Kilograms */
4590
+ | 'KG'
4591
+ /** Pounds */
4592
+ | 'LB';
4593
+
4555
4594
  export type Mutation = {
4556
4595
  __typename?: 'Mutation';
4557
4596
  _empty?: Maybe<Scalars['String']['output']>;
@@ -4658,6 +4697,8 @@ export type Mutation = {
4658
4697
  updateDriverStatus: MtwDriver;
4659
4698
  updateIntegration: MtwIntegration;
4660
4699
  updateInventoryItem: MtwInventoryItem;
4700
+ /** Save the workspace's MoveTheWheels operational defaults. Every field is validated. */
4701
+ updateMtwModuleSettings: MtwModuleSettings;
4661
4702
  updateRoute: MtwRoute;
4662
4703
  updateServiceOffering: MtwServiceOffering;
4663
4704
  updateServiceProvider: MtwServiceProvider;
@@ -5141,6 +5182,11 @@ export type MutationUpdateInventoryItemArgs = {
5141
5182
  };
5142
5183
 
5143
5184
 
5185
+ export type MutationUpdateMtwModuleSettingsArgs = {
5186
+ input: UpdateMtwModuleSettingsInput;
5187
+ };
5188
+
5189
+
5144
5190
  export type MutationUpdateRouteArgs = {
5145
5191
  id: Scalars['ID']['input'];
5146
5192
  input: UpdateRouteInput;
@@ -5375,6 +5421,8 @@ export type Query = {
5375
5421
  * entity create permission.
5376
5422
  */
5377
5423
  mtwImportSpec: Array<MtwImportEntitySpec>;
5424
+ /** The workspace's MoveTheWheels operational defaults; platform defaults when none were saved. */
5425
+ mtwModuleSettings: MtwModuleSettings;
5378
5426
  mtwPalletGeometryPreview: MtwPalletGeometryPreview;
5379
5427
  mtwPickPreparation: MtwPickPreparation;
5380
5428
  mtwPickReview: MtwPickReview;
@@ -6647,6 +6695,20 @@ export type UpdateInventoryItemInput = {
6647
6695
  unitCost?: InputMaybe<Scalars['Float']['input']>;
6648
6696
  };
6649
6697
 
6698
+ /**
6699
+ * Changes to the workspace's MoveTheWheels defaults. Omitted fields keep their
6700
+ * current value. `timezone: null` (or an empty string) clears the time zone so
6701
+ * each viewer sees their own local time.
6702
+ */
6703
+ export type UpdateMtwModuleSettingsInput = {
6704
+ defaultCurrency?: InputMaybe<Scalars['String']['input']>;
6705
+ defaultDeliveryWindowHours?: InputMaybe<Scalars['Int']['input']>;
6706
+ defaultOrderPriority?: InputMaybe<DeliveryPriority>;
6707
+ distanceUnit?: InputMaybe<MtwDistanceUnit>;
6708
+ timezone?: InputMaybe<Scalars['String']['input']>;
6709
+ weightUnit?: InputMaybe<MtwWeightUnit>;
6710
+ };
6711
+
6650
6712
  export type UpdateRouteInput = {
6651
6713
  actualDistance?: InputMaybe<Scalars['Float']['input']>;
6652
6714
  actualDuration?: InputMaybe<Scalars['Float']['input']>;
@@ -24,8 +24,10 @@ import {
24
24
  Bot,
25
25
  Store,
26
26
  ArrowDownUp,
27
+ Settings,
27
28
  } from 'lucide-react';
28
29
  import { MoveTheWheelsProvider } from './providers/MoveTheWheelsProvider';
30
+ import { ModuleSettingsProvider } from './providers/ModuleSettingsProvider';
29
31
  import { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';
30
32
  import type { NavItem, MoveTheWheelsRootProps } from './types';
31
33
  import { MoveTheWheelsRoutes } from './MoveTheWheelsRoutes';
@@ -305,6 +307,13 @@ const NavRegistrar: FC<Pick<MoveTheWheelsRootProps, 'registerNavItems' | 'basePa
305
307
  icon: <Bot size={16} />,
306
308
  order: 13,
307
309
  },
310
+ {
311
+ id: 'mtw-settings',
312
+ label: tr('movethewheels.root.nav.settings', 'Settings'),
313
+ path: joinPath(basePath, 'settings'),
314
+ icon: <Settings size={16} />,
315
+ order: 99,
316
+ },
308
317
  ],
309
318
  };
310
319
 
@@ -365,9 +374,11 @@ export const MoveTheWheelsRoot: FC<MoveTheWheelsRootProps> = (props) => {
365
374
  <FeatureFlagProvider workspaceId={null} gateway="global" defaultEnabled={true}>
366
375
  <NavRegistrar registerNavItems={registerNavItems} basePath={basePath} />
367
376
  <MoveTheWheelsProvider {...props}>
368
- <div className="movethewheels-root h-full">
369
- <MoveTheWheelsRoutes />
370
- </div>
377
+ <ModuleSettingsProvider>
378
+ <div className="movethewheels-root h-full">
379
+ <MoveTheWheelsRoutes />
380
+ </div>
381
+ </ModuleSettingsProvider>
371
382
  </MoveTheWheelsProvider>
372
383
  </FeatureFlagProvider>
373
384
  );
@@ -9,6 +9,7 @@ import { Routes, Route, useNavigate } from 'react-router';
9
9
  import { lazy, Suspense } from 'react';
10
10
  import { NotFoundPage } from '@burdenoff/fe-libs/shared/pages';
11
11
  import { useMoveTheWheels } from './providers/MoveTheWheelsProvider';
12
+ import { useModuleSettings } from './providers/ModuleSettingsProvider';
12
13
  import { useTr } from './hooks';
13
14
 
14
15
  /**
@@ -73,6 +74,7 @@ const CustomersPage = lazy(() => import('./pages/CustomersPage'));
73
74
  const CustomerDetailsPage = lazy(() => import('./pages/CustomerDetailsPage'));
74
75
  const MarketplacePage = lazy(() => import('./pages/MarketplacePage'));
75
76
  const ImportExportPage = lazy(() => import('./pages/ImportExportPage'));
77
+ const SettingsPage = lazy(() => import('./pages/SettingsPage'));
76
78
  const RoutesPage = lazy(() => import('./pages/RoutesPage'));
77
79
  const RouteDetailsPage = lazy(() => import('./pages/RouteDetailsPage'));
78
80
  const AnalyticsPage = lazy(() => import('./pages/AnalyticsPage'));
@@ -96,6 +98,10 @@ const ConsumedFuelPreviewPage = lazy(() => import('./pages/ConsumedFuelPreviewPa
96
98
  const EmptyReusePage = lazy(() => import('./pages/EmptyReusePage'));
97
99
 
98
100
  export function MoveTheWheelsRoutes() {
101
+ // Subscribing here re-renders every page when the workspace's display
102
+ // defaults load or change, so amounts, units and dates follow them
103
+ // (BOFF-10488). The value itself is read through the shared formatters.
104
+ useModuleSettings();
99
105
  return (
100
106
  <Suspense fallback={<PageLoader />}>
101
107
  <Routes>
@@ -164,6 +170,9 @@ export function MoveTheWheelsRoutes() {
164
170
  {/* Bulk CSV/JSON import and dataset export (BOFF-3990) */}
165
171
  <Route path="import-export" element={<ImportExportPage />} />
166
172
 
173
+ {/* Workspace operational defaults: currency, units, time zone (BOFF-10488) */}
174
+ <Route path="settings" element={<SettingsPage />} />
175
+
167
176
  {/* Routes */}
168
177
  <Route path="routes" element={<RoutesPage />} />
169
178
  <Route path="routes/trip-utilization" element={<TripUtilizationPage />} />
@@ -4,7 +4,7 @@
4
4
  * Form for creating or editing orders.
5
5
  */
6
6
 
7
- import { useEffect, useState, type ChangeEvent } from 'react';
7
+ import { useEffect, useRef, useState, type ChangeEvent } from 'react';
8
8
  import { useParams, useSearchParams } from 'react-router';
9
9
  import {
10
10
  Package,
@@ -43,6 +43,8 @@ import { useCustomer, useCustomers } from '../hooks/useCustomers';
43
43
  import { useRoute, useRoutes } from '../hooks/useRoutes';
44
44
  import { useProducts } from '../hooks/useInventory';
45
45
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
46
+ import { useModuleSettings } from '../providers/ModuleSettingsProvider';
47
+ import { deliveryWindowValue, orderFormPriority } from '../utils/moduleSettings';
46
48
  import { joinPath, buildCustomerCreatePath } from '../utils/navigation';
47
49
  import { CustomerPicker } from '../components/CustomerPicker';
48
50
  import { EntityCombobox } from '../components/EntityCombobox';
@@ -118,11 +120,16 @@ export default function CreateOrderPage() {
118
120
  });
119
121
 
120
122
  const isEditing = !!orderId && !!order;
123
+ // Workspace defaults for new orders (BOFF-10488).
124
+ const { settings: moduleSettings } = useModuleSettings();
125
+ const defaultPriority = orderFormPriority(moduleSettings.defaultOrderPriority);
126
+ const deliveryWindowHours = moduleSettings.defaultDeliveryWindowHours;
127
+ const priorityTouched = useRef(false);
121
128
 
122
129
  const [selectedCustomer, setSelectedCustomer] = useState<Customer | null>(null);
123
130
  const [formData, setFormData] = useState({
124
131
  customerId: prefilledCustomerId || order?.customerId || '',
125
- priority: order?.priority || 'normal',
132
+ priority: order?.priority || defaultPriority,
126
133
  routeId: order?.routeId || '',
127
134
  pickupAddress: {
128
135
  street: order?.pickupAddress.street || '',
@@ -308,9 +315,19 @@ export default function CreateOrderPage() {
308
315
  };
309
316
 
310
317
  const updateField = (field: string, value: unknown) => {
318
+ if (field === 'priority') priorityTouched.current = true;
311
319
  setFormData((prev) => ({ ...prev, [field]: value }));
312
320
  };
313
321
 
322
+ // The workspace defaults usually arrive after the first render; a new order
323
+ // picks up the default priority unless the user already chose one.
324
+ useEffect(() => {
325
+ if (orderId || priorityTouched.current) return;
326
+ setFormData((prev) =>
327
+ prev.priority === defaultPriority ? prev : { ...prev, priority: defaultPriority }
328
+ );
329
+ }, [orderId, defaultPriority]);
330
+
314
331
  const updateAddress = (
315
332
  type: 'pickupAddress' | 'deliveryAddress',
316
333
  field: string,
@@ -551,7 +568,9 @@ export default function CreateOrderPage() {
551
568
  <SelectItem value="urgent">Urgent</SelectItem>
552
569
  </SelectContent>
553
570
  </Select>
554
- <p className="text-xs text-text-secondary">Normal is selected by default.</p>
571
+ <p className="text-xs text-text-secondary">
572
+ New orders start with this workspace&apos;s default priority.
573
+ </p>
555
574
  </div>
556
575
  </div>
557
576
  </EmphasisPanel>
@@ -879,9 +898,22 @@ export default function CreateOrderPage() {
879
898
  updateField('scheduledDelivery', e.target.value)
880
899
  }
881
900
  />
882
- <p className="text-xs text-text-secondary">
883
- Leave blank to dispatch as soon as a driver is available.
884
- </p>
901
+ <div className="flex flex-wrap items-center gap-2">
902
+ <p className="text-xs text-text-secondary">
903
+ Leave blank to dispatch as soon as a driver is available.
904
+ </p>
905
+ <Button
906
+ type="button"
907
+ variant="link"
908
+ size="sm"
909
+ className="h-auto p-0 text-xs"
910
+ onClick={() =>
911
+ updateField('scheduledDelivery', deliveryWindowValue(deliveryWindowHours))
912
+ }
913
+ >
914
+ Use the default window ({deliveryWindowHours} h from now)
915
+ </Button>
916
+ </div>
885
917
  </div>
886
918
  <div className="space-y-2 lg:col-span-1">
887
919
  <Label htmlFor="specialInstructions">Special instructions</Label>
@@ -20,7 +20,7 @@ import { CTAOverflowMenu, PagePurpose } from '@burdenoff/fe-libs/ui';
20
20
  import { useProducts, useProductStats } from '../hooks/useInventory';
21
21
  import { useDebouncedValue } from '../hooks/useDebouncedValue';
22
22
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
23
- import { formatCurrency } from '../utils/formatters';
23
+ import { formatCurrency, formatWeight } from '../utils/formatters';
24
24
  import { joinPath } from '../utils/navigation';
25
25
  import { useEntityExport } from '../hooks/useEntityExport';
26
26
  import { ExportStatusBanner } from '../components/ExportStatusBanner';
@@ -90,7 +90,7 @@ export default function ProductsPage() {
90
90
  header: 'Weight',
91
91
  accessorKey: 'weight',
92
92
  sortable: true,
93
- cell: (row) => (row.weight !== undefined ? `${row.weight} kg` : 'Not provided'),
93
+ cell: (row) => (row.weight !== undefined ? formatWeight(row.weight) : 'Not provided'),
94
94
  },
95
95
  {
96
96
  id: 'value',