@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.
- package/README.md +82 -0
- package/dist/AIAssistantPage-hD0VYJdH.js +210 -0
- package/dist/AnalyticsPage-DHTHCUtr.js +201 -0
- package/dist/CreateOrderPage-Cprg4Y9V.js +471 -0
- package/dist/CustomerDetailsPage-DNDEw7IW.js +239 -0
- package/dist/CustomersPage-CDjjeCEL.js +119 -0
- package/dist/DashboardPage-8iTPXRAG.js +374 -0
- package/dist/DataTable-CRIKfdIN.js +239 -0
- package/dist/DriverDetailsPage-CRyRCno7.js +297 -0
- package/dist/DriversPage-16O8fVmf.js +127 -0
- package/dist/FinancePage-BYUxK5dR.js +154 -0
- package/dist/FleetPage-CHYETCWT.js +293 -0
- package/dist/ImportExportPage-C3MKKxfc.js +232 -0
- package/dist/InventoryPage--822AxZM.js +223 -0
- package/dist/LiveTrackingPage-Dp3rTJDr.js +332 -0
- package/dist/MarketplacePage-DjEqudfM.js +192 -0
- package/dist/MetricCard-GTbxAk1a.js +135 -0
- package/dist/OrderDetailsPage-BIuYG0ub.js +398 -0
- package/dist/OrdersListPage-CW5V0Uvh.js +257 -0
- package/dist/PageLayout-B7b0vl0R.js +1894 -0
- package/dist/ProductDetailsPage-Q3X7AT-7.js +168 -0
- package/dist/ProductsPage-CUj9JpnW.js +131 -0
- package/dist/ReportsPage-DblO5CdJ.js +227 -0
- package/dist/RouteDetailsPage-CLctgk6A.js +240 -0
- package/dist/RoutesPage-8hrv6RWT.js +116 -0
- package/dist/SettingsPage-BJ5BQeqn.js +247 -0
- package/dist/StatusBadge-BrrwraIA.js +206 -0
- package/dist/TrackingPage-BGqHDh-w.js +322 -0
- package/dist/VehicleDetailsPage-XnDH4iQR.js +194 -0
- package/dist/VehiclesPage-Cs4XxHkA.js +127 -0
- package/dist/WarehouseDetailsPage-GemdMvr_.js +215 -0
- package/dist/WarehousesPage-QTiuDuXy.js +121 -0
- package/dist/arrow-left-6CiLhqVp.js +11 -0
- package/dist/box-BunB_4UH.js +18 -0
- package/dist/chart-column-DWwVEVQ-.js +22 -0
- package/dist/chevron-right-DhZVf20o.js +8 -0
- package/dist/circle-alert-D5f6RZxt.js +26 -0
- package/dist/circle-check-big-D-JMHcTe.js +11 -0
- package/dist/clock-CvwBKbQP.js +13 -0
- package/dist/dev/main.d.ts +1 -0
- package/dist/dollar-sign-CP9qeU5d.js +14 -0
- package/dist/download-CIuG04pJ.js +21 -0
- package/dist/file-text-Dd_thxkn.js +26 -0
- package/dist/filter-DyRMX9CU.js +8 -0
- package/dist/formatters-_vJlC-47.js +50 -0
- package/dist/generated/global-operations.d.ts +1 -0
- package/dist/generated/global-types.d.ts +20715 -0
- package/dist/generated/wspace-operations.d.ts +3704 -0
- package/dist/generated/wspace-types.d.ts +53362 -0
- package/dist/graphqlClient-CdJyR_ed.js +55 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +772 -0
- package/dist/map-BqH1cBJi.js +18 -0
- package/dist/map-pin-CFBOmh-A.js +13 -0
- package/dist/movethewheels/MoveTheWheelsRoot.d.ts +25 -0
- package/dist/movethewheels/MoveTheWheelsRoutes.d.ts +7 -0
- package/dist/movethewheels/components/DataTable.d.ts +32 -0
- package/dist/movethewheels/components/MetricCard.d.ts +43 -0
- package/dist/movethewheels/components/PageLayout.d.ts +68 -0
- package/dist/movethewheels/components/StatusBadge.d.ts +49 -0
- package/dist/movethewheels/components/index.d.ts +10 -0
- package/dist/movethewheels/components/ui.d.ts +22 -0
- package/dist/movethewheels/constants/index.d.ts +24 -0
- package/dist/movethewheels/constants/mockData.d.ts +33 -0
- package/dist/movethewheels/hooks/index.d.ts +12 -0
- package/dist/movethewheels/hooks/useAnalytics.d.ts +118 -0
- package/dist/movethewheels/hooks/useCustomers.d.ts +37 -0
- package/dist/movethewheels/hooks/useFleet.d.ts +71 -0
- package/dist/movethewheels/hooks/useInventory.d.ts +60 -0
- package/dist/movethewheels/hooks/useOrders.d.ts +47 -0
- package/dist/movethewheels/hooks/useRoutes.d.ts +41 -0
- package/dist/movethewheels/hooks/useTracking.d.ts +69 -0
- package/dist/movethewheels/index.d.ts +30 -0
- package/dist/movethewheels/pages/AIAssistantPage.d.ts +4 -0
- package/dist/movethewheels/pages/AnalyticsPage.d.ts +4 -0
- package/dist/movethewheels/pages/CreateOrderPage.d.ts +6 -0
- package/dist/movethewheels/pages/CustomerDetailsPage.d.ts +4 -0
- package/dist/movethewheels/pages/CustomersPage.d.ts +4 -0
- package/dist/movethewheels/pages/DashboardPage.d.ts +6 -0
- package/dist/movethewheels/pages/DriverDetailsPage.d.ts +4 -0
- package/dist/movethewheels/pages/DriversPage.d.ts +4 -0
- package/dist/movethewheels/pages/FinancePage.d.ts +4 -0
- package/dist/movethewheels/pages/FleetPage.d.ts +6 -0
- package/dist/movethewheels/pages/ImportExportPage.d.ts +4 -0
- package/dist/movethewheels/pages/InventoryPage.d.ts +4 -0
- package/dist/movethewheels/pages/LiveTrackingPage.d.ts +6 -0
- package/dist/movethewheels/pages/MarketplacePage.d.ts +4 -0
- package/dist/movethewheels/pages/OrderDetailsPage.d.ts +6 -0
- package/dist/movethewheels/pages/OrdersListPage.d.ts +6 -0
- package/dist/movethewheels/pages/ProductDetailsPage.d.ts +4 -0
- package/dist/movethewheels/pages/ProductsPage.d.ts +4 -0
- package/dist/movethewheels/pages/ReportsPage.d.ts +4 -0
- package/dist/movethewheels/pages/RouteDetailsPage.d.ts +4 -0
- package/dist/movethewheels/pages/RoutesPage.d.ts +4 -0
- package/dist/movethewheels/pages/SettingsPage.d.ts +4 -0
- package/dist/movethewheels/pages/TrackingPage.d.ts +6 -0
- package/dist/movethewheels/pages/VehicleDetailsPage.d.ts +4 -0
- package/dist/movethewheels/pages/VehiclesPage.d.ts +4 -0
- package/dist/movethewheels/pages/WarehouseDetailsPage.d.ts +4 -0
- package/dist/movethewheels/pages/WarehousesPage.d.ts +4 -0
- package/dist/movethewheels/providers/MoveTheWheelsProvider.d.ts +16 -0
- package/dist/movethewheels/store/movethewheelsStore.d.ts +73 -0
- package/dist/movethewheels/types/index.d.ts +655 -0
- package/dist/movethewheels/utils/cn.d.ts +6 -0
- package/dist/movethewheels/utils/formatters.d.ts +60 -0
- package/dist/movethewheels/utils/graphqlClient.d.ts +11 -0
- package/dist/movethewheels/utils/index.d.ts +7 -0
- package/dist/movethewheels/utils/navigation.d.ts +23 -0
- package/dist/navigation-BgnOfsVd.js +6 -0
- package/dist/navigation-C2fY_aS9.js +8 -0
- package/dist/package-DVZbDRcV.js +22 -0
- package/dist/phone-KdwpVmC4.js +18 -0
- package/dist/plus-Bl7uX6Ji.js +11 -0
- package/dist/refresh-cw-BYjl3K-8.js +22 -0
- package/dist/route-Ce_poKFi.js +51 -0
- package/dist/save-C-qDVat-.js +18 -0
- package/dist/search-5pdn5eOO.js +13 -0
- package/dist/settings-C4kIDsYg.js +28 -0
- package/dist/square-pen-BwQ67vLE.js +11 -0
- package/dist/star-BlVsC3Ad.js +8 -0
- package/dist/store-DTmQT5M0.js +26 -0
- package/dist/trending-up-C1faflCI.js +11 -0
- package/dist/triangle-alert-CUoVAA4L.js +18 -0
- package/dist/truck-BmDAzu05.js +30 -0
- package/dist/useAnalytics-ph7eTIK6.js +297 -0
- package/dist/useCustomers-bS3a4ytk.js +186 -0
- package/dist/useFleet-BdETplNE.js +398 -0
- package/dist/useInventory-Dwn18FPz.js +323 -0
- package/dist/useOrders-D_3_hGMp.js +324 -0
- package/dist/useRoutes-v4aBaS-E.js +224 -0
- package/dist/useTracking-De2KIUNu.js +261 -0
- package/dist/user-BplzDrLP.js +13 -0
- package/dist/users-i-igmsP4.js +24 -0
- package/dist/warehouse-DewG0PXh.js +25 -0
- package/dist/wrench-CoSDEIC7.js +31 -0
- package/package.json +107 -0
- package/src/dev/main.tsx +110 -0
- package/src/dev/styles.css +139 -0
- package/src/generated/global-operations.ts +2 -0
- package/src/generated/global-types.ts +24048 -0
- package/src/generated/wspace-operations.ts +3734 -0
- package/src/generated/wspace-types.ts +60715 -0
- package/src/index.ts +4 -0
- package/src/movethewheels/MoveTheWheelsRoot.tsx +258 -0
- package/src/movethewheels/MoveTheWheelsRoutes.tsx +119 -0
- package/src/movethewheels/components/DataTable.tsx +367 -0
- package/src/movethewheels/components/MetricCard.tsx +180 -0
- package/src/movethewheels/components/PageLayout.tsx +234 -0
- package/src/movethewheels/components/StatusBadge.tsx +243 -0
- package/src/movethewheels/components/index.ts +26 -0
- package/src/movethewheels/components/ui.tsx +124 -0
- package/src/movethewheels/constants/index.ts +65 -0
- package/src/movethewheels/constants/mockData.ts +1342 -0
- package/src/movethewheels/hooks/index.ts +55 -0
- package/src/movethewheels/hooks/useAnalytics.ts +476 -0
- package/src/movethewheels/hooks/useCustomers.ts +359 -0
- package/src/movethewheels/hooks/useFleet.ts +778 -0
- package/src/movethewheels/hooks/useInventory.ts +632 -0
- package/src/movethewheels/hooks/useOrders.ts +703 -0
- package/src/movethewheels/hooks/useRoutes.ts +453 -0
- package/src/movethewheels/hooks/useTracking.ts +505 -0
- package/src/movethewheels/index.ts +68 -0
- package/src/movethewheels/pages/AIAssistantPage.tsx +160 -0
- package/src/movethewheels/pages/AnalyticsPage.tsx +190 -0
- package/src/movethewheels/pages/CreateOrderPage.tsx +454 -0
- package/src/movethewheels/pages/CustomerDetailsPage.tsx +207 -0
- package/src/movethewheels/pages/CustomersPage.tsx +115 -0
- package/src/movethewheels/pages/DashboardPage.tsx +414 -0
- package/src/movethewheels/pages/DriverDetailsPage.tsx +261 -0
- package/src/movethewheels/pages/DriversPage.tsx +118 -0
- package/src/movethewheels/pages/FinancePage.tsx +141 -0
- package/src/movethewheels/pages/FleetPage.tsx +289 -0
- package/src/movethewheels/pages/ImportExportPage.tsx +165 -0
- package/src/movethewheels/pages/InventoryPage.tsx +212 -0
- package/src/movethewheels/pages/LiveTrackingPage.tsx +325 -0
- package/src/movethewheels/pages/MarketplacePage.tsx +235 -0
- package/src/movethewheels/pages/OrderDetailsPage.tsx +387 -0
- package/src/movethewheels/pages/OrdersListPage.tsx +241 -0
- package/src/movethewheels/pages/ProductDetailsPage.tsx +155 -0
- package/src/movethewheels/pages/ProductsPage.tsx +124 -0
- package/src/movethewheels/pages/ReportsPage.tsx +164 -0
- package/src/movethewheels/pages/RouteDetailsPage.tsx +245 -0
- package/src/movethewheels/pages/RoutesPage.tsx +104 -0
- package/src/movethewheels/pages/SettingsPage.tsx +242 -0
- package/src/movethewheels/pages/TrackingPage.tsx +419 -0
- package/src/movethewheels/pages/VehicleDetailsPage.tsx +218 -0
- package/src/movethewheels/pages/VehiclesPage.tsx +124 -0
- package/src/movethewheels/pages/WarehouseDetailsPage.tsx +216 -0
- package/src/movethewheels/pages/WarehousesPage.tsx +122 -0
- package/src/movethewheels/providers/MoveTheWheelsProvider.tsx +66 -0
- package/src/movethewheels/store/movethewheelsStore.ts +136 -0
- package/src/movethewheels/types/index.ts +744 -0
- package/src/movethewheels/utils/cn.ts +9 -0
- package/src/movethewheels/utils/formatters.ts +215 -0
- package/src/movethewheels/utils/graphqlClient.ts +63 -0
- package/src/movethewheels/utils/index.ts +8 -0
- package/src/movethewheels/utils/navigation.ts +70 -0
- package/src/operations/global/.gitkeep +0 -0
- package/src/operations/wspace/movethewheels/fragments/core.graphql +191 -0
- package/src/operations/wspace/movethewheels/mutations/entities.graphql +87 -0
- package/src/operations/wspace/movethewheels/mutations/logistics.graphql +86 -0
- package/src/operations/wspace/movethewheels/mutations/marketplace-reports.graphql +81 -0
- package/src/operations/wspace/movethewheels/mutations/orders.graphql +21 -0
- package/src/operations/wspace/movethewheels/queries/dashboard.graphql +61 -0
- package/src/operations/wspace/movethewheels/queries/entities.graphql +83 -0
- package/src/operations/wspace/movethewheels/queries/logistics.graphql +84 -0
- package/src/operations/wspace/movethewheels/queries/marketplace-reports.graphql +40 -0
- package/src/operations/wspace/movethewheels/queries/orders.graphql +43 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product Details Page
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useParams } from 'react-router-dom';
|
|
6
|
+
import { Package, ArrowLeft, Edit, Box, DollarSign, Tag } from 'lucide-react';
|
|
7
|
+
import { PageLayout, LoadingState, ErrorState } from '../components/PageLayout';
|
|
8
|
+
import { Card, CardContent, CardHeader, CardTitle, Button, Badge } from '../components/ui';
|
|
9
|
+
import { useProduct } from '../hooks/useInventory';
|
|
10
|
+
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
11
|
+
import { formatCurrency } from '../utils/formatters';
|
|
12
|
+
import { joinPath } from '../utils/navigation';
|
|
13
|
+
|
|
14
|
+
export default function ProductDetailsPage() {
|
|
15
|
+
const { productId } = useParams<{ productId: string }>();
|
|
16
|
+
const { basePath, navigate } = useMoveTheWheels();
|
|
17
|
+
const { product, isLoading, error, refetch } = useProduct(productId);
|
|
18
|
+
|
|
19
|
+
const goBack = () => navigate?.(joinPath(basePath, 'inventory/products'));
|
|
20
|
+
|
|
21
|
+
if (isLoading) return <LoadingState message="Loading product details..." />;
|
|
22
|
+
if (error || !product) return <ErrorState title="Product not found" onRetry={refetch} />;
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<PageLayout
|
|
26
|
+
title={product.name}
|
|
27
|
+
subtitle={`SKU: ${product.sku}`}
|
|
28
|
+
icon={<Package size={24} />}
|
|
29
|
+
headerActions={
|
|
30
|
+
<div className="flex gap-2">
|
|
31
|
+
<Button variant="outline" onClick={goBack}>
|
|
32
|
+
<ArrowLeft size={16} /> Back
|
|
33
|
+
</Button>
|
|
34
|
+
<Button>
|
|
35
|
+
<Edit size={16} /> Edit
|
|
36
|
+
</Button>
|
|
37
|
+
</div>
|
|
38
|
+
}
|
|
39
|
+
headerContent={
|
|
40
|
+
<div className="flex gap-2">
|
|
41
|
+
<Badge variant="outline">{product.category}</Badge>
|
|
42
|
+
{product.brand && <Badge variant="secondary">{product.brand}</Badge>}
|
|
43
|
+
{product.fragile && <Badge variant="destructive">Fragile</Badge>}
|
|
44
|
+
{product.hazardous && <Badge variant="destructive">Hazardous</Badge>}
|
|
45
|
+
{product.temperatureControlled && <Badge variant="secondary">Temp Controlled</Badge>}
|
|
46
|
+
</div>
|
|
47
|
+
}
|
|
48
|
+
>
|
|
49
|
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
50
|
+
<div className="lg:col-span-2 space-y-6">
|
|
51
|
+
<Card>
|
|
52
|
+
<CardHeader>
|
|
53
|
+
<CardTitle>Description</CardTitle>
|
|
54
|
+
</CardHeader>
|
|
55
|
+
<CardContent>
|
|
56
|
+
<p>{product.description}</p>
|
|
57
|
+
</CardContent>
|
|
58
|
+
</Card>
|
|
59
|
+
|
|
60
|
+
<Card>
|
|
61
|
+
<CardHeader>
|
|
62
|
+
<CardTitle className="flex items-center gap-2">
|
|
63
|
+
<Box size={18} /> Dimensions & Weight
|
|
64
|
+
</CardTitle>
|
|
65
|
+
</CardHeader>
|
|
66
|
+
<CardContent>
|
|
67
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
68
|
+
<div>
|
|
69
|
+
<p className="text-sm text-muted-foreground">Length</p>
|
|
70
|
+
<p className="text-2xl font-bold">
|
|
71
|
+
{product.dimensions.length}
|
|
72
|
+
<span className="text-sm font-normal"> {product.dimensions.unit}</span>
|
|
73
|
+
</p>
|
|
74
|
+
</div>
|
|
75
|
+
<div>
|
|
76
|
+
<p className="text-sm text-muted-foreground">Width</p>
|
|
77
|
+
<p className="text-2xl font-bold">
|
|
78
|
+
{product.dimensions.width}
|
|
79
|
+
<span className="text-sm font-normal"> {product.dimensions.unit}</span>
|
|
80
|
+
</p>
|
|
81
|
+
</div>
|
|
82
|
+
<div>
|
|
83
|
+
<p className="text-sm text-muted-foreground">Height</p>
|
|
84
|
+
<p className="text-2xl font-bold">
|
|
85
|
+
{product.dimensions.height}
|
|
86
|
+
<span className="text-sm font-normal"> {product.dimensions.unit}</span>
|
|
87
|
+
</p>
|
|
88
|
+
</div>
|
|
89
|
+
<div>
|
|
90
|
+
<p className="text-sm text-muted-foreground">Weight</p>
|
|
91
|
+
<p className="text-2xl font-bold">
|
|
92
|
+
{product.weight}
|
|
93
|
+
<span className="text-sm font-normal"> kg</span>
|
|
94
|
+
</p>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</CardContent>
|
|
98
|
+
</Card>
|
|
99
|
+
|
|
100
|
+
{product.attributes.length > 0 && (
|
|
101
|
+
<Card>
|
|
102
|
+
<CardHeader>
|
|
103
|
+
<CardTitle className="flex items-center gap-2">
|
|
104
|
+
<Tag size={18} /> Attributes
|
|
105
|
+
</CardTitle>
|
|
106
|
+
</CardHeader>
|
|
107
|
+
<CardContent>
|
|
108
|
+
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
|
109
|
+
{product.attributes.map((attr, i) => (
|
|
110
|
+
<div key={i}>
|
|
111
|
+
<p className="text-sm text-muted-foreground">{attr.name}</p>
|
|
112
|
+
<p className="font-medium">{attr.value}</p>
|
|
113
|
+
</div>
|
|
114
|
+
))}
|
|
115
|
+
</div>
|
|
116
|
+
</CardContent>
|
|
117
|
+
</Card>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<div className="space-y-6">
|
|
122
|
+
<Card>
|
|
123
|
+
<CardHeader>
|
|
124
|
+
<CardTitle className="flex items-center gap-2">
|
|
125
|
+
<DollarSign size={18} /> Pricing
|
|
126
|
+
</CardTitle>
|
|
127
|
+
</CardHeader>
|
|
128
|
+
<CardContent>
|
|
129
|
+
<p className="text-3xl font-bold">{formatCurrency(product.value)}</p>
|
|
130
|
+
<p className="text-sm text-muted-foreground">Unit price</p>
|
|
131
|
+
</CardContent>
|
|
132
|
+
</Card>
|
|
133
|
+
|
|
134
|
+
<Card>
|
|
135
|
+
<CardHeader>
|
|
136
|
+
<CardTitle>Identifiers</CardTitle>
|
|
137
|
+
</CardHeader>
|
|
138
|
+
<CardContent className="space-y-3">
|
|
139
|
+
<div>
|
|
140
|
+
<p className="text-sm text-muted-foreground">SKU</p>
|
|
141
|
+
<p className="font-mono">{product.sku}</p>
|
|
142
|
+
</div>
|
|
143
|
+
{product.barcode && (
|
|
144
|
+
<div>
|
|
145
|
+
<p className="text-sm text-muted-foreground">Barcode</p>
|
|
146
|
+
<p className="font-mono">{product.barcode}</p>
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
</CardContent>
|
|
150
|
+
</Card>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</PageLayout>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Products Page - List of all products
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Package, Plus, RefreshCw, Download } from 'lucide-react';
|
|
6
|
+
import { PageLayout } from '../components/PageLayout';
|
|
7
|
+
import { DataTable, type Column } from '../components/DataTable';
|
|
8
|
+
import { Button, Badge } from '../components/ui';
|
|
9
|
+
import { useProducts } from '../hooks/useInventory';
|
|
10
|
+
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
11
|
+
import { formatCurrency } from '../utils/formatters';
|
|
12
|
+
import { joinPath } from '../utils/navigation';
|
|
13
|
+
import type { Product } from '../types';
|
|
14
|
+
|
|
15
|
+
export default function ProductsPage() {
|
|
16
|
+
const { basePath, navigate } = useMoveTheWheels();
|
|
17
|
+
const { products, totalProducts, isLoading, refetch } = useProducts();
|
|
18
|
+
|
|
19
|
+
const goTo = (path: string) => navigate?.(joinPath(basePath, path));
|
|
20
|
+
|
|
21
|
+
const columns: Column<Product>[] = [
|
|
22
|
+
{
|
|
23
|
+
id: 'sku',
|
|
24
|
+
header: 'SKU',
|
|
25
|
+
accessorKey: 'sku',
|
|
26
|
+
sortable: true,
|
|
27
|
+
cell: (row) => <span className="font-mono">{row.sku}</span>,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'name',
|
|
31
|
+
header: 'Product Name',
|
|
32
|
+
accessorKey: 'name',
|
|
33
|
+
sortable: true,
|
|
34
|
+
cell: (row) => (
|
|
35
|
+
<div>
|
|
36
|
+
<p className="font-medium">{row.name}</p>
|
|
37
|
+
<p className="text-sm text-muted-foreground line-clamp-1">{row.description}</p>
|
|
38
|
+
</div>
|
|
39
|
+
),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'category',
|
|
43
|
+
header: 'Category',
|
|
44
|
+
accessorKey: 'category',
|
|
45
|
+
sortable: true,
|
|
46
|
+
cell: (row) => <Badge variant="outline">{row.category}</Badge>,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'dimensions',
|
|
50
|
+
header: 'Dimensions',
|
|
51
|
+
cell: (row) =>
|
|
52
|
+
`${row.dimensions.length}x${row.dimensions.width}x${row.dimensions.height} ${row.dimensions.unit}`,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'weight',
|
|
56
|
+
header: 'Weight',
|
|
57
|
+
accessorKey: 'weight',
|
|
58
|
+
sortable: true,
|
|
59
|
+
cell: (row) => `${row.weight} kg`,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'value',
|
|
63
|
+
header: 'Value',
|
|
64
|
+
accessorKey: 'value',
|
|
65
|
+
sortable: true,
|
|
66
|
+
cell: (row) => <span className="font-medium">{formatCurrency(row.value)}</span>,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'flags',
|
|
70
|
+
header: 'Flags',
|
|
71
|
+
cell: (row) => (
|
|
72
|
+
<div className="flex gap-1">
|
|
73
|
+
{row.fragile && (
|
|
74
|
+
<Badge variant="destructive" className="text-xs">
|
|
75
|
+
Fragile
|
|
76
|
+
</Badge>
|
|
77
|
+
)}
|
|
78
|
+
{row.hazardous && (
|
|
79
|
+
<Badge variant="destructive" className="text-xs">
|
|
80
|
+
Hazardous
|
|
81
|
+
</Badge>
|
|
82
|
+
)}
|
|
83
|
+
{row.temperatureControlled && (
|
|
84
|
+
<Badge variant="secondary" className="text-xs">
|
|
85
|
+
Temp Control
|
|
86
|
+
</Badge>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
),
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<PageLayout
|
|
95
|
+
title="Products"
|
|
96
|
+
subtitle={`${totalProducts} products`}
|
|
97
|
+
icon={<Package size={24} />}
|
|
98
|
+
headerActions={
|
|
99
|
+
<div className="flex gap-2">
|
|
100
|
+
<Button variant="outline" onClick={refetch}>
|
|
101
|
+
<RefreshCw size={16} /> Refresh
|
|
102
|
+
</Button>
|
|
103
|
+
<Button variant="outline">
|
|
104
|
+
<Download size={16} /> Export
|
|
105
|
+
</Button>
|
|
106
|
+
<Button>
|
|
107
|
+
<Plus size={16} /> Add Product
|
|
108
|
+
</Button>
|
|
109
|
+
</div>
|
|
110
|
+
}
|
|
111
|
+
>
|
|
112
|
+
<DataTable
|
|
113
|
+
columns={columns}
|
|
114
|
+
data={products}
|
|
115
|
+
isLoading={isLoading}
|
|
116
|
+
searchable
|
|
117
|
+
searchPlaceholder="Search products..."
|
|
118
|
+
searchFields={['name', 'sku', 'category'] as (keyof Product)[]}
|
|
119
|
+
onRowClick={(row) => goTo(`inventory/products/${row.id}`)}
|
|
120
|
+
getRowId={(row) => row.id}
|
|
121
|
+
/>
|
|
122
|
+
</PageLayout>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reports Page
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { FileBarChart, Download, Calendar, Filter, Clock } from 'lucide-react';
|
|
6
|
+
import { PageLayout } from '../components/PageLayout';
|
|
7
|
+
import {
|
|
8
|
+
Card,
|
|
9
|
+
CardContent,
|
|
10
|
+
CardHeader,
|
|
11
|
+
CardTitle,
|
|
12
|
+
Button,
|
|
13
|
+
Badge,
|
|
14
|
+
Select,
|
|
15
|
+
SelectContent,
|
|
16
|
+
SelectItem,
|
|
17
|
+
SelectTrigger,
|
|
18
|
+
SelectValue,
|
|
19
|
+
} from '../components/ui';
|
|
20
|
+
|
|
21
|
+
export default function ReportsPage() {
|
|
22
|
+
const reports = [
|
|
23
|
+
{
|
|
24
|
+
name: 'Daily Operations Summary',
|
|
25
|
+
description: 'Overview of daily deliveries and performance',
|
|
26
|
+
frequency: 'Daily',
|
|
27
|
+
lastGenerated: '2024-01-15',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'Weekly Performance Report',
|
|
31
|
+
description: 'KPIs and metrics for the week',
|
|
32
|
+
frequency: 'Weekly',
|
|
33
|
+
lastGenerated: '2024-01-14',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'Monthly Financial Report',
|
|
37
|
+
description: 'Revenue, expenses, and profit analysis',
|
|
38
|
+
frequency: 'Monthly',
|
|
39
|
+
lastGenerated: '2024-01-01',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'Fleet Utilization Report',
|
|
43
|
+
description: 'Vehicle and driver utilization metrics',
|
|
44
|
+
frequency: 'Weekly',
|
|
45
|
+
lastGenerated: '2024-01-14',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'Customer Analytics Report',
|
|
49
|
+
description: 'Customer satisfaction and retention',
|
|
50
|
+
frequency: 'Monthly',
|
|
51
|
+
lastGenerated: '2024-01-01',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'Inventory Status Report',
|
|
55
|
+
description: 'Stock levels and movement analysis',
|
|
56
|
+
frequency: 'Daily',
|
|
57
|
+
lastGenerated: '2024-01-15',
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const scheduledReports = [
|
|
62
|
+
{ name: 'Daily Summary', nextRun: 'Tomorrow 6:00 AM', recipients: 3 },
|
|
63
|
+
{ name: 'Weekly Review', nextRun: 'Monday 9:00 AM', recipients: 5 },
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<PageLayout
|
|
68
|
+
title="Reports"
|
|
69
|
+
subtitle="Generate and schedule reports"
|
|
70
|
+
icon={<FileBarChart size={24} />}
|
|
71
|
+
headerActions={
|
|
72
|
+
<div className="flex gap-2">
|
|
73
|
+
<Select defaultValue="30d">
|
|
74
|
+
<SelectTrigger className="w-[150px]">
|
|
75
|
+
<SelectValue />
|
|
76
|
+
</SelectTrigger>
|
|
77
|
+
<SelectContent>
|
|
78
|
+
<SelectItem value="7d">Last 7 days</SelectItem>
|
|
79
|
+
<SelectItem value="30d">Last 30 days</SelectItem>
|
|
80
|
+
<SelectItem value="90d">Last 90 days</SelectItem>
|
|
81
|
+
<SelectItem value="custom">Custom</SelectItem>
|
|
82
|
+
</SelectContent>
|
|
83
|
+
</Select>
|
|
84
|
+
<Button>
|
|
85
|
+
<FileBarChart size={16} /> Create Report
|
|
86
|
+
</Button>
|
|
87
|
+
</div>
|
|
88
|
+
}
|
|
89
|
+
>
|
|
90
|
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
91
|
+
<div className="lg:col-span-2 space-y-4">
|
|
92
|
+
<h3 className="font-semibold">Available Reports</h3>
|
|
93
|
+
{reports.map((report) => (
|
|
94
|
+
<Card key={report.name}>
|
|
95
|
+
<CardContent className="p-4">
|
|
96
|
+
<div className="flex items-center justify-between">
|
|
97
|
+
<div className="flex items-center gap-3">
|
|
98
|
+
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center">
|
|
99
|
+
<FileBarChart size={20} className="text-primary" />
|
|
100
|
+
</div>
|
|
101
|
+
<div>
|
|
102
|
+
<p className="font-medium">{report.name}</p>
|
|
103
|
+
<p className="text-sm text-muted-foreground">{report.description}</p>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
<div className="flex items-center gap-3">
|
|
107
|
+
<div className="text-right">
|
|
108
|
+
<Badge variant="outline">{report.frequency}</Badge>
|
|
109
|
+
<p className="text-xs text-muted-foreground mt-1">
|
|
110
|
+
Last: {report.lastGenerated}
|
|
111
|
+
</p>
|
|
112
|
+
</div>
|
|
113
|
+
<Button variant="outline" size="sm">
|
|
114
|
+
<Download size={14} />
|
|
115
|
+
</Button>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</CardContent>
|
|
119
|
+
</Card>
|
|
120
|
+
))}
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div className="space-y-6">
|
|
124
|
+
<Card>
|
|
125
|
+
<CardHeader>
|
|
126
|
+
<CardTitle className="flex items-center gap-2">
|
|
127
|
+
<Clock size={18} /> Scheduled Reports
|
|
128
|
+
</CardTitle>
|
|
129
|
+
</CardHeader>
|
|
130
|
+
<CardContent className="space-y-3">
|
|
131
|
+
{scheduledReports.map((report) => (
|
|
132
|
+
<div key={report.name} className="p-3 border rounded-lg">
|
|
133
|
+
<p className="font-medium">{report.name}</p>
|
|
134
|
+
<p className="text-sm text-muted-foreground">Next: {report.nextRun}</p>
|
|
135
|
+
<p className="text-xs text-muted-foreground">{report.recipients} recipients</p>
|
|
136
|
+
</div>
|
|
137
|
+
))}
|
|
138
|
+
<Button variant="outline" className="w-full">
|
|
139
|
+
Schedule New Report
|
|
140
|
+
</Button>
|
|
141
|
+
</CardContent>
|
|
142
|
+
</Card>
|
|
143
|
+
|
|
144
|
+
<Card>
|
|
145
|
+
<CardHeader>
|
|
146
|
+
<CardTitle>Quick Actions</CardTitle>
|
|
147
|
+
</CardHeader>
|
|
148
|
+
<CardContent className="space-y-2">
|
|
149
|
+
<Button variant="outline" className="w-full justify-start">
|
|
150
|
+
<Calendar size={16} /> Schedule Report
|
|
151
|
+
</Button>
|
|
152
|
+
<Button variant="outline" className="w-full justify-start">
|
|
153
|
+
<Filter size={16} /> Custom Report
|
|
154
|
+
</Button>
|
|
155
|
+
<Button variant="outline" className="w-full justify-start">
|
|
156
|
+
<Download size={16} /> Export All
|
|
157
|
+
</Button>
|
|
158
|
+
</CardContent>
|
|
159
|
+
</Card>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
</PageLayout>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route Details Page
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useParams } from 'react-router-dom';
|
|
6
|
+
import { Navigation, ArrowLeft, Edit, MapPin, Clock, Zap, CheckCircle } from 'lucide-react';
|
|
7
|
+
import { PageLayout, LoadingState, ErrorState } from '../components/PageLayout';
|
|
8
|
+
import { StatusBadge } from '../components/StatusBadge';
|
|
9
|
+
import { Card, CardContent, CardHeader, CardTitle, Button, Badge } from '../components/ui';
|
|
10
|
+
import { useRoute, useRoutes } from '../hooks/useRoutes';
|
|
11
|
+
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
12
|
+
import { formatDate, formatAddress } from '../utils/formatters';
|
|
13
|
+
import { joinPath } from '../utils/navigation';
|
|
14
|
+
|
|
15
|
+
export default function RouteDetailsPage() {
|
|
16
|
+
const { routeId } = useParams<{ routeId: string }>();
|
|
17
|
+
const { basePath, navigate } = useMoveTheWheels();
|
|
18
|
+
const { route, isLoading, error, refetch } = useRoute(routeId);
|
|
19
|
+
const { optimizeRoute } = useRoutes();
|
|
20
|
+
|
|
21
|
+
const goBack = () => navigate?.(joinPath(basePath, 'routes'));
|
|
22
|
+
|
|
23
|
+
// Handle new route form
|
|
24
|
+
if (!routeId) {
|
|
25
|
+
return (
|
|
26
|
+
<PageLayout
|
|
27
|
+
title="Create Route"
|
|
28
|
+
subtitle="Plan a new delivery route"
|
|
29
|
+
icon={<Navigation size={24} />}
|
|
30
|
+
headerActions={
|
|
31
|
+
<Button variant="outline" onClick={goBack}>
|
|
32
|
+
<ArrowLeft size={16} /> Back
|
|
33
|
+
</Button>
|
|
34
|
+
}
|
|
35
|
+
>
|
|
36
|
+
<Card>
|
|
37
|
+
<CardContent className="p-8 text-center">
|
|
38
|
+
<p className="text-muted-foreground">Route creation form would go here</p>
|
|
39
|
+
</CardContent>
|
|
40
|
+
</Card>
|
|
41
|
+
</PageLayout>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (isLoading) return <LoadingState message="Loading route details..." />;
|
|
46
|
+
if (error || !route) return <ErrorState title="Route not found" onRetry={refetch} />;
|
|
47
|
+
|
|
48
|
+
const handleOptimize = async () => {
|
|
49
|
+
try {
|
|
50
|
+
await optimizeRoute(route.id);
|
|
51
|
+
refetch();
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.error(e);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const statusColors = {
|
|
58
|
+
planned: { bg: 'bg-blue-100', text: 'text-blue-700' },
|
|
59
|
+
in_progress: { bg: 'bg-yellow-100', text: 'text-yellow-700' },
|
|
60
|
+
completed: { bg: 'bg-green-100', text: 'text-green-700' },
|
|
61
|
+
cancelled: { bg: 'bg-red-100', text: 'text-red-700' },
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<PageLayout
|
|
66
|
+
title={route.name}
|
|
67
|
+
subtitle={`${route.stops.length} stops - ${route.distance.toFixed(1)} km`}
|
|
68
|
+
icon={<Navigation size={24} />}
|
|
69
|
+
headerActions={
|
|
70
|
+
<div className="flex gap-2">
|
|
71
|
+
<Button variant="outline" onClick={goBack}>
|
|
72
|
+
<ArrowLeft size={16} /> Back
|
|
73
|
+
</Button>
|
|
74
|
+
{!route.optimized && (
|
|
75
|
+
<Button variant="outline" onClick={handleOptimize}>
|
|
76
|
+
<Zap size={16} /> Optimize
|
|
77
|
+
</Button>
|
|
78
|
+
)}
|
|
79
|
+
<Button>
|
|
80
|
+
<Edit size={16} /> Edit
|
|
81
|
+
</Button>
|
|
82
|
+
</div>
|
|
83
|
+
}
|
|
84
|
+
headerContent={
|
|
85
|
+
<div className="flex gap-2">
|
|
86
|
+
<StatusBadge status={route.status} customColors={statusColors[route.status]} />
|
|
87
|
+
<Badge variant={route.optimized ? 'default' : 'secondary'}>
|
|
88
|
+
{route.optimized ? 'Optimized' : 'Not Optimized'}
|
|
89
|
+
</Badge>
|
|
90
|
+
</div>
|
|
91
|
+
}
|
|
92
|
+
>
|
|
93
|
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
94
|
+
<div className="lg:col-span-2 space-y-6">
|
|
95
|
+
<Card>
|
|
96
|
+
<CardHeader>
|
|
97
|
+
<CardTitle>Route Map</CardTitle>
|
|
98
|
+
</CardHeader>
|
|
99
|
+
<CardContent>
|
|
100
|
+
<div className="aspect-video bg-muted rounded-lg flex items-center justify-center">
|
|
101
|
+
<div className="text-center text-muted-foreground">
|
|
102
|
+
<MapPin size={48} className="mx-auto mb-2 opacity-50" />
|
|
103
|
+
<p>Route map visualization</p>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</CardContent>
|
|
107
|
+
</Card>
|
|
108
|
+
|
|
109
|
+
<Card>
|
|
110
|
+
<CardHeader>
|
|
111
|
+
<CardTitle>Stops ({route.stops.length})</CardTitle>
|
|
112
|
+
</CardHeader>
|
|
113
|
+
<CardContent>
|
|
114
|
+
<div className="space-y-4">
|
|
115
|
+
{route.stops.map((stop, index) => (
|
|
116
|
+
<div key={stop.id} className="flex gap-4">
|
|
117
|
+
<div className="relative">
|
|
118
|
+
<div
|
|
119
|
+
className={`w-8 h-8 rounded-full flex items-center justify-center ${stop.status === 'completed' ? 'bg-green-500 text-white' : 'bg-muted'}`}
|
|
120
|
+
>
|
|
121
|
+
{stop.status === 'completed' ? <CheckCircle size={16} /> : stop.sequence}
|
|
122
|
+
</div>
|
|
123
|
+
{index < route.stops.length - 1 && (
|
|
124
|
+
<div className="absolute top-8 left-4 w-px h-12 bg-border" />
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
<div className="flex-1 pb-4">
|
|
128
|
+
<div className="flex items-center justify-between">
|
|
129
|
+
<div>
|
|
130
|
+
<div className="flex items-center gap-2">
|
|
131
|
+
<Badge
|
|
132
|
+
variant={stop.type === 'pickup' ? 'secondary' : 'default'}
|
|
133
|
+
className="capitalize"
|
|
134
|
+
>
|
|
135
|
+
{stop.type}
|
|
136
|
+
</Badge>
|
|
137
|
+
<span className="font-medium">Order #{stop.orderId}</span>
|
|
138
|
+
</div>
|
|
139
|
+
<p className="text-sm text-muted-foreground mt-1">
|
|
140
|
+
{formatAddress(stop.address)}
|
|
141
|
+
</p>
|
|
142
|
+
</div>
|
|
143
|
+
<div className="text-right">
|
|
144
|
+
<p className="text-sm">
|
|
145
|
+
Scheduled: {formatDate(stop.scheduledTime, 'time')}
|
|
146
|
+
</p>
|
|
147
|
+
{stop.actualTime && (
|
|
148
|
+
<p className="text-sm text-green-600">
|
|
149
|
+
Actual: {formatDate(stop.actualTime, 'time')}
|
|
150
|
+
</p>
|
|
151
|
+
)}
|
|
152
|
+
<p className="text-xs text-muted-foreground">{stop.duration} min stop</p>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
))}
|
|
158
|
+
</div>
|
|
159
|
+
</CardContent>
|
|
160
|
+
</Card>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<div className="space-y-6">
|
|
164
|
+
<Card>
|
|
165
|
+
<CardHeader>
|
|
166
|
+
<CardTitle>Route Info</CardTitle>
|
|
167
|
+
</CardHeader>
|
|
168
|
+
<CardContent className="space-y-4">
|
|
169
|
+
<div>
|
|
170
|
+
<p className="text-sm text-muted-foreground">Total Distance</p>
|
|
171
|
+
<p className="text-2xl font-bold">
|
|
172
|
+
{route.distance.toFixed(1)}
|
|
173
|
+
<span className="text-sm font-normal"> km</span>
|
|
174
|
+
</p>
|
|
175
|
+
</div>
|
|
176
|
+
<div>
|
|
177
|
+
<p className="text-sm text-muted-foreground">Est. Duration</p>
|
|
178
|
+
<p className="text-2xl font-bold">
|
|
179
|
+
{route.estimatedDuration}
|
|
180
|
+
<span className="text-sm font-normal"> min</span>
|
|
181
|
+
</p>
|
|
182
|
+
</div>
|
|
183
|
+
{route.actualDuration && (
|
|
184
|
+
<div>
|
|
185
|
+
<p className="text-sm text-muted-foreground">Actual Duration</p>
|
|
186
|
+
<p className="text-2xl font-bold">
|
|
187
|
+
{route.actualDuration}
|
|
188
|
+
<span className="text-sm font-normal"> min</span>
|
|
189
|
+
</p>
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
192
|
+
</CardContent>
|
|
193
|
+
</Card>
|
|
194
|
+
|
|
195
|
+
<Card>
|
|
196
|
+
<CardHeader>
|
|
197
|
+
<CardTitle className="flex items-center gap-2">
|
|
198
|
+
<Clock size={18} /> Timeline
|
|
199
|
+
</CardTitle>
|
|
200
|
+
</CardHeader>
|
|
201
|
+
<CardContent className="space-y-3">
|
|
202
|
+
{route.startTime && (
|
|
203
|
+
<div>
|
|
204
|
+
<p className="text-sm text-muted-foreground">Started</p>
|
|
205
|
+
<p>{formatDate(route.startTime, 'full')}</p>
|
|
206
|
+
</div>
|
|
207
|
+
)}
|
|
208
|
+
{route.endTime && (
|
|
209
|
+
<div>
|
|
210
|
+
<p className="text-sm text-muted-foreground">Completed</p>
|
|
211
|
+
<p>{formatDate(route.endTime, 'full')}</p>
|
|
212
|
+
</div>
|
|
213
|
+
)}
|
|
214
|
+
<div>
|
|
215
|
+
<p className="text-sm text-muted-foreground">Created</p>
|
|
216
|
+
<p>{formatDate(route.createdAt, 'full')}</p>
|
|
217
|
+
</div>
|
|
218
|
+
</CardContent>
|
|
219
|
+
</Card>
|
|
220
|
+
|
|
221
|
+
{route.trafficConditions.length > 0 && (
|
|
222
|
+
<Card>
|
|
223
|
+
<CardHeader>
|
|
224
|
+
<CardTitle>Traffic Conditions</CardTitle>
|
|
225
|
+
</CardHeader>
|
|
226
|
+
<CardContent>
|
|
227
|
+
{route.trafficConditions.map((tc, i) => (
|
|
228
|
+
<div
|
|
229
|
+
key={i}
|
|
230
|
+
className={`p-2 rounded mb-2 ${tc.condition === 'severe' ? 'bg-red-50' : tc.condition === 'heavy' ? 'bg-orange-50' : 'bg-yellow-50'}`}
|
|
231
|
+
>
|
|
232
|
+
<p className="text-sm font-medium capitalize">{tc.condition} traffic</p>
|
|
233
|
+
<p className="text-xs text-muted-foreground">
|
|
234
|
+
{tc.location} - +{tc.delay} min delay
|
|
235
|
+
</p>
|
|
236
|
+
</div>
|
|
237
|
+
))}
|
|
238
|
+
</CardContent>
|
|
239
|
+
</Card>
|
|
240
|
+
)}
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
</PageLayout>
|
|
244
|
+
);
|
|
245
|
+
}
|