@burdenoff/microfe-movethewheels 2026.626.2 → 2026.703.1
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-C3ovZ0Fh.js} +100 -73
- package/dist/AnalyticsPage-O8SpY-V4.js +215 -0
- package/dist/{CreateOrderPage-CjlnEtk3.js → CreateOrderPage-CB1_-_mN.js} +33 -35
- package/dist/{CustomerDetailsPage-I6cdDHUi.js → CustomerDetailsPage-BaeKEI1D.js} +42 -44
- package/dist/{CustomersPage-CGGfD4ki.js → CustomersPage-DRaLuHMC.js} +26 -28
- package/dist/{DashboardPage-D2HRqwg9.js → DashboardPage-DPjhNfKB.js} +85 -83
- package/dist/{DataTable-CD9ZhpLB.js → DataTable-DmciIKKM.js} +3 -3
- package/dist/{DriverDetailsPage-rg_pGNuP.js → DriverDetailsPage-CSSQTOzn.js} +52 -54
- package/dist/{DriversPage-DKiynISH.js → DriversPage-C2Bk0w4Q.js} +24 -26
- package/dist/{FinancePage-Vg8WPs38.js → FinancePage-lmp0Sdsw.js} +9 -9
- package/dist/FleetPage-DZn5pIqd.js +365 -0
- package/dist/ImportExportPage-BIUJZc7O.js +290 -0
- package/dist/InventoryPage-Cxh1zYJd.js +253 -0
- package/dist/{LiveTrackingPage-DQ1q4Id3.js → LiveTrackingPage-BY1yAkIt.js} +60 -56
- package/dist/MarketplacePage-RMuL17EA.js +247 -0
- package/dist/{MetricCard-B7yMyvMX.js → MetricCard-7aYgzZHK.js} +1 -1
- package/dist/{OrderDetailsPage-CFASgsYX.js → OrderDetailsPage-C2Mf4KSy.js} +83 -85
- package/dist/{OrdersListPage-BHeT8S5o.js → OrdersListPage-BrPBtnhp.js} +31 -33
- package/dist/{PageLayout-D3N8MCTm.js → PageLayout-D4LDHJJq.js} +483 -422
- package/dist/{ProductDetailsPage-CFGcbaHo.js → ProductDetailsPage-CezzzZcj.js} +27 -29
- package/dist/{ProductsPage--xEpbQ8A.js → ProductsPage-I5DilIoY.js} +21 -23
- package/dist/ReportsPage-BDXNmUng.js +284 -0
- package/dist/RouteDetailsPage-BcomL20M.js +263 -0
- package/dist/RoutesPage-lqX9ylHP.js +144 -0
- package/dist/SettingsPage-CQVIOaNs.js +421 -0
- package/dist/StatusBadge-NpTeHVlw.js +152 -0
- package/dist/{TrackingPage-CcRc_dva.js → TrackingPage-BXrjz7V8.js} +59 -61
- package/dist/{VehicleDetailsPage-3YDMPsPs.js → VehicleDetailsPage-DZjLcJgr.js} +40 -42
- package/dist/{VehiclesPage-UlxPWP9K.js → VehiclesPage-6W39XT5W.js} +19 -21
- package/dist/{WarehouseDetailsPage-Db7KeN-d.js → WarehouseDetailsPage-C10pgRGW.js} +38 -40
- package/dist/{WarehousesPage-CuTO-OPp.js → WarehousesPage-CHvBqNaQ.js} +15 -17
- 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 +119 -111
- 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/{trending-up-C-85AwGT.js → trending-up-B7W-GL6P.js} +1 -1
- package/dist/{triangle-alert-BOhfGgmh.js → triangle-alert-BonHFFNz.js} +1 -1
- package/dist/{truck-CtDjNFbi.js → truck-DrlfSpRC.js} +1 -1
- package/dist/{useAnalytics-ph7eTIK6.js → useAnalytics-CgdzUfY_.js} +40 -40
- package/dist/useAssistant-D4v3oxD3.js +84 -0
- package/dist/{useCustomers-bS3a4ytk.js → useCustomers-QrlozybF.js} +24 -24
- package/dist/{useFleet-BdETplNE.js → useFleet-DiAC-IiH.js} +70 -67
- package/dist/useImportExport-BLMnAKCC.js +55 -0
- package/dist/{useInventory-Dwn18FPz.js → useInventory-DKoks37x.js} +33 -33
- package/dist/{useOrders-D_3_hGMp.js → useOrders-D3UZVy8Y.js} +42 -42
- package/dist/useReports-Puq4DCH9.js +78 -0
- package/dist/{useRoutes-v4aBaS-E.js → useRoutes-Do57LvRb.js} +31 -31
- package/dist/useSettings-CkDH_-gM.js +81 -0
- package/dist/{useTracking-De2KIUNu.js → useTracking-GJify4HN.js} +22 -22
- package/dist/{user-Dz5TJZFs.js → user-CjSoudeu.js} +1 -1
- package/dist/{users-g2N0eL-Z.js → users-BAM4gHTn.js} +1 -1
- package/dist/{warehouse-D3WWDeTX.js → warehouse-C7MSr47B.js} +1 -1
- package/dist/{wrench-CmxY1n4I.js → wrench-CANXWxF-.js} +1 -1
- package/package.json +1 -1
- package/src/movethewheels/MoveTheWheelsRoutes.tsx +27 -3
- 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 +63 -59
- package/src/movethewheels/hooks/useImportExport.ts +101 -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 +1 -1
- package/src/movethewheels/pages/CustomersPage.tsx +1 -1
- package/src/movethewheels/pages/DashboardPage.tsx +42 -22
- package/src/movethewheels/pages/DriverDetailsPage.tsx +1 -1
- package/src/movethewheels/pages/DriversPage.tsx +1 -1
- 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/ReportsPage.tsx +194 -90
- package/src/movethewheels/pages/RouteDetailsPage.tsx +5 -11
- 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/providers/MoveTheWheelsProvider.tsx +7 -6
- package/src/movethewheels/store/movethewheelsStore.ts +1 -1
- package/dist/AnalyticsPage-CCLfHgQZ.js +0 -212
- 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/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/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-DhKZ-eA1.js} +0 -0
|
@@ -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>
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
import { BarChart3, TrendingUp, Package, Truck, DollarSign, Users } from 'lucide-react';
|
|
6
6
|
import { PageLayout } from '../components/PageLayout';
|
|
7
|
-
import { MetricCard, MetricGrid } from '../components/MetricCard';
|
|
8
|
-
import { Card, CardContent, CardHeader, CardTitle, Progress } from '../components/ui';
|
|
7
|
+
import { MetricCard, MetricGrid, MetricCardSkeleton } from '../components/MetricCard';
|
|
8
|
+
import { Card, CardContent, CardHeader, CardTitle, Progress, Button } from '../components/ui';
|
|
9
9
|
import { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
10
10
|
import { useAnalytics, useOrderAnalytics, useFleetAnalytics } from '../hooks/useAnalytics';
|
|
11
11
|
import { formatCurrency } from '../utils/formatters';
|
|
12
12
|
|
|
13
13
|
export default function AnalyticsPage() {
|
|
14
|
-
const { analytics } = useAnalytics();
|
|
14
|
+
const { analytics, isLoading, error, refetch } = useAnalytics();
|
|
15
15
|
const { orderData } = useOrderAnalytics();
|
|
16
16
|
const { fleetData } = useFleetAnalytics();
|
|
17
17
|
|
|
@@ -27,43 +27,55 @@ export default function AnalyticsPage() {
|
|
|
27
27
|
to invest — whether that's more vehicles, better routes, or new customers.
|
|
28
28
|
</PagePurpose>
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
30
|
+
{error && !isLoading ? (
|
|
31
|
+
<Card className="border-status-error-border">
|
|
32
|
+
<CardContent className="p-6 text-center space-y-3">
|
|
33
|
+
<p className="text-status-error-text font-medium">Couldn't load analytics</p>
|
|
34
|
+
<p className="text-sm text-text-secondary">{error.message}</p>
|
|
35
|
+
<Button variant="outline" onClick={refetch}>
|
|
36
|
+
Retry
|
|
37
|
+
</Button>
|
|
38
|
+
</CardContent>
|
|
39
|
+
</Card>
|
|
40
|
+
) : isLoading ? (
|
|
41
|
+
<MetricGrid columns={5}>
|
|
42
|
+
{Array.from({ length: 5 }).map((_, i) => (
|
|
43
|
+
<MetricCardSkeleton key={i} />
|
|
44
|
+
))}
|
|
45
|
+
</MetricGrid>
|
|
46
|
+
) : (
|
|
47
|
+
<MetricGrid columns={5}>
|
|
48
|
+
<MetricCard
|
|
49
|
+
label="Total Orders"
|
|
50
|
+
value={analytics.overview.totalOrders.toLocaleString()}
|
|
51
|
+
icon={<Package size={20} />}
|
|
52
|
+
/>
|
|
53
|
+
<MetricCard
|
|
54
|
+
treatment="glass"
|
|
55
|
+
glow
|
|
56
|
+
label="Revenue"
|
|
57
|
+
value={formatCurrency(analytics.overview.totalRevenue)}
|
|
58
|
+
icon={<DollarSign size={20} />}
|
|
59
|
+
/>
|
|
60
|
+
<MetricCard
|
|
61
|
+
label="Active Vehicles"
|
|
62
|
+
value={analytics.overview.activeVehicles}
|
|
63
|
+
icon={<Truck size={20} />}
|
|
64
|
+
/>
|
|
65
|
+
<MetricCard
|
|
66
|
+
label="On-Time Rate"
|
|
67
|
+
value={`${analytics.overview.onTimeDeliveryRate.toFixed(1)}%`}
|
|
68
|
+
icon={<TrendingUp size={20} />}
|
|
69
|
+
changeType={analytics.overview.onTimeDeliveryRate >= 90 ? 'positive' : 'neutral'}
|
|
70
|
+
/>
|
|
71
|
+
<MetricCard
|
|
72
|
+
label="Satisfaction"
|
|
73
|
+
value={analytics.overview.customerSatisfaction.toFixed(1)}
|
|
74
|
+
icon={<Users size={20} />}
|
|
75
|
+
subtitle="out of 5"
|
|
76
|
+
/>
|
|
77
|
+
</MetricGrid>
|
|
78
|
+
)}
|
|
67
79
|
|
|
68
80
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
69
81
|
<Card>
|
|
@@ -149,45 +161,36 @@ export default function AnalyticsPage() {
|
|
|
149
161
|
</div>
|
|
150
162
|
<Progress value={fleetData.vehicleUtilization} />
|
|
151
163
|
</div>
|
|
152
|
-
|
|
153
|
-
<div className="
|
|
154
|
-
<
|
|
155
|
-
|
|
164
|
+
{fleetData.vehiclesByType.length > 0 && (
|
|
165
|
+
<div className="pt-2">
|
|
166
|
+
<p className="text-sm text-text-secondary mb-2">Vehicles by Type</p>
|
|
167
|
+
{fleetData.vehiclesByType.map(({ type, count }) => (
|
|
168
|
+
<div key={type} className="flex justify-between text-sm py-1">
|
|
169
|
+
<span className="capitalize">{type}</span>
|
|
170
|
+
<span className="font-medium">{count}</span>
|
|
171
|
+
</div>
|
|
172
|
+
))}
|
|
156
173
|
</div>
|
|
157
|
-
|
|
158
|
-
</div>
|
|
159
|
-
<div className="pt-2">
|
|
160
|
-
<p className="text-sm text-text-secondary mb-2">Vehicles by Type</p>
|
|
161
|
-
{fleetData.vehiclesByType.map(({ type, count }) => (
|
|
162
|
-
<div key={type} className="flex justify-between text-sm py-1">
|
|
163
|
-
<span className="capitalize">{type}</span>
|
|
164
|
-
<span className="font-medium">{count}</span>
|
|
165
|
-
</div>
|
|
166
|
-
))}
|
|
167
|
-
</div>
|
|
174
|
+
)}
|
|
168
175
|
</CardContent>
|
|
169
176
|
</Card>
|
|
170
177
|
|
|
171
178
|
{/* Performance Metrics — the page's single emphasis zone */}
|
|
172
179
|
<EmphasisPanel className="space-y-4">
|
|
173
180
|
<h2 className="text-base font-semibold text-text-primary">Performance Metrics</h2>
|
|
174
|
-
<div>
|
|
175
|
-
<p className="text-sm text-text-secondary">On-Time Pickup</p>
|
|
176
|
-
<p className="text-2xl font-bold">{analytics.performance.onTimePickup}%</p>
|
|
177
|
-
</div>
|
|
178
181
|
<div>
|
|
179
182
|
<p className="text-sm text-text-secondary">On-Time Delivery</p>
|
|
180
|
-
<p className="text-2xl font-bold">{analytics.performance.onTimeDelivery}%</p>
|
|
183
|
+
<p className="text-2xl font-bold">{analytics.performance.onTimeDelivery.toFixed(1)}%</p>
|
|
181
184
|
</div>
|
|
182
185
|
<div>
|
|
183
|
-
<p className="text-sm text-text-secondary">
|
|
184
|
-
<p className="text-2xl font-bold
|
|
185
|
-
{analytics.
|
|
186
|
+
<p className="text-sm text-text-secondary">Customer Satisfaction</p>
|
|
187
|
+
<p className="text-2xl font-bold">
|
|
188
|
+
{analytics.overview.customerSatisfaction.toFixed(1)}/5
|
|
186
189
|
</p>
|
|
187
190
|
</div>
|
|
188
191
|
<div>
|
|
189
192
|
<p className="text-sm text-text-secondary">Driver Ratings</p>
|
|
190
|
-
<p className="text-2xl font-bold">{analytics.performance.driverRatings}/5</p>
|
|
193
|
+
<p className="text-2xl font-bold">{analytics.performance.driverRatings.toFixed(1)}/5</p>
|
|
191
194
|
</div>
|
|
192
195
|
</EmphasisPanel>
|
|
193
196
|
</div>
|
|
@@ -78,7 +78,7 @@ export default function CustomerDetailsPage() {
|
|
|
78
78
|
{customer.isActive ? 'Active' : 'Inactive'}
|
|
79
79
|
</Badge>
|
|
80
80
|
<div className="flex items-center gap-1">
|
|
81
|
-
<Star size={16} className="text-
|
|
81
|
+
<Star size={16} className="text-status-warning-text fill-current" />
|
|
82
82
|
<span className="font-medium">{customer.rating.toFixed(1)}</span>
|
|
83
83
|
</div>
|
|
84
84
|
</div>
|
|
@@ -59,7 +59,7 @@ export default function CustomersPage() {
|
|
|
59
59
|
header: 'Rating',
|
|
60
60
|
cell: (row) => (
|
|
61
61
|
<div className="flex items-center gap-1">
|
|
62
|
-
<Star size={14} className="text-
|
|
62
|
+
<Star size={14} className="text-status-warning-text fill-current" />
|
|
63
63
|
<span>{row.rating.toFixed(1)}</span>
|
|
64
64
|
</div>
|
|
65
65
|
),
|