@burdenoff/microfe-movethewheels 2026.703.1 → 2026.703.2
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/dist/{AIAssistantPage-C3ovZ0Fh.js → AIAssistantPage-C3cPsWt-.js} +1 -1
- package/dist/{AnalyticsPage-O8SpY-V4.js → AnalyticsPage-Dz6g2MvK.js} +6 -6
- package/dist/{CreateOrderPage-CB1_-_mN.js → CreateOrderPage-C5iwnjp4.js} +29 -58
- package/dist/CustomerDetailsPage-B6nwFyLO.js +406 -0
- package/dist/{CustomersPage-DRaLuHMC.js → CustomersPage-2Y05LVyh.js} +4 -4
- package/dist/{DashboardPage-DPjhNfKB.js → DashboardPage-BVjQi0EB.js} +11 -11
- package/dist/DriverDetailsPage-CN3WQ0B9.js +507 -0
- package/dist/{DriversPage-C2Bk0w4Q.js → DriversPage-B0Zpx8OQ.js} +5 -5
- package/dist/{FinancePage-lmp0Sdsw.js → FinancePage-DEDykSOP.js} +5 -5
- package/dist/{FleetPage-DZn5pIqd.js → FleetPage-DqkFi3s3.js} +5 -5
- package/dist/FormError-BlOjnXOq.js +15 -0
- package/dist/{ImportExportPage-BIUJZc7O.js → ImportExportPage-DKJRCezl.js} +1 -1
- package/dist/{InventoryPage-Cxh1zYJd.js → InventoryPage-DwfgCUQq.js} +5 -5
- package/dist/{LiveTrackingPage-BY1yAkIt.js → LiveTrackingPage-DCX-K-LR.js} +5 -5
- package/dist/{MarketplacePage-RMuL17EA.js → MarketplacePage-CIQ97_zp.js} +1 -1
- package/dist/{OrderDetailsPage-C2Mf4KSy.js → OrderDetailsPage-DYiZ-9UK.js} +5 -5
- package/dist/{OrdersListPage-BrPBtnhp.js → OrdersListPage-BjDlCKJd.js} +3 -3
- package/dist/ProductDetailsPage-C9uOiYLp.js +350 -0
- package/dist/{ProductsPage-I5DilIoY.js → ProductsPage-C1G9h7Xd.js} +3 -3
- package/dist/{ReportsPage-BDXNmUng.js → ReportsPage-Zp53q3_8.js} +2 -2
- package/dist/RouteDetailsPage-DUStXSf9.js +531 -0
- package/dist/{RoutesPage-lqX9ylHP.js → RoutesPage-DMfoicM1.js} +3 -3
- package/dist/{SettingsPage-CQVIOaNs.js → SettingsPage-Bn-v0mbQ.js} +1 -1
- package/dist/{TrackingPage-BXrjz7V8.js → TrackingPage-W-jtdM3Y.js} +4 -4
- package/dist/VehicleDetailsPage-Bceo5oz3.js +410 -0
- package/dist/{VehiclesPage-6W39XT5W.js → VehiclesPage-VBtOep91.js} +4 -4
- package/dist/WarehouseDetailsPage-L_6sWigM.js +493 -0
- package/dist/{WarehousesPage-CHvBqNaQ.js → WarehousesPage-DCbXXXWE.js} +3 -3
- package/dist/index.js +154 -138
- package/dist/trash-2-BKhar6Hs.js +32 -0
- package/dist/{useFleet-DiAC-IiH.js → useFleet-DKqXZ16m.js} +77 -56
- package/dist/{useInventory-DKoks37x.js → useInventory-DkyiVY_A.js} +73 -53
- package/dist/{useTracking-GJify4HN.js → useTracking-B-va1kO3.js} +2 -2
- package/package.json +1 -1
- package/src/movethewheels/MoveTheWheelsRoutes.tsx +4 -0
- package/src/movethewheels/components/FormError.tsx +25 -0
- package/src/movethewheels/hooks/useFleet.ts +107 -0
- package/src/movethewheels/hooks/useInventory.ts +107 -0
- package/src/movethewheels/pages/CustomerDetailsPage.tsx +153 -20
- package/src/movethewheels/pages/DriverDetailsPage.tsx +159 -2
- package/src/movethewheels/pages/DriversPage.tsx +5 -1
- package/src/movethewheels/pages/ProductDetailsPage.tsx +146 -3
- package/src/movethewheels/pages/ProductsPage.tsx +5 -1
- package/src/movethewheels/pages/RouteDetailsPage.tsx +285 -25
- package/src/movethewheels/pages/VehicleDetailsPage.tsx +158 -2
- package/src/movethewheels/pages/VehiclesPage.tsx +5 -1
- package/src/movethewheels/pages/WarehouseDetailsPage.tsx +224 -2
- package/src/movethewheels/pages/WarehousesPage.tsx +5 -1
- package/dist/CustomerDetailsPage-BaeKEI1D.js +0 -263
- package/dist/DriverDetailsPage-CSSQTOzn.js +0 -328
- package/dist/ProductDetailsPage-CezzzZcj.js +0 -193
- package/dist/RouteDetailsPage-BcomL20M.js +0 -263
- package/dist/VehicleDetailsPage-DZjLcJgr.js +0 -218
- package/dist/WarehouseDetailsPage-C10pgRGW.js +0 -236
- /package/dist/{MetricCard-7aYgzZHK.js → MetricCard-BY0QrMD4.js} +0 -0
- /package/dist/{StatusBadge-NpTeHVlw.js → StatusBadge-BZurYHMy.js} +0 -0
- /package/dist/{formatters-DhKZ-eA1.js → formatters-CF2S2l1m.js} +0 -0
- /package/dist/{trending-up-B7W-GL6P.js → trending-up-Dohgd4t_.js} +0 -0
- /package/dist/{triangle-alert-BonHFFNz.js → triangle-alert-i-XQL9Rd.js} +0 -0
- /package/dist/{truck-DrlfSpRC.js → truck-C0v_hbLR.js} +0 -0
- /package/dist/{useAnalytics-CgdzUfY_.js → useAnalytics-DS8n0xgg.js} +0 -0
- /package/dist/{useCustomers-QrlozybF.js → useCustomers-BmTNGxiI.js} +0 -0
- /package/dist/{useImportExport-BLMnAKCC.js → useImportExport-DHr-SWSw.js} +0 -0
- /package/dist/{useOrders-D3UZVy8Y.js → useOrders-Cfk1GLiq.js} +0 -0
- /package/dist/{useReports-Puq4DCH9.js → useReports-CTqZr-AB.js} +0 -0
- /package/dist/{useRoutes-Do57LvRb.js → useRoutes-BQWO607N.js} +0 -0
- /package/dist/{user-CjSoudeu.js → user-jyFU3lXk.js} +0 -0
- /package/dist/{users-BAM4gHTn.js → users-DZ-ilS6B.js} +0 -0
- /package/dist/{warehouse-C7MSr47B.js → warehouse-6m5kXMLu.js} +0 -0
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Vehicle Details Page
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { useState } from 'react';
|
|
5
6
|
import { useParams } from 'react-router-dom';
|
|
6
|
-
import { Truck, ArrowLeft, MapPin, Fuel, Wrench, FileText, Edit } from 'lucide-react';
|
|
7
|
+
import { Truck, ArrowLeft, MapPin, Fuel, Wrench, FileText, Edit, Save } from 'lucide-react';
|
|
7
8
|
import { PageLayout, LoadingState, ErrorState } from '../components/PageLayout';
|
|
8
9
|
import { StatusBadge, VehicleTypeBadge } from '../components/StatusBadge';
|
|
9
10
|
import {
|
|
@@ -14,12 +15,166 @@ import {
|
|
|
14
15
|
Button,
|
|
15
16
|
Badge,
|
|
16
17
|
Progress,
|
|
18
|
+
Input,
|
|
19
|
+
Label,
|
|
20
|
+
Select,
|
|
21
|
+
SelectContent,
|
|
22
|
+
SelectItem,
|
|
23
|
+
SelectTrigger,
|
|
24
|
+
SelectValue,
|
|
17
25
|
} from '../components/ui';
|
|
18
26
|
import { EmphasisPanel, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
19
|
-
import { useVehicle } from '../hooks/useFleet';
|
|
27
|
+
import { useVehicle, useFleet } from '../hooks/useFleet';
|
|
20
28
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
21
29
|
import { formatDate } from '../utils/formatters';
|
|
22
30
|
import { joinPath } from '../utils/navigation';
|
|
31
|
+
import { FormError } from '../components/FormError';
|
|
32
|
+
|
|
33
|
+
const VEHICLE_TYPE_OPTIONS = [
|
|
34
|
+
{ value: 'TRUCK', label: 'Truck' },
|
|
35
|
+
{ value: 'VAN', label: 'Van' },
|
|
36
|
+
{ value: 'MOTORCYCLE', label: 'Motorcycle' },
|
|
37
|
+
{ value: 'BICYCLE', label: 'Bicycle' },
|
|
38
|
+
{ value: 'FLATBED', label: 'Flatbed' },
|
|
39
|
+
{ value: 'REFRIGERATED', label: 'Refrigerated' },
|
|
40
|
+
{ value: 'SEMI_TRAILER', label: 'Semi-Trailer' },
|
|
41
|
+
{ value: 'DRONE', label: 'Drone' },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
function NewVehicleForm() {
|
|
45
|
+
const { basePath, navigate } = useMoveTheWheels();
|
|
46
|
+
const { createVehicle } = useFleet();
|
|
47
|
+
const [form, setForm] = useState({
|
|
48
|
+
vehicleNumber: '',
|
|
49
|
+
type: 'VAN',
|
|
50
|
+
make: '',
|
|
51
|
+
model: '',
|
|
52
|
+
licensePlate: '',
|
|
53
|
+
});
|
|
54
|
+
const [submitting, setSubmitting] = useState(false);
|
|
55
|
+
const [error, setError] = useState<string | null>(null);
|
|
56
|
+
|
|
57
|
+
const goBack = () => navigate?.(joinPath(basePath, 'fleet/vehicles'));
|
|
58
|
+
|
|
59
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
if (!form.vehicleNumber.trim()) {
|
|
62
|
+
setError('Vehicle number is required.');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
setSubmitting(true);
|
|
66
|
+
setError(null);
|
|
67
|
+
try {
|
|
68
|
+
const created = await createVehicle({
|
|
69
|
+
vehicleNumber: form.vehicleNumber.trim(),
|
|
70
|
+
type: form.type,
|
|
71
|
+
make: form.make.trim() || undefined,
|
|
72
|
+
model: form.model.trim() || undefined,
|
|
73
|
+
licensePlate: form.licensePlate.trim() || undefined,
|
|
74
|
+
});
|
|
75
|
+
navigate?.(joinPath(basePath, `fleet/vehicles/${created.id}`));
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.error('Failed to create vehicle:', err);
|
|
78
|
+
setError(err instanceof Error ? err.message : 'Failed to create vehicle. Please try again.');
|
|
79
|
+
setSubmitting(false);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<PageLayout
|
|
85
|
+
title="Add Vehicle"
|
|
86
|
+
subtitle="Register a new vehicle in your fleet"
|
|
87
|
+
icon={<Truck size={24} />}
|
|
88
|
+
headerActions={
|
|
89
|
+
<div className="flex gap-2">
|
|
90
|
+
<Button variant="outline" onClick={goBack} disabled={submitting}>
|
|
91
|
+
<ArrowLeft size={16} /> Cancel
|
|
92
|
+
</Button>
|
|
93
|
+
<Button onClick={handleSubmit} disabled={submitting}>
|
|
94
|
+
<Save size={16} /> {submitting ? 'Saving...' : 'Add Vehicle'}
|
|
95
|
+
</Button>
|
|
96
|
+
</div>
|
|
97
|
+
}
|
|
98
|
+
>
|
|
99
|
+
<PagePurpose icon={<Truck size={18} />}>
|
|
100
|
+
Add a vehicle so it can be assigned to deliveries and routes. Give it a fleet number and type
|
|
101
|
+
to start — make, model and plate help identify it on the road.
|
|
102
|
+
</PagePurpose>
|
|
103
|
+
|
|
104
|
+
<form onSubmit={handleSubmit} className="max-w-2xl space-y-6">
|
|
105
|
+
<FormError message={error} />
|
|
106
|
+
<EmphasisPanel>
|
|
107
|
+
<h2 className="text-base font-semibold text-text-primary mb-4">Vehicle Details</h2>
|
|
108
|
+
<div className="space-y-4">
|
|
109
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
110
|
+
<div className="space-y-2">
|
|
111
|
+
<Label htmlFor="vehicleNumber">
|
|
112
|
+
Vehicle Number <span className="text-status-error-text">*</span>
|
|
113
|
+
</Label>
|
|
114
|
+
<Input
|
|
115
|
+
id="vehicleNumber"
|
|
116
|
+
value={form.vehicleNumber}
|
|
117
|
+
onChange={(e) => setForm((prev) => ({ ...prev, vehicleNumber: e.target.value }))}
|
|
118
|
+
placeholder="MTW-001"
|
|
119
|
+
required
|
|
120
|
+
/>
|
|
121
|
+
</div>
|
|
122
|
+
<div className="space-y-2">
|
|
123
|
+
<Label htmlFor="type">
|
|
124
|
+
Type <span className="text-status-error-text">*</span>
|
|
125
|
+
</Label>
|
|
126
|
+
<Select
|
|
127
|
+
value={form.type}
|
|
128
|
+
onValueChange={(value) => setForm((prev) => ({ ...prev, type: value }))}
|
|
129
|
+
>
|
|
130
|
+
<SelectTrigger id="type">
|
|
131
|
+
<SelectValue />
|
|
132
|
+
</SelectTrigger>
|
|
133
|
+
<SelectContent>
|
|
134
|
+
{VEHICLE_TYPE_OPTIONS.map((option) => (
|
|
135
|
+
<SelectItem key={option.value} value={option.value}>
|
|
136
|
+
{option.label}
|
|
137
|
+
</SelectItem>
|
|
138
|
+
))}
|
|
139
|
+
</SelectContent>
|
|
140
|
+
</Select>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
144
|
+
<div className="space-y-2">
|
|
145
|
+
<Label htmlFor="make">Make</Label>
|
|
146
|
+
<Input
|
|
147
|
+
id="make"
|
|
148
|
+
value={form.make}
|
|
149
|
+
onChange={(e) => setForm((prev) => ({ ...prev, make: e.target.value }))}
|
|
150
|
+
placeholder="Ford"
|
|
151
|
+
/>
|
|
152
|
+
</div>
|
|
153
|
+
<div className="space-y-2">
|
|
154
|
+
<Label htmlFor="model">Model</Label>
|
|
155
|
+
<Input
|
|
156
|
+
id="model"
|
|
157
|
+
value={form.model}
|
|
158
|
+
onChange={(e) => setForm((prev) => ({ ...prev, model: e.target.value }))}
|
|
159
|
+
placeholder="Transit"
|
|
160
|
+
/>
|
|
161
|
+
</div>
|
|
162
|
+
<div className="space-y-2">
|
|
163
|
+
<Label htmlFor="licensePlate">License Plate</Label>
|
|
164
|
+
<Input
|
|
165
|
+
id="licensePlate"
|
|
166
|
+
value={form.licensePlate}
|
|
167
|
+
onChange={(e) => setForm((prev) => ({ ...prev, licensePlate: e.target.value }))}
|
|
168
|
+
placeholder="ABC-1234"
|
|
169
|
+
/>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
</EmphasisPanel>
|
|
174
|
+
</form>
|
|
175
|
+
</PageLayout>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
23
178
|
|
|
24
179
|
export default function VehicleDetailsPage() {
|
|
25
180
|
const { vehicleId } = useParams<{ vehicleId: string }>();
|
|
@@ -29,6 +184,7 @@ export default function VehicleDetailsPage() {
|
|
|
29
184
|
const goBack = () => navigate?.(joinPath(basePath, 'fleet/vehicles'));
|
|
30
185
|
const goTo = (path: string) => navigate?.(joinPath(basePath, path));
|
|
31
186
|
|
|
187
|
+
if (!vehicleId) return <NewVehicleForm />;
|
|
32
188
|
if (isLoading) return <LoadingState message="Loading vehicle details..." />;
|
|
33
189
|
if (error || !vehicle) return <ErrorState title="Vehicle not found" onRetry={refetch} />;
|
|
34
190
|
|
|
@@ -95,7 +95,11 @@ export default function VehiclesPage() {
|
|
|
95
95
|
icon={<Truck size={24} />}
|
|
96
96
|
headerActions={
|
|
97
97
|
<CTAOverflowMenu
|
|
98
|
-
primary={{
|
|
98
|
+
primary={{
|
|
99
|
+
label: 'Add Vehicle',
|
|
100
|
+
icon: <Plus size={16} />,
|
|
101
|
+
onSelect: () => goTo('fleet/vehicles/new'),
|
|
102
|
+
}}
|
|
99
103
|
actions={[
|
|
100
104
|
{ label: 'Refresh', icon: <RefreshCw size={16} />, onSelect: () => refetch() },
|
|
101
105
|
{ label: 'Export', icon: <Download size={16} />, onSelect: () => {} },
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Warehouse Details Page
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { useState } from 'react';
|
|
5
6
|
import { useParams } from 'react-router-dom';
|
|
6
|
-
import { Warehouse, ArrowLeft, Edit, MapPin, Clock, Users, Box } from 'lucide-react';
|
|
7
|
+
import { Warehouse, ArrowLeft, Edit, MapPin, Clock, Users, Box, Save } from 'lucide-react';
|
|
7
8
|
import { PageLayout, LoadingState, ErrorState } from '../components/PageLayout';
|
|
8
9
|
import {
|
|
9
10
|
Card,
|
|
@@ -13,12 +14,232 @@ import {
|
|
|
13
14
|
Button,
|
|
14
15
|
Badge,
|
|
15
16
|
Progress,
|
|
17
|
+
Input,
|
|
18
|
+
Label,
|
|
19
|
+
Select,
|
|
20
|
+
SelectContent,
|
|
21
|
+
SelectItem,
|
|
22
|
+
SelectTrigger,
|
|
23
|
+
SelectValue,
|
|
16
24
|
} from '../components/ui';
|
|
17
25
|
import { EmphasisPanel, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
18
|
-
import { useWarehouse } from '../hooks/useInventory';
|
|
26
|
+
import { useWarehouse, useInventory } from '../hooks/useInventory';
|
|
19
27
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
20
28
|
import { formatAddress } from '../utils/formatters';
|
|
21
29
|
import { joinPath } from '../utils/navigation';
|
|
30
|
+
import { FormError } from '../components/FormError';
|
|
31
|
+
|
|
32
|
+
const WAREHOUSE_TYPE_OPTIONS = [
|
|
33
|
+
{ value: 'DISTRIBUTION', label: 'Distribution' },
|
|
34
|
+
{ value: 'FULFILLMENT', label: 'Fulfillment' },
|
|
35
|
+
{ value: 'COLD_STORAGE', label: 'Cold Storage' },
|
|
36
|
+
{ value: 'CROSS_DOCK', label: 'Cross-Dock' },
|
|
37
|
+
{ value: 'RETAIL', label: 'Retail' },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
function NewWarehouseForm() {
|
|
41
|
+
const { basePath, navigate } = useMoveTheWheels();
|
|
42
|
+
const { createWarehouse } = useInventory();
|
|
43
|
+
const [form, setForm] = useState({
|
|
44
|
+
code: '',
|
|
45
|
+
name: '',
|
|
46
|
+
type: 'DISTRIBUTION',
|
|
47
|
+
street: '',
|
|
48
|
+
city: '',
|
|
49
|
+
state: '',
|
|
50
|
+
postalCode: '',
|
|
51
|
+
country: 'USA',
|
|
52
|
+
totalCapacity: '',
|
|
53
|
+
});
|
|
54
|
+
const [submitting, setSubmitting] = useState(false);
|
|
55
|
+
const [error, setError] = useState<string | null>(null);
|
|
56
|
+
|
|
57
|
+
const goBack = () => navigate?.(joinPath(basePath, 'inventory/warehouses'));
|
|
58
|
+
|
|
59
|
+
const update = (field: keyof typeof form, value: string) =>
|
|
60
|
+
setForm((prev) => ({ ...prev, [field]: value }));
|
|
61
|
+
|
|
62
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
63
|
+
e.preventDefault();
|
|
64
|
+
if (!form.code.trim() || !form.name.trim()) {
|
|
65
|
+
setError('Warehouse code and name are required.');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!form.street.trim() || !form.city.trim()) {
|
|
69
|
+
setError('A street and city are required for the warehouse address.');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
setSubmitting(true);
|
|
73
|
+
setError(null);
|
|
74
|
+
try {
|
|
75
|
+
const totalCapacity = form.totalCapacity.trim() ? Number(form.totalCapacity) : undefined;
|
|
76
|
+
const created = await createWarehouse({
|
|
77
|
+
code: form.code.trim(),
|
|
78
|
+
name: form.name.trim(),
|
|
79
|
+
type: form.type,
|
|
80
|
+
address: {
|
|
81
|
+
street: form.street.trim(),
|
|
82
|
+
city: form.city.trim(),
|
|
83
|
+
state: form.state.trim(),
|
|
84
|
+
postalCode: form.postalCode.trim(),
|
|
85
|
+
country: form.country.trim(),
|
|
86
|
+
},
|
|
87
|
+
totalCapacity:
|
|
88
|
+
totalCapacity !== undefined && !Number.isNaN(totalCapacity) ? totalCapacity : undefined,
|
|
89
|
+
});
|
|
90
|
+
navigate?.(joinPath(basePath, `inventory/warehouses/${created.id}`));
|
|
91
|
+
} catch (err) {
|
|
92
|
+
console.error('Failed to create warehouse:', err);
|
|
93
|
+
setError(err instanceof Error ? err.message : 'Failed to create warehouse. Please try again.');
|
|
94
|
+
setSubmitting(false);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<PageLayout
|
|
100
|
+
title="Add Warehouse"
|
|
101
|
+
subtitle="Register a storage and fulfilment site"
|
|
102
|
+
icon={<Warehouse size={24} />}
|
|
103
|
+
headerActions={
|
|
104
|
+
<div className="flex gap-2">
|
|
105
|
+
<Button variant="outline" onClick={goBack} disabled={submitting}>
|
|
106
|
+
<ArrowLeft size={16} /> Cancel
|
|
107
|
+
</Button>
|
|
108
|
+
<Button onClick={handleSubmit} disabled={submitting}>
|
|
109
|
+
<Save size={16} /> {submitting ? 'Saving...' : 'Add Warehouse'}
|
|
110
|
+
</Button>
|
|
111
|
+
</div>
|
|
112
|
+
}
|
|
113
|
+
>
|
|
114
|
+
<PagePurpose icon={<Warehouse size={18} />}>
|
|
115
|
+
Add a warehouse so you can store inventory and fulfil orders from it. A code, name, type and
|
|
116
|
+
location get it on the network — set a total capacity to track utilization.
|
|
117
|
+
</PagePurpose>
|
|
118
|
+
|
|
119
|
+
<form onSubmit={handleSubmit} className="max-w-2xl space-y-6">
|
|
120
|
+
<FormError message={error} />
|
|
121
|
+
<EmphasisPanel>
|
|
122
|
+
<h2 className="text-base font-semibold text-text-primary mb-4">Warehouse Details</h2>
|
|
123
|
+
<div className="space-y-4">
|
|
124
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
125
|
+
<div className="space-y-2">
|
|
126
|
+
<Label htmlFor="code">
|
|
127
|
+
Code <span className="text-status-error-text">*</span>
|
|
128
|
+
</Label>
|
|
129
|
+
<Input
|
|
130
|
+
id="code"
|
|
131
|
+
value={form.code}
|
|
132
|
+
onChange={(e) => update('code', e.target.value)}
|
|
133
|
+
placeholder="WH-SF-01"
|
|
134
|
+
required
|
|
135
|
+
/>
|
|
136
|
+
</div>
|
|
137
|
+
<div className="space-y-2">
|
|
138
|
+
<Label htmlFor="name">
|
|
139
|
+
Name <span className="text-status-error-text">*</span>
|
|
140
|
+
</Label>
|
|
141
|
+
<Input
|
|
142
|
+
id="name"
|
|
143
|
+
value={form.name}
|
|
144
|
+
onChange={(e) => update('name', e.target.value)}
|
|
145
|
+
placeholder="San Francisco DC"
|
|
146
|
+
required
|
|
147
|
+
/>
|
|
148
|
+
</div>
|
|
149
|
+
<div className="space-y-2">
|
|
150
|
+
<Label htmlFor="type">
|
|
151
|
+
Type <span className="text-status-error-text">*</span>
|
|
152
|
+
</Label>
|
|
153
|
+
<Select value={form.type} onValueChange={(value) => update('type', value)}>
|
|
154
|
+
<SelectTrigger id="type">
|
|
155
|
+
<SelectValue />
|
|
156
|
+
</SelectTrigger>
|
|
157
|
+
<SelectContent>
|
|
158
|
+
{WAREHOUSE_TYPE_OPTIONS.map((option) => (
|
|
159
|
+
<SelectItem key={option.value} value={option.value}>
|
|
160
|
+
{option.label}
|
|
161
|
+
</SelectItem>
|
|
162
|
+
))}
|
|
163
|
+
</SelectContent>
|
|
164
|
+
</Select>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
<div className="space-y-2">
|
|
168
|
+
<Label htmlFor="totalCapacity">Total Capacity (sq ft)</Label>
|
|
169
|
+
<Input
|
|
170
|
+
id="totalCapacity"
|
|
171
|
+
type="number"
|
|
172
|
+
min="0"
|
|
173
|
+
value={form.totalCapacity}
|
|
174
|
+
onChange={(e) => update('totalCapacity', e.target.value)}
|
|
175
|
+
placeholder="50000"
|
|
176
|
+
/>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
</EmphasisPanel>
|
|
180
|
+
|
|
181
|
+
<Card>
|
|
182
|
+
<CardHeader>
|
|
183
|
+
<CardTitle className="flex items-center gap-2">
|
|
184
|
+
<MapPin size={18} /> Address <span className="text-status-error-text">*</span>
|
|
185
|
+
</CardTitle>
|
|
186
|
+
</CardHeader>
|
|
187
|
+
<CardContent className="space-y-4">
|
|
188
|
+
<div className="space-y-2">
|
|
189
|
+
<Label htmlFor="street">Street</Label>
|
|
190
|
+
<Input
|
|
191
|
+
id="street"
|
|
192
|
+
value={form.street}
|
|
193
|
+
onChange={(e) => update('street', e.target.value)}
|
|
194
|
+
placeholder="500 Industrial Way"
|
|
195
|
+
/>
|
|
196
|
+
</div>
|
|
197
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
198
|
+
<div className="space-y-2">
|
|
199
|
+
<Label htmlFor="city">City</Label>
|
|
200
|
+
<Input
|
|
201
|
+
id="city"
|
|
202
|
+
value={form.city}
|
|
203
|
+
onChange={(e) => update('city', e.target.value)}
|
|
204
|
+
placeholder="San Francisco"
|
|
205
|
+
/>
|
|
206
|
+
</div>
|
|
207
|
+
<div className="space-y-2">
|
|
208
|
+
<Label htmlFor="state">State</Label>
|
|
209
|
+
<Input
|
|
210
|
+
id="state"
|
|
211
|
+
value={form.state}
|
|
212
|
+
onChange={(e) => update('state', e.target.value)}
|
|
213
|
+
placeholder="CA"
|
|
214
|
+
/>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
218
|
+
<div className="space-y-2">
|
|
219
|
+
<Label htmlFor="postalCode">ZIP / Postal Code</Label>
|
|
220
|
+
<Input
|
|
221
|
+
id="postalCode"
|
|
222
|
+
value={form.postalCode}
|
|
223
|
+
onChange={(e) => update('postalCode', e.target.value)}
|
|
224
|
+
placeholder="94103"
|
|
225
|
+
/>
|
|
226
|
+
</div>
|
|
227
|
+
<div className="space-y-2">
|
|
228
|
+
<Label htmlFor="country">Country</Label>
|
|
229
|
+
<Input
|
|
230
|
+
id="country"
|
|
231
|
+
value={form.country}
|
|
232
|
+
onChange={(e) => update('country', e.target.value)}
|
|
233
|
+
placeholder="USA"
|
|
234
|
+
/>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
</CardContent>
|
|
238
|
+
</Card>
|
|
239
|
+
</form>
|
|
240
|
+
</PageLayout>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
22
243
|
|
|
23
244
|
export default function WarehouseDetailsPage() {
|
|
24
245
|
const { warehouseId } = useParams<{ warehouseId: string }>();
|
|
@@ -28,6 +249,7 @@ export default function WarehouseDetailsPage() {
|
|
|
28
249
|
const goBack = () => navigate?.(joinPath(basePath, 'inventory/warehouses'));
|
|
29
250
|
const goTo = (path: string) => navigate?.(joinPath(basePath, path));
|
|
30
251
|
|
|
252
|
+
if (!warehouseId) return <NewWarehouseForm />;
|
|
31
253
|
if (isLoading) return <LoadingState message="Loading warehouse details..." />;
|
|
32
254
|
if (error || !warehouse) return <ErrorState title="Warehouse not found" onRetry={refetch} />;
|
|
33
255
|
|
|
@@ -99,7 +99,11 @@ export default function WarehousesPage() {
|
|
|
99
99
|
icon={<Warehouse size={24} />}
|
|
100
100
|
headerActions={
|
|
101
101
|
<CTAOverflowMenu
|
|
102
|
-
primary={{
|
|
102
|
+
primary={{
|
|
103
|
+
label: 'Add Warehouse',
|
|
104
|
+
icon: <Plus size={16} />,
|
|
105
|
+
onSelect: () => goTo('inventory/warehouses/new'),
|
|
106
|
+
}}
|
|
103
107
|
actions={[{ label: 'Refresh', icon: <RefreshCw size={16} />, onSelect: () => refetch() }]}
|
|
104
108
|
/>
|
|
105
109
|
}
|