@burdenoff/microfe-movethewheels 2026.925.2 → 2026.929.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 (43) hide show
  1. package/dist/components/StatusBadge.js +34 -31
  2. package/dist/constants/index.js +12 -5
  3. package/dist/hooks/useCustomers.js +0 -3
  4. package/dist/hooks/useFleet.js +148 -135
  5. package/dist/hooks/useInventory.js +146 -146
  6. package/dist/hooks/useRoutes.js +5 -14
  7. package/dist/movethewheels/components/StatusBadge.d.ts +3 -1
  8. package/dist/movethewheels/constants/index.d.ts +1 -0
  9. package/dist/movethewheels/hooks/useFleet.d.ts +17 -4
  10. package/dist/movethewheels/hooks/useInventory.d.ts +11 -0
  11. package/dist/movethewheels/types/index.d.ts +19 -75
  12. package/dist/pages/CustomerDetailsPage.js +666 -344
  13. package/dist/pages/CustomersPage.js +71 -80
  14. package/dist/pages/DriverDetailsPage.js +2 -2
  15. package/dist/pages/DriversPage.js +2 -2
  16. package/dist/pages/FleetPage.js +1 -3
  17. package/dist/pages/InventoryPage.js +5 -5
  18. package/dist/pages/RouteDetailsPage.js +273 -248
  19. package/dist/pages/RoutesPage.js +130 -67
  20. package/dist/pages/VehicleDetailsPage.js +527 -389
  21. package/dist/pages/VehiclesPage.js +120 -71
  22. package/dist/pages/WarehouseDetailsPage.js +411 -368
  23. package/dist/pages/WarehousesPage.js +132 -83
  24. package/package.json +1 -1
  25. package/src/movethewheels/components/StatusBadge.tsx +15 -7
  26. package/src/movethewheels/constants/index.ts +9 -0
  27. package/src/movethewheels/hooks/useCustomers.ts +0 -3
  28. package/src/movethewheels/hooks/useFleet.ts +89 -25
  29. package/src/movethewheels/hooks/useInventory.ts +77 -33
  30. package/src/movethewheels/hooks/useRoutes.ts +17 -12
  31. package/src/movethewheels/pages/CustomerDetailsPage.tsx +376 -109
  32. package/src/movethewheels/pages/CustomersPage.tsx +6 -15
  33. package/src/movethewheels/pages/DriverDetailsPage.tsx +3 -1
  34. package/src/movethewheels/pages/DriversPage.tsx +5 -1
  35. package/src/movethewheels/pages/FleetPage.tsx +2 -1
  36. package/src/movethewheels/pages/InventoryPage.tsx +18 -6
  37. package/src/movethewheels/pages/RouteDetailsPage.tsx +74 -34
  38. package/src/movethewheels/pages/RoutesPage.tsx +66 -2
  39. package/src/movethewheels/pages/VehicleDetailsPage.tsx +185 -23
  40. package/src/movethewheels/pages/VehiclesPage.tsx +72 -5
  41. package/src/movethewheels/pages/WarehouseDetailsPage.tsx +217 -138
  42. package/src/movethewheels/pages/WarehousesPage.tsx +81 -17
  43. package/src/movethewheels/types/index.ts +28 -75
@@ -11,24 +11,44 @@ import {
11
11
  CircleCheck,
12
12
  Route as RouteIcon,
13
13
  Wrench,
14
+ Filter,
14
15
  } from 'lucide-react';
15
16
  import { PageLayout } from '../components/PageLayout';
16
17
  import { MetricCard, MetricGrid } from '../components/MetricCard';
17
18
  import { DataTable, type Column } from '../components/DataTable';
18
19
  import { StatusBadge, VehicleTypeBadge } from '../components/StatusBadge';
