@burdenoff/microfe-movethewheels 2026.924.1 → 2026.924.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/components/OrderItemRow.js +124 -0
- package/dist/movethewheels/components/OrderItemRow.d.ts +13 -0
- package/dist/pages/CreateOrderPage.js +257 -287
- package/package.json +1 -1
- package/src/movethewheels/components/OrderItemRow.tsx +172 -0
- package/src/movethewheels/pages/CreateOrderPage.tsx +31 -69
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Trash2 as e } from "../node_modules/lucide-react/dist/esm/icons/trash-2.js";
|
|
2
|
+
import { useProduct as t, useProductStockLevels as n } from "../hooks/useInventory.js";
|
|
3
|
+
import { Button as r, Input as i, Label as a, Separator as o } from "./ui.js";
|
|
4
|
+
import { EntityCombobox as s } from "./EntityCombobox.js";
|
|
5
|
+
import { Fragment as c, jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
6
|
+
//#region src/movethewheels/components/OrderItemRow.tsx
|
|
7
|
+
function d(e, t) {
|
|
8
|
+
return e <= 0 ? "No stock recorded for this item." : t.length === 1 ? `${e.toLocaleString()} available at ${t[0].warehouseName}` : `${e.toLocaleString()} available across ${t.length} warehouses`;
|
|
9
|
+
}
|
|
10
|
+
function f({ item: f, index: p, isFirst: m, products: h, productsLoading: g, onUpdate: _, onRemove: v }) {
|
|
11
|
+
let { product: y, isLoading: b } = t(f.productId || void 0), { levels: x, totalAvailable: S, isLoading: C } = n(f.productId || void 0);
|
|
12
|
+
return /* @__PURE__ */ u("div", { children: [
|
|
13
|
+
!m && /* @__PURE__ */ l(o, { className: "my-4" }),
|
|
14
|
+
/* @__PURE__ */ u("div", {
|
|
15
|
+
className: "mb-3 space-y-2",
|
|
16
|
+
children: [
|
|
17
|
+
/* @__PURE__ */ l(a, {
|
|
18
|
+
htmlFor: `item-product-${f.id}`,
|
|
19
|
+
children: "Catalog item (optional)"
|
|
20
|
+
}),
|
|
21
|
+
/* @__PURE__ */ l(s, {
|
|
22
|
+
id: `item-product-${f.id}`,
|
|
23
|
+
value: f.productId,
|
|
24
|
+
onChange: (e) => {
|
|
25
|
+
if (!e) {
|
|
26
|
+
_(p, "productId", "");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
let t = h.find((t) => t.id === e);
|
|
30
|
+
t && (_(p, "productId", t.id), _(p, "name", t.name), _(p, "sku", t.sku), _(p, "weight", t.weight), _(p, "value", t.value), _(p, "dimensions", t.dimensions));
|
|
31
|
+
},
|
|
32
|
+
options: h,
|
|
33
|
+
loading: g,
|
|
34
|
+
getId: (e) => e.id,
|
|
35
|
+
getLabel: (e) => `${e.name} (${e.sku})`,
|
|
36
|
+
renderOption: (e) => /* @__PURE__ */ u(c, { children: [
|
|
37
|
+
e.name,
|
|
38
|
+
" ",
|
|
39
|
+
/* @__PURE__ */ u("span", {
|
|
40
|
+
className: "text-text-tertiary",
|
|
41
|
+
children: [
|
|
42
|
+
"(",
|
|
43
|
+
e.sku,
|
|
44
|
+
")"
|
|
45
|
+
]
|
|
46
|
+
})
|
|
47
|
+
] }),
|
|
48
|
+
selectedOption: y,
|
|
49
|
+
selectedLoading: b,
|
|
50
|
+
placeholder: "Pick from your product catalog",
|
|
51
|
+
emptyText: "No products found.",
|
|
52
|
+
noneLabel: "Enter details manually"
|
|
53
|
+
}),
|
|
54
|
+
f.productId && !C && /* @__PURE__ */ l("p", {
|
|
55
|
+
className: "text-xs text-text-secondary",
|
|
56
|
+
children: d(S, x)
|
|
57
|
+
})
|
|
58
|
+
]
|
|
59
|
+
}),
|
|
60
|
+
/* @__PURE__ */ u("div", {
|
|
61
|
+
className: "grid grid-cols-1 gap-4 md:grid-cols-7",
|
|
62
|
+
children: [
|
|
63
|
+
/* @__PURE__ */ u("div", {
|
|
64
|
+
className: "space-y-2 md:col-span-2",
|
|
65
|
+
children: [/* @__PURE__ */ l(a, { children: "Item Name" }), /* @__PURE__ */ l(i, {
|
|
66
|
+
value: f.name,
|
|
67
|
+
onChange: (e) => _(p, "name", e.target.value),
|
|
68
|
+
placeholder: "Product name"
|
|
69
|
+
})]
|
|
70
|
+
}),
|
|
71
|
+
/* @__PURE__ */ u("div", {
|
|
72
|
+
className: "space-y-2",
|
|
73
|
+
children: [/* @__PURE__ */ l(a, { children: "SKU" }), /* @__PURE__ */ l(i, {
|
|
74
|
+
value: f.sku,
|
|
75
|
+
onChange: (e) => _(p, "sku", e.target.value),
|
|
76
|
+
placeholder: "SKU-001"
|
|
77
|
+
})]
|
|
78
|
+
}),
|
|
79
|
+
/* @__PURE__ */ u("div", {
|
|
80
|
+
className: "space-y-2",
|
|
81
|
+
children: [/* @__PURE__ */ l(a, { children: "Quantity" }), /* @__PURE__ */ l(i, {
|
|
82
|
+
type: "number",
|
|
83
|
+
min: "1",
|
|
84
|
+
value: f.quantity,
|
|
85
|
+
onChange: (e) => _(p, "quantity", parseInt(e.target.value) || 1)
|
|
86
|
+
})]
|
|
87
|
+
}),
|
|
88
|
+
/* @__PURE__ */ u("div", {
|
|
89
|
+
className: "space-y-2",
|
|
90
|
+
children: [/* @__PURE__ */ l(a, { children: "Weight (kg)" }), /* @__PURE__ */ l(i, {
|
|
91
|
+
type: "number",
|
|
92
|
+
min: "0",
|
|
93
|
+
step: "0.1",
|
|
94
|
+
value: f.weight,
|
|
95
|
+
onChange: (e) => _(p, "weight", parseFloat(e.target.value) || 0)
|
|
96
|
+
})]
|
|
97
|
+
}),
|
|
98
|
+
/* @__PURE__ */ u("div", {
|
|
99
|
+
className: "space-y-2",
|
|
100
|
+
children: [/* @__PURE__ */ l(a, { children: "Value ($)" }), /* @__PURE__ */ l(i, {
|
|
101
|
+
type: "number",
|
|
102
|
+
min: "0",
|
|
103
|
+
step: "0.01",
|
|
104
|
+
value: f.value,
|
|
105
|
+
onChange: (e) => _(p, "value", parseFloat(e.target.value) || 0)
|
|
106
|
+
})]
|
|
107
|
+
}),
|
|
108
|
+
/* @__PURE__ */ l("div", {
|
|
109
|
+
className: "flex items-end",
|
|
110
|
+
children: /* @__PURE__ */ l(r, {
|
|
111
|
+
type: "button",
|
|
112
|
+
variant: "ghost",
|
|
113
|
+
size: "icon",
|
|
114
|
+
className: "text-status-error-text",
|
|
115
|
+
onClick: () => v(p),
|
|
116
|
+
children: /* @__PURE__ */ l(e, { size: 16 })
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
]
|
|
120
|
+
})
|
|
121
|
+
] });
|
|
122
|
+
}
|
|
123
|
+
//#endregion
|
|
124
|
+
export { f as OrderItemRow };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { OrderItem, Product } from '../types';
|
|
2
|
+
|
|
3
|
+
interface OrderItemRowProps {
|
|
4
|
+
item: OrderItem;
|
|
5
|
+
index: number;
|
|
6
|
+
isFirst: boolean;
|
|
7
|
+
products: Product[];
|
|
8
|
+
productsLoading: boolean;
|
|
9
|
+
onUpdate: (index: number, field: string, value: unknown) => void;
|
|
10
|
+
onRemove: (index: number) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare function OrderItemRow({ item, index, isFirst, products, productsLoading, onUpdate, onRemove, }: OrderItemRowProps): import("react").JSX.Element;
|
|
13
|
+
export {};
|
|
@@ -4,148 +4,152 @@ import { MapPin as n } from "../node_modules/lucide-react/dist/esm/icons/map-pin
|
|
|
4
4
|
import { Package as r } from "../node_modules/lucide-react/dist/esm/icons/package.js";
|
|
5
5
|
import { Plus as i } from "../node_modules/lucide-react/dist/esm/icons/plus.js";
|
|
6
6
|
import { Route as a } from "../node_modules/lucide-react/dist/esm/icons/route.js";
|
|
7
|
-
import { Save as
|
|
8
|
-
import { Trash2 as ee } from "../node_modules/lucide-react/dist/esm/icons/trash-2.js";
|
|
7
|
+
import { Save as ee } from "../node_modules/lucide-react/dist/esm/icons/save.js";
|
|
9
8
|
import { Users as te } from "../node_modules/lucide-react/dist/esm/icons/users.js";
|
|
10
|
-
import { buildCustomerCreatePath as ne, joinPath as
|
|
11
|
-
import { useMoveTheWheels as
|
|
12
|
-
import { useRoute as
|
|
13
|
-
import { useOrder as
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
9
|
+
import { buildCustomerCreatePath as ne, joinPath as o } from "../utils/navigation.js";
|
|
10
|
+
import { useMoveTheWheels as s } from "../providers/MoveTheWheelsProvider.js";
|
|
11
|
+
import { useRoute as c, useRoutes as l } from "../hooks/useRoutes.js";
|
|
12
|
+
import { useOrder as re, useOrders as ie } from "../hooks/useOrders.js";
|
|
13
|
+
import { useProducts as ae } from "../hooks/useInventory.js";
|
|
14
|
+
import { useCustomer as oe, useCustomers as se } from "../hooks/useCustomers.js";
|
|
15
|
+
import { Button as u, Card as d, CardContent as f, CardHeader as p, CardTitle as m, Input as h, Label as g, Select as ce, SelectContent as le, SelectItem as _, SelectTrigger as v, SelectValue as ue, Textarea as de } from "../components/ui.js";
|
|
16
16
|
import { EmptyState as fe, PageLayout as pe } from "../components/PageLayout.js";
|
|
17
17
|
import { FormError as me } from "../components/FormError.js";
|
|
18
18
|
import { EntityCombobox as he } from "../components/EntityCombobox.js";
|
|
19
19
|
import { CustomerPicker as ge } from "../components/CustomerPicker.js";
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
20
|
+
import { OrderItemRow as _e } from "../components/OrderItemRow.js";
|
|
21
|
+
import { useEffect as y, useState as b } from "react";
|
|
22
|
+
import { useParams as ve, useSearchParams as ye } from "react-router";
|
|
23
|
+
import { Fragment as be, jsx as x, jsxs as S } from "react/jsx-runtime";
|
|
24
|
+
import { EmphasisPanel as xe, NextSteps as Se, PagePurpose as Ce } from "@burdenoff/fe-libs/ui";
|
|
24
25
|
//#region src/movethewheels/pages/CreateOrderPage.tsx
|
|
25
|
-
function
|
|
26
|
+
function C(e) {
|
|
26
27
|
if (!e || Number.isNaN(e.getTime())) return "";
|
|
27
28
|
let t = (e) => String(e).padStart(2, "0");
|
|
28
29
|
return `${e.getFullYear()}-${t(e.getMonth() + 1)}-${t(e.getDate())}T${t(e.getHours())}:${t(e.getMinutes())}`;
|
|
29
30
|
}
|
|
30
|
-
function
|
|
31
|
-
let { orderId:
|
|
31
|
+
function w() {
|
|
32
|
+
let { orderId: w } = ve(), [T] = ye(), E = T.get("customerId")?.trim() || void 0, { basePath: D, navigate: O } = s(), { order: k } = re(w), { createOrder: A, updateOrder: we } = ie(), [Te, Ee] = b(""), { customers: j, createCustomer: M, isLoading: N, error: P, refetch: De } = se({
|
|
32
33
|
limit: 20,
|
|
33
|
-
filters: { search:
|
|
34
|
-
}), { customer: F } =
|
|
34
|
+
filters: { search: Te || void 0 }
|
|
35
|
+
}), { customer: F } = oe(E ?? k?.customerId), [Oe, ke] = b(""), { routes: I, isLoading: L, error: R, hasMore: Ae, totalRoutes: je } = l({
|
|
35
36
|
status: "planned",
|
|
36
37
|
search: Oe || void 0,
|
|
37
38
|
limit: 20
|
|
38
|
-
}), z
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
}), [z, Me] = b(""), { products: Ne, isLoading: Pe } = ae({
|
|
40
|
+
search: z || void 0,
|
|
41
|
+
limit: 20
|
|
42
|
+
}), B = !!w && !!k, [V, H] = b(null), [U, W] = b({
|
|
43
|
+
customerId: E || k?.customerId || "",
|
|
44
|
+
priority: k?.priority || "normal",
|
|
45
|
+
routeId: k?.routeId || "",
|
|
42
46
|
pickupAddress: {
|
|
43
|
-
street:
|
|
44
|
-
city:
|
|
45
|
-
state:
|
|
46
|
-
postalCode:
|
|
47
|
-
country:
|
|
47
|
+
street: k?.pickupAddress.street || "",
|
|
48
|
+
city: k?.pickupAddress.city || "",
|
|
49
|
+
state: k?.pickupAddress.state || "",
|
|
50
|
+
postalCode: k?.pickupAddress.postalCode || "",
|
|
51
|
+
country: k?.pickupAddress.country || "USA"
|
|
48
52
|
},
|
|
49
53
|
deliveryAddress: {
|
|
50
|
-
street:
|
|
51
|
-
city:
|
|
52
|
-
state:
|
|
53
|
-
postalCode:
|
|
54
|
-
country:
|
|
54
|
+
street: k?.deliveryAddress.street || "",
|
|
55
|
+
city: k?.deliveryAddress.city || "",
|
|
56
|
+
state: k?.deliveryAddress.state || "",
|
|
57
|
+
postalCode: k?.deliveryAddress.postalCode || "",
|
|
58
|
+
country: k?.deliveryAddress.country || "USA"
|
|
55
59
|
},
|
|
56
|
-
items:
|
|
57
|
-
scheduledDelivery:
|
|
58
|
-
specialInstructions:
|
|
59
|
-
}), { route:
|
|
60
|
-
|
|
61
|
-
F && (
|
|
60
|
+
items: k?.items || [],
|
|
61
|
+
scheduledDelivery: C(k?.scheduledDelivery),
|
|
62
|
+
specialInstructions: k?.specialInstructions || ""
|
|
63
|
+
}), { route: Fe, isLoading: Ie } = c(U.routeId || void 0), [G, K] = b(!1), [Le, q] = b(null);
|
|
64
|
+
y(() => {
|
|
65
|
+
F && (H((e) => e?.id === F.id ? e : F), W((e) => e.customerId ? e : {
|
|
62
66
|
...e,
|
|
63
67
|
customerId: F.id
|
|
64
68
|
}));
|
|
65
|
-
}, [F]),
|
|
66
|
-
|
|
67
|
-
customerId:
|
|
68
|
-
priority:
|
|
69
|
-
routeId:
|
|
69
|
+
}, [F]), y(() => {
|
|
70
|
+
k && W({
|
|
71
|
+
customerId: k.customerId || "",
|
|
72
|
+
priority: k.priority || "normal",
|
|
73
|
+
routeId: k.routeId || "",
|
|
70
74
|
pickupAddress: {
|
|
71
|
-
street:
|
|
72
|
-
city:
|
|
73
|
-
state:
|
|
74
|
-
postalCode:
|
|
75
|
-
country:
|
|
75
|
+
street: k.pickupAddress.street || "",
|
|
76
|
+
city: k.pickupAddress.city || "",
|
|
77
|
+
state: k.pickupAddress.state || "",
|
|
78
|
+
postalCode: k.pickupAddress.postalCode || "",
|
|
79
|
+
country: k.pickupAddress.country || "USA"
|
|
76
80
|
},
|
|
77
81
|
deliveryAddress: {
|
|
78
|
-
street:
|
|
79
|
-
city:
|
|
80
|
-
state:
|
|
81
|
-
postalCode:
|
|
82
|
-
country:
|
|
82
|
+
street: k.deliveryAddress.street || "",
|
|
83
|
+
city: k.deliveryAddress.city || "",
|
|
84
|
+
state: k.deliveryAddress.state || "",
|
|
85
|
+
postalCode: k.deliveryAddress.postalCode || "",
|
|
86
|
+
country: k.deliveryAddress.country || "USA"
|
|
83
87
|
},
|
|
84
|
-
items:
|
|
85
|
-
scheduledDelivery:
|
|
86
|
-
specialInstructions:
|
|
88
|
+
items: k.items || [],
|
|
89
|
+
scheduledDelivery: C(k.scheduledDelivery),
|
|
90
|
+
specialInstructions: k.specialInstructions || ""
|
|
87
91
|
});
|
|
88
|
-
}, [
|
|
89
|
-
let
|
|
90
|
-
if (
|
|
91
|
-
if (!
|
|
92
|
-
|
|
92
|
+
}, [k]);
|
|
93
|
+
let J = () => {
|
|
94
|
+
if (O) {
|
|
95
|
+
if (!B && E) {
|
|
96
|
+
O(o(D, `customers/${E}`));
|
|
93
97
|
return;
|
|
94
98
|
}
|
|
95
|
-
|
|
99
|
+
O(o(D, "orders"));
|
|
96
100
|
}
|
|
97
|
-
},
|
|
98
|
-
e.preventDefault(),
|
|
99
|
-
let t =
|
|
101
|
+
}, Y = () => V ?? j.find((e) => e.id === U.customerId), Re = () => !U.customerId || !Y() ? P ? "Customers could not be loaded. Retry or create a customer before continuing." : "Select a customer for this order." : U.pickupAddress.street.trim() ? U.deliveryAddress.street.trim() ? U.items.length === 0 ? "Add at least one item to the order." : U.scheduledDelivery && Number.isNaN(Date.parse(U.scheduledDelivery)) ? "Enter a valid scheduled delivery date and time." : null : "Enter a delivery street address." : "Enter a pickup street address.", X = async (e) => {
|
|
102
|
+
e.preventDefault(), q(null);
|
|
103
|
+
let t = Re();
|
|
100
104
|
if (t) {
|
|
101
|
-
|
|
105
|
+
q(t);
|
|
102
106
|
return;
|
|
103
107
|
}
|
|
104
|
-
let n =
|
|
108
|
+
let n = Y();
|
|
105
109
|
if (!n) {
|
|
106
|
-
|
|
110
|
+
q(P ? "Customers could not be loaded. Retry or create a customer before continuing." : "Select a customer for this order.");
|
|
107
111
|
return;
|
|
108
112
|
}
|
|
109
|
-
|
|
113
|
+
K(!0);
|
|
110
114
|
try {
|
|
111
115
|
let e = {
|
|
112
|
-
customerId:
|
|
116
|
+
customerId: U.customerId,
|
|
113
117
|
customerInfo: {
|
|
114
118
|
name: n.name,
|
|
115
119
|
email: n.email,
|
|
116
120
|
phone: n.phone,
|
|
117
121
|
company: n.company
|
|
118
122
|
},
|
|
119
|
-
priority:
|
|
120
|
-
pickupAddress:
|
|
121
|
-
deliveryAddress:
|
|
122
|
-
items:
|
|
123
|
-
scheduledDelivery:
|
|
124
|
-
routeId:
|
|
125
|
-
specialInstructions:
|
|
123
|
+
priority: U.priority,
|
|
124
|
+
pickupAddress: U.pickupAddress,
|
|
125
|
+
deliveryAddress: U.deliveryAddress,
|
|
126
|
+
items: U.items,
|
|
127
|
+
scheduledDelivery: U.scheduledDelivery ? new Date(U.scheduledDelivery) : null,
|
|
128
|
+
routeId: U.routeId || void 0,
|
|
129
|
+
specialInstructions: U.specialInstructions
|
|
126
130
|
};
|
|
127
|
-
|
|
131
|
+
B && w ? (await we(w, e), O && O(o(D, `orders/${w}`))) : (await A(e), J());
|
|
128
132
|
} catch (e) {
|
|
129
|
-
|
|
133
|
+
q(e instanceof Error ? e.message : B ? "Could not update the order. Please try again." : "Could not create the order. Please try again.");
|
|
130
134
|
} finally {
|
|
131
|
-
|
|
135
|
+
K(!1);
|
|
132
136
|
}
|
|
133
|
-
},
|
|
134
|
-
|
|
137
|
+
}, Z = (e, t) => {
|
|
138
|
+
W((n) => ({
|
|
135
139
|
...n,
|
|
136
140
|
[e]: t
|
|
137
141
|
}));
|
|
138
|
-
},
|
|
139
|
-
|
|
142
|
+
}, Q = (e, t, n) => {
|
|
143
|
+
W((r) => ({
|
|
140
144
|
...r,
|
|
141
145
|
[e]: {
|
|
142
146
|
...r[e],
|
|
143
147
|
[t]: n
|
|
144
148
|
}
|
|
145
149
|
}));
|
|
146
|
-
},
|
|
150
|
+
}, ze = (e) => {
|
|
147
151
|
if (!e) {
|
|
148
|
-
|
|
152
|
+
Z("routeId", "");
|
|
149
153
|
return;
|
|
150
154
|
}
|
|
151
155
|
let t = I.find((t) => t.id === e);
|
|
@@ -157,14 +161,14 @@ function T() {
|
|
|
157
161
|
postalCode: e.postalCode.trim() ? e.postalCode : t.postalCode,
|
|
158
162
|
country: e.country.trim() ? e.country : t.country
|
|
159
163
|
} : e;
|
|
160
|
-
|
|
164
|
+
W((e) => ({
|
|
161
165
|
...e,
|
|
162
166
|
routeId: t.id,
|
|
163
167
|
pickupAddress: i(e.pickupAddress, n),
|
|
164
168
|
deliveryAddress: i(e.deliveryAddress, r)
|
|
165
169
|
}));
|
|
166
|
-
},
|
|
167
|
-
|
|
170
|
+
}, $ = () => {
|
|
171
|
+
W((e) => ({
|
|
168
172
|
...e,
|
|
169
173
|
items: [...e.items, {
|
|
170
174
|
id: `item-${Date.now()}`,
|
|
@@ -182,13 +186,13 @@ function T() {
|
|
|
182
186
|
sku: ""
|
|
183
187
|
}]
|
|
184
188
|
}));
|
|
185
|
-
},
|
|
186
|
-
|
|
189
|
+
}, Be = (e) => {
|
|
190
|
+
W((t) => ({
|
|
187
191
|
...t,
|
|
188
192
|
items: t.items.filter((t, n) => n !== e)
|
|
189
193
|
}));
|
|
190
|
-
},
|
|
191
|
-
|
|
194
|
+
}, Ve = (e, t, n) => {
|
|
195
|
+
W((r) => ({
|
|
192
196
|
...r,
|
|
193
197
|
items: r.items.map((r, i) => i === e ? {
|
|
194
198
|
...r,
|
|
@@ -196,111 +200,111 @@ function T() {
|
|
|
196
200
|
} : r)
|
|
197
201
|
}));
|
|
198
202
|
};
|
|
199
|
-
return /* @__PURE__ */
|
|
200
|
-
title:
|
|
201
|
-
subtitle:
|
|
202
|
-
icon: /* @__PURE__ */
|
|
203
|
-
headerActions: /* @__PURE__ */
|
|
203
|
+
return /* @__PURE__ */ S(pe, {
|
|
204
|
+
title: B ? "Edit Order" : "Create Order",
|
|
205
|
+
subtitle: B ? `Editing ${k?.orderNumber}` : "Create a new delivery order",
|
|
206
|
+
icon: /* @__PURE__ */ x(r, { size: 24 }),
|
|
207
|
+
headerActions: /* @__PURE__ */ S("div", {
|
|
204
208
|
className: "flex gap-2",
|
|
205
|
-
children: [/* @__PURE__ */
|
|
209
|
+
children: [/* @__PURE__ */ S(u, {
|
|
206
210
|
variant: "outline",
|
|
207
|
-
onClick:
|
|
208
|
-
children: [/* @__PURE__ */
|
|
209
|
-
}), /* @__PURE__ */
|
|
210
|
-
onClick:
|
|
211
|
-
disabled:
|
|
212
|
-
children: [/* @__PURE__ */
|
|
211
|
+
onClick: J,
|
|
212
|
+
children: [/* @__PURE__ */ x(e, { size: 16 }), "Cancel"]
|
|
213
|
+
}), /* @__PURE__ */ S(u, {
|
|
214
|
+
onClick: X,
|
|
215
|
+
disabled: G || N,
|
|
216
|
+
children: [/* @__PURE__ */ x(ee, { size: 16 }), G ? "Saving..." : B ? "Save Changes" : "Create Order"]
|
|
213
217
|
})]
|
|
214
218
|
}),
|
|
215
219
|
children: [
|
|
216
|
-
/* @__PURE__ */
|
|
217
|
-
icon: /* @__PURE__ */
|
|
218
|
-
children:
|
|
220
|
+
/* @__PURE__ */ x(Ce, {
|
|
221
|
+
icon: /* @__PURE__ */ x(r, { size: 18 }),
|
|
222
|
+
children: B ? "Update this delivery before it goes out — change the customer, addresses, items or handling notes, then save." : "Book a new delivery in a few steps: choose who it’s for, set pickup and drop-off addresses, list what’s being shipped, and add any handling notes. Once created it joins the orders pipeline ready to assign and track."
|
|
219
223
|
}),
|
|
220
|
-
/* @__PURE__ */
|
|
224
|
+
/* @__PURE__ */ x(Se, {
|
|
221
225
|
storageKey: "movethewheels-create-order",
|
|
222
226
|
steps: [
|
|
223
227
|
{
|
|
224
228
|
id: "customer",
|
|
225
229
|
label: "Pick the customer first",
|
|
226
230
|
description: "Their saved contact details auto-fill the order.",
|
|
227
|
-
icon: /* @__PURE__ */
|
|
228
|
-
done: !!
|
|
231
|
+
icon: /* @__PURE__ */ x(te, { size: 16 }),
|
|
232
|
+
done: !!U.customerId,
|
|
229
233
|
onClick: () => document.getElementById("customer")?.focus()
|
|
230
234
|
},
|
|
231
235
|
{
|
|
232
236
|
id: "addresses",
|
|
233
237
|
label: "Set pickup & delivery addresses",
|
|
234
238
|
description: "Where the shipment is collected and dropped off.",
|
|
235
|
-
icon: /* @__PURE__ */
|
|
236
|
-
done: !!
|
|
239
|
+
icon: /* @__PURE__ */ x(n, { size: 16 }),
|
|
240
|
+
done: !!U.pickupAddress.street && !!U.deliveryAddress.street,
|
|
237
241
|
onClick: () => document.getElementById("pickupStreet")?.focus()
|
|
238
242
|
},
|
|
239
243
|
{
|
|
240
244
|
id: "items",
|
|
241
245
|
label: "Add the items to ship",
|
|
242
246
|
description: "List each product with its quantity and value.",
|
|
243
|
-
icon: /* @__PURE__ */
|
|
244
|
-
done:
|
|
245
|
-
onClick:
|
|
247
|
+
icon: /* @__PURE__ */ x(r, { size: 16 }),
|
|
248
|
+
done: U.items.length > 0,
|
|
249
|
+
onClick: $
|
|
246
250
|
}
|
|
247
251
|
]
|
|
248
252
|
}),
|
|
249
|
-
/* @__PURE__ */
|
|
250
|
-
onSubmit:
|
|
253
|
+
/* @__PURE__ */ S("form", {
|
|
254
|
+
onSubmit: X,
|
|
251
255
|
className: "space-y-6",
|
|
252
256
|
children: [
|
|
253
|
-
/* @__PURE__ */
|
|
254
|
-
/* @__PURE__ */
|
|
257
|
+
/* @__PURE__ */ x(me, { message: Le }),
|
|
258
|
+
/* @__PURE__ */ S(xe, { children: [/* @__PURE__ */ x("h2", {
|
|
255
259
|
className: "text-base font-semibold text-text-primary mb-4",
|
|
256
260
|
children: "Order Details"
|
|
257
|
-
}), /* @__PURE__ */
|
|
261
|
+
}), /* @__PURE__ */ x("div", {
|
|
258
262
|
className: "space-y-4",
|
|
259
|
-
children: /* @__PURE__ */
|
|
263
|
+
children: /* @__PURE__ */ S("div", {
|
|
260
264
|
className: "grid grid-cols-1 md:grid-cols-2 gap-4",
|
|
261
|
-
children: [/* @__PURE__ */
|
|
262
|
-
customers:
|
|
263
|
-
value:
|
|
265
|
+
children: [/* @__PURE__ */ x(ge, {
|
|
266
|
+
customers: j,
|
|
267
|
+
value: U.customerId,
|
|
264
268
|
onValueChange: (e) => {
|
|
265
|
-
e && (
|
|
269
|
+
e && (Z("customerId", e), H(j.find((t) => t.id === e) ?? (V?.id === e ? V : null)), q(null));
|
|
266
270
|
},
|
|
267
271
|
onCreated: (e) => {
|
|
268
|
-
|
|
272
|
+
H(e), Z("customerId", e.id), q(null);
|
|
269
273
|
},
|
|
270
|
-
createCustomer:
|
|
274
|
+
createCustomer: M,
|
|
271
275
|
isLoading: N,
|
|
272
276
|
error: P,
|
|
273
277
|
onRetry: De,
|
|
274
278
|
onOpenCustomers: () => {
|
|
275
|
-
|
|
279
|
+
O && O(o(D, ne(B && w ? `orders/${w}/edit` : "orders/new")));
|
|
276
280
|
},
|
|
277
|
-
selectedCustomer:
|
|
278
|
-
onSearchQueryChange:
|
|
279
|
-
}), /* @__PURE__ */
|
|
281
|
+
selectedCustomer: V,
|
|
282
|
+
onSearchQueryChange: Ee
|
|
283
|
+
}), /* @__PURE__ */ S("div", {
|
|
280
284
|
className: "space-y-2",
|
|
281
|
-
children: [/* @__PURE__ */
|
|
285
|
+
children: [/* @__PURE__ */ x(g, {
|
|
282
286
|
htmlFor: "priority",
|
|
283
287
|
children: "Priority"
|
|
284
|
-
}), /* @__PURE__ */
|
|
285
|
-
value:
|
|
286
|
-
onValueChange: (e) =>
|
|
287
|
-
children: [/* @__PURE__ */
|
|
288
|
+
}), /* @__PURE__ */ S(ce, {
|
|
289
|
+
value: U.priority,
|
|
290
|
+
onValueChange: (e) => Z("priority", e),
|
|
291
|
+
children: [/* @__PURE__ */ x(v, {
|
|
288
292
|
id: "priority",
|
|
289
|
-
children: /* @__PURE__ */
|
|
290
|
-
}), /* @__PURE__ */
|
|
291
|
-
/* @__PURE__ */
|
|
293
|
+
children: /* @__PURE__ */ x(ue, {})
|
|
294
|
+
}), /* @__PURE__ */ S(le, { children: [
|
|
295
|
+
/* @__PURE__ */ x(_, {
|
|
292
296
|
value: "low",
|
|
293
297
|
children: "Low"
|
|
294
298
|
}),
|
|
295
|
-
/* @__PURE__ */
|
|
299
|
+
/* @__PURE__ */ x(_, {
|
|
296
300
|
value: "normal",
|
|
297
301
|
children: "Normal"
|
|
298
302
|
}),
|
|
299
|
-
/* @__PURE__ */
|
|
303
|
+
/* @__PURE__ */ x(_, {
|
|
300
304
|
value: "high",
|
|
301
305
|
children: "High"
|
|
302
306
|
}),
|
|
303
|
-
/* @__PURE__ */
|
|
307
|
+
/* @__PURE__ */ x(_, {
|
|
304
308
|
value: "urgent",
|
|
305
309
|
children: "Urgent"
|
|
306
310
|
})
|
|
@@ -309,27 +313,27 @@ function T() {
|
|
|
309
313
|
})]
|
|
310
314
|
})
|
|
311
315
|
})] }),
|
|
312
|
-
/* @__PURE__ */
|
|
316
|
+
/* @__PURE__ */ S(d, { children: [/* @__PURE__ */ x(p, { children: /* @__PURE__ */ S(m, {
|
|
313
317
|
className: "flex items-center gap-2",
|
|
314
|
-
children: [/* @__PURE__ */
|
|
315
|
-
}) }), /* @__PURE__ */
|
|
318
|
+
children: [/* @__PURE__ */ x(a, { size: 18 }), "Saved Route (Optional)"]
|
|
319
|
+
}) }), /* @__PURE__ */ S(f, {
|
|
316
320
|
className: "space-y-2",
|
|
317
|
-
children: [/* @__PURE__ */
|
|
321
|
+
children: [/* @__PURE__ */ x(he, {
|
|
318
322
|
id: "saved-route",
|
|
319
|
-
value:
|
|
320
|
-
onChange:
|
|
323
|
+
value: U.routeId,
|
|
324
|
+
onChange: ze,
|
|
321
325
|
options: I,
|
|
322
326
|
loading: L,
|
|
323
327
|
getId: (e) => e.id,
|
|
324
328
|
getLabel: (e) => `${e.name} · ${e.stops.length} stops`,
|
|
325
|
-
renderOption: (e) => /* @__PURE__ */
|
|
329
|
+
renderOption: (e) => /* @__PURE__ */ S(be, { children: [
|
|
326
330
|
e.name,
|
|
327
331
|
" · ",
|
|
328
332
|
e.stops.length,
|
|
329
333
|
" stops"
|
|
330
334
|
] }),
|
|
331
|
-
selectedOption:
|
|
332
|
-
selectedLoading:
|
|
335
|
+
selectedOption: Fe,
|
|
336
|
+
selectedLoading: Ie,
|
|
333
337
|
placeholder: "Choose a saved route",
|
|
334
338
|
searchPlaceholder: "Search saved routes",
|
|
335
339
|
emptyText: "No saved routes found.",
|
|
@@ -338,144 +342,144 @@ function T() {
|
|
|
338
342
|
onSearchChange: ke,
|
|
339
343
|
hasMore: Ae,
|
|
340
344
|
totalCount: je
|
|
341
|
-
}), /* @__PURE__ */
|
|
345
|
+
}), /* @__PURE__ */ x("p", {
|
|
342
346
|
className: "text-xs text-text-secondary",
|
|
343
347
|
children: L ? "Loading planned routes…" : R ? "Saved routes are unavailable; enter pickup and delivery addresses manually." : "A saved route fills any blank address fields. You can always edit both addresses below."
|
|
344
348
|
})]
|
|
345
349
|
})] }),
|
|
346
|
-
/* @__PURE__ */
|
|
350
|
+
/* @__PURE__ */ S("div", {
|
|
347
351
|
className: "grid grid-cols-1 md:grid-cols-2 gap-6",
|
|
348
|
-
children: [/* @__PURE__ */
|
|
352
|
+
children: [/* @__PURE__ */ S(d, { children: [/* @__PURE__ */ x(p, { children: /* @__PURE__ */ x(m, { children: "Pickup Address" }) }), /* @__PURE__ */ S(f, {
|
|
349
353
|
className: "space-y-4",
|
|
350
354
|
children: [
|
|
351
|
-
/* @__PURE__ */
|
|
355
|
+
/* @__PURE__ */ S("div", {
|
|
352
356
|
className: "space-y-2",
|
|
353
|
-
children: [/* @__PURE__ */
|
|
357
|
+
children: [/* @__PURE__ */ x(g, {
|
|
354
358
|
htmlFor: "pickupStreet",
|
|
355
359
|
children: "Street Address"
|
|
356
|
-
}), /* @__PURE__ */
|
|
360
|
+
}), /* @__PURE__ */ x(h, {
|
|
357
361
|
id: "pickupStreet",
|
|
358
|
-
value:
|
|
359
|
-
onChange: (e) =>
|
|
362
|
+
value: U.pickupAddress.street,
|
|
363
|
+
onChange: (e) => Q("pickupAddress", "street", e.target.value),
|
|
360
364
|
placeholder: "123 Main St"
|
|
361
365
|
})]
|
|
362
366
|
}),
|
|
363
|
-
/* @__PURE__ */
|
|
367
|
+
/* @__PURE__ */ S("div", {
|
|
364
368
|
className: "grid grid-cols-1 sm:grid-cols-2 gap-4",
|
|
365
|
-
children: [/* @__PURE__ */
|
|
369
|
+
children: [/* @__PURE__ */ S("div", {
|
|
366
370
|
className: "space-y-2",
|
|
367
|
-
children: [/* @__PURE__ */
|
|
371
|
+
children: [/* @__PURE__ */ x(g, {
|
|
368
372
|
htmlFor: "pickupCity",
|
|
369
373
|
children: "City"
|
|
370
|
-
}), /* @__PURE__ */
|
|
374
|
+
}), /* @__PURE__ */ x(h, {
|
|
371
375
|
id: "pickupCity",
|
|
372
|
-
value:
|
|
373
|
-
onChange: (e) =>
|
|
376
|
+
value: U.pickupAddress.city,
|
|
377
|
+
onChange: (e) => Q("pickupAddress", "city", e.target.value),
|
|
374
378
|
placeholder: "San Francisco"
|
|
375
379
|
})]
|
|
376
|
-
}), /* @__PURE__ */
|
|
380
|
+
}), /* @__PURE__ */ S("div", {
|
|
377
381
|
className: "space-y-2",
|
|
378
|
-
children: [/* @__PURE__ */
|
|
382
|
+
children: [/* @__PURE__ */ x(g, {
|
|
379
383
|
htmlFor: "pickupState",
|
|
380
384
|
children: "State"
|
|
381
|
-
}), /* @__PURE__ */
|
|
385
|
+
}), /* @__PURE__ */ x(h, {
|
|
382
386
|
id: "pickupState",
|
|
383
|
-
value:
|
|
384
|
-
onChange: (e) =>
|
|
387
|
+
value: U.pickupAddress.state,
|
|
388
|
+
onChange: (e) => Q("pickupAddress", "state", e.target.value),
|
|
385
389
|
placeholder: "CA"
|
|
386
390
|
})]
|
|
387
391
|
})]
|
|
388
392
|
}),
|
|
389
|
-
/* @__PURE__ */
|
|
393
|
+
/* @__PURE__ */ S("div", {
|
|
390
394
|
className: "grid grid-cols-1 sm:grid-cols-2 gap-4",
|
|
391
|
-
children: [/* @__PURE__ */
|
|
395
|
+
children: [/* @__PURE__ */ S("div", {
|
|
392
396
|
className: "space-y-2",
|
|
393
|
-
children: [/* @__PURE__ */
|
|
397
|
+
children: [/* @__PURE__ */ x(g, {
|
|
394
398
|
htmlFor: "pickupPostal",
|
|
395
399
|
children: "Postal Code"
|
|
396
|
-
}), /* @__PURE__ */
|
|
400
|
+
}), /* @__PURE__ */ x(h, {
|
|
397
401
|
id: "pickupPostal",
|
|
398
|
-
value:
|
|
399
|
-
onChange: (e) =>
|
|
402
|
+
value: U.pickupAddress.postalCode,
|
|
403
|
+
onChange: (e) => Q("pickupAddress", "postalCode", e.target.value),
|
|
400
404
|
placeholder: "94103"
|
|
401
405
|
})]
|
|
402
|
-
}), /* @__PURE__ */
|
|
406
|
+
}), /* @__PURE__ */ S("div", {
|
|
403
407
|
className: "space-y-2",
|
|
404
|
-
children: [/* @__PURE__ */
|
|
408
|
+
children: [/* @__PURE__ */ x(g, {
|
|
405
409
|
htmlFor: "pickupCountry",
|
|
406
410
|
children: "Country"
|
|
407
|
-
}), /* @__PURE__ */
|
|
411
|
+
}), /* @__PURE__ */ x(h, {
|
|
408
412
|
id: "pickupCountry",
|
|
409
|
-
value:
|
|
410
|
-
onChange: (e) =>
|
|
413
|
+
value: U.pickupAddress.country,
|
|
414
|
+
onChange: (e) => Q("pickupAddress", "country", e.target.value),
|
|
411
415
|
placeholder: "USA"
|
|
412
416
|
})]
|
|
413
417
|
})]
|
|
414
418
|
})
|
|
415
419
|
]
|
|
416
|
-
})] }), /* @__PURE__ */
|
|
420
|
+
})] }), /* @__PURE__ */ S(d, { children: [/* @__PURE__ */ x(p, { children: /* @__PURE__ */ x(m, { children: "Delivery Address" }) }), /* @__PURE__ */ S(f, {
|
|
417
421
|
className: "space-y-4",
|
|
418
422
|
children: [
|
|
419
|
-
/* @__PURE__ */
|
|
423
|
+
/* @__PURE__ */ S("div", {
|
|
420
424
|
className: "space-y-2",
|
|
421
|
-
children: [/* @__PURE__ */
|
|
425
|
+
children: [/* @__PURE__ */ x(g, {
|
|
422
426
|
htmlFor: "deliveryStreet",
|
|
423
427
|
children: "Street Address"
|
|
424
|
-
}), /* @__PURE__ */
|
|
428
|
+
}), /* @__PURE__ */ x(h, {
|
|
425
429
|
id: "deliveryStreet",
|
|
426
|
-
value:
|
|
427
|
-
onChange: (e) =>
|
|
430
|
+
value: U.deliveryAddress.street,
|
|
431
|
+
onChange: (e) => Q("deliveryAddress", "street", e.target.value),
|
|
428
432
|
placeholder: "456 Oak Ave"
|
|
429
433
|
})]
|
|
430
434
|
}),
|
|
431
|
-
/* @__PURE__ */
|
|
435
|
+
/* @__PURE__ */ S("div", {
|
|
432
436
|
className: "grid grid-cols-1 sm:grid-cols-2 gap-4",
|
|
433
|
-
children: [/* @__PURE__ */
|
|
437
|
+
children: [/* @__PURE__ */ S("div", {
|
|
434
438
|
className: "space-y-2",
|
|
435
|
-
children: [/* @__PURE__ */
|
|
439
|
+
children: [/* @__PURE__ */ x(g, {
|
|
436
440
|
htmlFor: "deliveryCity",
|
|
437
441
|
children: "City"
|
|
438
|
-
}), /* @__PURE__ */
|
|
442
|
+
}), /* @__PURE__ */ x(h, {
|
|
439
443
|
id: "deliveryCity",
|
|
440
|
-
value:
|
|
441
|
-
onChange: (e) =>
|
|
444
|
+
value: U.deliveryAddress.city,
|
|
445
|
+
onChange: (e) => Q("deliveryAddress", "city", e.target.value),
|
|
442
446
|
placeholder: "Los Angeles"
|
|
443
447
|
})]
|
|
444
|
-
}), /* @__PURE__ */
|
|
448
|
+
}), /* @__PURE__ */ S("div", {
|
|
445
449
|
className: "space-y-2",
|
|
446
|
-
children: [/* @__PURE__ */
|
|
450
|
+
children: [/* @__PURE__ */ x(g, {
|
|
447
451
|
htmlFor: "deliveryState",
|
|
448
452
|
children: "State"
|
|
449
|
-
}), /* @__PURE__ */
|
|
453
|
+
}), /* @__PURE__ */ x(h, {
|
|
450
454
|
id: "deliveryState",
|
|
451
|
-
value:
|
|
452
|
-
onChange: (e) =>
|
|
455
|
+
value: U.deliveryAddress.state,
|
|
456
|
+
onChange: (e) => Q("deliveryAddress", "state", e.target.value),
|
|
453
457
|
placeholder: "CA"
|
|
454
458
|
})]
|
|
455
459
|
})]
|
|
456
460
|
}),
|
|
457
|
-
/* @__PURE__ */
|
|
461
|
+
/* @__PURE__ */ S("div", {
|
|
458
462
|
className: "grid grid-cols-1 sm:grid-cols-2 gap-4",
|
|
459
|
-
children: [/* @__PURE__ */
|
|
463
|
+
children: [/* @__PURE__ */ S("div", {
|
|
460
464
|
className: "space-y-2",
|
|
461
|
-
children: [/* @__PURE__ */
|
|
465
|
+
children: [/* @__PURE__ */ x(g, {
|
|
462
466
|
htmlFor: "deliveryPostal",
|
|
463
467
|
children: "Postal Code"
|
|
464
|
-
}), /* @__PURE__ */
|
|
468
|
+
}), /* @__PURE__ */ x(h, {
|
|
465
469
|
id: "deliveryPostal",
|
|
466
|
-
value:
|
|
467
|
-
onChange: (e) =>
|
|
470
|
+
value: U.deliveryAddress.postalCode,
|
|
471
|
+
onChange: (e) => Q("deliveryAddress", "postalCode", e.target.value),
|
|
468
472
|
placeholder: "90210"
|
|
469
473
|
})]
|
|
470
|
-
}), /* @__PURE__ */
|
|
474
|
+
}), /* @__PURE__ */ S("div", {
|
|
471
475
|
className: "space-y-2",
|
|
472
|
-
children: [/* @__PURE__ */
|
|
476
|
+
children: [/* @__PURE__ */ x(g, {
|
|
473
477
|
htmlFor: "deliveryCountry",
|
|
474
478
|
children: "Country"
|
|
475
|
-
}), /* @__PURE__ */
|
|
479
|
+
}), /* @__PURE__ */ x(h, {
|
|
476
480
|
id: "deliveryCountry",
|
|
477
|
-
value:
|
|
478
|
-
onChange: (e) =>
|
|
481
|
+
value: U.deliveryAddress.country,
|
|
482
|
+
onChange: (e) => Q("deliveryAddress", "country", e.target.value),
|
|
479
483
|
placeholder: "USA"
|
|
480
484
|
})]
|
|
481
485
|
})]
|
|
@@ -483,125 +487,91 @@ function T() {
|
|
|
483
487
|
]
|
|
484
488
|
})] })]
|
|
485
489
|
}),
|
|
486
|
-
/* @__PURE__ */
|
|
490
|
+
/* @__PURE__ */ S(d, { children: [/* @__PURE__ */ S(p, {
|
|
487
491
|
className: "flex flex-row items-center justify-between",
|
|
488
|
-
children: [/* @__PURE__ */
|
|
492
|
+
children: [/* @__PURE__ */ x(m, { children: "Order Items" }), /* @__PURE__ */ S(u, {
|
|
489
493
|
type: "button",
|
|
490
494
|
variant: "outline",
|
|
491
495
|
size: "sm",
|
|
492
|
-
onClick:
|
|
493
|
-
children: [/* @__PURE__ */
|
|
496
|
+
onClick: $,
|
|
497
|
+
children: [/* @__PURE__ */ x(i, { size: 16 }), "Add Item"]
|
|
494
498
|
})]
|
|
495
|
-
}), /* @__PURE__ */
|
|
499
|
+
}), /* @__PURE__ */ x(f, { children: U.items.length === 0 ? /* @__PURE__ */ x(fe, {
|
|
496
500
|
illustration: "empty-files",
|
|
497
501
|
title: "No items added yet",
|
|
498
502
|
description: "Add the products this order will ship — each with a name, SKU, quantity and value.",
|
|
499
|
-
action: /* @__PURE__ */
|
|
503
|
+
action: /* @__PURE__ */ S(u, {
|
|
500
504
|
type: "button",
|
|
501
505
|
variant: "outline",
|
|
502
|
-
onClick:
|
|
503
|
-
children: [/* @__PURE__ */
|
|
506
|
+
onClick: $,
|
|
507
|
+
children: [/* @__PURE__ */ x(i, {
|
|
504
508
|
size: 16,
|
|
505
509
|
className: "mr-1.5"
|
|
506
510
|
}), "Add your first item"]
|
|
507
511
|
})
|
|
508
512
|
}) : /* @__PURE__ */ S("div", {
|
|
509
513
|
className: "space-y-4",
|
|
510
|
-
children:
|
|
511
|
-
className: "
|
|
514
|
+
children: [/* @__PURE__ */ S("div", {
|
|
515
|
+
className: "space-y-2",
|
|
512
516
|
children: [
|
|
513
|
-
/* @__PURE__ */
|
|
514
|
-
|
|
515
|
-
children:
|
|
516
|
-
value: e.name,
|
|
517
|
-
onChange: (e) => $(t, "name", e.target.value),
|
|
518
|
-
placeholder: "Product name"
|
|
519
|
-
})]
|
|
520
|
-
}),
|
|
521
|
-
/* @__PURE__ */ C("div", {
|
|
522
|
-
className: "space-y-2",
|
|
523
|
-
children: [/* @__PURE__ */ S(_, { children: "SKU" }), /* @__PURE__ */ S(g, {
|
|
524
|
-
value: e.sku,
|
|
525
|
-
onChange: (e) => $(t, "sku", e.target.value),
|
|
526
|
-
placeholder: "SKU-001"
|
|
527
|
-
})]
|
|
517
|
+
/* @__PURE__ */ x(g, {
|
|
518
|
+
htmlFor: "item-product-search",
|
|
519
|
+
children: "Search product catalog"
|
|
528
520
|
}),
|
|
529
|
-
/* @__PURE__ */
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
value: e.quantity,
|
|
535
|
-
onChange: (e) => $(t, "quantity", parseInt(e.target.value) || 1)
|
|
536
|
-
})]
|
|
537
|
-
}),
|
|
538
|
-
/* @__PURE__ */ C("div", {
|
|
539
|
-
className: "space-y-2",
|
|
540
|
-
children: [/* @__PURE__ */ S(_, { children: "Weight (kg)" }), /* @__PURE__ */ S(g, {
|
|
541
|
-
type: "number",
|
|
542
|
-
min: "0",
|
|
543
|
-
step: "0.1",
|
|
544
|
-
value: e.weight,
|
|
545
|
-
onChange: (e) => $(t, "weight", parseFloat(e.target.value) || 0)
|
|
546
|
-
})]
|
|
547
|
-
}),
|
|
548
|
-
/* @__PURE__ */ C("div", {
|
|
549
|
-
className: "space-y-2",
|
|
550
|
-
children: [/* @__PURE__ */ S(_, { children: "Value ($)" }), /* @__PURE__ */ S(g, {
|
|
551
|
-
type: "number",
|
|
552
|
-
min: "0",
|
|
553
|
-
step: "0.01",
|
|
554
|
-
value: e.value,
|
|
555
|
-
onChange: (e) => $(t, "value", parseFloat(e.target.value) || 0)
|
|
556
|
-
})]
|
|
521
|
+
/* @__PURE__ */ x(h, {
|
|
522
|
+
id: "item-product-search",
|
|
523
|
+
value: z,
|
|
524
|
+
onChange: (e) => Me(e.target.value),
|
|
525
|
+
placeholder: "Search by name or SKU"
|
|
557
526
|
}),
|
|
558
|
-
/* @__PURE__ */
|
|
559
|
-
className: "
|
|
560
|
-
children:
|
|
561
|
-
type: "button",
|
|
562
|
-
variant: "ghost",
|
|
563
|
-
size: "icon",
|
|
564
|
-
className: "text-status-error-text",
|
|
565
|
-
onClick: () => Le(t),
|
|
566
|
-
children: /* @__PURE__ */ S(ee, { size: 16 })
|
|
567
|
-
})
|
|
527
|
+
/* @__PURE__ */ x("p", {
|
|
528
|
+
className: "text-xs text-text-secondary",
|
|
529
|
+
children: "Narrows the \"Catalog item\" picker below for every row. Picking a catalog item fills in name, SKU, weight, and value — you can still edit them, or skip the picker and enter details manually."
|
|
568
530
|
})
|
|
569
531
|
]
|
|
570
|
-
})
|
|
532
|
+
}), U.items.map((e, t) => /* @__PURE__ */ x(_e, {
|
|
533
|
+
item: e,
|
|
534
|
+
index: t,
|
|
535
|
+
isFirst: t === 0,
|
|
536
|
+
products: Ne,
|
|
537
|
+
productsLoading: Pe,
|
|
538
|
+
onUpdate: Ve,
|
|
539
|
+
onRemove: Be
|
|
540
|
+
}, e.id))]
|
|
571
541
|
}) })] }),
|
|
572
|
-
/* @__PURE__ */
|
|
542
|
+
/* @__PURE__ */ S(d, { children: [/* @__PURE__ */ x(p, { children: /* @__PURE__ */ x(m, { children: "Scheduling & Handling" }) }), /* @__PURE__ */ x(f, { children: /* @__PURE__ */ S("div", {
|
|
573
543
|
className: "grid grid-cols-1 md:grid-cols-3 gap-6",
|
|
574
|
-
children: [/* @__PURE__ */
|
|
544
|
+
children: [/* @__PURE__ */ S("div", {
|
|
575
545
|
className: "space-y-2",
|
|
576
546
|
children: [
|
|
577
|
-
/* @__PURE__ */
|
|
547
|
+
/* @__PURE__ */ S(g, {
|
|
578
548
|
htmlFor: "scheduledDelivery",
|
|
579
549
|
className: "flex items-center gap-1.5",
|
|
580
|
-
children: [/* @__PURE__ */
|
|
550
|
+
children: [/* @__PURE__ */ x(t, {
|
|
581
551
|
size: 14,
|
|
582
552
|
className: "text-text-secondary"
|
|
583
553
|
}), "Scheduled Delivery"]
|
|
584
554
|
}),
|
|
585
|
-
/* @__PURE__ */
|
|
555
|
+
/* @__PURE__ */ x(h, {
|
|
586
556
|
id: "scheduledDelivery",
|
|
587
557
|
type: "datetime-local",
|
|
588
|
-
value:
|
|
589
|
-
onChange: (e) =>
|
|
558
|
+
value: U.scheduledDelivery,
|
|
559
|
+
onChange: (e) => Z("scheduledDelivery", e.target.value)
|
|
590
560
|
}),
|
|
591
|
-
/* @__PURE__ */
|
|
561
|
+
/* @__PURE__ */ x("p", {
|
|
592
562
|
className: "text-xs text-text-secondary",
|
|
593
563
|
children: "The delivery window promised to the customer. Leave blank to dispatch as soon as a driver is free."
|
|
594
564
|
})
|
|
595
565
|
]
|
|
596
|
-
}), /* @__PURE__ */
|
|
566
|
+
}), /* @__PURE__ */ S("div", {
|
|
597
567
|
className: "md:col-span-2 space-y-2",
|
|
598
|
-
children: [/* @__PURE__ */
|
|
568
|
+
children: [/* @__PURE__ */ x(g, {
|
|
599
569
|
htmlFor: "specialInstructions",
|
|
600
570
|
children: "Special Instructions"
|
|
601
|
-
}), /* @__PURE__ */
|
|
571
|
+
}), /* @__PURE__ */ x(de, {
|
|
602
572
|
id: "specialInstructions",
|
|
603
|
-
value:
|
|
604
|
-
onChange: (e) =>
|
|
573
|
+
value: U.specialInstructions,
|
|
574
|
+
onChange: (e) => Z("specialInstructions", e.target.value),
|
|
605
575
|
placeholder: "Any special handling or delivery instructions...",
|
|
606
576
|
rows: 4
|
|
607
577
|
})]
|
|
@@ -613,4 +583,4 @@ function T() {
|
|
|
613
583
|
});
|
|
614
584
|
}
|
|
615
585
|
//#endregion
|
|
616
|
-
export {
|
|
586
|
+
export { w as default };
|
package/package.json
CHANGED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OrderItemRow — one order-item line in CreateOrderPage's "Order Items" card.
|
|
3
|
+
*
|
|
4
|
+
* Its own component (not inlined in a `.map()`) because it needs one
|
|
5
|
+
* `useProduct`/`useProductStockLevels` hook call per row to resolve the
|
|
6
|
+
* selected catalog item's label and stock hint independently of the shared,
|
|
7
|
+
* search-filtered `products` list passed down from the page — the Rules of
|
|
8
|
+
* Hooks don't allow that from inside a loop in the parent.
|
|
9
|
+
*/
|
|
10
|
+
import type { ChangeEvent } from 'react';
|
|
11
|
+
import { Trash2 } from 'lucide-react';
|
|
12
|
+
import { Button, Input, Label, Separator } from './ui';
|
|
13
|
+
import { EntityCombobox } from './EntityCombobox';
|
|
14
|
+
import { useProduct, useProductStockLevels } from '../hooks/useInventory';
|
|
15
|
+
import type { OrderItem, Product } from '../types';
|
|
16
|
+
|
|
17
|
+
interface OrderItemRowProps {
|
|
18
|
+
item: OrderItem;
|
|
19
|
+
index: number;
|
|
20
|
+
isFirst: boolean;
|
|
21
|
+
products: Product[];
|
|
22
|
+
productsLoading: boolean;
|
|
23
|
+
onUpdate: (index: number, field: string, value: unknown) => void;
|
|
24
|
+
onRemove: (index: number) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function stockHintText(totalAvailable: number, levels: { warehouseName: string }[]): string {
|
|
28
|
+
if (totalAvailable <= 0) {
|
|
29
|
+
return 'No stock recorded for this item.';
|
|
30
|
+
}
|
|
31
|
+
if (levels.length === 1) {
|
|
32
|
+
return `${totalAvailable.toLocaleString()} available at ${levels[0].warehouseName}`;
|
|
33
|
+
}
|
|
34
|
+
return `${totalAvailable.toLocaleString()} available across ${levels.length} warehouses`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function OrderItemRow({
|
|
38
|
+
item,
|
|
39
|
+
index,
|
|
40
|
+
isFirst,
|
|
41
|
+
products,
|
|
42
|
+
productsLoading,
|
|
43
|
+
onUpdate,
|
|
44
|
+
onRemove,
|
|
45
|
+
}: OrderItemRowProps) {
|
|
46
|
+
// Resolved independently of the (search-filtered) `products` list so the
|
|
47
|
+
// trigger still shows the right label once a pick scrolls out of the
|
|
48
|
+
// current search results.
|
|
49
|
+
const { product: selectedProduct, isLoading: selectedProductLoading } = useProduct(
|
|
50
|
+
item.productId || undefined
|
|
51
|
+
);
|
|
52
|
+
const {
|
|
53
|
+
levels,
|
|
54
|
+
totalAvailable,
|
|
55
|
+
isLoading: stockLoading,
|
|
56
|
+
} = useProductStockLevels(item.productId || undefined);
|
|
57
|
+
|
|
58
|
+
const selectProduct = (productId: string) => {
|
|
59
|
+
if (!productId) {
|
|
60
|
+
onUpdate(index, 'productId', '');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const product = products.find((candidate) => candidate.id === productId);
|
|
64
|
+
if (!product) return;
|
|
65
|
+
|
|
66
|
+
onUpdate(index, 'productId', product.id);
|
|
67
|
+
onUpdate(index, 'name', product.name);
|
|
68
|
+
onUpdate(index, 'sku', product.sku);
|
|
69
|
+
onUpdate(index, 'weight', product.weight);
|
|
70
|
+
onUpdate(index, 'value', product.value);
|
|
71
|
+
onUpdate(index, 'dimensions', product.dimensions);
|
|
72
|
+
// Quantity is never set from the catalog — there's no per-unit default
|
|
73
|
+
// quantity on a cargo item, it's always this specific order's own call.
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div>
|
|
78
|
+
{!isFirst && <Separator className="my-4" />}
|
|
79
|
+
<div className="mb-3 space-y-2">
|
|
80
|
+
<Label htmlFor={`item-product-${item.id}`}>Catalog item (optional)</Label>
|
|
81
|
+
<EntityCombobox
|
|
82
|
+
id={`item-product-${item.id}`}
|
|
83
|
+
value={item.productId}
|
|
84
|
+
onChange={selectProduct}
|
|
85
|
+
options={products}
|
|
86
|
+
loading={productsLoading}
|
|
87
|
+
getId={(product) => product.id}
|
|
88
|
+
getLabel={(product) => `${product.name} (${product.sku})`}
|
|
89
|
+
renderOption={(product) => (
|
|
90
|
+
<>
|
|
91
|
+
{product.name} <span className="text-text-tertiary">({product.sku})</span>
|
|
92
|
+
</>
|
|
93
|
+
)}
|
|
94
|
+
selectedOption={selectedProduct}
|
|
95
|
+
selectedLoading={selectedProductLoading}
|
|
96
|
+
placeholder="Pick from your product catalog"
|
|
97
|
+
emptyText="No products found."
|
|
98
|
+
noneLabel="Enter details manually"
|
|
99
|
+
// Search is driven by the shared "Search product catalog" box above
|
|
100
|
+
// this list — no second input inside the popover.
|
|
101
|
+
/>
|
|
102
|
+
{item.productId && !stockLoading && (
|
|
103
|
+
<p className="text-xs text-text-secondary">{stockHintText(totalAvailable, levels)}</p>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
<div className="grid grid-cols-1 gap-4 md:grid-cols-7">
|
|
107
|
+
<div className="space-y-2 md:col-span-2">
|
|
108
|
+
<Label>Item Name</Label>
|
|
109
|
+
<Input
|
|
110
|
+
value={item.name}
|
|
111
|
+
onChange={(e: ChangeEvent<HTMLInputElement>) => onUpdate(index, 'name', e.target.value)}
|
|
112
|
+
placeholder="Product name"
|
|
113
|
+
/>
|
|
114
|
+
</div>
|
|
115
|
+
<div className="space-y-2">
|
|
116
|
+
<Label>SKU</Label>
|
|
117
|
+
<Input
|
|
118
|
+
value={item.sku}
|
|
119
|
+
onChange={(e: ChangeEvent<HTMLInputElement>) => onUpdate(index, 'sku', e.target.value)}
|
|
120
|
+
placeholder="SKU-001"
|
|
121
|
+
/>
|
|
122
|
+
</div>
|
|
123
|
+
<div className="space-y-2">
|
|
124
|
+
<Label>Quantity</Label>
|
|
125
|
+
<Input
|
|
126
|
+
type="number"
|
|
127
|
+
min="1"
|
|
128
|
+
value={item.quantity}
|
|
129
|
+
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
|
130
|
+
onUpdate(index, 'quantity', parseInt(e.target.value) || 1)
|
|
131
|
+
}
|
|
132
|
+
/>
|
|
133
|
+
</div>
|
|
134
|
+
<div className="space-y-2">
|
|
135
|
+
<Label>Weight (kg)</Label>
|
|
136
|
+
<Input
|
|
137
|
+
type="number"
|
|
138
|
+
min="0"
|
|
139
|
+
step="0.1"
|
|
140
|
+
value={item.weight}
|
|
141
|
+
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
|
142
|
+
onUpdate(index, 'weight', parseFloat(e.target.value) || 0)
|
|
143
|
+
}
|
|
144
|
+
/>
|
|
145
|
+
</div>
|
|
146
|
+
<div className="space-y-2">
|
|
147
|
+
<Label>Value ($)</Label>
|
|
148
|
+
<Input
|
|
149
|
+
type="number"
|
|
150
|
+
min="0"
|
|
151
|
+
step="0.01"
|
|
152
|
+
value={item.value}
|
|
153
|
+
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
|
154
|
+
onUpdate(index, 'value', parseFloat(e.target.value) || 0)
|
|
155
|
+
}
|
|
156
|
+
/>
|
|
157
|
+
</div>
|
|
158
|
+
<div className="flex items-end">
|
|
159
|
+
<Button
|
|
160
|
+
type="button"
|
|
161
|
+
variant="ghost"
|
|
162
|
+
size="icon"
|
|
163
|
+
className="text-status-error-text"
|
|
164
|
+
onClick={() => onRemove(index)}
|
|
165
|
+
>
|
|
166
|
+
<Trash2 size={16} />
|
|
167
|
+
</Button>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
ArrowLeft,
|
|
12
12
|
Save,
|
|
13
13
|
Plus,
|
|
14
|
-
Trash2,
|
|
15
14
|
Users,
|
|
16
15
|
MapPin,
|
|
17
16
|
CalendarClock,
|
|
@@ -33,16 +32,17 @@ import {
|
|
|
33
32
|
SelectItem,
|
|
34
33
|
SelectTrigger,
|
|
35
34
|
SelectValue,
|
|
36
|
-
Separator,
|
|
37
35
|
} from '../components/ui';
|
|
38
36
|
import { EmphasisPanel, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
39
37
|
import { useOrder, useOrders } from '../hooks/useOrders';
|
|
40
38
|
import { useCustomer, useCustomers } from '../hooks/useCustomers';
|
|
41
39
|
import { useRoute, useRoutes } from '../hooks/useRoutes';
|
|
40
|
+
import { useProducts } from '../hooks/useInventory';
|
|
42
41
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
43
42
|
import { joinPath, buildCustomerCreatePath } from '../utils/navigation';
|
|
44
43
|
import { CustomerPicker } from '../components/CustomerPicker';
|
|
45
44
|
import { EntityCombobox } from '../components/EntityCombobox';
|
|
45
|
+
import { OrderItemRow } from '../components/OrderItemRow';
|
|
46
46
|
import type { Customer } from '../types';
|
|
47
47
|
|
|
48
48
|
/**
|
|
@@ -92,6 +92,11 @@ export default function CreateOrderPage() {
|
|
|
92
92
|
search: routeSearch || undefined,
|
|
93
93
|
limit: 20,
|
|
94
94
|
});
|
|
95
|
+
const [productSearch, setProductSearch] = useState('');
|
|
96
|
+
const { products, isLoading: productsLoading } = useProducts({
|
|
97
|
+
search: productSearch || undefined,
|
|
98
|
+
limit: 20,
|
|
99
|
+
});
|
|
95
100
|
|
|
96
101
|
const isEditing = !!orderId && !!order;
|
|
97
102
|
|
|
@@ -650,74 +655,31 @@ export default function CreateOrderPage() {
|
|
|
650
655
|
/>
|
|
651
656
|
) : (
|
|
652
657
|
<div className="space-y-4">
|
|
658
|
+
<div className="space-y-2">
|
|
659
|
+
<Label htmlFor="item-product-search">Search product catalog</Label>
|
|
660
|
+
<Input
|
|
661
|
+
id="item-product-search"
|
|
662
|
+
value={productSearch}
|
|
663
|
+
onChange={(e) => setProductSearch(e.target.value)}
|
|
664
|
+
placeholder="Search by name or SKU"
|
|
665
|
+
/>
|
|
666
|
+
<p className="text-xs text-text-secondary">
|
|
667
|
+
Narrows the "Catalog item" picker below for every row. Picking a catalog item
|
|
668
|
+
fills in name, SKU, weight, and value — you can still edit them, or skip the
|
|
669
|
+
picker and enter details manually.
|
|
670
|
+
</p>
|
|
671
|
+
</div>
|
|
653
672
|
{formData.items.map((item, index) => (
|
|
654
|
-
<
|
|
655
|
-
{
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
</div>
|
|
665
|
-
<div className="space-y-2">
|
|
666
|
-
<Label>SKU</Label>
|
|
667
|
-
<Input
|
|
668
|
-
value={item.sku}
|
|
669
|
-
onChange={(e) => updateItem(index, 'sku', e.target.value)}
|
|
670
|
-
placeholder="SKU-001"
|
|
671
|
-
/>
|
|
672
|
-
</div>
|
|
673
|
-
<div className="space-y-2">
|
|
674
|
-
<Label>Quantity</Label>
|
|
675
|
-
<Input
|
|
676
|
-
type="number"
|
|
677
|
-
min="1"
|
|
678
|
-
value={item.quantity}
|
|
679
|
-
onChange={(e) =>
|
|
680
|
-
updateItem(index, 'quantity', parseInt(e.target.value) || 1)
|
|
681
|
-
}
|
|
682
|
-
/>
|
|
683
|
-
</div>
|
|
684
|
-
<div className="space-y-2">
|
|
685
|
-
<Label>Weight (kg)</Label>
|
|
686
|
-
<Input
|
|
687
|
-
type="number"
|
|
688
|
-
min="0"
|
|
689
|
-
step="0.1"
|
|
690
|
-
value={item.weight}
|
|
691
|
-
onChange={(e) =>
|
|
692
|
-
updateItem(index, 'weight', parseFloat(e.target.value) || 0)
|
|
693
|
-
}
|
|
694
|
-
/>
|
|
695
|
-
</div>
|
|
696
|
-
<div className="space-y-2">
|
|
697
|
-
<Label>Value ($)</Label>
|
|
698
|
-
<Input
|
|
699
|
-
type="number"
|
|
700
|
-
min="0"
|
|
701
|
-
step="0.01"
|
|
702
|
-
value={item.value}
|
|
703
|
-
onChange={(e) =>
|
|
704
|
-
updateItem(index, 'value', parseFloat(e.target.value) || 0)
|
|
705
|
-
}
|
|
706
|
-
/>
|
|
707
|
-
</div>
|
|
708
|
-
<div className="flex items-end">
|
|
709
|
-
<Button
|
|
710
|
-
type="button"
|
|
711
|
-
variant="ghost"
|
|
712
|
-
size="icon"
|
|
713
|
-
className="text-status-error-text"
|
|
714
|
-
onClick={() => removeItem(index)}
|
|
715
|
-
>
|
|
716
|
-
<Trash2 size={16} />
|
|
717
|
-
</Button>
|
|
718
|
-
</div>
|
|
719
|
-
</div>
|
|
720
|
-
</div>
|
|
673
|
+
<OrderItemRow
|
|
674
|
+
key={item.id}
|
|
675
|
+
item={item}
|
|
676
|
+
index={index}
|
|
677
|
+
isFirst={index === 0}
|
|
678
|
+
products={products}
|
|
679
|
+
productsLoading={productsLoading}
|
|
680
|
+
onUpdate={updateItem}
|
|
681
|
+
onRemove={removeItem}
|
|
682
|
+
/>
|
|
721
683
|
))}
|
|
722
684
|
</div>
|
|
723
685
|
)}
|