@burdenoff/microfe-movethewheels 2026.822.1 → 2026.828.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/generated/wspace-operations.d.ts +168 -912
- package/dist/generated/wspace-types.d.ts +78432 -35831
- package/dist/hooks/useFleet.js +142 -74
- package/dist/hooks/useImportExport.js +22 -51
- package/dist/hooks/useInventory.js +163 -70
- package/dist/hooks/useOrders.js +2 -2
- package/dist/hooks/useReports.js +63 -73
- package/dist/hooks/useSettings.js +52 -44
- package/dist/hooks/useTracking.js +5 -5
- package/dist/index.js +33 -33
- package/dist/movethewheels/hooks/index.d.ts +2 -2
- package/dist/movethewheels/hooks/useFleet.d.ts +32 -1
- package/dist/movethewheels/hooks/useImportExport.d.ts +10 -0
- package/dist/movethewheels/hooks/useInventory.d.ts +67 -12
- package/dist/movethewheels/hooks/useOrders.d.ts +25 -2
- package/dist/movethewheels/pages/MarketplacePage.d.ts +7 -4
- package/dist/movethewheels/types/index.d.ts +2 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/arrow-down-right.js +11 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/arrow-left-right.js +22 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/arrow-up-right.js +11 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/calendar-clock.js +32 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/history.js +18 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/lock.js +16 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/user-round.js +13 -0
- package/dist/pages/CreateOrderPage.js +246 -195
- package/dist/pages/DeliveryCapturePage.js +91 -78
- package/dist/pages/DriversPage.js +202 -108
- package/dist/pages/InventoryPage.js +356 -131
- package/dist/pages/MarketplacePage.js +11 -432
- package/dist/pages/ProductDetailsPage.js +212 -156
- package/dist/pages/ReportsPage.js +120 -109
- package/dist/pages/VehicleDetailsPage.js +257 -193
- package/dist/utils/formatters.js +4 -1
- package/package.json +2 -2
- package/src/generated/wspace-operations.ts +134 -929
- package/src/generated/wspace-types.ts +91730 -40059
- package/src/movethewheels/hooks/index.ts +9 -0
- package/src/movethewheels/hooks/useFleet.ts +133 -3
- package/src/movethewheels/hooks/useImportExport.ts +36 -72
- package/src/movethewheels/hooks/useInventory.ts +311 -53
- package/src/movethewheels/hooks/useOrders.ts +31 -7
- package/src/movethewheels/hooks/useReports.ts +73 -87
- package/src/movethewheels/hooks/useSettings.ts +50 -53
- package/src/movethewheels/hooks/useTracking.ts +14 -14
- package/src/movethewheels/pages/CreateOrderPage.tsx +67 -10
- package/src/movethewheels/pages/DeliveryCapturePage.tsx +8 -0
- package/src/movethewheels/pages/DriversPage.tsx +181 -31
- package/src/movethewheels/pages/InventoryPage.tsx +299 -19
- package/src/movethewheels/pages/MarketplacePage.tsx +16 -539
- package/src/movethewheels/pages/ProductDetailsPage.tsx +68 -3
- package/src/movethewheels/pages/ReportsPage.tsx +23 -0
- package/src/movethewheels/pages/VehicleDetailsPage.tsx +86 -27
- package/src/movethewheels/types/index.ts +2 -0
- package/src/operations/wspace/movethewheels/fragments/core.graphql +23 -53
- package/src/operations/wspace/movethewheels/fragments/core.withdrawn.graphql +59 -0
- package/src/operations/wspace/movethewheels/mutations/entities.graphql +5 -5
- package/src/operations/wspace/movethewheels/mutations/marketplace-reports.graphql +18 -78
- package/src/operations/wspace/movethewheels/mutations/marketplace-reports.withdrawn.graphql +93 -0
- package/src/operations/wspace/movethewheels/mutations/orders.graphql +7 -7
- package/src/operations/wspace/movethewheels/queries/dashboard.graphql +3 -3
- package/src/operations/wspace/movethewheels/queries/entities.graphql +3 -3
- package/src/operations/wspace/movethewheels/queries/logistics.graphql +3 -3
- package/src/operations/wspace/movethewheels/queries/marketplace-reports.withdrawn.graphql +58 -0
- package/src/operations/wspace/movethewheels/queries/orders.graphql +3 -3
- package/dist/generated/global-operations.d.ts +0 -1
- package/dist/generated/global-types.d.ts +0 -23526
- package/dist/node_modules/lucide-react/dist/esm/icons/chevron-down.js +0 -8
- package/dist/node_modules/lucide-react/dist/esm/icons/chevron-up.js +0 -8
- package/src/generated/global-operations.ts +0 -2
- package/src/generated/global-types.ts +0 -27259
- package/src/operations/wspace/movethewheels/queries/marketplace-reports.graphql +0 -40
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { useCallback, useEffect,
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
2
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
3
|
-
import { executeGraphQL } from '../utils/graphqlClient';
|
|
4
3
|
|
|
5
4
|
export interface MovethewheelsReport {
|
|
6
5
|
id: string;
|
|
@@ -20,46 +19,6 @@ export interface GeneratedReport {
|
|
|
20
19
|
report: MovethewheelsReport;
|
|
21
20
|
}
|
|
22
21
|
|
|
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
22
|
export interface CreateReportInput {
|
|
64
23
|
name: string;
|
|
65
24
|
type: string;
|
|
@@ -87,44 +46,60 @@ interface UseReportsResult {
|
|
|
87
46
|
) => Promise<GeneratedReport>;
|
|
88
47
|
}
|
|
89
48
|
|
|
49
|
+
function newId(): string {
|
|
50
|
+
if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
|
|
51
|
+
return crypto.randomUUID();
|
|
52
|
+
}
|
|
53
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function storageKey(workspaceId: string | undefined): string {
|
|
57
|
+
return `movethewheels:reports:${workspaceId ?? 'default'}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function readReports(workspaceId: string | undefined): MovethewheelsReport[] {
|
|
61
|
+
if (typeof window === 'undefined') return [];
|
|
62
|
+
try {
|
|
63
|
+
const raw = window.localStorage.getItem(storageKey(workspaceId));
|
|
64
|
+
if (!raw) return [];
|
|
65
|
+
const parsed = JSON.parse(raw) as MovethewheelsReport[];
|
|
66
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
67
|
+
} catch {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function writeReports(workspaceId: string | undefined, reports: MovethewheelsReport[]): void {
|
|
73
|
+
if (typeof window === 'undefined') return;
|
|
74
|
+
try {
|
|
75
|
+
window.localStorage.setItem(storageKey(workspaceId), JSON.stringify(reports));
|
|
76
|
+
} catch {
|
|
77
|
+
// Ignore storage errors.
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
90
81
|
export function useReports(options: UseReportsOptions = {}): UseReportsResult {
|
|
91
82
|
const { limit = 100, offset = 0 } = options;
|
|
92
|
-
const {
|
|
93
|
-
useMoveTheWheels();
|
|
83
|
+
const { workspaceId } = useMoveTheWheels();
|
|
94
84
|
const [reports, setReports] = useState<MovethewheelsReport[]>([]);
|
|
95
85
|
const [hasMore, setHasMore] = useState(false);
|
|
96
86
|
const [isLoading, setIsLoading] = useState(false);
|
|
97
87
|
const [error, setError] = useState<Error | null>(null);
|
|
98
88
|
|
|
99
|
-
const
|
|
100
|
-
() => ({
|
|
101
|
-
apiGatewayUrl,
|
|
102
|
-
authToken,
|
|
103
|
-
workspaceToken,
|
|
104
|
-
workspaceId,
|
|
105
|
-
tenantId,
|
|
106
|
-
actorId: currentUser?.id,
|
|
107
|
-
}),
|
|
108
|
-
[apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser?.id]
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
const load = useCallback(async () => {
|
|
89
|
+
const load = useCallback(() => {
|
|
112
90
|
setIsLoading(true);
|
|
113
91
|
setError(null);
|
|
114
92
|
try {
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
setReports(data.movethewheelsReports);
|
|
122
|
-
} catch (fetchError) {
|
|
123
|
-
setError(fetchError instanceof Error ? fetchError : new Error('Failed to load reports'));
|
|
93
|
+
const all = readReports(workspaceId);
|
|
94
|
+
const page = all.slice(offset, offset + limit);
|
|
95
|
+
setHasMore(page.length === limit);
|
|
96
|
+
setReports(page);
|
|
97
|
+
} catch (loadError) {
|
|
98
|
+
setError(loadError instanceof Error ? loadError : new Error('Failed to load reports'));
|
|
124
99
|
} finally {
|
|
125
100
|
setIsLoading(false);
|
|
126
101
|
}
|
|
127
|
-
}, [
|
|
102
|
+
}, [workspaceId, limit, offset]);
|
|
128
103
|
|
|
129
104
|
useEffect(() => {
|
|
130
105
|
void load();
|
|
@@ -136,33 +111,44 @@ export function useReports(options: UseReportsOptions = {}): UseReportsResult {
|
|
|
136
111
|
|
|
137
112
|
const createReport = useCallback(
|
|
138
113
|
async (input: CreateReportInput): Promise<MovethewheelsReport> => {
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
114
|
+
const now = new Date().toISOString();
|
|
115
|
+
const created: MovethewheelsReport = {
|
|
116
|
+
id: newId(),
|
|
117
|
+
name: input.name,
|
|
118
|
+
description: input.description ?? null,
|
|
119
|
+
type: input.type,
|
|
120
|
+
status: input.status ?? 'ready',
|
|
121
|
+
schedule: input.schedule ?? null,
|
|
122
|
+
lastGenerated: null,
|
|
123
|
+
createdAt: now,
|
|
124
|
+
updatedAt: now,
|
|
125
|
+
};
|
|
126
|
+
setReports((previous) => {
|
|
127
|
+
const next = [created, ...previous];
|
|
128
|
+
writeReports(workspaceId, next);
|
|
129
|
+
return next;
|
|
130
|
+
});
|
|
146
131
|
return created;
|
|
147
132
|
},
|
|
148
|
-
[
|
|
133
|
+
[workspaceId]
|
|
149
134
|
);
|
|
150
135
|
|
|
151
136
|
const generateReport = useCallback(
|
|
152
|
-
async (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
GENERATE_REPORT_MUTATION,
|
|
156
|
-
{ input: { reportId, parameters } }
|
|
157
|
-
);
|
|
158
|
-
const generated = data.generateMovethewheelsReport;
|
|
159
|
-
setReports((previous) =>
|
|
160
|
-
previous.map((item) => (item.id === reportId ? generated.report : item))
|
|
137
|
+
async (_reportId: string, _parameters?: Record<string, unknown>): Promise<GeneratedReport> => {
|
|
138
|
+
throw new Error(
|
|
139
|
+
'Report generation is not available: the movethewheelsReports surface was withdrawn from the service.'
|
|
161
140
|
);
|
|
162
|
-
return generated;
|
|
163
141
|
},
|
|
164
|
-
[
|
|
142
|
+
[]
|
|
165
143
|
);
|
|
166
144
|
|
|
167
|
-
return {
|
|
145
|
+
return {
|
|
146
|
+
reports,
|
|
147
|
+
hasMore,
|
|
148
|
+
isLoading,
|
|
149
|
+
error,
|
|
150
|
+
refetch,
|
|
151
|
+
createReport,
|
|
152
|
+
generateReport,
|
|
153
|
+
};
|
|
168
154
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
3
|
-
import { executeGraphQL } from '../utils/graphqlClient';
|
|
4
3
|
|
|
5
4
|
export type ModulePreferences = Record<string, unknown>;
|
|
6
5
|
|
|
@@ -10,26 +9,6 @@ export interface ModuleSettings {
|
|
|
10
9
|
workspaceId: string;
|
|
11
10
|
}
|
|
12
11
|
|
|
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
12
|
interface UseModuleSettingsResult {
|
|
34
13
|
settings: ModuleSettings | null;
|
|
35
14
|
preferences: ModulePreferences;
|
|
@@ -40,41 +19,58 @@ interface UseModuleSettingsResult {
|
|
|
40
19
|
savePreferences: (preferences: ModulePreferences) => Promise<ModuleSettings>;
|
|
41
20
|
}
|
|
42
21
|
|
|
22
|
+
function readSettings(workspaceId: string): ModuleSettings | null {
|
|
23
|
+
if (typeof window === 'undefined') return null;
|
|
24
|
+
try {
|
|
25
|
+
const raw = window.localStorage.getItem(`movethewheels:moduleSettings:${workspaceId}`);
|
|
26
|
+
if (!raw) return null;
|
|
27
|
+
const parsed = JSON.parse(raw) as ModuleSettings;
|
|
28
|
+
if (parsed && typeof parsed === 'object' && 'preferences' in parsed) {
|
|
29
|
+
return parsed;
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function writeSettings(settings: ModuleSettings): void {
|
|
38
|
+
if (typeof window === 'undefined') return;
|
|
39
|
+
try {
|
|
40
|
+
window.localStorage.setItem(
|
|
41
|
+
`movethewheels:moduleSettings:${settings.workspaceId}`,
|
|
42
|
+
JSON.stringify(settings)
|
|
43
|
+
);
|
|
44
|
+
} catch {
|
|
45
|
+
// Ignore storage errors (e.g. private mode quota).
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
43
49
|
export function useModuleSettings(): UseModuleSettingsResult {
|
|
44
|
-
const {
|
|
45
|
-
useMoveTheWheels();
|
|
50
|
+
const { workspaceId } = useMoveTheWheels();
|
|
46
51
|
const [settings, setSettings] = useState<ModuleSettings | null>(null);
|
|
47
52
|
const [isLoading, setIsLoading] = useState(false);
|
|
48
53
|
const [isSaving, setIsSaving] = useState(false);
|
|
49
54
|
const [error, setError] = useState<Error | null>(null);
|
|
50
55
|
|
|
51
|
-
const
|
|
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 () => {
|
|
56
|
+
const load = useCallback(() => {
|
|
64
57
|
setIsLoading(true);
|
|
65
58
|
setError(null);
|
|
66
59
|
try {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
const stored = workspaceId ? readSettings(workspaceId) : null;
|
|
61
|
+
setSettings(
|
|
62
|
+
stored ?? {
|
|
63
|
+
preferences: {},
|
|
64
|
+
updatedAt: new Date().toISOString(),
|
|
65
|
+
workspaceId: workspaceId ?? '',
|
|
66
|
+
}
|
|
70
67
|
);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
setError(fetchError instanceof Error ? fetchError : new Error('Failed to load settings'));
|
|
68
|
+
} catch (loadError) {
|
|
69
|
+
setError(loadError instanceof Error ? loadError : new Error('Failed to load settings'));
|
|
74
70
|
} finally {
|
|
75
71
|
setIsLoading(false);
|
|
76
72
|
}
|
|
77
|
-
}, [
|
|
73
|
+
}, [workspaceId]);
|
|
78
74
|
|
|
79
75
|
useEffect(() => {
|
|
80
76
|
void load();
|
|
@@ -89,23 +85,24 @@ export function useModuleSettings(): UseModuleSettingsResult {
|
|
|
89
85
|
setIsSaving(true);
|
|
90
86
|
setError(null);
|
|
91
87
|
try {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
88
|
+
const next: ModuleSettings = {
|
|
89
|
+
preferences,
|
|
90
|
+
updatedAt: new Date().toISOString(),
|
|
91
|
+
workspaceId: workspaceId ?? '',
|
|
92
|
+
};
|
|
93
|
+
writeSettings(next);
|
|
94
|
+
setSettings(next);
|
|
95
|
+
return next;
|
|
96
|
+
} catch (saveError) {
|
|
100
97
|
const normalized =
|
|
101
|
-
|
|
98
|
+
saveError instanceof Error ? saveError : new Error('Failed to save settings');
|
|
102
99
|
setError(normalized);
|
|
103
100
|
throw normalized;
|
|
104
101
|
} finally {
|
|
105
102
|
setIsSaving(false);
|
|
106
103
|
}
|
|
107
104
|
},
|
|
108
|
-
[
|
|
105
|
+
[workspaceId]
|
|
109
106
|
);
|
|
110
107
|
|
|
111
108
|
// Stabilize the preferences reference. Returning `settings?.preferences ?? {}`
|
|
@@ -30,7 +30,7 @@ interface ApiTrackingEvent {
|
|
|
30
30
|
interface ApiShipment {
|
|
31
31
|
id: string;
|
|
32
32
|
shipmentNumber: string;
|
|
33
|
-
|
|
33
|
+
deliveryJobId?: string | null;
|
|
34
34
|
status: string;
|
|
35
35
|
estimatedDelivery?: string | null;
|
|
36
36
|
trackingEvents?: ApiTrackingEvent[];
|
|
@@ -41,7 +41,7 @@ const SHIPMENTS_QUERY = `
|
|
|
41
41
|
shipments(filter: $filter, pagination: $pagination) {
|
|
42
42
|
id
|
|
43
43
|
shipmentNumber
|
|
44
|
-
|
|
44
|
+
deliveryJobId
|
|
45
45
|
status
|
|
46
46
|
estimatedDelivery
|
|
47
47
|
}
|
|
@@ -53,7 +53,7 @@ const SHIPMENT_QUERY = `
|
|
|
53
53
|
shipment(id: $id) {
|
|
54
54
|
id
|
|
55
55
|
shipmentNumber
|
|
56
|
-
|
|
56
|
+
deliveryJobId
|
|
57
57
|
status
|
|
58
58
|
estimatedDelivery
|
|
59
59
|
trackingEvents {
|
|
@@ -68,9 +68,9 @@ const SHIPMENT_QUERY = `
|
|
|
68
68
|
}
|
|
69
69
|
`;
|
|
70
70
|
|
|
71
|
-
const
|
|
72
|
-
query
|
|
73
|
-
|
|
71
|
+
const DELIVERY_JOB_TRACKING_QUERY = `
|
|
72
|
+
query GetDeliveryJobTracking($deliveryJobId: ID!) {
|
|
73
|
+
deliveryJobTracking(deliveryJobId: $deliveryJobId) {
|
|
74
74
|
id
|
|
75
75
|
shipmentId
|
|
76
76
|
eventType
|
|
@@ -86,7 +86,7 @@ const ACTIVE_DELIVERIES_QUERY = `
|
|
|
86
86
|
activeDeliveries {
|
|
87
87
|
id
|
|
88
88
|
shipmentNumber
|
|
89
|
-
|
|
89
|
+
deliveryJobId
|
|
90
90
|
status
|
|
91
91
|
estimatedDelivery
|
|
92
92
|
}
|
|
@@ -163,7 +163,7 @@ export function useTracking(options: UseTrackingOptions = {}): UseTrackingResult
|
|
|
163
163
|
|
|
164
164
|
const mapped = data.shipments.map((shipment) => ({
|
|
165
165
|
id: shipment.id,
|
|
166
|
-
entityId: shipment.
|
|
166
|
+
entityId: shipment.deliveryJobId ?? shipment.id,
|
|
167
167
|
entityType: 'order' as const,
|
|
168
168
|
currentLocation: {
|
|
169
169
|
latitude: 0,
|
|
@@ -224,14 +224,14 @@ export function useOrderTracking(orderId: string | undefined) {
|
|
|
224
224
|
setIsLoading(true);
|
|
225
225
|
setError(null);
|
|
226
226
|
try {
|
|
227
|
-
const data = await executeGraphQL<{
|
|
227
|
+
const data = await executeGraphQL<{ deliveryJobTracking: ApiTrackingEvent[] }>(
|
|
228
228
|
graphQLOptions,
|
|
229
|
-
|
|
229
|
+
DELIVERY_JOB_TRACKING_QUERY,
|
|
230
230
|
{
|
|
231
|
-
orderId,
|
|
231
|
+
deliveryJobId: orderId,
|
|
232
232
|
}
|
|
233
233
|
);
|
|
234
|
-
const latest = data.
|
|
234
|
+
const latest = data.deliveryJobTracking[data.deliveryJobTracking.length - 1];
|
|
235
235
|
setTracking(latest ? mapTrackingEventToTracking(latest, orderId) : null);
|
|
236
236
|
} catch (fetchError) {
|
|
237
237
|
setError(
|
|
@@ -327,7 +327,7 @@ export function useLiveTracking() {
|
|
|
327
327
|
|
|
328
328
|
const shipmentEvents = shipmentData.shipment?.trackingEvents ?? [];
|
|
329
329
|
const latestEvent = shipmentEvents[shipmentEvents.length - 1];
|
|
330
|
-
const order = orders.find((item) => item.id === shipment.
|
|
330
|
+
const order = orders.find((item) => item.id === shipment.deliveryJobId);
|
|
331
331
|
if (!order) {
|
|
332
332
|
return null;
|
|
333
333
|
}
|
|
@@ -346,7 +346,7 @@ export function useLiveTracking() {
|
|
|
346
346
|
driver,
|
|
347
347
|
vehicle,
|
|
348
348
|
tracking: latestEvent
|
|
349
|
-
? mapTrackingEventToTracking(latestEvent, shipment.
|
|
349
|
+
? mapTrackingEventToTracking(latestEvent, shipment.deliveryJobId ?? shipment.id)
|
|
350
350
|
: null,
|
|
351
351
|
estimatedArrival: shipment.estimatedDelivery
|
|
352
352
|
? new Date(shipment.estimatedDelivery)
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { useEffect, useState } from 'react';
|
|
8
8
|
import { useParams } from 'react-router';
|
|
9
|
-
import { Package, ArrowLeft, Save, Plus, Trash2, Users, MapPin } from 'lucide-react';
|
|
9
|
+
import { Package, ArrowLeft, Save, Plus, Trash2, Users, MapPin, CalendarClock } from 'lucide-react';
|
|
10
10
|
import { PageLayout, EmptyState } from '../components/PageLayout';
|
|
11
11
|
import { FormError } from '../components/FormError';
|
|
12
12
|
import {
|
|
@@ -31,6 +31,22 @@ import { useCustomers } from '../hooks/useCustomers';
|
|
|
31
31
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
32
32
|
import { joinPath } from '../utils/navigation';
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* `<input type="datetime-local">` needs a `YYYY-MM-DDTHH:mm` string in LOCAL
|
|
36
|
+
* time — `Date.toISOString()` would shift the value by the UTC offset and show
|
|
37
|
+
* the operator a different slot than the one stored. Returns '' for no date.
|
|
38
|
+
*/
|
|
39
|
+
function toDateTimeLocalValue(date: Date | undefined): string {
|
|
40
|
+
if (!date || Number.isNaN(date.getTime())) {
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
const pad = (value: number) => String(value).padStart(2, '0');
|
|
44
|
+
return (
|
|
45
|
+
`${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}` +
|
|
46
|
+
`T${pad(date.getHours())}:${pad(date.getMinutes())}`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
34
50
|
export default function CreateOrderPage() {
|
|
35
51
|
const { orderId } = useParams<{ orderId: string }>();
|
|
36
52
|
const { basePath, navigate } = useMoveTheWheels();
|
|
@@ -58,6 +74,7 @@ export default function CreateOrderPage() {
|
|
|
58
74
|
country: order?.deliveryAddress.country || 'USA',
|
|
59
75
|
},
|
|
60
76
|
items: order?.items || [],
|
|
77
|
+
scheduledDelivery: toDateTimeLocalValue(order?.scheduledDelivery),
|
|
61
78
|
specialInstructions: order?.specialInstructions || '',
|
|
62
79
|
});
|
|
63
80
|
|
|
@@ -89,6 +106,7 @@ export default function CreateOrderPage() {
|
|
|
89
106
|
country: order.deliveryAddress.country || 'USA',
|
|
90
107
|
},
|
|
91
108
|
items: order.items || [],
|
|
109
|
+
scheduledDelivery: toDateTimeLocalValue(order.scheduledDelivery),
|
|
92
110
|
specialInstructions: order.specialInstructions || '',
|
|
93
111
|
});
|
|
94
112
|
}, [order]);
|
|
@@ -113,6 +131,9 @@ export default function CreateOrderPage() {
|
|
|
113
131
|
if (formData.items.length === 0) {
|
|
114
132
|
return 'Add at least one item to the order.';
|
|
115
133
|
}
|
|
134
|
+
if (formData.scheduledDelivery && Number.isNaN(Date.parse(formData.scheduledDelivery))) {
|
|
135
|
+
return 'Enter a valid scheduled delivery date and time.';
|
|
136
|
+
}
|
|
116
137
|
return null;
|
|
117
138
|
};
|
|
118
139
|
|
|
@@ -146,6 +167,8 @@ export default function CreateOrderPage() {
|
|
|
146
167
|
pickupAddress: formData.pickupAddress,
|
|
147
168
|
deliveryAddress: formData.deliveryAddress,
|
|
148
169
|
items: formData.items,
|
|
170
|
+
// `null` clears an existing slot on edit; the service field is nullable.
|
|
171
|
+
scheduledDelivery: formData.scheduledDelivery ? new Date(formData.scheduledDelivery) : null,
|
|
149
172
|
specialInstructions: formData.specialInstructions,
|
|
150
173
|
};
|
|
151
174
|
|
|
@@ -466,7 +489,7 @@ export default function CreateOrderPage() {
|
|
|
466
489
|
{formData.items.map((item, index) => (
|
|
467
490
|
<div key={item.id}>
|
|
468
491
|
{index > 0 && <Separator className="my-4" />}
|
|
469
|
-
<div className="grid grid-cols-1 md:grid-cols-
|
|
492
|
+
<div className="grid grid-cols-1 md:grid-cols-7 gap-4">
|
|
470
493
|
<div className="md:col-span-2 space-y-2">
|
|
471
494
|
<Label>Item Name</Label>
|
|
472
495
|
<Input
|
|
@@ -494,6 +517,18 @@ export default function CreateOrderPage() {
|
|
|
494
517
|
}
|
|
495
518
|
/>
|
|
496
519
|
</div>
|
|
520
|
+
<div className="space-y-2">
|
|
521
|
+
<Label>Weight (kg)</Label>
|
|
522
|
+
<Input
|
|
523
|
+
type="number"
|
|
524
|
+
min="0"
|
|
525
|
+
step="0.1"
|
|
526
|
+
value={item.weight}
|
|
527
|
+
onChange={(e) =>
|
|
528
|
+
updateItem(index, 'weight', parseFloat(e.target.value) || 0)
|
|
529
|
+
}
|
|
530
|
+
/>
|
|
531
|
+
</div>
|
|
497
532
|
<div className="space-y-2">
|
|
498
533
|
<Label>Value ($)</Label>
|
|
499
534
|
<Input
|
|
@@ -525,18 +560,40 @@ export default function CreateOrderPage() {
|
|
|
525
560
|
</CardContent>
|
|
526
561
|
</Card>
|
|
527
562
|
|
|
528
|
-
{/*
|
|
563
|
+
{/* Scheduling & handling notes */}
|
|
529
564
|
<Card>
|
|
530
565
|
<CardHeader>
|
|
531
|
-
<CardTitle>
|
|
566
|
+
<CardTitle>Scheduling & Handling</CardTitle>
|
|
532
567
|
</CardHeader>
|
|
533
568
|
<CardContent>
|
|
534
|
-
<
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
569
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
570
|
+
<div className="space-y-2">
|
|
571
|
+
<Label htmlFor="scheduledDelivery" className="flex items-center gap-1.5">
|
|
572
|
+
<CalendarClock size={14} className="text-text-secondary" />
|
|
573
|
+
Scheduled Delivery
|
|
574
|
+
</Label>
|
|
575
|
+
<Input
|
|
576
|
+
id="scheduledDelivery"
|
|
577
|
+
type="datetime-local"
|
|
578
|
+
value={formData.scheduledDelivery}
|
|
579
|
+
onChange={(e) => updateField('scheduledDelivery', e.target.value)}
|
|
580
|
+
/>
|
|
581
|
+
<p className="text-xs text-text-secondary">
|
|
582
|
+
The delivery window promised to the customer. Leave blank to dispatch as soon as a
|
|
583
|
+
driver is free.
|
|
584
|
+
</p>
|
|
585
|
+
</div>
|
|
586
|
+
<div className="md:col-span-2 space-y-2">
|
|
587
|
+
<Label htmlFor="specialInstructions">Special Instructions</Label>
|
|
588
|
+
<Textarea
|
|
589
|
+
id="specialInstructions"
|
|
590
|
+
value={formData.specialInstructions}
|
|
591
|
+
onChange={(e) => updateField('specialInstructions', e.target.value)}
|
|
592
|
+
placeholder="Any special handling or delivery instructions..."
|
|
593
|
+
rows={4}
|
|
594
|
+
/>
|
|
595
|
+
</div>
|
|
596
|
+
</div>
|
|
540
597
|
</CardContent>
|
|
541
598
|
</Card>
|
|
542
599
|
</form>
|
|
@@ -38,6 +38,8 @@ import {
|
|
|
38
38
|
Textarea,
|
|
39
39
|
} from '../components/ui';
|
|
40
40
|
import { SignaturePad, type SignaturePadHandle } from '../components/SignaturePad';
|
|
41
|
+
import { StatusBadge, PriorityBadge } from '../components/StatusBadge';
|
|
42
|
+
import { mapOrderPriority, mapOrderStatus } from '../hooks/useOrders';
|
|
41
43
|
import { FormError } from '../components/FormError';
|
|
42
44
|
import { Notice, useNotice } from '../components/Notice';
|
|
43
45
|
import {
|
|
@@ -284,6 +286,12 @@ export default function DeliveryCapturePage() {
|
|
|
284
286
|
<p className="font-medium flex items-center gap-2">
|
|
285
287
|
<Package size={16} /> {job.deliveryJobNumber}
|
|
286
288
|
</p>
|
|
289
|
+
{/* Stop state + urgency, straight off the job record — a driver
|
|
290
|
+
needs to know which stop is already out for delivery. */}
|
|
291
|
+
<div className="flex flex-wrap items-center gap-1.5 py-1">
|
|
292
|
+
<StatusBadge status={mapOrderStatus(job.status)} type="order" size="sm" />
|
|
293
|
+
<PriorityBadge priority={mapOrderPriority(job.priority)} size="sm" />
|
|
294
|
+
</div>
|
|
287
295
|
{job.deliveryAddress && (
|
|
288
296
|
<p className="text-sm text-text-secondary truncate">
|
|
289
297
|
{formatAddress(job.deliveryAddress)}
|