@lodashventure/medusa-parcel-shipping 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. package/.medusa/server/src/admin/index.js +1165 -0
  2. package/.medusa/server/src/admin/index.mjs +1166 -0
  3. package/.medusa/server/src/api/admin/parcel-boxes/[id]/route.js +47 -0
  4. package/.medusa/server/src/api/admin/parcel-boxes/route.js +48 -0
  5. package/.medusa/server/src/api/admin/shipping-config/areas/[id]/route.js +44 -0
  6. package/.medusa/server/src/api/admin/shipping-config/areas/route.js +48 -0
  7. package/.medusa/server/src/api/admin/shipping-config/rates/[id]/route.js +53 -0
  8. package/.medusa/server/src/api/admin/shipping-config/rates/route.js +55 -0
  9. package/.medusa/server/src/api/store/parcel-box-selector/route.js +53 -0
  10. package/.medusa/server/src/index.js +24 -0
  11. package/.medusa/server/src/modules/parcel-shipping/index.js +28 -0
  12. package/.medusa/server/src/modules/parcel-shipping/migrations/Migration20251015120000.js +70 -0
  13. package/.medusa/server/src/modules/parcel-shipping/models/parcel-box.js +21 -0
  14. package/.medusa/server/src/modules/parcel-shipping/models/service-area.js +20 -0
  15. package/.medusa/server/src/modules/parcel-shipping/models/shipping-rate.js +20 -0
  16. package/.medusa/server/src/modules/parcel-shipping/service.js +172 -0
  17. package/.medusa/server/src/modules/parcel-shipping/types.js +3 -0
  18. package/.medusa/server/src/modules/parcel-shipping/utils/packing.js +319 -0
  19. package/.medusa/server/src/providers/company-truck/index.js +19 -0
  20. package/.medusa/server/src/providers/parcel-fulfillment-base.js +180 -0
  21. package/.medusa/server/src/providers/private-carrier/index.js +19 -0
  22. package/package.json +81 -0