19
- import { Progress } from '../components/ui';
20
+ import {
21
+ Progress,
22
+ Card,
23
+ CardContent,
24
+ Select,
25
+ SelectContent,
26
+ SelectItem,
27
+ SelectTrigger,
28
+ SelectValue,
29
+ Button,
30
+ } from '../components/ui';
20
31
  import { CTAOverflowMenu, PagePurpose } from '@burdenoff/fe-libs/ui';
21
32
  import { useVehicles, useFleetStats } from '../hooks/useFleet';
22
33
  import { useDebouncedValue } from '../hooks/useDebouncedValue';
23
34
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
24
35
  import { joinPath } from '../utils/navigation';
25
- import type { Vehicle } from '../types';
36
+ import { VEHICLE_STATUS_LABELS } from '../constants';
37
+ import type { Vehicle, VehicleStatus } from '../types';
38
+
39
+ const STATUS_OPTIONS: Array<{ value: VehicleStatus | 'all'; label: string }> = [
40
+ { value: 'all', label: 'All statuses' },
41
+ ...(Object.entries(VEHICLE_STATUS_LABELS) as Array<[VehicleStatus, string]>).map(
42
+ ([value, label]) => ({ value, label })
43
+ ),
44
+ ];
26
45
 
27
46
  export default function VehiclesPage() {
28
47
  const { basePath, navigate } = useMoveTheWheels();
29
48
  const [page, setPage] = useState(0);
30
49
  const [pageSize, setPageSize] = useState(25);
31
50
  const [search, setSearch] = useState('');
51
+ const [statusFilter, setStatusFilter] = useState<VehicleStatus | 'all'>('all');
32
52
  const debouncedSearch = useDebouncedValue(search);
33
53
  const {
34
54
  vehicles,
@@ -41,7 +61,9 @@ export default function VehiclesPage() {
41
61
  limit: pageSize,
42
62
  offset: page * pageSize,
43
63
  search: debouncedSearch || undefined,
64
+ status: statusFilter === 'all' ? undefined : statusFilter,
44
65
  });
66
+ const filtersActive = statusFilter !== 'all' || search.length > 0;
45
67
  // Fleet totals + tiles from the live aggregate, not the current page.
46
68
  const { stats } = useFleetStats();
47
69
  const totalVehicles = stats.totalVehicles;
@@ -74,7 +96,7 @@ export default function VehiclesPage() {
74
96
  <p className="font-medium">
75
97
  {row.make} {row.model}
76
98
  </p>
77
- <p className="text-sm text-text-secondary">{row.year}</p>
99
+ <p className="text-sm text-text-secondary">{row.year ?? '—'}</p>
78
100
  </div>
79
101
  ),
80
102
  },
@@ -90,8 +112,10 @@ export default function VehiclesPage() {
90
112
  header: 'Capacity',
91
113
  cell: (row) => (
92
114
  <div className="text-sm">
93
- <p>{row.capacity.weight} kg</p>
94
- <p className="text-text-secondary">{row.capacity.volume} m³</p>
115
+ <p>{row.capacity.weight !== undefined ? `${row.capacity.weight} kg` : '—'}</p>
116
+ <p className="text-text-secondary">
117
+ {row.capacity.volume !== undefined ? `${row.capacity.volume} m³` : '—'}
118
+ </p>
95
119
  </div>
96
120
  ),
97
121
  },
