@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.
- package/dist/{AIAssistantPage-AvsMbunM.js → AIAssistantPage-C3cPsWt-.js} +100 -73
- package/dist/AnalyticsPage-Dz6g2MvK.js +215 -0
- package/dist/{CreateOrderPage-CjlnEtk3.js → CreateOrderPage-C5iwnjp4.js} +36 -67
- package/dist/CustomerDetailsPage-B6nwFyLO.js +406 -0
- package/dist/{CustomersPage-CGGfD4ki.js → CustomersPage-2Y05LVyh.js} +26 -28
- package/dist/{DashboardPage-D2HRqwg9.js → DashboardPage-BVjQi0EB.js} +85 -83
- package/dist/{DataTable-CD9ZhpLB.js → DataTable-DmciIKKM.js} +3 -3
- package/dist/DriverDetailsPage-CN3WQ0B9.js +507 -0
- package/dist/{DriversPage-DKiynISH.js → DriversPage-B0Zpx8OQ.js} +25 -27
- package/dist/{FinancePage-Vg8WPs38.js → FinancePage-DEDykSOP.js} +9 -9
- package/dist/FleetPage-DqkFi3s3.js +365 -0
- package/dist/FormError-BlOjnXOq.js +15 -0
- package/dist/ImportExportPage-DKJRCezl.js +290 -0
- package/dist/InventoryPage-DwfgCUQq.js +253 -0
- package/dist/{LiveTrackingPage-DQ1q4Id3.js → LiveTrackingPage-DCX-K-LR.js} +60 -56
- package/dist/MarketplacePage-CIQ97_zp.js +247 -0
- package/dist/{MetricCard-B7yMyvMX.js → MetricCard-BY0QrMD4.js} +1 -1
- package/dist/{OrderDetailsPage-CFASgsYX.js → OrderDetailsPage-DYiZ-9UK.js} +83 -85
- package/dist/{OrdersListPage-BHeT8S5o.js → OrdersListPage-BjDlCKJd.js} +31 -33
- package/dist/{PageLayout-D3N8MCTm.js → PageLayout-D4LDHJJq.js} +483 -422
- package/dist/ProductDetailsPage-C9uOiYLp.js +350 -0
- package/dist/{ProductsPage--xEpbQ8A.js → ProductsPage-C1G9h7Xd.js} +22 -24
- package/dist/ReportsPage-Zp53q3_8.js +284 -0
- package/dist/RouteDetailsPage-DUStXSf9.js +531 -0
- package/dist/RoutesPage-DMfoicM1.js +144 -0
- package/dist/SettingsPage-Bn-v0mbQ.js +421 -0
- package/dist/StatusBadge-BZurYHMy.js +152 -0
- package/dist/{TrackingPage-CcRc_dva.js → TrackingPage-W-jtdM3Y.js} +59 -61
- package/dist/VehicleDetailsPage-Bceo5oz3.js +410 -0
- package/dist/{VehiclesPage-UlxPWP9K.js → VehiclesPage-VBtOep91.js} +20 -22
- package/dist/WarehouseDetailsPage-L_6sWigM.js +493 -0
- package/dist/{WarehousesPage-CuTO-OPp.js → WarehousesPage-DCbXXXWE.js} +16 -18
- package/dist/{arrow-left-CBpZKkpq.js → arrow-left-n48Y-Wis.js} +1 -1
- package/dist/{box-CAaiElsb.js → box-D9Pal4HM.js} +1 -1
- package/dist/{chart-column-D1Ym15-S.js → chart-column-BFIJysDR.js} +1 -1
- package/dist/{chevron-right-BIFGi0UA.js → chevron-right-EMIIyyJ1.js} +1 -1
- package/dist/{circle-alert-DjmNo5_L.js → circle-alert-iY24lSlp.js} +1 -1
- package/dist/{circle-check-big-CjuPVo3_.js → circle-check-big-w0TrLKMF.js} +1 -1
- package/dist/{clock-BjkdnddD.js → clock-DJZtdl8u.js} +1 -1
- package/dist/{dollar-sign-IB4O0wuQ.js → dollar-sign-CgKQRz_e.js} +1 -1
- package/dist/{download-Dpa9vqoG.js → download-DRXxoiQR.js} +1 -1
- package/dist/{file-text-C8Uioy11.js → file-text-DWWY6_f5.js} +1 -1
- package/dist/{filter-Da5OZaHI.js → filter-DloLC2eE.js} +1 -1
- package/dist/index.js +186 -162
- package/dist/{map-_Per4sT9.js → map-CkJaULb2.js} +1 -1
- package/dist/{map-pin-BSaf5IDL.js → map-pin-BGiCvyCs.js} +1 -1
- package/dist/{navigation-DaTbrhw2.js → navigation-D36prnAn.js} +1 -1
- package/dist/{package-BE7U0wQE.js → package-BChUzpJS.js} +1 -1
- package/dist/{phone-DV4StZiA.js → phone-Ac9s3mo_.js} +1 -1
- package/dist/{plus-Dl1rLeJR.js → plus-Z76q-FZm.js} +1 -1
- package/dist/{refresh-cw-CWfERAit.js → refresh-cw-DKt6f5T6.js} +1 -1
- package/dist/{save-XH8vydOn.js → save-CojywNhG.js} +1 -1
- package/dist/{search-OxoIiL_C.js → search-CDGefqb3.js} +1 -1
- package/dist/{square-pen-hLIbFQFV.js → square-pen-CopE9GmB.js} +1 -1
- package/dist/{star-YsVV3MNQ.js → star-nHlLjDKR.js} +1 -1
- package/dist/{store-RCBl7HB-.js → store-BR9Voi5E.js} +1 -1
- package/dist/trash-2-BKhar6Hs.js +32 -0
- package/dist/{trending-up-C-85AwGT.js → trending-up-Dohgd4t_.js} +1 -1
- package/dist/{triangle-alert-BOhfGgmh.js → triangle-alert-i-XQL9Rd.js} +1 -1
- package/dist/{truck-CtDjNFbi.js → truck-C0v_hbLR.js} +1 -1
- package/dist/{useAnalytics-ph7eTIK6.js → useAnalytics-DS8n0xgg.js} +40 -40
- package/dist/useAssistant-D4v3oxD3.js +84 -0
- package/dist/{useCustomers-bS3a4ytk.js → useCustomers-BmTNGxiI.js} +24 -24
- package/dist/{useFleet-BdETplNE.js → useFleet-DKqXZ16m.js} +133 -109
- package/dist/useImportExport-DHr-SWSw.js +55 -0
- package/dist/{useInventory-Dwn18FPz.js → useInventory-DkyiVY_A.js} +95 -75
- package/dist/{useOrders-D_3_hGMp.js → useOrders-Cfk1GLiq.js} +42 -42
- package/dist/useReports-CTqZr-AB.js +78 -0
- package/dist/{useRoutes-v4aBaS-E.js → useRoutes-BQWO607N.js} +31 -31
- package/dist/useSettings-CkDH_-gM.js +81 -0
- package/dist/{useTracking-De2KIUNu.js → useTracking-B-va1kO3.js} +22 -22
- package/dist/{user-Dz5TJZFs.js → user-jyFU3lXk.js} +1 -1
- package/dist/{users-g2N0eL-Z.js → users-DZ-ilS6B.js} +1 -1
- package/dist/{warehouse-D3WWDeTX.js → warehouse-6m5kXMLu.js} +1 -1
- package/dist/{wrench-CmxY1n4I.js → wrench-CANXWxF-.js} +1 -1
- package/package.json +1 -1
- package/src/movethewheels/MoveTheWheelsRoutes.tsx +31 -3
- package/src/movethewheels/components/FormError.tsx +25 -0
- package/src/movethewheels/components/StatusBadge.tsx +18 -37
- package/src/movethewheels/constants/index.ts +67 -26
- package/src/movethewheels/hooks/index.ts +12 -0
- package/src/movethewheels/hooks/useAnalytics.ts +20 -6
- package/src/movethewheels/hooks/useAssistant.ts +74 -0
- package/src/movethewheels/hooks/useFleet.ts +170 -59
- package/src/movethewheels/hooks/useImportExport.ts +101 -0
- package/src/movethewheels/hooks/useInventory.ts +107 -0
- package/src/movethewheels/hooks/useReports.ts +158 -0
- package/src/movethewheels/hooks/useSettings.ts +120 -0
- package/src/movethewheels/pages/AIAssistantPage.tsx +53 -15
- package/src/movethewheels/pages/AnalyticsPage.tsx +67 -64
- package/src/movethewheels/pages/CustomerDetailsPage.tsx +154 -21
- package/src/movethewheels/pages/CustomersPage.tsx +1 -1
- package/src/movethewheels/pages/DashboardPage.tsx +42 -22
- package/src/movethewheels/pages/DriverDetailsPage.tsx +160 -3
- package/src/movethewheels/pages/DriversPage.tsx +6 -2
- package/src/movethewheels/pages/FleetPage.tsx +96 -38
- package/src/movethewheels/pages/ImportExportPage.tsx +174 -68
- package/src/movethewheels/pages/InventoryPage.tsx +66 -28
- package/src/movethewheels/pages/LiveTrackingPage.tsx +12 -3
- package/src/movethewheels/pages/MarketplacePage.tsx +87 -45
- package/src/movethewheels/pages/OrderDetailsPage.tsx +2 -2
- package/src/movethewheels/pages/ProductDetailsPage.tsx +146 -3
- package/src/movethewheels/pages/ProductsPage.tsx +5 -1
- package/src/movethewheels/pages/ReportsPage.tsx +194 -90
- package/src/movethewheels/pages/RouteDetailsPage.tsx +290 -36
- package/src/movethewheels/pages/RoutesPage.tsx +4 -9
- package/src/movethewheels/pages/SettingsPage.tsx +234 -56
- package/src/movethewheels/pages/TrackingPage.tsx +6 -6
- package/src/movethewheels/pages/VehicleDetailsPage.tsx +158 -2
- package/src/movethewheels/pages/VehiclesPage.tsx +5 -1
- package/src/movethewheels/pages/WarehouseDetailsPage.tsx +224 -2
- package/src/movethewheels/pages/WarehousesPage.tsx +5 -1
- package/src/movethewheels/providers/MoveTheWheelsProvider.tsx +7 -6
- package/src/movethewheels/store/movethewheelsStore.ts +1 -1
- package/dist/AnalyticsPage-CCLfHgQZ.js +0 -212
- package/dist/CustomerDetailsPage-I6cdDHUi.js +0 -265
- package/dist/DriverDetailsPage-rg_pGNuP.js +0 -330
- package/dist/FleetPage-DddXt8r8.js +0 -332
- package/dist/ImportExportPage-BW6qO3kc.js +0 -248
- package/dist/InventoryPage-rMGuaJN-.js +0 -222
- package/dist/MarketplacePage-DKrXAv62.js +0 -219
- package/dist/ProductDetailsPage-CFGcbaHo.js +0 -195
- package/dist/ReportsPage-Bb7_90rK.js +0 -235
- package/dist/RouteDetailsPage-CVtDVb1N.js +0 -282
- package/dist/RoutesPage-s57PtgcQ.js +0 -163
- package/dist/SettingsPage-BYNCBOpN.js +0 -298
- package/dist/StatusBadge-CijYQXtb.js +0 -209
- package/dist/VehicleDetailsPage-3YDMPsPs.js +0 -220
- package/dist/WarehouseDetailsPage-Db7KeN-d.js +0 -238
- package/dist/graphqlClient-CdJyR_ed.js +0 -55
- package/dist/navigation-BgnOfsVd.js +0 -6
- package/dist/route-Dp9JiN_E.js +0 -51
- package/dist/settings-3Rw5NwFF.js +0 -28
- package/src/movethewheels/constants/mockData.ts +0 -1342
- /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
|
|
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
|
|
46
|
-
|
|
47
|
-
|
|
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 ${
|
|
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
|
-
|
|
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>
|