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