@burdenoff/microfe-movethewheels 2026.1031.3 → 2026.1031.5

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 (63) hide show
  1. package/dist/MoveTheWheelsRoot.js +136 -127
  2. package/dist/MoveTheWheelsRoutes.js +143 -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 +18 -5
  48. package/src/movethewheels/MoveTheWheelsRoutes.tsx +19 -4
  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
  59. package/dist/movethewheels/pages/AIAssistantPage.d.ts +0 -22
  60. package/dist/node_modules/lucide-react/dist/esm/icons/message-circle.js +0 -8
  61. package/dist/node_modules/lucide-react/dist/esm/icons/wallet.js +0 -11
  62. package/dist/pages/AIAssistantPage.js +0 -81
  63. package/src/movethewheels/pages/AIAssistantPage.tsx +0 -123
@@ -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.5",
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';
@@ -300,11 +302,20 @@ const NavRegistrar: FC<Pick<MoveTheWheelsRootProps, 'registerNavItems' | 'basePa
300
302
  },
301
303
  {
302
304
  id: 'mtw-ai-assistant',
303
- label: tr('movethewheels.root.nav.aiAssistant', 'AI Assistant'),
304
- path: joinPath(basePath, 'ai-assistant'),
305
+ label: tr('movethewheels.root.nav.aiOperations', 'AI Operations'),
306
+ // Shell-level shared AI operations page (fe-libs AiPage), not an
307
+ // MFE route — see BOFF-3897.
308
+ path: '/ai',
305
309
  icon: <Bot size={16} />,
306
310
  order: 13,
307
311
  },
312
+ {
313
+ id: 'mtw-settings',
314
+ label: tr('movethewheels.root.nav.settings', 'Settings'),
315
+ path: joinPath(basePath, 'settings'),
316
+ icon: <Settings size={16} />,
317
+ order: 99,
318
+ },
308
319
  ],
309
320
  };
310
321
 
@@ -365,9 +376,11 @@ export const MoveTheWheelsRoot: FC<MoveTheWheelsRootProps> = (props) => {
365
376
  <FeatureFlagProvider workspaceId={null} gateway="global" defaultEnabled={true}>
366
377
  <NavRegistrar registerNavItems={registerNavItems} basePath={basePath} />
367
378
  <MoveTheWheelsProvider {...props}>
368
- <div className="movethewheels-root h-full">
369
- <MoveTheWheelsRoutes />
370
- </div>
379
+ <ModuleSettingsProvider>
380
+ <div className="movethewheels-root h-full">
381
+ <MoveTheWheelsRoutes />
382
+ </div>
383
+ </ModuleSettingsProvider>
371
384
  </MoveTheWheelsProvider>
372
385
  </FeatureFlagProvider>
373
386
  );
@@ -5,10 +5,11 @@
5
5
  * Uses lazy loading for code splitting.
6
6
  */
7
7
 
8
- import { Routes, Route, useNavigate } from 'react-router';
8
+ import { Routes, Route, Navigate, 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'));
@@ -81,7 +83,6 @@ const FreightEstimatePage = lazy(() => import('./pages/FreightEstimatePage'));
81
83
  const FreightInvoicePage = lazy(() => import('./pages/FreightInvoicePage'));
82
84
  const ConsolidationPage = lazy(() => import('./pages/ConsolidationPage'));
83
85
  const LogisticsProceduresPage = lazy(() => import('./pages/LogisticsProceduresPage'));
84
- const AIAssistantPage = lazy(() => import('./pages/AIAssistantPage'));
85
86
  const DeliveryCapturePage = lazy(() => import('./pages/DeliveryCapturePage'));
86
87
  const MultiModalShipmentPage = lazy(() => import('./pages/MultiModalShipmentPage'));
87
88
  const ShippingRatesPage = lazy(() => import('./pages/ShippingRatesPage'));
@@ -96,6 +97,10 @@ const ConsumedFuelPreviewPage = lazy(() => import('./pages/ConsumedFuelPreviewPa
96
97
  const EmptyReusePage = lazy(() => import('./pages/EmptyReusePage'));
97
98
 
98
99
  export function MoveTheWheelsRoutes() {
100
+ // Subscribing here re-renders every page when the workspace's display
101
+ // defaults load or change, so amounts, units and dates follow them
102
+ // (BOFF-10488). The value itself is read through the shared formatters.
103
+ useModuleSettings();
99
104
  return (
100
105
  <Suspense fallback={<PageLoader />}>
101
106
  <Routes>
@@ -164,6 +169,9 @@ export function MoveTheWheelsRoutes() {
164
169
  {/* Bulk CSV/JSON import and dataset export (BOFF-3990) */}
165
170
  <Route path="import-export" element={<ImportExportPage />} />
166
171
 
172
+ {/* Workspace operational defaults: currency, units, time zone (BOFF-10488) */}
173
+ <Route path="settings" element={<SettingsPage />} />
174
+
167
175
  {/* Routes */}
168
176
  <Route path="routes" element={<RoutesPage />} />
169
177
  <Route path="routes/trip-utilization" element={<TripUtilizationPage />} />
@@ -180,8 +188,15 @@ export function MoveTheWheelsRoutes() {
180
188
  <Route path="finance/freight-estimates" element={<FreightEstimatePage />} />
181
189
  <Route path="finance/freight-invoice-comparisons" element={<FreightInvoicePage />} />
182
190
 
183
- {/* AI Assistant */}
184
- <Route path="ai-assistant" element={<AIAssistantPage />} />
191
+ {/*
192
+ AI Operations lives on the shell's shared /ai page (fe-libs AiPage:
193
+ per-product AI credits, recorded usage by model / surface / trigger,
194
+ and the workspace model + provider catalog). The old ai-assistant
195
+ page advertised capabilities the backend never had (BOFF-3897), so
196
+ this path only redirects existing links and bookmarks there. Chat
197
+ stays on the shell's AssistantWidget.
198
+ */}
199
+ <Route path="ai-assistant" element={<Navigate to="/ai" replace />} />
185
200
 
186
201
  {/* Catch-all - friendly 404 */}
187
202
  <Route path="*" element={<MoveTheWheelsNotFound />} />
@@ -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>