@@ -178,6 +202,49 @@ export default function VehiclesPage() {
178
202
  </MetricGrid>
179
203
  </div>
180
204
 
205
+ {/* Filters */}
206
+ <Card>
207
+ <CardContent className="p-4">
208
+ <div className="flex flex-wrap items-center gap-4">
209
+ <div className="flex items-center gap-2">
210
+ <Filter size={16} className="text-text-secondary" />
211
+ <span className="text-sm font-medium">Filters:</span>
212
+ </div>
213
+ <Select
214
+ value={statusFilter}
215
+ onValueChange={(value) => {
216
+ setStatusFilter(value as VehicleStatus | 'all');
217
+ setPage(0);
218
+ }}
219
+ >
220
+ <SelectTrigger className="w-[180px]">
221
+ <SelectValue placeholder="Status" />
222
+ </SelectTrigger>
223
+ <SelectContent>
224
+ {STATUS_OPTIONS.map((option) => (
225
+ <SelectItem key={option.value} value={option.value}>
226
+ {option.label}
227
+ </SelectItem>
228
+ ))}
229
+ </SelectContent>
230
+ </Select>
231
+ {filtersActive && (
232
+ <Button
233
+ variant="ghost"
234
+ size="sm"
235
+ onClick={() => {
236
+ setStatusFilter('all');
237
+ setSearch('');
238
+ setPage(0);
239
+ }}
240
+ >
241
+ Clear filters
242
+ </Button>
243
+ )}
244
+ </div>
245
+ </CardContent>
246
+ </Card>
247
+
181
248
  <DataTable
182
249
  columns={columns}
183
250
  data={vehicles}
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { useEffect, useState } from 'react';
6
6
  import { useLocation, useParams } from 'react-router';
7
- import { Warehouse, ArrowLeft, Edit, MapPin, Clock, Users, Box, Save } from 'lucide-react';
7
+ import { Warehouse, ArrowLeft, Edit, MapPin, Clock, Box, Save, Trash2 } from 'lucide-react';
8
8
  import { PageLayout, LoadingState, ErrorState } from '../components/PageLayout';
9
9
  import {
10
10
  Card,
@@ -22,7 +22,17 @@ import {
22
22
  SelectTrigger,
23
23
  SelectValue,
24
24
  } from '../components/ui';
25
- import { EmphasisPanel, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';
25
+ import {
26
+ EmphasisPanel,
27
+ NextSteps,
28
+ PagePurpose,
29
+ ResponsiveDialog,
30
+ ResponsiveDialogContent,
31
+ ResponsiveDialogHeader,
32
+ ResponsiveDialogFooter,
33
+ ResponsiveDialogTitle,
34
+ ResponsiveDialogDescription,
35
+ } from '@burdenoff/fe-libs/ui';
26
36
  import { useWarehouse, useInventory } from '../hooks/useInventory';
27
37
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
28
38
  import { formatAddress } from '../utils/formatters';
@@ -37,24 +47,36 @@ const WAREHOUSE_TYPE_OPTIONS = [
37
47
  { value: 'RETAIL', label: 'Retail' },
38
48
  ];
39
49
 
40
- function NewWarehouseForm({ editId }: { editId?: string }) {
50
+ const WAREHOUSE_STATUS_OPTIONS = [
51
+ { value: 'ACTIVE', label: 'Active' },
52
+ { value: 'INACTIVE', label: 'Inactive' },
53
+ ];
54
+
55
+ function NewWarehouseForm({ editId, onSaved }: { editId?: string; onSaved?: () => Promise<void> }) {
41
56
  const { basePath, navigate } = useMoveTheWheels();
42
- const { createWarehouse, updateWarehouse } = useInventory();
57
+ const { createWarehouse, updateWarehouse, deleteWarehouse } = useInventory();
43
58
  const { warehouse } = useWarehouse(editId);
44
59
  const isEditing = !!editId;
45
60
  const [form, setForm] = useState({
46
61
  code: '',
47
62
  name: '',
48
63
  type: 'DISTRIBUTION',
64
+ status: 'ACTIVE',
49
65
  street: '',
50
66
  city: '',
51
67
  state: '',
52
68
  postalCode: '',
53
69
  country: 'USA',
54
70
  totalCapacity: '',
71
+ usedCapacity: '',
72
+ contactEmail: '',
73
+ contactPhone: '',
55
74
  });
56
75
  const [submitting, setSubmitting] = useState(false);
57
76
  const [error, setError] = useState<string | null>(null);
77
+ const [deleteOpen, setDeleteOpen] = useState(false);
78
+ const [deleting, setDeleting] = useState(false);
79
+ const [deleteError, setDeleteError] = useState<string | null>(null);
58
80
 
59
81
  useEffect(() => {
60
82
  if (!editId || !warehouse) return;
@@ -62,12 +84,16 @@ function NewWarehouseForm({ editId }: { editId?: string }) {
62
84
  code: warehouse.code || '',
63
85
  name: warehouse.name || '',
64
86
  type: warehouse.type ? warehouse.type.toUpperCase() : 'DISTRIBUTION',
87
+ status: warehouse.status || 'ACTIVE',
65
88
  street: warehouse.address.street || '',
66
89
  city: warehouse.address.city || '',
67
90
  state: warehouse.address.state || '',
68
91
  postalCode: warehouse.address.postalCode || '',
69
92
  country: warehouse.address.country || 'USA',
70
93
  totalCapacity: warehouse.capacity.totalSpace ? String(warehouse.capacity.totalSpace) : '',
94
+ usedCapacity: warehouse.capacity.usedSpace ? String(warehouse.capacity.usedSpace) : '',
95
+ contactEmail: warehouse.contactInfo?.email || '',
96
+ contactPhone: warehouse.contactInfo?.phone || '',
71
97
  });
72
98
  }, [editId, warehouse]);
73
99
 
@@ -90,10 +116,12 @@ function NewWarehouseForm({ editId }: { editId?: string }) {
90
116
  setError(null);
91
117
  try {
92
118
  const totalCapacity = form.totalCapacity.trim() ? Number(form.totalCapacity) : undefined;
119
+ const usedCapacity = form.usedCapacity.trim() ? Number(form.usedCapacity) : undefined;
93
120
  const warehouseInput = {
94
121
  code: form.code.trim(),
95
122
  name: form.name.trim(),
96
123
  type: form.type,
124
+ status: form.status,
97
125
  address: {
98
126
  street: form.street.trim(),
99
127
  city: form.city.trim(),
@@ -101,11 +129,21 @@ function NewWarehouseForm({ editId }: { editId?: string }) {
101
129
  postalCode: form.postalCode.trim(),
102
130
  country: form.country.trim(),
103
131
  },
132
+ contactInfo:
133
+ form.contactEmail.trim() || form.contactPhone.trim()
134
+ ? {
135
+ email: form.contactEmail.trim() || undefined,
136
+ phone: form.contactPhone.trim() || undefined,
137
+ }
138
+ : undefined,
104
139
  totalCapacity:
105
140
  totalCapacity !== undefined && !Number.isNaN(totalCapacity) ? totalCapacity : undefined,
141
+ usedCapacity:
142
+ usedCapacity !== undefined && !Number.isNaN(usedCapacity) ? usedCapacity : undefined,
106
143
  };
107
144
  if (editId) {
108
145
  await updateWarehouse(editId, warehouseInput);
146
+ await onSaved?.();
109
147
  navigate?.(joinPath(basePath, `inventory/warehouses/${editId}`));
110
148
  } else {
111
149
  const created = await createWarehouse(warehouseInput);
@@ -120,6 +158,20 @@ function NewWarehouseForm({ editId }: { editId?: string }) {
120
158
  }
121
159
  };
122
160
 
161
+ const handleDelete = async () => {
162
+ if (!editId) return;
163
+ setDeleting(true);
164
+ setDeleteError(null);
165
+ try {
166
+ await deleteWarehouse(editId);
167
+ setDeleteOpen(false);
168
+ goBack();
169
+ } catch (err) {
170
+ setDeleteError(err instanceof Error ? err.message : 'Failed to delete warehouse.');
171
+ setDeleting(false);
172
+ }
173
+ };
174
+
123
175
  return (
124
176
  <PageLayout
125
177
  title={isEditing ? 'Edit Warehouse' : 'Add Warehouse'}
@@ -196,16 +248,65 @@ function NewWarehouseForm({ editId }: { editId?: string }) {
196
248
  </Select>
197
249
  </div>
198
250
  </div>
199
- <div className="space-y-2">
200
- <Label htmlFor="totalCapacity">Total Capacity (sq ft)</Label>
201
- <Input
202
- id="totalCapacity"
203
- type="number"
204
- min="0"
205
- value={form.totalCapacity}
206
- onChange={(e) => update('totalCapacity', e.target.value)}
207
- placeholder="50000"
208
- />
251
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
252
+ <div className="space-y-2">
253
+ <Label htmlFor="status">Status</Label>
254
+ <Select value={form.status} onValueChange={(value) => update('status', value)}>
255
+ <SelectTrigger id="status">
256
+ <SelectValue />
257
+ </SelectTrigger>
258
+ <SelectContent>
259
+ {WAREHOUSE_STATUS_OPTIONS.map((option) => (
260
+ <SelectItem key={option.value} value={option.value}>
261
+ {option.label}
262
+ </SelectItem>
263
+ ))}
264
+ </SelectContent>
265
+ </Select>
266
+ </div>
267
+ <div className="space-y-2">
268
+ <Label htmlFor="totalCapacity">Total Capacity (sq ft)</Label>
269
+ <Input
270
+ id="totalCapacity"
271
+ type="number"
272
+ min="0"
273
+ value={form.totalCapacity}
274
+ onChange={(e) => update('totalCapacity', e.target.value)}
275
+ placeholder="50000"
276
+ />
277
+ </div>
278
+ <div className="space-y-2">
279
+ <Label htmlFor="usedCapacity">Used Capacity (sq ft)</Label>
280
+ <Input
281
+ id="usedCapacity"
282
+ type="number"
283
+ min="0"
284
+ value={form.usedCapacity}
285
+ onChange={(e) => update('usedCapacity', e.target.value)}
286
+ placeholder="0"
287
+ />
288
+ </div>
289
+ </div>
290
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
291
+ <div className="space-y-2">
292
+ <Label htmlFor="contactEmail">Contact Email</Label>
293
+ <Input
294
+ id="contactEmail"
295
+ type="email"
296
+ value={form.contactEmail}
297
+ onChange={(e) => update('contactEmail', e.target.value)}
298
+ placeholder="dc-sf@example.com"
299
+ />
300
+ </div>
301
+ <div className="space-y-2">
302
+ <Label htmlFor="contactPhone">Contact Phone</Label>
303
+ <Input
304
+ id="contactPhone"
305
+ value={form.contactPhone}
306
+ onChange={(e) => update('contactPhone', e.target.value)}
307
+ placeholder="+1-555-0100"
308
+ />
309
+ </div>
209
310
  </div>
210
311
  </div>
211
312
  </EmphasisPanel>
@@ -268,7 +369,56 @@ function NewWarehouseForm({ editId }: { editId?: string }) {
268
369
  </div>
269
370
  </CardContent>
270
371
  </Card>
372
+
373
+ {isEditing && (
374
+ <Card className="border-status-error-border">
375
+ <CardHeader>
376
+ <CardTitle className="flex items-center gap-2 text-status-error-text">
377
+ <Trash2 size={18} /> Delete Warehouse
378
+ </CardTitle>
379
+ </CardHeader>
380
+ <CardContent>
381
+ <p className="mb-3 text-sm text-text-secondary">
382
+ Removes this warehouse from your network. This cannot be undone from here.
383
+ </p>
384
+ <Button
385
+ type="button"
386
+ variant="outline"
387
+ className="border-status-error-border text-status-error-text hover:bg-status-error-bg-subtle"
388
+ onClick={() => setDeleteOpen(true)}
389
+ >
390
+ <Trash2 size={16} /> Delete this warehouse
391
+ </Button>
392
+ </CardContent>
393
+ </Card>
394
+ )}
271
395
  </form>
396
+
397
+ <ResponsiveDialog open={deleteOpen} onOpenChange={setDeleteOpen}>
398
+ <ResponsiveDialogContent>
399
+ <ResponsiveDialogHeader>
400
+ <ResponsiveDialogTitle>Delete this warehouse?</ResponsiveDialogTitle>
401
+ <ResponsiveDialogDescription>
402
+ This removes {form.name || 'this warehouse'} and cannot be undone from here.
403
+ </ResponsiveDialogDescription>
404
+ </ResponsiveDialogHeader>
405
+ <div className="px-1">
406
+ <FormError message={deleteError} />
407
+ </div>
408
+ <ResponsiveDialogFooter>
409
+ <Button variant="outline" onClick={() => setDeleteOpen(false)} disabled={deleting}>
410
+ Cancel
411
+ </Button>
412
+ <Button
413
+ className="bg-status-error-text text-text-inverse hover:opacity-90"
414
+ onClick={handleDelete}
415
+ disabled={deleting}
416
+ >
417
+ {deleting ? 'Deleting...' : 'Delete warehouse'}
418
+ </Button>
419
+ </ResponsiveDialogFooter>
420
+ </ResponsiveDialogContent>
421
+ </ResponsiveDialog>
272
422
  </PageLayout>
273
423
  );
274
424
  }
@@ -277,17 +427,17 @@ export default function WarehouseDetailsPage() {
277
427
  const { warehouseId } = useParams<{ warehouseId: string }>();
278
428
  const { basePath, navigate } = useMoveTheWheels();
279
429
  const isEditRoute = useLocation().pathname.endsWith('/edit');
280
- const { warehouse, isLoading, error, refetch } = useWarehouse(warehouseId);
430
+ const { warehouse, isLoading, error, refetch, reload } = useWarehouse(warehouseId);
281
431
 
282
432
  const goBack = () => navigate?.(joinPath(basePath, 'inventory/warehouses'));
283
433
  const goTo = (path: string) => navigate?.(joinPath(basePath, path));
284
434
 
285
435
  if (!warehouseId) return <NewWarehouseForm />;
286
- if (isEditRoute) return <NewWarehouseForm editId={warehouseId} />;
436
+ if (isEditRoute) return <NewWarehouseForm editId={warehouseId} onSaved={reload} />;
287
437
  if (isLoading) return <LoadingState message="Loading warehouse details..." />;
288
438
  if (error || !warehouse) return <ErrorState title="Warehouse not found" onRetry={refetch} />;
289
439
 
290
- const utilization = (warehouse.capacity.usedSpace / warehouse.capacity.totalSpace) * 100;
440
+ const isActive = warehouse.status.toUpperCase() === 'ACTIVE';
291
441
 
292
442
  return (
293
443
  <PageLayout
@@ -310,8 +460,8 @@ export default function WarehouseDetailsPage() {
310
460
  }
311
461
  headerContent={
312
462
  <div className="flex gap-2">
313
- <Badge variant={warehouse.isActive ? 'default' : 'secondary'}>
314
- {warehouse.isActive ? 'Active' : 'Inactive'}
463
+ <Badge variant={isActive ? 'default' : 'secondary'}>
464
+ {isActive ? 'Active' : 'Inactive'}
315
465
  </Badge>
316
466
  <Badge variant="outline" className="capitalize">
317
467
  {warehouse.type.replace('_', ' ')}
@@ -320,9 +470,9 @@ export default function WarehouseDetailsPage() {
320
470
  }
321
471
  >
322
472
  <PagePurpose icon={<Warehouse size={18} />}>
323
- A storage and fulfilment site at a glance — how full it is, its zones, operating hours,
324
- throughput and staff. Use it to decide whether this warehouse has the space and hours to
325
- fulfil an order, and where stock is running tight.
473
+ A storage and fulfilment site at a glance — how full it is, its operating hours and how to
474
+ reach it. Use it to decide whether this warehouse has the space and hours to fulfil an
475
+ order.
326
476
  </PagePurpose>
327
477
 
328
478
  <NextSteps
@@ -352,65 +502,19 @@ export default function WarehouseDetailsPage() {
352
502
  <h2 className="flex items-center gap-2 text-base font-semibold text-text-primary mb-4">
353
503
  <Box size={18} /> Capacity
354
504
  </h2>
355
- <div className="space-y-4">
356
- <div>
357
- <div className="flex justify-between text-sm mb-2">
358
- <span>Space Utilization</span>
359
- <span>{utilization.toFixed(0)}%</span>
360
- </div>
361
- <Progress value={utilization} />
362
- <p className="text-sm text-text-secondary mt-1">
363
- {warehouse.capacity.usedSpace.toLocaleString()} /{' '}
364
- {warehouse.capacity.totalSpace.toLocaleString()} sq ft
365
- </p>
366
- </div>
367
- <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
368
- <div>
369
- <p className="text-sm text-text-secondary">Pallets Used</p>
370
- <p className="text-2xl font-bold">
371
- {warehouse.capacity.usedPallets}
372
- <span className="text-sm font-normal text-text-secondary">
373
- {' '}
374
- / {warehouse.capacity.totalPallets}
375
- </span>
376
- </p>
377
- </div>
505
+ <div>
506
+ <div className="flex justify-between text-sm mb-2">
507
+ <span>Space Utilization</span>
508
+ <span>{warehouse.utilization.toFixed(0)}%</span>
378
509
  </div>
510
+ <Progress value={warehouse.utilization} />
511
+ <p className="text-sm text-text-secondary mt-1">
512
+ {warehouse.capacity.usedSpace.toLocaleString()} /{' '}
513
+ {warehouse.capacity.totalSpace.toLocaleString()} sq ft
514
+ </p>
379
515
  </div>
380
516
  </EmphasisPanel>
381
517
 
382
- <Card>
383
- <CardHeader>
384
- <CardTitle>Zones ({warehouse.zones.length})</CardTitle>
385
- </CardHeader>
386
- <CardContent>
387
- <div className="space-y-3">
388
- {warehouse.zones.map((zone) => (
389
- <div
390
- key={zone.id}
391
- className="flex items-center justify-between p-3 border rounded-lg"
392
- >
393
- <div>
394
- <p className="font-medium">{zone.name}</p>
395
- <p className="text-sm text-text-secondary capitalize">
396
- {zone.type} - {zone.location}
397
- </p>
398
- </div>
399
- <div className="text-right">
400
- <Progress
401
- value={(zone.currentOccupancy / zone.capacity) * 100}
402
- className="w-24 h-2"
403
- />
404
- <p className="text-sm text-text-secondary">
405
- {zone.currentOccupancy} / {zone.capacity}
406
- </p>
407
- </div>
408
- </div>
409
- ))}
410
- </div>
411
- </CardContent>
412
- </Card>
413
-
414
518
  <Card>
415
519
  <CardHeader>
416
520
  <CardTitle className="flex items-center gap-2">
@@ -418,16 +522,20 @@ export default function WarehouseDetailsPage() {
418
522
  </CardTitle>
419
523
  </CardHeader>
420
524
  <CardContent>
421
- <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
422
- {Object.entries(warehouse.operatingHours).map(([day, hours]) => (
423
- <div key={day}>
424
- <p className="text-sm font-medium">{day}</p>
425
- <p className="text-sm text-text-secondary">
426
- {hours ? `${hours.open} - ${hours.close}` : 'Closed'}
427
- </p>
428
- </div>
429
- ))}
430
- </div>
525
+ {Object.keys(warehouse.operatingHours).length > 0 ? (
526
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
527
+ {Object.entries(warehouse.operatingHours).map(([day, hours]) => (
528
+ <div key={day}>
529
+ <p className="text-sm font-medium">{day}</p>
530
+ <p className="text-sm text-text-secondary">
531
+ {hours ? `${hours.open} - ${hours.close}` : 'Closed'}
532
+ </p>
533
+ </div>
534
+ ))}
535
+ </div>
536
+ ) : (
537
+ <p className="text-sm text-text-secondary">Not recorded</p>
538
+ )}
431
539
  </CardContent>
432
540
  </Card>
433
541
  </div>
@@ -440,60 +548,31 @@ export default function WarehouseDetailsPage() {
440
548
  </CardTitle>
441
549
  </CardHeader>
442
550
  <CardContent>
443
- <p className="text-sm">{formatAddress(warehouse.address)}</p>
551
+ <p className="text-sm">{formatAddress(warehouse.address) || '—'}</p>
444
552
  </CardContent>
445
553
  </Card>
446
554
 
447
- <Card>
448
- <CardHeader>
449
- <CardTitle>Metrics</CardTitle>
450
- </CardHeader>
451
- <CardContent className="space-y-3">
452
- <div>
453
- <p className="text-sm text-text-secondary">Throughput</p>
454
- <p className="text-2xl font-bold">
455
- {warehouse.metrics.throughput}
456
- <span className="text-sm font-normal"> orders/day</span>
457
- </p>
458
- </div>
459
- <div>
460
- <p className="text-sm text-text-secondary">Accuracy</p>
461
- <p className="text-2xl font-bold">{warehouse.metrics.accuracy}%</p>
462
- </div>
463
- <div>
464
- <p className="text-sm text-text-secondary">Productivity</p>
465
- <p className="text-2xl font-bold">{warehouse.metrics.productivity}%</p>
466
- </div>
467
- <div>
468
- <p className="text-sm text-text-secondary">Orders Fulfilled</p>
469
- <p className="text-2xl font-bold">
470
- {warehouse.metrics.ordersFulfilled.toLocaleString()}
471
- </p>
472
- </div>
473
- </CardContent>
474
- </Card>
475
-
476
- <Card>
477
- <CardHeader>
478
- <CardTitle className="flex items-center gap-2">
479
- <Users size={18} /> Staff ({warehouse.staff.length})
480
- </CardTitle>
481
- </CardHeader>
482
- <CardContent>
483
- {warehouse.staff.length > 0 ? (
484
- <div className="space-y-2">
485
- {warehouse.staff.map((member, i) => (
486
- <div key={i} className="flex items-center justify-between p-2 border rounded">
487
- <span className="text-sm capitalize">{member.role}</span>
488
- <Badge variant="outline">{member.shift}</Badge>
489
- </div>
490
- ))}
491
- </div>
492
- ) : (
493
- <p className="text-text-secondary">No staff assigned</p>
494
- )}
495
- </CardContent>
496
- </Card>
555
+ {(warehouse.contactInfo?.email || warehouse.contactInfo?.phone) && (
556
+ <Card>
557
+ <CardHeader>
558
+ <CardTitle>Contact</CardTitle>
559
+ </CardHeader>
560
+ <CardContent className="space-y-3">
561
+ {warehouse.contactInfo?.email && (
562
+ <div>
563
+ <p className="text-sm text-text-secondary">Email</p>
564
+ <p className="font-medium">{warehouse.contactInfo.email}</p>
565
+ </div>
566
+ )}
567
+ {warehouse.contactInfo?.phone && (
568
+ <div>
569
+ <p className="text-sm text-text-secondary">Phone</p>
570
+ <p className="font-medium">{warehouse.contactInfo.phone}</p>
571
+ </div>
572
+ )}
573
+ </CardContent>
574
+ </Card>
575
+ )}
497
576
  </div>
498
577
  </div>
499
578
  </PageLayout>