@burdenoff/microfe-movethewheels 2026.510.105

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 (208) hide show
  1. package/README.md +82 -0
  2. package/dist/AIAssistantPage-hD0VYJdH.js +210 -0
  3. package/dist/AnalyticsPage-DHTHCUtr.js +201 -0
  4. package/dist/CreateOrderPage-Cprg4Y9V.js +471 -0
  5. package/dist/CustomerDetailsPage-DNDEw7IW.js +239 -0
  6. package/dist/CustomersPage-CDjjeCEL.js +119 -0
  7. package/dist/DashboardPage-8iTPXRAG.js +374 -0
  8. package/dist/DataTable-CRIKfdIN.js +239 -0
  9. package/dist/DriverDetailsPage-CRyRCno7.js +297 -0
  10. package/dist/DriversPage-16O8fVmf.js +127 -0
  11. package/dist/FinancePage-BYUxK5dR.js +154 -0
  12. package/dist/FleetPage-CHYETCWT.js +293 -0
  13. package/dist/ImportExportPage-C3MKKxfc.js +232 -0
  14. package/dist/InventoryPage--822AxZM.js +223 -0
  15. package/dist/LiveTrackingPage-Dp3rTJDr.js +332 -0
  16. package/dist/MarketplacePage-DjEqudfM.js +192 -0
  17. package/dist/MetricCard-GTbxAk1a.js +135 -0
  18. package/dist/OrderDetailsPage-BIuYG0ub.js +398 -0
  19. package/dist/OrdersListPage-CW5V0Uvh.js +257 -0
  20. package/dist/PageLayout-B7b0vl0R.js +1894 -0
  21. package/dist/ProductDetailsPage-Q3X7AT-7.js +168 -0
  22. package/dist/ProductsPage-CUj9JpnW.js +131 -0
  23. package/dist/ReportsPage-DblO5CdJ.js +227 -0
  24. package/dist/RouteDetailsPage-CLctgk6A.js +240 -0
  25. package/dist/RoutesPage-8hrv6RWT.js +116 -0
  26. package/dist/SettingsPage-BJ5BQeqn.js +247 -0
  27. package/dist/StatusBadge-BrrwraIA.js +206 -0
  28. package/dist/TrackingPage-BGqHDh-w.js +322 -0
  29. package/dist/VehicleDetailsPage-XnDH4iQR.js +194 -0
  30. package/dist/VehiclesPage-Cs4XxHkA.js +127 -0
  31. package/dist/WarehouseDetailsPage-GemdMvr_.js +215 -0
  32. package/dist/WarehousesPage-QTiuDuXy.js +121 -0
  33. package/dist/arrow-left-6CiLhqVp.js +11 -0
  34. package/dist/box-BunB_4UH.js +18 -0
  35. package/dist/chart-column-DWwVEVQ-.js +22 -0
  36. package/dist/chevron-right-DhZVf20o.js +8 -0
  37. package/dist/circle-alert-D5f6RZxt.js +26 -0
  38. package/dist/circle-check-big-D-JMHcTe.js +11 -0
  39. package/dist/clock-CvwBKbQP.js +13 -0
  40. package/dist/dev/main.d.ts +1 -0
  41. package/dist/dollar-sign-CP9qeU5d.js +14 -0
  42. package/dist/download-CIuG04pJ.js +21 -0
  43. package/dist/file-text-Dd_thxkn.js +26 -0
  44. package/dist/filter-DyRMX9CU.js +8 -0
  45. package/dist/formatters-_vJlC-47.js +50 -0
  46. package/dist/generated/global-operations.d.ts +1 -0
  47. package/dist/generated/global-types.d.ts +20715 -0
  48. package/dist/generated/wspace-operations.d.ts +3704 -0
  49. package/dist/generated/wspace-types.d.ts +53362 -0
  50. package/dist/graphqlClient-CdJyR_ed.js +55 -0
  51. package/dist/index.d.ts +4 -0
  52. package/dist/index.js +772 -0
  53. package/dist/map-BqH1cBJi.js +18 -0
  54. package/dist/map-pin-CFBOmh-A.js +13 -0
  55. package/dist/movethewheels/MoveTheWheelsRoot.d.ts +25 -0
  56. package/dist/movethewheels/MoveTheWheelsRoutes.d.ts +7 -0
  57. package/dist/movethewheels/components/DataTable.d.ts +32 -0
  58. package/dist/movethewheels/components/MetricCard.d.ts +43 -0
  59. package/dist/movethewheels/components/PageLayout.d.ts +68 -0
  60. package/dist/movethewheels/components/StatusBadge.d.ts +49 -0
  61. package/dist/movethewheels/components/index.d.ts +10 -0
  62. package/dist/movethewheels/components/ui.d.ts +22 -0
  63. package/dist/movethewheels/constants/index.d.ts +24 -0
  64. package/dist/movethewheels/constants/mockData.d.ts +33 -0
  65. package/dist/movethewheels/hooks/index.d.ts +12 -0
  66. package/dist/movethewheels/hooks/useAnalytics.d.ts +118 -0
  67. package/dist/movethewheels/hooks/useCustomers.d.ts +37 -0
  68. package/dist/movethewheels/hooks/useFleet.d.ts +71 -0
  69. package/dist/movethewheels/hooks/useInventory.d.ts +60 -0
  70. package/dist/movethewheels/hooks/useOrders.d.ts +47 -0
  71. package/dist/movethewheels/hooks/useRoutes.d.ts +41 -0
  72. package/dist/movethewheels/hooks/useTracking.d.ts +69 -0
  73. package/dist/movethewheels/index.d.ts +30 -0
  74. package/dist/movethewheels/pages/AIAssistantPage.d.ts +4 -0
  75. package/dist/movethewheels/pages/AnalyticsPage.d.ts +4 -0
  76. package/dist/movethewheels/pages/CreateOrderPage.d.ts +6 -0
  77. package/dist/movethewheels/pages/CustomerDetailsPage.d.ts +4 -0
  78. package/dist/movethewheels/pages/CustomersPage.d.ts +4 -0
  79. package/dist/movethewheels/pages/DashboardPage.d.ts +6 -0
  80. package/dist/movethewheels/pages/DriverDetailsPage.d.ts +4 -0
  81. package/dist/movethewheels/pages/DriversPage.d.ts +4 -0
  82. package/dist/movethewheels/pages/FinancePage.d.ts +4 -0
  83. package/dist/movethewheels/pages/FleetPage.d.ts +6 -0
  84. package/dist/movethewheels/pages/ImportExportPage.d.ts +4 -0
  85. package/dist/movethewheels/pages/InventoryPage.d.ts +4 -0
  86. package/dist/movethewheels/pages/LiveTrackingPage.d.ts +6 -0
  87. package/dist/movethewheels/pages/MarketplacePage.d.ts +4 -0
  88. package/dist/movethewheels/pages/OrderDetailsPage.d.ts +6 -0
  89. package/dist/movethewheels/pages/OrdersListPage.d.ts +6 -0
  90. package/dist/movethewheels/pages/ProductDetailsPage.d.ts +4 -0
  91. package/dist/movethewheels/pages/ProductsPage.d.ts +4 -0
  92. package/dist/movethewheels/pages/ReportsPage.d.ts +4 -0
  93. package/dist/movethewheels/pages/RouteDetailsPage.d.ts +4 -0
  94. package/dist/movethewheels/pages/RoutesPage.d.ts +4 -0
  95. package/dist/movethewheels/pages/SettingsPage.d.ts +4 -0
  96. package/dist/movethewheels/pages/TrackingPage.d.ts +6 -0
  97. package/dist/movethewheels/pages/VehicleDetailsPage.d.ts +4 -0
  98. package/dist/movethewheels/pages/VehiclesPage.d.ts +4 -0
  99. package/dist/movethewheels/pages/WarehouseDetailsPage.d.ts +4 -0
  100. package/dist/movethewheels/pages/WarehousesPage.d.ts +4 -0
  101. package/dist/movethewheels/providers/MoveTheWheelsProvider.d.ts +16 -0
  102. package/dist/movethewheels/store/movethewheelsStore.d.ts +73 -0
  103. package/dist/movethewheels/types/index.d.ts +655 -0
  104. package/dist/movethewheels/utils/cn.d.ts +6 -0
  105. package/dist/movethewheels/utils/formatters.d.ts +60 -0
  106. package/dist/movethewheels/utils/graphqlClient.d.ts +11 -0
  107. package/dist/movethewheels/utils/index.d.ts +7 -0
  108. package/dist/movethewheels/utils/navigation.d.ts +23 -0
  109. package/dist/navigation-BgnOfsVd.js +6 -0
  110. package/dist/navigation-C2fY_aS9.js +8 -0
  111. package/dist/package-DVZbDRcV.js +22 -0
  112. package/dist/phone-KdwpVmC4.js +18 -0
  113. package/dist/plus-Bl7uX6Ji.js +11 -0
  114. package/dist/refresh-cw-BYjl3K-8.js +22 -0
  115. package/dist/route-Ce_poKFi.js +51 -0
  116. package/dist/save-C-qDVat-.js +18 -0
  117. package/dist/search-5pdn5eOO.js +13 -0
  118. package/dist/settings-C4kIDsYg.js +28 -0
  119. package/dist/square-pen-BwQ67vLE.js +11 -0
  120. package/dist/star-BlVsC3Ad.js +8 -0
  121. package/dist/store-DTmQT5M0.js +26 -0
  122. package/dist/trending-up-C1faflCI.js +11 -0
  123. package/dist/triangle-alert-CUoVAA4L.js +18 -0
  124. package/dist/truck-BmDAzu05.js +30 -0
  125. package/dist/useAnalytics-ph7eTIK6.js +297 -0
  126. package/dist/useCustomers-bS3a4ytk.js +186 -0
  127. package/dist/useFleet-BdETplNE.js +398 -0
  128. package/dist/useInventory-Dwn18FPz.js +323 -0
  129. package/dist/useOrders-D_3_hGMp.js +324 -0
  130. package/dist/useRoutes-v4aBaS-E.js +224 -0
  131. package/dist/useTracking-De2KIUNu.js +261 -0
  132. package/dist/user-BplzDrLP.js +13 -0
  133. package/dist/users-i-igmsP4.js +24 -0
  134. package/dist/warehouse-DewG0PXh.js +25 -0
  135. package/dist/wrench-CoSDEIC7.js +31 -0
  136. package/package.json +107 -0
  137. package/src/dev/main.tsx +110 -0
  138. package/src/dev/styles.css +139 -0
  139. package/src/generated/global-operations.ts +2 -0
  140. package/src/generated/global-types.ts +24048 -0
  141. package/src/generated/wspace-operations.ts +3734 -0
  142. package/src/generated/wspace-types.ts +60715 -0
  143. package/src/index.ts +4 -0
  144. package/src/movethewheels/MoveTheWheelsRoot.tsx +258 -0
  145. package/src/movethewheels/MoveTheWheelsRoutes.tsx +119 -0
  146. package/src/movethewheels/components/DataTable.tsx +367 -0
  147. package/src/movethewheels/components/MetricCard.tsx +180 -0
  148. package/src/movethewheels/components/PageLayout.tsx +234 -0
  149. package/src/movethewheels/components/StatusBadge.tsx +243 -0
  150. package/src/movethewheels/components/index.ts +26 -0
  151. package/src/movethewheels/components/ui.tsx +124 -0
  152. package/src/movethewheels/constants/index.ts +65 -0
  153. package/src/movethewheels/constants/mockData.ts +1342 -0
  154. package/src/movethewheels/hooks/index.ts +55 -0
  155. package/src/movethewheels/hooks/useAnalytics.ts +476 -0
  156. package/src/movethewheels/hooks/useCustomers.ts +359 -0
  157. package/src/movethewheels/hooks/useFleet.ts +778 -0
  158. package/src/movethewheels/hooks/useInventory.ts +632 -0
  159. package/src/movethewheels/hooks/useOrders.ts +703 -0
  160. package/src/movethewheels/hooks/useRoutes.ts +453 -0
  161. package/src/movethewheels/hooks/useTracking.ts +505 -0
  162. package/src/movethewheels/index.ts +68 -0
  163. package/src/movethewheels/pages/AIAssistantPage.tsx +160 -0
  164. package/src/movethewheels/pages/AnalyticsPage.tsx +190 -0
  165. package/src/movethewheels/pages/CreateOrderPage.tsx +454 -0
  166. package/src/movethewheels/pages/CustomerDetailsPage.tsx +207 -0
  167. package/src/movethewheels/pages/CustomersPage.tsx +115 -0
  168. package/src/movethewheels/pages/DashboardPage.tsx +414 -0
  169. package/src/movethewheels/pages/DriverDetailsPage.tsx +261 -0
  170. package/src/movethewheels/pages/DriversPage.tsx +118 -0
  171. package/src/movethewheels/pages/FinancePage.tsx +141 -0
  172. package/src/movethewheels/pages/FleetPage.tsx +289 -0
  173. package/src/movethewheels/pages/ImportExportPage.tsx +165 -0
  174. package/src/movethewheels/pages/InventoryPage.tsx +212 -0
  175. package/src/movethewheels/pages/LiveTrackingPage.tsx +325 -0
  176. package/src/movethewheels/pages/MarketplacePage.tsx +235 -0
  177. package/src/movethewheels/pages/OrderDetailsPage.tsx +387 -0
  178. package/src/movethewheels/pages/OrdersListPage.tsx +241 -0
  179. package/src/movethewheels/pages/ProductDetailsPage.tsx +155 -0
  180. package/src/movethewheels/pages/ProductsPage.tsx +124 -0
  181. package/src/movethewheels/pages/ReportsPage.tsx +164 -0
  182. package/src/movethewheels/pages/RouteDetailsPage.tsx +245 -0
  183. package/src/movethewheels/pages/RoutesPage.tsx +104 -0
  184. package/src/movethewheels/pages/SettingsPage.tsx +242 -0
  185. package/src/movethewheels/pages/TrackingPage.tsx +419 -0
  186. package/src/movethewheels/pages/VehicleDetailsPage.tsx +218 -0
  187. package/src/movethewheels/pages/VehiclesPage.tsx +124 -0
  188. package/src/movethewheels/pages/WarehouseDetailsPage.tsx +216 -0
  189. package/src/movethewheels/pages/WarehousesPage.tsx +122 -0
  190. package/src/movethewheels/providers/MoveTheWheelsProvider.tsx +66 -0
  191. package/src/movethewheels/store/movethewheelsStore.ts +136 -0
  192. package/src/movethewheels/types/index.ts +744 -0
  193. package/src/movethewheels/utils/cn.ts +9 -0
  194. package/src/movethewheels/utils/formatters.ts +215 -0
  195. package/src/movethewheels/utils/graphqlClient.ts +63 -0
  196. package/src/movethewheels/utils/index.ts +8 -0
  197. package/src/movethewheels/utils/navigation.ts +70 -0
  198. package/src/operations/global/.gitkeep +0 -0
  199. package/src/operations/wspace/movethewheels/fragments/core.graphql +191 -0
  200. package/src/operations/wspace/movethewheels/mutations/entities.graphql +87 -0
  201. package/src/operations/wspace/movethewheels/mutations/logistics.graphql +86 -0
  202. package/src/operations/wspace/movethewheels/mutations/marketplace-reports.graphql +81 -0
  203. package/src/operations/wspace/movethewheels/mutations/orders.graphql +21 -0
  204. package/src/operations/wspace/movethewheels/queries/dashboard.graphql +61 -0
  205. package/src/operations/wspace/movethewheels/queries/entities.graphql +83 -0
  206. package/src/operations/wspace/movethewheels/queries/logistics.graphql +84 -0
  207. package/src/operations/wspace/movethewheels/queries/marketplace-reports.graphql +40 -0
  208. package/src/operations/wspace/movethewheels/queries/orders.graphql +43 -0