@@ -0,0 +1,1165 @@
1
+ "use strict";
2
+ const jsxRuntime = require("react/jsx-runtime");
3
+ const adminSdk = require("@medusajs/admin-sdk");
4
+ const icons = require("@medusajs/icons");
5
+ const react = require("react");
6
+ const ui = require("@medusajs/ui");
7
+ const requestJson = async (url, method = "GET", body) => {
8
+ const response = await fetch(url, {
9
+ method,
10
+ headers: {
11
+ "Content-Type": "application/json"
12
+ },
13
+ body: body ? JSON.stringify(body) : void 0
14
+ });
15
+ if (!response.ok) {
16
+ let message = response.statusText;
17
+ try {
18
+ const payload = await response.json();
19
+ message = (payload == null ? void 0 : payload.code) ?? (payload == null ? void 0 : payload.error) ?? message;
20
+ } catch (_) {
21
+ }
22
+ throw new Error(message);
23
+ }
24
+ if (response.status === 204) {
25
+ return void 0;
26
+ }
27
+ return await response.json();
28
+ };
29
+ const toNumber = (value) => {
30
+ if (value.trim().length === 0) {
31
+ return null;
32
+ }
33
+ const parsed = Number(value);
34
+ return Number.isFinite(parsed) ? parsed : null;
35
+ };
36
+ const getThaiAddressComponents = () => null;
37
+ const ParcelShippingPage = () => {
38
+ const [tab, setTab] = react.useState("boxes");
39
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "space-y-6 p-6", children: [
40
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
41
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Parcel Shipping" }),
42
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Configure parcel boxes, shipping rates, and supported service areas." })
43
+ ] }),
44
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
45
+ /* @__PURE__ */ jsxRuntime.jsx(
46
+ ui.Button,
47
+ {
48
+ variant: tab === "boxes" ? "primary" : "secondary",
49
+ size: "small",
50
+ onClick: () => setTab("boxes"),
51
+ children: "Boxes"
52
+ }
53
+ ),
54
+ /* @__PURE__ */ jsxRuntime.jsx(
55
+ ui.Button,
56
+ {
57
+ variant: tab === "rates" ? "primary" : "secondary",
58
+ size: "small",
59
+ onClick: () => setTab("rates"),
60
+ children: "Shipping Rates"
61
+ }
62
+ ),
63
+ /* @__PURE__ */ jsxRuntime.jsx(
64
+ ui.Button,
65
+ {
66
+ variant: tab === "areas" ? "primary" : "secondary",
67
+ size: "small",
68
+ onClick: () => setTab("areas"),
69
+ children: "Service Areas"
70
+ }
71
+ )
72
+ ] }),
73
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pt-6", children: [
74
+ tab === "boxes" && /* @__PURE__ */ jsxRuntime.jsx(BoxesSection, {}),
75
+ tab === "rates" && /* @__PURE__ */ jsxRuntime.jsx(RatesSection, {}),
76
+ tab === "areas" && /* @__PURE__ */ jsxRuntime.jsx(AreasSection, {})
77
+ ] })
78
+ ] });
79
+ };
80
+ const BoxesSection = () => {
81
+ const [boxes, setBoxes] = react.useState([]);
82
+ const [loading, setLoading] = react.useState(true);
83
+ const [formState, setFormState] = react.useState({
84
+ id: null,
85
+ name: "",
86
+ width_cm: "",
87
+ length_cm: "",
88
+ height_cm: "",
89
+ max_weight_kg: "",
90
+ price_thb: "",
91
+ active: true
92
+ });
93
+ const [saving, setSaving] = react.useState(false);
94
+ const [deletingId, setDeletingId] = react.useState(null);
95
+ const refresh = react.useCallback(async () => {
96
+ setLoading(true);
97
+ try {
98
+ const data = await requestJson(
99
+ "/admin/parcel-boxes"
100
+ );
101
+ setBoxes(data.boxes ?? []);
102
+ } catch (error) {
103
+ ui.toast.error(error.message ?? "Failed to load boxes", {
104
+ dismissable: true
105
+ });
106
+ } finally {
107
+ setLoading(false);
108
+ }
109
+ }, []);
110
+ react.useEffect(() => {
111
+ refresh();
112
+ }, [refresh]);
113
+ const resetForm = () => setFormState({
114
+ id: null,
115
+ name: "",
116
+ width_cm: "",
117
+ length_cm: "",
118
+ height_cm: "",
119
+ max_weight_kg: "",
120
+ price_thb: "",
121
+ active: true
122
+ });
123
+ const handleSubmit = async () => {
124
+ const width = toNumber(formState.width_cm);
125
+ const length = toNumber(formState.length_cm);
126
+ const height = toNumber(formState.height_cm);
127
+ const maxWeight = toNumber(formState.max_weight_kg);
128
+ const price = toNumber(formState.price_thb);
129
+ if (!formState.name.trim() || width === null || length === null || height === null || maxWeight === null || price === null) {
130
+ ui.toast.error("Fill all fields with valid numeric values", {
131
+ dismissable: true
132
+ });
133
+ return;
134
+ }
135
+ const payload = {
136
+ name: formState.name.trim(),
137
+ width_cm: width,
138
+ length_cm: length,
139
+ height_cm: height,
140
+ max_weight_kg: maxWeight,
141
+ price_thb: price,
142
+ active: formState.active
143
+ };
144
+ setSaving(true);
145
+ try {
146
+ if (formState.id) {
147
+ await requestJson(
148
+ `/admin/parcel-boxes/${formState.id}`,
149
+ "PUT",
150
+ payload
151
+ );
152
+ ui.toast.success("Box updated", { dismissable: true });
153
+ } else {
154
+ await requestJson("/admin/parcel-boxes", "POST", payload);
155
+ ui.toast.success("Box created", { dismissable: true });
156
+ }
157
+ await refresh();
158
+ resetForm();
159
+ } catch (error) {
160
+ ui.toast.error(error.message ?? "Failed to save box", {
161
+ dismissable: true
162
+ });
163
+ } finally {
164
+ setSaving(false);
165
+ }
166
+ };
167
+ const handleDelete = async (id) => {
168
+ setDeletingId(id);
169
+ try {
170
+ await requestJson(`/admin/parcel-boxes/${id}`, "DELETE");
171
+ ui.toast.success("Box removed", { dismissable: true });
172
+ await refresh();
173
+ } catch (error) {
174
+ ui.toast.error(error.message ?? "Failed to delete box", {
175
+ dismissable: true
176
+ });
177
+ } finally {
178
+ setDeletingId(null);
179
+ }
180
+ };
181
+ const handleToggle = async (box) => {
182
+ try {
183
+ await requestJson(`/admin/parcel-boxes/${box.id}`, "PUT", {
184
+ active: !box.active
185
+ });
186
+ await refresh();
187
+ } catch (error) {
188
+ ui.toast.error(error.message ?? "Failed to update box status", {
189
+ dismissable: true
190
+ });
191
+ }
192
+ };
193
+ const tableRows = react.useMemo(() => {
194
+ if (loading) {
195
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Row, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { colSpan: 6, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-center text-ui-fg-subtle", children: "Loading boxes..." }) }) });
196
+ }
197
+ if (boxes.length === 0) {
198
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Row, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { colSpan: 6, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-center text-ui-fg-subtle", children: "No boxes configured yet." }) }) });
199
+ }
200
+ return boxes.map((box) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
201
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: box.name }) }),
202
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-sm text-ui-fg-subtle", children: [
203
+ box.width_cm,
204
+ " × ",
205
+ box.length_cm,
206
+ " × ",
207
+ box.height_cm
208
+ ] }) }),
209
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: box.max_weight_kg }),
210
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: box.price_thb }),
211
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: box.active ? "green" : "grey", children: box.active ? "Active" : "Inactive" }) }),
212
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-2", children: [
213
+ /* @__PURE__ */ jsxRuntime.jsx(
214
+ ui.Switch,
215
+ {
216
+ checked: box.active,
217
+ onCheckedChange: () => handleToggle(box)
218
+ }
219
+ ),
220
+ /* @__PURE__ */ jsxRuntime.jsx(
221
+ ui.Button,
222
+ {
223
+ variant: "secondary",
224
+ size: "small",
225
+ onClick: () => setFormState({
226
+ id: box.id,
227
+ name: box.name,
228
+ width_cm: String(box.width_cm),
229
+ length_cm: String(box.length_cm),
230
+ height_cm: String(box.height_cm),
231
+ max_weight_kg: String(box.max_weight_kg),
232
+ price_thb: String(box.price_thb),
233
+ active: box.active
234
+ }),
235
+ children: "Edit"
236
+ }
237
+ ),
238
+ /* @__PURE__ */ jsxRuntime.jsx(
239
+ ui.Button,
240
+ {
241
+ variant: "danger",
242
+ size: "small",
243
+ onClick: () => handleDelete(box.id),
244
+ disabled: deletingId === box.id,
245
+ children: "Delete"
246
+ }
247
+ )
248
+ ] }) })
249
+ ] }, box.id));
250
+ }, [boxes, deletingId, loading]);
251
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "space-y-6", children: [
252
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 rounded-md border p-4", children: [
253
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: formState.id ? "Edit Box" : "Create Box" }),
254
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 md:grid-cols-3", children: [
255
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
256
+ /* @__PURE__ */ jsxRuntime.jsx(
257
+ ui.Label,
258
+ {
259
+ className: "text-sm font-medium text-ui-fg-base",
260
+ htmlFor: "parcel-box-name",
261
+ children: "Name"
262
+ }
263
+ ),
264
+ /* @__PURE__ */ jsxRuntime.jsx(
265
+ ui.Input,
266
+ {
267
+ id: "parcel-box-name",
268
+ placeholder: "Enter box name",
269
+ value: formState.name,
270
+ onChange: (event) => setFormState((prev) => ({ ...prev, name: event.target.value }))
271
+ }
272
+ )
273
+ ] }),
274
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
275
+ /* @__PURE__ */ jsxRuntime.jsx(
276
+ ui.Label,
277
+ {
278
+ className: "text-sm font-medium text-ui-fg-base",
279
+ htmlFor: "parcel-box-width",
280
+ children: "Width (cm)"
281
+ }
282
+ ),
283
+ /* @__PURE__ */ jsxRuntime.jsx(
284
+ ui.Input,
285
+ {
286
+ id: "parcel-box-width",
287
+ placeholder: "e.g. 20",
288
+ type: "number",
289
+ value: formState.width_cm,
290
+ onChange: (event) => setFormState((prev) => ({
291
+ ...prev,
292
+ width_cm: event.target.value
293
+ }))
294
+ }
295
+ )
296
+ ] }),
297
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
298
+ /* @__PURE__ */ jsxRuntime.jsx(
299
+ ui.Label,
300
+ {
301
+ className: "text-sm font-medium text-ui-fg-base",
302
+ htmlFor: "parcel-box-length",
303
+ children: "Length (cm)"
304
+ }
305
+ ),
306
+ /* @__PURE__ */ jsxRuntime.jsx(
307
+ ui.Input,
308
+ {
309
+ id: "parcel-box-length",
310
+ placeholder: "e.g. 30",
311
+ type: "number",
312
+ value: formState.length_cm,
313
+ onChange: (event) => setFormState((prev) => ({
314
+ ...prev,
315
+ length_cm: event.target.value
316
+ }))
317
+ }
318
+ )
319
+ ] }),
320
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
321
+ /* @__PURE__ */ jsxRuntime.jsx(
322
+ ui.Label,
323
+ {
324
+ className: "text-sm font-medium text-ui-fg-base",
325
+ htmlFor: "parcel-box-height",
326
+ children: "Height (cm)"
327
+ }
328
+ ),
329
+ /* @__PURE__ */ jsxRuntime.jsx(
330
+ ui.Input,
331
+ {
332
+ id: "parcel-box-height",
333
+ placeholder: "e.g. 15",
334
+ type: "number",
335
+ value: formState.height_cm,
336
+ onChange: (event) => setFormState((prev) => ({
337
+ ...prev,
338
+ height_cm: event.target.value
339
+ }))
340
+ }
341
+ )
342
+ ] }),
343
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
344
+ /* @__PURE__ */ jsxRuntime.jsx(
345
+ ui.Label,
346
+ {
347
+ className: "text-sm font-medium text-ui-fg-base",
348
+ htmlFor: "parcel-box-max-weight",
349
+ children: "Max Weight (kg)"
350
+ }
351
+ ),
352
+ /* @__PURE__ */ jsxRuntime.jsx(
353
+ ui.Input,
354
+ {
355
+ id: "parcel-box-max-weight",
356
+ placeholder: "e.g. 0.5",
357
+ type: "number",
358
+ value: formState.max_weight_kg,
359
+ onChange: (event) => setFormState((prev) => ({
360
+ ...prev,
361
+ max_weight_kg: event.target.value
362
+ }))
363
+ }
364
+ )
365
+ ] }),
366
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
367
+ /* @__PURE__ */ jsxRuntime.jsx(
368
+ ui.Label,
369
+ {
370
+ className: "text-sm font-medium text-ui-fg-base",
371
+ htmlFor: "parcel-box-price",
372
+ children: "Box Price (THB)"
373
+ }
374
+ ),
375
+ /* @__PURE__ */ jsxRuntime.jsx(
376
+ ui.Input,
377
+ {
378
+ id: "parcel-box-price",
379
+ placeholder: "e.g. 10",
380
+ type: "number",
381
+ value: formState.price_thb,
382
+ onChange: (event) => setFormState((prev) => ({
383
+ ...prev,
384
+ price_thb: event.target.value
385
+ }))
386
+ }
387
+ )
388
+ ] })
389
+ ] }),
390
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
391
+ /* @__PURE__ */ jsxRuntime.jsx(
392
+ ui.Switch,
393
+ {
394
+ checked: formState.active,
395
+ onCheckedChange: (active) => setFormState((prev) => ({ ...prev, active }))
396
+ }
397
+ ),
398
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm text-ui-fg-subtle", children: "Active" })
399
+ ] }),
400
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [
401
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: handleSubmit, disabled: saving, children: formState.id ? "Update" : "Create" }),
402
+ formState.id && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: resetForm, children: "Cancel" })
403
+ ] })
404
+ ] }),
405
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden rounded-md border", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
406
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
407
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Name" }),
408
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Dimensions (cm)" }),
409
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Max Weight (kg)" }),
410
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Price" }),
411
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Status" }),
412
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { className: "text-right", children: "Actions" })
413
+ ] }) }),
414
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: tableRows })
415
+ ] }) })
416
+ ] });
417
+ };
418
+ const RatesSection = () => {
419
+ const [rates, setRates] = react.useState([]);
420
+ const [loading, setLoading] = react.useState(true);
421
+ const [formState, setFormState] = react.useState({
422
+ id: null,
423
+ carrier: "COMPANY_TRUCK",
424
+ min_weight_kg: "0",
425
+ max_weight_kg: "",
426
+ price_thb: "",
427
+ currency: "THB",
428
+ active: true
429
+ });
430
+ const [currencyOptions, setCurrencyOptions] = react.useState([]);
431
+ const [defaultCurrency, setDefaultCurrency] = react.useState("THB");
432
+ const [currenciesLoading, setCurrenciesLoading] = react.useState(true);
433
+ const [saving, setSaving] = react.useState(false);
434
+ const [deletingId, setDeletingId] = react.useState(null);
435
+ const refresh = react.useCallback(async () => {
436
+ setLoading(true);
437
+ try {
438
+ const data = await requestJson(
439
+ "/admin/shipping-config/rates"
440
+ );
441
+ setRates(
442
+ (data.rates ?? []).map((rate) => ({
443
+ ...rate,
444
+ currency: (rate.currency ?? "").toUpperCase()
445
+ }))
446
+ );
447
+ } catch (error) {
448
+ ui.toast.error(error.message ?? "Failed to load rates", {
449
+ dismissable: true
450
+ });
451
+ } finally {
452
+ setLoading(false);
453
+ }
454
+ }, []);
455
+ const loadCurrencies = react.useCallback(async () => {
456
+ var _a, _b, _c;
457
+ setCurrenciesLoading(true);
458
+ try {
459
+ const data = await requestJson("/admin/store");
460
+ const supported = ((_b = (_a = data.store) == null ? void 0 : _a.supported_currencies) == null ? void 0 : _b.map(
461
+ (item) => item.code.toUpperCase()
462
+ )) ?? [];
463
+ const defaultCode = (((_c = data.store) == null ? void 0 : _c.default_currency_code) ?? supported[0] ?? "THB").toUpperCase();
464
+ const options = supported.length > 0 ? supported : [defaultCode];
465
+ setCurrencyOptions(options);
466
+ setDefaultCurrency(defaultCode);
467
+ setFormState((prev) => ({
468
+ ...prev,
469
+ currency: prev.currency && options.includes(prev.currency.toUpperCase()) ? prev.currency.toUpperCase() : defaultCode
470
+ }));
471
+ } catch (error) {
472
+ ui.toast.error(
473
+ error.message ?? "Failed to load store currencies",
474
+ { dismissable: true }
475
+ );
476
+ setCurrencyOptions((prev) => prev.length > 0 ? prev : ["THB"]);
477
+ setDefaultCurrency((prev) => prev || "THB");
478
+ } finally {
479
+ setCurrenciesLoading(false);
480
+ }
481
+ }, []);
482
+ react.useEffect(() => {
483
+ refresh();
484
+ }, [refresh]);
485
+ react.useEffect(() => {
486
+ loadCurrencies();
487
+ }, [loadCurrencies]);
488
+ const getInitialCurrency = react.useCallback(() => {
489
+ if (currencyOptions.length === 0) {
490
+ return defaultCurrency || "THB";
491
+ }
492
+ return currencyOptions.includes(defaultCurrency) ? defaultCurrency : currencyOptions[0];
493
+ }, [currencyOptions, defaultCurrency]);
494
+ const resetForm = react.useCallback(() => {
495
+ setFormState({
496
+ id: null,
497
+ carrier: "COMPANY_TRUCK",
498
+ min_weight_kg: "0",
499
+ max_weight_kg: "",
500
+ price_thb: "",
501
+ currency: getInitialCurrency(),
502
+ active: true
503
+ });
504
+ }, [getInitialCurrency]);
505
+ react.useEffect(() => {
506
+ if (!formState.id && !currencyOptions.includes(formState.currency)) {
507
+ setFormState((prev) => ({
508
+ ...prev,
509
+ currency: getInitialCurrency()
510
+ }));
511
+ }
512
+ }, [currencyOptions, formState.currency, formState.id, getInitialCurrency]);
513
+ const handleSubmit = async () => {
514
+ const min = toNumber(formState.min_weight_kg);
515
+ const max = toNumber(formState.max_weight_kg);
516
+ const price = toNumber(formState.price_thb);
517
+ if (max === null || price === null || max <= 0) {
518
+ ui.toast.error("Provide valid max weight and price", { dismissable: true });
519
+ return;
520
+ }
521
+ if (min !== null && max <= min) {
522
+ ui.toast.error("Max weight must be greater than min weight", {
523
+ dismissable: true
524
+ });
525
+ return;
526
+ }
527
+ const payload = {
528
+ carrier: formState.carrier,
529
+ min_weight_kg: min ?? 0,
530
+ max_weight_kg: max,
531
+ price_thb: price,
532
+ currency: (formState.currency || getInitialCurrency()).trim().toUpperCase(),
533
+ active: formState.active
534
+ };
535
+ setSaving(true);
536
+ try {
537
+ if (formState.id) {
538
+ await requestJson(
539
+ `/admin/shipping-config/rates/${formState.id}`,
540
+ "PUT",
541
+ payload
542
+ );
543
+ ui.toast.success("Rate updated", { dismissable: true });
544
+ } else {
545
+ await requestJson("/admin/shipping-config/rates", "POST", payload);
546
+ ui.toast.success("Rate created", { dismissable: true });
547
+ }
548
+ await refresh();
549
+ resetForm();
550
+ } catch (error) {
551
+ ui.toast.error(error.message ?? "Failed to save rate", {
552
+ dismissable: true
553
+ });
554
+ } finally {
555
+ setSaving(false);
556
+ }
557
+ };
558
+ const handleDelete = async (id) => {
559
+ setDeletingId(id);
560
+ try {
561
+ await requestJson(`/admin/shipping-config/rates/${id}`, "DELETE");
562
+ ui.toast.success("Rate removed", { dismissable: true });
563
+ await refresh();
564
+ } catch (error) {
565
+ ui.toast.error(error.message ?? "Failed to delete rate", {
566
+ dismissable: true
567
+ });
568
+ } finally {
569
+ setDeletingId(null);
570
+ }
571
+ };
572
+ const handleToggle = async (rate) => {
573
+ try {
574
+ await requestJson(`/admin/shipping-config/rates/${rate.id}`, "PUT", {
575
+ active: !rate.active
576
+ });
577
+ await refresh();
578
+ } catch (error) {
579
+ ui.toast.error(error.message ?? "Failed to update rate status", {
580
+ dismissable: true
581
+ });
582
+ }
583
+ };
584
+ const tableRows = react.useMemo(() => {
585
+ if (loading) {
586
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Row, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { colSpan: 5, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-center text-ui-fg-subtle", children: "Loading rates..." }) }) });
587
+ }
588
+ if (rates.length === 0) {
589
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Row, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { colSpan: 5, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-center text-ui-fg-subtle", children: "No shipping rates configured." }) }) });
590
+ }
591
+ return rates.map((rate) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
592
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: rate.carrier === "COMPANY_TRUCK" ? "Company Truck" : "Private Carrier" }) }),
593
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-sm text-ui-fg-subtle", children: [
594
+ rate.min_weight_kg,
595
+ " - ",
596
+ rate.max_weight_kg
597
+ ] }) }),
598
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Cell, { children: [
599
+ rate.price_thb,
600
+ " ",
601
+ rate.currency
602
+ ] }),
603
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: rate.active ? "green" : "grey", children: rate.active ? "Active" : "Inactive" }) }),
604
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-2", children: [
605
+ /* @__PURE__ */ jsxRuntime.jsx(
606
+ ui.Switch,
607
+ {
608
+ checked: rate.active,
609
+ onCheckedChange: () => handleToggle(rate)
610
+ }
611
+ ),
612
+ /* @__PURE__ */ jsxRuntime.jsx(
613
+ ui.Button,
614
+ {
615
+ variant: "secondary",
616
+ size: "small",
617
+ onClick: () => setFormState({
618
+ id: rate.id,
619
+ carrier: rate.carrier,
620
+ min_weight_kg: String(rate.min_weight_kg ?? 0),
621
+ max_weight_kg: String(rate.max_weight_kg),
622
+ price_thb: String(rate.price_thb),
623
+ currency: (rate.currency ?? "").toUpperCase(),
624
+ active: rate.active
625
+ }),
626
+ children: "Edit"
627
+ }
628
+ ),
629
+ /* @__PURE__ */ jsxRuntime.jsx(
630
+ ui.Button,
631
+ {
632
+ variant: "danger",
633
+ size: "small",
634
+ onClick: () => handleDelete(rate.id),
635
+ disabled: deletingId === rate.id,
636
+ children: "Delete"
637
+ }
638
+ )
639
+ ] }) })
640
+ ] }, rate.id));
641
+ }, [deletingId, loading, rates]);
642
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "space-y-6", children: [
643
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 rounded-md border p-4", children: [
644
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: formState.id ? "Edit Shipping Rate" : "Create Shipping Rate" }),
645
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 md:grid-cols-3", children: [
646
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
647
+ /* @__PURE__ */ jsxRuntime.jsx(
648
+ ui.Label,
649
+ {
650
+ className: "text-sm font-medium text-ui-fg-base",
651
+ htmlFor: "parcel-rate-carrier",
652
+ children: "Carrier"
653
+ }
654
+ ),
655
+ /* @__PURE__ */ jsxRuntime.jsxs(
656
+ "select",
657
+ {
658
+ id: "parcel-rate-carrier",
659
+ className: "mt-1 w-full rounded-md border border-ui-border-base bg-ui-bg-field px-3 py-2 text-sm",
660
+ value: formState.carrier,
661
+ onChange: (event) => setFormState((prev) => ({
662
+ ...prev,
663
+ carrier: event.target.value
664
+ })),
665
+ children: [
666
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "COMPANY_TRUCK", children: "Company Truck" }),
667
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "PRIVATE_CARRIER", children: "Private Carrier" })
668
+ ]
669
+ }
670
+ )
671
+ ] }),
672
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
673
+ /* @__PURE__ */ jsxRuntime.jsx(
674
+ ui.Label,
675
+ {
676
+ className: "text-sm font-medium text-ui-fg-base",
677
+ htmlFor: "parcel-rate-min-weight",
678
+ children: "Min Weight (kg)"
679
+ }
680
+ ),
681
+ /* @__PURE__ */ jsxRuntime.jsx(
682
+ ui.Input,
683
+ {
684
+ id: "parcel-rate-min-weight",
685
+ placeholder: "e.g. 0",
686
+ type: "number",
687
+ value: formState.min_weight_kg,
688
+ onChange: (event) => setFormState((prev) => ({
689
+ ...prev,
690
+ min_weight_kg: event.target.value
691
+ }))
692
+ }
693
+ )
694
+ ] }),
695
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
696
+ /* @__PURE__ */ jsxRuntime.jsx(
697
+ ui.Label,
698
+ {
699
+ className: "text-sm font-medium text-ui-fg-base",
700
+ htmlFor: "parcel-rate-max-weight",
701
+ children: "Max Weight (kg)"
702
+ }
703
+ ),
704
+ /* @__PURE__ */ jsxRuntime.jsx(
705
+ ui.Input,
706
+ {
707
+ id: "parcel-rate-max-weight",
708
+ placeholder: "e.g. 5",
709
+ type: "number",
710
+ value: formState.max_weight_kg,
711
+ onChange: (event) => setFormState((prev) => ({
712
+ ...prev,
713
+ max_weight_kg: event.target.value
714
+ }))
715
+ }
716
+ )
717
+ ] }),
718
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
719
+ /* @__PURE__ */ jsxRuntime.jsx(
720
+ ui.Label,
721
+ {
722
+ className: "text-sm font-medium text-ui-fg-base",
723
+ htmlFor: "parcel-rate-price",
724
+ children: "Price (THB)"
725
+ }
726
+ ),
727
+ /* @__PURE__ */ jsxRuntime.jsx(
728
+ ui.Input,
729
+ {
730
+ id: "parcel-rate-price",
731
+ placeholder: "e.g. 45",
732
+ type: "number",
733
+ value: formState.price_thb,
734
+ onChange: (event) => setFormState((prev) => ({
735
+ ...prev,
736
+ price_thb: event.target.value
737
+ }))
738
+ }
739
+ )
740
+ ] }),
741
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
742
+ /* @__PURE__ */ jsxRuntime.jsx(
743
+ ui.Label,
744
+ {
745
+ className: "text-sm font-medium text-ui-fg-base",
746
+ htmlFor: "parcel-rate-currency",
747
+ children: "Currency"
748
+ }
749
+ ),
750
+ /* @__PURE__ */ jsxRuntime.jsx(
751
+ "select",
752
+ {
753
+ id: "parcel-rate-currency",
754
+ className: "w-full rounded-md border border-ui-border-base bg-ui-bg-field px-3 py-2 text-sm",
755
+ value: formState.currency,
756
+ onChange: (event) => setFormState((prev) => ({
757
+ ...prev,
758
+ currency: event.target.value
759
+ })),
760
+ disabled: currencyOptions.length === 0,
761
+ children: currencyOptions.map((code) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: code, children: code }, code))
762
+ }
763
+ ),
764
+ currenciesLoading && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-xs text-ui-fg-subtle", children: "Loading currencies..." }),
765
+ !currenciesLoading && currencyOptions.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-xs text-ui-fg-subtle", children: "Configure store currencies to enable selection." })
766
+ ] })
767
+ ] }),
768
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
769
+ /* @__PURE__ */ jsxRuntime.jsx(
770
+ ui.Switch,
771
+ {
772
+ checked: formState.active,
773
+ onCheckedChange: (active) => setFormState((prev) => ({ ...prev, active }))
774
+ }
775
+ ),
776
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm text-ui-fg-subtle", children: "Active" })
777
+ ] }),
778
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [
779
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: handleSubmit, disabled: saving, children: formState.id ? "Update" : "Create" }),
780
+ formState.id && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: resetForm, children: "Cancel" })
781
+ ] })
782
+ ] }),
783
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden rounded-md border", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
784
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
785
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Carrier" }),
786
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Weight Range (kg)" }),
787
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Price" }),
788
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Status" }),
789
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { className: "text-right", children: "Actions" })
790
+ ] }) }),
791
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: tableRows })
792
+ ] }) })
793
+ ] });
794
+ };
795
+ const AreasSection = () => {
796
+ const [areas, setAreas] = react.useState([]);
797
+ const [loading, setLoading] = react.useState(true);
798
+ const [formState, setFormState] = react.useState({
799
+ id: null,
800
+ kind: "PROVINCE",
801
+ value: "",
802
+ active: true
803
+ });
804
+ const thaiAddressInputs = react.useMemo(() => getThaiAddressComponents(), []);
805
+ const ThaiProvinceInput = thaiAddressInputs == null ? void 0 : thaiAddressInputs.Province;
806
+ const ThaiZipcodeInput = thaiAddressInputs == null ? void 0 : thaiAddressInputs.Zipcode;
807
+ const [thaiAddress, setThaiAddress] = react.useState({
808
+ district: "",
809
+ amphoe: "",
810
+ province: "",
811
+ zipcode: ""
812
+ });
813
+ const [saving, setSaving] = react.useState(false);
814
+ const [deletingId, setDeletingId] = react.useState(null);
815
+ const refresh = react.useCallback(async () => {
816
+ setLoading(true);
817
+ try {
818
+ const data = await requestJson(
819
+ "/admin/shipping-config/areas"
820
+ );
821
+ setAreas(data.areas ?? []);
822
+ } catch (error) {
823
+ ui.toast.error(error.message ?? "Failed to load service areas", {
824
+ dismissable: true
825
+ });
826
+ } finally {
827
+ setLoading(false);
828
+ }
829
+ }, []);
830
+ react.useEffect(() => {
831
+ refresh();
832
+ }, [refresh]);
833
+ const resetForm = react.useCallback(() => {
834
+ setFormState({
835
+ id: null,
836
+ kind: "PROVINCE",
837
+ value: "",
838
+ active: true
839
+ });
840
+ setThaiAddress({
841
+ district: "",
842
+ amphoe: "",
843
+ province: "",
844
+ zipcode: ""
845
+ });
846
+ }, []);
847
+ const handleSubmit = async () => {
848
+ if (!formState.value.trim()) {
849
+ ui.toast.error("Provide a value", { dismissable: true });
850
+ return;
851
+ }
852
+ const payload = {
853
+ kind: formState.kind,
854
+ value: formState.value.trim(),
855
+ active: formState.active
856
+ };
857
+ setSaving(true);
858
+ try {
859
+ if (formState.id) {
860
+ await requestJson(
861
+ `/admin/shipping-config/areas/${formState.id}`,
862
+ "PUT",
863
+ payload
864
+ );
865
+ ui.toast.success("Service area updated", { dismissable: true });
866
+ } else {
867
+ await requestJson("/admin/shipping-config/areas", "POST", payload);
868
+ ui.toast.success("Service area created", { dismissable: true });
869
+ }
870
+ await refresh();
871
+ resetForm();
872
+ } catch (error) {
873
+ ui.toast.error(error.message ?? "Failed to save service area", {
874
+ dismissable: true
875
+ });
876
+ } finally {
877
+ setSaving(false);
878
+ }
879
+ };
880
+ const handleDelete = async (id) => {
881
+ setDeletingId(id);
882
+ try {
883
+ await requestJson(`/admin/shipping-config/areas/${id}`, "DELETE");
884
+ ui.toast.success("Service area removed", { dismissable: true });
885
+ await refresh();
886
+ } catch (error) {
887
+ ui.toast.error(error.message ?? "Failed to delete service area", {
888
+ dismissable: true
889
+ });
890
+ } finally {
891
+ setDeletingId(null);
892
+ }
893
+ };
894
+ const handleToggle = async (area) => {
895
+ try {
896
+ await requestJson(`/admin/shipping-config/areas/${area.id}`, "PUT", {
897
+ active: !area.active
898
+ });
899
+ await refresh();
900
+ } catch (error) {
901
+ ui.toast.error(
902
+ error.message ?? "Failed to update service area status",
903
+ { dismissable: true }
904
+ );
905
+ }
906
+ };
907
+ const beginEdit = react.useCallback((area) => {
908
+ setFormState({
909
+ id: area.id,
910
+ kind: area.kind,
911
+ value: area.value,
912
+ active: area.active
913
+ });
914
+ setThaiAddress((prev) => ({
915
+ ...prev,
916
+ province: area.kind === "PROVINCE" ? area.value : prev.province,
917
+ zipcode: area.kind === "POSTCODE_PREFIX" ? area.value : prev.zipcode
918
+ }));
919
+ }, []);
920
+ const handleThaiAddressSelect = react.useCallback(
921
+ (addressRecord) => {
922
+ const nextAddress = {
923
+ district: addressRecord.district ?? "",
924
+ amphoe: addressRecord.amphoe ?? "",
925
+ province: addressRecord.province ?? "",
926
+ zipcode: addressRecord.zipcode ? String(addressRecord.zipcode) : ""
927
+ };
928
+ setThaiAddress(nextAddress);
929
+ if (formState.kind === "PROVINCE") {
930
+ setFormState((prev) => ({
931
+ ...prev,
932
+ value: nextAddress.province
933
+ }));
934
+ } else {
935
+ setFormState((prev) => ({
936
+ ...prev,
937
+ value: nextAddress.zipcode.slice(0, 3)
938
+ }));
939
+ }
940
+ },
941
+ [formState.kind]
942
+ );
943
+ const handleThaiAddressChange = react.useCallback(
944
+ (scope) => (rawValue) => {
945
+ const value = typeof rawValue === "number" ? String(rawValue) : rawValue;
946
+ setThaiAddress((prev) => ({
947
+ ...prev,
948
+ [scope]: value
949
+ }));
950
+ if (scope === "province" && formState.kind === "PROVINCE") {
951
+ setFormState((prev) => ({
952
+ ...prev,
953
+ value
954
+ }));
955
+ }
956
+ if (scope === "zipcode" && formState.kind === "POSTCODE_PREFIX") {
957
+ setFormState((prev) => ({
958
+ ...prev,
959
+ value: value.slice(0, 3)
960
+ }));
961
+ }
962
+ },
963
+ [formState.kind]
964
+ );
965
+ const tableRows = react.useMemo(() => {
966
+ if (loading) {
967
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Row, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { colSpan: 4, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-center text-ui-fg-subtle", children: "Loading service areas..." }) }) });
968
+ }
969
+ if (areas.length === 0) {
970
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Row, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { colSpan: 4, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-center text-ui-fg-subtle", children: "No service areas configured." }) }) });
971
+ }
972
+ return areas.map((area) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
973
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: area.kind === "PROVINCE" ? "Province" : "Postcode Prefix" }) }),
974
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: area.value }),
975
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: area.active ? "green" : "grey", children: area.active ? "Active" : "Inactive" }) }),
976
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-2", children: [
977
+ /* @__PURE__ */ jsxRuntime.jsx(
978
+ ui.Switch,
979
+ {
980
+ checked: area.active,
981
+ onCheckedChange: () => handleToggle(area)
982
+ }
983
+ ),
984
+ /* @__PURE__ */ jsxRuntime.jsx(
985
+ ui.Button,
986
+ {
987
+ variant: "secondary",
988
+ size: "small",
989
+ onClick: () => beginEdit(area),
990
+ children: "Edit"
991
+ }
992
+ ),
993
+ /* @__PURE__ */ jsxRuntime.jsx(
994
+ ui.Button,
995
+ {
996
+ variant: "danger",
997
+ size: "small",
998
+ onClick: () => handleDelete(area.id),
999
+ disabled: deletingId === area.id,
1000
+ children: "Delete"
1001
+ }
1002
+ )
1003
+ ] }) })
1004
+ ] }, area.id));
1005
+ }, [areas, beginEdit, deletingId, handleDelete, handleToggle, loading]);
1006
+ const valueLabel = formState.kind === "PROVINCE" ? "Province" : "Postcode Prefix";
1007
+ react.useEffect(() => {
1008
+ if (formState.kind === "PROVINCE") {
1009
+ setThaiAddress(
1010
+ (prev) => prev.province === formState.value ? prev : { ...prev, province: formState.value }
1011
+ );
1012
+ } else if (formState.kind === "POSTCODE_PREFIX") {
1013
+ setThaiAddress(
1014
+ (prev) => prev.zipcode === formState.value ? prev : { ...prev, zipcode: formState.value }
1015
+ );
1016
+ }
1017
+ }, [formState.kind, formState.value]);
1018
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "space-y-6", children: [
1019
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 rounded-md border p-4", children: [
1020
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: formState.id ? "Edit Service Area" : "Create Service Area" }),
1021
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 md:grid-cols-3", children: [
1022
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1023
+ /* @__PURE__ */ jsxRuntime.jsx(
1024
+ ui.Label,
1025
+ {
1026
+ className: "text-sm font-medium text-ui-fg-base",
1027
+ htmlFor: "parcel-area-kind",
1028
+ children: "Kind"
1029
+ }
1030
+ ),
1031
+ /* @__PURE__ */ jsxRuntime.jsxs(
1032
+ "select",
1033
+ {
1034
+ id: "parcel-area-kind",
1035
+ className: "mt-1 w-full rounded-md border border-ui-border-base bg-ui-bg-field px-3 py-2 text-sm",
1036
+ value: formState.kind,
1037
+ onChange: (event) => {
1038
+ const nextKind = event.target.value;
1039
+ setFormState((prev) => ({
1040
+ ...prev,
1041
+ kind: nextKind,
1042
+ value: nextKind === "PROVINCE" ? thaiAddress.province : thaiAddress.zipcode.slice(0, 3)
1043
+ }));
1044
+ },
1045
+ children: [
1046
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "PROVINCE", children: "Province" }),
1047
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "POSTCODE_PREFIX", children: "Postcode Prefix" })
1048
+ ]
1049
+ }
1050
+ )
1051
+ ] }),
1052
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
1053
+ /* @__PURE__ */ jsxRuntime.jsx(
1054
+ ui.Label,
1055
+ {
1056
+ className: "text-sm font-medium text-ui-fg-base",
1057
+ htmlFor: "parcel-area-value",
1058
+ children: valueLabel
1059
+ }
1060
+ ),
1061
+ ThaiProvinceInput && ThaiZipcodeInput ? formState.kind === "PROVINCE" ? /* @__PURE__ */ jsxRuntime.jsx(
1062
+ ThaiProvinceInput,
1063
+ {
1064
+ className: "w-full",
1065
+ value: thaiAddress.province,
1066
+ onChange: handleThaiAddressChange("province"),
1067
+ onSelect: handleThaiAddressSelect,
1068
+ autoCompleteProps: {
1069
+ style: { width: "100%" },
1070
+ dropdownMatchSelectWidth: false,
1071
+ placement: "bottomLeft"
1072
+ }
1073
+ }
1074
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1075
+ ThaiZipcodeInput,
1076
+ {
1077
+ className: "w-full",
1078
+ value: thaiAddress.zipcode,
1079
+ onChange: handleThaiAddressChange("zipcode"),
1080
+ onSelect: handleThaiAddressSelect,
1081
+ autoCompleteProps: {
1082
+ style: { width: "100%" },
1083
+ dropdownMatchSelectWidth: false,
1084
+ placement: "bottomLeft"
1085
+ }
1086
+ }
1087
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1088
+ ui.Input,
1089
+ {
1090
+ id: "parcel-area-value",
1091
+ placeholder: `Enter ${valueLabel.toLowerCase()}`,
1092
+ value: formState.value,
1093
+ onChange: (event) => setFormState((prev) => ({
1094
+ ...prev,
1095
+ value: event.target.value
1096
+ }))
1097
+ }
1098
+ ),
1099
+ formState.kind === "POSTCODE_PREFIX" && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-xs text-ui-fg-subtle", children: "Using the first three digits of the selected postal code." })
1100
+ ] })
1101
+ ] }),
1102
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
1103
+ /* @__PURE__ */ jsxRuntime.jsx(
1104
+ ui.Switch,
1105
+ {
1106
+ checked: formState.active,
1107
+ onCheckedChange: (active) => setFormState((prev) => ({ ...prev, active }))
1108
+ }
1109
+ ),
1110
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm text-ui-fg-subtle", children: "Active" })
1111
+ ] }),
1112
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [
1113
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: handleSubmit, disabled: saving, children: formState.id ? "Update" : "Create" }),
1114
+ formState.id && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: resetForm, children: "Cancel" })
1115
+ ] })
1116
+ ] }),
1117
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden rounded-md border", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
1118
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
1119
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Kind" }),
1120
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Value" }),
1121
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Status" }),
1122
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { className: "text-right", children: "Actions" })
1123
+ ] }) }),
1124
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: tableRows })
1125
+ ] }) })
1126
+ ] });
1127
+ };
1128
+ const ParcelShippingRoute = () => {
1129
+ return /* @__PURE__ */ jsxRuntime.jsx(ParcelShippingPage, {});
1130
+ };
1131
+ const config = adminSdk.defineRouteConfig({
1132
+ label: "Parcel Shipping",
1133
+ icon: icons.Directions
1134
+ });
1135
+ const widgetModule = { widgets: [] };
1136
+ const routeModule = {
1137
+ routes: [
1138
+ {
1139
+ Component: ParcelShippingRoute,
1140
+ path: "/parcel-shipping"
1141
+ }
1142
+ ]
1143
+ };
1144
+ const menuItemModule = {
1145
+ menuItems: [
1146
+ {
1147
+ label: config.label,
1148
+ icon: config.icon,
1149
+ path: "/parcel-shipping",
1150
+ nested: void 0
1151
+ }
1152
+ ]
1153
+ };
1154
+ const formModule = { customFields: {} };
1155
+ const displayModule = {
1156
+ displays: {}
1157
+ };
1158
+ const plugin = {
1159
+ widgetModule,
1160
+ routeModule,
1161
+ menuItemModule,
1162
+ formModule,
1163
+ displayModule
1164
+ };
1165
+ module.exports = plugin;