@burdenoff/microfe-movethewheels 2026.1031.2 → 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 (74) hide show
  1. package/dist/MoveTheWheelsRoot.js +121 -104
  2. package/dist/MoveTheWheelsRoutes.js +142 -133
  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 +140 -0
  9. package/dist/hooks/useEntityImport.js +68 -0
  10. package/dist/hooks/useOrderDispatch.js +17 -17
  11. package/dist/movethewheels/hooks/useEntityImport.d.ts +19 -0
  12. package/dist/movethewheels/pages/ImportExportPage.d.ts +9 -0
  13. package/dist/movethewheels/pages/SettingsPage.d.ts +1 -0
  14. package/dist/movethewheels/providers/ModuleSettingsProvider.d.ts +17 -0
  15. package/dist/movethewheels/utils/entityImport.d.ts +109 -0
  16. package/dist/movethewheels/utils/formatters.d.ts +23 -3
  17. package/dist/movethewheels/utils/moduleSettings.d.ts +52 -0
  18. package/dist/node_modules/lucide-react/dist/esm/icons/arrow-down-up.js +22 -0
  19. package/dist/node_modules/lucide-react/dist/esm/icons/bell.js +11 -0
  20. package/dist/node_modules/lucide-react/dist/esm/icons/file-spreadsheet.js +30 -0
  21. package/dist/node_modules/lucide-react/dist/esm/icons/file-up.js +22 -0
  22. package/dist/node_modules/lucide-react/dist/esm/icons/key-round.js +14 -0
  23. package/dist/node_modules/lucide-react/dist/esm/icons/plug.js +22 -0
  24. package/dist/node_modules/lucide-react/dist/esm/icons/rotate-ccw.js +11 -0
  25. package/dist/node_modules/lucide-react/dist/esm/icons/settings.js +13 -0
  26. package/dist/pages/AnalyticsPage.js +27 -27
  27. package/dist/pages/CreateOrderPage.js +340 -317
  28. package/dist/pages/CustomerDetailsPage.js +246 -246
  29. package/dist/pages/CustomersPage.js +88 -79
  30. package/dist/pages/DashboardPage.js +24 -24
  31. package/dist/pages/DeliveryCapturePage.js +74 -74
  32. package/dist/pages/DriverDetailsPage.js +337 -337
  33. package/dist/pages/DriversPage.js +119 -110
  34. package/dist/pages/FinancePage.js +26 -26
  35. package/dist/pages/ImportExportPage.js +435 -0
  36. package/dist/pages/InventoryPage.js +28 -28
  37. package/dist/pages/LiveTrackingPage.js +108 -108
  38. package/dist/pages/MarketplacePage.js +44 -44
  39. package/dist/pages/OrderDetailsPage.js +306 -306
  40. package/dist/pages/OrdersListPage.js +27 -27
  41. package/dist/pages/ProductDetailsPage.js +89 -89
  42. package/dist/pages/ProductsPage.js +86 -77
  43. package/dist/pages/RouteDetailsPage.js +282 -285
  44. package/dist/pages/RoutesPage.js +78 -78
  45. package/dist/pages/SettingsPage.js +459 -0
  46. package/dist/pages/TrackingPage.js +41 -41
  47. package/dist/pages/VehicleDetailsPage.js +207 -207
  48. package/dist/pages/VehiclesPage.js +100 -90
  49. package/dist/pages/WarehouseDetailsPage.js +132 -132
  50. package/dist/pages/WarehousesPage.js +97 -88
  51. package/dist/providers/ModuleSettingsProvider.js +74 -0
  52. package/dist/utils/entityImport.js +73 -0
  53. package/dist/utils/formatters.js +53 -26
  54. package/dist/utils/moduleSettings.js +132 -0
  55. package/package.json +4 -2
  56. package/src/generated/tracking-quality-operations.ts +184 -0
  57. package/src/movethewheels/MoveTheWheelsRoot.tsx +22 -3
  58. package/src/movethewheels/MoveTheWheelsRoutes.tsx +13 -0
  59. package/src/movethewheels/hooks/useEntityImport.ts +108 -0
  60. package/src/movethewheels/pages/CreateOrderPage.tsx +38 -6
  61. package/src/movethewheels/pages/CustomersPage.tsx +6 -1
  62. package/src/movethewheels/pages/DriversPage.tsx +6 -0
  63. package/src/movethewheels/pages/ImportExportPage.tsx +483 -0
  64. package/src/movethewheels/pages/ProductsPage.tsx +16 -3
  65. package/src/movethewheels/pages/RouteDetailsPage.tsx +3 -6
  66. package/src/movethewheels/pages/RoutesPage.tsx +2 -2
  67. package/src/movethewheels/pages/SettingsPage.tsx +516 -0
  68. package/src/movethewheels/pages/VehicleDetailsPage.tsx +4 -2
  69. package/src/movethewheels/pages/VehiclesPage.tsx +8 -1
  70. package/src/movethewheels/pages/WarehousesPage.tsx +6 -1
  71. package/src/movethewheels/providers/ModuleSettingsProvider.tsx +133 -0
  72. package/src/movethewheels/utils/entityImport.ts +239 -0
  73. package/src/movethewheels/utils/formatters.ts +69 -10
  74. package/src/movethewheels/utils/moduleSettings.ts +224 -0
