@burdenoff/microfe-movethewheels 2026.626.2 → 2026.703.2

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 (135) hide show
  1. package/dist/{AIAssistantPage-AvsMbunM.js → AIAssistantPage-C3cPsWt-.js} +100 -73
  2. package/dist/AnalyticsPage-Dz6g2MvK.js +215 -0
  3. package/dist/{CreateOrderPage-CjlnEtk3.js → CreateOrderPage-C5iwnjp4.js} +36 -67
  4. package/dist/CustomerDetailsPage-B6nwFyLO.js +406 -0
  5. package/dist/{CustomersPage-CGGfD4ki.js → CustomersPage-2Y05LVyh.js} +26 -28
  6. package/dist/{DashboardPage-D2HRqwg9.js → DashboardPage-BVjQi0EB.js} +85 -83
  7. package/dist/{DataTable-CD9ZhpLB.js → DataTable-DmciIKKM.js} +3 -3
  8. package/dist/DriverDetailsPage-CN3WQ0B9.js +507 -0
  9. package/dist/{DriversPage-DKiynISH.js → DriversPage-B0Zpx8OQ.js} +25 -27
  10. package/dist/{FinancePage-Vg8WPs38.js → FinancePage-DEDykSOP.js} +9 -9
  11. package/dist/FleetPage-DqkFi3s3.js +365 -0
  12. package/dist/FormError-BlOjnXOq.js +15 -0
  13. package/dist/ImportExportPage-DKJRCezl.js +290 -0
  14. package/dist/InventoryPage-DwfgCUQq.js +253 -0
  15. package/dist/{LiveTrackingPage-DQ1q4Id3.js → LiveTrackingPage-DCX-K-LR.js} +60 -56
  16. package/dist/MarketplacePage-CIQ97_zp.js +247 -0
  17. package/dist/{MetricCard-B7yMyvMX.js → MetricCard-BY0QrMD4.js} +1 -1
  18. package/dist/{OrderDetailsPage-CFASgsYX.js → OrderDetailsPage-DYiZ-9UK.js} +83 -85
  19. package/dist/{OrdersListPage-BHeT8S5o.js → OrdersListPage-BjDlCKJd.js} +31 -33
  20. package/dist/{PageLayout-D3N8MCTm.js → PageLayout-D4LDHJJq.js} +483 -422
  21. package/dist/ProductDetailsPage-C9uOiYLp.js +350 -0
  22. package/dist/{ProductsPage--xEpbQ8A.js → ProductsPage-C1G9h7Xd.js} +22 -24
  23. package/dist/ReportsPage-Zp53q3_8.js +284 -0
  24. package/dist/RouteDetailsPage-DUStXSf9.js +531 -0
  25. package/dist/RoutesPage-DMfoicM1.js +144 -0
  26. package/dist/SettingsPage-Bn-v0mbQ.js +421 -0
  27. package/dist/StatusBadge-BZurYHMy.js +152 -0
  28. package/dist/{TrackingPage-CcRc_dva.js → TrackingPage-W-jtdM3Y.js} +59 -61
  29. package/dist/VehicleDetailsPage-Bceo5oz3.js +410 -0
  30. package/dist/{VehiclesPage-UlxPWP9K.js → VehiclesPage-VBtOep91.js} +20 -22
  31. package/dist/WarehouseDetailsPage-L_6sWigM.js +493 -0
  32. package/dist/{WarehousesPage-CuTO-OPp.js → WarehousesPage-DCbXXXWE.js} +16 -18
  33. package/dist/{arrow-left-CBpZKkpq.js → arrow-left-n48Y-Wis.js} +1 -1
  34. package/dist/{box-CAaiElsb.js → box-D9Pal4HM.js} +1 -1
  35. package/dist/{chart-column-D1Ym15-S.js → chart-column-BFIJysDR.js} +1 -1
  36. package/dist/{chevron-right-BIFGi0UA.js → chevron-right-EMIIyyJ1.js} +1 -1
  37. package/dist/{circle-alert-DjmNo5_L.js → circle-alert-iY24lSlp.js} +1 -1
  38. package/dist/{circle-check-big-CjuPVo3_.js → circle-check-big-w0TrLKMF.js} +1 -1
  39. package/dist/{clock-BjkdnddD.js → clock-DJZtdl8u.js} +1 -1
  40. package/dist/{dollar-sign-IB4O0wuQ.js → dollar-sign-CgKQRz_e.js} +1 -1
  41. package/dist/{download-Dpa9vqoG.js → download-DRXxoiQR.js} +1 -1
  42. package/dist/{file-text-C8Uioy11.js → file-text-DWWY6_f5.js} +1 -1
  43. package/dist/{filter-Da5OZaHI.js → filter-DloLC2eE.js} +1 -1
  44. package/dist/index.js +186 -162
  45. package/dist/{map-_Per4sT9.js → map-CkJaULb2.js} +1 -1
  46. package/dist/{map-pin-BSaf5IDL.js → map-pin-BGiCvyCs.js} +1 -1
  47. package/dist/{navigation-DaTbrhw2.js → navigation-D36prnAn.js} +1 -1
  48. package/dist/{package-BE7U0wQE.js → package-BChUzpJS.js} +1 -1
  49. package/dist/{phone-DV4StZiA.js → phone-Ac9s3mo_.js} +1 -1
  50. package/dist/{plus-Dl1rLeJR.js → plus-Z76q-FZm.js} +1 -1
  51. package/dist/{refresh-cw-CWfERAit.js → refresh-cw-DKt6f5T6.js} +1 -1
  52. package/dist/{save-XH8vydOn.js → save-CojywNhG.js} +1 -1
  53. package/dist/{search-OxoIiL_C.js → search-CDGefqb3.js} +1 -1
  54. package/dist/{square-pen-hLIbFQFV.js → square-pen-CopE9GmB.js} +1 -1
  55. package/dist/{star-YsVV3MNQ.js → star-nHlLjDKR.js} +1 -1
  56. package/dist/{store-RCBl7HB-.js → store-BR9Voi5E.js} +1 -1
  57. package/dist/trash-2-BKhar6Hs.js +32 -0
  58. package/dist/{trending-up-C-85AwGT.js → trending-up-Dohgd4t_.js} +1 -1
  59. package/dist/{triangle-alert-BOhfGgmh.js → triangle-alert-i-XQL9Rd.js} +1 -1
  60. package/dist/{truck-CtDjNFbi.js → truck-C0v_hbLR.js} +1 -1
  61. package/dist/{useAnalytics-ph7eTIK6.js → useAnalytics-DS8n0xgg.js} +40 -40
  62. package/dist/useAssistant-D4v3oxD3.js +84 -0
  63. package/dist/{useCustomers-bS3a4ytk.js → useCustomers-BmTNGxiI.js} +24 -24
  64. package/dist/{useFleet-BdETplNE.js → useFleet-DKqXZ16m.js} +133 -109
  65. package/dist/useImportExport-DHr-SWSw.js +55 -0
  66. package/dist/{useInventory-Dwn18FPz.js → useInventory-DkyiVY_A.js} +95 -75
  67. package/dist/{useOrders-D_3_hGMp.js → useOrders-Cfk1GLiq.js} +42 -42
  68. package/dist/useReports-CTqZr-AB.js +78 -0
  69. package/dist/{useRoutes-v4aBaS-E.js → useRoutes-BQWO607N.js} +31 -31
  70. package/dist/useSettings-CkDH_-gM.js +81 -0
  71. package/dist/{useTracking-De2KIUNu.js → useTracking-B-va1kO3.js} +22 -22
  72. package/dist/{user-Dz5TJZFs.js → user-jyFU3lXk.js} +1 -1
  73. package/dist/{users-g2N0eL-Z.js → users-DZ-ilS6B.js} +1 -1
  74. package/dist/{warehouse-D3WWDeTX.js → warehouse-6m5kXMLu.js} +1 -1
  75. package/dist/{wrench-CmxY1n4I.js → wrench-CANXWxF-.js} +1 -1
  76. package/package.json +1 -1
  77. package/src/movethewheels/MoveTheWheelsRoutes.tsx +31 -3
  78. package/src/movethewheels/components/FormError.tsx +25 -0
  79. package/src/movethewheels/components/StatusBadge.tsx +18 -37
  80. package/src/movethewheels/constants/index.ts +67 -26
  81. package/src/movethewheels/hooks/index.ts +12 -0
  82. package/src/movethewheels/hooks/useAnalytics.ts +20 -6
  83. package/src/movethewheels/hooks/useAssistant.ts +74 -0
  84. package/src/movethewheels/hooks/useFleet.ts +170 -59
  85. package/src/movethewheels/hooks/useImportExport.ts +101 -0
  86. package/src/movethewheels/hooks/useInventory.ts +107 -0
  87. package/src/movethewheels/hooks/useReports.ts +158 -0
  88. package/src/movethewheels/hooks/useSettings.ts +120 -0
  89. package/src/movethewheels/pages/AIAssistantPage.tsx +53 -15
  90. package/src/movethewheels/pages/AnalyticsPage.tsx +67 -64
  91. package/src/movethewheels/pages/CustomerDetailsPage.tsx +154 -21
  92. package/src/movethewheels/pages/CustomersPage.tsx +1 -1
  93. package/src/movethewheels/pages/DashboardPage.tsx +42 -22
  94. package/src/movethewheels/pages/DriverDetailsPage.tsx +160 -3
  95. package/src/movethewheels/pages/DriversPage.tsx +6 -2
  96. package/src/movethewheels/pages/FleetPage.tsx +96 -38
  97. package/src/movethewheels/pages/ImportExportPage.tsx +174 -68
  98. package/src/movethewheels/pages/InventoryPage.tsx +66 -28
  99. package/src/movethewheels/pages/LiveTrackingPage.tsx +12 -3
  100. package/src/movethewheels/pages/MarketplacePage.tsx +87 -45
  101. package/src/movethewheels/pages/OrderDetailsPage.tsx +2 -2
  102. package/src/movethewheels/pages/ProductDetailsPage.tsx +146 -3
  103. package/src/movethewheels/pages/ProductsPage.tsx +5 -1
  104. package/src/movethewheels/pages/ReportsPage.tsx +194 -90
  105. package/src/movethewheels/pages/RouteDetailsPage.tsx +290 -36
  106. package/src/movethewheels/pages/RoutesPage.tsx +4 -9
  107. package/src/movethewheels/pages/SettingsPage.tsx +234 -56
  108. package/src/movethewheels/pages/TrackingPage.tsx +6 -6
  109. package/src/movethewheels/pages/VehicleDetailsPage.tsx +158 -2
  110. package/src/movethewheels/pages/VehiclesPage.tsx +5 -1
  111. package/src/movethewheels/pages/WarehouseDetailsPage.tsx +224 -2
  112. package/src/movethewheels/pages/WarehousesPage.tsx +5 -1
  113. package/src/movethewheels/providers/MoveTheWheelsProvider.tsx +7 -6
  114. package/src/movethewheels/store/movethewheelsStore.ts +1 -1
  115. package/dist/AnalyticsPage-CCLfHgQZ.js +0 -212
  116. package/dist/CustomerDetailsPage-I6cdDHUi.js +0 -265
  117. package/dist/DriverDetailsPage-rg_pGNuP.js +0 -330
  118. package/dist/FleetPage-DddXt8r8.js +0 -332
  119. package/dist/ImportExportPage-BW6qO3kc.js +0 -248
  120. package/dist/InventoryPage-rMGuaJN-.js +0 -222
  121. package/dist/MarketplacePage-DKrXAv62.js +0 -219
  122. package/dist/ProductDetailsPage-CFGcbaHo.js +0 -195
  123. package/dist/ReportsPage-Bb7_90rK.js +0 -235
  124. package/dist/RouteDetailsPage-CVtDVb1N.js +0 -282
  125. package/dist/RoutesPage-s57PtgcQ.js +0 -163
  126. package/dist/SettingsPage-BYNCBOpN.js +0 -298
  127. package/dist/StatusBadge-CijYQXtb.js +0 -209
  128. package/dist/VehicleDetailsPage-3YDMPsPs.js +0 -220
  129. package/dist/WarehouseDetailsPage-Db7KeN-d.js +0 -238
  130. package/dist/graphqlClient-CdJyR_ed.js +0 -55
  131. package/dist/navigation-BgnOfsVd.js +0 -6
  132. package/dist/route-Dp9JiN_E.js +0 -51
  133. package/dist/settings-3Rw5NwFF.js +0 -28
  134. package/src/movethewheels/constants/mockData.ts +0 -1342
  135. /package/dist/{formatters-_vJlC-47.js → formatters-CF2S2l1m.js} +0 -0
