@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,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fleet Page
|
|
3
|
+
*
|
|
4
|
+
* Overview of vehicles and drivers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Truck, Users, Wrench, Fuel, Star, ChevronRight } from 'lucide-react';
|
|
8
|
+
import { PageLayout, PageSection } from '../components/PageLayout';
|
|
9
|
+
import { MetricCard, MetricGrid } from '../components/MetricCard';
|
|
10
|
+
import { StatusBadge, VehicleTypeBadge } from '../components/StatusBadge';
|
|
11
|
+
import {
|
|
12
|
+
Card,
|
|
13
|
+
CardContent,
|
|
14
|
+
CardHeader,
|
|
15
|
+
CardTitle,
|
|
16
|
+
Button,
|
|
17
|
+
Progress,
|
|
18
|
+
Avatar,
|
|
19
|
+
AvatarFallback,
|
|
20
|
+
} from '../components/ui';
|
|
21
|
+
import { useFleetStats, useVehicles, useDrivers } from '../hooks/useFleet';
|
|
22
|
+
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
23
|
+
import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';
|
|
24
|
+
import { joinPath } from '../utils/navigation';
|
|
25
|
+
|
|
26
|
+
export default function FleetPage() {
|
|
27
|
+
const { t: translate } = useI18n();
|
|
28
|
+
const t = (key: string, fallback: string): string => {
|
|
29
|
+
const value = translate(key);
|
|
30
|
+
return value === key ? fallback : value;
|
|
31
|
+
};
|
|
32
|
+
const { basePath, navigate } = useMoveTheWheels();
|
|
33
|
+
const { stats } = useFleetStats();
|
|
34
|
+
const { vehicles } = useVehicles();
|
|
35
|
+
const { drivers } = useDrivers();
|
|
36
|
+
|
|
37
|
+
const goTo = (path: string) => {
|
|
38
|
+
if (navigate) {
|
|
39
|
+
navigate(joinPath(basePath, path));
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<PageLayout
|
|
45
|
+
title={t('movethewheels.fleet.title', 'Fleet Management')}
|
|
46
|
+
subtitle={t('movethewheels.fleet.subtitle', 'Manage your vehicles and drivers')}
|
|
47
|
+
icon={<Truck size={24} />}
|
|
48
|
+
headerActions={
|
|
49
|
+
<div className="flex gap-2">
|
|
50
|
+
<Button variant="outline" onClick={() => goTo('fleet/vehicles')}>
|
|
51
|
+
<Truck size={16} />
|
|
52
|
+
{t('movethewheels.fleet.actions.allVehicles', 'All Vehicles')}
|
|
53
|
+
</Button>
|
|
54
|
+
<Button variant="outline" onClick={() => goTo('fleet/drivers')}>
|
|
55
|
+
<Users size={16} />
|
|
56
|
+
{t('movethewheels.fleet.actions.allDrivers', 'All Drivers')}
|
|
57
|
+
</Button>
|
|
58
|
+
</div>
|
|
59
|
+
}
|
|
60
|
+
>
|
|
61
|
+
{/* Key Metrics */}
|
|
62
|
+
<MetricGrid columns={4}>
|
|
63
|
+
<MetricCard
|
|
64
|
+
label={t('movethewheels.fleet.metrics.totalVehicles', 'Total Vehicles')}
|
|
65
|
+
value={stats.totalVehicles}
|
|
66
|
+
icon={<Truck size={20} />}
|
|
67
|
+
subtitle={`${stats.vehiclesByStatus.available} ${t('movethewheels.fleet.metrics.available', 'available')}`}
|
|
68
|
+
onClick={() => goTo('fleet/vehicles')}
|
|
69
|
+
/>
|
|
70
|
+
<MetricCard
|
|
71
|
+
label={t('movethewheels.fleet.metrics.totalDrivers', 'Total Drivers')}
|
|
72
|
+
value={stats.totalDrivers}
|
|
73
|
+
icon={<Users size={20} />}
|
|
74
|
+
subtitle={`${stats.driversByStatus.available} ${t('movethewheels.fleet.metrics.available', 'available')}`}
|
|
75
|
+
onClick={() => goTo('fleet/drivers')}
|
|
76
|
+
/>
|
|
77
|
+
<MetricCard
|
|
78
|
+
label={t('movethewheels.fleet.metrics.utilization', 'Fleet Utilization')}
|
|
79
|
+
value={`${stats.utilization.toFixed(0)}%`}
|
|
80
|
+
icon={<Truck size={20} />}
|
|
81
|
+
changeType={stats.utilization >= 70 ? 'positive' : 'neutral'}
|
|
82
|
+
/>
|
|
83
|
+
<MetricCard
|
|
84
|
+
label={t('movethewheels.fleet.metrics.avgDriverRating', 'Avg Driver Rating')}
|
|
85
|
+
value={stats.avgDriverRating.toFixed(1)}
|
|
86
|
+
icon={<Star size={20} />}
|
|
87
|
+
subtitle={t('movethewheels.fleet.metrics.outOfFive', 'out of 5')}
|
|
88
|
+
changeType={stats.avgDriverRating >= 4.5 ? 'positive' : 'neutral'}
|
|
89
|
+
/>
|
|
90
|
+
</MetricGrid>
|
|
91
|
+
|
|
92
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
93
|
+
{/* Vehicles Overview */}
|
|
94
|
+
<PageSection
|
|
95
|
+
title={t('movethewheels.fleet.vehicles.title', 'Vehicles')}
|
|
96
|
+
actions={
|
|
97
|
+
<Button variant="ghost" size="sm" onClick={() => goTo('fleet/vehicles')}>
|
|
98
|
+
{t('movethewheels.fleet.common.viewAll', 'View all')} <ChevronRight size={16} />
|
|
99
|
+
</Button>
|
|
100
|
+
}
|
|
101
|
+
>
|
|
102
|
+
<Card>
|
|
103
|
+
<CardContent className="p-0">
|
|
104
|
+
<div className="divide-y">
|
|
105
|
+
{vehicles.slice(0, 5).map((vehicle) => (
|
|
106
|
+
<div
|
|
107
|
+
key={vehicle.id}
|
|
108
|
+
className="p-4 hover:bg-muted/50 cursor-pointer transition-colors"
|
|
109
|
+
onClick={() => goTo(`fleet/vehicles/${vehicle.id}`)}
|
|
110
|
+
>
|
|
111
|
+
<div className="flex items-center justify-between">
|
|
112
|
+
<div className="flex items-center gap-3">
|
|
113
|
+
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center">
|
|
114
|
+
<Truck size={20} className="text-primary" />
|
|
115
|
+
</div>
|
|
116
|
+
<div>
|
|
117
|
+
<div className="flex items-center gap-2">
|
|
118
|
+
<span className="font-medium">{vehicle.vehicleNumber}</span>
|
|
119
|
+
<VehicleTypeBadge type={vehicle.type} size="sm" />
|
|
120
|
+
</div>
|
|
121
|
+
<p className="text-sm text-muted-foreground">
|
|
122
|
+
{vehicle.make} {vehicle.model} ({vehicle.year})
|
|
123
|
+
</p>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
<div className="text-right">
|
|
127
|
+
<StatusBadge status={vehicle.status} type="vehicle" size="sm" />
|
|
128
|
+
{vehicle.fuelLevel !== undefined && (
|
|
129
|
+
<div className="mt-2 flex items-center gap-2">
|
|
130
|
+
<Fuel size={12} className="text-muted-foreground" />
|
|
131
|
+
<Progress value={vehicle.fuelLevel} className="w-16 h-2" />
|
|
132
|
+
<span className="text-xs text-muted-foreground">
|
|
133
|
+
{vehicle.fuelLevel}%
|
|
134
|
+
</span>
|
|
135
|
+
</div>
|
|
136
|
+
)}
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
))}
|
|
141
|
+
</div>
|
|
142
|
+
</CardContent>
|
|
143
|
+
</Card>
|
|
144
|
+
</PageSection>
|
|
145
|
+
|
|
146
|
+
{/* Drivers Overview */}
|
|
147
|
+
<PageSection
|
|
148
|
+
title={t('movethewheels.fleet.drivers.title', 'Drivers')}
|
|
149
|
+
actions={
|
|
150
|
+
<Button variant="ghost" size="sm" onClick={() => goTo('fleet/drivers')}>
|
|
151
|
+
{t('movethewheels.fleet.common.viewAll', 'View all')} <ChevronRight size={16} />
|
|
152
|
+
</Button>
|
|
153
|
+
}
|
|
154
|
+
>
|
|
155
|
+
<Card>
|
|
156
|
+
<CardContent className="p-0">
|
|
157
|
+
<div className="divide-y">
|
|
158
|
+
{drivers.slice(0, 5).map((driver) => (
|
|
159
|
+
<div
|
|
160
|
+
key={driver.id}
|
|
161
|
+
className="p-4 hover:bg-muted/50 cursor-pointer transition-colors"
|
|
162
|
+
onClick={() => goTo(`fleet/drivers/${driver.id}`)}
|
|
163
|
+
>
|
|
164
|
+
<div className="flex items-center justify-between">
|
|
165
|
+
<div className="flex items-center gap-3">
|
|
166
|
+
<Avatar>
|
|
167
|
+
<AvatarFallback>
|
|
168
|
+
{driver.fullName
|
|
169
|
+
.split(' ')
|
|
170
|
+
.map((n) => n[0])
|
|
171
|
+
.join('')}
|
|
172
|
+
</AvatarFallback>
|
|
173
|
+
</Avatar>
|
|
174
|
+
<div>
|
|
175
|
+
<p className="font-medium">{driver.fullName}</p>
|
|
176
|
+
<p className="text-sm text-muted-foreground">
|
|
177
|
+
{driver.employeeId} - {driver.completedOrders}{' '}
|
|
178
|
+
{t('movethewheels.fleet.drivers.deliveries', 'deliveries')}
|
|
179
|
+
</p>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
<div className="text-right">
|
|
183
|
+
<StatusBadge status={driver.status} type="driver" size="sm" />
|
|
184
|
+
<div className="mt-1 flex items-center gap-1 justify-end">
|
|
185
|
+
<Star size={12} className="text-yellow-500 fill-yellow-500" />
|
|
186
|
+
<span className="text-sm font-medium">
|
|
187
|
+
{driver.ratings.overall.toFixed(1)}
|
|
188
|
+
</span>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
))}
|
|
194
|
+
</div>
|
|
195
|
+
</CardContent>
|
|
196
|
+
</Card>
|
|
197
|
+
</PageSection>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
{/* Status Summary */}
|
|
201
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
202
|
+
{/* Vehicle Status */}
|
|
203
|
+
<Card>
|
|
204
|
+
<CardHeader>
|
|
205
|
+
<CardTitle>{t('movethewheels.fleet.status.vehicleTitle', 'Vehicle Status')}</CardTitle>
|
|
206
|
+
</CardHeader>
|
|
207
|
+
<CardContent>
|
|
208
|
+
<div className="space-y-4">
|
|
209
|
+
{Object.entries(stats.vehiclesByStatus).map(([status, count]) => (
|
|
210
|
+
<div key={status} className="flex items-center justify-between">
|
|
211
|
+
<div className="flex items-center gap-3">
|
|
212
|
+
<StatusBadge status={status} type="vehicle" />
|
|
213
|
+
</div>
|
|
214
|
+
<div className="flex items-center gap-3">
|
|
215
|
+
<span className="font-medium">{count}</span>
|
|
216
|
+
<Progress value={(count / stats.totalVehicles) * 100} className="w-24 h-2" />
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
))}
|
|
220
|
+
</div>
|
|
221
|
+
</CardContent>
|
|
222
|
+
</Card>
|
|
223
|
+
|
|
224
|
+
{/* Driver Status */}
|
|
225
|
+
<Card>
|
|
226
|
+
<CardHeader>
|
|
227
|
+
<CardTitle>{t('movethewheels.fleet.status.driverTitle', 'Driver Status')}</CardTitle>
|
|
228
|
+
</CardHeader>
|
|
229
|
+
<CardContent>
|
|
230
|
+
<div className="space-y-4">
|
|
231
|
+
{Object.entries(stats.driversByStatus).map(([status, count]) => (
|
|
232
|
+
<div key={status} className="flex items-center justify-between">
|
|
233
|
+
<div className="flex items-center gap-3">
|
|
234
|
+
<StatusBadge status={status} type="driver" />
|
|
235
|
+
</div>
|
|
236
|
+
<div className="flex items-center gap-3">
|
|
237
|
+
<span className="font-medium">{count}</span>
|
|
238
|
+
<Progress value={(count / stats.totalDrivers) * 100} className="w-24 h-2" />
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
))}
|
|
242
|
+
</div>
|
|
243
|
+
</CardContent>
|
|
244
|
+
</Card>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
{/* Maintenance Alerts */}
|
|
248
|
+
{stats.vehiclesByStatus.maintenance > 0 && (
|
|
249
|
+
<Card className="border-yellow-200 dark:border-yellow-800">
|
|
250
|
+
<CardHeader>
|
|
251
|
+
<CardTitle className="flex items-center gap-2 text-yellow-600">
|
|
252
|
+
<Wrench size={18} />
|
|
253
|
+
{t('movethewheels.fleet.maintenance.title', 'Maintenance Required')}
|
|
254
|
+
</CardTitle>
|
|
255
|
+
</CardHeader>
|
|
256
|
+
<CardContent>
|
|
257
|
+
<div className="space-y-3">
|
|
258
|
+
{vehicles
|
|
259
|
+
.filter((v) => v.status === 'maintenance')
|
|
260
|
+
.map((vehicle) => (
|
|
261
|
+
<div
|
|
262
|
+
key={vehicle.id}
|
|
263
|
+
className="flex items-center justify-between p-3 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg"
|
|
264
|
+
>
|
|
265
|
+
<div className="flex items-center gap-3">
|
|
266
|
+
<Truck size={20} className="text-yellow-600" />
|
|
267
|
+
<div>
|
|
268
|
+
<p className="font-medium">{vehicle.vehicleNumber}</p>
|
|
269
|
+
<p className="text-sm text-muted-foreground">
|
|
270
|
+
{vehicle.make} {vehicle.model}
|
|
271
|
+
</p>
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
<Button
|
|
275
|
+
variant="outline"
|
|
276
|
+
size="sm"
|
|
277
|
+
onClick={() => goTo(`fleet/vehicles/${vehicle.id}`)}
|
|
278
|
+
>
|
|
279
|
+
{t('movethewheels.fleet.maintenance.viewDetails', 'View Details')}
|
|
280
|
+
</Button>
|
|
281
|
+
</div>
|
|
282
|
+
))}
|
|
283
|
+
</div>
|
|
284
|
+
</CardContent>
|
|
285
|
+
</Card>
|
|
286
|
+
)}
|
|
287
|
+
</PageLayout>
|
|
288
|
+
);
|
|
289
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import/Export Page
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Upload, Download, FileText, FileSpreadsheet, Check } from 'lucide-react';
|
|
6
|
+
import { PageLayout } from '../components/PageLayout';
|
|
7
|
+
import { Card, CardContent, CardHeader, CardTitle, Button, Badge } from '../components/ui';
|
|
8
|
+
|
|
9
|
+
export default function ImportExportPage() {
|
|
10
|
+
const importOptions = [
|
|
11
|
+
{
|
|
12
|
+
name: 'Orders',
|
|
13
|
+
description: 'Import orders from CSV or Excel',
|
|
14
|
+
icon: FileSpreadsheet,
|
|
15
|
+
format: 'CSV, XLSX',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'Customers',
|
|
19
|
+
description: 'Import customer database',
|
|
20
|
+
icon: FileSpreadsheet,
|
|
21
|
+
format: 'CSV, XLSX',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'Products',
|
|
25
|
+
description: 'Import product catalog',
|
|
26
|
+
icon: FileSpreadsheet,
|
|
27
|
+
format: 'CSV, XLSX',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'Inventory',
|
|
31
|
+
description: 'Import inventory levels',
|
|
32
|
+
icon: FileSpreadsheet,
|
|
33
|
+
format: 'CSV, XLSX',
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const exportOptions = [
|
|
38
|
+
{
|
|
39
|
+
name: 'Orders Report',
|
|
40
|
+
description: 'Export all orders data',
|
|
41
|
+
formats: ['CSV', 'Excel', 'PDF'],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'Financial Report',
|
|
45
|
+
description: 'Export revenue and payments',
|
|
46
|
+
formats: ['CSV', 'Excel', 'PDF'],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'Fleet Report',
|
|
50
|
+
description: 'Export vehicle and driver data',
|
|
51
|
+
formats: ['CSV', 'Excel'],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'Analytics Report',
|
|
55
|
+
description: 'Export performance metrics',
|
|
56
|
+
formats: ['CSV', 'Excel', 'PDF'],
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const recentActivity = [
|
|
61
|
+
{ type: 'export', name: 'Orders Report', date: '2024-01-15', status: 'completed' },
|
|
62
|
+
{ type: 'import', name: 'Customer Data', date: '2024-01-14', status: 'completed' },
|
|
63
|
+
{ type: 'export', name: 'Financial Report', date: '2024-01-13', status: 'completed' },
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<PageLayout
|
|
68
|
+
title="Import / Export"
|
|
69
|
+
subtitle="Import and export your data"
|
|
70
|
+
icon={<FileText size={24} />}
|
|
71
|
+
>
|
|
72
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
73
|
+
<Card>
|
|
74
|
+
<CardHeader>
|
|
75
|
+
<CardTitle className="flex items-center gap-2">
|
|
76
|
+
<Upload size={18} /> Import Data
|
|
77
|
+
</CardTitle>
|
|
78
|
+
</CardHeader>
|
|
79
|
+
<CardContent className="space-y-4">
|
|
80
|
+
{importOptions.map((opt) => (
|
|
81
|
+
<div
|
|
82
|
+
key={opt.name}
|
|
83
|
+
className="flex items-center justify-between p-4 border rounded-lg hover:border-primary/50 transition-colors"
|
|
84
|
+
>
|
|
85
|
+
<div className="flex items-center gap-3">
|
|
86
|
+
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center">
|
|
87
|
+
<opt.icon size={20} className="text-primary" />
|
|
88
|
+
</div>
|
|
89
|
+
<div>
|
|
90
|
+
<p className="font-medium">{opt.name}</p>
|
|
91
|
+
<p className="text-sm text-muted-foreground">{opt.description}</p>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
<div className="text-right">
|
|
95
|
+
<Badge variant="outline">{opt.format}</Badge>
|
|
96
|
+
<Button variant="outline" size="sm" className="ml-2">
|
|
97
|
+
<Upload size={14} />
|
|
98
|
+
</Button>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
))}
|
|
102
|
+
</CardContent>
|
|
103
|
+
</Card>
|
|
104
|
+
|
|
105
|
+
<Card>
|
|
106
|
+
<CardHeader>
|
|
107
|
+
<CardTitle className="flex items-center gap-2">
|
|
108
|
+
<Download size={18} /> Export Data
|
|
109
|
+
</CardTitle>
|
|
110
|
+
</CardHeader>
|
|
111
|
+
<CardContent className="space-y-4">
|
|
112
|
+
{exportOptions.map((opt) => (
|
|
113
|
+
<div
|
|
114
|
+
key={opt.name}
|
|
115
|
+
className="flex items-center justify-between p-4 border rounded-lg hover:border-primary/50 transition-colors"
|
|
116
|
+
>
|
|
117
|
+
<div>
|
|
118
|
+
<p className="font-medium">{opt.name}</p>
|
|
119
|
+
<p className="text-sm text-muted-foreground">{opt.description}</p>
|
|
120
|
+
</div>
|
|
121
|
+
<div className="flex gap-1">
|
|
122
|
+
{opt.formats.map((f) => (
|
|
123
|
+
<Button key={f} variant="outline" size="sm">
|
|
124
|
+
{f}
|
|
125
|
+
</Button>
|
|
126
|
+
))}
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
))}
|
|
130
|
+
</CardContent>
|
|
131
|
+
</Card>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<Card>
|
|
135
|
+
<CardHeader>
|
|
136
|
+
<CardTitle>Recent Activity</CardTitle>
|
|
137
|
+
</CardHeader>
|
|
138
|
+
<CardContent>
|
|
139
|
+
<div className="space-y-3">
|
|
140
|
+
{recentActivity.map((activity, i) => (
|
|
141
|
+
<div key={i} className="flex items-center justify-between p-3 bg-muted/50 rounded-lg">
|
|
142
|
+
<div className="flex items-center gap-3">
|
|
143
|
+
{activity.type === 'import' ? (
|
|
144
|
+
<Upload size={16} className="text-primary" />
|
|
145
|
+
) : (
|
|
146
|
+
<Download size={16} className="text-primary" />
|
|
147
|
+
)}
|
|
148
|
+
<div>
|
|
149
|
+
<p className="font-medium">{activity.name}</p>
|
|
150
|
+
<p className="text-sm text-muted-foreground">
|
|
151
|
+
{activity.type === 'import' ? 'Imported' : 'Exported'} on {activity.date}
|
|
152
|
+
</p>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
<Badge variant="default" className="gap-1">
|
|
156
|
+
<Check size={12} /> {activity.status}
|
|
157
|
+
</Badge>
|
|
158
|
+
</div>
|
|
159
|
+
))}
|
|
160
|
+
</div>
|
|
161
|
+
</CardContent>
|
|
162
|
+
</Card>
|
|
163
|
+
</PageLayout>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inventory Page - Overview of inventory management
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Package, Warehouse, AlertTriangle, ChevronRight, Plus } from 'lucide-react';
|
|
6
|
+
import { PageLayout, PageSection } from '../components/PageLayout';
|
|
7
|
+
import { MetricCard, MetricGrid } from '../components/MetricCard';
|
|
8
|
+
import {
|
|
9
|
+
Card,
|
|
10
|
+
CardContent,
|
|
11
|
+
CardHeader,
|
|
12
|
+
CardTitle,
|
|
13
|
+
Button,
|
|
14
|
+
Badge,
|
|
15
|
+
Progress,
|
|
16
|
+
} from '../components/ui';
|
|
17
|
+
import { useInventory, useInventoryStats } from '../hooks/useInventory';
|
|
18
|
+
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
19
|
+
import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';
|
|
20
|
+
import { formatCurrency } from '../utils/formatters';
|
|
21
|
+
import { joinPath } from '../utils/navigation';
|
|
22
|
+
|
|
23
|
+
export default function InventoryPage() {
|
|
24
|
+
const { t: translate } = useI18n();
|
|
25
|
+
const t = (key: string, fallback: string): string => {
|
|
26
|
+
const value = translate(key);
|
|
27
|
+
return value === key ? fallback : value;
|
|
28
|
+
};
|
|
29
|
+
const { basePath, navigate } = useMoveTheWheels();
|
|
30
|
+
const { inventory, products, warehouses } = useInventory();
|
|
31
|
+
const { stats } = useInventoryStats();
|
|
32
|
+
|
|
33
|
+
const goTo = (path: string) => navigate?.(joinPath(basePath, path));
|
|
34
|
+
const lowStockItems = inventory.filter((inv) => inv.availableQuantity <= inv.reorderLevel);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<PageLayout
|
|
38
|
+
title={t('movethewheels.inventory.title', 'Inventory')}
|
|
39
|
+
subtitle={t('movethewheels.inventory.subtitle', 'Manage your products and warehouses')}
|
|
40
|
+
icon={<Package size={24} />}
|
|
41
|
+
headerActions={
|
|
42
|
+
<div className="flex gap-2">
|
|
43
|
+
<Button variant="outline" onClick={() => goTo('inventory/products')}>
|
|
44
|
+
{t('movethewheels.inventory.actions.products', 'Products')}
|
|
45
|
+
</Button>
|
|
46
|
+
<Button variant="outline" onClick={() => goTo('inventory/warehouses')}>
|
|
47
|
+
{t('movethewheels.inventory.actions.warehouses', 'Warehouses')}
|
|
48
|
+
</Button>
|
|
49
|
+
<Button>
|
|
50
|
+
<Plus size={16} /> {t('movethewheels.inventory.actions.addProduct', 'Add Product')}
|
|
51
|
+
</Button>
|
|
52
|
+
</div>
|
|
53
|
+
}
|
|
54
|
+
>
|
|
55
|
+
<MetricGrid columns={4}>
|
|
56
|
+
<MetricCard
|
|
57
|
+
label={t('movethewheels.inventory.metrics.totalProducts', 'Total Products')}
|
|
58
|
+
value={stats.totalProducts}
|
|
59
|
+
icon={<Package size={20} />}
|
|
60
|
+
onClick={() => goTo('inventory/products')}
|
|
61
|
+
/>
|
|
62
|
+
<MetricCard
|
|
63
|
+
label={t('movethewheels.inventory.metrics.totalItems', 'Total Items')}
|
|
64
|
+
value={stats.totalItems.toLocaleString()}
|
|
65
|
+
icon={<Package size={20} />}
|
|
66
|
+
/>
|
|
67
|
+
<MetricCard
|
|
68
|
+
label={t('movethewheels.inventory.metrics.inventoryValue', 'Inventory Value')}
|
|
69
|
+
value={formatCurrency(stats.totalValue)}
|
|
70
|
+
icon={<Package size={20} />}
|
|
71
|
+
/>
|
|
72
|
+
<MetricCard
|
|
73
|
+
label={t('movethewheels.inventory.metrics.warehouses', 'Warehouses')}
|
|
74
|
+
value={stats.totalWarehouses}
|
|
75
|
+
icon={<Warehouse size={20} />}
|
|
76
|
+
onClick={() => goTo('inventory/warehouses')}
|
|
77
|
+
/>
|
|
78
|
+
</MetricGrid>
|
|
79
|
+
|
|
80
|
+
{lowStockItems.length > 0 && (
|
|
81
|
+
<Card className="border-yellow-200">
|
|
82
|
+
<CardHeader>
|
|
83
|
+
<CardTitle className="flex items-center gap-2 text-yellow-600">
|
|
84
|
+
<AlertTriangle size={18} />
|
|
85
|
+
{t('movethewheels.inventory.lowStock.title', 'Low Stock Alert')} (
|
|
86
|
+
{lowStockItems.length} {t('movethewheels.inventory.lowStock.items', 'items')})
|
|
87
|
+
</CardTitle>
|
|
88
|
+
</CardHeader>
|
|
89
|
+
<CardContent>
|
|
90
|
+
<div className="space-y-3">
|
|
91
|
+
{lowStockItems.slice(0, 5).map((item) => {
|
|
92
|
+
const product = products.find((p) => p.id === item.productId);
|
|
93
|
+
return (
|
|
94
|
+
<div
|
|
95
|
+
key={item.id}
|
|
96
|
+
className="flex items-center justify-between p-3 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg"
|
|
97
|
+
>
|
|
98
|
+
<div>
|
|
99
|
+
<p className="font-medium">
|
|
100
|
+
{product?.name ||
|
|
101
|
+
t('movethewheels.inventory.lowStock.unknownProduct', 'Unknown Product')}
|
|
102
|
+
</p>
|
|
103
|
+
<p className="text-sm text-muted-foreground">
|
|
104
|
+
{t('movethewheels.inventory.lowStock.sku', 'SKU:')} {product?.sku}
|
|
105
|
+
</p>
|
|
106
|
+
</div>
|
|
107
|
+
<div className="text-right">
|
|
108
|
+
<p className="font-medium text-yellow-600">
|
|
109
|
+
{item.availableQuantity}{' '}
|
|
110
|
+
{t('movethewheels.inventory.lowStock.left', 'left')}
|
|
111
|
+
</p>
|
|
112
|
+
<p className="text-sm text-muted-foreground">
|
|
113
|
+
{t('movethewheels.inventory.lowStock.reorderAt', 'Reorder at:')}{' '}
|
|
114
|
+
{item.reorderLevel}
|
|
115
|
+
</p>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
})}
|
|
120
|
+
</div>
|
|
121
|
+
</CardContent>
|
|
122
|
+
</Card>
|
|
123
|
+
)}
|
|
124
|
+
|
|
125
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
126
|
+
<PageSection
|
|
127
|
+
title={t('movethewheels.inventory.warehouses.title', 'Warehouses')}
|
|
128
|
+
actions={
|
|
129
|
+
<Button variant="ghost" size="sm" onClick={() => goTo('inventory/warehouses')}>
|
|
130
|
+
{t('movethewheels.inventory.common.viewAll', 'View all')} <ChevronRight size={16} />
|
|
131
|
+
</Button>
|
|
132
|
+
}
|
|
133
|
+
>
|
|
134
|
+
<Card>
|
|
135
|
+
<CardContent className="p-0">
|
|
136
|
+
<div className="divide-y">
|
|
137
|
+
{warehouses.map((warehouse) => (
|
|
138
|
+
<div
|
|
139
|
+
key={warehouse.id}
|
|
140
|
+
className="p-4 hover:bg-muted/50 cursor-pointer"
|
|
141
|
+
onClick={() => goTo(`inventory/warehouses/${warehouse.id}`)}
|
|
142
|
+
>
|
|
143
|
+
<div className="flex items-center justify-between">
|
|
144
|
+
<div>
|
|
145
|
+
<p className="font-medium">{warehouse.name}</p>
|
|
146
|
+
<p className="text-sm text-muted-foreground">
|
|
147
|
+
{warehouse.address.city}, {warehouse.address.state}
|
|
148
|
+
</p>
|
|
149
|
+
</div>
|
|
150
|
+
<div className="text-right">
|
|
151
|
+
<Badge variant={warehouse.isActive ? 'default' : 'secondary'}>
|
|
152
|
+
{warehouse.isActive
|
|
153
|
+
? t('movethewheels.inventory.warehouses.active', 'Active')
|
|
154
|
+
: t('movethewheels.inventory.warehouses.inactive', 'Inactive')}
|
|
155
|
+
</Badge>
|
|
156
|
+
<div className="mt-2">
|
|
157
|
+
<p className="text-xs text-muted-foreground">
|
|
158
|
+
{t('movethewheels.inventory.warehouses.utilization', 'Utilization')}
|
|
159
|
+
</p>
|
|
160
|
+
<Progress
|
|
161
|
+
value={
|
|
162
|
+
(warehouse.capacity.usedSpace / warehouse.capacity.totalSpace) * 100
|
|
163
|
+
}
|
|
164
|
+
className="w-24 h-2"
|
|
165
|
+
/>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
))}
|
|
171
|
+
</div>
|
|
172
|
+
</CardContent>
|
|
173
|
+
</Card>
|
|
174
|
+
</PageSection>
|
|
175
|
+
|
|
176
|
+
<PageSection
|
|
177
|
+
title={t('movethewheels.inventory.products.title', 'Recent Products')}
|
|
178
|
+
actions={
|
|
179
|
+
<Button variant="ghost" size="sm" onClick={() => goTo('inventory/products')}>
|
|
180
|
+
{t('movethewheels.inventory.common.viewAll', 'View all')} <ChevronRight size={16} />
|
|
181
|
+
</Button>
|
|
182
|
+
}
|
|
183
|
+
>
|
|
184
|
+
<Card>
|
|
185
|
+
<CardContent className="p-0">
|
|
186
|
+
<div className="divide-y">
|
|
187
|
+
{products.slice(0, 5).map((product) => (
|
|
188
|
+
<div
|
|
189
|
+
key={product.id}
|
|
190
|
+
className="p-4 hover:bg-muted/50 cursor-pointer"
|
|
191
|
+
onClick={() => goTo(`inventory/products/${product.id}`)}
|
|
192
|
+
>
|
|
193
|
+
<div className="flex items-center justify-between">
|
|
194
|
+
<div>
|
|
195
|
+
<p className="font-medium">{product.name}</p>
|
|
196
|
+
<p className="text-sm text-muted-foreground">{product.sku}</p>
|
|
197
|
+
</div>
|
|
198
|
+
<div className="text-right">
|
|
199
|
+
<p className="font-medium">{formatCurrency(product.value)}</p>
|
|
200
|
+
<Badge variant="outline">{product.category}</Badge>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
))}
|
|
205
|
+
</div>
|
|
206
|
+
</CardContent>
|
|
207
|
+
</Card>
|
|
208
|
+
</PageSection>
|
|
209
|
+
</div>
|
|
210
|
+
</PageLayout>
|
|
211
|
+
);
|
|
212
|
+
}
|