@burdenoff/microfe-movethewheels 2026.1031.2 → 2026.1031.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/MoveTheWheelsRoot.js +144 -136
  2. package/dist/MoveTheWheelsRoutes.js +61 -57
  3. package/dist/generated/tracking-quality-operations.d.ts +84 -0
  4. package/dist/hooks/useEntityImport.js +68 -0
  5. package/dist/movethewheels/hooks/useEntityImport.d.ts +19 -0
  6. package/dist/movethewheels/pages/ImportExportPage.d.ts +9 -0
  7. package/dist/movethewheels/utils/entityImport.d.ts +109 -0
  8. package/dist/node_modules/lucide-react/dist/esm/icons/arrow-down-up.js +22 -0
  9. package/dist/node_modules/lucide-react/dist/esm/icons/file-spreadsheet.js +30 -0
  10. package/dist/node_modules/lucide-react/dist/esm/icons/file-up.js +22 -0
  11. package/dist/pages/CustomersPage.js +88 -79
  12. package/dist/pages/DriversPage.js +115 -106
  13. package/dist/pages/ImportExportPage.js +435 -0
  14. package/dist/pages/ProductsPage.js +85 -76
  15. package/dist/pages/VehiclesPage.js +99 -90
  16. package/dist/pages/WarehousesPage.js +97 -88
  17. package/dist/utils/entityImport.js +73 -0
  18. package/package.json +3 -2
  19. package/src/generated/tracking-quality-operations.ts +122 -0
  20. package/src/movethewheels/MoveTheWheelsRoot.tsx +8 -0
  21. package/src/movethewheels/MoveTheWheelsRoutes.tsx +4 -0
  22. package/src/movethewheels/hooks/useEntityImport.ts +108 -0
  23. package/src/movethewheels/pages/CustomersPage.tsx +6 -1
  24. package/src/movethewheels/pages/DriversPage.tsx +6 -0
  25. package/src/movethewheels/pages/ImportExportPage.tsx +483 -0
  26. package/src/movethewheels/pages/ProductsPage.tsx +14 -1
  27. package/src/movethewheels/pages/VehiclesPage.tsx +6 -0
  28. package/src/movethewheels/pages/WarehousesPage.tsx +6 -1
  29. package/src/movethewheels/utils/entityImport.ts +239 -0
@@ -3,22 +3,23 @@ import { Filter as t } from "../node_modules/lucide-react/dist/esm/icons/filter.
3
3
  import { Gauge as n } from "../node_modules/lucide-react/dist/esm/icons/gauge.js";
4
4
  import { Plus as r } from "../node_modules/lucide-react/dist/esm/icons/plus.js";
5
5
  import { RefreshCw as i } from "../node_modules/lucide-react/dist/esm/icons/refresh-cw.js";
6
- import { Warehouse as a } from "../node_modules/lucide-react/dist/esm/icons/warehouse.js";
7
- import { joinPath as o } from "../utils/navigation.js";
8
- import { useMoveTheWheels as s } from "../providers/MoveTheWheelsProvider.js";
9
- import { useTr as c } from "../shared/hooks/useTr.js";
10
- import { useWarehouseStats as l, useWarehouses as u } from "../hooks/useInventory.js";
6
+ import { Upload as a } from "../node_modules/lucide-react/dist/esm/icons/upload.js";
7
+ import { Warehouse as o } from "../node_modules/lucide-react/dist/esm/icons/warehouse.js";
8
+ import { joinPath as s } from "../utils/navigation.js";
9
+ import { useMoveTheWheels as c } from "../providers/MoveTheWheelsProvider.js";
10
+ import { useTr as l } from "../shared/hooks/useTr.js";
11
+ import { useWarehouseStats as u, useWarehouses as d } from "../hooks/useInventory.js";
11
12
  import "../hooks/index.js";