@@ -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.2",
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: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",
@@ -73,6 +73,8 @@
73
73
  "verify:tracking-alerts": "bun test scripts/check-tracking-alerts.test.ts",
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
+ "verify:entity-import": "bun test scripts/check-entity-import.test.ts",
77
+ "verify:module-settings": "bun test scripts/check-module-settings.test.ts",
76
78
  "verify:pallet-preview": "bun test scripts/check-pallet-preview.test.ts && bun scripts/verify-pallet-preview-hook.mjs",
77
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"
78
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']>;
@@ -2443,6 +2450,91 @@ export type MtwFuelVolumeObservationInput = {
2443
2450
  sourceRevision?: InputMaybe<Scalars['String']['input']>;
2444
2451
  };
2445
2452
 
2453
+ export type MtwImportColumn = {
2454
+ __typename?: 'MtwImportColumn';
2455
+ allowedValues?: Maybe<Array<Scalars['String']['output']>>;
2456
+ description: Scalars['String']['output'];
2457
+ max?: Maybe<Scalars['Float']['output']>;
2458
+ maxLength?: Maybe<Scalars['Int']['output']>;
2459
+ min?: Maybe<Scalars['Float']['output']>;
2460
+ name: Scalars['String']['output'];
2461
+ required: Scalars['Boolean']['output'];
2462
+ type: MtwImportColumnType;
2463
+ };
2464
+
2465
+ export type MtwImportColumnType =
2466
+ | 'BOOLEAN'
2467
+ | 'DATE'
2468
+ | 'EMAIL'
2469
+ | 'ENUM'
2470
+ | 'INTEGER'
2471
+ | 'JSON'
2472
+ | 'NUMBER'
2473
+ | 'STRING';
2474
+
2475
+ export type MtwImportEntitySpec = {
2476
+ __typename?: 'MtwImportEntitySpec';
2477
+ columns: Array<MtwImportColumn>;
2478
+ entityType: MtwImportEntityType;
2479
+ /** Column that must be unique in the workspace, if any. */
2480
+ keyColumn?: Maybe<Scalars['String']['output']>;
2481
+ };
2482
+
2483
+ export type MtwImportEntityType =
2484
+ | 'CARGO_ITEM'
2485
+ | 'CUSTOMER'
2486
+ | 'DRIVER'
2487
+ | 'VEHICLE'
2488
+ | 'WAREHOUSE';
2489
+
2490
+ export type MtwImportFormat =
2491
+ | 'CSV'
2492
+ | 'JSON';
2493
+
2494
+ export type MtwImportInput = {
2495
+ /** Import the valid rows even when other rows are invalid. */
2496
+ allowPartial?: InputMaybe<Scalars['Boolean']['input']>;
2497
+ /** The CSV (with a header row) or JSON (array of objects) document, at most 2 MB and 1000 rows. */
2498
+ content: Scalars['String']['input'];
2499
+ /** Validate every row without writing anything. */
2500
+ dryRun?: InputMaybe<Scalars['Boolean']['input']>;
2501
+ format: MtwImportFormat;
2502
+ };
2503
+
2504
+ export type MtwImportResult = {
2505
+ __typename?: 'MtwImportResult';
2506
+ createdIds: Array<Scalars['ID']['output']>;
2507
+ dryRun: Scalars['Boolean']['output'];
2508
+ entityType: MtwImportEntityType;
2509
+ errors: Array<MtwImportRowError>;
2510
+ /** True when more row errors exist than are returned (the first 500 are returned). */
2511
+ errorsTruncated: Scalars['Boolean']['output'];
2512
+ failedRows: Scalars['Int']['output'];
2513
+ importedRows: Scalars['Int']['output'];
2514
+ status: MtwImportStatus;
2515
+ totalRows: Scalars['Int']['output'];
2516
+ validRows: Scalars['Int']['output'];
2517
+ };
2518
+
2519
+ export type MtwImportRowError = {
2520
+ __typename?: 'MtwImportRowError';
2521
+ field?: Maybe<Scalars['String']['output']>;
2522
+ message: Scalars['String']['output'];
2523
+ /** 1-based data row number (the CSV header is not counted). */
2524
+ row: Scalars['Int']['output'];
2525
+ };
2526
+
2527
+ /**
2528
+ * DRY_RUN: validated only, nothing written. COMPLETED: every row imported.
2529
+ * PARTIAL: allowPartial was set and some rows failed. REJECTED: some rows were
2530
+ * invalid and allowPartial was not set, so nothing was written.
2531
+ */
2532
+ export type MtwImportStatus =
2533
+ | 'COMPLETED'
2534
+ | 'DRY_RUN'
2535
+ | 'PARTIAL'
2536
+ | 'REJECTED';
2537
+
2446
2538
  /** A recorded manual issuer review; workspace privileges do not establish professional qualifications. */
