@burdenoff/microfe-movethewheels 2026.920.2 → 2026.921.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.
Files changed (82) hide show
  1. package/dist/components/CarbonFootprintPanel.js +1 -1
  2. package/dist/components/CustomerPicker.js +271 -0
  3. package/dist/components/DataTable.js +140 -139
  4. package/dist/components/DocumentsCard.js +14 -8
  5. package/dist/generated/wspace-types.d.ts +1043 -5
  6. package/dist/hooks/useAnalytics.js +21 -1
  7. package/dist/hooks/useCustomers.js +94 -78
  8. package/dist/hooks/useDebouncedValue.js +11 -0
  9. package/dist/hooks/useEntityDocuments.js +28 -18
  10. package/dist/hooks/useEpod.js +24 -17
  11. package/dist/hooks/useFleet.js +146 -125
  12. package/dist/hooks/useInventory.js +255 -186
  13. package/dist/hooks/useMultiModalShipments.js +35 -27
  14. package/dist/hooks/useOrders.js +73 -73
  15. package/dist/hooks/useRoutes.js +97 -75
  16. package/dist/hooks/useTracking.js +100 -88
  17. package/dist/movethewheels/components/CustomerPicker.d.ts +19 -0
  18. package/dist/movethewheels/components/DataTable.d.ts +5 -1
  19. package/dist/movethewheels/hooks/useCustomers.d.ts +0 -1
  20. package/dist/movethewheels/hooks/useDebouncedValue.d.ts +1 -0
  21. package/dist/movethewheels/hooks/useEntityDocuments.d.ts +2 -0
  22. package/dist/movethewheels/hooks/useEpod.d.ts +6 -1
  23. package/dist/movethewheels/hooks/useFleet.d.ts +49 -5
  24. package/dist/movethewheels/hooks/useInventory.d.ts +24 -17
  25. package/dist/movethewheels/hooks/useMultiModalShipments.d.ts +6 -1
  26. package/dist/movethewheels/hooks/useOrders.d.ts +33 -5
  27. package/dist/movethewheels/hooks/useRoutes.d.ts +2 -1
  28. package/dist/movethewheels/hooks/useTracking.d.ts +12 -11
  29. package/dist/movethewheels/types/index.d.ts +7 -5
  30. package/dist/movethewheels/utils/navigation.d.ts +14 -0
  31. package/dist/pages/CreateOrderPage.js +228 -217
  32. package/dist/pages/CustomerDetailsPage.js +220 -220
  33. package/dist/pages/CustomersPage.js +61 -54
  34. package/dist/pages/DeliveryCapturePage.js +131 -109
  35. package/dist/pages/DriversPage.js +100 -92
  36. package/dist/pages/InventoryPage.js +239 -236
  37. package/dist/pages/MultiModalShipmentPage.js +163 -130
  38. package/dist/pages/OrderDetailsPage.js +224 -208
  39. package/dist/pages/OrdersListPage.js +66 -60
  40. package/dist/pages/ProductDetailsPage.js +241 -200
  41. package/dist/pages/ProductsPage.js +59 -52
  42. package/dist/pages/RouteDetailsPage.js +343 -321
  43. package/dist/pages/RoutesPage.js +55 -48
  44. package/dist/pages/VehiclesPage.js +63 -56
  45. package/dist/pages/WarehousesPage.js +60 -53
  46. package/dist/utils/navigation.js +22 -1
  47. package/package.json +1 -1
  48. package/src/generated/wspace-types.ts +1188 -5
  49. package/src/movethewheels/components/CarbonFootprintPanel.tsx +0 -1
  50. package/src/movethewheels/components/CustomerPicker.tsx +321 -0
  51. package/src/movethewheels/components/DataTable.tsx +34 -18
  52. package/src/movethewheels/components/DocumentsCard.tsx +6 -1
  53. package/src/movethewheels/components/maps/leaflet-setup.ts +1 -8
  54. package/src/movethewheels/hooks/useAnalytics.ts +27 -0
  55. package/src/movethewheels/hooks/useCustomers.ts +64 -24
  56. package/src/movethewheels/hooks/useDebouncedValue.ts +12 -0
  57. package/src/movethewheels/hooks/useEntityDocuments.ts +43 -25
  58. package/src/movethewheels/hooks/useEpod.ts +14 -7
  59. package/src/movethewheels/hooks/useFleet.ts +58 -37
  60. package/src/movethewheels/hooks/useFleetIntelligence.ts +1 -4
  61. package/src/movethewheels/hooks/useInventory.ts +211 -90
  62. package/src/movethewheels/hooks/useMultiModalShipments.ts +15 -11
  63. package/src/movethewheels/hooks/useOrders.ts +13 -14
  64. package/src/movethewheels/hooks/useRoutes.ts +80 -23
  65. package/src/movethewheels/hooks/useTracking.ts +130 -98
  66. package/src/movethewheels/pages/CreateOrderPage.tsx +92 -31
  67. package/src/movethewheels/pages/CustomerDetailsPage.tsx +15 -6
  68. package/src/movethewheels/pages/CustomersPage.tsx +11 -1
  69. package/src/movethewheels/pages/DeliveryCapturePage.tsx +31 -1
  70. package/src/movethewheels/pages/DriversPage.tsx +24 -12
  71. package/src/movethewheels/pages/InventoryPage.tsx +57 -32
  72. package/src/movethewheels/pages/MultiModalShipmentPage.tsx +52 -3
  73. package/src/movethewheels/pages/OrderDetailsPage.tsx +17 -1
  74. package/src/movethewheels/pages/OrdersListPage.tsx +20 -3
  75. package/src/movethewheels/pages/ProductDetailsPage.tsx +52 -3
  76. package/src/movethewheels/pages/ProductsPage.tsx +11 -1
  77. package/src/movethewheels/pages/RouteDetailsPage.tsx +35 -5
  78. package/src/movethewheels/pages/RoutesPage.tsx +11 -1
  79. package/src/movethewheels/pages/VehiclesPage.tsx +19 -2
  80. package/src/movethewheels/pages/WarehousesPage.tsx +11 -1
  81. package/src/movethewheels/types/index.ts +7 -5
  82. package/src/movethewheels/utils/navigation.ts +32 -0