12
- import { Badge as d, Button as f, Card as p, CardContent as m, Progress as h, Select as g, SelectContent as _, SelectItem as v, SelectTrigger as y, SelectValue as b } from "../components/ui.js";
13
- import { PageLayout as x } from "../components/PageLayout.js";
14
- import { MetricCard as S, MetricGrid as C } from "../components/MetricCard.js";
15
- import { DataTable as w } from "../components/DataTable.js";
16
- import { useDebouncedValue as T } from "../hooks/useDebouncedValue.js";
17
- import { useState as E } from "react";
18
- import { jsx as D, jsxs as O } from "react/jsx-runtime";
19
- import { CTAOverflowMenu as k, PagePurpose as A } from "@burdenoff/fe-libs/ui";
13
+ import { Badge as f, Button as p, Card as m, CardContent as h, Progress as g, Select as _, SelectContent as v, SelectItem as y, SelectTrigger as b, SelectValue as x } from "../components/ui.js";
14
+ import { PageLayout as S } from "../components/PageLayout.js";
15
+ import { MetricCard as C, MetricGrid as w } from "../components/MetricCard.js";
16
+ import { DataTable as T } from "../components/DataTable.js";
17
+ import { useDebouncedValue as E } from "../hooks/useDebouncedValue.js";
18
+ import { useState as D } from "react";
19
+ import { jsx as O, jsxs as k } from "react/jsx-runtime";
20
+ import { CTAOverflowMenu as A, PagePurpose as j } from "@burdenoff/fe-libs/ui";
20
21
  //#region src/movethewheels/pages/WarehousesPage.tsx