2447
2539
  export type MtwInspectorQualification = {
2448
2540
  __typename?: 'MtwInspectorQualification';
@@ -2615,6 +2707,31 @@ export type MtwMarketplaceStats = {
2615
2707
  verifiedProviders: Scalars['Int']['output'];
2616
2708
  };
2617
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
+
2618
2735
  export type MtwMoveReviewSource = {
2619
2736
  __typename?: 'MtwMoveReviewSource';
2620
2737
  reference: Scalars['String']['output'];
@@ -4467,6 +4584,13 @@ export type MtwWarehouseStats = {
4467
4584
  total: Scalars['Int']['output'];
4468
4585
  };
4469
4586
 
4587
+ /** Unit MoveTheWheels shows weights in. */
4588
+ export type MtwWeightUnit =
4589
+ /** Kilograms */
4590
+ | 'KG'
4591
+ /** Pounds */
4592
+ | 'LB';
4593
+
4470
4594
  export type Mutation = {
4471
4595
  __typename?: 'Mutation';
4472
4596
  _empty?: Maybe<Scalars['String']['output']>;
@@ -4523,6 +4647,11 @@ export type Mutation = {
4523
4647
  deleteWarehouse: Scalars['Boolean']['output'];
4524
4648
  exportMtwEntities: MtwExportJob;
4525
4649
  holdMtwContainer: MtwContainerHold;
4650
+ importMtwCargoItems: MtwImportResult;
4651
+ importMtwCustomers: MtwImportResult;
4652
+ importMtwDrivers: MtwImportResult;
4653
+ importMtwVehicles: MtwImportResult;
4654
+ importMtwWarehouses: MtwImportResult;
4526
4655
  openMtwContainerCycle: MtwContainerCycle;
4527
4656
  openMtwFreeTimeClock: MtwFreeTimeClock;
4528
4657
  optimizeRoute: MtwRoute;
@@ -4568,6 +4697,8 @@ export type Mutation = {
4568
4697
  updateDriverStatus: MtwDriver;
4569
4698
  updateIntegration: MtwIntegration;
4570
4699
  updateInventoryItem: MtwInventoryItem;
4700
+ /** Save the workspace's MoveTheWheels operational defaults. Every field is validated. */
4701
+ updateMtwModuleSettings: MtwModuleSettings;
4571
4702
  updateRoute: MtwRoute;
4572
4703
  updateServiceOffering: MtwServiceOffering;
4573
4704
  updateServiceProvider: MtwServiceProvider;
@@ -4791,6 +4922,31 @@ export type MutationHoldMtwContainerArgs = {
4791
4922
  };
4792
4923
 
4793
4924
 
4925
+ export type MutationImportMtwCargoItemsArgs = {
4926
+ input: MtwImportInput;
4927
+ };
4928
+
4929
+
4930
+ export type MutationImportMtwCustomersArgs = {
4931
+ input: MtwImportInput;
4932
+ };
4933
+
4934
+
4935
+ export type MutationImportMtwDriversArgs = {
4936
+ input: MtwImportInput;
4937
+ };
4938
+
4939
+
4940
+ export type MutationImportMtwVehiclesArgs = {
4941
+ input: MtwImportInput;
4942
+ };
4943
+
4944
+
4945
+ export type MutationImportMtwWarehousesArgs = {
4946
+ input: MtwImportInput;
4947
+ };
4948
+
4949
+
4794
4950
  export type MutationOpenMtwContainerCycleArgs = {
4795
4951
  input: OpenMtwContainerCycleInput;
4796
4952
  };
@@ -5026,6 +5182,11 @@ export type MutationUpdateInventoryItemArgs = {
5026
5182
  };
5027
5183
 
5028
5184
 
5185
+ export type MutationUpdateMtwModuleSettingsArgs = {
5186
+ input: UpdateMtwModuleSettingsInput;
5187
+ };
5188
+
5189
+
5029
5190
  export type MutationUpdateRouteArgs = {
5030
5191
  id: Scalars['ID']['input'];
5031
5192
  input: UpdateRouteInput;
@@ -5253,6 +5414,15 @@ export type Query = {
5253
5414
  /** Null is not proof of no effect and never authorizes resubmission. Exact original actor/context only. */
5254
5415
  mtwFreightInvoiceComparisonByRequestKey?: Maybe<MtwFreightInvoiceComparison>;
5255
5416
  mtwFreightInvoiceComparisonPage: MtwFreightInvoiceComparisonPage;
5417
+ /**
5418
+ * Columns accepted by each import, for building templates and showing guidance.
5419
+ * This is static reference metadata with no workspace data in it, so it only
5420
+ * needs an authenticated caller; every import mutation enforces its own
5421
+ * entity create permission.
5422
+ */
5423
+ mtwImportSpec: Array<MtwImportEntitySpec>;
5424
+ /** The workspace's MoveTheWheels operational defaults; platform defaults when none were saved. */
5425
+ mtwModuleSettings: MtwModuleSettings;
5256
5426
  mtwPalletGeometryPreview: MtwPalletGeometryPreview;
5257
5427
  mtwPickPreparation: MtwPickPreparation;
5258
5428
  mtwPickReview: MtwPickReview;
@@ -6525,6 +6695,20 @@ export type UpdateInventoryItemInput = {
6525
6695
  unitCost?: InputMaybe<Scalars['Float']['input']>;
6526
6696
  };
6527
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
+
6528
6712
  export type UpdateRouteInput = {
6529
6713
  actualDistance?: InputMaybe<Scalars['Float']['input']>;
6530
6714
  actualDuration?: InputMaybe<Scalars['Float']['input']>;
@@ -23,8 +23,11 @@ import {
23
23
  DollarSign,
24
24
  Bot,
25
25
  Store,
26
+ ArrowDownUp,
27
+ Settings,
26
28
  } from 'lucide-react';
27
29
  import { MoveTheWheelsProvider } from './providers/MoveTheWheelsProvider';
30
+ import { ModuleSettingsProvider } from './providers/ModuleSettingsProvider';
28
31
  import { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';
29
32
  import type { NavItem, MoveTheWheelsRootProps } from './types';
30
33
  import { MoveTheWheelsRoutes } from './MoveTheWheelsRoutes';
@@ -241,6 +244,13 @@ const NavRegistrar: FC<Pick<MoveTheWheelsRootProps, 'registerNavItems' | 'basePa
241
244
  icon: <Store size={16} />,
242
245
  order: 6,
243
246
  },
247
+ {
248
+ id: 'mtw-import-export',
249
+ label: tr('movethewheels.root.nav.importExport', 'Import / Export'),
250
+ path: joinPath(basePath, 'import-export'),
251
+ icon: <ArrowDownUp size={16} />,
252
+ order: 6,
253
+ },
244
254
  {
245
255
  id: 'mtw-routes',
246
256
  label: tr('movethewheels.root.nav.routes', 'Routes'),
@@ -297,6 +307,13 @@ const NavRegistrar: FC<Pick<MoveTheWheelsRootProps, 'registerNavItems' | 'basePa
297
307
  icon: <Bot size={16} />,
298
308
  order: 13,
299
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
+ },
300
317
  ],
301
318
  };
302
319
 
@@ -357,9 +374,11 @@ export const MoveTheWheelsRoot: FC<MoveTheWheelsRootProps> = (props) => {
357
374
  <FeatureFlagProvider workspaceId={null} gateway="global" defaultEnabled={true}>
358
375
  <NavRegistrar registerNavItems={registerNavItems} basePath={basePath} />
359
376
  <MoveTheWheelsProvider {...props}>
360
- <div className="movethewheels-root h-full">
361
- <MoveTheWheelsRoutes />
362
- </div>
377
+ <ModuleSettingsProvider>
378
+ <div className="movethewheels-root h-full">
379
+ <MoveTheWheelsRoutes />
380
+ </div>
381
+ </ModuleSettingsProvider>
363
382
  </MoveTheWheelsProvider>
364
383
  </FeatureFlagProvider>
365
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
  /**
@@ -72,6 +73,8 @@ const WarehouseDetailsPage = lazy(() => import('./pages/WarehouseDetailsPage'));
72
73
  const CustomersPage = lazy(() => import('./pages/CustomersPage'));
73
74
  const CustomerDetailsPage = lazy(() => import('./pages/CustomerDetailsPage'));
74
75
  const MarketplacePage = lazy(() => import('./pages/MarketplacePage'));
76
+ const ImportExportPage = lazy(() => import('./pages/ImportExportPage'));
77
+ const SettingsPage = lazy(() => import('./pages/SettingsPage'));
75
78
  const RoutesPage = lazy(() => import('./pages/RoutesPage'));
76
79
  const RouteDetailsPage = lazy(() => import('./pages/RouteDetailsPage'));
77
80
  const AnalyticsPage = lazy(() => import('./pages/AnalyticsPage'));
@@ -95,6 +98,10 @@ const ConsumedFuelPreviewPage = lazy(() => import('./pages/ConsumedFuelPreviewPa
95
98
  const EmptyReusePage = lazy(() => import('./pages/EmptyReusePage'));
96
99
 
97
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();
98
105
  return (
99
106
  <Suspense fallback={<PageLoader />}>
100
107
  <Routes>
@@ -160,6 +167,12 @@ export function MoveTheWheelsRoutes() {
160
167
  {/* Marketplace — service providers and offerings (BOFF-3988) */}
161
168
  <Route path="marketplace" element={<MarketplacePage />} />
162
169
 
170
+ {/* Bulk CSV/JSON import and dataset export (BOFF-3990) */}
171
+ <Route path="import-export" element={<ImportExportPage />} />
172
+
173
+ {/* Workspace operational defaults: currency, units, time zone (BOFF-10488) */}
174
+ <Route path="settings" element={<SettingsPage />} />
175
+
163
176
  {/* Routes */}
164
177
  <Route path="routes" element={<RoutesPage />} />
165
178
  <Route path="routes/trip-utilization" element={<TripUtilizationPage />} />
@@ -0,0 +1,108 @@
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
3
+ import { executeGraphQL } from '../utils/graphqlClient';
4
+ import {
5
+ MTW_IMPORT_MAX_BYTES,
6
+ MTW_IMPORT_SPEC_QUERY,
7
+ importEntity,
8
+ importMutation,
9
+ type MtwImportEntitySpec,
10
+ type MtwImportEntityType,
11
+ type MtwImportFormat,
12
+ type MtwImportResult,
13
+ } from '../utils/entityImport';
14
+
15
+ function useGraphQLOptions() {
16
+ const { apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser } =
17
+ useMoveTheWheels();
18
+ return useMemo(
19
+ () => ({
20
+ apiGatewayUrl,
21
+ authToken,
22
+ workspaceToken,
23
+ workspaceId,
24
+ tenantId,
25
+ actorId: currentUser?.id,
26
+ }),
27
+ [apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser?.id]
28
+ );
29
+ }
30
+
31
+ /** Column specification for every importable entity. */
32
+ export function useImportSpec() {
33
+ const graphQLOptions = useGraphQLOptions();
34
+ const [specs, setSpecs] = useState<MtwImportEntitySpec[]>([]);
35
+ const [isLoading, setIsLoading] = useState(true);
36
+ const [error, setError] = useState<Error | null>(null);
37
+
38
+ useEffect(() => {
39
+ if (!graphQLOptions.workspaceId) return;
40
+ let cancelled = false;
41
+ setIsLoading(true);
42
+ executeGraphQL<{ mtwImportSpec: MtwImportEntitySpec[] }>(graphQLOptions, MTW_IMPORT_SPEC_QUERY)
43
+ .then((data) => {
44
+ if (cancelled) return;
45
+ setSpecs(data.mtwImportSpec);
46
+ setError(null);
47
+ })
48
+ .catch((caught: unknown) => {
49
+ if (!cancelled)
50
+ setError(caught instanceof Error ? caught : new Error('Could not load import columns'));
51
+ })
52
+ .finally(() => {
53
+ if (!cancelled) setIsLoading(false);
54
+ });
55
+ return () => {
56
+ cancelled = true;
57
+ };
58
+ }, [graphQLOptions]);
59
+
60
+ return { specs, isLoading, error };
61
+ }
62
+
63
+ export interface ImportRequest {
64
+ entityType: MtwImportEntityType;
65
+ format: MtwImportFormat;
66
+ content: string;
67
+ dryRun: boolean;
68
+ allowPartial: boolean;
69
+ }
70
+
71
+ /** Runs a validation or an import. Writes are sent once: a retry could create rows twice. */
72
+ export function useEntityImport() {
73
+ const graphQLOptions = useGraphQLOptions();
74
+ const [isRunning, setIsRunning] = useState(false);
75
+
76
+ const run = useCallback(
77
+ async (request: ImportRequest): Promise<MtwImportResult> => {
78
+ if (new Blob([request.content]).size > MTW_IMPORT_MAX_BYTES) {
79
+ throw new Error('The file is larger than 2 MB. Split it into smaller files.');
80
+ }
81
+ const { field } = importEntity(request.entityType);
82
+ setIsRunning(true);
83
+ try {
84
+ const data = await executeGraphQL<Record<string, MtwImportResult>>(
85
+ graphQLOptions,
86
+ importMutation(request.entityType),
87
+ {
88
+ input: {
89
+ format: request.format,
90
+ content: request.content,
91
+ dryRun: request.dryRun,
92
+ allowPartial: request.allowPartial,
93
+ },
94
+ },
95
+ request.dryRun ? 'RETRY_TRANSIENT' : 'SINGLE_ATTEMPT'
96
+ );
97
+ const result = data[field];
98
+ if (!result) throw new Error('The import returned no result');
99
+ return result;
100
+ } finally {
101
+ setIsRunning(false);
102
+ }
103
+ },
104
+ [graphQLOptions]
105
+ );
106
+
107
+ return { run, isRunning };
108
+ }