@burdenoff/microfe-movethewheels 2026.929.2 → 2026.929.4
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-types.d.ts +1547 -64
- package/dist/hooks/useCustomers.js +3 -9
- package/dist/hooks/useFleet.js +147 -150
- package/dist/hooks/useInventory.js +178 -153
- package/dist/hooks/useRoutes.js +9 -9
- package/dist/hooks/useTracking.js +14 -16
- package/dist/movethewheels/hooks/useFleet.d.ts +11 -4
- package/dist/movethewheels/hooks/useInventory.d.ts +13 -12
- package/dist/movethewheels/hooks/useRoutes.d.ts +4 -1
- package/dist/movethewheels/types/index.d.ts +33 -23
- package/dist/movethewheels/utils/formatters.d.ts +1 -1
- package/dist/pages/CustomerDetailsPage.js +6 -6
- package/dist/pages/CustomersPage.js +2 -2
- package/dist/pages/DashboardPage.js +1 -1
- package/dist/pages/DriverDetailsPage.js +68 -43
- package/dist/pages/DriversPage.js +4 -4
- package/dist/pages/FleetPage.js +1 -1
- package/dist/pages/InventoryPage.js +11 -12
- package/dist/pages/LiveTrackingPage.js +4 -4
- package/dist/pages/ProductDetailsPage.js +408 -249
- package/dist/pages/ProductsPage.js +5 -5
- package/dist/pages/RouteDetailsPage.js +8 -10
- package/dist/pages/RoutesPage.js +2 -2
- package/dist/pages/TrackingPage.js +14 -8
- package/dist/pages/WarehouseDetailsPage.js +166 -163
- package/dist/pages/WarehousesPage.js +29 -21
- package/package.json +1 -1
- package/src/generated/wspace-types.ts +1762 -64
- package/src/movethewheels/hooks/useCustomers.ts +7 -9
- package/src/movethewheels/hooks/useFleet.ts +92 -34
- package/src/movethewheels/hooks/useInventory.ts +143 -49
- package/src/movethewheels/hooks/useRoutes.ts +36 -16
- package/src/movethewheels/hooks/useTracking.ts +23 -15
- package/src/movethewheels/pages/CustomerDetailsPage.tsx +14 -8
- package/src/movethewheels/pages/CustomersPage.tsx +5 -2
- package/src/movethewheels/pages/DashboardPage.tsx +3 -1
- package/src/movethewheels/pages/DriverDetailsPage.tsx +44 -12
- package/src/movethewheels/pages/DriversPage.tsx +1 -1
- package/src/movethewheels/pages/FleetPage.tsx +1 -1
- package/src/movethewheels/pages/InventoryPage.tsx +20 -16
- package/src/movethewheels/pages/LiveTrackingPage.tsx +6 -4
- package/src/movethewheels/pages/ProductDetailsPage.tsx +228 -49
- package/src/movethewheels/pages/ProductsPage.tsx +13 -5
- package/src/movethewheels/pages/RouteDetailsPage.tsx +16 -8
- package/src/movethewheels/pages/RoutesPage.tsx +4 -2
- package/src/movethewheels/pages/TrackingPage.tsx +25 -10
- package/src/movethewheels/pages/WarehouseDetailsPage.tsx +42 -29
- package/src/movethewheels/pages/WarehousesPage.tsx +23 -15
- package/src/movethewheels/types/index.ts +36 -24
- package/src/movethewheels/utils/formatters.ts +1 -1
|
@@ -21,7 +21,13 @@ interface UseRoutesResult {
|
|
|
21
21
|
error: Error | null;
|
|
22
22
|
refetch: () => void;
|
|
23
23
|
createRoute: (route: Partial<Route>) => Promise<Route>;
|
|
24
|
-
updateRoute: (
|
|
24
|
+
updateRoute: (
|
|
25
|
+
id: string,
|
|
26
|
+
updates: Omit<Partial<Route>, 'vehicleId' | 'driverId'> & {
|
|
27
|
+
vehicleId?: string | null;
|
|
28
|
+
driverId?: string | null;
|
|
29
|
+
}
|
|
30
|
+
) => Promise<Route>;
|
|
25
31
|
deleteRoute: (id: string) => Promise<void>;
|
|
26
32
|
optimizeRoute: (routeId: string) => Promise<Route>;
|
|
27
33
|
updateStopStatus: (
|
|
@@ -205,13 +211,19 @@ function mapWaypoints(waypoints: unknown): RouteStop[] {
|
|
|
205
211
|
state: String(addrObj.state ?? item.state ?? ''),
|
|
206
212
|
postalCode: String(addrObj.postalCode ?? item.postalCode ?? ''),
|
|
207
213
|
country: String(addrObj.country ?? item.country ?? ''),
|
|
208
|
-
latitude:
|
|
209
|
-
|
|
214
|
+
latitude:
|
|
215
|
+
typeof (addrObj.latitude ?? item.latitude ?? item.lat) === 'number'
|
|
216
|
+
? ((addrObj.latitude ?? item.latitude ?? item.lat) as number)
|
|
217
|
+
: undefined,
|
|
218
|
+
longitude:
|
|
219
|
+
typeof (addrObj.longitude ?? item.longitude ?? item.lng) === 'number'
|
|
220
|
+
? ((addrObj.longitude ?? item.longitude ?? item.lng) as number)
|
|
221
|
+
: undefined,
|
|
210
222
|
},
|
|
211
223
|
type: item.type === 'pickup' ? 'pickup' : 'delivery',
|
|
212
|
-
scheduledTime: item.scheduledTime ? new Date(String(item.scheduledTime)) :
|
|
224
|
+
scheduledTime: item.scheduledTime ? new Date(String(item.scheduledTime)) : undefined,
|
|
213
225
|
actualTime: item.actualTime ? new Date(String(item.actualTime)) : undefined,
|
|
214
|
-
duration:
|
|
226
|
+
duration: typeof item.duration === 'number' ? item.duration : undefined,
|
|
215
227
|
status: (item.status as RouteStop['status']) ?? 'pending',
|
|
216
228
|
notes: item.notes ? String(item.notes) : undefined,
|
|
217
229
|
};
|
|
@@ -222,8 +234,8 @@ export function mapRoute(route: ApiRoute): Route {
|
|
|
222
234
|
return {
|
|
223
235
|
id: route.id,
|
|
224
236
|
name: route.name,
|
|
225
|
-
driverId: route.driverId ??
|
|
226
|
-
vehicleId: route.vehicleId ??
|
|
237
|
+
driverId: route.driverId ?? undefined,
|
|
238
|
+
vehicleId: route.vehicleId ?? undefined,
|
|
227
239
|
status: mapRouteStatus(route.status),
|
|
228
240
|
stops: mapWaypoints(route.waypoints),
|
|
229
241
|
// Prefer the real post-drive distance once it exists; the backend fetches
|
|
@@ -232,12 +244,10 @@ export function mapRoute(route: ApiRoute): Route {
|
|
|
232
244
|
estimatedDuration: route.estimatedDuration ?? 0,
|
|
233
245
|
fuelCost: route.fuelCost ?? undefined,
|
|
234
246
|
actualDuration: route.actualDuration ?? undefined,
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
// route that really had been optimized (BOFF-7909 follow-up).
|
|
240
|
-
optimized: (route.estimatedDistance ?? 0) > 0,
|
|
247
|
+
// The route object has no optimization marker. `estimatedDistance` is a
|
|
248
|
+
// measurement, not proof that the optimize mutation ran, so leave this
|
|
249
|
+
// unknown rather than presenting an inferred Yes/No.
|
|
250
|
+
optimized: undefined,
|
|
241
251
|
createdAt: new Date(route.createdAt),
|
|
242
252
|
};
|
|
243
253
|
}
|
|
@@ -334,7 +344,13 @@ export function useRoutes(options: UseRoutesOptions = {}): UseRoutesResult {
|
|
|
334
344
|
);
|
|
335
345
|
|
|
336
346
|
const updateRoute = useCallback(
|
|
337
|
-
async (
|
|
347
|
+
async (
|
|
348
|
+
id: string,
|
|
349
|
+
updates: Omit<Partial<Route>, 'vehicleId' | 'driverId'> & {
|
|
350
|
+
vehicleId?: string | null;
|
|
351
|
+
driverId?: string | null;
|
|
352
|
+
}
|
|
353
|
+
): Promise<Route> => {
|
|
338
354
|
const data = await executeGraphQL<{ updateRoute: ApiRoute }>(
|
|
339
355
|
graphQLOptions,
|
|
340
356
|
UPDATE_ROUTE_MUTATION,
|
|
@@ -344,8 +360,12 @@ export function useRoutes(options: UseRoutesOptions = {}): UseRoutesResult {
|
|
|
344
360
|
name: updates.name,
|
|
345
361
|
status: toApiRouteStatus(updates.status),
|
|
346
362
|
shipmentId: undefined,
|
|
347
|
-
vehicleId: updates
|
|
348
|
-
|
|
363
|
+
vehicleId: Object.prototype.hasOwnProperty.call(updates, 'vehicleId')
|
|
364
|
+
? (updates.vehicleId ?? null)
|
|
365
|
+
: undefined,
|
|
366
|
+
driverId: Object.prototype.hasOwnProperty.call(updates, 'driverId')
|
|
367
|
+
? (updates.driverId ?? null)
|
|
368
|
+
: undefined,
|
|
349
369
|
waypoints: updates.stops,
|
|
350
370
|
// See createRoute — the edit form doesn't collect a distance
|
|
351
371
|
// either, and `distance` must never masquerade as `actualDistance`.
|
|
@@ -209,11 +209,23 @@ const TRACKING_ALERTS_QUERY = `
|
|
|
209
209
|
}
|
|
210
210
|
`;
|
|
211
211
|
|
|
212
|
-
function mapTrackingEventToLocation(event: ApiTrackingEvent): Location {
|
|
212
|
+
function mapTrackingEventToLocation(event: ApiTrackingEvent): Location | null {
|
|
213
|
+
const raw = event.location;
|
|
214
|
+
if (!raw) return null;
|
|
215
|
+
const latitude = raw.latitude ?? raw.lat;
|
|
216
|
+
const longitude = raw.longitude ?? raw.lng;
|
|
217
|
+
if (
|
|
218
|
+
typeof latitude !== 'number' ||
|
|
219
|
+
!Number.isFinite(latitude) ||
|
|
220
|
+
typeof longitude !== 'number' ||
|
|
221
|
+
!Number.isFinite(longitude)
|
|
222
|
+
) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
213
225
|
return {
|
|
214
|
-
latitude
|
|
215
|
-
longitude
|
|
216
|
-
address:
|
|
226
|
+
latitude,
|
|
227
|
+
longitude,
|
|
228
|
+
address: typeof raw.address === 'string' ? raw.address : undefined,
|
|
217
229
|
timestamp: new Date(event.timestamp),
|
|
218
230
|
};
|
|
219
231
|
}
|
|
@@ -225,9 +237,10 @@ function mapTrackingEventsToTracking(
|
|
|
225
237
|
const latest = events[0];
|
|
226
238
|
if (!latest) return null;
|
|
227
239
|
|
|
228
|
-
const locationHistory = events
|
|
240
|
+
const locationHistory = events
|
|
241
|
+
.map(mapTrackingEventToLocation)
|
|
242
|
+
.filter((location): location is Location => location !== null);
|
|
229
243
|
const currentLocation = locationHistory[0];
|
|
230
|
-
if (!currentLocation) return null;
|
|
231
244
|
|
|
232
245
|
return {
|
|
233
246
|
id: latest.id,
|
|
@@ -307,11 +320,7 @@ export function useTracking(options: UseTrackingOptions = {}): UseTrackingResult
|
|
|
307
320
|
id: shipment.id,
|
|
308
321
|
entityId: entityId ?? shipment.deliveryJobId ?? shipment.id,
|
|
309
322
|
entityType: entityType ?? 'order',
|
|
310
|
-
currentLocation:
|
|
311
|
-
latitude: 0,
|
|
312
|
-
longitude: 0,
|
|
313
|
-
timestamp: new Date(),
|
|
314
|
-
},
|
|
323
|
+
currentLocation: undefined,
|
|
315
324
|
locationHistory: [],
|
|
316
325
|
status: shipment.status.toLowerCase(),
|
|
317
326
|
estimatedArrival: shipment.estimatedDelivery
|
|
@@ -319,7 +328,7 @@ export function useTracking(options: UseTrackingOptions = {}): UseTrackingResult
|
|
|
319
328
|
: undefined,
|
|
320
329
|
nextCheckpoint: shipment.shipmentNumber,
|
|
321
330
|
alerts: [],
|
|
322
|
-
lastUpdated:
|
|
331
|
+
lastUpdated: undefined,
|
|
323
332
|
}));
|
|
324
333
|
|
|
325
334
|
setTracking(mapped);
|
|
@@ -518,7 +527,7 @@ export function useVehicleTracking(vehicleId: string | undefined) {
|
|
|
518
527
|
currentLocation: vehicle.currentLocation,
|
|
519
528
|
locationHistory: [vehicle.currentLocation],
|
|
520
529
|
status: vehicle.status,
|
|
521
|
-
lastUpdated: vehicle.currentLocation.timestamp
|
|
530
|
+
lastUpdated: vehicle.currentLocation.timestamp,
|
|
522
531
|
alerts: [],
|
|
523
532
|
};
|
|
524
533
|
}, [vehicle]);
|
|
@@ -538,7 +547,7 @@ export function useDriverTracking(driverId: string | undefined) {
|
|
|
538
547
|
currentLocation: driver.currentLocation,
|
|
539
548
|
locationHistory: [driver.currentLocation],
|
|
540
549
|
status: driver.status,
|
|
541
|
-
lastUpdated: driver.currentLocation.timestamp
|
|
550
|
+
lastUpdated: driver.currentLocation.timestamp,
|
|
542
551
|
alerts: [],
|
|
543
552
|
};
|
|
544
553
|
}, [driver]);
|
|
@@ -622,7 +631,6 @@ export function useTrackingAlerts() {
|
|
|
622
631
|
type: 'delay',
|
|
623
632
|
severity: 'medium',
|
|
624
633
|
message: `${alert.shipmentNumber}: ${alert.message}`,
|
|
625
|
-
timestamp: new Date(),
|
|
626
634
|
acknowledged: false,
|
|
627
635
|
}))
|
|
628
636
|
);
|
|
@@ -333,10 +333,16 @@ function customerToForm(customer: {
|
|
|
333
333
|
phone: string;
|
|
334
334
|
company?: string;
|
|
335
335
|
type: 'b2b' | 'b2c';
|
|
336
|
-
address: {
|
|
336
|
+
address: Partial<{
|
|
337
|
+
street: string;
|
|
338
|
+
city: string;
|
|
339
|
+
state: string;
|
|
340
|
+
postalCode: string;
|
|
341
|
+
country: string;
|
|
342
|
+
}>;
|
|
337
343
|
businessLicense?: string;
|
|
338
344
|
taxId?: string;
|
|
339
|
-
rating
|
|
345
|
+
rating?: number;
|
|
340
346
|
}): CustomerFormState {
|
|
341
347
|
return {
|
|
342
348
|
name: customer.name,
|
|
@@ -344,11 +350,11 @@ function customerToForm(customer: {
|
|
|
344
350
|
phone: customer.phone,
|
|
345
351
|
company: customer.company ?? '',
|
|
346
352
|
type: customer.type,
|
|
347
|
-
street: customer.address.street,
|
|
348
|
-
city: customer.address.city,
|
|
349
|
-
state: customer.address.state,
|
|
350
|
-
postalCode: customer.address.postalCode,
|
|
351
|
-
country: customer.address.country,
|
|
353
|
+
street: customer.address.street ?? '',
|
|
354
|
+
city: customer.address.city ?? '',
|
|
355
|
+
state: customer.address.state ?? '',
|
|
356
|
+
postalCode: customer.address.postalCode ?? '',
|
|
357
|
+
country: customer.address.country ?? '',
|
|
352
358
|
businessLicense: customer.businessLicense ?? '',
|
|
353
359
|
taxId: customer.taxId ?? '',
|
|
354
360
|
rating: customer.rating ? String(customer.rating) : '',
|
|
@@ -480,7 +486,7 @@ export default function CustomerDetailsPage() {
|
|
|
480
486
|
headerContent={
|
|
481
487
|
<div className="flex items-center gap-4">
|
|
482
488
|
<TypeBadge type={customer.type} />
|
|
483
|
-
{customer.rating
|
|
489
|
+
{customer.rating !== undefined && (
|
|
484
490
|
<div className="flex items-center gap-1">
|
|
485
491
|
<Star size={16} className="text-status-warning-text fill-current" />
|
|
486
492
|
<span className="font-medium">{customer.rating.toFixed(1)}</span>
|
|
@@ -65,7 +65,10 @@ export default function CustomersPage() {
|
|
|
65
65
|
{
|
|
66
66
|
id: 'location',
|
|
67
67
|
header: 'Location',
|
|
68
|
-
cell: (row) =>
|
|
68
|
+
cell: (row) => {
|
|
69
|
+
const location = [row.address.city, row.address.state].filter(Boolean).join(', ');
|
|
70
|
+
return location || 'Not provided';
|
|
71
|
+
},
|
|
69
72
|
},
|
|
70
73
|
{
|
|
71
74
|
id: 'rating',
|
|
@@ -73,7 +76,7 @@ export default function CustomersPage() {
|
|
|
73
76
|
cell: (row) => (
|
|
74
77
|
<div className="flex items-center gap-1">
|
|
75
78
|
<Star size={14} className="text-status-warning-text fill-current" />
|
|
76
|
-
<span>{row.rating.toFixed(1)}</span>
|
|
79
|
+
<span>{row.rating !== undefined ? row.rating.toFixed(1) : 'Not provided'}</span>
|
|
77
80
|
</div>
|
|
78
81
|
),
|
|
79
82
|
},
|
|
@@ -277,7 +277,9 @@ export default function DashboardPage() {
|
|
|
277
277
|
>
|
|
278
278
|
<p className="font-medium">{alert.message}</p>
|
|
279
279
|
<p className="mt-1 text-xs text-text-secondary">
|
|
280
|
-
{
|
|
280
|
+
{alert.timestamp
|
|
281
|
+
? formatDate(alert.timestamp, 'relative')
|
|
282
|
+
: 'Time not recorded'}
|
|
281
283
|
</p>
|
|
282
284
|
</div>
|
|
283
285
|
))}
|
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
useVehicle,
|
|
54
54
|
useVehicles,
|
|
55
55
|
toApiDriverStatus,
|
|
56
|
+
type CreateDriverInput,
|
|
56
57
|
type UpdateDriverInput,
|
|
57
58
|
} from '../hooks/useFleet';
|
|
58
59
|
import { useDebouncedValue } from '../hooks/useDebouncedValue';
|
|
@@ -85,6 +86,7 @@ interface DriverFormState {
|
|
|
85
86
|
licenseExpiry: string;
|
|
86
87
|
status: DriverStatus;
|
|
87
88
|
vehicleId: string;
|
|
89
|
+
vehicleIds: string[];
|
|
88
90
|
rating: string;
|
|
89
91
|
notes: string;
|
|
90
92
|
hireDate: string;
|
|
@@ -105,6 +107,7 @@ const emptyForm: DriverFormState = {
|
|
|
105
107
|
licenseExpiry: '',
|
|
106
108
|
status: 'available',
|
|
107
109
|
vehicleId: '',
|
|
110
|
+
vehicleIds: [],
|
|
108
111
|
rating: '',
|
|
109
112
|
notes: '',
|
|
110
113
|
hireDate: '',
|
|
@@ -151,6 +154,7 @@ function NewDriverForm({ editId }: { editId?: string }) {
|
|
|
151
154
|
licenseExpiry: toDateInput(driver.licenseExpiry),
|
|
152
155
|
status: driver.status,
|
|
153
156
|
vehicleId: driver.assignedVehicle || '',
|
|
157
|
+
vehicleIds: driver.vehicleIds,
|
|
154
158
|
rating: driver.rating ? String(driver.rating) : '',
|
|
155
159
|
notes: driver.notes || '',
|
|
156
160
|
hireDate: toDateInput(driver.hireDate),
|
|
@@ -163,8 +167,7 @@ function NewDriverForm({ editId }: { editId?: string }) {
|
|
|
163
167
|
|
|
164
168
|
const goBack = () => navigate?.(joinPath(basePath, 'fleet/drivers'));
|
|
165
169
|
|
|
166
|
-
const buildPayload = (): UpdateDriverInput => ({
|
|
167
|
-
employeeId: form.employeeId.trim(),
|
|
170
|
+
const buildPayload = (includeLeaveFields = true): UpdateDriverInput => ({
|
|
168
171
|
firstName: form.firstName.trim(),
|
|
169
172
|
lastName: form.lastName.trim(),
|
|
170
173
|
email: form.email.trim() || undefined,
|
|
@@ -173,13 +176,20 @@ function NewDriverForm({ editId }: { editId?: string }) {
|
|
|
173
176
|
licenseClass: form.licenseClass.trim() || undefined,
|
|
174
177
|
licenseExpiry: fromDateInput(form.licenseExpiry),
|
|
175
178
|
status: toApiDriverStatus(form.status),
|
|
176
|
-
vehicleId: form.vehicleId ||
|
|
177
|
-
|
|
179
|
+
vehicleId: form.vehicleId || null,
|
|
180
|
+
vehicleIds: form.vehicleIds,
|
|
181
|
+
rating: form.rating.trim() ? Number(form.rating) : undefined,
|
|
178
182
|
notes: form.notes.trim() || undefined,
|
|
179
183
|
hireDate: fromDateInput(form.hireDate),
|
|
180
|
-
baseSalary: form.baseSalary ? Number(form.baseSalary) : undefined,
|
|
181
|
-
bonusYtd: form.bonusYtd ? Number(form.bonusYtd) : undefined,
|
|
184
|
+
baseSalary: form.baseSalary.trim() ? Number(form.baseSalary) : undefined,
|
|
185
|
+
bonusYtd: form.bonusYtd.trim() ? Number(form.bonusYtd) : undefined,
|
|
182
186
|
payCurrency: form.payCurrency.trim() || undefined,
|
|
187
|
+
...(includeLeaveFields ? { onLeaveUntil: fromDateInput(form.onLeaveUntil) ?? null } : {}),
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const buildCreatePayload = (): CreateDriverInput => ({
|
|
191
|
+
employeeId: form.employeeId.trim(),
|
|
192
|
+
...buildPayload(false),
|
|
183
193
|
});
|
|
184
194
|
|
|
185
195
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
@@ -199,7 +209,7 @@ function NewDriverForm({ editId }: { editId?: string }) {
|
|
|
199
209
|
const updated = await updateDriver(editId, buildPayload());
|
|
200
210
|
navigate?.(joinPath(basePath, `fleet/drivers/${updated.id}`));
|
|
201
211
|
} else {
|
|
202
|
-
const created = await createDriver(
|
|
212
|
+
const created = await createDriver(buildCreatePayload());
|
|
203
213
|
navigate?.(joinPath(basePath, `fleet/drivers/${created.id}`));
|
|
204
214
|
}
|
|
205
215
|
} catch (err) {
|
|
@@ -280,7 +290,8 @@ function NewDriverForm({ editId }: { editId?: string }) {
|
|
|
280
290
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
281
291
|
<div className="space-y-2">
|
|
282
292
|
<Label htmlFor="employeeId">
|
|
283
|
-
Employee ID
|
|
293
|
+
Employee ID {isEditing ? '(immutable)' : ''}{' '}
|
|
294
|
+
<span className="text-status-error-text">{!isEditing && '*'}</span>
|
|
284
295
|
</Label>
|
|
285
296
|
<Input
|
|
286
297
|
id="employeeId"
|
|
@@ -288,6 +299,7 @@ function NewDriverForm({ editId }: { editId?: string }) {
|
|
|
288
299
|
onChange={(e) => setForm((prev) => ({ ...prev, employeeId: e.target.value }))}
|
|
289
300
|
placeholder="EMP-001"
|
|
290
301
|
required
|
|
302
|
+
disabled={isEditing}
|
|
291
303
|
/>
|
|
292
304
|
</div>
|
|
293
305
|
<div className="space-y-2">
|
|
@@ -403,7 +415,13 @@ function NewDriverForm({ editId }: { editId?: string }) {
|
|
|
403
415
|
<EntityCombobox
|
|
404
416
|
id="vehicle"
|
|
405
417
|
value={form.vehicleId}
|
|
406
|
-
onChange={(value) =>
|
|
418
|
+
onChange={(value) =>
|
|
419
|
+
setForm((prev) => ({
|
|
420
|
+
...prev,
|
|
421
|
+
vehicleId: value,
|
|
422
|
+
vehicleIds: value ? [value] : [],
|
|
423
|
+
}))
|
|
424
|
+
}
|
|
407
425
|
options={vehicles}
|
|
408
426
|
loading={vehiclesLoading}
|
|
409
427
|
getId={(vehicle) => vehicle.id}
|
|
@@ -445,7 +463,19 @@ function NewDriverForm({ editId }: { editId?: string }) {
|
|
|
445
463
|
/>
|
|
446
464
|
</div>
|
|
447
465
|
<div className="space-y-2">
|
|
448
|
-
<
|
|
466
|
+
<div className="flex items-center justify-between gap-2">
|
|
467
|
+
<Label htmlFor="onLeaveUntil">On Leave Until</Label>
|
|
468
|
+
{form.onLeaveUntil && (
|
|
469
|
+
<Button
|
|
470
|
+
type="button"
|
|
471
|
+
variant="ghost"
|
|
472
|
+
size="sm"
|
|
473
|
+
onClick={() => setForm((prev) => ({ ...prev, onLeaveUntil: '' }))}
|
|
474
|
+
>
|
|
475
|
+
Clear
|
|
476
|
+
</Button>
|
|
477
|
+
)}
|
|
478
|
+
</div>
|
|
449
479
|
<Input
|
|
450
480
|
id="onLeaveUntil"
|
|
451
481
|
type="date"
|
|
@@ -641,7 +671,7 @@ export default function DriverDetailsPage() {
|
|
|
641
671
|
customColors={{ bg: 'bg-status-warning-bg-subtle', text: 'text-status-warning-text' }}
|
|
642
672
|
/>
|
|
643
673
|
)}
|
|
644
|
-
{driver.rating
|
|
674
|
+
{driver.rating !== undefined && (
|
|
645
675
|
<div className="flex items-center gap-1">
|
|
646
676
|
<Star size={16} className="text-status-warning-text fill-current" />
|
|
647
677
|
<span className="font-medium">{driver.rating.toFixed(1)}</span>
|
|
@@ -823,7 +853,9 @@ export default function DriverDetailsPage() {
|
|
|
823
853
|
{driver.currentLocation.address || 'Location updating...'}
|
|
824
854
|
</p>
|
|
825
855
|
<p className="text-xs text-text-secondary mt-1">
|
|
826
|
-
|
|
856
|
+
{driver.currentLocation.timestamp
|
|
857
|
+
? `Updated ${formatDate(driver.currentLocation.timestamp, 'relative')}`
|
|
858
|
+
: 'Update time not recorded'}
|
|
827
859
|
</p>
|
|
828
860
|
</CardContent>
|
|
829
861
|
</Card>
|
|
@@ -170,7 +170,7 @@ export default function DriversPage() {
|
|
|
170
170
|
id: 'rating',
|
|
171
171
|
header: 'Rating',
|
|
172
172
|
cell: (row) =>
|
|
173
|
-
row.rating
|
|
173
|
+
row.rating !== undefined ? (
|
|
174
174
|
<div className="flex items-center gap-1">
|
|
175
175
|
<Star size={14} className="text-status-warning-text fill-current" />
|
|
176
176
|
<span className="font-medium">{row.rating.toFixed(1)}</span>
|
|
@@ -308,7 +308,7 @@ export default function FleetPage() {
|
|
|
308
308
|
</div>
|
|
309
309
|
<div className="text-right">
|
|
310
310
|
<StatusBadge status={driver.status} type="driver" size="sm" />
|
|
311
|
-
{driver.rating
|
|
311
|
+
{driver.rating !== undefined && (
|
|
312
312
|
<div className="mt-1 flex items-center gap-1 justify-end">
|
|
313
313
|
<Star size={12} className="text-status-warning-text fill-current" />
|
|
314
314
|
<span className="text-sm font-medium">{driver.rating.toFixed(1)}</span>
|
|
@@ -502,32 +502,32 @@ export default function InventoryPage() {
|
|
|
502
502
|
<div>
|
|
503
503
|
<p className="font-medium">{warehouse.name}</p>
|
|
504
504
|
<p className="text-sm text-text-secondary">
|
|
505
|
-
{warehouse.address.city
|
|
505
|
+
{[warehouse.address.city, warehouse.address.state]
|
|
506
|
+
.filter(Boolean)
|
|
507
|
+
.join(', ') || 'Not provided'}
|
|
506
508
|
</p>
|
|
507
509
|
</div>
|
|
508
510
|
<div className="text-right">
|
|
509
511
|
<Badge
|
|
510
512
|
variant={
|
|
511
|
-
warehouse.status
|
|
513
|
+
warehouse.status?.toUpperCase() === 'ACTIVE' ? 'default' : 'secondary'
|
|
512
514
|
}
|
|
513
515
|
>
|
|
514
|
-
{warehouse.status
|
|
515
|
-
?
|
|
516
|
-
|
|
516
|
+
{warehouse.status
|
|
517
|
+
? warehouse.status.toUpperCase() === 'ACTIVE'
|
|
518
|
+
? t('movethewheels.inventory.warehouses.active', 'Active')
|
|
519
|
+
: t('movethewheels.inventory.warehouses.inactive', 'Inactive')
|
|
520
|
+
: 'Not provided'}
|
|
517
521
|
</Badge>
|
|
518
522
|
<div className="mt-2">
|
|
519
523
|
<p className="text-xs text-text-secondary">
|
|
520
524
|
{t('movethewheels.inventory.warehouses.utilization', 'Utilization')}
|
|
521
525
|
</p>
|
|
522
|
-
|
|
523
|
-
value={
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
: 0
|
|
528
|
-
}
|
|
529
|
-
className="w-24 h-2"
|
|
530
|
-
/>
|
|
526
|
+
{warehouse.utilization !== undefined ? (
|
|
527
|
+
<Progress value={warehouse.utilization} className="w-24 h-2" />
|
|
528
|
+
) : (
|
|
529
|
+
<p className="text-xs text-text-tertiary">Not provided</p>
|
|
530
|
+
)}
|
|
531
531
|
</div>
|
|
532
532
|
</div>
|
|
533
533
|
</div>
|
|
@@ -567,8 +567,12 @@ export default function InventoryPage() {
|
|
|
567
567
|
<p className="text-sm text-text-secondary">{product.sku}</p>
|
|
568
568
|
</div>
|
|
569
569
|
<div className="text-right">
|
|
570
|
-
<p className="font-medium">
|
|
571
|
-
|
|
570
|
+
<p className="font-medium">
|
|
571
|
+
{product.value !== undefined
|
|
572
|
+
? formatCurrency(product.value)
|
|
573
|
+
: 'Not provided'}
|
|
574
|
+
</p>
|
|
575
|
+
<Badge variant="outline">{product.category ?? 'Not provided'}</Badge>
|
|
572
576
|
</div>
|
|
573
577
|
</div>
|
|
574
578
|
</div>
|
|
@@ -83,7 +83,7 @@ export default function LiveTrackingPage() {
|
|
|
83
83
|
sublabel:
|
|
84
84
|
d.driver?.fullName ||
|
|
85
85
|
d.vehicle?.vehicleNumber ||
|
|
86
|
-
d.tracking?.currentLocation
|
|
86
|
+
d.tracking?.currentLocation?.address ||
|
|
87
87
|
undefined,
|
|
88
88
|
},
|
|
89
89
|
];
|
|
@@ -292,7 +292,7 @@ export default function LiveTrackingPage() {
|
|
|
292
292
|
: 'N/A'}
|
|
293
293
|
</p>
|
|
294
294
|
<p className="text-xs text-text-secondary">
|
|
295
|
-
{delivery.tracking?.currentLocation
|
|
295
|
+
{delivery.tracking?.currentLocation?.address ||
|
|
296
296
|
'Location updating...'}
|
|
297
297
|
</p>
|
|
298
298
|
</div>
|
|
@@ -352,7 +352,7 @@ export default function LiveTrackingPage() {
|
|
|
352
352
|
<div>
|
|
353
353
|
<p className="text-sm text-text-secondary">Current Location</p>
|
|
354
354
|
<p className="text-sm">
|
|
355
|
-
{selected.tracking?.currentLocation
|
|
355
|
+
{selected.tracking?.currentLocation?.address || 'Updating...'}
|
|
356
356
|
</p>
|
|
357
357
|
{(() => {
|
|
358
358
|
const coord = firstRealLatLng(
|
|
@@ -440,7 +440,9 @@ export default function LiveTrackingPage() {
|
|
|
440
440
|
>
|
|
441
441
|
<p className="font-medium">{alert.message}</p>
|
|
442
442
|
<p className="text-xs text-text-secondary mt-1">
|
|
443
|
-
{
|
|
443
|
+
{alert.timestamp
|
|
444
|
+
? formatDate(alert.timestamp, 'relative')
|
|
445
|
+
: 'Time not recorded'}
|
|
444
446
|
</p>
|
|
445
447
|
</div>
|
|
446
448
|
))}
|