@@ -0,0 +1,325 @@
1
+ /**
2
+ * Live Tracking Page
3
+ *
4
+ * Real-time map view of all active deliveries.
5
+ */
6
+
7
+ import { useState } from 'react';
8
+ import { MapPin, Truck, Package, RefreshCw, List, Map } from 'lucide-react';
9
+ import { PageLayout } from '../components/PageLayout';
10
+ import { StatusBadge } from '../components/StatusBadge';
11
+ import { MetricCard, MetricGrid } from '../components/MetricCard';
12
+ import {
13
+ Card,
14
+ CardContent,
15
+ CardHeader,
16
+ CardTitle,
17
+ Button,
18
+ Badge,
19
+ Tabs,
20
+ TabsList,
21
+ TabsTrigger,
22
+ ScrollArea,
23
+ } from '../components/ui';
24
+ import { useLiveTracking, useTrackingAlerts } from '../hooks/useTracking';
25
+ import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
26
+ import { formatDate } from '../utils/formatters';
27
+ import { joinPath } from '../utils/navigation';
28
+
29
+ export default function LiveTrackingPage() {
30
+ const { basePath, navigate } = useMoveTheWheels();
31
+ const { activeDeliveries, isLoading, refetch } = useLiveTracking();
32
+ const { unacknowledgedAlerts } = useTrackingAlerts();
33
+ const [viewMode, setViewMode] = useState<'map' | 'list'>('map');
34
+ const [selectedDelivery, setSelectedDelivery] = useState<string | null>(null);
35
+
36
+ const goTo = (path: string) => {
37
+ if (navigate) {
38
+ navigate(joinPath(basePath, path));
39
+ }
40
+ };
41
+
42
+ const selected = selectedDelivery
43
+ ? activeDeliveries.find((d) => d.order?.id === selectedDelivery)
44
+ : null;
45
+
46
+ return (
47
+ <PageLayout
48
+ title="Live Tracking"
49
+ subtitle={`${activeDeliveries.length} active deliveries`}
50
+ icon={<MapPin size={24} />}
51
+ headerActions={
52
+ <div className="flex gap-2">
53
+ <Button variant="outline" onClick={refetch} disabled={isLoading}>
54
+ <RefreshCw size={16} className={isLoading ? 'animate-spin' : ''} />
55
+ Refresh
56
+ </Button>
57
+ <Tabs value={viewMode} onValueChange={(v) => setViewMode(v as 'map' | 'list')}>
58
+ <TabsList>
59
+ <TabsTrigger value="map">
60
+ <Map size={16} />
61
+ </TabsTrigger>
62
+ <TabsTrigger value="list">
63
+ <List size={16} />
64
+ </TabsTrigger>
65
+ </TabsList>
66
+ </Tabs>
67
+ </div>
68
+ }
69
+ >
70
+ {/* Quick Stats */}
71
+ <MetricGrid columns={4}>
72
+ <MetricCard
73
+ size="sm"
74
+ label="Active Deliveries"
75
+ value={activeDeliveries.length}
76
+ icon={<Truck size={18} />}
77
+ />
78
+ <MetricCard
79
+ size="sm"
80
+ label="In Transit"
81
+ value={activeDeliveries.filter((d) => d.order?.status === 'in_transit').length}
82
+ icon={<Package size={18} />}
83
+ />
84
+ <MetricCard
85
+ size="sm"
86
+ label="Out for Delivery"
87
+ value={activeDeliveries.filter((d) => d.order?.status === 'out_for_delivery').length}
88
+ icon={<MapPin size={18} />}
89
+ />
90
+ <MetricCard
91
+ size="sm"
92
+ label="Alerts"
93
+ value={unacknowledgedAlerts.length}
94
+ changeType={unacknowledgedAlerts.length > 0 ? 'negative' : 'neutral'}
95
+ />
96
+ </MetricGrid>
97
+
98
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
99
+ {/* Map / List View */}
100
+ <div className="lg:col-span-2">
101
+ {viewMode === 'map' ? (
102
+ <Card className="h-[600px]">
103
+ <CardContent className="p-0 h-full">
104
+ <div className="w-full h-full bg-muted rounded-lg flex items-center justify-center">
105
+ <div className="text-center text-muted-foreground">
106
+ <MapPin size={64} className="mx-auto mb-4 opacity-50" />
107
+ <p className="text-lg font-medium">Map View</p>
108
+ <p className="text-sm">
109
+ Interactive map showing {activeDeliveries.length} active deliveries
110
+ </p>
111
+ <p className="text-xs mt-2">
112
+ (Map integration with Mapbox/Google Maps would go here)
113
+ </p>
114
+ </div>
115
+ </div>
116
+ </CardContent>
117
+ </Card>
118
+ ) : (
119
+ <Card>
120
+ <CardHeader>
121
+ <CardTitle>Active Deliveries</CardTitle>
122
+ </CardHeader>
123
+ <CardContent className="p-0">
124
+ <ScrollArea className="h-[550px]">
125
+ <div className="divide-y">
126
+ {activeDeliveries.map((delivery) => {
127
+ if (!delivery.order) {
128
+ return null;
129
+ }
130
+
131
+ return (
132
+ <div
133
+ key={delivery.order.id}
134
+ className={`p-4 hover:bg-muted/50 cursor-pointer transition-colors ${
135
+ selectedDelivery === delivery.order.id ? 'bg-muted/50' : ''
136
+ }`}
137
+ onClick={() => setSelectedDelivery(delivery.order.id)}
138
+ >
139
+ <div className="flex items-start justify-between">
140
+ <div>
141
+ <div className="flex items-center gap-2 mb-1">
142
+ <span className="font-medium">{delivery.order.orderNumber}</span>
143
+ <StatusBadge
144
+ status={delivery.order.status}
145
+ type="order"
146
+ size="sm"
147
+ />
148
+ </div>
149
+ <p className="text-sm text-muted-foreground">
150
+ {delivery.order.customerInfo.name}
151
+ </p>
152
+ <p className="text-sm text-muted-foreground">
153
+ {delivery.driver?.fullName || 'Unassigned'}
154
+ {delivery.vehicle && ` - ${delivery.vehicle.vehicleNumber}`}
155
+ </p>
156
+ </div>
157
+ <div className="text-right">
158
+ <p className="text-sm font-medium">
159
+ ETA:{' '}
160
+ {delivery.estimatedArrival
161
+ ? formatDate(delivery.estimatedArrival, 'time')
162
+ : 'N/A'}
163
+ </p>
164
+ <p className="text-xs text-muted-foreground">
165
+ {delivery.tracking?.currentLocation.address ||
166
+ 'Location updating...'}
167
+ </p>
168
+ </div>
169
+ </div>
170
+ </div>
171
+ );
172
+ })}
173
+ {activeDeliveries.length === 0 && (
174
+ <div className="p-8 text-center text-muted-foreground">
175
+ No active deliveries
176
+ </div>
177
+ )}
178
+ </div>
179
+ </ScrollArea>
180
+ </CardContent>
181
+ </Card>
182
+ )}
183
+ </div>
184
+
185
+ {/* Sidebar */}
186
+ <div className="space-y-6">
187
+ {/* Selected Delivery Details */}
188
+ {selected ? (
189
+ <Card>
190
+ <CardHeader>
191
+ <div className="flex items-center justify-between">
192
+ <CardTitle>Delivery Details</CardTitle>
193
+ <Button variant="ghost" size="sm" onClick={() => setSelectedDelivery(null)}>
194
+ Clear
195
+ </Button>
196
+ </div>
197
+ </CardHeader>
198
+ <CardContent className="space-y-4">
199
+ <div>
200
+ <p className="text-sm text-muted-foreground">Order</p>
201
+ <p className="font-medium">{selected.order?.orderNumber ?? '-'}</p>
202
+ </div>
203
+ <div>
204
+ <p className="text-sm text-muted-foreground">Customer</p>
205
+ <p className="font-medium">{selected.order?.customerInfo.name ?? '-'}</p>
206
+ <p className="text-sm text-muted-foreground">
207
+ {selected.order?.customerInfo.phone ?? '-'}
208
+ </p>
209
+ </div>
210
+ <div>
211
+ <p className="text-sm text-muted-foreground">Driver</p>
212
+ <p className="font-medium">{selected.driver?.fullName || 'Unassigned'}</p>
213
+ </div>
214
+ <div>
215
+ <p className="text-sm text-muted-foreground">Vehicle</p>
216
+ <p className="font-medium">
217
+ {selected.vehicle
218
+ ? `${selected.vehicle.vehicleNumber} - ${selected.vehicle.make} ${selected.vehicle.model}`
219
+ : 'Not assigned'}
220
+ </p>
221
+ </div>
222
+ <div>
223
+ <p className="text-sm text-muted-foreground">Current Location</p>
224
+ <p className="text-sm">
225
+ {selected.tracking?.currentLocation.address || 'Updating...'}
226
+ </p>
227
+ </div>
228
+ <div>
229
+ <p className="text-sm text-muted-foreground">Estimated Arrival</p>
230
+ <p className="font-medium">
231
+ {selected.estimatedArrival
232
+ ? formatDate(selected.estimatedArrival, 'full')
233
+ : 'Calculating...'}
234
+ </p>
235
+ </div>
236
+ <div className="pt-2 space-y-2">
237
+ <Button
238
+ className="w-full"
239
+ onClick={() => selected.order && goTo(`orders/${selected.order.id}`)}
240
+ disabled={!selected.order}
241
+ >
242
+ View Order
243
+ </Button>
244
+ <Button
245
+ variant="outline"
246
+ className="w-full"
247
+ onClick={() => selected.order && goTo(`tracking?order=${selected.order.id}`)}
248
+ disabled={!selected.order}
249
+ >
250
+ Track Details
251
+ </Button>
252
+ </div>
253
+ </CardContent>
254
+ </Card>
255
+ ) : (
256
+ <Card>
257
+ <CardContent className="p-8 text-center text-muted-foreground">
258
+ <MapPin size={32} className="mx-auto mb-2 opacity-50" />
259
+ <p>Select a delivery to see details</p>
260
+ </CardContent>
261
+ </Card>
262
+ )}
263
+
264
+ {/* Alerts */}
265
+ <Card>
266
+ <CardHeader>
267
+ <CardTitle className="flex items-center gap-2">
268
+ Alerts
269
+ {unacknowledgedAlerts.length > 0 && (
270
+ <Badge variant="destructive">{unacknowledgedAlerts.length}</Badge>
271
+ )}
272
+ </CardTitle>
273
+ </CardHeader>
274
+ <CardContent>
275
+ {unacknowledgedAlerts.length > 0 ? (
276
+ <div className="space-y-3">
277
+ {unacknowledgedAlerts.slice(0, 5).map((alert) => (
278
+ <div
279
+ key={alert.id}
280
+ className={`p-3 rounded-lg text-sm ${
281
+ alert.severity === 'critical' || alert.severity === 'high'
282
+ ? 'bg-red-50 dark:bg-red-900/20'
283
+ : 'bg-yellow-50 dark:bg-yellow-900/20'
284
+ }`}
285
+ >
286
+ <p className="font-medium">{alert.message}</p>
287
+ <p className="text-xs text-muted-foreground mt-1">
288
+ {formatDate(alert.timestamp, 'relative')}
289
+ </p>
290
+ </div>
291
+ ))}
292
+ </div>
293
+ ) : (
294
+ <div className="text-center text-muted-foreground py-4">
295
+ <p className="text-sm">No active alerts</p>
296
+ </div>
297
+ )}
298
+ </CardContent>
299
+ </Card>
300
+
301
+ {/* Quick Actions */}
302
+ <Card>
303
+ <CardHeader>
304
+ <CardTitle>Quick Actions</CardTitle>
305
+ </CardHeader>
306
+ <CardContent className="space-y-2">
307
+ <Button variant="outline" className="w-full" onClick={() => goTo('orders/new')}>
308
+ <Package size={16} />
309
+ Create Order
310
+ </Button>
311
+ <Button variant="outline" className="w-full" onClick={() => goTo('routes')}>
312
+ <MapPin size={16} />
313
+ Manage Routes
314
+ </Button>
315
+ <Button variant="outline" className="w-full" onClick={() => goTo('fleet')}>
316
+ <Truck size={16} />
317
+ Fleet Status
318
+ </Button>
319
+ </CardContent>
320
+ </Card>
321
+ </div>
322
+ </div>
323
+ </PageLayout>
324
+ );
325
+ }
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Marketplace Page
3
+ */
4
+
5
+ import { useCallback, useEffect, useMemo, useState } from 'react';
6
+ import { Store, Star, MapPin, Clock, Filter, Search } from 'lucide-react';
7
+ import { PageLayout } from '../components/PageLayout';
8
+ import { Card, CardContent, CardHeader, CardTitle, Button, Badge, Input } from '../components/ui';
9
+ import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
10
+ import { formatCurrency } from '../utils/formatters';
11
+ import { executeGraphQL } from '../utils/graphqlClient';
12
+
13
+ interface ApiServiceProvider {
14
+ id: string;
15
+ name: string;
16
+ type: string;
17
+ description?: string | null;
18
+ rating?: number | null;
19
+ isVerified: boolean;
20
+ isActive: boolean;
21
+ services?: unknown;
22
+ contactInfo?: unknown;
23
+ }
24
+
25
+ interface ApiServiceOffering {
26
+ id: string;
27
+ providerId: string;
28
+ name: string;
29
+ description?: string | null;
30
+ category?: string | null;
31
+ price: number;
32
+ currency: string;
33
+ availability?: unknown;
34
+ provider?: {
35
+ id: string;
36
+ name: string;
37
+ type: string;
38
+ rating?: number | null;
39
+ } | null;
40
+ }
41
+
42
+ const PROVIDERS_QUERY = `
43
+ query ListServiceProviders($filter: ServiceProviderFilterInput, $pagination: PaginationInput) {
44
+ serviceProviders(filter: $filter, pagination: $pagination) {
45
+ id
46
+ name
47
+ type
48
+ description
49
+ rating
50
+ isVerified
51
+ isActive
52
+ services
53
+ contactInfo
54
+ }
55
+ }
56
+ `;
57
+
58
+ const OFFERINGS_QUERY = `
59
+ query ListServiceOfferings($filter: ServiceOfferingFilterInput, $pagination: PaginationInput) {
60
+ serviceOfferings(filter: $filter, pagination: $pagination) {
61
+ id
62
+ providerId
63
+ name
64
+ description
65
+ category
66
+ price
67
+ currency
68
+ availability
69
+ provider {
70
+ id
71
+ name
72
+ type
73
+ rating
74
+ }
75
+ }
76
+ }
77
+ `;
78
+
79
+ export default function MarketplacePage() {
80
+ const { apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser } =
81
+ useMoveTheWheels();
82
+ const [providers, setProviders] = useState<ApiServiceProvider[]>([]);
83
+ const [offerings, setOfferings] = useState<ApiServiceOffering[]>([]);
84
+ const [search, setSearch] = useState('');
85
+
86
+ const graphQLOptions = useMemo(
87
+ () => ({
88
+ apiGatewayUrl,
89
+ authToken,
90
+ workspaceToken,
91
+ workspaceId,
92
+ tenantId,
93
+ actorId: currentUser?.id,
94
+ }),
95
+ [apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser?.id]
96
+ );
97
+
98
+ const load = useCallback(async () => {
99
+ const [providersData, offeringsData] = await Promise.all([
100
+ executeGraphQL<{ serviceProviders: ApiServiceProvider[] }>(graphQLOptions, PROVIDERS_QUERY, {
101
+ pagination: { limit: 200, offset: 0 },
102
+ }),
103
+ executeGraphQL<{ serviceOfferings: ApiServiceOffering[] }>(graphQLOptions, OFFERINGS_QUERY, {
104
+ pagination: { limit: 200, offset: 0 },
105
+ }),
106
+ ]);
107
+
108
+ setProviders(providersData.serviceProviders);
109
+ setOfferings(offeringsData.serviceOfferings);
110
+ }, [graphQLOptions]);
111
+
112
+ useEffect(() => {
113
+ void load();
114
+ }, [load]);
115
+
116
+ const filteredProviders = useMemo(() => {
117
+ const term = search.trim().toLowerCase();
118
+ if (!term) return providers;
119
+ return providers.filter(
120
+ (provider) =>
121
+ provider.name.toLowerCase().includes(term) ||
122
+ (provider.description ?? '').toLowerCase().includes(term) ||
123
+ provider.type.toLowerCase().includes(term)
124
+ );
125
+ }, [providers, search]);
126
+
127
+ const filteredOfferings = useMemo(() => {
128
+ const term = search.trim().toLowerCase();
129
+ if (!term) return offerings;
130
+ return offerings.filter(
131
+ (offering) =>
132
+ offering.name.toLowerCase().includes(term) ||
133
+ (offering.description ?? '').toLowerCase().includes(term) ||
134
+ (offering.category ?? '').toLowerCase().includes(term)
135
+ );
136
+ }, [offerings, search]);
137
+
138
+ return (
139
+ <PageLayout
140
+ title="Marketplace"
141
+ subtitle="Find logistics service providers"
142
+ icon={<Store size={24} />}
143
+ headerActions={
144
+ <Button variant="outline">
145
+ <Filter size={16} /> Filters
146
+ </Button>
147
+ }
148
+ >
149
+ <Card>
150
+ <CardContent className="p-4">
151
+ <div className="relative">
152
+ <Search
153
+ className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground"
154
+ size={20}
155
+ />
156
+ <Input
157
+ placeholder="Search services, providers, locations..."
158
+ className="pl-10"
159
+ value={search}
160
+ onChange={(event) => setSearch(event.target.value)}
161
+ />
162
+ </div>
163
+ </CardContent>
164
+ </Card>
165
+
166
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
167
+ <div className="lg:col-span-2 space-y-4">
168
+ <h3 className="font-semibold">Service Providers ({filteredProviders.length})</h3>
169
+ {filteredProviders.map((provider) => (
170
+ <Card key={provider.id} className="hover:shadow-md transition-shadow cursor-pointer">
171
+ <CardContent className="p-6">
172
+ <div className="flex items-start justify-between">
173
+ <div>
174
+ <div className="flex items-center gap-2">
175
+ <h4 className="font-semibold text-lg">{provider.name}</h4>
176
+ {provider.isVerified && <Badge variant="default">Verified</Badge>}
177
+ <Badge variant={provider.isActive ? 'default' : 'secondary'}>
178
+ {provider.isActive ? 'active' : 'inactive'}
179
+ </Badge>
180
+ </div>
181
+ <p className="text-muted-foreground mt-1">{provider.description}</p>
182
+ <div className="flex items-center gap-4 mt-3 text-sm text-muted-foreground">
183
+ <span className="flex items-center gap-1">
184
+ <Star size={14} className="text-yellow-500 fill-yellow-500" />{' '}
185
+ {provider.rating ?? 0}
186
+ </span>
187
+ <span className="flex items-center gap-1">
188
+ <MapPin size={14} /> {provider.type}
189
+ </span>
190
+ <span className="flex items-center gap-1">
191
+ <Clock size={14} /> Listed
192
+ </span>
193
+ </div>
194
+ </div>
195
+ <div className="text-right">
196
+ <p className="text-sm text-muted-foreground">Services</p>
197
+ <p className="text-lg font-bold">
198
+ {Array.isArray(provider.services) ? provider.services.length : 0}
199
+ </p>
200
+ </div>
201
+ </div>
202
+ </CardContent>
203
+ </Card>
204
+ ))}
205
+ </div>
206
+
207
+ <div className="space-y-4">
208
+ <h3 className="font-semibold">Popular Services</h3>
209
+ {filteredOfferings.map((service) => (
210
+ <Card key={service.id}>
211
+ <CardHeader>
212
+ <CardTitle className="text-base">{service.name}</CardTitle>
213
+ </CardHeader>
214
+ <CardContent>
215
+ <p className="text-sm text-muted-foreground mb-3">{service.description}</p>
216
+ <div className="flex flex-wrap gap-1 mb-3">
217
+ <Badge variant="outline" className="text-xs">
218
+ {service.category ?? 'general'}
219
+ </Badge>
220
+ <Badge variant="outline" className="text-xs">
221
+ {service.provider?.name ?? 'provider'}
222
+ </Badge>
223
+ </div>
224
+ <div className="flex items-center justify-between">
225
+ <span className="font-bold">{formatCurrency(service.price)}</span>
226
+ <span className="text-sm text-muted-foreground">{service.currency}</span>
227
+ </div>
228
+ </CardContent>
229
+ </Card>
230
+ ))}
231
+ </div>
232
+ </div>
233
+ </PageLayout>
234
+ );
235
+ }