@@ -7,6 +7,29 @@ interface UseInventoryOptions {
7
7
  filters?: InventoryFilter;
8
8
  }
9
9
 
10
+ export interface CreateProductInput {
11
+ name: string;
12
+ sku: string;
13
+ category?: string;
14
+ brand?: string;
15
+ weight?: number;
16
+ }
17
+
18
+ export interface CreateWarehouseInput {
19
+ code: string;
20
+ name: string;
21
+ /** WarehouseType enum value, e.g. `DISTRIBUTION`, `FULFILLMENT`. */
22
+ type: string;
23
+ address: {
24
+ street: string;
25
+ city: string;
26
+ state: string;
27
+ postalCode: string;
28
+ country: string;
29
+ };
30
+ totalCapacity?: number;
31
+ }
32
+
10
33
  interface UseInventoryResult {
11
34
  inventory: Inventory[];
12
35
  products: Product[];
@@ -22,6 +45,8 @@ interface UseInventoryResult {
22
45
  productId: string,
23
46
  quantity: number
24
47
  ) => Promise<void>;
48
+ createProduct: (input: CreateProductInput) => Promise<Product>;
49
+ createWarehouse: (input: CreateWarehouseInput) => Promise<Warehouse>;
25
50
  }
26
51
 
27
52
  interface ApiInventoryItem {
@@ -166,6 +191,42 @@ const INVENTORY_STATS_QUERY = `
166
191
  }
167
192
  `;
168
193
 
194
+ const CREATE_PRODUCT_MUTATION = `
195
+ mutation CreateProduct($input: CreateProductInput!) {
196
+ createProduct(input: $input) {
197
+ id
198
+ sku
199
+ name
200
+ description
201
+ category
202
+ brand
203
+ dimensions
204
+ weight
205
+ value
206
+ hazardous
207
+ fragile
208
+ temperatureControlled
209
+ createdAt
210
+ updatedAt
211
+ }
212
+ }
213
+ `;
214
+
215
+ const CREATE_WAREHOUSE_MUTATION = `
216
+ mutation CreateWarehouse($input: CreateWarehouseInput!) {
217
+ createWarehouse(input: $input) {
218
+ id
219
+ name
220
+ code
221
+ type
222
+ address
223
+ totalCapacity
224
+ usedCapacity
225
+ createdAt
226
+ }
227
+ }
228
+ `;
229
+
169
230
  const ADJUST_INVENTORY_MUTATION = `
170
231
  mutation AdjustInventoryStock($input: AdjustInventoryStockInput!) {
171
232
  adjustInventoryStock(input: $input) {
@@ -412,6 +473,50 @@ export function useInventory(options: UseInventoryOptions = {}): UseInventoryRes
412
473
  [inventory, updateStock]
413
474
  );
414
475
 
476
+ const createProduct = useCallback(
477
+ async (input: CreateProductInput): Promise<Product> => {
478
+ const data = await executeGraphQL<{ createProduct: ApiProduct }>(
479
+ graphQLOptions,
480
+ CREATE_PRODUCT_MUTATION,
481
+ {
482
+ input: {
483
+ name: input.name,
484
+ sku: input.sku,
485
+ category: input.category,
486
+ brand: input.brand,
487
+ weight: input.weight,
488
+ },
489
+ }
490
+ );
491
+ const created = mapProduct(data.createProduct);
492
+ setProducts((previous) => [created, ...previous]);
493
+ return created;
494
+ },
495
+ [graphQLOptions]
496
+ );
497
+
498
+ const createWarehouse = useCallback(
499
+ async (input: CreateWarehouseInput): Promise<Warehouse> => {
500
+ const data = await executeGraphQL<{ createWarehouse: ApiWarehouse }>(
501
+ graphQLOptions,
502
+ CREATE_WAREHOUSE_MUTATION,
503
+ {
504
+ input: {
505
+ code: input.code,
506
+ name: input.name,
507
+ type: input.type,
508
+ address: input.address,
509
+ totalCapacity: input.totalCapacity,
510
+ },
511
+ }
512
+ );
513
+ const created = mapWarehouse(data.createWarehouse);
514
+ setWarehouses((previous) => [created, ...previous]);
515
+ return created;
516
+ },
517
+ [graphQLOptions]
518
+ );
519
+
415
520
  return {
416
521
  inventory,
417
522
  products,
@@ -422,6 +527,8 @@ export function useInventory(options: UseInventoryOptions = {}): UseInventoryRes
422
527
  refetch,
423
528
  updateStock,
424
529
  transferStock,
530
+ createProduct,
531
+ createWarehouse,
425
532
  };
426
533
  }
427
534
 
@@ -0,0 +1,158 @@
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
3
+ import { executeGraphQL } from '../utils/graphqlClient';
4
+
5
+ export interface MovethewheelsReport {
6
+ id: string;
7
+ name: string;
8
+ description?: string | null;
9
+ type: string;
10
+ status: string;
11
+ schedule?: string | null;
12
+ lastGenerated?: string | null;
13
+ createdAt: string;
14
+ updatedAt: string;
15
+ }
16
+
17
+ export interface GeneratedReport {
18
+ generatedAt: string;
19
+ result?: Record<string, unknown> | null;
20
+ report: MovethewheelsReport;
21
+ }
22
+
23
+ const REPORT_FIELDS = `
24
+ id
25
+ name
26
+ description
27
+ type
28
+ status
29
+ schedule
30
+ lastGenerated
31
+ createdAt
32
+ updatedAt
33
+ `;
34
+
35
+ const LIST_REPORTS_QUERY = `
36
+ query MovethewheelsReports($filter: MovethewheelsReportFilterInput, $pagination: PaginationInput) {
37
+ movethewheelsReports(filter: $filter, pagination: $pagination) {
38
+ ${REPORT_FIELDS}
39
+ }
40
+ }
41
+ `;
42
+
43
+ const CREATE_REPORT_MUTATION = `
44
+ mutation CreateMovethewheelsReport($input: CreateMovethewheelsReportInput!) {
45
+ createMovethewheelsReport(input: $input) {
46
+ ${REPORT_FIELDS}
47
+ }
48
+ }
49
+ `;
50
+
51
+ const GENERATE_REPORT_MUTATION = `
52
+ mutation GenerateMovethewheelsReport($input: GenerateMovethewheelsReportInput!) {
53
+ generateMovethewheelsReport(input: $input) {
54
+ generatedAt
55
+ result
56
+ report {
57
+ ${REPORT_FIELDS}
58
+ }
59
+ }
60
+ }
61
+ `;
62
+
63
+ export interface CreateReportInput {
64
+ name: string;
65
+ type: string;
66
+ description?: string;
67
+ schedule?: string;
68
+ status?: string;
69
+ }
70
+
71
+ interface UseReportsResult {
72
+ reports: MovethewheelsReport[];
73
+ isLoading: boolean;
74
+ error: Error | null;
75
+ refetch: () => void;
76
+ createReport: (input: CreateReportInput) => Promise<MovethewheelsReport>;
77
+ generateReport: (
78
+ reportId: string,
79
+ parameters?: Record<string, unknown>
80
+ ) => Promise<GeneratedReport>;
81
+ }
82
+
83
+ export function useReports(): UseReportsResult {
84
+ const { apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser } =
85
+ useMoveTheWheels();
86
+ const [reports, setReports] = useState<MovethewheelsReport[]>([]);
87
+ const [isLoading, setIsLoading] = useState(false);
88
+ const [error, setError] = useState<Error | null>(null);
89
+
90
+ const graphQLOptions = useMemo(
91
+ () => ({
92
+ apiGatewayUrl,
93
+ authToken,
94
+ workspaceToken,
95
+ workspaceId,
96
+ tenantId,
97
+ actorId: currentUser?.id,
98
+ }),
99
+ [apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser?.id]
100
+ );
101
+
102
+ const load = useCallback(async () => {
103
+ setIsLoading(true);
104
+ setError(null);
105
+ try {
106
+ const data = await executeGraphQL<{ movethewheelsReports: MovethewheelsReport[] }>(
107
+ graphQLOptions,
108
+ LIST_REPORTS_QUERY,
109
+ { pagination: { limit: 100, offset: 0 } }
110
+ );
111
+ setReports(data.movethewheelsReports);
112
+ } catch (fetchError) {
113
+ setError(fetchError instanceof Error ? fetchError : new Error('Failed to load reports'));
114
+ } finally {
115
+ setIsLoading(false);
116
+ }
117
+ }, [graphQLOptions]);
118
+
119
+ useEffect(() => {
120
+ void load();
121
+ }, [load]);
122
+
123
+ const refetch = useCallback(() => {
124
+ void load();
125
+ }, [load]);
126
+
127
+ const createReport = useCallback(
128
+ async (input: CreateReportInput): Promise<MovethewheelsReport> => {
129
+ const data = await executeGraphQL<{ createMovethewheelsReport: MovethewheelsReport }>(
130
+ graphQLOptions,
131
+ CREATE_REPORT_MUTATION,
132
+ { input }
133
+ );
134
+ const created = data.createMovethewheelsReport;
135
+ setReports((previous) => [created, ...previous]);
136
+ return created;
137
+ },
138
+ [graphQLOptions]
139
+ );
140
+
141
+ const generateReport = useCallback(
142
+ async (reportId: string, parameters?: Record<string, unknown>): Promise<GeneratedReport> => {
143
+ const data = await executeGraphQL<{ generateMovethewheelsReport: GeneratedReport }>(
144
+ graphQLOptions,
145
+ GENERATE_REPORT_MUTATION,
146
+ { input: { reportId, parameters } }
147
+ );
148
+ const generated = data.generateMovethewheelsReport;
149
+ setReports((previous) =>
150
+ previous.map((item) => (item.id === reportId ? generated.report : item))
151
+ );
152
+ return generated;
153
+ },
154
+ [graphQLOptions]
155
+ );
156
+
157
+ return { reports, isLoading, error, refetch, createReport, generateReport };
158
+ }
@@ -0,0 +1,120 @@
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
3
+ import { executeGraphQL } from '../utils/graphqlClient';
4
+
5
+ export type ModulePreferences = Record<string, unknown>;
6
+
7
+ export interface ModuleSettings {
8
+ preferences: ModulePreferences;
9
+ updatedAt: string;
10
+ workspaceId: string;
11
+ }
12
+
13
+ const MODULE_SETTINGS_QUERY = `
14
+ query MovethewheelsModuleSettings {
15
+ moduleSettings {
16
+ preferences
17
+ updatedAt
18
+ workspaceId
19
+ }
20
+ }
21
+ `;
22
+
23
+ const UPDATE_MODULE_SETTINGS_MUTATION = `
24
+ mutation UpdateMovethewheelsModuleSettings($input: UpdateMovethewheelsModuleSettingsInput!) {
25
+ updateModuleSettings(input: $input) {
26
+ preferences
27
+ updatedAt
28
+ workspaceId
29
+ }
30
+ }
31
+ `;
32
+
33
+ interface UseModuleSettingsResult {
34
+ settings: ModuleSettings | null;
35
+ preferences: ModulePreferences;
36
+ isLoading: boolean;
37
+ isSaving: boolean;
38
+ error: Error | null;
39
+ refetch: () => void;
40
+ savePreferences: (preferences: ModulePreferences) => Promise<ModuleSettings>;
41
+ }
42
+
43
+ export function useModuleSettings(): UseModuleSettingsResult {
44
+ const { apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser } =
45
+ useMoveTheWheels();
46
+ const [settings, setSettings] = useState<ModuleSettings | null>(null);
47
+ const [isLoading, setIsLoading] = useState(false);
48
+ const [isSaving, setIsSaving] = useState(false);
49
+ const [error, setError] = useState<Error | null>(null);
50
+
51
+ const graphQLOptions = useMemo(
52
+ () => ({
53
+ apiGatewayUrl,
54
+ authToken,
55
+ workspaceToken,
56
+ workspaceId,
57
+ tenantId,
58
+ actorId: currentUser?.id,
59
+ }),
60
+ [apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser?.id]
61
+ );
62
+
63
+ const load = useCallback(async () => {
64
+ setIsLoading(true);
65
+ setError(null);
66
+ try {
67
+ const data = await executeGraphQL<{ moduleSettings: ModuleSettings }>(
68
+ graphQLOptions,
69
+ MODULE_SETTINGS_QUERY
70
+ );
71
+ setSettings(data.moduleSettings);
72
+ } catch (fetchError) {
73
+ setError(fetchError instanceof Error ? fetchError : new Error('Failed to load settings'));
74
+ } finally {
75
+ setIsLoading(false);
76
+ }
77
+ }, [graphQLOptions]);
78
+
79
+ useEffect(() => {
80
+ void load();
81
+ }, [load]);
82
+
83
+ const refetch = useCallback(() => {
84
+ void load();
85
+ }, [load]);
86
+
87
+ const savePreferences = useCallback(
88
+ async (preferences: ModulePreferences): Promise<ModuleSettings> => {
89
+ setIsSaving(true);
90
+ setError(null);
91
+ try {
92
+ const data = await executeGraphQL<{ updateModuleSettings: ModuleSettings }>(
93
+ graphQLOptions,
94
+ UPDATE_MODULE_SETTINGS_MUTATION,
95
+ { input: { preferences } }
96
+ );
97
+ setSettings(data.updateModuleSettings);
98
+ return data.updateModuleSettings;
99
+ } catch (fetchError) {
100
+ const normalized =
101
+ fetchError instanceof Error ? fetchError : new Error('Failed to save settings');
102
+ setError(normalized);
103
+ throw normalized;
104
+ } finally {
105
+ setIsSaving(false);
106
+ }
107
+ },
108
+ [graphQLOptions]
109
+ );
110
+
111
+ return {
112
+ settings,
113
+ preferences: settings?.preferences ?? {},
114
+ isLoading,
115
+ isSaving,
116
+ error,
117
+ refetch,
118
+ savePreferences,
119
+ };
120
+ }
@@ -16,14 +16,22 @@ import {
16
16
  ScrollArea,
17
17
  } from '../components/ui';
18
18
  import { GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';
19
+ import { useAssistant } from '../hooks/useAssistant';
20
+
21
+ interface ChatMessage {
22
+ role: 'assistant' | 'user';
23
+ content: string;
24
+ isError?: boolean;
25
+ }
19
26
 
20
27
  export default function AIAssistantPage() {
28
+ const { ask, isLoading } = useAssistant();
21
29
  const [message, setMessage] = useState('');
22
- const [messages, setMessages] = useState([
30
+ const [messages, setMessages] = useState<ChatMessage[]>([
23
31
  {
24
32
  role: 'assistant',
25
33
  content:
26
- "Hello! I'm your logistics AI assistant. I can help you with route optimization, demand forecasting, fleet management insights, and more. How can I help you today?",
34
+ "Hello! I'm your logistics assistant. Ask me about your orders, deliveries, fleet or inventory and I'll answer from your live operational data.",
27
35
  },
28
36
  ]);
29
37
 
@@ -42,17 +50,31 @@ export default function AIAssistantPage() {
42
50
  'Natural Language Queries - Ask questions in plain English',
43
51
  ];
44
52
 
45
- const handleSend = () => {
46
- if (!message.trim()) return;
47
- setMessages([
48
- ...messages,
49
- { role: 'user', content: message },
50
- {
51
- role: 'assistant',
52
- content: `I understand you're asking about "${message}". Let me analyze that for you...\n\nBased on your current data, here are my findings:\n\n1. Your fleet utilization is currently at 78%, which is within the optimal range.\n2. There are 5 pending orders that could benefit from route consolidation.\n3. Estimated fuel savings from optimization: $120/day.\n\nWould you like me to generate a detailed report or take any action?`,
53
- },
54
- ]);
53
+ const submit = async (question: string) => {
54
+ const trimmed = question.trim();
55
+ if (!trimmed || isLoading) return;
55
56
  setMessage('');
57
+ setMessages((previous) => [...previous, { role: 'user', content: trimmed }]);
58
+ try {
59
+ const result = await ask(trimmed);
60
+ setMessages((previous) => [...previous, { role: 'assistant', content: result.answer }]);
61
+ } catch (fetchError) {
62
+ setMessages((previous) => [
63
+ ...previous,
64
+ {
65
+ role: 'assistant',
66
+ content:
67
+ fetchError instanceof Error
68
+ ? `Sorry, I couldn't answer that: ${fetchError.message}`
69
+ : "Sorry, I couldn't reach the assistant. Please try again.",
70
+ isError: true,
71
+ },
72
+ ]);
73
+ }
74
+ };
75
+
76
+ const handleSend = () => {
77
+ void submit(message);
56
78
  };