@@ -95,7 +95,6 @@ export function CarbonFootprintPanel() {
95
95
  <div className="space-y-2">
96
96
  {summary.byVehicle
97
97
  .filter((v) => v.totalDistanceKm > 0 || v.co2eKg > 0)
98
- .slice(0, 5)
99
98
  .map((vehicle) => (
100
99
  <div
101
100
  key={vehicle.vehicleId ?? 'unassigned'}
@@ -0,0 +1,321 @@
1
+ import { useState, type ChangeEvent, type FormEvent } from 'react';
2
+ import { Plus, RefreshCw, UserPlus } from 'lucide-react';
3
+ import {
4
+ Button,
5
+ Dialog,
6
+ DialogContent,
7
+ DialogDescription,
8
+ DialogFooter,
9
+ DialogHeader,
10
+ DialogTitle,
11
+ Input,
12
+ Label,
13
+ Select,
14
+ SelectContent,
15
+ SelectItem,
16
+ SelectTrigger,
17
+ SelectValue,
18
+ } from './ui';
19
+ import type { Customer } from '../types';
20
+ import { FormError } from './FormError';
21
+
22
+ interface CustomerPickerProps {
23
+ customers: Customer[];
24
+ value: string;
25
+ onValueChange: (value: string) => void;
26
+ onCreated: (customer: Customer) => void;
27
+ createCustomer: (customer: Partial<Customer>) => Promise<Customer>;
28
+ isLoading: boolean;
29
+ error: Error | null;
30
+ onRetry: () => void;
31
+ onOpenCustomers?: () => void;
32
+ selectedCustomer?: Customer | null;
33
+ searchQuery?: string;
34
+ onSearchQueryChange?: (query: string) => void;
35
+ disabled?: boolean;
36
+ }
37
+
38
+ interface CustomerFormState {
39
+ name: string;
40
+ type: Customer['type'];
41
+ email: string;
42
+ phone: string;
43
+ company: string;
44
+ }
45
+
46
+ const EMPTY_FORM: CustomerFormState = {
47
+ name: '',
48
+ type: 'b2c',
49
+ email: '',
50
+ phone: '',
51
+ company: '',
52
+ };
53
+
54
+ function mergeCustomers(customers: Customer[], selectedCustomer?: Customer | null): Customer[] {
55
+ if (!selectedCustomer || customers.some((customer) => customer.id === selectedCustomer.id)) {
56
+ return customers;
57
+ }
58
+ return [selectedCustomer, ...customers];
59
+ }
60
+
61
+ export function CustomerPicker({
62
+ customers,
63
+ value,
64
+ onValueChange,
65
+ onCreated,
66
+ createCustomer,
67
+ isLoading,
68
+ error,
69
+ onRetry,
70
+ onOpenCustomers,
71
+ selectedCustomer,
72
+ searchQuery = '',
73
+ onSearchQueryChange,
74
+ disabled = false,
75
+ }: CustomerPickerProps) {
76
+ const [isCreateOpen, setIsCreateOpen] = useState(false);
77
+ const [form, setForm] = useState<CustomerFormState>(EMPTY_FORM);
78
+ const [isCreating, setIsCreating] = useState(false);
79
+ const [createError, setCreateError] = useState<string | null>(null);
80
+ const options = mergeCustomers(customers, selectedCustomer);
81
+
82
+ const openCreateDialog = () => {
83
+ setCreateError(null);
84
+ setForm(EMPTY_FORM);
85
+ setIsCreateOpen(true);
86
+ };
87
+
88
+ const closeCreateDialog = () => {
89
+ if (isCreating) return;
90
+ setIsCreateOpen(false);
91
+ };
92
+
93
+ const handleCreate = async (event: FormEvent<HTMLFormElement>) => {
94
+ event.preventDefault();
95
+ event.stopPropagation();
96
+ const name = form.name.trim();
97
+ if (!name) {
98
+ setCreateError('Customer name is required.');
99
+ return;
100
+ }
101
+
102
+ setIsCreating(true);
103
+ setCreateError(null);
104
+ try {
105
+ const created = await createCustomer({
106
+ name,
107
+ type: form.type,
108
+ email: form.email.trim() || undefined,
109
+ phone: form.phone.trim() || undefined,
110
+ company: form.company.trim() || undefined,
111
+ });
112
+ onCreated(created);
113
+ setIsCreateOpen(false);
114
+ setForm(EMPTY_FORM);
115
+ } catch (creationError) {
116
+ setCreateError(
117
+ creationError instanceof Error
118
+ ? creationError.message
119
+ : 'Could not create the customer. Please try again.'
120
+ );
121
+ } finally {
122
+ setIsCreating(false);
123
+ }
124
+ };
125
+
126
+ return (
127
+ <div className="space-y-2">
128
+ <div className="flex items-center justify-between gap-2">
129
+ <Label htmlFor="customer">Customer</Label>
130
+ <Button
131
+ type="button"
132
+ variant="ghost"
133
+ size="sm"
134
+ onClick={openCreateDialog}
135
+ disabled={disabled || isLoading}
136
+ >
137
+ <Plus size={14} className="mr-1" />
138
+ Add customer
139
+ </Button>
140
+ </div>
141
+ <div className="flex flex-col gap-2 sm:flex-row sm:items-start">
142
+ {onSearchQueryChange && (
143
+ <Input
144
+ value={searchQuery}
145
+ onChange={(event) => onSearchQueryChange(event.target.value)}
146
+ placeholder="Search customers"
147
+ aria-label="Search customers"
148
+ disabled={disabled}
149
+ className="sm:max-w-56"
150
+ />
151
+ )}
152
+ <Select
153
+ value={value}
154
+ onValueChange={onValueChange}
155
+ disabled={disabled || isLoading || Boolean(error)}
156
+ >
157
+ <SelectTrigger id="customer" className="flex-1">
158
+ <SelectValue placeholder={isLoading ? 'Loading customers...' : 'Select customer'} />
159
+ </SelectTrigger>
160
+ <SelectContent>
161
+ {options.map((customer) => (
162
+ <SelectItem key={customer.id} value={customer.id}>
163
+ {customer.name} {customer.email ? `(${customer.email})` : ''}
164
+ </SelectItem>
165
+ ))}
166
+ </SelectContent>
167
+ </Select>
168
+ {error && (
169
+ <Button type="button" variant="outline" size="sm" onClick={onRetry}>
170
+ <RefreshCw size={14} className="mr-1" />
171
+ Retry
172
+ </Button>
173
+ )}
174
+ </div>
175
+ {error ? (
176
+ <div className="flex flex-wrap items-center gap-2">
177
+ <p role="alert" className="text-sm text-status-error-text">
178
+ Customers could not be loaded. Retry or add a customer to continue.
179
+ </p>
180
+ {onOpenCustomers && (
181
+ <Button type="button" variant="outline" size="sm" onClick={onOpenCustomers}>
182
+ Open customer page
183
+ </Button>
184
+ )}
185
+ </div>
186
+ ) : !isLoading && options.length === 0 ? (
187
+ <div className="rounded-lg border border-border-seam bg-bg-sunken/40 p-3">
188
+ <div className="flex items-start gap-2">
189
+ <UserPlus size={16} className="mt-0.5 shrink-0 text-text-secondary" />
190
+ <div>
191
+ <p className="text-sm font-medium text-text-primary">Add a customer to continue</p>
192
+ <p className="text-sm text-text-secondary">
193
+ The customer will be selected automatically and your order details will stay here.
194
+ </p>
195
+ </div>
196
+ </div>
197
+ <Button
198
+ type="button"
199
+ size="sm"
200
+ className="mt-3"
201
+ onClick={openCreateDialog}
202
+ disabled={disabled}
203
+ >
204
+ <Plus size={14} className="mr-1.5" />
205
+ Create customer
206
+ </Button>
207
+ </div>
208
+ ) : null}
209
+
210
+ <Dialog
211
+ open={isCreateOpen}
212
+ onOpenChange={(open: boolean) => (open ? setIsCreateOpen(true) : closeCreateDialog())}
213
+ >
214
+ <DialogContent>
215
+ <DialogHeader>
216
+ <DialogTitle>Create customer</DialogTitle>
217
+ <DialogDescription>
218
+ Add the customer without leaving this order. They will be selected automatically.
219
+ </DialogDescription>
220
+ </DialogHeader>
221
+ <form onSubmit={handleCreate} className="space-y-4">
222
+ <FormError message={createError} />
223
+ <div className="space-y-2">
224
+ <Label htmlFor="quick-customer-name">Name</Label>
225
+ <Input
226
+ id="quick-customer-name"
227
+ value={form.name}
228
+ onChange={(event: ChangeEvent<HTMLInputElement>) =>
229
+ setForm((previous) => ({ ...previous, name: event.target.value }))
230
+ }
231
+ placeholder="Acme Corp or Jane Doe"
232
+ autoFocus
233
+ required
234
+ />
235
+ </div>
236
+ <div className="space-y-2">
237
+ <Label htmlFor="quick-customer-type">Customer type</Label>
238
+ <Select
239
+ value={form.type}
240
+ onValueChange={(value: string) =>
241
+ setForm((previous) => ({ ...previous, type: value as Customer['type'] }))
242
+ }
243
+ >
244
+ <SelectTrigger id="quick-customer-type">
245
+ <SelectValue />
246
+ </SelectTrigger>
247
+ <SelectContent>
248
+ <SelectItem value="b2c">B2C (Individual)</SelectItem>
249
+ <SelectItem value="b2b">B2B (Business)</SelectItem>
250
+ </SelectContent>
251
+ </Select>
252
+ </div>
253
+ <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
254
+ <div className="space-y-2">
255
+ <Label htmlFor="quick-customer-email">Email</Label>
256
+ <Input
257
+ id="quick-customer-email"
258
+ type="email"
259
+ value={form.email}
260
+ onChange={(event: ChangeEvent<HTMLInputElement>) =>
261
+ setForm((previous) => ({ ...previous, email: event.target.value }))
262
+ }
263
+ placeholder="customer@example.com"
264
+ />
265
+ </div>
266
+ <div className="space-y-2">
267
+ <Label htmlFor="quick-customer-phone">Phone</Label>
268
+ <Input
269
+ id="quick-customer-phone"
270
+ value={form.phone}
271
+ onChange={(event: ChangeEvent<HTMLInputElement>) =>
272
+ setForm((previous) => ({ ...previous, phone: event.target.value }))
273
+ }
274
+ placeholder="+1-555-0100"
275
+ />
276
+ </div>
277
+ </div>
278
+ <div className="space-y-2">
279
+ <Label htmlFor="quick-customer-company">Company</Label>
280
+ <Input
281
+ id="quick-customer-company"
282
+ value={form.company}
283
+ onChange={(event: ChangeEvent<HTMLInputElement>) =>
284
+ setForm((previous) => ({ ...previous, company: event.target.value }))
285
+ }
286
+ placeholder="Company name (optional)"
287
+ />
288
+ </div>
289
+ <DialogFooter>
290
+ {onOpenCustomers && (
291
+ <Button
292
+ type="button"
293
+ variant="outline"
294
+ onClick={() => {
295
+ closeCreateDialog();
296
+ onOpenCustomers();
297
+ }}
298
+ disabled={isCreating}
299
+ >
300
+ Use customer page
301
+ </Button>
302
+ )}
303
+ <Button
304
+ type="button"
305
+ variant="outline"
306
+ onClick={closeCreateDialog}
307
+ disabled={isCreating}
308
+ >
309
+ Cancel
310
+ </Button>
311
+ <Button type="submit" disabled={isCreating}>
312
+ <Plus size={16} />
313
+ {isCreating ? 'Creating...' : 'Create and select'}
314
+ </Button>
315
+ </DialogFooter>
316
+ </form>
317
+ </DialogContent>
318
+ </Dialog>
319
+ </div>
320
+ );
321
+ }
@@ -1,4 +1,4 @@
1
- import { useState, useMemo, type ReactNode } from 'react';
1
+ import { useState, useMemo, type ReactNode, type ChangeEvent } from 'react';
2
2
  import {
3
3
  Table,
4
4
  TableBody,
@@ -56,6 +56,10 @@ interface DataTableProps<T> {
56
56
  searchable?: boolean;
57
57
  searchPlaceholder?: string;
58
58
  searchFields?: (keyof T)[];
59
+ /** Controlled search query for server-side filtering. */
60
+ searchQuery?: string;
61
+ /** Called when the search input changes; disables current-page filtering. */
62
+ onSearchQueryChange?: (query: string) => void;
59
63
  pagination?: boolean;
60
64
  pageSize?: number;
61
65
  pageSizeOptions?: number[];
@@ -106,6 +110,8 @@ export function DataTable<T extends object>({
106
110
  searchable = false,
107
111
  searchPlaceholder = 'Search...',
108
112
  searchFields,
113
+ searchQuery: controlledSearchQuery,
114
+ onSearchQueryChange,
109
115
  pagination = true,
110
116
  pageSize: pageSizeProp = 10,
111
117
  pageSizeOptions = [10, 25, 50, 100],
@@ -128,6 +134,8 @@ export function DataTable<T extends object>({
128
134
  const controlledPageIndex = pageIndex ?? 0;
129
135
 
130
136
  const [searchQuery, setSearchQuery] = useState('');
137
+ const isServerSearch = typeof onSearchQueryChange === 'function';
138
+ const activeSearchQuery = isServerSearch ? (controlledSearchQuery ?? '') : searchQuery;
131
139
  const [sortColumn, setSortColumn] = useState<string | null>(null);
132
140
  const [sortDirection, setSortDirection] = useState<SortDirection>(null);
133
141
  const [currentPage, setCurrentPage] = useState(1);
@@ -135,25 +143,26 @@ export function DataTable<T extends object>({
135
143
 
136
144
  const activePageSize = isControlled ? pageSizeProp : internalPageSize;
137
145
 
138
- // Filter data based on search (client-side; in controlled mode this scopes to
139
- // the current page only, which is expected for server-paginated lists).
146
+ // In server-search mode, `data` is already filtered by the backend. Never
147
+ // filter just the current page again, or matching rows on other pages vanish.
140
148
  const filteredData = useMemo(() => {
141
- if (!searchQuery || !searchFields || searchFields.length === 0) {
149
+ if (isServerSearch || !activeSearchQuery || !searchFields || searchFields.length === 0) {
142
150
  return data;
143
151
  }
144
152
 
145
- const query = searchQuery.toLowerCase();
153
+ const query = activeSearchQuery.toLowerCase();
146
154
  return data.filter((row) =>
147
155
  searchFields.some((field) => {
148
156
  const value = row[field] as unknown;
149
157
  return value != null && String(value).toLowerCase().includes(query);
150
158
  })
151
159
  );
152
- }, [data, searchQuery, searchFields]);
160
+ }, [data, activeSearchQuery, searchFields, isServerSearch]);
153
161
 
154
- // Sort data
162
+ // Controlled pages use the backend's stable order. Local sorting a fetched
163
+ // page would produce a misleading partial-workspace sort.
155
164
  const sortedData = useMemo(() => {
156
- if (!sortColumn || !sortDirection) {
165
+ if (isControlled || !sortColumn || !sortDirection) {
157
166
  return filteredData;
158
167
  }
159
168
 
@@ -201,6 +210,7 @@ export function DataTable<T extends object>({
201
210
 
202
211
  // Handle sort
203
212
  const handleSort = (columnId: string) => {
213
+ if (isControlled) return;
204
214
  if (sortColumn === columnId) {
205
215
  if (sortDirection === 'asc') {
206
216
  setSortDirection('desc');
@@ -320,11 +330,11 @@ export function DataTable<T extends object>({
320
330
 
321
331
  const renderEmpty = () =>
322
332
  emptyIllustration ? (
323
- searchQuery ? (
333
+ activeSearchQuery ? (
324
334
  <IllustratedEmptyState
325
335
  illustration="empty-search"
326
336
  title="No matches"
327
- description={`Nothing matches “${searchQuery}”. Try a different search.`}
337
+ description={`Nothing matches “${activeSearchQuery}”. Try a different search.`}
328
338
  />
329
339
  ) : (
330
340
  <IllustratedEmptyState
@@ -352,10 +362,14 @@ export function DataTable<T extends object>({
352
362
  <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-text-secondary" />
353
363
  <Input
354
364
  placeholder={searchPlaceholder}
355
- value={searchQuery}
356
- onChange={(e) => {
357
- setSearchQuery(e.target.value);
358
- setCurrentPage(1);
365
+ value={activeSearchQuery}
366
+ onChange={(e: ChangeEvent<HTMLInputElement>) => {
367
+ if (isServerSearch) {
368
+ onSearchQueryChange?.(e.target.value);
369
+ } else {
370
+ setSearchQuery(e.target.value);
371
+ setCurrentPage(1);
372
+ }
359
373
  }}
360
374
  className="pl-9"
361
375
  />
@@ -372,14 +386,16 @@ export function DataTable<T extends object>({
372
386
  <TableHead
373
387
  key={column.id}
374
388
  className={cn(
375
- column.sortable && 'cursor-pointer select-none hover:bg-bg-sunken/50',
389
+ !isControlled &&
390
+ column.sortable &&
391
+ 'cursor-pointer select-none hover:bg-bg-sunken/50',
376
392
  column.headerClassName
377
393
  )}
378
- onClick={() => column.sortable && handleSort(column.id)}
394
+ onClick={() => !isControlled && column.sortable && handleSort(column.id)}
379
395
  >
380
396
  <div className="flex items-center gap-2">
381
397
  {column.header}
382
- {column.sortable && getSortIcon(column.id)}
398
+ {!isControlled && column.sortable && getSortIcon(column.id)}
383
399
  </div>
384
400
  </TableHead>
385
401
  ))}
@@ -481,7 +497,7 @@ export function DataTable<T extends object>({
481
497
  )}
482
498
  <Select
483
499
  value={String(activePageSize)}
484
- onValueChange={(value) => {
500
+ onValueChange={(value: string) => {
485
501
  const next = Number(value);
486
502
  if (isControlled) {
487
503
  onPageSizeChange?.(next);
@@ -33,7 +33,7 @@ function formatFileSize(bytes: number): string {
33
33
 
34
34
  export function DocumentsCard({ entityType, entityId }: DocumentsCardProps) {
35
35
  const { navigate } = useMoveTheWheels();
36
- const { documents, isLoading } = useEntityDocuments(entityType, entityId);
36
+ const { documents, hasMore, isLoading, loadMore } = useEntityDocuments(entityType, entityId);
37
37
 
38
38
  // Deep-link to the host Files module (mounted at /files in the app shell).
39
39
  // This is a host-absolute path, so it uses the raw navigate — not the
@@ -82,6 +82,11 @@ export function DocumentsCard({ entityType, entityId }: DocumentsCardProps) {
82
82
  )}
83
83
  </div>
84
84
  ))}
85
+ {hasMore && (
86
+ <Button variant="outline" size="sm" onClick={loadMore} disabled={isLoading}>
87
+ Load more documents
88
+ </Button>
89
+ )}
85
90
  </div>
86
91
  ) : (
87
92
  <div className="text-center py-6">
@@ -13,14 +13,7 @@ export const OSM_ATTRIBUTION =
13
13
  '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors';
14
14
 
15
15
  export type MapMarkerStatus =
16
- | 'active'
17
- | 'in_transit'
18
- | 'idle'
19
- | 'delayed'
20
- | 'delivered'
21
- | 'origin'
22
- | 'destination'
23
- | 'default';
16
+ 'active' | 'in_transit' | 'idle' | 'delayed' | 'delivered' | 'origin' | 'destination' | 'default';
24
17
 
25
18
  /**
26
19
  * A coordinate is "real" only when it is finite and not the null-island (0,0)
@@ -67,10 +67,15 @@ const DASHBOARD_QUERY = `
67
67
  deliveryJobStats {
68
68
  total
69
69
  pending
70
+ confirmed
71
+ assigned
72
+ pickedUp
70
73
  inTransit
74
+ outForDelivery
71
75
  delivered
72
76
  cancelled
73
77
  failed
78
+ returned
74
79
  }
75
80
  fleetStats {
76
81
  totalVehicles
@@ -94,10 +99,15 @@ const ORDER_ANALYTICS_QUERY = `
94
99
  query GetOrderAnalytics {
95
100
  deliveryJobStats {
96
101
  pending
102
+ confirmed
103
+ assigned
104
+ pickedUp
97
105
  inTransit
106
+ outForDelivery
98
107
  delivered
99
108
  cancelled
100
109
  failed
110
+ returned
101
111
  }
102
112
  analytics {
103
113
  orderTrend {
@@ -307,8 +317,15 @@ export function useDashboardMetrics() {
307
317
  deliveryJobStats: {
308
318
  total: number;
309
319
  pending: number;
320
+ confirmed: number;
321
+ assigned: number;
322
+ pickedUp: number;
310
323
  inTransit: number;
324
+ outForDelivery: number;
311
325
  delivered: number;
326
+ cancelled: number;
327
+ failed: number;
328
+ returned: number;
312
329
  };
313
330
  fleetStats: {
314
331
  totalVehicles: number;
@@ -416,10 +433,15 @@ export function useOrderAnalytics() {
416
433
  const data = await executeGraphQL<{
417
434
  deliveryJobStats: {
418
435
  pending: number;
436
+ confirmed: number;
437
+ assigned: number;
438
+ pickedUp: number;
419
439
  inTransit: number;
440
+ outForDelivery: number;
420
441
  delivered: number;
421
442
  cancelled: number;
422
443
  failed: number;
444
+ returned: number;
423
445
  };
424
446
  analytics: {
425
447
  orderTrend: Array<{ label: string; value: number }>;
@@ -429,10 +451,15 @@ export function useOrderAnalytics() {
429
451
  setOrderData({
430
452
  byStatus: [
431
453
  { status: 'pending', count: data.deliveryJobStats.pending },
454
+ { status: 'confirmed', count: data.deliveryJobStats.confirmed },
455
+ { status: 'assigned', count: data.deliveryJobStats.assigned },
456
+ { status: 'picked_up', count: data.deliveryJobStats.pickedUp },
432
457
  { status: 'in_transit', count: data.deliveryJobStats.inTransit },
458
+ { status: 'out_for_delivery', count: data.deliveryJobStats.outForDelivery },
433
459
  { status: 'delivered', count: data.deliveryJobStats.delivered },
434
460
  { status: 'cancelled', count: data.deliveryJobStats.cancelled },
435
461
  { status: 'failed', count: data.deliveryJobStats.failed },
462
+ { status: 'returned', count: data.deliveryJobStats.returned },
436
463
  ],
437
464
  byType: [],
438
465
  byPriority: [],