@burdenoff/microfe-movethewheels 2026.1031.3 → 2026.1031.5
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/MoveTheWheelsRoot.js +136 -127
- package/dist/MoveTheWheelsRoutes.js +143 -135
- package/dist/components/DocumentsCard.js +11 -11
- package/dist/components/ProofOfDeliveryCard.js +13 -13
- package/dist/components/ShipmentRiskDialog.js +24 -24
- package/dist/components/ShippingQuoteResults.js +46 -46
- package/dist/components/TrackingAlertList.js +17 -17
- package/dist/generated/tracking-quality-operations.d.ts +56 -0
- package/dist/hooks/useOrderDispatch.js +17 -17
- package/dist/movethewheels/pages/SettingsPage.d.ts +1 -0
- package/dist/movethewheels/providers/ModuleSettingsProvider.d.ts +17 -0
- package/dist/movethewheels/utils/formatters.d.ts +23 -3
- package/dist/movethewheels/utils/moduleSettings.d.ts +52 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/bell.js +11 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/key-round.js +14 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/plug.js +22 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/rotate-ccw.js +11 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/settings.js +13 -0
- package/dist/pages/AnalyticsPage.js +27 -27
- package/dist/pages/CreateOrderPage.js +340 -317
- package/dist/pages/CustomerDetailsPage.js +246 -246
- package/dist/pages/CustomersPage.js +19 -19
- package/dist/pages/DashboardPage.js +24 -24
- package/dist/pages/DeliveryCapturePage.js +74 -74
- package/dist/pages/DriverDetailsPage.js +337 -337
- package/dist/pages/DriversPage.js +26 -26
- package/dist/pages/FinancePage.js +26 -26
- package/dist/pages/InventoryPage.js +28 -28
- package/dist/pages/LiveTrackingPage.js +108 -108
- package/dist/pages/MarketplacePage.js +44 -44
- package/dist/pages/OrderDetailsPage.js +306 -306
- package/dist/pages/OrdersListPage.js +27 -27
- package/dist/pages/ProductDetailsPage.js +89 -89
- package/dist/pages/ProductsPage.js +73 -73
- package/dist/pages/RouteDetailsPage.js +282 -285
- package/dist/pages/RoutesPage.js +78 -78
- package/dist/pages/SettingsPage.js +459 -0
- package/dist/pages/TrackingPage.js +41 -41
- package/dist/pages/VehicleDetailsPage.js +207 -207
- package/dist/pages/VehiclesPage.js +17 -16
- package/dist/pages/WarehouseDetailsPage.js +132 -132
- package/dist/providers/ModuleSettingsProvider.js +74 -0
- package/dist/utils/formatters.js +53 -26
- package/dist/utils/moduleSettings.js +132 -0
- package/package.json +3 -2
- package/src/generated/tracking-quality-operations.ts +62 -0
- package/src/movethewheels/MoveTheWheelsRoot.tsx +18 -5
- package/src/movethewheels/MoveTheWheelsRoutes.tsx +19 -4
- package/src/movethewheels/pages/CreateOrderPage.tsx +38 -6
- package/src/movethewheels/pages/ProductsPage.tsx +2 -2
- package/src/movethewheels/pages/RouteDetailsPage.tsx +3 -6
- package/src/movethewheels/pages/RoutesPage.tsx +2 -2
- package/src/movethewheels/pages/SettingsPage.tsx +516 -0
- package/src/movethewheels/pages/VehicleDetailsPage.tsx +4 -2
- package/src/movethewheels/pages/VehiclesPage.tsx +2 -1
- package/src/movethewheels/providers/ModuleSettingsProvider.tsx +133 -0
- package/src/movethewheels/utils/formatters.ts +69 -10
- package/src/movethewheels/utils/moduleSettings.ts +224 -0
- package/dist/movethewheels/pages/AIAssistantPage.d.ts +0 -22
- package/dist/node_modules/lucide-react/dist/esm/icons/message-circle.js +0 -8
- package/dist/node_modules/lucide-react/dist/esm/icons/wallet.js +0 -11
- package/dist/pages/AIAssistantPage.js +0 -81
- package/src/movethewheels/pages/AIAssistantPage.tsx +0 -123
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module settings provider (BOFF-10488).
|
|
3
|
+
*
|
|
4
|
+
* Loads the workspace's MoveTheWheels operational defaults once per workspace
|
|
5
|
+
* and applies them to the shared formatters, so amounts, distances, weights
|
|
6
|
+
* and dates across every page follow the workspace's currency, units and time
|
|
7
|
+
* zone. Until the settings load — or if they cannot be read — the platform
|
|
8
|
+
* defaults apply, which is exactly what the formatters showed before.
|
|
9
|
+
*/
|
|
10
|
+
import {
|
|
11
|
+
createContext,
|
|
12
|
+
useCallback,
|
|
13
|
+
useContext,
|
|
14
|
+
useEffect,
|
|
15
|
+
useMemo,
|
|
16
|
+
useState,
|
|
17
|
+
type FC,
|
|
18
|
+
type PropsWithChildren,
|
|
19
|
+
} from 'react';
|
|
20
|
+
import { useMoveTheWheels } from './MoveTheWheelsProvider';
|
|
21
|
+
import { executeGraphQL } from '../utils/graphqlClient';
|
|
22
|
+
import { setFormatPreferences } from '../utils/formatters';
|
|
23
|
+
import {
|
|
24
|
+
MTW_MODULE_SETTINGS_QUERY,
|
|
25
|
+
UPDATE_MTW_MODULE_SETTINGS_MUTATION,
|
|
26
|
+
platformDefaults,
|
|
27
|
+
toFormatPreferences,
|
|
28
|
+
type MtwModuleSettings,
|
|
29
|
+
type MtwModuleSettingsInput,
|
|
30
|
+
} from '../utils/moduleSettings';
|
|
31
|
+
|
|
32
|
+
interface ModuleSettingsContextValue {
|
|
33
|
+
settings: MtwModuleSettings;
|
|
34
|
+
/** True once the workspace's settings were read (or the read failed). */
|
|
35
|
+
isLoaded: boolean;
|
|
36
|
+
error: Error | null;
|
|
37
|
+
save: (input: MtwModuleSettingsInput) => Promise<MtwModuleSettings>;
|
|
38
|
+
reload: () => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const ModuleSettingsContext = createContext<ModuleSettingsContextValue | null>(null);
|
|
42
|
+
|
|
43
|
+
export const ModuleSettingsProvider: FC<PropsWithChildren> = ({ children }) => {
|
|
44
|
+
const { apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser } =
|
|
45
|
+
useMoveTheWheels();
|
|
46
|
+
const graphQLOptions = useMemo(
|
|
47
|
+
() => ({
|
|
48
|
+
apiGatewayUrl,
|
|
49
|
+
authToken,
|
|
50
|
+
workspaceToken,
|
|
51
|
+
workspaceId,
|
|
52
|
+
tenantId,
|
|
53
|
+
actorId: currentUser?.id,
|
|
54
|
+
}),
|
|
55
|
+
[apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser?.id]
|
|
56
|
+
);
|
|
57
|
+
const [settings, setSettings] = useState<MtwModuleSettings>(() =>
|
|
58
|
+
platformDefaults(workspaceId ?? '')
|
|
59
|
+
);
|
|
60
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
61
|
+
const [error, setError] = useState<Error | null>(null);
|
|
62
|
+
const [reloadKey, setReloadKey] = useState(0);
|
|
63
|
+
|
|
64
|
+
const apply = useCallback((next: MtwModuleSettings) => {
|
|
65
|
+
setFormatPreferences(toFormatPreferences(next));
|
|
66
|
+
setSettings(next);
|
|
67
|
+
}, []);
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (!graphQLOptions.workspaceId) return;
|
|
71
|
+
let cancelled = false;
|
|
72
|
+
executeGraphQL<{ mtwModuleSettings: MtwModuleSettings }>(
|
|
73
|
+
graphQLOptions,
|
|
74
|
+
MTW_MODULE_SETTINGS_QUERY
|
|
75
|
+
)
|
|
76
|
+
.then((data) => {
|
|
77
|
+
if (cancelled) return;
|
|
78
|
+
apply(data.mtwModuleSettings);
|
|
79
|
+
setError(null);
|
|
80
|
+
})
|
|
81
|
+
.catch((caught: unknown) => {
|
|
82
|
+
if (cancelled) return;
|
|
83
|
+
// Keep the platform defaults: a failed read must not blank any page.
|
|
84
|
+
apply(platformDefaults(graphQLOptions.workspaceId ?? ''));
|
|
85
|
+
setError(caught instanceof Error ? caught : new Error('Could not load settings'));
|
|
86
|
+
})
|
|
87
|
+
.finally(() => {
|
|
88
|
+
if (!cancelled) setIsLoaded(true);
|
|
89
|
+
});
|
|
90
|
+
return () => {
|
|
91
|
+
cancelled = true;
|
|
92
|
+
};
|
|
93
|
+
}, [graphQLOptions, reloadKey, apply]);
|
|
94
|
+
|
|
95
|
+
const save = useCallback(
|
|
96
|
+
async (input: MtwModuleSettingsInput) => {
|
|
97
|
+
const data = await executeGraphQL<{ updateMtwModuleSettings: MtwModuleSettings }>(
|
|
98
|
+
graphQLOptions,
|
|
99
|
+
UPDATE_MTW_MODULE_SETTINGS_MUTATION,
|
|
100
|
+
{ input }
|
|
101
|
+
);
|
|
102
|
+
apply(data.updateMtwModuleSettings);
|
|
103
|
+
setError(null);
|
|
104
|
+
return data.updateMtwModuleSettings;
|
|
105
|
+
},
|
|
106
|
+
[graphQLOptions, apply]
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const reload = useCallback(() => setReloadKey((key) => key + 1), []);
|
|
110
|
+
|
|
111
|
+
const value = useMemo(
|
|
112
|
+
() => ({ settings, isLoaded, error, save, reload }),
|
|
113
|
+
[settings, isLoaded, error, save, reload]
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
return <ModuleSettingsContext.Provider value={value}>{children}</ModuleSettingsContext.Provider>;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The workspace's MoveTheWheels defaults. Outside the provider (stories,
|
|
121
|
+
* isolated component tests) it returns the platform defaults read-only.
|
|
122
|
+
*/
|
|
123
|
+
export function useModuleSettings(): ModuleSettingsContextValue {
|
|
124
|
+
const context = useContext(ModuleSettingsContext);
|
|
125
|
+
if (context) return context;
|
|
126
|
+
return {
|
|
127
|
+
settings: platformDefaults(),
|
|
128
|
+
isLoaded: true,
|
|
129
|
+
error: null,
|
|
130
|
+
save: () => Promise.reject(new Error('Module settings are not available here')),
|
|
131
|
+
reload: () => undefined,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -4,6 +4,46 @@
|
|
|
4
4
|
|
|
5
5
|
import type { Address } from '../types';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Workspace display preferences (BOFF-10488). The module settings provider
|
|
9
|
+
* applies the workspace's saved MoveTheWheels defaults here once they load,
|
|
10
|
+
* so every formatter below honours them without each call site passing them.
|
|
11
|
+
* Until then the platform defaults apply.
|
|
12
|
+
*/
|
|
13
|
+
export interface FormatPreferences {
|
|
14
|
+
/** ISO 4217 code for amounts that carry no currency of their own. */
|
|
15
|
+
currency: string;
|
|
16
|
+
distanceUnit: 'KM' | 'MI';
|
|
17
|
+
weightUnit: 'KG' | 'LB';
|
|
18
|
+
/** IANA zone; null formats in the viewer's own local time. */
|
|
19
|
+
timeZone: string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const DEFAULT_FORMAT_PREFERENCES: Readonly<FormatPreferences> = {
|
|
23
|
+
currency: 'USD',
|
|
24
|
+
distanceUnit: 'KM',
|
|
25
|
+
weightUnit: 'KG',
|
|
26
|
+
timeZone: null,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
let preferences: FormatPreferences = { ...DEFAULT_FORMAT_PREFERENCES };
|
|
30
|
+
|
|
31
|
+
export function setFormatPreferences(next: FormatPreferences): void {
|
|
32
|
+
preferences = { ...next };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getFormatPreferences(): Readonly<FormatPreferences> {
|
|
36
|
+
return preferences;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The workspace time zone for Intl options; undefined means the viewer's local zone. */
|
|
40
|
+
function zone(): { timeZone?: string } {
|
|
41
|
+
return preferences.timeZone ? { timeZone: preferences.timeZone } : {};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const KM_PER_MILE = 1.609344;
|
|
45
|
+
const KG_PER_POUND = 0.45359237;
|
|
46
|
+
|
|
7
47
|
/**
|
|
8
48
|
* Format an address object into a readable string.
|
|
9
49
|
*/
|
|
@@ -19,12 +59,13 @@ export function formatAddress(address: Partial<Address>): string {
|
|
|
19
59
|
}
|
|
20
60
|
|
|
21
61
|
/**
|
|
22
|
-
* Format a number as currency.
|
|
62
|
+
* Format a number as currency. Amounts without their own currency use the
|
|
63
|
+
* workspace default currency.
|
|
23
64
|
*/
|
|
24
|
-
export function formatCurrency(amount: number, currency
|
|
65
|
+
export function formatCurrency(amount: number, currency?: string): string {
|
|
25
66
|
return new Intl.NumberFormat('en-US', {
|
|
26
67
|
style: 'currency',
|
|
27
|
-
currency,
|
|
68
|
+
currency: currency || preferences.currency,
|
|
28
69
|
minimumFractionDigits: 2,
|
|
29
70
|
maximumFractionDigits: 2,
|
|
30
71
|
}).format(amount);
|
|
@@ -50,6 +91,7 @@ export function formatDate(
|
|
|
50
91
|
hour: 'numeric',
|
|
51
92
|
minute: '2-digit',
|
|
52
93
|
hour12: true,
|
|
94
|
+
...zone(),
|
|
53
95
|
}).format(d);
|
|
54
96
|
}
|
|
55
97
|
|
|
@@ -62,6 +104,7 @@ export function formatDate(
|
|
|
62
104
|
hour: 'numeric',
|
|
63
105
|
minute: '2-digit',
|
|
64
106
|
hour12: true,
|
|
107
|
+
...zone(),
|
|
65
108
|
}).format(d);
|
|
66
109
|
}
|
|
67
110
|
|
|
@@ -70,6 +113,7 @@ export function formatDate(
|
|
|
70
113
|
year: 'numeric',
|
|
71
114
|
month: 'short',
|
|
72
115
|
day: 'numeric',
|
|
116
|
+
...zone(),
|
|
73
117
|
}).format(d);
|
|
74
118
|
}
|
|
75
119
|
|
|
@@ -85,6 +129,7 @@ export function formatDateTime(date: Date | string): string {
|
|
|
85
129
|
hour: 'numeric',
|
|
86
130
|
minute: '2-digit',
|
|
87
131
|
hour12: true,
|
|
132
|
+
...zone(),
|
|
88
133
|
}).format(d);
|
|
89
134
|
}
|
|
90
135
|
|
|
@@ -97,6 +142,7 @@ export function formatTime(date: Date | string): string {
|
|
|
97
142
|
hour: 'numeric',
|
|
98
143
|
minute: '2-digit',
|
|
99
144
|
hour12: true,
|
|
145
|
+
...zone(),
|
|
100
146
|
}).format(d);
|
|
101
147
|
}
|
|
102
148
|
|
|
@@ -144,24 +190,37 @@ export function formatPercentage(value: number, decimals = 1): string {
|
|
|
144
190
|
return `${value.toFixed(decimals)}%`;
|
|
145
191
|
}
|
|
146
192
|
|
|
193
|
+
const twoDecimals = new Intl.NumberFormat('en-US', { maximumFractionDigits: 2 });
|
|
194
|
+
const oneDecimal = new Intl.NumberFormat('en-US', { maximumFractionDigits: 1 });
|
|
195
|
+
|
|
147
196
|
/**
|
|
148
|
-
* Format weight
|
|
197
|
+
* Format a weight given in kilograms, in the workspace weight unit: kg (and
|
|
198
|
+
* tonnes from 1,000 kg) or lb.
|
|
149
199
|
*/
|
|
150
200
|
export function formatWeight(kg: number): string {
|
|
151
|
-
if (
|
|
152
|
-
return `${(kg /
|
|
201
|
+
if (preferences.weightUnit === 'LB') {
|
|
202
|
+
return `${twoDecimals.format(kg / KG_PER_POUND)} lb`;
|
|
153
203
|
}
|
|
154
|
-
|
|
204
|
+
if (Math.abs(kg) >= 1000) {
|
|
205
|
+
return `${twoDecimals.format(kg / 1000)} tonnes`;
|
|
206
|
+
}
|
|
207
|
+
return `${twoDecimals.format(kg)} kg`;
|
|
155
208
|
}
|
|
156
209
|
|
|
157
210
|
/**
|
|
158
|
-
* Format distance
|
|
211
|
+
* Format a distance given in kilometres, in the workspace distance unit: km
|
|
212
|
+
* (metres below 1 km) or mi (feet below 0.1 mi).
|
|
159
213
|
*/
|
|
160
214
|
export function formatDistance(km: number): string {
|
|
161
|
-
if (
|
|
215
|
+
if (preferences.distanceUnit === 'MI') {
|
|
216
|
+
const miles = km / KM_PER_MILE;
|
|
217
|
+
if (Math.abs(miles) < 0.1) return `${Math.round(miles * 5280)} ft`;
|
|
218
|
+
return `${oneDecimal.format(miles)} mi`;
|
|
219
|
+
}
|
|
220
|
+
if (Math.abs(km) < 1) {
|
|
162
221
|
return `${Math.round(km * 1000)} m`;
|
|
163
222
|
}
|
|
164
|
-
return `${
|
|
223
|
+
return `${oneDecimal.format(km)} km`;
|
|
165
224
|
}
|
|
166
225
|
|
|
167
226
|
/**
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module settings — client side of wspace-movethewheels-svc's workspace
|
|
3
|
+
* operational defaults (BOFF-10488).
|
|
4
|
+
*
|
|
5
|
+
* The service owns validation and storage; this module holds the GraphQL
|
|
6
|
+
* documents, the platform defaults (used until the settings load), the
|
|
7
|
+
* picker options and the mapping between the service's order priorities and
|
|
8
|
+
* the order form's. Kept free of React so the contract can be unit-tested.
|
|
9
|
+
*/
|
|
10
|
+
import type { FormatPreferences } from './formatters';
|
|
11
|
+
|
|
12
|
+
export type MtwDistanceUnit = 'KM' | 'MI';
|
|
13
|
+
export type MtwWeightUnit = 'KG' | 'LB';
|
|
14
|
+
export type MtwOrderPriority = 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT' | 'CRITICAL';
|
|
15
|
+
|
|
16
|
+
export interface MtwModuleSettings {
|
|
17
|
+
workspaceId: string;
|
|
18
|
+
defaultCurrency: string;
|
|
19
|
+
distanceUnit: MtwDistanceUnit;
|
|
20
|
+
weightUnit: MtwWeightUnit;
|
|
21
|
+
/** IANA zone; null shows each viewer their own local time. */
|
|
22
|
+
timezone: string | null;
|
|
23
|
+
defaultOrderPriority: MtwOrderPriority;
|
|
24
|
+
defaultDeliveryWindowHours: number;
|
|
25
|
+
isCustomized: boolean;
|
|
26
|
+
updatedAt: string | null;
|
|
27
|
+
updatedBy: string | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type MtwModuleSettingsInput = Partial<
|
|
31
|
+
Pick<
|
|
32
|
+
MtwModuleSettings,
|
|
33
|
+
| 'defaultCurrency'
|
|
34
|
+
| 'distanceUnit'
|
|
35
|
+
| 'weightUnit'
|
|
36
|
+
| 'timezone'
|
|
37
|
+
| 'defaultOrderPriority'
|
|
38
|
+
| 'defaultDeliveryWindowHours'
|
|
39
|
+
>
|
|
40
|
+
>;
|
|
41
|
+
|
|
42
|
+
/** Mirrors the service range for the default delivery window. */
|
|
43
|
+
export const DELIVERY_WINDOW_MIN_HOURS = 1;
|
|
44
|
+
export const DELIVERY_WINDOW_MAX_HOURS = 720;
|
|
45
|
+
|
|
46
|
+
/** The service's platform defaults, shown until the workspace's settings load. */
|
|
47
|
+
export function platformDefaults(workspaceId = ''): MtwModuleSettings {
|
|
48
|
+
return {
|
|
49
|
+
workspaceId,
|
|
50
|
+
defaultCurrency: 'USD',
|
|
51
|
+
distanceUnit: 'KM',
|
|
52
|
+
weightUnit: 'KG',
|
|
53
|
+
timezone: null,
|
|
54
|
+
defaultOrderPriority: 'MEDIUM',
|
|
55
|
+
defaultDeliveryWindowHours: 24,
|
|
56
|
+
isCustomized: false,
|
|
57
|
+
updatedAt: null,
|
|
58
|
+
updatedBy: null,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const SETTINGS_FIELDS = `
|
|
63
|
+
workspaceId
|
|
64
|
+
defaultCurrency
|
|
65
|
+
distanceUnit
|
|
66
|
+
weightUnit
|
|
67
|
+
timezone
|
|
68
|
+
defaultOrderPriority
|
|
69
|
+
defaultDeliveryWindowHours
|
|
70
|
+
isCustomized
|
|
71
|
+
updatedAt
|
|
72
|
+
updatedBy
|
|
73
|
+
`;
|
|
74
|
+
|
|
75
|
+
export const MTW_MODULE_SETTINGS_QUERY = `
|
|
76
|
+
query MtwModuleSettings {
|
|
77
|
+
mtwModuleSettings {
|
|
78
|
+
${SETTINGS_FIELDS}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
`;
|
|
82
|
+
|
|
83
|
+
export const UPDATE_MTW_MODULE_SETTINGS_MUTATION = `
|
|
84
|
+
mutation UpdateMtwModuleSettings($input: UpdateMtwModuleSettingsInput!) {
|
|
85
|
+
updateMtwModuleSettings(input: $input) {
|
|
86
|
+
${SETTINGS_FIELDS}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
`;
|
|
90
|
+
|
|
91
|
+
/** What the formatters need from the settings. */
|
|
92
|
+
export function toFormatPreferences(settings: MtwModuleSettings): FormatPreferences {
|
|
93
|
+
return {
|
|
94
|
+
currency: settings.defaultCurrency,
|
|
95
|
+
distanceUnit: settings.distanceUnit,
|
|
96
|
+
weightUnit: settings.weightUnit,
|
|
97
|
+
timeZone: settings.timezone,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const DISTANCE_UNIT_OPTIONS: ReadonlyArray<{ value: MtwDistanceUnit; label: string }> = [
|
|
102
|
+
{ value: 'KM', label: 'Kilometres (km)' },
|
|
103
|
+
{ value: 'MI', label: 'Miles (mi)' },
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
export const WEIGHT_UNIT_OPTIONS: ReadonlyArray<{ value: MtwWeightUnit; label: string }> = [
|
|
107
|
+
{ value: 'KG', label: 'Kilograms (kg)' },
|
|
108
|
+
{ value: 'LB', label: 'Pounds (lb)' },
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
export const ORDER_PRIORITY_OPTIONS: ReadonlyArray<{ value: MtwOrderPriority; label: string }> = [
|
|
112
|
+
{ value: 'LOW', label: 'Low' },
|
|
113
|
+
{ value: 'MEDIUM', label: 'Normal' },
|
|
114
|
+
{ value: 'HIGH', label: 'High' },
|
|
115
|
+
{ value: 'URGENT', label: 'Urgent' },
|
|
116
|
+
{ value: 'CRITICAL', label: 'Critical' },
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
/** The order form's four priorities for a service priority (CRITICAL shows as urgent). */
|
|
120
|
+
export function orderFormPriority(
|
|
121
|
+
priority: MtwOrderPriority
|
|
122
|
+
): 'low' | 'normal' | 'high' | 'urgent' {
|
|
123
|
+
switch (priority) {
|
|
124
|
+
case 'LOW':
|
|
125
|
+
return 'low';
|
|
126
|
+
case 'HIGH':
|
|
127
|
+
return 'high';
|
|
128
|
+
case 'URGENT':
|
|
129
|
+
case 'CRITICAL':
|
|
130
|
+
return 'urgent';
|
|
131
|
+
default:
|
|
132
|
+
return 'normal';
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Commonly used currencies first, then every other code the browser knows. */
|
|
137
|
+
const PREFERRED_CURRENCIES = ['USD', 'EUR', 'GBP', 'INR', 'AED', 'SGD', 'AUD', 'CAD', 'JPY', 'CNY'];
|
|
138
|
+
|
|
139
|
+
function supportedValues(key: 'currency' | 'timeZone'): string[] {
|
|
140
|
+
// Older browsers lack Intl.supportedValuesOf; the pickers then offer the
|
|
141
|
+
// preferred list plus the saved value.
|
|
142
|
+
return typeof Intl.supportedValuesOf === 'function' ? Intl.supportedValuesOf(key) : [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function currencyOptions(current?: string): string[] {
|
|
146
|
+
const all = new Set([...PREFERRED_CURRENCIES, ...supportedValues('currency')]);
|
|
147
|
+
if (current) all.add(current);
|
|
148
|
+
const rest = [...all].filter((code) => !PREFERRED_CURRENCIES.includes(code)).sort();
|
|
149
|
+
return [...PREFERRED_CURRENCIES, ...rest];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function currencyLabel(code: string): string {
|
|
153
|
+
try {
|
|
154
|
+
const name = new Intl.DisplayNames(['en'], { type: 'currency' }).of(code);
|
|
155
|
+
return name && name !== code ? `${code} — ${name}` : code;
|
|
156
|
+
} catch {
|
|
157
|
+
return code;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function timeZoneOptions(current?: string | null): string[] {
|
|
162
|
+
const zones = new Set(['UTC', ...supportedValues('timeZone')]);
|
|
163
|
+
if (current) zones.add(current);
|
|
164
|
+
return [...zones].sort((a, b) => (a === 'UTC' ? -1 : b === 'UTC' ? 1 : a.localeCompare(b)));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** The browser's own zone, for the "viewer's local time" option label. */
|
|
168
|
+
export function browserTimeZone(): string {
|
|
169
|
+
try {
|
|
170
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
|
171
|
+
} catch {
|
|
172
|
+
return 'UTC';
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Client-side check of the delivery window before it is sent. */
|
|
177
|
+
export function deliveryWindowError(value: string): string | null {
|
|
178
|
+
const hours = Number(value);
|
|
179
|
+
if (
|
|
180
|
+
!value.trim() ||
|
|
181
|
+
!Number.isInteger(hours) ||
|
|
182
|
+
hours < DELIVERY_WINDOW_MIN_HOURS ||
|
|
183
|
+
hours > DELIVERY_WINDOW_MAX_HOURS
|
|
184
|
+
) {
|
|
185
|
+
return `Enter a whole number of hours between ${DELIVERY_WINDOW_MIN_HOURS} and ${DELIVERY_WINDOW_MAX_HOURS}.`;
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Only the fields that differ from the saved settings. */
|
|
191
|
+
export function changedSettings(
|
|
192
|
+
saved: MtwModuleSettings,
|
|
193
|
+
draft: MtwModuleSettingsInput
|
|
194
|
+
): MtwModuleSettingsInput {
|
|
195
|
+
const changes: MtwModuleSettingsInput = {};
|
|
196
|
+
if (draft.defaultCurrency !== undefined && draft.defaultCurrency !== saved.defaultCurrency)
|
|
197
|
+
changes.defaultCurrency = draft.defaultCurrency;
|
|
198
|
+
if (draft.distanceUnit !== undefined && draft.distanceUnit !== saved.distanceUnit)
|
|
199
|
+
changes.distanceUnit = draft.distanceUnit;
|
|
200
|
+
if (draft.weightUnit !== undefined && draft.weightUnit !== saved.weightUnit)
|
|
201
|
+
changes.weightUnit = draft.weightUnit;
|
|
202
|
+
if (draft.timezone !== undefined && draft.timezone !== saved.timezone)
|
|
203
|
+
changes.timezone = draft.timezone;
|
|
204
|
+
if (
|
|
205
|
+
draft.defaultOrderPriority !== undefined &&
|
|
206
|
+
draft.defaultOrderPriority !== saved.defaultOrderPriority
|
|
207
|
+
)
|
|
208
|
+
changes.defaultOrderPriority = draft.defaultOrderPriority;
|
|
209
|
+
if (
|
|
210
|
+
draft.defaultDeliveryWindowHours !== undefined &&
|
|
211
|
+
draft.defaultDeliveryWindowHours !== saved.defaultDeliveryWindowHours
|
|
212
|
+
)
|
|
213
|
+
changes.defaultDeliveryWindowHours = draft.defaultDeliveryWindowHours;
|
|
214
|
+
return changes;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** A `datetime-local` value `hours` from `from`, in the viewer's clock. */
|
|
218
|
+
export function deliveryWindowValue(hours: number, from: Date = new Date()): string {
|
|
219
|
+
const target = new Date(from.getTime() + hours * 3600 * 1000);
|
|
220
|
+
const pad = (value: number) => String(value).padStart(2, '0');
|
|
221
|
+
return `${target.getFullYear()}-${pad(target.getMonth() + 1)}-${pad(target.getDate())}T${pad(
|
|
222
|
+
target.getHours()
|
|
223
|
+
)}:${pad(target.getMinutes())}`;
|
|
224
|
+
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI Operations Page
|
|
3
|
-
*
|
|
4
|
-
* This page previously shipped a second, fabricated chat surface
|
|
5
|
-
* (`useAssistant` → `Query.assistantInsights`) whose "capabilities" list
|
|
6
|
-
* advertised route optimization, demand forecasting, fleet analysis, cost
|
|
7
|
-
* optimization and natural-language queries — none of which the backend
|
|
8
|
-
* implemented; every question routed to a fixed template built from four
|
|
9
|
-
* Prisma counts. See BOFF-3897.
|
|
10
|
-
*
|
|
11
|
-
* The real conversational assistant is the shared `AssistantWidget`, already
|
|
12
|
-
* mounted globally in the app shell (`AppShell.tsx`) on the sandbox
|
|
13
|
-
* transport — this page must never contain a second chat box.
|
|
14
|
-
*
|
|
15
|
-
* The full "AI Operations" surface this page is meant to become — a model
|
|
16
|
-
* picker, an agent-activity feed reading the shared AgentRun ledger,
|
|
17
|
-
* accept/reject decision cards for AI dispatch proposals, and per-workspace
|
|
18
|
-
* AI credit visibility — depends on the shared multi-model AI gateway
|
|
19
|
-
* (BOFF-3601), which has not landed yet. Until it does, this page shows an
|
|
20
|
-
* honest roadmap state instead of fabricating any of that content.
|
|
21
|
-
*/
|
|
22
|
-
export default function AIAssistantPage(): import('react').JSX.Element;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { createLucideIcon as e } from "../createLucideIcon.js";
|
|
2
|
-
//#region node_modules/lucide-react/dist/esm/icons/message-circle.js
|
|
3
|
-
var t = e("MessageCircle", [["path", {
|
|
4
|
-
d: "M7.9 20A9 9 0 1 0 4 16.1L2 22Z",
|
|
5
|
-
key: "vv11sd"
|
|
6
|
-
}]]);
|
|
7
|
-
//#endregion
|
|
8
|
-
export { t as MessageCircle };
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { createLucideIcon as e } from "../createLucideIcon.js";
|
|
2
|
-
//#region node_modules/lucide-react/dist/esm/icons/wallet.js
|
|
3
|
-
var t = e("Wallet", [["path", {
|
|
4
|
-
d: "M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1",
|
|
5
|
-
key: "18etb6"
|
|
6
|
-
}], ["path", {
|
|
7
|
-
d: "M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4",
|
|
8
|
-
key: "xoc0q4"
|
|
9
|
-
}]]);
|
|
10
|
-
//#endregion
|
|
11
|
-
export { t as Wallet };
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { Activity as e } from "../node_modules/lucide-react/dist/esm/icons/activity.js";
|
|
2
|
-
import { Bot as t } from "../node_modules/lucide-react/dist/esm/icons/bot.js";
|
|
3
|
-
import { MessageCircle as n } from "../node_modules/lucide-react/dist/esm/icons/message-circle.js";
|
|
4
|
-
import { ShieldCheck as r } from "../node_modules/lucide-react/dist/esm/icons/shield-check.js";
|
|
5
|
-
import { Wallet as i } from "../node_modules/lucide-react/dist/esm/icons/wallet.js";
|
|
6
|
-
import { useTr as a } from "../shared/hooks/useTr.js";
|
|
7
|
-
import "../hooks/index.js";
|
|
8
|
-
import { Card as o, CardContent as s, CardHeader as c, CardTitle as l } from "../components/ui.js";
|
|
9
|
-
import { EmptyState as u, PageLayout as d } from "../components/PageLayout.js";
|
|
10
|
-
import { jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
11
|
-
import { GlassCard as m, PagePurpose as h } from "@burdenoff/fe-libs/ui";
|
|
12
|
-
//#region src/movethewheels/pages/AIAssistantPage.tsx
|
|
13
|
-
function g() {
|
|
14
|
-
let g = a(), _ = [
|
|
15
|
-
{
|
|
16
|
-
icon: e,
|
|
17
|
-
title: g("movethewheels.ai.roadmap.agentActivity.title", "Agent activity feed"),
|
|
18
|
-
description: g("movethewheels.ai.roadmap.agentActivity.description", "A live log of what the AI decided, on which shipments, with trigger, model, latency and cost — so an autonomous run is never a black box.")
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
icon: r,
|
|
22
|
-
title: g("movethewheels.ai.roadmap.dispatchDecisionCards.title", "Dispatch decision cards"),
|
|
23
|
-
description: g("movethewheels.ai.roadmap.dispatchDecisionCards.description", "Proposed driver/vehicle assignments and carrier recommendations with rationale and confidence, requiring an explicit accept or reject before anything is written.")
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
icon: i,
|
|
27
|
-
title: g("movethewheels.ai.roadmap.aiCreditVisibility.title", "AI credit visibility"),
|
|
28
|
-
description: g("movethewheels.ai.roadmap.aiCreditVisibility.description", "Per-workspace AI spend for admins, so cost is visible before the invoice.")
|
|
29
|
-
}
|
|
30
|
-
];
|
|
31
|
-
return /* @__PURE__ */ p(d, {
|
|
32
|
-
title: g("movethewheels.ai.title", "AI Operations"),
|
|
33
|
-
subtitle: g("movethewheels.ai.subtitle", "What the AI is doing on your workspace, and why"),
|
|
34
|
-
icon: /* @__PURE__ */ f(t, { size: 24 }),
|
|
35
|
-
children: [
|
|
36
|
-
/* @__PURE__ */ f(h, {
|
|
37
|
-
icon: /* @__PURE__ */ f(t, { size: 18 }),
|
|
38
|
-
children: g("movethewheels.ai.purpose", "This page will surface AI-driven dispatch decisions and their outcomes once the shared AI runtime lands. It does not host a chat box — chat with your live operational data is always available from the assistant icon anywhere in the app.")
|
|
39
|
-
}),
|
|
40
|
-
/* @__PURE__ */ f(m, {
|
|
41
|
-
treatment: "glass",
|
|
42
|
-
className: "p-4",
|
|
43
|
-
children: /* @__PURE__ */ p("div", {
|
|
44
|
-
className: "flex items-start gap-3",
|
|
45
|
-
children: [/* @__PURE__ */ f(n, {
|
|
46
|
-
size: 20,
|
|
47
|
-
className: "text-primary flex-shrink-0 mt-0.5"
|
|
48
|
-
}), /* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("p", {
|
|
49
|
-
className: "text-sm font-medium text-text-primary",
|
|
50
|
-
children: g("movethewheels.ai.callout.title", "Looking to ask a question about your operations?")
|
|
51
|
-
}), /* @__PURE__ */ f("p", {
|
|
52
|
-
className: "text-sm text-text-secondary mt-1",
|
|
53
|
-
children: g("movethewheels.ai.callout.description", "Open the assistant from its icon in the corner of the screen — it answers from your live orders, shipments, fleet and inventory data, and it is the only chat surface in this product.")
|
|
54
|
-
})] })]
|
|
55
|
-
})
|
|
56
|
-
}),
|
|
57
|
-
/* @__PURE__ */ p(o, { children: [/* @__PURE__ */ f(c, { children: /* @__PURE__ */ f(l, { children: g("movethewheels.ai.roadmap.title", "On the roadmap") }) }), /* @__PURE__ */ p(s, { children: [/* @__PURE__ */ f(u, {
|
|
58
|
-
illustration: "empty-agents",
|
|
59
|
-
title: g("movethewheels.ai.emptyState.title", "Autonomous dispatch operations aren't live yet"),
|
|
60
|
-
description: g("movethewheels.ai.emptyState.description", "Autonomous dispatch isn't available yet — these capabilities are still in development. Nothing below is available today.")
|
|
61
|
-
}), /* @__PURE__ */ f("ul", {
|
|
62
|
-
className: "mt-4 space-y-4",
|
|
63
|
-
children: _.map((e) => /* @__PURE__ */ p("li", {
|
|
64
|
-
className: "flex items-start gap-3",
|
|
65
|
-
children: [/* @__PURE__ */ f(e.icon, {
|
|
66
|
-
size: 18,
|
|
67
|
-
className: "text-text-secondary flex-shrink-0 mt-0.5"
|
|
68
|
-
}), /* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("p", {
|
|
69
|
-
className: "text-sm font-medium text-text-primary",
|
|
70
|
-
children: e.title
|
|
71
|
-
}), /* @__PURE__ */ f("p", {
|
|
72
|
-
className: "text-xs text-text-secondary",
|
|
73
|
-
children: e.description
|
|
74
|
-
})] })]
|
|
75
|
-
}, e.title))
|
|
76
|
-
})] })] })
|
|
77
|
-
]
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
//#endregion
|
|
81
|
-
export { g as default };
|