57
79
 
58
80
  return (
@@ -84,12 +106,26 @@ export default function AIAssistantPage() {
84
106
  className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}
85
107
  >
86
108
  <div
87
- className={`max-w-[80%] p-3 rounded-lg ${msg.role === 'user' ? 'bg-action-primary-bg text-action-primary-text' : 'bg-bg-sunken'}`}
109
+ className={`max-w-[80%] p-3 rounded-lg ${
110
+ msg.role === 'user'
111
+ ? 'bg-action-primary-bg text-action-primary-text'
112
+ : msg.isError
113
+ ? 'bg-status-error-bg-subtle text-status-error-text'
114
+ : 'bg-bg-sunken'
115
+ }`}
88
116
  >
89
117
  <p className="whitespace-pre-line text-sm">{msg.content}</p>
90
118
  </div>
91
119
  </div>
92
120
  ))}
121
+ {isLoading && (
122
+ <div className="flex justify-start" aria-live="polite">
123
+ <div className="max-w-[80%] p-3 rounded-lg bg-bg-sunken flex items-center gap-2">
124
+ <Sparkles size={14} className="text-primary motion-safe:animate-pulse" />
125
+ <span className="text-sm text-text-secondary">Analyzing your data…</span>
126
+ </div>
127
+ </div>
128
+ )}
93
129
  </div>
94
130
  </ScrollArea>
95
131
  <div className="p-4 border-t">
@@ -98,9 +134,10 @@ export default function AIAssistantPage() {
98
134
  value={message}
99
135
  onChange={(e) => setMessage(e.target.value)}
100
136
  placeholder="Ask me anything about your logistics operations..."
137
+ disabled={isLoading}
101
138
  onKeyPress={(e) => e.key === 'Enter' && handleSend()}
102
139
  />
103
- <Button onClick={handleSend}>
140
+ <Button onClick={handleSend} disabled={isLoading || !message.trim()}>
104
141
  <Send size={16} />
105
142
  </Button>
106
143
  </div>
@@ -119,7 +156,8 @@ export default function AIAssistantPage() {
119
156
  key={i}
120
157
  variant="outline"
121
158
  className="w-full justify-start text-left h-auto py-3"
122
- onClick={() => setMessage(s.text)}
159
+ disabled={isLoading}
160
+ onClick={() => void submit(s.text)}
123
161
  >
124
162
  <s.icon size={16} className="mr-2 flex-shrink-0" />
125
163
  <span className="flex-1">{s.text}</span>