21
- var j = [
22
+ var M = [
22
23
  {
23
24
  value: "all",
24
25
  label: "All statuses"
@@ -32,114 +33,122 @@ var j = [
32
33
  label: "Inactive"
33
34
  }
34
35
  ];
35
- function M() {
36
- let M = c(), { basePath: N, navigate: P } = s(), [F, I] = E(0), [L, R] = E(25), [z, B] = E(""), [V, H] = E("all"), U = T(z), { warehouses: W, totalWarehouses: G, hasMore: K, isLoading: q, error: J, refetch: Y } = u({
37
- limit: L,
38
- offset: F * L,
39
- search: U,
40
- status: V === "all" ? void 0 : V
41
- }), X = V !== "all" || z.length > 0, { stats: Z } = l(), Q = (e) => P?.(o(N, e));
42
- return /* @__PURE__ */ O(x, {
36
+ function N() {
37
+ let N = l(), { basePath: P, navigate: F } = c(), [I, L] = D(0), [R, z] = D(25), [B, V] = D(""), [H, U] = D("all"), W = E(B), { warehouses: G, totalWarehouses: K, hasMore: q, isLoading: J, error: Y, refetch: X } = d({
38
+ limit: R,
39
+ offset: I * R,
40
+ search: W,
41
+ status: H === "all" ? void 0 : H
42
+ }), Z = H !== "all" || B.length > 0, { stats: Q } = u(), $ = (e) => F?.(s(P, e));
43
+ return /* @__PURE__ */ k(S, {
43
44
  title: "Warehouses",
44
- subtitle: `${Z.total} warehouses`,
45
- icon: /* @__PURE__ */ D(a, { size: 24 }),
46
- headerActions: /* @__PURE__ */ D(k, {
45
+ subtitle: `${Q.total} warehouses`,
46
+ icon: /* @__PURE__ */ O(o, { size: 24 }),
47
+ headerActions: /* @__PURE__ */ O(A, {
47
48
  primary: {
48
49
  label: "Add Warehouse",
49
- icon: /* @__PURE__ */ D(r, { size: 16 }),
50
- onSelect: () => Q("inventory/warehouses/new")
50
+ icon: /* @__PURE__ */ O(r, { size: 16 }),
51
+ onSelect: () => $("inventory/warehouses/new")
51
52
  },
52
- actions: [{
53
- label: M("movethewheels.palletPreview.title", "Pallet preview"),
54
- onSelect: () => Q("inventory/warehouses/pallet-preview")
55
- }, {
56
- label: "Refresh",
57
- icon: /* @__PURE__ */ D(i, { size: 16 }),
58
- onSelect: () => Y()
59
- }]
53
+ actions: [
54
+ {
55
+ label: N("movethewheels.palletPreview.title", "Pallet preview"),
56
+ onSelect: () => $("inventory/warehouses/pallet-preview")
57
+ },
58
+ {
59
+ label: "Refresh",
60
+ icon: /* @__PURE__ */ O(i, { size: 16 }),
61
+ onSelect: () => X()
62
+ },
63
+ {
64
+ label: "Import",
65
+ icon: /* @__PURE__ */ O(a, { size: 16 }),
66
+ onSelect: () => $("import-export?entity=WAREHOUSE")
67
+ }
68
+ ]
60
69
  }),
61
70
  children: [
62
- /* @__PURE__ */ D(A, {
63
- icon: /* @__PURE__ */ D(a, { size: 18 }),
71
+ /* @__PURE__ */ O(j, {
72
+ icon: /* @__PURE__ */ O(o, { size: 18 }),
64
73
  children: "Your storage and fulfilment sites. Watch space and pallet utilization across each warehouse to balance stock, spot capacity crunches, and decide where to fulfil an order from."
65
74
  }),
66
- /* @__PURE__ */ O("div", { children: [/* @__PURE__ */ D("p", {
75
+ /* @__PURE__ */ k("div", { children: [/* @__PURE__ */ O("p", {
67
76
  className: "mb-3 text-sm font-medium text-text-secondary",
68
77
  children: "Network at a glance"
69
- }), /* @__PURE__ */ O(C, {
78
+ }), /* @__PURE__ */ k(w, {
70
79
  columns: 3,
71
80
  children: [
72
- /* @__PURE__ */ D(S, {
81
+ /* @__PURE__ */ O(C, {
73
82
  label: "Total sites",
74
- value: Z.total,
75
- icon: /* @__PURE__ */ D(a, { size: 20 }),
83
+ value: Q.total,
84
+ icon: /* @__PURE__ */ O(o, { size: 20 }),
76
85
  accent: "brand"
77
86
  }),
78
- /* @__PURE__ */ D(S, {
87
+ /* @__PURE__ */ O(C, {
79
88
  label: "Active",
80
- value: Z.active,
81
- icon: /* @__PURE__ */ D(e, { size: 20 }),
89
+ value: Q.active,
90
+ icon: /* @__PURE__ */ O(e, { size: 20 }),
82
91
  accent: "success",
83
92
  subtitle: "Fulfilling orders"
84
93
  }),
85
- /* @__PURE__ */ D(S, {
94
+ /* @__PURE__ */ O(C, {
86
95
  label: "Avg utilization",
87
- value: Z.total > 0 ? `${Math.round(Z.avgUtilization * 100)}%` : "—",
88
- icon: /* @__PURE__ */ D(n, { size: 20 }),
96
+ value: Q.total > 0 ? `${Math.round(Q.avgUtilization * 100)}%` : "—",
97
+ icon: /* @__PURE__ */ O(n, { size: 20 }),
89
98
  accent: "blue"
90
99
  })
91
100
  ]
92
101
  })] }),
93
- /* @__PURE__ */ D(p, { children: /* @__PURE__ */ D(m, {
102
+ /* @__PURE__ */ O(m, { children: /* @__PURE__ */ O(h, {
94
103
  className: "p-4",
95
- children: /* @__PURE__ */ O("div", {
104
+ children: /* @__PURE__ */ k("div", {
96
105
  className: "flex flex-wrap items-center gap-4",
97
106
  children: [
98
- /* @__PURE__ */ O("div", {
107
+ /* @__PURE__ */ k("div", {
99
108
  className: "flex items-center gap-2",
100
- children: [/* @__PURE__ */ D(t, {
109
+ children: [/* @__PURE__ */ O(t, {
101
110
  size: 16,
102
111
  className: "text-text-secondary"
103
- }), /* @__PURE__ */ D("span", {
112
+ }), /* @__PURE__ */ O("span", {
104
113
  className: "text-sm font-medium",
105
114
  children: "Filters:"
106
115
  })]
107
116
  }),
108
- /* @__PURE__ */ O(g, {
109
- value: V,
117
+ /* @__PURE__ */ k(_, {
118
+ value: H,
110
119
  onValueChange: (e) => {
111
- H(e), I(0);
120
+ U(e), L(0);
112
121
  },
113
- children: [/* @__PURE__ */ D(y, {
122
+ children: [/* @__PURE__ */ O(b, {
114
123
  className: "w-[180px]",
115
- children: /* @__PURE__ */ D(b, { placeholder: "Status" })
116
- }), /* @__PURE__ */ D(_, { children: j.map((e) => /* @__PURE__ */ D(v, {
124
+ children: /* @__PURE__ */ O(x, { placeholder: "Status" })
125
+ }), /* @__PURE__ */ O(v, { children: M.map((e) => /* @__PURE__ */ O(y, {
117
126
  value: e.value,
118
127
  children: e.label
119
128
  }, e.value)) })]
120
129
  }),
121
- X && /* @__PURE__ */ D(f, {
130
+ Z && /* @__PURE__ */ O(p, {
122
131
  variant: "ghost",
123
132
  size: "sm",
124
133
  onClick: () => {
125
- H("all"), B(""), I(0);
134
+ U("all"), V(""), L(0);
126
135
  },
127
136
  children: "Clear filters"
128
137
  })
129
138
  ]
130
139
  })
131
140
  }) }),
132
- /* @__PURE__ */ D(w, {
141
+ /* @__PURE__ */ O(T, {
133
142
  columns: [
134
143
  {
135
144
  id: "name",
136
145
  header: "Warehouse",
137
146
  accessorKey: "name",
138
147
  sortable: !0,
139
- cell: (e) => /* @__PURE__ */ O("div", { children: [/* @__PURE__ */ D("p", {
148
+ cell: (e) => /* @__PURE__ */ k("div", { children: [/* @__PURE__ */ O("p", {
140
149
  className: "font-medium",
141
150
  children: e.name
142
- }), /* @__PURE__ */ D("p", {
151
+ }), /* @__PURE__ */ O("p", {
143
152
  className: "text-sm text-text-secondary",
144
153
  children: e.code
145
154
  })] })
@@ -147,7 +156,7 @@ function M() {
147
156
  {
148
157
  id: "location",
149
158
  header: "Location",
150
- cell: (e) => /* @__PURE__ */ O("div", { children: [/* @__PURE__ */ D("p", { children: [e.address.city, e.address.state].filter(Boolean).join(", ") || "Not provided" }), /* @__PURE__ */ D("p", {
159
+ cell: (e) => /* @__PURE__ */ k("div", { children: [/* @__PURE__ */ O("p", { children: [e.address.city, e.address.state].filter(Boolean).join(", ") || "Not provided" }), /* @__PURE__ */ O("p", {
151
160
  className: "text-sm text-text-secondary",
152
161
  children: e.address.country || "Not provided"
153
162
  })] })
@@ -156,7 +165,7 @@ function M() {
156
165
  id: "type",
157
166
  header: "Type",
158
167
  accessorKey: "type",
159
- cell: (e) => /* @__PURE__ */ D(d, {
168
+ cell: (e) => /* @__PURE__ */ O(f, {
160
169
  variant: "outline",
161
170
  className: "capitalize",
162
171
  children: e.type.replace("_", " ")
@@ -167,15 +176,15 @@ function M() {
167
176
  header: "Utilization",
168
177
  cell: (e) => {
169
178
  let { totalSpace: t, usedSpace: n } = e.capacity, r = typeof t == "number" && t > 0 && typeof n == "number" ? n / t * 100 : void 0;
170
- return r === void 0 ? /* @__PURE__ */ D("span", {
179
+ return r === void 0 ? /* @__PURE__ */ O("span", {
171
180
  className: "text-sm text-text-secondary",
172
181
  children: "Not provided"
173
- }) : /* @__PURE__ */ O("div", {
182
+ }) : /* @__PURE__ */ k("div", {
174
183
  className: "flex items-center gap-2",
175
- children: [/* @__PURE__ */ D(h, {
184
+ children: [/* @__PURE__ */ O(g, {
176
185
  value: r,
177
186
  className: "w-16 h-2"
178
- }), /* @__PURE__ */ O("span", {
187
+ }), /* @__PURE__ */ k("span", {
179
188
  className: "text-sm",
180
189
  children: [r.toFixed(0), "%"]
181
190
  })]
@@ -187,7 +196,7 @@ function M() {
187
196
  header: "Capacity",
188
197
  cell: (e) => {
189
198
  let { totalSpace: t, usedSpace: n } = e.capacity;
190
- return typeof n == "number" && typeof t == "number" ? /* @__PURE__ */ O("p", {
199
+ return typeof n == "number" && typeof t == "number" ? /* @__PURE__ */ k("p", {
191
200
  className: "text-sm",
192
201
  children: [
193
202
  n.toLocaleString(),
@@ -195,7 +204,7 @@ function M() {
195
204
  t.toLocaleString(),
196
205
  " sq ft"
197
206
  ]
198
- }) : /* @__PURE__ */ D("span", {
207
+ }) : /* @__PURE__ */ O("span", {
199
208
  className: "text-sm text-text-secondary",
200
209
  children: "Not provided"
201
210
  });
@@ -206,37 +215,37 @@ function M() {
206
215
  header: "Status",
207
216
  cell: (e) => {
208
217
  let t = e.status?.toUpperCase() === "ACTIVE";
209
- return e.status ? /* @__PURE__ */ D(d, {
218
+ return e.status ? /* @__PURE__ */ O(f, {
210
219
  variant: t ? "default" : "secondary",
211
220
  children: t ? "Active" : "Inactive"
212
- }) : /* @__PURE__ */ D("span", {
221
+ }) : /* @__PURE__ */ O("span", {
213
222
  className: "text-sm text-text-secondary",
214
223
  children: "Not provided"
215
224
  });
216
225
  }
217
226
  }
218
227
  ],
219
- data: W,
220
- isLoading: q,
221
- error: J,
222
- onRetry: Y,
228
+ data: G,
229
+ isLoading: J,
230
+ error: Y,
231
+ onRetry: X,
223
232
  searchable: !0,
224
233
  searchPlaceholder: "Search warehouses...",
225
234
  searchFields: ["name", "code"],
226
- searchQuery: z,
235
+ searchQuery: B,
227
236
  onSearchQueryChange: (e) => {
228
- B(e), I(0);
237
+ V(e), L(0);
229
238
  },
230
- onRowClick: (e) => Q(`inventory/warehouses/${e.id}`),
239
+ onRowClick: (e) => $(`inventory/warehouses/${e.id}`),
231
240
  getRowId: (e) => e.id,
232
- pageIndex: F,
233
- pageSize: L,
234
- onPageChange: I,
241
+ pageIndex: I,
242
+ pageSize: R,
243
+ onPageChange: L,
235
244
  onPageSizeChange: (e) => {
236
- R(e), I(0);
245
+ z(e), L(0);
237
246
  },
238
- totalItems: G,
239
- hasNextPage: K,
247
+ totalItems: K,
248
+ hasNextPage: q,
240
249
  emptyIllustration: "empty-data",
241
250
  emptyTitle: "No warehouses yet",
242
251
  emptyDescription: "Add a warehouse to store inventory and fulfil orders from it."
@@ -245,4 +254,4 @@ function M() {
245
254
  });
246
255
  }
247
256
  //#endregion
248
- export { M as default };
257
+ export { N as default };
@@ -0,0 +1,73 @@
1
+ //#region src/movethewheels/utils/entityImport.ts
2
+ var e = [
3
+ {
4
+ entityType: "CUSTOMER",
5
+ label: "Customers",
6
+ noun: "customer",
7
+ field: "importMtwCustomers"
8
+ },
9
+ {
10
+ entityType: "VEHICLE",
11
+ label: "Vehicles",
12
+ noun: "vehicle",
13
+ field: "importMtwVehicles"
14
+ },
15
+ {
16
+ entityType: "DRIVER",
17
+ label: "Drivers",
18
+ noun: "driver",
19
+ field: "importMtwDrivers"
20
+ },
21
+ {
22
+ entityType: "WAREHOUSE",
23
+ label: "Warehouses",
24
+ noun: "warehouse",
25
+ field: "importMtwWarehouses"
26
+ },
27
+ {
28
+ entityType: "CARGO_ITEM",
29
+ label: "Products",
30
+ noun: "product",
31
+ field: "importMtwCargoItems"
32
+ }
33
+ ], t = 2 * 1024 * 1024, n = 1e3;
34
+ function r(t) {
35
+ return e.some((e) => e.entityType === t);
36
+ }
37
+ function i(t) {
38
+ let n = e.find((e) => e.entityType === t);
39
+ if (!n) throw Error(`Unknown import entity ${t}`);
40
+ return n;
41
+ }
42
+ var a = {
43
+ CUSTOMER: "\n mutation ImportMtwCustomers($input: MtwImportInput!) {\n importMtwCustomers(input: $input) {\n entityType\n status\n dryRun\n totalRows\n validRows\n importedRows\n failedRows\n createdIds\n errorsTruncated\n errors {\n row\n field\n message\n }\n }\n }\n ",
44
+ VEHICLE: "\n mutation ImportMtwVehicles($input: MtwImportInput!) {\n importMtwVehicles(input: $input) {\n entityType\n status\n dryRun\n totalRows\n validRows\n importedRows\n failedRows\n createdIds\n errorsTruncated\n errors {\n row\n field\n message\n }\n }\n }\n ",
45
+ DRIVER: "\n mutation ImportMtwDrivers($input: MtwImportInput!) {\n importMtwDrivers(input: $input) {\n entityType\n status\n dryRun\n totalRows\n validRows\n importedRows\n failedRows\n createdIds\n errorsTruncated\n errors {\n row\n field\n message\n }\n }\n }\n ",
46
+ WAREHOUSE: "\n mutation ImportMtwWarehouses($input: MtwImportInput!) {\n importMtwWarehouses(input: $input) {\n entityType\n status\n dryRun\n totalRows\n validRows\n importedRows\n failedRows\n createdIds\n errorsTruncated\n errors {\n row\n field\n message\n }\n }\n }\n ",
47
+ CARGO_ITEM: "\n mutation ImportMtwCargoItems($input: MtwImportInput!) {\n importMtwCargoItems(input: $input) {\n entityType\n status\n dryRun\n totalRows\n validRows\n importedRows\n failedRows\n createdIds\n errorsTruncated\n errors {\n row\n field\n message\n }\n }\n }\n "
48
+ };
49
+ function o(e) {
50
+ return a[i(e).entityType];
51
+ }
52
+ var s = "\n query MtwImportSpec {\n mtwImportSpec {\n entityType\n keyColumn\n columns {\n name\n type\n required\n description\n allowedValues\n maxLength\n min\n max\n }\n }\n }\n";
53
+ function c(e, t = "") {
54
+ let n = e.toLowerCase();
55
+ return n.endsWith(".csv") ? "CSV" : n.endsWith(".json") ? "JSON" : t.includes("csv") ? "CSV" : t.includes("json") ? "JSON" : null;
56
+ }
57
+ function l(e) {
58
+ return /[",\r\n]/.test(e) ? `"${e.replace(/"/g, "\"\"")}"` : e;
59
+ }
60
+ function u(e) {
61
+ return `${[...e.columns.filter((e) => e.required), ...e.columns.filter((e) => !e.required)].map((e) => l(e.name)).join(",")}\n`;
62
+ }
63
+ function d(e, t) {
64
+ let n = (e) => `${e} ${t}${e === 1 ? "" : "s"}`;
65
+ switch (e.status) {
66
+ case "DRY_RUN": return e.failedRows === 0 ? `All ${n(e.totalRows)} are valid. Nothing was imported yet.` : `${e.validRows} of ${n(e.totalRows)} are valid; ${e.failedRows} need fixing. Nothing was imported.`;
67
+ case "COMPLETED": return `Imported ${n(e.importedRows)}.`;
68
+ case "PARTIAL": return `Imported ${n(e.importedRows)}; ${e.failedRows} row${e.failedRows === 1 ? "" : "s"} failed.`;
69
+ case "REJECTED": return `Nothing was imported: ${e.failedRows} of ${e.totalRows} rows have problems. Fix them, or allow a partial import.`;
70
+ }
71
+ }
72
+ //#endregion
73
+ export { e as MTW_IMPORT_ENTITIES, t as MTW_IMPORT_MAX_BYTES, n as MTW_IMPORT_MAX_ROWS, s as MTW_IMPORT_SPEC_QUERY, u as buildCsvTemplate, c as detectImportFormat, i as importEntity, o as importMutation, r as isImportEntityType, d as summariseImport };
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.3",
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: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,7 @@
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",
76
77
  "verify:pallet-preview": "bun test scripts/check-pallet-preview.test.ts && bun scripts/verify-pallet-preview-hook.mjs",
77
78
  "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
79
  },
@@ -2443,6 +2443,91 @@ export type MtwFuelVolumeObservationInput = {
2443
2443
  sourceRevision?: InputMaybe<Scalars['String']['input']>;
2444
2444
  };
2445
2445
 
2446
+ export type MtwImportColumn = {
2447
+ __typename?: 'MtwImportColumn';
2448
+ allowedValues?: Maybe<Array<Scalars['String']['output']>>;
2449
+ description: Scalars['String']['output'];
2450
+ max?: Maybe<Scalars['Float']['output']>;
2451
+ maxLength?: Maybe<Scalars['Int']['output']>;
2452
+ min?: Maybe<Scalars['Float']['output']>;
2453
+ name: Scalars['String']['output'];
2454
+ required: Scalars['Boolean']['output'];
2455
+ type: MtwImportColumnType;
2456
+ };
2457
+
2458
+ export type MtwImportColumnType =
2459
+ | 'BOOLEAN'
2460
+ | 'DATE'
2461
+ | 'EMAIL'
2462
+ | 'ENUM'
2463
+ | 'INTEGER'
2464
+ | 'JSON'
2465
+ | 'NUMBER'
2466
+ | 'STRING';
2467
+
2468
+ export type MtwImportEntitySpec = {
2469
+ __typename?: 'MtwImportEntitySpec';
2470
+ columns: Array<MtwImportColumn>;
2471
+ entityType: MtwImportEntityType;
2472
+ /** Column that must be unique in the workspace, if any. */
2473
+ keyColumn?: Maybe<Scalars['String']['output']>;
2474
+ };
2475
+
2476
+ export type MtwImportEntityType =
2477
+ | 'CARGO_ITEM'
2478
+ | 'CUSTOMER'
2479
+ | 'DRIVER'
2480
+ | 'VEHICLE'
2481
+ | 'WAREHOUSE';
2482
+
2483
+ export type MtwImportFormat =
2484
+ | 'CSV'
2485
+ | 'JSON';
2486
+
2487
+ export type MtwImportInput = {
2488
+ /** Import the valid rows even when other rows are invalid. */
2489
+ allowPartial?: InputMaybe<Scalars['Boolean']['input']>;
2490
+ /** The CSV (with a header row) or JSON (array of objects) document, at most 2 MB and 1000 rows. */
2491
+ content: Scalars['String']['input'];
2492
+ /** Validate every row without writing anything. */
2493
+ dryRun?: InputMaybe<Scalars['Boolean']['input']>;
2494
+ format: MtwImportFormat;
2495
+ };
2496
+
2497
+ export type MtwImportResult = {
2498
+ __typename?: 'MtwImportResult';
2499
+ createdIds: Array<Scalars['ID']['output']>;
2500
+ dryRun: Scalars['Boolean']['output'];
2501
+ entityType: MtwImportEntityType;
2502
+ errors: Array<MtwImportRowError>;
2503
+ /** True when more row errors exist than are returned (the first 500 are returned). */
2504
+ errorsTruncated: Scalars['Boolean']['output'];
2505
+ failedRows: Scalars['Int']['output'];
2506
+ importedRows: Scalars['Int']['output'];
2507
+ status: MtwImportStatus;
2508
+ totalRows: Scalars['Int']['output'];
2509
+ validRows: Scalars['Int']['output'];
2510
+ };
2511
+
2512
+ export type MtwImportRowError = {
2513
+ __typename?: 'MtwImportRowError';
2514
+ field?: Maybe<Scalars['String']['output']>;
2515
+ message: Scalars['String']['output'];
2516
+ /** 1-based data row number (the CSV header is not counted). */
2517
+ row: Scalars['Int']['output'];
2518
+ };
2519
+
2520
+ /**
2521
+ * DRY_RUN: validated only, nothing written. COMPLETED: every row imported.
2522
+ * PARTIAL: allowPartial was set and some rows failed. REJECTED: some rows were
2523
+ * invalid and allowPartial was not set, so nothing was written.
2524
+ */
2525
+ export type MtwImportStatus =
2526
+ | 'COMPLETED'
2527
+ | 'DRY_RUN'
2528
+ | 'PARTIAL'
2529
+ | 'REJECTED';
2530
+
2446
2531
  /** A recorded manual issuer review; workspace privileges do not establish professional qualifications. */
2447
2532
  export type MtwInspectorQualification = {
2448
2533
  __typename?: 'MtwInspectorQualification';
@@ -4523,6 +4608,11 @@ export type Mutation = {
4523
4608
  deleteWarehouse: Scalars['Boolean']['output'];
4524
4609
  exportMtwEntities: MtwExportJob;
4525
4610
  holdMtwContainer: MtwContainerHold;
4611
+ importMtwCargoItems: MtwImportResult;
4612
+ importMtwCustomers: MtwImportResult;
4613
+ importMtwDrivers: MtwImportResult;
4614
+ importMtwVehicles: MtwImportResult;
4615
+ importMtwWarehouses: MtwImportResult;
4526
4616
  openMtwContainerCycle: MtwContainerCycle;
4527
4617
  openMtwFreeTimeClock: MtwFreeTimeClock;
4528
4618
  optimizeRoute: MtwRoute;
@@ -4791,6 +4881,31 @@ export type MutationHoldMtwContainerArgs = {
4791
4881
  };
4792
4882
 
4793
4883
 
4884
+ export type MutationImportMtwCargoItemsArgs = {
4885
+ input: MtwImportInput;
4886
+ };
4887
+
4888
+
4889
+ export type MutationImportMtwCustomersArgs = {
4890
+ input: MtwImportInput;
4891
+ };
4892
+
4893
+
4894
+ export type MutationImportMtwDriversArgs = {
4895
+ input: MtwImportInput;
4896
+ };
4897
+
4898
+
4899
+ export type MutationImportMtwVehiclesArgs = {
4900
+ input: MtwImportInput;
4901
+ };
4902
+
4903
+
4904
+ export type MutationImportMtwWarehousesArgs = {
4905
+ input: MtwImportInput;
4906
+ };
4907
+
4908
+
4794
4909
  export type MutationOpenMtwContainerCycleArgs = {
4795
4910
  input: OpenMtwContainerCycleInput;
4796
4911
  };
@@ -5253,6 +5368,13 @@ export type Query = {
5253
5368
  /** Null is not proof of no effect and never authorizes resubmission. Exact original actor/context only. */
5254
5369
  mtwFreightInvoiceComparisonByRequestKey?: Maybe<MtwFreightInvoiceComparison>;
5255
5370
  mtwFreightInvoiceComparisonPage: MtwFreightInvoiceComparisonPage;
5371
+ /**
5372
+ * Columns accepted by each import, for building templates and showing guidance.
5373
+ * This is static reference metadata with no workspace data in it, so it only
5374
+ * needs an authenticated caller; every import mutation enforces its own
5375
+ * entity create permission.
5376
+ */
5377
+ mtwImportSpec: Array<MtwImportEntitySpec>;
5256
5378
  mtwPalletGeometryPreview: MtwPalletGeometryPreview;
5257
5379
  mtwPickPreparation: MtwPickPreparation;
5258
5380
  mtwPickReview: MtwPickReview;
@@ -23,6 +23,7 @@ import {
23
23
  DollarSign,
24
24
  Bot,
25
25
  Store,
26
+ ArrowDownUp,
26
27
  } from 'lucide-react';
27
28
  import { MoveTheWheelsProvider } from './providers/MoveTheWheelsProvider';
28
29
  import { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';
@@ -241,6 +242,13 @@ const NavRegistrar: FC<Pick<MoveTheWheelsRootProps, 'registerNavItems' | 'basePa
241
242
  icon: <Store size={16} />,
242
243
  order: 6,
243
244
  },
245
+ {
246
+ id: 'mtw-import-export',
247
+ label: tr('movethewheels.root.nav.importExport', 'Import / Export'),
248
+ path: joinPath(basePath, 'import-export'),
249
+ icon: <ArrowDownUp size={16} />,
250
+ order: 6,
251
+ },
244
252
  {
245
253
  id: 'mtw-routes',
246
254
  label: tr('movethewheels.root.nav.routes', 'Routes'),
@@ -72,6 +72,7 @@ const WarehouseDetailsPage = lazy(() => import('./pages/WarehouseDetailsPage'));
72
72
  const CustomersPage = lazy(() => import('./pages/CustomersPage'));
73
73
  const CustomerDetailsPage = lazy(() => import('./pages/CustomerDetailsPage'));
74
74
  const MarketplacePage = lazy(() => import('./pages/MarketplacePage'));
75
+ const ImportExportPage = lazy(() => import('./pages/ImportExportPage'));
75
76
  const RoutesPage = lazy(() => import('./pages/RoutesPage'));
76
77
  const RouteDetailsPage = lazy(() => import('./pages/RouteDetailsPage'));
77
78
  const AnalyticsPage = lazy(() => import('./pages/AnalyticsPage'));
@@ -160,6 +161,9 @@ export function MoveTheWheelsRoutes() {
160
161
  {/* Marketplace — service providers and offerings (BOFF-3988) */}
161
162
  <Route path="marketplace" element={<MarketplacePage />} />
162
163
 
164
+ {/* Bulk CSV/JSON import and dataset export (BOFF-3990) */}
165
+ <Route path="import-export" element={<ImportExportPage />} />
166
+
163
167
  {/* Routes */}
164
168
  <Route path="routes" element={<RoutesPage />} />
165
169
  <Route path="routes/trip-utilization" element={<TripUtilizationPage />} />