@burdenoff/microfe-movethewheels 2026.1026.3 → 2026.1030.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/MoveTheWheelsRoot.js +130 -122
- package/dist/MoveTheWheelsRoutes.js +55 -51
- package/dist/components/ProofOfDeliveryCard.js +1 -1
- package/dist/components/UpcomingDemandPanel.js +1 -1
- package/dist/components/marketplace/MarketplaceDialogs.js +524 -0
- package/dist/generated/wspace-types.d.ts +30737 -8505
- package/dist/hooks/useMarketplace.js +265 -0
- package/dist/movethewheels/components/marketplace/MarketplaceDialogs.d.ts +18 -0
- package/dist/movethewheels/hooks/useMarketplace.d.ts +121 -0
- package/dist/movethewheels/pages/MarketplacePage.d.ts +1 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/badge-check.js +11 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/chevron-down.js +8 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/chevron-up.js +8 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/pencil.js +11 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/store.js +26 -0
- package/dist/pages/ContainersPage.js +3 -3
- package/dist/pages/MarketplacePage.js +503 -0
- package/dist/pages/TrackingPage.js +111 -56
- package/package.json +2 -1
- package/src/generated/wspace-types.ts +34017 -8803
- package/src/movethewheels/MoveTheWheelsRoot.tsx +8 -0
- package/src/movethewheels/MoveTheWheelsRoutes.tsx +4 -0
- package/src/movethewheels/components/ProofOfDeliveryCard.tsx +1 -1
- package/src/movethewheels/components/UpcomingDemandPanel.tsx +2 -2
- package/src/movethewheels/components/marketplace/MarketplaceDialogs.tsx +633 -0
- package/src/movethewheels/hooks/useMarketplace.ts +526 -0
- package/src/movethewheels/pages/ContainersPage.tsx +3 -3
- package/src/movethewheels/pages/MarketplacePage.tsx +571 -0
- package/src/movethewheels/pages/TrackingPage.tsx +54 -9
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marketplace hooks (BOFF-3988, MOV-UC-13).
|
|
3
|
+
*
|
|
4
|
+
* The workspace's directory of vetted service providers and their offerings,
|
|
5
|
+
* served by wspace-movethewheels-svc's marketplace module. A provider becomes a
|
|
6
|
+
* shipment's carrier through `assignShipmentServiceProvider`.
|
|
7
|
+
*/
|
|
8
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
9
|
+
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
10
|
+
import { executeGraphQL } from '../utils/graphqlClient';
|
|
11
|
+
|
|
12
|
+
export const PROVIDER_TYPES = [
|
|
13
|
+
'CARRIER',
|
|
14
|
+
'FREIGHT_FORWARDER',
|
|
15
|
+
'THIRD_PARTY_LOGISTICS',
|
|
16
|
+
'WAREHOUSE',
|
|
17
|
+
'CUSTOMS_BROKER',
|
|
18
|
+
'LAST_MILE',
|
|
19
|
+
'COURIER',
|
|
20
|
+
'OTHER',
|
|
21
|
+
] as const;
|
|
22
|
+
export type ProviderType = (typeof PROVIDER_TYPES)[number];
|
|
23
|
+
|
|
24
|
+
export const OFFERING_CATEGORIES = [
|
|
25
|
+
'FULL_TRUCKLOAD',
|
|
26
|
+
'LESS_THAN_TRUCKLOAD',
|
|
27
|
+
'LAST_MILE',
|
|
28
|
+
'EXPRESS',
|
|
29
|
+
'OCEAN_FREIGHT',
|
|
30
|
+
'AIR_FREIGHT',
|
|
31
|
+
'RAIL_FREIGHT',
|
|
32
|
+
'WAREHOUSING',
|
|
33
|
+
'CUSTOMS_CLEARANCE',
|
|
34
|
+
'COLD_CHAIN',
|
|
35
|
+
'OTHER',
|
|
36
|
+
] as const;
|
|
37
|
+
export type OfferingCategory = (typeof OFFERING_CATEGORIES)[number];
|
|
38
|
+
|
|
39
|
+
export const AVAILABILITIES = ['AVAILABLE', 'LIMITED', 'UNAVAILABLE'] as const;
|
|
40
|
+
export type Availability = (typeof AVAILABILITIES)[number];
|
|
41
|
+
|
|
42
|
+
/** "THIRD_PARTY_LOGISTICS" → "Third party logistics". */
|
|
43
|
+
export function humanizeEnum(value: string): string {
|
|
44
|
+
const words = value.toLowerCase().split('_');
|
|
45
|
+
const text = words.join(' ');
|
|
46
|
+
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface ProviderContact {
|
|
50
|
+
contactName?: string;
|
|
51
|
+
email?: string;
|
|
52
|
+
phone?: string;
|
|
53
|
+
website?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ServiceOffering {
|
|
57
|
+
id: string;
|
|
58
|
+
providerId: string;
|
|
59
|
+
name: string;
|
|
60
|
+
description: string | null;
|
|
61
|
+
category: OfferingCategory;
|
|
62
|
+
price: number | null;
|
|
63
|
+
currency: string;
|
|
64
|
+
pricingUnit: string | null;
|
|
65
|
+
transitDays: number | null;
|
|
66
|
+
availability: Availability;
|
|
67
|
+
isActive: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ServiceProvider {
|
|
71
|
+
id: string;
|
|
72
|
+
name: string;
|
|
73
|
+
type: ProviderType;
|
|
74
|
+
description: string | null;
|
|
75
|
+
rating: number | null;
|
|
76
|
+
contact: ProviderContact;
|
|
77
|
+
services: string[];
|
|
78
|
+
serviceAreas: string[];
|
|
79
|
+
isVerified: boolean;
|
|
80
|
+
isActive: boolean;
|
|
81
|
+
offeringsCount: number;
|
|
82
|
+
createdAt: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface MarketplaceStats {
|
|
86
|
+
providers: number;
|
|
87
|
+
verifiedProviders: number;
|
|
88
|
+
activeProviders: number;
|
|
89
|
+
offerings: number;
|
|
90
|
+
avgRating: number | null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ProviderFilters {
|
|
94
|
+
search?: string;
|
|
95
|
+
type?: ProviderType;
|
|
96
|
+
isVerified?: boolean;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface ProviderInput {
|
|
100
|
+
name: string;
|
|
101
|
+
type: ProviderType;
|
|
102
|
+
description: string | null;
|
|
103
|
+
rating: number | null;
|
|
104
|
+
contactInfo: ProviderContact | null;
|
|
105
|
+
services: string[];
|
|
106
|
+
serviceAreas: string[];
|
|
107
|
+
isVerified: boolean;
|
|
108
|
+
isActive: boolean;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface OfferingInput {
|
|
112
|
+
name: string;
|
|
113
|
+
category: OfferingCategory;
|
|
114
|
+
description: string | null;
|
|
115
|
+
price: number | null;
|
|
116
|
+
currency: string;
|
|
117
|
+
pricingUnit: string | null;
|
|
118
|
+
transitDays: number | null;
|
|
119
|
+
availability: Availability;
|
|
120
|
+
isActive: boolean;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
interface ApiProvider {
|
|
124
|
+
id: string;
|
|
125
|
+
name: string;
|
|
126
|
+
type: ProviderType;
|
|
127
|
+
description?: string | null;
|
|
128
|
+
rating?: number | null;
|
|
129
|
+
contactInfo?: unknown;
|
|
130
|
+
services: string[];
|
|
131
|
+
serviceAreas: string[];
|
|
132
|
+
isVerified: boolean;
|
|
133
|
+
isActive: boolean;
|
|
134
|
+
offeringsCount: number;
|
|
135
|
+
createdAt: string;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const PROVIDER_FIELDS = `
|
|
139
|
+
id
|
|
140
|
+
name
|
|
141
|
+
type
|
|
142
|
+
description
|
|
143
|
+
rating
|
|
144
|
+
contactInfo
|
|
145
|
+
services
|
|
146
|
+
serviceAreas
|
|
147
|
+
isVerified
|
|
148
|
+
isActive
|
|
149
|
+
offeringsCount
|
|
150
|
+
createdAt
|
|
151
|
+
`;
|
|
152
|
+
|
|
153
|
+
const OFFERING_FIELDS = `
|
|
154
|
+
id
|
|
155
|
+
providerId
|
|
156
|
+
name
|
|
157
|
+
description
|
|
158
|
+
category
|
|
159
|
+
price
|
|
160
|
+
currency
|
|
161
|
+
pricingUnit
|
|
162
|
+
transitDays
|
|
163
|
+
availability
|
|
164
|
+
isActive
|
|
165
|
+
`;
|
|
166
|
+
|
|
167
|
+
const LIST_PROVIDERS_QUERY = `
|
|
168
|
+
query ListMtwServiceProviders($filter: MtwServiceProviderFilterInput, $pagination: PaginationInput) {
|
|
169
|
+
serviceProviders(filter: $filter, pagination: $pagination) {
|
|
170
|
+
${PROVIDER_FIELDS}
|
|
171
|
+
}
|
|
172
|
+
serviceProvidersCount(filter: $filter)
|
|
173
|
+
}
|
|
174
|
+
`;
|
|
175
|
+
|
|
176
|
+
const MARKETPLACE_STATS_QUERY = `
|
|
177
|
+
query MtwMarketplaceStats {
|
|
178
|
+
marketplaceStats {
|
|
179
|
+
providers
|
|
180
|
+
verifiedProviders
|
|
181
|
+
activeProviders
|
|
182
|
+
offerings
|
|
183
|
+
avgRating
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
`;
|
|
187
|
+
|
|
188
|
+
const LIST_OFFERINGS_QUERY = `
|
|
189
|
+
query ListMtwServiceOfferings($filter: MtwServiceOfferingFilterInput, $pagination: PaginationInput) {
|
|
190
|
+
serviceOfferings(filter: $filter, pagination: $pagination) {
|
|
191
|
+
${OFFERING_FIELDS}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
`;
|
|
195
|
+
|
|
196
|
+
const PROVIDER_SHIPMENTS_QUERY = `
|
|
197
|
+
query MtwProviderShipments($filter: ShipmentFilterInput, $pagination: PaginationInput) {
|
|
198
|
+
shipments(filter: $filter, pagination: $pagination) {
|
|
199
|
+
id
|
|
200
|
+
shipmentNumber
|
|
201
|
+
status
|
|
202
|
+
}
|
|
203
|
+
shipmentsCount(filter: $filter)
|
|
204
|
+
}
|
|
205
|
+
`;
|
|
206
|
+
|
|
207
|
+
const CREATE_PROVIDER_MUTATION = `
|
|
208
|
+
mutation CreateMtwServiceProvider($input: MtwCreateServiceProviderInput!) {
|
|
209
|
+
createServiceProvider(input: $input) {
|
|
210
|
+
${PROVIDER_FIELDS}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
`;
|
|
214
|
+
|
|
215
|
+
const UPDATE_PROVIDER_MUTATION = `
|
|
216
|
+
mutation UpdateMtwServiceProvider($id: ID!, $input: MtwUpdateServiceProviderInput!) {
|
|
217
|
+
updateServiceProvider(id: $id, input: $input) {
|
|
218
|
+
${PROVIDER_FIELDS}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
`;
|
|
222
|
+
|
|
223
|
+
const DELETE_PROVIDER_MUTATION = `
|
|
224
|
+
mutation DeleteMtwServiceProvider($id: ID!) {
|
|
225
|
+
deleteServiceProvider(id: $id)
|
|
226
|
+
}
|
|
227
|
+
`;
|
|
228
|
+
|
|
229
|
+
const CREATE_OFFERING_MUTATION = `
|
|
230
|
+
mutation CreateMtwServiceOffering($input: MtwCreateServiceOfferingInput!) {
|
|
231
|
+
createServiceOffering(input: $input) {
|
|
232
|
+
${OFFERING_FIELDS}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
`;
|
|
236
|
+
|
|
237
|
+
const UPDATE_OFFERING_MUTATION = `
|
|
238
|
+
mutation UpdateMtwServiceOffering($id: ID!, $input: MtwUpdateServiceOfferingInput!) {
|
|
239
|
+
updateServiceOffering(id: $id, input: $input) {
|
|
240
|
+
${OFFERING_FIELDS}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
`;
|
|
244
|
+
|
|
245
|
+
const DELETE_OFFERING_MUTATION = `
|
|
246
|
+
mutation DeleteMtwServiceOffering($id: ID!) {
|
|
247
|
+
deleteServiceOffering(id: $id)
|
|
248
|
+
}
|
|
249
|
+
`;
|
|
250
|
+
|
|
251
|
+
const ASSIGN_SHIPMENT_MUTATION = `
|
|
252
|
+
mutation AssignMtwShipmentServiceProvider($shipmentId: ID!, $providerId: ID) {
|
|
253
|
+
assignShipmentServiceProvider(shipmentId: $shipmentId, providerId: $providerId) {
|
|
254
|
+
id
|
|
255
|
+
shipmentNumber
|
|
256
|
+
carrierId
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
`;
|
|
260
|
+
|
|
261
|
+
function readContact(value: unknown): ProviderContact {
|
|
262
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
|
|
263
|
+
const record = value as Record<string, unknown>;
|
|
264
|
+
const pick = (key: string) =>
|
|
265
|
+
typeof record[key] === 'string' && (record[key] as string).trim()
|
|
266
|
+
? (record[key] as string)
|
|
267
|
+
: undefined;
|
|
268
|
+
return {
|
|
269
|
+
contactName: pick('contactName'),
|
|
270
|
+
email: pick('email'),
|
|
271
|
+
phone: pick('phone'),
|
|
272
|
+
website: pick('website'),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function mapProvider(provider: ApiProvider): ServiceProvider {
|
|
277
|
+
return {
|
|
278
|
+
id: provider.id,
|
|
279
|
+
name: provider.name,
|
|
280
|
+
type: provider.type,
|
|
281
|
+
description: provider.description ?? null,
|
|
282
|
+
rating: provider.rating ?? null,
|
|
283
|
+
contact: readContact(provider.contactInfo),
|
|
284
|
+
services: provider.services,
|
|
285
|
+
serviceAreas: provider.serviceAreas,
|
|
286
|
+
isVerified: provider.isVerified,
|
|
287
|
+
isActive: provider.isActive,
|
|
288
|
+
offeringsCount: provider.offeringsCount,
|
|
289
|
+
createdAt: provider.createdAt,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function useGraphQLOptions() {
|
|
294
|
+
const { apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser } =
|
|
295
|
+
useMoveTheWheels();
|
|
296
|
+
return useMemo(
|
|
297
|
+
() => ({
|
|
298
|
+
apiGatewayUrl,
|
|
299
|
+
authToken,
|
|
300
|
+
workspaceToken,
|
|
301
|
+
workspaceId,
|
|
302
|
+
tenantId,
|
|
303
|
+
actorId: currentUser?.id,
|
|
304
|
+
}),
|
|
305
|
+
[apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser?.id]
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function toError(error: unknown, fallback: string): Error {
|
|
310
|
+
return error instanceof Error ? error : new Error(fallback);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export function useServiceProviders(options: {
|
|
314
|
+
filters: ProviderFilters;
|
|
315
|
+
limit: number;
|
|
316
|
+
offset: number;
|
|
317
|
+
}) {
|
|
318
|
+
const graphQLOptions = useGraphQLOptions();
|
|
319
|
+
const { limit, offset } = options;
|
|
320
|
+
const { search, type, isVerified } = options.filters;
|
|
321
|
+
const [providers, setProviders] = useState<ServiceProvider[]>([]);
|
|
322
|
+
const [total, setTotal] = useState(0);
|
|
323
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
324
|
+
const [error, setError] = useState<Error | null>(null);
|
|
325
|
+
|
|
326
|
+
const load = useCallback(async () => {
|
|
327
|
+
setIsLoading(true);
|
|
328
|
+
setError(null);
|
|
329
|
+
try {
|
|
330
|
+
const data = await executeGraphQL<{
|
|
331
|
+
serviceProviders: ApiProvider[];
|
|
332
|
+
serviceProvidersCount: number;
|
|
333
|
+
}>(graphQLOptions, LIST_PROVIDERS_QUERY, {
|
|
334
|
+
filter: {
|
|
335
|
+
search: search?.trim() || undefined,
|
|
336
|
+
type,
|
|
337
|
+
isVerified,
|
|
338
|
+
},
|
|
339
|
+
pagination: { limit, offset },
|
|
340
|
+
});
|
|
341
|
+
setProviders(data.serviceProviders.map(mapProvider));
|
|
342
|
+
setTotal(data.serviceProvidersCount);
|
|
343
|
+
} catch (loadError) {
|
|
344
|
+
setError(toError(loadError, 'Failed to load service providers'));
|
|
345
|
+
} finally {
|
|
346
|
+
setIsLoading(false);
|
|
347
|
+
}
|
|
348
|
+
}, [graphQLOptions, search, type, isVerified, limit, offset]);
|
|
349
|
+
|
|
350
|
+
useEffect(() => {
|
|
351
|
+
void load();
|
|
352
|
+
}, [load]);
|
|
353
|
+
|
|
354
|
+
return { providers, total, isLoading, error, refetch: load };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export function useMarketplaceStats(refreshKey: number) {
|
|
358
|
+
const graphQLOptions = useGraphQLOptions();
|
|
359
|
+
const [stats, setStats] = useState<MarketplaceStats | null>(null);
|
|
360
|
+
|
|
361
|
+
useEffect(() => {
|
|
362
|
+
let cancelled = false;
|
|
363
|
+
executeGraphQL<{ marketplaceStats: MarketplaceStats }>(graphQLOptions, MARKETPLACE_STATS_QUERY)
|
|
364
|
+
.then((data) => {
|
|
365
|
+
if (!cancelled) setStats(data.marketplaceStats);
|
|
366
|
+
})
|
|
367
|
+
.catch(() => {
|
|
368
|
+
// Tiles fall back to "—"; the directory itself reports load errors.
|
|
369
|
+
if (!cancelled) setStats(null);
|
|
370
|
+
});
|
|
371
|
+
return () => {
|
|
372
|
+
cancelled = true;
|
|
373
|
+
};
|
|
374
|
+
}, [graphQLOptions, refreshKey]);
|
|
375
|
+
|
|
376
|
+
return stats;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export function useProviderOfferings(providerId: string | null, refreshKey: number) {
|
|
380
|
+
const graphQLOptions = useGraphQLOptions();
|
|
381
|
+
const [offerings, setOfferings] = useState<ServiceOffering[]>([]);
|
|
382
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
383
|
+
const [error, setError] = useState<Error | null>(null);
|
|
384
|
+
|
|
385
|
+
useEffect(() => {
|
|
386
|
+
if (!providerId) {
|
|
387
|
+
setOfferings([]);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
let cancelled = false;
|
|
391
|
+
setIsLoading(true);
|
|
392
|
+
setError(null);
|
|
393
|
+
executeGraphQL<{ serviceOfferings: ServiceOffering[] }>(graphQLOptions, LIST_OFFERINGS_QUERY, {
|
|
394
|
+
filter: { providerId },
|
|
395
|
+
pagination: { limit: 100, offset: 0 },
|
|
396
|
+
})
|
|
397
|
+
.then((data) => {
|
|
398
|
+
if (!cancelled) setOfferings(data.serviceOfferings);
|
|
399
|
+
})
|
|
400
|
+
.catch((loadError: unknown) => {
|
|
401
|
+
if (!cancelled) setError(toError(loadError, 'Failed to load offerings'));
|
|
402
|
+
})
|
|
403
|
+
.finally(() => {
|
|
404
|
+
if (!cancelled) setIsLoading(false);
|
|
405
|
+
});
|
|
406
|
+
return () => {
|
|
407
|
+
cancelled = true;
|
|
408
|
+
};
|
|
409
|
+
}, [graphQLOptions, providerId, refreshKey]);
|
|
410
|
+
|
|
411
|
+
return { offerings, isLoading, error };
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export interface ShipmentOption {
|
|
415
|
+
id: string;
|
|
416
|
+
shipmentNumber: string;
|
|
417
|
+
status: string;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** Shipments, optionally only those whose carrier is the given provider. */
|
|
421
|
+
export function useShipmentOptions(options: {
|
|
422
|
+
search?: string;
|
|
423
|
+
carrierId?: string;
|
|
424
|
+
limit: number;
|
|
425
|
+
}) {
|
|
426
|
+
const graphQLOptions = useGraphQLOptions();
|
|
427
|
+
const { search, carrierId, limit } = options;
|
|
428
|
+
const [shipments, setShipments] = useState<ShipmentOption[]>([]);
|
|
429
|
+
const [total, setTotal] = useState(0);
|
|
430
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
431
|
+
|
|
432
|
+
const load = useCallback(async () => {
|
|
433
|
+
setIsLoading(true);
|
|
434
|
+
try {
|
|
435
|
+
const data = await executeGraphQL<{ shipments: ShipmentOption[]; shipmentsCount: number }>(
|
|
436
|
+
graphQLOptions,
|
|
437
|
+
PROVIDER_SHIPMENTS_QUERY,
|
|
438
|
+
{
|
|
439
|
+
filter: { search: search?.trim() || undefined, carrierId },
|
|
440
|
+
pagination: { limit, offset: 0 },
|
|
441
|
+
}
|
|
442
|
+
);
|
|
443
|
+
setShipments(data.shipments);
|
|
444
|
+
setTotal(data.shipmentsCount);
|
|
445
|
+
} catch {
|
|
446
|
+
setShipments([]);
|
|
447
|
+
setTotal(0);
|
|
448
|
+
} finally {
|
|
449
|
+
setIsLoading(false);
|
|
450
|
+
}
|
|
451
|
+
}, [graphQLOptions, search, carrierId, limit]);
|
|
452
|
+
|
|
453
|
+
useEffect(() => {
|
|
454
|
+
void load();
|
|
455
|
+
}, [load]);
|
|
456
|
+
|
|
457
|
+
return { shipments, total, isLoading, refetch: load };
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export function useMarketplaceActions() {
|
|
461
|
+
const graphQLOptions = useGraphQLOptions();
|
|
462
|
+
|
|
463
|
+
return useMemo(
|
|
464
|
+
() => ({
|
|
465
|
+
createProvider: async (input: ProviderInput) => {
|
|
466
|
+
const data = await executeGraphQL<{ createServiceProvider: ApiProvider }>(
|
|
467
|
+
graphQLOptions,
|
|
468
|
+
CREATE_PROVIDER_MUTATION,
|
|
469
|
+
{ input }
|
|
470
|
+
);
|
|
471
|
+
return mapProvider(data.createServiceProvider);
|
|
472
|
+
},
|
|
473
|
+
updateProvider: async (id: string, input: Partial<ProviderInput>) => {
|
|
474
|
+
const data = await executeGraphQL<{ updateServiceProvider: ApiProvider }>(
|
|
475
|
+
graphQLOptions,
|
|
476
|
+
UPDATE_PROVIDER_MUTATION,
|
|
477
|
+
{ id, input }
|
|
478
|
+
);
|
|
479
|
+
return mapProvider(data.updateServiceProvider);
|
|
480
|
+
},
|
|
481
|
+
deleteProvider: async (id: string) => {
|
|
482
|
+
const data = await executeGraphQL<{ deleteServiceProvider: boolean }>(
|
|
483
|
+
graphQLOptions,
|
|
484
|
+
DELETE_PROVIDER_MUTATION,
|
|
485
|
+
{ id }
|
|
486
|
+
);
|
|
487
|
+
return data.deleteServiceProvider;
|
|
488
|
+
},
|
|
489
|
+
createOffering: async (providerId: string, input: OfferingInput) => {
|
|
490
|
+
const data = await executeGraphQL<{ createServiceOffering: ServiceOffering }>(
|
|
491
|
+
graphQLOptions,
|
|
492
|
+
CREATE_OFFERING_MUTATION,
|
|
493
|
+
{ input: { ...input, providerId } }
|
|
494
|
+
);
|
|
495
|
+
return data.createServiceOffering;
|
|
496
|
+
},
|
|
497
|
+
updateOffering: async (id: string, input: Partial<OfferingInput>) => {
|
|
498
|
+
const data = await executeGraphQL<{ updateServiceOffering: ServiceOffering }>(
|
|
499
|
+
graphQLOptions,
|
|
500
|
+
UPDATE_OFFERING_MUTATION,
|
|
501
|
+
{ id, input }
|
|
502
|
+
);
|
|
503
|
+
return data.updateServiceOffering;
|
|
504
|
+
},
|
|
505
|
+
deleteOffering: async (id: string) => {
|
|
506
|
+
const data = await executeGraphQL<{ deleteServiceOffering: boolean }>(
|
|
507
|
+
graphQLOptions,
|
|
508
|
+
DELETE_OFFERING_MUTATION,
|
|
509
|
+
{ id }
|
|
510
|
+
);
|
|
511
|
+
return data.deleteServiceOffering;
|
|
512
|
+
},
|
|
513
|
+
assignShipment: async (shipmentId: string, providerId: string | null) => {
|
|
514
|
+
const data = await executeGraphQL<{
|
|
515
|
+
assignShipmentServiceProvider: {
|
|
516
|
+
id: string;
|
|
517
|
+
shipmentNumber: string;
|
|
518
|
+
carrierId: string | null;
|
|
519
|
+
};
|
|
520
|
+
}>(graphQLOptions, ASSIGN_SHIPMENT_MUTATION, { shipmentId, providerId });
|
|
521
|
+
return data.assignShipmentServiceProvider;
|
|
522
|
+
},
|
|
523
|
+
}),
|
|
524
|
+
[graphQLOptions]
|
|
525
|
+
);
|
|
526
|
+
}
|
|
@@ -142,7 +142,7 @@ function ContainerWorkbench({ access }: { access: ReturnType<typeof useShipmentP
|
|
|
142
142
|
actual empty return and recorded return acceptance.
|
|
143
143
|
</p>
|
|
144
144
|
{actionError && (
|
|
145
|
-
<p role="alert" className="mb-4 rounded-md border border-
|
|
145
|
+
<p role="alert" className="mb-4 rounded-md border border-status-error-border p-3">
|
|
146
146
|
{actionError}
|
|
147
147
|
</p>
|
|
148
148
|
)}
|
|
@@ -178,7 +178,7 @@ function ContainerWorkbench({ access }: { access: ReturnType<typeof useShipmentP
|
|
|
178
178
|
setActionError(null);
|
|
179
179
|
setNotice(null);
|
|
180
180
|
}}
|
|
181
|
-
className={`w-full rounded-md border p-3 text-left ${row.id === data.containerId ? 'border-
|
|
181
|
+
className={`w-full rounded-md border p-3 text-left ${row.id === data.containerId ? 'border-action-primary-border' : ''}`}
|
|
182
182
|
>
|
|
183
183
|
<span className="font-semibold">{row.containerNumber}</span>
|
|
184
184
|
<br />
|
|
@@ -269,7 +269,7 @@ function ContainerWorkbench({ access }: { access: ReturnType<typeof useShipmentP
|
|
|
269
269
|
type="button"
|
|
270
270
|
aria-pressed={row.id === data.cycleId}
|
|
271
271
|
disabled={busy}
|
|
272
|
-
className={`w-full rounded-md border p-3 text-left ${row.id === data.cycleId ? 'border-
|
|
272
|
+
className={`w-full rounded-md border p-3 text-left ${row.id === data.cycleId ? 'border-action-primary-border' : ''}`}
|
|
273
273
|
onClick={() => {
|
|
274
274
|
data.selectCycle(row.id);
|
|
275
275
|
setActionError(null);
|