@lodashventure/medusa-parcel-shipping 0.2.13 → 0.2.18

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 (128) hide show
  1. package/.medusa/server/src/admin/index.js +1787 -1278
  2. package/.medusa/server/src/admin/index.mjs +1790 -1279
  3. package/.medusa/server/src/api/admin/boxes/[id]/route.js +52 -0
  4. package/.medusa/server/src/api/admin/boxes/route.js +44 -0
  5. package/.medusa/server/src/api/admin/distance/route.js +75 -0
  6. package/.medusa/server/src/api/admin/documents/document-packing-slip-settings/route.js +65 -0
  7. package/.medusa/server/{api → src/api}/admin/documents/packing-slip/preview/route.js +1 -1
  8. package/.medusa/server/{api → src/api}/admin/documents/packing-slip/route.js +2 -2
  9. package/.medusa/server/src/api/admin/material-costs/[id]/route.js +51 -0
  10. package/.medusa/server/src/api/admin/material-costs/route.js +46 -0
  11. package/.medusa/server/src/api/admin/packaging-materials/[id]/route.js +50 -0
  12. package/.medusa/server/src/api/admin/packaging-materials/route.js +52 -0
  13. package/.medusa/server/src/api/admin/packing-policies/[id]/route.js +41 -0
  14. package/.medusa/server/src/api/admin/packing-policies/route.js +43 -0
  15. package/.medusa/server/src/api/admin/rates/[id]/route.js +8 -0
  16. package/.medusa/server/src/api/admin/rates/route.js +7 -0
  17. package/.medusa/server/src/api/admin/service-areas/[id]/route.js +36 -0
  18. package/.medusa/server/src/api/admin/service-areas/route.js +40 -0
  19. package/.medusa/server/src/api/admin/shipping-rates/[id]/route.js +48 -0
  20. package/.medusa/server/src/api/admin/shipping-rates/route.js +66 -0
  21. package/.medusa/server/src/api/store/distance/route.js +75 -0
  22. package/.medusa/server/src/api/store/quote/route.js +116 -0
  23. package/.medusa/server/{common → src/common}/types.js +1 -1
  24. package/.medusa/server/src/common/utils/distance-calculator.js +98 -0
  25. package/.medusa/server/src/common/utils/geocoding.js +125 -0
  26. package/.medusa/server/{common → src/common}/utils/osrm-client.js +1 -1
  27. package/.medusa/server/src/common/utils/packing.js +484 -0
  28. package/.medusa/server/src/index.js +38 -0
  29. package/.medusa/server/{links → src/links}/index.js +1 -1
  30. package/.medusa/server/{links → src/links}/packing-slip-order.js +1 -1
  31. package/.medusa/server/{modules → src/modules}/documents/index.js +1 -1
  32. package/.medusa/server/{modules → src/modules}/documents/models/document-invoice.js +1 -1
  33. package/.medusa/server/{modules → src/modules}/documents/models/document-packing-slip-settings.js +1 -1
  34. package/.medusa/server/{modules → src/modules}/documents/models/document-packing-slip.js +1 -1
  35. package/.medusa/server/{modules → src/modules}/documents/models/document-settings.js +1 -1
  36. package/.medusa/server/{modules → src/modules}/documents/models/index.js +1 -1
  37. package/.medusa/server/{modules → src/modules}/documents/service.js +1 -1
  38. package/.medusa/server/{modules → src/modules}/documents/services/generators/invoice-generator.js +1 -1
  39. package/.medusa/server/{modules → src/modules}/documents/services/generators/packing-slip-generator.js +1 -1
  40. package/.medusa/server/{modules → src/modules}/documents/services/templates/invoices/basic/basic-logo.js +1 -1
  41. package/.medusa/server/{modules → src/modules}/documents/services/templates/invoices/basic/basic.js +1 -1
  42. package/.medusa/server/src/modules/documents/services/templates/invoices/basic/parts/customer-info.js +69 -0
  43. package/.medusa/server/{modules → src/modules}/documents/services/templates/invoices/basic/parts/header-for-logo.js +1 -1
  44. package/.medusa/server/{modules → src/modules}/documents/services/templates/invoices/basic/parts/header-logo.js +1 -1
  45. package/.medusa/server/src/modules/documents/services/templates/invoices/basic/parts/header.js +35 -0
  46. package/.medusa/server/{modules → src/modules}/documents/services/templates/invoices/basic/parts/hr.js +1 -1
  47. package/.medusa/server/{modules → src/modules}/documents/services/templates/invoices/basic/parts/invoice-info.js +1 -1
  48. package/.medusa/server/src/modules/documents/services/templates/invoices/basic/parts/table.js +97 -0
  49. package/.medusa/server/{modules → src/modules}/documents/services/templates/packing-slips/basic/basic.js +1 -1
  50. package/.medusa/server/src/modules/documents/services/templates/packing-slips/basic/parts/customer-info-small.js +69 -0
  51. package/.medusa/server/src/modules/documents/services/templates/packing-slips/basic/parts/customer-info.js +65 -0
  52. package/.medusa/server/src/modules/documents/services/templates/packing-slips/basic/parts/header-small.js +37 -0
  53. package/.medusa/server/src/modules/documents/services/templates/packing-slips/basic/parts/header.js +37 -0
  54. package/.medusa/server/{modules → src/modules}/documents/services/templates/packing-slips/basic/parts/hr.js +1 -1
  55. package/.medusa/server/src/modules/documents/services/templates/packing-slips/basic/parts/table-items-small.js +44 -0
  56. package/.medusa/server/src/modules/documents/services/templates/packing-slips/basic/parts/table-items.js +44 -0
  57. package/.medusa/server/src/modules/documents/services/templates/packing-slips/basic/parts/table-order-info-small.js +35 -0
  58. package/.medusa/server/{modules → src/modules}/documents/services/templates/packing-slips/basic/parts/table-order-info.js +1 -1
  59. package/.medusa/server/{modules → src/modules}/documents/services/templates/packing-slips/basic/small.js +1 -1
  60. package/.medusa/server/{modules → src/modules}/documents/types/api.js +1 -1
  61. package/.medusa/server/{modules → src/modules}/documents/types/constants.js +1 -1
  62. package/.medusa/server/{modules → src/modules}/documents/types/dto.js +1 -1
  63. package/.medusa/server/{modules → src/modules}/documents/types/template-kind.js +1 -1
  64. package/.medusa/server/{modules → src/modules}/documents/utils/currency.js +1 -1
  65. package/.medusa/server/src/modules/parcel-shipping/index.js +13 -0
  66. package/.medusa/server/src/modules/parcel-shipping/migrations/Migration20251023000000.js +37 -0
  67. package/.medusa/server/src/modules/parcel-shipping/models/index.js +16 -0
  68. package/.medusa/server/src/modules/parcel-shipping/models/material-cost.js +24 -0
  69. package/.medusa/server/src/modules/parcel-shipping/models/packaging-material.js +41 -0
  70. package/.medusa/server/src/modules/parcel-shipping/models/packing-policy.js +37 -0
  71. package/.medusa/server/src/modules/parcel-shipping/models/parcel-box.js +25 -0
  72. package/.medusa/server/src/modules/parcel-shipping/models/service-area.js +30 -0
  73. package/.medusa/server/src/modules/parcel-shipping/models/shipping-rate.js +50 -0
  74. package/.medusa/server/src/modules/parcel-shipping/service.js +485 -0
  75. package/.medusa/server/{providers → src/providers}/company-truck/index.js +1 -1
  76. package/.medusa/server/src/providers/company-truck/models/company-truck-rate.js +23 -0
  77. package/.medusa/server/{providers → src/providers}/company-truck/models/index.js +3 -2
  78. package/.medusa/server/src/providers/company-truck/models/material-cost.js +24 -0
  79. package/.medusa/server/src/providers/company-truck/models/parcel-box.js +21 -0
  80. package/.medusa/server/src/providers/company-truck/models/service-area.js +20 -0
  81. package/.medusa/server/src/providers/company-truck/service.js +456 -0
  82. package/.medusa/server/{providers → src/providers}/company-truck/types.js +1 -1
  83. package/.medusa/server/src/providers/index.js +8 -0
  84. package/.medusa/server/src/providers/parcel-fulfillment.js +378 -0
  85. package/.medusa/server/{providers → src/providers}/private-carrier/index.js +1 -1
  86. package/.medusa/server/{providers → src/providers}/private-carrier/models/index.js +1 -1
  87. package/.medusa/server/src/providers/private-carrier/models/parcel-box.js +21 -0
  88. package/.medusa/server/src/providers/private-carrier/models/private-carrier-rate.js +23 -0
  89. package/.medusa/server/src/providers/private-carrier/models/service-area.js +20 -0
  90. package/.medusa/server/src/providers/private-carrier/service.js +392 -0
  91. package/.medusa/server/{providers → src/providers}/private-carrier/types.js +1 -1
  92. package/.medusa/server/src/types/index.js +11 -0
  93. package/.medusa/server/src/utils/packing.js +386 -0
  94. package/.medusa/server/src/workflows/assign-packing-slip.js +35 -0
  95. package/.medusa/server/{workflows → src/workflows}/index.js +1 -1
  96. package/package.json +21 -11
  97. package/.medusa/server/api/admin/documents/document-packing-slip-settings/route.js +0 -62
  98. package/.medusa/server/api/admin/parcel-boxes/[id]/route.js +0 -45
  99. package/.medusa/server/api/admin/parcel-boxes/route.js +0 -49
  100. package/.medusa/server/api/admin/shipping-config/areas/[id]/route.js +0 -42
  101. package/.medusa/server/api/admin/shipping-config/areas/route.js +0 -49
  102. package/.medusa/server/api/admin/shipping-config/rates/[id]/route.js +0 -82
  103. package/.medusa/server/api/admin/shipping-config/rates/route.js +0 -95
  104. package/.medusa/server/api/shipping/distance/route.js +0 -85
  105. package/.medusa/server/api/shipping/distance/utils/zodTransform.js +0 -20
  106. package/.medusa/server/api/store/parcel-box-selector/route.js +0 -55
  107. package/.medusa/server/api/webhooks/tracking/route.js +0 -48
  108. package/.medusa/server/common/utils/packing.js +0 -379
  109. package/.medusa/server/index.js +0 -28
  110. package/.medusa/server/modules/documents/services/templates/invoices/basic/parts/customer-info.js +0 -69
  111. package/.medusa/server/modules/documents/services/templates/invoices/basic/parts/header.js +0 -35
  112. package/.medusa/server/modules/documents/services/templates/invoices/basic/parts/table.js +0 -97
  113. package/.medusa/server/modules/documents/services/templates/packing-slips/basic/parts/customer-info-small.js +0 -69
  114. package/.medusa/server/modules/documents/services/templates/packing-slips/basic/parts/customer-info.js +0 -65
  115. package/.medusa/server/modules/documents/services/templates/packing-slips/basic/parts/header-small.js +0 -37
  116. package/.medusa/server/modules/documents/services/templates/packing-slips/basic/parts/header.js +0 -37
  117. package/.medusa/server/modules/documents/services/templates/packing-slips/basic/parts/table-items-small.js +0 -44
  118. package/.medusa/server/modules/documents/services/templates/packing-slips/basic/parts/table-items.js +0 -44
  119. package/.medusa/server/modules/documents/services/templates/packing-slips/basic/parts/table-order-info-small.js +0 -35
  120. package/.medusa/server/providers/company-truck/models/company-truck-rate.js +0 -23
  121. package/.medusa/server/providers/company-truck/models/parcel-box.js +0 -21
  122. package/.medusa/server/providers/company-truck/models/service-area.js +0 -20
  123. package/.medusa/server/providers/company-truck/service.js +0 -456
  124. package/.medusa/server/providers/private-carrier/models/parcel-box.js +0 -21
  125. package/.medusa/server/providers/private-carrier/models/private-carrier-rate.js +0 -23
  126. package/.medusa/server/providers/private-carrier/models/service-area.js +0 -20
  127. package/.medusa/server/providers/private-carrier/service.js +0 -392
  128. package/.medusa/server/workflows/assign-packing-slip.js +0 -31
@@ -1,1535 +1,2046 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
2
- import { defineRouteConfig } from "@medusajs/admin-sdk";
3
- import { FlyingBox, Directions } from "@medusajs/icons";
4
- import { useState, useCallback, useEffect, useMemo } from "react";
5
- import Medusa from "@medusajs/js-sdk";
6
- import { toast, Table, Text, Badge, Switch, Button, Container, Heading, Label, Input } from "@medusajs/ui";
7
- const sdk$1 = new Medusa({
8
- baseUrl: typeof window !== "undefined" ? window.location.origin : "/",
9
- auth: {
10
- type: "session"
11
- }
12
- });
13
- const toNumber$1 = (value) => {
14
- if (value.trim().length === 0) {
15
- return null;
16
- }
17
- const parsed = Number(value);
18
- return Number.isFinite(parsed) ? parsed : null;
19
- };
20
- function ParcelBoxesPage() {
21
- const [boxes, setBoxes] = useState([]);
22
- const [loading, setLoading] = useState(true);
23
- const [formState, setFormState] = useState({
24
- id: null,
25
- name: "",
26
- width_cm: "",
27
- length_cm: "",
28
- height_cm: "",
29
- max_weight_kg: "",
30
- price_thb: "",
31
- active: true
32
- });
33
- const [saving, setSaving] = useState(false);
34
- const [deletingId, setDeletingId] = useState(null);
35
- const refresh = useCallback(async () => {
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
+ import { defineWidgetConfig, defineRouteConfig } from "@medusajs/admin-sdk";
3
+ import { Container, Heading, Button, Text, Badge, Drawer, Label, Select, Input, Switch, toast, Table, Prompt, Textarea } from "@medusajs/ui";
4
+ import { useState, useEffect } from "react";
5
+ import { Package, AlertCircle, Truck, Clock, Plus, Edit, Trash } from "lucide-react";
6
+ import { MapPin, ArchiveBox, CurrencyDollarSolid, HandTruck } from "@medusajs/icons";
7
+ const OrderShippingQuoteWidget = ({ data }) => {
8
+ const [quote, setQuote] = useState(null);
9
+ const [loading, setLoading] = useState(false);
10
+ const [error, setError] = useState(null);
11
+ const order = data;
12
+ const fetchShippingQuote = async () => {
36
13
  setLoading(true);
14
+ setError(null);
37
15
  try {
38
- const response = await sdk$1.client.fetch("/admin/parcel-boxes", {
39
- method: "GET"
16
+ const items = order.items.map((item) => {
17
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s;
18
+ return {
19
+ sku: ((_a = item.variant) == null ? void 0 : _a.sku) || item.title,
20
+ width: ((_b = item.variant) == null ? void 0 : _b.width) || ((_d = (_c = item.variant) == null ? void 0 : _c.product) == null ? void 0 : _d.width) || 10,
21
+ length: ((_e = item.variant) == null ? void 0 : _e.length) || ((_g = (_f = item.variant) == null ? void 0 : _f.product) == null ? void 0 : _g.length) || 10,
22
+ height: ((_h = item.variant) == null ? void 0 : _h.height) || ((_j = (_i = item.variant) == null ? void 0 : _i.product) == null ? void 0 : _j.height) || 10,
23
+ weight: ((_k = item.variant) == null ? void 0 : _k.weight) || ((_m = (_l = item.variant) == null ? void 0 : _l.product) == null ? void 0 : _m.weight) || 0.1,
24
+ quantity: item.quantity,
25
+ attributes: {
26
+ fragile: ((_o = (_n = item.variant) == null ? void 0 : _n.metadata) == null ? void 0 : _o.fragile) || false,
27
+ noStack: ((_q = (_p = item.variant) == null ? void 0 : _p.metadata) == null ? void 0 : _q.noStack) || false,
28
+ stackable: (_s = (_r = item.variant) == null ? void 0 : _r.metadata) == null ? void 0 : _s.stackable
29
+ }
30
+ };
40
31
  });
41
- const data = await response.json();
42
- setBoxes(data.boxes ?? []);
43
- } catch (error) {
44
- toast.error(error.message ?? "Failed to load boxes", {
45
- dismissable: true
32
+ const shippingAddress = order.shipping_address;
33
+ const payload = {
34
+ items,
35
+ shipping_address: {
36
+ address_line_1: (shippingAddress == null ? void 0 : shippingAddress.address_1) || "",
37
+ address_line_2: (shippingAddress == null ? void 0 : shippingAddress.address_2) || "",
38
+ city: (shippingAddress == null ? void 0 : shippingAddress.city) || "",
39
+ province: (shippingAddress == null ? void 0 : shippingAddress.province) || "",
40
+ postcode: (shippingAddress == null ? void 0 : shippingAddress.postal_code) || "",
41
+ country: (shippingAddress == null ? void 0 : shippingAddress.country_code) || "TH"
42
+ },
43
+ allow_split_boxes: true
44
+ };
45
+ const response = await fetch("/store/quote", {
46
+ method: "POST",
47
+ headers: {
48
+ "Content-Type": "application/json",
49
+ "x-publishable-api-key": "pk_99d0d47048eaf52697f42d149a7f58c1661652292b9e1ea1445f519cd16dc071"
50
+ },
51
+ body: JSON.stringify(payload)
46
52
  });
53
+ if (!response.ok) {
54
+ const errorData = await response.json();
55
+ throw new Error(errorData.message || "Failed to fetch quote");
56
+ }
57
+ const data2 = await response.json();
58
+ setQuote(data2);
59
+ } catch (err) {
60
+ setError(err.message);
47
61
  } finally {
48
62
  setLoading(false);
49
63
  }
50
- }, []);
51
- useEffect(() => {
52
- refresh();
53
- }, [refresh]);
54
- const resetForm = () => setFormState({
55
- id: null,
56
- name: "",
57
- width_cm: "",
58
- length_cm: "",
59
- height_cm: "",
60
- max_weight_kg: "",
61
- price_thb: "",
64
+ };
65
+ const getServiceName = (carrierType, serviceCode) => {
66
+ const serviceNames = {
67
+ COMPANY_FLEET_MESSENGER_3H: "ส่งด่วน 3 ชั่วโมง",
68
+ COMPANY_TRUCK_SAME_DAY: "ส่งด่วนภายในวัน",
69
+ PRIVATE_CARRIER_STANDARD_3_5D: "EMS ไปรษณีย์ (3-5 วัน)",
70
+ PRIVATE_CARRIER_EXPRESS_1_2D: "Express (1-2 วัน)"
71
+ };
72
+ return serviceNames[`${carrierType}_${serviceCode}`] || `${carrierType} - ${serviceCode}`;
73
+ };
74
+ const formatETA = (option) => {
75
+ if (option.eta_hours_min && option.eta_hours_max) {
76
+ return `${option.eta_hours_min}-${option.eta_hours_max} ชั่วโมง`;
77
+ }
78
+ if (option.eta_days_min && option.eta_days_max) {
79
+ return `${option.eta_days_min}-${option.eta_days_max} วัน`;
80
+ }
81
+ return "N/A";
82
+ };
83
+ return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
84
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
85
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
86
+ /* @__PURE__ */ jsx(Package, { className: "text-ui-fg-subtle" }),
87
+ /* @__PURE__ */ jsx(Heading, { level: "h2", children: "ตัวเลือกการจัดส่ง" })
88
+ ] }),
89
+ /* @__PURE__ */ jsx(
90
+ Button,
91
+ {
92
+ size: "small",
93
+ onClick: fetchShippingQuote,
94
+ isLoading: loading,
95
+ disabled: !order.shipping_address,
96
+ children: "คำนวณค่าจัดส่ง"
97
+ }
98
+ )
99
+ ] }),
100
+ !order.shipping_address && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-ui-fg-subtle", children: [
101
+ /* @__PURE__ */ jsx(AlertCircle, {}),
102
+ /* @__PURE__ */ jsx(Text, { size: "small", children: "กรุณาเพิ่มที่อยู่จัดส่งก่อนคำนวณค่าจัดส่ง" })
103
+ ] }) }),
104
+ error && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-red-500", children: [
105
+ /* @__PURE__ */ jsx(AlertCircle, {}),
106
+ /* @__PURE__ */ jsx(Text, { size: "small", children: error })
107
+ ] }) }),
108
+ quote && /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 space-y-4", children: [
109
+ /* @__PURE__ */ jsxs("div", { className: "rounded-lg border p-4", children: [
110
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-2", children: [
111
+ /* @__PURE__ */ jsx(Package, { className: "text-ui-fg-subtle", size: 20 }),
112
+ /* @__PURE__ */ jsxs(Text, { weight: "plus", size: "small", children: [
113
+ "กล่องที่แนะนำ: ",
114
+ quote.best_box.name
115
+ ] })
116
+ ] }),
117
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2 text-sm text-ui-fg-subtle", children: [
118
+ /* @__PURE__ */ jsxs("div", { children: [
119
+ "ขนาด: ",
120
+ quote.best_box.width_cm,
121
+ " × ",
122
+ quote.best_box.length_cm,
123
+ " ×",
124
+ " ",
125
+ quote.best_box.height_cm,
126
+ " cm"
127
+ ] }),
128
+ /* @__PURE__ */ jsxs("div", { children: [
129
+ "น้ำหนักรวม: ",
130
+ quote.packing.totalWeightKg.toFixed(2),
131
+ " kg"
132
+ ] }),
133
+ /* @__PURE__ */ jsxs("div", { children: [
134
+ "ความสูงที่ใช้: ",
135
+ quote.packing.usedHeight,
136
+ " cm"
137
+ ] }),
138
+ /* @__PURE__ */ jsxs("div", { children: [
139
+ "ว่างเหลือ: ",
140
+ quote.packing.headroom,
141
+ " cm"
142
+ ] })
143
+ ] })
144
+ ] }),
145
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
146
+ /* @__PURE__ */ jsx(Text, { weight: "plus", size: "small", children: "ตัวเลือกการจัดส่ง" }),
147
+ quote.shipping_options.map((option, index) => /* @__PURE__ */ jsxs(
148
+ "div",
149
+ {
150
+ className: `rounded-lg border p-4 ${!option.available ? "opacity-50 bg-ui-bg-subtle" : "bg-ui-bg-base"}`,
151
+ children: [
152
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between mb-2", children: [
153
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
154
+ /* @__PURE__ */ jsx(Truck, { className: "text-ui-fg-subtle", size: 20 }),
155
+ /* @__PURE__ */ jsx(Text, { weight: "plus", children: getServiceName(option.carrier_type, option.service_code) }),
156
+ option.available ? /* @__PURE__ */ jsx(Badge, { color: "green", size: "small", children: "พร้อมใช้งาน" }) : /* @__PURE__ */ jsx(Badge, { color: "red", size: "small", children: "ไม่พร้อมใช้งาน" })
157
+ ] }),
158
+ /* @__PURE__ */ jsxs(Text, { weight: "plus", size: "large", children: [
159
+ "฿",
160
+ option.price.toFixed(2)
161
+ ] })
162
+ ] }),
163
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-2 text-sm text-ui-fg-subtle mb-2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
164
+ /* @__PURE__ */ jsx(Clock, { size: 16 }),
165
+ /* @__PURE__ */ jsxs("span", { children: [
166
+ "เวลาจัดส่ง: ",
167
+ formatETA(option)
168
+ ] })
169
+ ] }) }),
170
+ !option.available && option.reason && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-sm text-red-500 mb-2", children: [
171
+ /* @__PURE__ */ jsx(AlertCircle, { size: 16 }),
172
+ /* @__PURE__ */ jsx("span", { children: option.reason })
173
+ ] }),
174
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 pt-3 border-t space-y-1 text-sm", children: [
175
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-ui-fg-subtle", children: [
176
+ /* @__PURE__ */ jsx("span", { children: "ค่ากล่อง:" }),
177
+ /* @__PURE__ */ jsxs("span", { children: [
178
+ "฿",
179
+ option.price_breakdown.box_cost.toFixed(2)
180
+ ] })
181
+ ] }),
182
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-ui-fg-subtle", children: [
183
+ /* @__PURE__ */ jsx("span", { children: "ค่าวัสดุห่อหุ้ม:" }),
184
+ /* @__PURE__ */ jsxs("span", { children: [
185
+ "฿",
186
+ option.price_breakdown.packaging_materials_cost.toFixed(
187
+ 2
188
+ )
189
+ ] })
190
+ ] }),
191
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-ui-fg-subtle", children: [
192
+ /* @__PURE__ */ jsx("span", { children: "ค่าจัดส่ง:" }),
193
+ /* @__PURE__ */ jsxs("span", { children: [
194
+ "฿",
195
+ option.price_breakdown.shipping_cost.toFixed(2)
196
+ ] })
197
+ ] }),
198
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between font-semibold pt-1 border-t", children: [
199
+ /* @__PURE__ */ jsx("span", { children: "รวมทั้งหมด:" }),
200
+ /* @__PURE__ */ jsxs("span", { children: [
201
+ "฿",
202
+ option.price_breakdown.total.toFixed(2)
203
+ ] })
204
+ ] })
205
+ ] })
206
+ ]
207
+ },
208
+ index
209
+ ))
210
+ ] })
211
+ ] })
212
+ ] });
213
+ };
214
+ defineWidgetConfig({
215
+ zone: "order.details.after"
216
+ });
217
+ const TH_PROVINCES = [
218
+ "กรุงเทพมหานคร",
219
+ "กระบี่",
220
+ "กาญจนบุรี",
221
+ "กาฬสินธุ์",
222
+ "กำแพงเพชร",
223
+ "ขอนแก่น",
224
+ "จันทบุรี",
225
+ "ฉะเชิงเทรา",
226
+ "ชลบุรี",
227
+ "ชัยนาท",
228
+ "ชัยภูมิ",
229
+ "ชุมพร",
230
+ "เชียงราย",
231
+ "เชียงใหม่",
232
+ "ตรัง",
233
+ "ตราด",
234
+ "ตาก",
235
+ "นครนายก",
236
+ "นครปฐม",
237
+ "นครพนม",
238
+ "นครราชสีมา",
239
+ "นครศรีธรรมราช",
240
+ "นครสวรรค์",
241
+ "นนทบุรี",
242
+ "นราธิวาส",
243
+ "น่าน",
244
+ "บึงกาฬ",
245
+ "บุรีรัมย์",
246
+ "ปทุมธานี",
247
+ "ประจวบคีรีขันธ์",
248
+ "ปราจีนบุรี",
249
+ "ปัตตานี",
250
+ "พระนครศรีอยุธยา",
251
+ "พะเยา",
252
+ "พังงา",
253
+ "พัทลุง",
254
+ "พิจิตร",
255
+ "พิษณุโลก",
256
+ "เพชรบุรี",
257
+ "เพชรบูรณ์",
258
+ "แพร่",
259
+ "ภูเก็ต",
260
+ "มหาสารคาม",
261
+ "มุกดาหาร",
262
+ "แม่ฮ่องสอน",
263
+ "ยโสธร",
264
+ "ยะลา",
265
+ "ร้อยเอ็ด",
266
+ "ระนอง",
267
+ "ระยอง",
268
+ "ราชบุรี",
269
+ "ลพบุรี",
270
+ "ลำปาง",
271
+ "ลำพูน",
272
+ "เลย",
273
+ "ศรีสะเกษ",
274
+ "สกลนคร",
275
+ "สงขลา",
276
+ "สตูล",
277
+ "สมุทรปราการ",
278
+ "สมุทรสงคราม",
279
+ "สมุทรสาคร",
280
+ "สระแก้ว",
281
+ "สระบุรี",
282
+ "สิงห์บุรี",
283
+ "สุโขทัย",
284
+ "สุพรรณบุรี",
285
+ "สุราษฎร์ธานี",
286
+ "สุรินทร์",
287
+ "หนองคาย",
288
+ "หนองบัวลำภู",
289
+ "อ่างทอง",
290
+ "อำนาจเจริญ",
291
+ "อุดรธานี",
292
+ "อุตรดิตถ์",
293
+ "อุทัยธานี",
294
+ "อุบลราชธานี"
295
+ ];
296
+ const ServiceAreaModal = ({ area, onClose }) => {
297
+ const [formData, setFormData] = useState({
298
+ kind: "PROVINCE",
299
+ value: "",
62
300
  active: true
63
301
  });
64
- const handleSubmit = async () => {
65
- const width = toNumber$1(formState.width_cm);
66
- const length = toNumber$1(formState.length_cm);
67
- const height = toNumber$1(formState.height_cm);
68
- const maxWeight = toNumber$1(formState.max_weight_kg);
69
- const price = toNumber$1(formState.price_thb);
70
- if (!formState.name.trim() || width === null || length === null || height === null || maxWeight === null || price === null) {
71
- toast.error("Fill all fields with valid numeric values", {
72
- dismissable: true
302
+ const [isSaving, setIsSaving] = useState(false);
303
+ useEffect(() => {
304
+ if (area) {
305
+ setFormData({
306
+ kind: area.kind,
307
+ value: area.value,
308
+ active: area.active
309
+ });
310
+ }
311
+ }, [area]);
312
+ const handleSubmit = async (e) => {
313
+ e.preventDefault();
314
+ const errors = [];
315
+ const trimmedValue = formData.value.trim();
316
+ if (!trimmedValue) {
317
+ errors.push("กรุณากรอกค่า");
318
+ }
319
+ if (formData.kind === "PROVINCE") {
320
+ if (!TH_PROVINCES.includes(trimmedValue)) {
321
+ errors.push("จังหวัดไม่ถูกต้อง กรุณาตรวจสอบการสะกดชื่อจังหวัด");
322
+ }
323
+ } else if (formData.kind === "POSTCODE_PREFIX") {
324
+ if (!/^\d{1,5}$/.test(trimmedValue)) {
325
+ errors.push("รหัสไปรษณีย์ต้องเป็นตัวเลข 1-5 หลัก");
326
+ }
327
+ }
328
+ if (errors.length > 0) {
329
+ toast.error("ข้อมูลไม่ถูกต้อง", {
330
+ description: errors.join(", ")
73
331
  });
74
332
  return;
75
333
  }
76
- const payload = {
77
- name: formState.name.trim(),
78
- width_cm: width,
79
- length_cm: length,
80
- height_cm: height,
81
- max_weight_kg: maxWeight,
82
- price_thb: price,
83
- active: formState.active
84
- };
85
- setSaving(true);
334
+ setIsSaving(true);
86
335
  try {
87
- if (formState.id) {
88
- await sdk$1.client.fetch(`/admin/parcel-boxes/${formState.id}`, {
89
- method: "PUT",
90
- body: payload
336
+ const payload = {
337
+ kind: formData.kind,
338
+ value: trimmedValue,
339
+ active: formData.active
340
+ };
341
+ const url = area ? `/admin/service-areas/${area.id}` : "/admin/service-areas";
342
+ const method = area ? "PUT" : "POST";
343
+ const response = await fetch(url, {
344
+ method,
345
+ headers: { "Content-Type": "application/json" },
346
+ credentials: "include",
347
+ body: JSON.stringify(payload)
348
+ });
349
+ if (response.ok) {
350
+ toast.success("สำเร็จ", {
351
+ description: area ? "แก้ไขพื้นที่บริการแล้ว" : "สร้างพื้นที่บริการใหม่แล้ว"
91
352
  });
92
- toast.success("Box updated", { dismissable: true });
353
+ onClose();
93
354
  } else {
94
- await sdk$1.client.fetch("/admin/parcel-boxes", {
95
- method: "POST",
96
- body: payload
97
- });
98
- toast.success("Box created", { dismissable: true });
355
+ const error = await response.json();
356
+ throw new Error(error.message || "Failed to save");
99
357
  }
100
- await refresh();
101
- resetForm();
102
358
  } catch (error) {
103
- toast.error(error.message ?? "Failed to save box", {
104
- dismissable: true
359
+ toast.error("ข้อผิดพลาด", {
360
+ description: error instanceof Error ? error.message : "ไม่สามารถบันทึกข้อมูลได้"
105
361
  });
106
362
  } finally {
107
- setSaving(false);
363
+ setIsSaving(false);
108
364
  }
109
365
  };
110
- const handleDelete = async (id) => {
111
- setDeletingId(id);
366
+ return /* @__PURE__ */ jsx(Drawer, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs(Drawer.Content, { children: [
367
+ /* @__PURE__ */ jsx(Drawer.Header, { children: /* @__PURE__ */ jsx(Drawer.Title, { children: area ? "แก้ไขพื้นที่บริการ" : "สร้างพื้นที่บริการใหม่" }) }),
368
+ /* @__PURE__ */ jsx(Drawer.Body, { children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-y-4", children: [
369
+ /* @__PURE__ */ jsxs("div", { children: [
370
+ /* @__PURE__ */ jsx(Label, { htmlFor: "kind", children: "ประเภท *" }),
371
+ /* @__PURE__ */ jsxs(
372
+ Select,
373
+ {
374
+ value: formData.kind,
375
+ onValueChange: (value) => setFormData({ ...formData, kind: value, value: "" }),
376
+ required: true,
377
+ children: [
378
+ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "เลือกประเภท" }) }),
379
+ /* @__PURE__ */ jsxs(Select.Content, { children: [
380
+ /* @__PURE__ */ jsx(Select.Item, { value: "PROVINCE", children: "จังหวัด" }),
381
+ /* @__PURE__ */ jsx(Select.Item, { value: "POSTCODE_PREFIX", children: "รหัสไปรษณีย์" })
382
+ ] })
383
+ ]
384
+ }
385
+ )
386
+ ] }),
387
+ /* @__PURE__ */ jsxs("div", { children: [
388
+ /* @__PURE__ */ jsx(Label, { htmlFor: "value", children: formData.kind === "PROVINCE" ? "ชื่อจังหวัด *" : "รหัสไปรษณีย์ (1-5 หลัก) *" }),
389
+ formData.kind === "PROVINCE" ? /* @__PURE__ */ jsxs(
390
+ Select,
391
+ {
392
+ value: formData.value,
393
+ onValueChange: (value) => setFormData({ ...formData, value }),
394
+ required: true,
395
+ children: [
396
+ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "เลือกจังหวัด" }) }),
397
+ /* @__PURE__ */ jsx(Select.Content, { children: TH_PROVINCES.map((province) => /* @__PURE__ */ jsx(Select.Item, { value: province, children: province }, province)) })
398
+ ]
399
+ }
400
+ ) : /* @__PURE__ */ jsx(
401
+ Input,
402
+ {
403
+ id: "value",
404
+ type: "text",
405
+ value: formData.value,
406
+ onChange: (e) => setFormData({ ...formData, value: e.target.value }),
407
+ placeholder: "เช่น 10, 102, 10200",
408
+ maxLength: 5,
409
+ pattern: "\\d{1,5}",
410
+ required: true
411
+ }
412
+ ),
413
+ formData.kind === "POSTCODE_PREFIX" && /* @__PURE__ */ jsx("p", { className: "text-xs text-ui-fg-subtle mt-1", children: 'ใส่รหัส 1-5 หลักเพื่อครอบคลุมหลายพื้นที่ เช่น "10" = กรุงเทพฯ ทั้งหมด' })
414
+ ] }),
415
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
416
+ /* @__PURE__ */ jsx(
417
+ Switch,
418
+ {
419
+ id: "active",
420
+ checked: formData.active,
421
+ onCheckedChange: (checked) => setFormData({ ...formData, active: checked })
422
+ }
423
+ ),
424
+ /* @__PURE__ */ jsx(Label, { htmlFor: "active", children: "เปิดใช้งาน" })
425
+ ] }),
426
+ /* @__PURE__ */ jsxs(Drawer.Footer, { children: [
427
+ /* @__PURE__ */ jsx(
428
+ Button,
429
+ {
430
+ type: "button",
431
+ variant: "secondary",
432
+ onClick: onClose,
433
+ disabled: isSaving,
434
+ children: "ยกเลิก"
435
+ }
436
+ ),
437
+ /* @__PURE__ */ jsx(Button, { type: "submit", disabled: isSaving, children: isSaving ? "กำลังบันทึก..." : "บันทึก" })
438
+ ] })
439
+ ] }) })
440
+ ] }) });
441
+ };
442
+ const AREA_KINDS = [
443
+ { value: "ALL", label: "ทั้งหมด" },
444
+ { value: "PROVINCE", label: "จังหวัด" },
445
+ { value: "POSTCODE_PREFIX", label: "รหัสไปรษณีย์" }
446
+ ];
447
+ const ServiceAreasTable = () => {
448
+ const [areas, setAreas] = useState([]);
449
+ const [filteredAreas, setFilteredAreas] = useState([]);
450
+ const [searchTerm, setSearchTerm] = useState("");
451
+ const [kindFilter, setKindFilter] = useState("ALL");
452
+ const [isModalOpen, setIsModalOpen] = useState(false);
453
+ const [editingArea, setEditingArea] = useState(null);
454
+ const [isLoading, setIsLoading] = useState(false);
455
+ const [deleteAreaId, setDeleteAreaId] = useState(null);
456
+ const [deleteAreaValue, setDeleteAreaValue] = useState("");
457
+ useEffect(() => {
458
+ fetchAreas();
459
+ }, []);
460
+ useEffect(() => {
461
+ let filtered = areas;
462
+ if (kindFilter !== "ALL") {
463
+ filtered = filtered.filter((area) => area.kind === kindFilter);
464
+ }
465
+ if (searchTerm) {
466
+ filtered = filtered.filter(
467
+ (area) => area.value.toLowerCase().includes(searchTerm.toLowerCase())
468
+ );
469
+ }
470
+ setFilteredAreas(filtered);
471
+ }, [searchTerm, kindFilter, areas]);
472
+ const fetchAreas = async () => {
473
+ setIsLoading(true);
112
474
  try {
113
- await sdk$1.client.fetch(`/admin/parcel-boxes/${id}`, {
114
- method: "DELETE"
475
+ const response = await fetch("/admin/service-areas", {
476
+ credentials: "include"
115
477
  });
116
- toast.success("Box removed", { dismissable: true });
117
- await refresh();
478
+ const data = await response.json();
479
+ console.log("Fetched service areas:", data);
480
+ setAreas(data.service_areas || []);
118
481
  } catch (error) {
119
- toast.error(error.message ?? "Failed to delete box", {
120
- dismissable: true
482
+ toast.error("ข้อผิดพลาด", {
483
+ description: "ไม่สามารถโหลดข้อมูลพื้นที่บริการได้"
121
484
  });
122
485
  } finally {
123
- setDeletingId(null);
486
+ setIsLoading(false);
124
487
  }
125
488
  };
126
- const handleToggle = async (box) => {
489
+ const handleToggleActive = async (area) => {
127
490
  try {
128
- await sdk$1.client.fetch(`/admin/parcel-boxes/${box.id}`, {
491
+ const response = await fetch(`/admin/service-areas/${area.id}`, {
129
492
  method: "PUT",
130
- body: { active: !box.active }
493
+ headers: { "Content-Type": "application/json" },
494
+ credentials: "include",
495
+ body: JSON.stringify({ ...area, active: !area.active })
131
496
  });
132
- await refresh();
497
+ if (response.ok) {
498
+ toast.success("สำเร็จ", {
499
+ description: `${area.active ? "ปิดใช้งาน" : "เปิดใช้งาน"}พื้นที่ ${area.value} แล้ว`
500
+ });
501
+ fetchAreas();
502
+ } else {
503
+ throw new Error("Failed to update");
504
+ }
133
505
  } catch (error) {
134
- toast.error(error.message ?? "Failed to update box status", {
135
- dismissable: true
506
+ toast.error("ข้อผิดพลาด", {
507
+ description: "ไม่สามารถอัพเดทสถานะได้"
136
508
  });
137
509
  }
138
510
  };
139
- const tableRows = useMemo(() => {
140
- if (loading) {
141
- 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..." }) }) });
142
- }
143
- if (boxes.length === 0) {
144
- 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." }) }) });
511
+ const handleDelete = async () => {
512
+ if (!deleteAreaId) return;
513
+ try {
514
+ const response = await fetch(`/admin/service-areas/${deleteAreaId}`, {
515
+ method: "DELETE",
516
+ credentials: "include"
517
+ });
518
+ if (response.ok) {
519
+ toast.success("สำเร็จ", {
520
+ description: `ลบพื้นที่ ${deleteAreaValue} แล้ว`
521
+ });
522
+ fetchAreas();
523
+ } else {
524
+ throw new Error("Failed to delete");
525
+ }
526
+ } catch (error) {
527
+ toast.error("ข้อผิดพลาด", {
528
+ description: "ไม่สามารถลบพื้นที่ได้"
529
+ });
530
+ } finally {
531
+ setDeleteAreaId(null);
532
+ setDeleteAreaValue("");
145
533
  }
146
- return boxes.map((box) => /* @__PURE__ */ jsxs(Table.Row, { children: [
147
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Text, { className: "font-medium", children: box.name }) }),
148
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs(Text, { className: "text-sm text-ui-fg-subtle", children: [
149
- box.width_cm,
150
- " × ",
151
- box.length_cm,
152
- " × ",
153
- box.height_cm
154
- ] }) }),
155
- /* @__PURE__ */ jsx(Table.Cell, { children: box.max_weight_kg }),
156
- /* @__PURE__ */ jsx(Table.Cell, { children: box.price_thb }),
157
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Badge, { color: box.active ? "green" : "grey", children: box.active ? "Active" : "Inactive" }) }),
158
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
159
- /* @__PURE__ */ jsx(
160
- Switch,
161
- {
162
- checked: box.active,
163
- onCheckedChange: () => handleToggle(box)
164
- }
165
- ),
166
- /* @__PURE__ */ jsx(
167
- Button,
168
- {
169
- variant: "secondary",
170
- size: "small",
171
- onClick: () => setFormState({
172
- id: box.id,
173
- name: box.name,
174
- width_cm: String(box.width_cm),
175
- length_cm: String(box.length_cm),
176
- height_cm: String(box.height_cm),
177
- max_weight_kg: String(box.max_weight_kg),
178
- price_thb: String(box.price_thb),
179
- active: box.active
180
- }),
181
- children: "Edit"
182
- }
183
- ),
184
- /* @__PURE__ */ jsx(
185
- Button,
186
- {
187
- variant: "danger",
188
- size: "small",
189
- onClick: () => handleDelete(box.id),
190
- disabled: deletingId === box.id,
191
- children: "Delete"
192
- }
193
- )
194
- ] }) })
195
- ] }, box.id));
196
- }, [boxes, deletingId, loading]);
197
- return /* @__PURE__ */ jsxs(Container, { className: "space-y-6 p-6", children: [
198
- /* @__PURE__ */ jsxs("div", { children: [
199
- /* @__PURE__ */ jsx(Heading, { level: "h1", children: "Parcel Boxes" }),
200
- /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Configure and manage parcel box sizes and pricing." })
201
- ] }),
202
- /* @__PURE__ */ jsxs("div", { className: "grid gap-4 rounded-md border p-4", children: [
203
- /* @__PURE__ */ jsx(Heading, { level: "h2", children: formState.id ? "Edit Box" : "Create Box" }),
204
- /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-3", children: [
205
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
206
- /* @__PURE__ */ jsx(
207
- Label,
208
- {
209
- className: "text-sm font-medium text-ui-fg-base",
210
- htmlFor: "parcel-box-name",
211
- children: "Name"
212
- }
213
- ),
214
- /* @__PURE__ */ jsx(
215
- Input,
216
- {
217
- id: "parcel-box-name",
218
- placeholder: "Enter box name",
219
- value: formState.name,
220
- onChange: (event) => setFormState((prev) => ({ ...prev, name: event.target.value }))
221
- }
222
- )
223
- ] }),
224
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
225
- /* @__PURE__ */ jsx(
226
- Label,
227
- {
228
- className: "text-sm font-medium text-ui-fg-base",
229
- htmlFor: "parcel-box-width",
230
- children: "Width (cm)"
231
- }
232
- ),
534
+ };
535
+ const handleEdit = (area) => {
536
+ setEditingArea(area);
537
+ setIsModalOpen(true);
538
+ };
539
+ const handleCreateNew = () => {
540
+ setEditingArea(null);
541
+ setIsModalOpen(true);
542
+ };
543
+ const handleModalClose = () => {
544
+ setIsModalOpen(false);
545
+ setEditingArea(null);
546
+ fetchAreas();
547
+ };
548
+ const openDeletePrompt = (id, value) => {
549
+ setDeleteAreaId(id);
550
+ setDeleteAreaValue(value);
551
+ };
552
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
553
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-4", children: [
554
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-x-4", children: [
555
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-x-4 flex-1", children: [
233
556
  /* @__PURE__ */ jsx(
234
557
  Input,
235
558
  {
236
- id: "parcel-box-width",
237
- placeholder: "e.g. 20",
238
- type: "number",
239
- value: formState.width_cm,
240
- onChange: (event) => setFormState((prev) => ({
241
- ...prev,
242
- width_cm: event.target.value
243
- }))
244
- }
245
- )
246
- ] }),
247
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
248
- /* @__PURE__ */ jsx(
249
- Label,
250
- {
251
- className: "text-sm font-medium text-ui-fg-base",
252
- htmlFor: "parcel-box-length",
253
- children: "Length (cm)"
559
+ type: "search",
560
+ placeholder: "ค้นหาจังหวัดหรือรหัสไปรษณีย์...",
561
+ value: searchTerm,
562
+ onChange: (e) => setSearchTerm(e.target.value),
563
+ className: "max-w-md"
254
564
  }
255
565
  ),
256
- /* @__PURE__ */ jsx(
257
- Input,
258
- {
259
- id: "parcel-box-length",
260
- placeholder: "e.g. 30",
261
- type: "number",
262
- value: formState.length_cm,
263
- onChange: (event) => setFormState((prev) => ({
264
- ...prev,
265
- length_cm: event.target.value
266
- }))
267
- }
268
- )
566
+ /* @__PURE__ */ jsxs(Select, { value: kindFilter, onValueChange: setKindFilter, children: [
567
+ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "ประเภท" }) }),
568
+ /* @__PURE__ */ jsx(Select.Content, { children: AREA_KINDS.map((kind) => /* @__PURE__ */ jsx(Select.Item, { value: kind.value, children: kind.label }, kind.value)) })
569
+ ] })
269
570
  ] }),
270
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
271
- /* @__PURE__ */ jsx(
272
- Label,
571
+ /* @__PURE__ */ jsxs(Button, { onClick: handleCreateNew, children: [
572
+ /* @__PURE__ */ jsx(Plus, {}),
573
+ "สร้างพื้นที่ใหม่"
574
+ ] })
575
+ ] }),
576
+ /* @__PURE__ */ jsxs(Table, { children: [
577
+ /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
578
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ประเภท" }),
579
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ค่า" }),
580
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "สถานะ" }),
581
+ /* @__PURE__ */ jsx(Table.HeaderCell, { className: "text-right", children: "การจัดการ" })
582
+ ] }) }),
583
+ /* @__PURE__ */ jsx(Table.Body, { children: isLoading ? /* @__PURE__ */ jsx(Table.Row, { children: /* @__PURE__ */ jsx(Table.Cell, { className: "text-center", children: "กำลังโหลด..." }) }) : filteredAreas.length === 0 ? /* @__PURE__ */ jsx(Table.Row, { children: /* @__PURE__ */ jsx(Table.Cell, { className: "text-center", children: "ไม่พบข้อมูลพื้นที่บริการ" }) }) : filteredAreas.map((area) => /* @__PURE__ */ jsxs(Table.Row, { children: [
584
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Badge, { color: area.kind === "PROVINCE" ? "blue" : "purple", children: area.kind === "PROVINCE" ? "จังหวัด" : "รหัสไปรษณีย์" }) }),
585
+ /* @__PURE__ */ jsx(Table.Cell, { children: area.value }),
586
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
587
+ Badge,
273
588
  {
274
- className: "text-sm font-medium text-ui-fg-base",
275
- htmlFor: "parcel-box-height",
276
- children: "Height (cm)"
589
+ color: area.active ? "green" : "grey",
590
+ className: "cursor-pointer",
591
+ onClick: () => handleToggleActive(area),
592
+ children: area.active ? "เปิดใช้งาน" : "ปิดใช้งาน"
277
593
  }
278
- ),
594
+ ) }),
595
+ /* @__PURE__ */ jsx(Table.Cell, { className: "text-right", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-x-2", children: [
596
+ /* @__PURE__ */ jsx(
597
+ Button,
598
+ {
599
+ variant: "transparent",
600
+ size: "small",
601
+ onClick: () => handleEdit(area),
602
+ children: /* @__PURE__ */ jsx(Edit, {})
603
+ }
604
+ ),
605
+ /* @__PURE__ */ jsx(
606
+ Button,
607
+ {
608
+ variant: "transparent",
609
+ size: "small",
610
+ onClick: () => openDeletePrompt(area.id, area.value),
611
+ children: /* @__PURE__ */ jsx(Trash, {})
612
+ }
613
+ )
614
+ ] }) })
615
+ ] }, area.id)) })
616
+ ] })
617
+ ] }),
618
+ isModalOpen && /* @__PURE__ */ jsx(ServiceAreaModal, { area: editingArea, onClose: handleModalClose }),
619
+ /* @__PURE__ */ jsx(
620
+ Prompt,
621
+ {
622
+ variant: "confirmation",
623
+ open: !!deleteAreaId,
624
+ onOpenChange: () => {
625
+ setDeleteAreaId(null);
626
+ setDeleteAreaValue("");
627
+ },
628
+ children: /* @__PURE__ */ jsxs(Prompt.Content, { children: [
629
+ /* @__PURE__ */ jsxs(Prompt.Header, { children: [
630
+ /* @__PURE__ */ jsx(Prompt.Title, { children: "ลบพื้นที่บริการ" }),
631
+ /* @__PURE__ */ jsxs(Prompt.Description, { children: [
632
+ 'คุณต้องการลบพื้นที่ "',
633
+ deleteAreaValue,
634
+ '" ใช่หรือไม่? การดำเนินการนี้ไม่สามารถย้อนกลับได้'
635
+ ] })
636
+ ] }),
637
+ /* @__PURE__ */ jsxs(Prompt.Footer, { children: [
638
+ /* @__PURE__ */ jsx(Prompt.Cancel, { children: "ยกเลิก" }),
639
+ /* @__PURE__ */ jsx(Prompt.Action, { onClick: handleDelete, children: "ลบ" })
640
+ ] })
641
+ ] })
642
+ }
643
+ )
644
+ ] });
645
+ };
646
+ const AreasPage = () => {
647
+ return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-4", children: [
648
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsx(Heading, { level: "h1", children: "พื้นที่บริการ" }) }),
649
+ /* @__PURE__ */ jsx(ServiceAreasTable, {})
650
+ ] }) });
651
+ };
652
+ const config$3 = defineRouteConfig({
653
+ icon: MapPin,
654
+ label: "พื้นที่บริการ"
655
+ });
656
+ const ParcelBoxModal = ({ box, onClose }) => {
657
+ const [formData, setFormData] = useState({
658
+ name: "",
659
+ width_cm: "",
660
+ length_cm: "",
661
+ height_cm: "",
662
+ max_weight_kg: "",
663
+ price_thb: "",
664
+ active: true
665
+ });
666
+ const [isSaving, setIsSaving] = useState(false);
667
+ useEffect(() => {
668
+ if (box) {
669
+ setFormData({
670
+ name: box.name,
671
+ width_cm: box.width_cm.toString(),
672
+ length_cm: box.length_cm.toString(),
673
+ height_cm: box.height_cm.toString(),
674
+ max_weight_kg: box.max_weight_kg.toString(),
675
+ price_thb: box.price_thb.toString(),
676
+ active: box.active
677
+ });
678
+ }
679
+ }, [box]);
680
+ const handleSubmit = async (e) => {
681
+ e.preventDefault();
682
+ const errors = [];
683
+ if (!formData.name.trim()) {
684
+ errors.push("กรุณากรอกชื่อกล่อง");
685
+ }
686
+ const width = parseFloat(formData.width_cm);
687
+ const length = parseFloat(formData.length_cm);
688
+ const height = parseFloat(formData.height_cm);
689
+ const maxWeight = parseFloat(formData.max_weight_kg);
690
+ const price = parseFloat(formData.price_thb);
691
+ if (isNaN(width) || width <= 0) {
692
+ errors.push("ความกว้างต้องเป็นตัวเลขมากกว่า 0");
693
+ }
694
+ if (isNaN(length) || length <= 0) {
695
+ errors.push("ความยาวต้องเป็นตัวเลขมากกว่า 0");
696
+ }
697
+ if (isNaN(height) || height <= 0) {
698
+ errors.push("ความสูงต้องเป็นตัวเลขมากกว่า 0");
699
+ }
700
+ if (isNaN(maxWeight) || maxWeight <= 0) {
701
+ errors.push("น้ำหนักสูงสุดต้องเป็นตัวเลขมากกว่า 0");
702
+ }
703
+ if (isNaN(price) || price < 0) {
704
+ errors.push("ราคาต้องเป็นตัวเลขไม่ติดลบ");
705
+ }
706
+ if (errors.length > 0) {
707
+ toast.error("ข้อมูลไม่ถูกต้อง", {
708
+ description: errors.join(", ")
709
+ });
710
+ return;
711
+ }
712
+ setIsSaving(true);
713
+ try {
714
+ const payload = {
715
+ name: formData.name.trim(),
716
+ width_cm: width,
717
+ length_cm: length,
718
+ height_cm: height,
719
+ max_weight_kg: maxWeight,
720
+ price_thb: price,
721
+ active: formData.active
722
+ };
723
+ const url = box ? `/admin/boxes/${box.id}` : "/admin/boxes";
724
+ const method = box ? "PUT" : "POST";
725
+ const response = await fetch(url, {
726
+ method,
727
+ headers: { "Content-Type": "application/json" },
728
+ credentials: "include",
729
+ body: JSON.stringify(payload)
730
+ });
731
+ if (response.ok) {
732
+ toast.success("สำเร็จ", {
733
+ description: box ? "แก้ไขกล่องพัสดุแล้ว" : "สร้างกล่องพัสดุใหม่แล้ว"
734
+ });
735
+ onClose();
736
+ } else {
737
+ const error = await response.json();
738
+ throw new Error(error.message || "Failed to save");
739
+ }
740
+ } catch (error) {
741
+ toast.error("ข้อผิดพลาด", {
742
+ description: error instanceof Error ? error.message : "ไม่สามารถบันทึกข้อมูลได้"
743
+ });
744
+ } finally {
745
+ setIsSaving(false);
746
+ }
747
+ };
748
+ return /* @__PURE__ */ jsx(Drawer, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs(Drawer.Content, { children: [
749
+ /* @__PURE__ */ jsx(Drawer.Header, { children: /* @__PURE__ */ jsx(Drawer.Title, { children: box ? "แก้ไขกล่องพัสดุ" : "สร้างกล่องพัสดุใหม่" }) }),
750
+ /* @__PURE__ */ jsx(Drawer.Body, { children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-y-4", children: [
751
+ /* @__PURE__ */ jsxs("div", { children: [
752
+ /* @__PURE__ */ jsx(Label, { htmlFor: "name", children: "ชื่อกล่อง *" }),
753
+ /* @__PURE__ */ jsx(
754
+ Input,
755
+ {
756
+ id: "name",
757
+ value: formData.name,
758
+ onChange: (e) => setFormData({ ...formData, name: e.target.value }),
759
+ placeholder: "เช่น กล่อง S, กล่อง M",
760
+ required: true
761
+ }
762
+ )
763
+ ] }),
764
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-x-4", children: [
765
+ /* @__PURE__ */ jsxs("div", { children: [
766
+ /* @__PURE__ */ jsx(Label, { htmlFor: "width", children: "ความกว้าง (cm) *" }),
279
767
  /* @__PURE__ */ jsx(
280
768
  Input,
281
769
  {
282
- id: "parcel-box-height",
283
- placeholder: "e.g. 15",
770
+ id: "width",
284
771
  type: "number",
285
- value: formState.height_cm,
286
- onChange: (event) => setFormState((prev) => ({
287
- ...prev,
288
- height_cm: event.target.value
289
- }))
772
+ step: "0.1",
773
+ value: formData.width_cm,
774
+ onChange: (e) => setFormData({ ...formData, width_cm: e.target.value }),
775
+ placeholder: "20",
776
+ required: true
290
777
  }
291
778
  )
292
779
  ] }),
293
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
294
- /* @__PURE__ */ jsx(
295
- Label,
296
- {
297
- className: "text-sm font-medium text-ui-fg-base",
298
- htmlFor: "parcel-box-max-weight",
299
- children: "Max Weight (kg)"
300
- }
301
- ),
780
+ /* @__PURE__ */ jsxs("div", { children: [
781
+ /* @__PURE__ */ jsx(Label, { htmlFor: "length", children: "ความยาว (cm) *" }),
302
782
  /* @__PURE__ */ jsx(
303
783
  Input,
304
784
  {
305
- id: "parcel-box-max-weight",
306
- placeholder: "e.g. 0.5",
785
+ id: "length",
307
786
  type: "number",
308
- value: formState.max_weight_kg,
309
- onChange: (event) => setFormState((prev) => ({
310
- ...prev,
311
- max_weight_kg: event.target.value
312
- }))
787
+ step: "0.1",
788
+ value: formData.length_cm,
789
+ onChange: (e) => setFormData({ ...formData, length_cm: e.target.value }),
790
+ placeholder: "30",
791
+ required: true
313
792
  }
314
793
  )
315
794
  ] }),
316
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
317
- /* @__PURE__ */ jsx(
318
- Label,
319
- {
320
- className: "text-sm font-medium text-ui-fg-base",
321
- htmlFor: "parcel-box-price",
322
- children: "Box Price (THB)"
323
- }
324
- ),
795
+ /* @__PURE__ */ jsxs("div", { children: [
796
+ /* @__PURE__ */ jsx(Label, { htmlFor: "height", children: "ความสูง (cm) *" }),
325
797
  /* @__PURE__ */ jsx(
326
798
  Input,
327
799
  {
328
- id: "parcel-box-price",
329
- placeholder: "e.g. 10",
800
+ id: "height",
330
801
  type: "number",
331
- value: formState.price_thb,
332
- onChange: (event) => setFormState((prev) => ({
333
- ...prev,
334
- price_thb: event.target.value
335
- }))
802
+ step: "0.1",
803
+ value: formData.height_cm,
804
+ onChange: (e) => setFormData({ ...formData, height_cm: e.target.value }),
805
+ placeholder: "15",
806
+ required: true
336
807
  }
337
808
  )
338
809
  ] })
339
810
  ] }),
340
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
811
+ /* @__PURE__ */ jsxs("div", { children: [
812
+ /* @__PURE__ */ jsx(Label, { htmlFor: "maxWeight", children: "น้ำหนักสูงสุด (kg) *" }),
813
+ /* @__PURE__ */ jsx(
814
+ Input,
815
+ {
816
+ id: "maxWeight",
817
+ type: "number",
818
+ step: "0.1",
819
+ value: formData.max_weight_kg,
820
+ onChange: (e) => setFormData({ ...formData, max_weight_kg: e.target.value }),
821
+ placeholder: "0.5",
822
+ required: true
823
+ }
824
+ )
825
+ ] }),
826
+ /* @__PURE__ */ jsxs("div", { children: [
827
+ /* @__PURE__ */ jsx(Label, { htmlFor: "price", children: "ราคา (THB) *" }),
828
+ /* @__PURE__ */ jsx(
829
+ Input,
830
+ {
831
+ id: "price",
832
+ type: "number",
833
+ step: "0.01",
834
+ value: formData.price_thb,
835
+ onChange: (e) => setFormData({ ...formData, price_thb: e.target.value }),
836
+ placeholder: "10.00",
837
+ required: true
838
+ }
839
+ )
840
+ ] }),
841
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
341
842
  /* @__PURE__ */ jsx(
342
843
  Switch,
343
844
  {
344
- checked: formState.active,
345
- onCheckedChange: (active) => setFormState((prev) => ({ ...prev, active }))
845
+ id: "active",
846
+ checked: formData.active,
847
+ onCheckedChange: (checked) => setFormData({ ...formData, active: checked })
346
848
  }
347
849
  ),
348
- /* @__PURE__ */ jsx(Text, { className: "text-sm text-ui-fg-subtle", children: "Active" })
850
+ /* @__PURE__ */ jsx(Label, { htmlFor: "active", children: "เปิดใช้งาน" })
349
851
  ] }),
350
- /* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
351
- /* @__PURE__ */ jsx(Button, { onClick: handleSubmit, disabled: saving, children: formState.id ? "Update" : "Create" }),
352
- formState.id && /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: resetForm, children: "Cancel" })
852
+ /* @__PURE__ */ jsxs(Drawer.Footer, { children: [
853
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", onClick: onClose, disabled: isSaving, children: "ยกเลิก" }),
854
+ /* @__PURE__ */ jsx(Button, { type: "submit", disabled: isSaving, children: isSaving ? "กำลังบันทึก..." : "บันทึก" })
353
855
  ] })
354
- ] }),
355
- /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-md border", children: /* @__PURE__ */ jsxs(Table, { children: [
356
- /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
357
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Name" }),
358
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Dimensions (cm)" }),
359
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Max Weight (kg)" }),
360
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Price" }),
361
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Status" }),
362
- /* @__PURE__ */ jsx(Table.HeaderCell, { className: "text-right", children: "Actions" })
363
- ] }) }),
364
- /* @__PURE__ */ jsx(Table.Body, { children: tableRows })
365
856
  ] }) })
366
- ] });
367
- }
368
- const ParcelBoxesRoute = () => {
369
- return /* @__PURE__ */ jsx(ParcelBoxesPage, {});
857
+ ] }) });
370
858
  };
371
- const config$1 = defineRouteConfig({
372
- label: "Parcel Boxes",
373
- icon: FlyingBox
374
- });
375
- const sdk = new Medusa({
376
- baseUrl: typeof window !== "undefined" ? window.location.origin : "/",
377
- auth: {
378
- type: "session"
379
- }
380
- });
381
- const toNumber = (value) => {
382
- if (value.trim().length === 0) {
383
- return null;
384
- }
385
- const parsed = Number(value);
386
- return Number.isFinite(parsed) ? parsed : null;
387
- };
388
- const getThaiAddressComponents = () => null;
389
- function ParcelShippingPage() {
390
- const [tab, setTab] = useState("boxes");
391
- return /* @__PURE__ */ jsxs(Container, { className: "space-y-6 p-6", children: [
392
- /* @__PURE__ */ jsxs("div", { children: [
393
- /* @__PURE__ */ jsx(Heading, { level: "h1", children: "Parcel Shipping" }),
394
- /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Configure parcel boxes, shipping rates, and supported service areas." })
395
- ] }),
396
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
397
- /* @__PURE__ */ jsx(
398
- Button,
399
- {
400
- variant: tab === "boxes" ? "primary" : "secondary",
401
- size: "small",
402
- onClick: () => setTab("boxes"),
403
- children: "Boxes"
404
- }
405
- ),
406
- /* @__PURE__ */ jsx(
407
- Button,
408
- {
409
- variant: tab === "rates" ? "primary" : "secondary",
410
- size: "small",
411
- onClick: () => setTab("rates"),
412
- children: "Shipping Rates"
413
- }
414
- ),
415
- /* @__PURE__ */ jsx(
416
- Button,
417
- {
418
- variant: tab === "areas" ? "primary" : "secondary",
419
- size: "small",
420
- onClick: () => setTab("areas"),
421
- children: "Service Areas"
422
- }
423
- )
424
- ] }),
425
- /* @__PURE__ */ jsxs("div", { className: "pt-6", children: [
426
- tab === "boxes" && /* @__PURE__ */ jsx(BoxesSection, {}),
427
- tab === "rates" && /* @__PURE__ */ jsx(RatesSection, {}),
428
- tab === "areas" && /* @__PURE__ */ jsx(AreasSection, {})
429
- ] })
430
- ] });
431
- }
432
- const BoxesSection = () => {
859
+ const ParcelBoxesTable = () => {
433
860
  const [boxes, setBoxes] = useState([]);
434
- const [loading, setLoading] = useState(true);
435
- const [formState, setFormState] = useState({
436
- id: null,
437
- name: "",
438
- width_cm: "",
439
- length_cm: "",
440
- height_cm: "",
441
- max_weight_kg: "",
442
- price_thb: "",
443
- active: true
444
- });
445
- const [saving, setSaving] = useState(false);
446
- const [deletingId, setDeletingId] = useState(null);
447
- const refresh = useCallback(async () => {
448
- setLoading(true);
861
+ const [filteredBoxes, setFilteredBoxes] = useState([]);
862
+ const [searchTerm, setSearchTerm] = useState("");
863
+ const [isModalOpen, setIsModalOpen] = useState(false);
864
+ const [editingBox, setEditingBox] = useState(null);
865
+ const [isLoading, setIsLoading] = useState(false);
866
+ const [deleteBoxId, setDeleteBoxId] = useState(null);
867
+ const [deleteBoxName, setDeleteBoxName] = useState("");
868
+ useEffect(() => {
869
+ fetchBoxes();
870
+ }, []);
871
+ useEffect(() => {
872
+ const filtered = boxes.filter(
873
+ (box) => box.name.toLowerCase().includes(searchTerm.toLowerCase())
874
+ );
875
+ setFilteredBoxes(filtered);
876
+ }, [searchTerm, boxes]);
877
+ const fetchBoxes = async () => {
878
+ setIsLoading(true);
449
879
  try {
450
- const response = await sdk.client.fetch("/admin/parcel-boxes", {
451
- method: "GET"
880
+ const response = await fetch("/admin/boxes", {
881
+ credentials: "include"
452
882
  });
453
883
  const data = await response.json();
454
- setBoxes(data.boxes ?? []);
884
+ setBoxes(data.boxes || []);
455
885
  } catch (error) {
456
- toast.error(error.message ?? "Failed to load boxes", {
457
- dismissable: true
886
+ toast.error("ข้อผิดพลาด", {
887
+ description: "ไม่สามารถโหลดข้อมูลกล่องพัสดุได้"
458
888
  });
459
889
  } finally {
460
- setLoading(false);
461
- }
462
- }, []);
463
- useEffect(() => {
464
- refresh();
465
- }, [refresh]);
466
- const resetForm = () => setFormState({
467
- id: null,
468
- name: "",
469
- width_cm: "",
470
- length_cm: "",
471
- height_cm: "",
472
- max_weight_kg: "",
473
- price_thb: "",
474
- active: true
475
- });
476
- const handleSubmit = async () => {
477
- const width = toNumber(formState.width_cm);
478
- const length = toNumber(formState.length_cm);
479
- const height = toNumber(formState.height_cm);
480
- const maxWeight = toNumber(formState.max_weight_kg);
481
- const price = toNumber(formState.price_thb);
482
- if (!formState.name.trim() || width === null || length === null || height === null || maxWeight === null || price === null) {
483
- toast.error("Fill all fields with valid numeric values", {
484
- dismissable: true
485
- });
486
- return;
890
+ setIsLoading(false);
487
891
  }
488
- const payload = {
489
- name: formState.name.trim(),
490
- width_cm: width,
491
- length_cm: length,
492
- height_cm: height,
493
- max_weight_kg: maxWeight,
494
- price_thb: price,
495
- active: formState.active
496
- };
497
- setSaving(true);
892
+ };
893
+ const handleToggleActive = async (box) => {
498
894
  try {
499
- if (formState.id) {
500
- await sdk.client.fetch(`/admin/parcel-boxes/${formState.id}`, {
501
- method: "PUT",
502
- body: payload
895
+ const response = await fetch(`/admin/boxes/${box.id}`, {
896
+ method: "PUT",
897
+ headers: { "Content-Type": "application/json" },
898
+ credentials: "include",
899
+ body: JSON.stringify({ ...box, active: !box.active })
900
+ });
901
+ if (response.ok) {
902
+ toast.success("สำเร็จ", {
903
+ description: `${box.active ? "ปิดใช้งาน" : "เปิดใช้งาน"}กล่อง ${box.name} แล้ว`
503
904
  });
504
- toast.success("Box updated", { dismissable: true });
905
+ fetchBoxes();
505
906
  } else {
506
- await sdk.client.fetch("/admin/parcel-boxes", {
507
- method: "POST",
508
- body: payload
509
- });
510
- toast.success("Box created", { dismissable: true });
907
+ throw new Error("Failed to update");
511
908
  }
512
- await refresh();
513
- resetForm();
514
909
  } catch (error) {
515
- toast.error(error.message ?? "Failed to save box", {
516
- dismissable: true
910
+ toast.error("ข้อผิดพลาด", {
911
+ description: "ไม่สามารถอัพเดทสถานะได้"
517
912
  });
518
- } finally {
519
- setSaving(false);
520
913
  }
521
914
  };
522
- const handleDelete = async (id) => {
523
- setDeletingId(id);
915
+ const handleDelete = async () => {
916
+ if (!deleteBoxId) return;
524
917
  try {
525
- await sdk.client.fetch(`/admin/parcel-boxes/${id}`, {
526
- method: "DELETE"
918
+ const response = await fetch(`/admin/boxes/${deleteBoxId}`, {
919
+ method: "DELETE",
920
+ credentials: "include"
527
921
  });
528
- toast.success("Box removed", { dismissable: true });
529
- await refresh();
922
+ if (response.ok) {
923
+ toast.success("สำเร็จ", {
924
+ description: `ลบกล่อง ${deleteBoxName} แล้ว`
925
+ });
926
+ fetchBoxes();
927
+ } else {
928
+ throw new Error("Failed to delete");
929
+ }
530
930
  } catch (error) {
531
- toast.error(error.message ?? "Failed to delete box", {
532
- dismissable: true
931
+ toast.error("ข้อผิดพลาด", {
932
+ description: "ไม่สามารถลบกล่องได้"
533
933
  });
534
934
  } finally {
535
- setDeletingId(null);
935
+ setDeleteBoxId(null);
936
+ setDeleteBoxName("");
536
937
  }
537
938
  };
538
- const handleToggle = async (box) => {
939
+ const handleEdit = (box) => {
940
+ setEditingBox(box);
941
+ setIsModalOpen(true);
942
+ };
943
+ const handleCreateNew = () => {
944
+ setEditingBox(null);
945
+ setIsModalOpen(true);
946
+ };
947
+ const handleModalClose = () => {
948
+ setIsModalOpen(false);
949
+ setEditingBox(null);
950
+ fetchBoxes();
951
+ };
952
+ const openDeletePrompt = (id, name) => {
953
+ setDeleteBoxId(id);
954
+ setDeleteBoxName(name);
955
+ };
956
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
957
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-4", children: [
958
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-x-4", children: [
959
+ /* @__PURE__ */ jsx(
960
+ Input,
961
+ {
962
+ type: "search",
963
+ placeholder: "ค้นหาชื่อกล่อง...",
964
+ value: searchTerm,
965
+ onChange: (e) => setSearchTerm(e.target.value),
966
+ className: "max-w-md"
967
+ }
968
+ ),
969
+ /* @__PURE__ */ jsxs(Button, { onClick: handleCreateNew, children: [
970
+ /* @__PURE__ */ jsx(Plus, {}),
971
+ "สร้างกล่องใหม่"
972
+ ] })
973
+ ] }),
974
+ /* @__PURE__ */ jsxs(Table, { children: [
975
+ /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
976
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ชื่อ" }),
977
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ขนาด (กว้าง×ยาว×สูง cm)" }),
978
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "น้ำหนักสูงสุด (kg)" }),
979
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ราคา (THB)" }),
980
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "สถานะ" }),
981
+ /* @__PURE__ */ jsx(Table.HeaderCell, { className: "text-right", children: "การจัดการ" })
982
+ ] }) }),
983
+ /* @__PURE__ */ jsx(Table.Body, { children: isLoading ? /* @__PURE__ */ jsx(Table.Row, { children: /* @__PURE__ */ jsx(Table.Cell, { className: "text-center", children: "กำลังโหลด..." }) }) : filteredBoxes.length === 0 ? /* @__PURE__ */ jsx(Table.Row, { children: /* @__PURE__ */ jsx(Table.Cell, { className: "text-center", children: "ไม่พบข้อมูลกล่องพัสดุ" }) }) : filteredBoxes.map((box) => /* @__PURE__ */ jsxs(Table.Row, { children: [
984
+ /* @__PURE__ */ jsx(Table.Cell, { children: box.name }),
985
+ /* @__PURE__ */ jsxs(Table.Cell, { children: [
986
+ box.width_cm,
987
+ " × ",
988
+ box.length_cm,
989
+ " × ",
990
+ box.height_cm
991
+ ] }),
992
+ /* @__PURE__ */ jsx(Table.Cell, { children: box.max_weight_kg }),
993
+ /* @__PURE__ */ jsx(Table.Cell, { children: box.price_thb.toFixed(2) }),
994
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
995
+ Badge,
996
+ {
997
+ color: box.active ? "green" : "grey",
998
+ className: "cursor-pointer",
999
+ onClick: () => handleToggleActive(box),
1000
+ children: box.active ? "เปิดใช้งาน" : "ปิดใช้งาน"
1001
+ }
1002
+ ) }),
1003
+ /* @__PURE__ */ jsx(Table.Cell, { className: "text-right", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-x-2", children: [
1004
+ /* @__PURE__ */ jsx(
1005
+ Button,
1006
+ {
1007
+ variant: "transparent",
1008
+ size: "small",
1009
+ onClick: () => handleEdit(box),
1010
+ children: /* @__PURE__ */ jsx(Edit, {})
1011
+ }
1012
+ ),
1013
+ /* @__PURE__ */ jsx(
1014
+ Button,
1015
+ {
1016
+ variant: "transparent",
1017
+ size: "small",
1018
+ onClick: () => openDeletePrompt(box.id, box.name),
1019
+ children: /* @__PURE__ */ jsx(Trash, {})
1020
+ }
1021
+ )
1022
+ ] }) })
1023
+ ] }, box.id)) })
1024
+ ] })
1025
+ ] }),
1026
+ isModalOpen && /* @__PURE__ */ jsx(ParcelBoxModal, { box: editingBox, onClose: handleModalClose }),
1027
+ /* @__PURE__ */ jsx(
1028
+ Prompt,
1029
+ {
1030
+ variant: "confirmation",
1031
+ open: !!deleteBoxId,
1032
+ onOpenChange: () => {
1033
+ setDeleteBoxId(null);
1034
+ setDeleteBoxName("");
1035
+ },
1036
+ children: /* @__PURE__ */ jsxs(Prompt.Content, { children: [
1037
+ /* @__PURE__ */ jsxs(Prompt.Header, { children: [
1038
+ /* @__PURE__ */ jsx(Prompt.Title, { children: "ลบกล่องพัสดุ" }),
1039
+ /* @__PURE__ */ jsxs(Prompt.Description, { children: [
1040
+ 'คุณต้องการลบกล่อง "',
1041
+ deleteBoxName,
1042
+ '" ใช่หรือไม่? การดำเนินการนี้ไม่สามารถย้อนกลับได้'
1043
+ ] })
1044
+ ] }),
1045
+ /* @__PURE__ */ jsxs(Prompt.Footer, { children: [
1046
+ /* @__PURE__ */ jsx(Prompt.Cancel, { children: "ยกเลิก" }),
1047
+ /* @__PURE__ */ jsx(Prompt.Action, { onClick: handleDelete, children: "ลบ" })
1048
+ ] })
1049
+ ] })
1050
+ }
1051
+ )
1052
+ ] });
1053
+ };
1054
+ const BoxesPage = () => {
1055
+ return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-4", children: [
1056
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsx(Heading, { level: "h1", children: "กล่องพัสดุ" }) }),
1057
+ /* @__PURE__ */ jsx(ParcelBoxesTable, {})
1058
+ ] }) });
1059
+ };
1060
+ const config$2 = defineRouteConfig({
1061
+ icon: ArchiveBox,
1062
+ label: "กล่องพัสดุ"
1063
+ });
1064
+ const CATEGORIES$1 = [
1065
+ { value: "PACKAGING", label: "วัสดุหีบห่อ" },
1066
+ { value: "PROTECTION", label: "วัสดุป้องกัน" },
1067
+ { value: "FILLING", label: "วัสดุเติมช่องว่าง" },
1068
+ { value: "TAPE", label: "เทป/กาว" },
1069
+ { value: "LABEL", label: "ฉลาก/สติกเกอร์" },
1070
+ { value: "OTHER", label: "อื่นๆ" }
1071
+ ];
1072
+ const MaterialCostModal = ({
1073
+ materialCost,
1074
+ onClose
1075
+ }) => {
1076
+ const [formData, setFormData] = useState({
1077
+ name: "",
1078
+ unit: "",
1079
+ cost_per_unit: "",
1080
+ currency: "THB",
1081
+ category: "",
1082
+ description: "",
1083
+ active: true
1084
+ });
1085
+ const [isSaving, setIsSaving] = useState(false);
1086
+ useEffect(() => {
1087
+ if (materialCost) {
1088
+ setFormData({
1089
+ name: materialCost.name,
1090
+ unit: materialCost.unit,
1091
+ cost_per_unit: materialCost.cost_per_unit.toString(),
1092
+ currency: materialCost.currency,
1093
+ category: materialCost.category || "",
1094
+ description: materialCost.description || "",
1095
+ active: materialCost.active
1096
+ });
1097
+ }
1098
+ }, [materialCost]);
1099
+ const handleSubmit = async (e) => {
1100
+ e.preventDefault();
1101
+ const errors = [];
1102
+ if (!formData.name.trim()) {
1103
+ errors.push("กรุณากรอกชื่อวัสดุ");
1104
+ }
1105
+ if (!formData.unit.trim()) {
1106
+ errors.push("กรุณากรอกหน่วย");
1107
+ }
1108
+ const costPerUnit = parseFloat(formData.cost_per_unit);
1109
+ if (isNaN(costPerUnit) || costPerUnit < 0) {
1110
+ errors.push("ต้นทุนต่อหน่วยต้องเป็นตัวเลขไม่ติดลบ");
1111
+ }
1112
+ if (errors.length > 0) {
1113
+ toast.error("ข้อมูลไม่ถูกต้อง", {
1114
+ description: errors.join(", ")
1115
+ });
1116
+ return;
1117
+ }
1118
+ setIsSaving(true);
539
1119
  try {
540
- await sdk.client.fetch(`/admin/parcel-boxes/${box.id}`, {
541
- method: "PUT",
542
- body: { active: !box.active }
1120
+ const payload = {
1121
+ name: formData.name.trim(),
1122
+ unit: formData.unit.trim(),
1123
+ cost_per_unit: costPerUnit,
1124
+ currency: formData.currency,
1125
+ category: formData.category || void 0,
1126
+ description: formData.description.trim() || void 0,
1127
+ active: formData.active
1128
+ };
1129
+ const url = materialCost ? `/admin/material-costs/${materialCost.id}` : "/admin/material-costs";
1130
+ const method = materialCost ? "PUT" : "POST";
1131
+ const response = await fetch(url, {
1132
+ method,
1133
+ headers: { "Content-Type": "application/json" },
1134
+ credentials: "include",
1135
+ body: JSON.stringify(payload)
543
1136
  });
544
- await refresh();
1137
+ if (response.ok) {
1138
+ toast.success("สำเร็จ", {
1139
+ description: materialCost ? "แก้ไขรายการต้นทุนวัสดุแล้ว" : "สร้างรายการต้นทุนวัสดุใหม่แล้ว"
1140
+ });
1141
+ onClose();
1142
+ } else {
1143
+ const error = await response.json();
1144
+ throw new Error(error.message || "Failed to save");
1145
+ }
545
1146
  } catch (error) {
546
- toast.error(error.message ?? "Failed to update box status", {
547
- dismissable: true
1147
+ toast.error("ข้อผิดพลาด", {
1148
+ description: error instanceof Error ? error.message : "ไม่สามารถบันทึกข้อมูลได้"
548
1149
  });
1150
+ } finally {
1151
+ setIsSaving(false);
549
1152
  }
550
1153
  };
551
- const tableRows = useMemo(() => {
552
- if (loading) {
553
- 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..." }) }) });
554
- }
555
- if (boxes.length === 0) {
556
- 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." }) }) });
557
- }
558
- return boxes.map((box) => /* @__PURE__ */ jsxs(Table.Row, { children: [
559
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Text, { className: "font-medium", children: box.name }) }),
560
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs(Text, { className: "text-sm text-ui-fg-subtle", children: [
561
- box.width_cm,
562
- " × ",
563
- box.length_cm,
564
- " × ",
565
- box.height_cm
566
- ] }) }),
567
- /* @__PURE__ */ jsx(Table.Cell, { children: box.max_weight_kg }),
568
- /* @__PURE__ */ jsx(Table.Cell, { children: box.price_thb }),
569
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Badge, { color: box.active ? "green" : "grey", children: box.active ? "Active" : "Inactive" }) }),
570
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
1154
+ return /* @__PURE__ */ jsx(Drawer, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs(Drawer.Content, { children: [
1155
+ /* @__PURE__ */ jsx(Drawer.Header, { children: /* @__PURE__ */ jsx(Drawer.Title, { children: materialCost ? "แก้ไขรายการต้นทุนวัสดุ" : "สร้างรายการต้นทุนวัสดุใหม่" }) }),
1156
+ /* @__PURE__ */ jsx(Drawer.Body, { children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-y-4", children: [
1157
+ /* @__PURE__ */ jsxs("div", { children: [
1158
+ /* @__PURE__ */ jsx(Label, { htmlFor: "name", children: "ชื่อวัสดุ *" }),
571
1159
  /* @__PURE__ */ jsx(
572
- Switch,
1160
+ Input,
573
1161
  {
574
- checked: box.active,
575
- onCheckedChange: () => handleToggle(box)
1162
+ id: "name",
1163
+ value: formData.name,
1164
+ onChange: (e) => setFormData({ ...formData, name: e.target.value }),
1165
+ placeholder: "เช่น พลาสติกกันกระแทก, กล่องกระดาษ",
1166
+ required: true
576
1167
  }
577
- ),
578
- /* @__PURE__ */ jsx(
579
- Button,
580
- {
581
- variant: "secondary",
582
- size: "small",
583
- onClick: () => setFormState({
584
- id: box.id,
585
- name: box.name,
586
- width_cm: String(box.width_cm),
587
- length_cm: String(box.length_cm),
588
- height_cm: String(box.height_cm),
589
- max_weight_kg: String(box.max_weight_kg),
590
- price_thb: String(box.price_thb),
591
- active: box.active
592
- }),
593
- children: "Edit"
594
- }
595
- ),
596
- /* @__PURE__ */ jsx(
597
- Button,
1168
+ )
1169
+ ] }),
1170
+ /* @__PURE__ */ jsxs("div", { children: [
1171
+ /* @__PURE__ */ jsx(Label, { htmlFor: "category", children: "หมวดหมู่ (ไม่บังคับ)" }),
1172
+ /* @__PURE__ */ jsxs(
1173
+ Select,
598
1174
  {
599
- variant: "danger",
600
- size: "small",
601
- onClick: () => handleDelete(box.id),
602
- disabled: deletingId === box.id,
603
- children: "Delete"
1175
+ value: formData.category || void 0,
1176
+ onValueChange: (value) => setFormData({ ...formData, category: value }),
1177
+ children: [
1178
+ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "เลือกหมวดหมู่ (ไม่บังคับ)" }) }),
1179
+ /* @__PURE__ */ jsx(Select.Content, { children: CATEGORIES$1.map((category) => /* @__PURE__ */ jsx(Select.Item, { value: category.value, children: category.label }, category.value)) })
1180
+ ]
604
1181
  }
605
1182
  )
606
- ] }) })
607
- ] }, box.id));
608
- }, [boxes, deletingId, loading]);
609
- return /* @__PURE__ */ jsxs("section", { className: "space-y-6", children: [
610
- /* @__PURE__ */ jsxs("div", { className: "grid gap-4 rounded-md border p-4", children: [
611
- /* @__PURE__ */ jsx(Heading, { level: "h2", children: formState.id ? "Edit Box" : "Create Box" }),
612
- /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-3", children: [
613
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
614
- /* @__PURE__ */ jsx(
615
- Label,
616
- {
617
- className: "text-sm font-medium text-ui-fg-base",
618
- htmlFor: "parcel-box-name",
619
- children: "Name"
620
- }
621
- ),
622
- /* @__PURE__ */ jsx(
623
- Input,
624
- {
625
- id: "parcel-box-name",
626
- placeholder: "Enter box name",
627
- value: formState.name,
628
- onChange: (event) => setFormState((prev) => ({ ...prev, name: event.target.value }))
629
- }
630
- )
631
- ] }),
632
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
633
- /* @__PURE__ */ jsx(
634
- Label,
635
- {
636
- className: "text-sm font-medium text-ui-fg-base",
637
- htmlFor: "parcel-box-width",
638
- children: "Width (cm)"
639
- }
640
- ),
1183
+ ] }),
1184
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-x-4", children: [
1185
+ /* @__PURE__ */ jsxs("div", { children: [
1186
+ /* @__PURE__ */ jsx(Label, { htmlFor: "unit", children: "หน่วย *" }),
641
1187
  /* @__PURE__ */ jsx(
642
1188
  Input,
643
1189
  {
644
- id: "parcel-box-width",
645
- placeholder: "e.g. 20",
646
- type: "number",
647
- value: formState.width_cm,
648
- onChange: (event) => setFormState((prev) => ({
649
- ...prev,
650
- width_cm: event.target.value
651
- }))
1190
+ id: "unit",
1191
+ value: formData.unit,
1192
+ onChange: (e) => setFormData({ ...formData, unit: e.target.value }),
1193
+ placeholder: "เช่น ชิ้น, เมตร, กิโลกรัม",
1194
+ required: true
652
1195
  }
653
1196
  )
654
1197
  ] }),
655
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
656
- /* @__PURE__ */ jsx(
657
- Label,
658
- {
659
- className: "text-sm font-medium text-ui-fg-base",
660
- htmlFor: "parcel-box-length",
661
- children: "Length (cm)"
662
- }
663
- ),
1198
+ /* @__PURE__ */ jsxs("div", { children: [
1199
+ /* @__PURE__ */ jsx(Label, { htmlFor: "costPerUnit", children: "ต้นทุนต่อหน่วย *" }),
664
1200
  /* @__PURE__ */ jsx(
665
1201
  Input,
666
1202
  {
667
- id: "parcel-box-length",
668
- placeholder: "e.g. 30",
1203
+ id: "costPerUnit",
669
1204
  type: "number",
670
- value: formState.length_cm,
671
- onChange: (event) => setFormState((prev) => ({
672
- ...prev,
673
- length_cm: event.target.value
674
- }))
1205
+ step: "0.01",
1206
+ value: formData.cost_per_unit,
1207
+ onChange: (e) => setFormData({ ...formData, cost_per_unit: e.target.value }),
1208
+ placeholder: "0.00",
1209
+ required: true
675
1210
  }
676
1211
  )
677
- ] }),
678
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
679
- /* @__PURE__ */ jsx(
680
- Label,
681
- {
682
- className: "text-sm font-medium text-ui-fg-base",
683
- htmlFor: "parcel-box-height",
684
- children: "Height (cm)"
685
- }
686
- ),
1212
+ ] })
1213
+ ] }),
1214
+ /* @__PURE__ */ jsxs("div", { children: [
1215
+ /* @__PURE__ */ jsx(Label, { htmlFor: "currency", children: "สกุลเงิน" }),
1216
+ /* @__PURE__ */ jsx(
1217
+ Input,
1218
+ {
1219
+ id: "currency",
1220
+ value: formData.currency,
1221
+ onChange: (e) => setFormData({ ...formData, currency: e.target.value }),
1222
+ placeholder: "THB"
1223
+ }
1224
+ )
1225
+ ] }),
1226
+ /* @__PURE__ */ jsxs("div", { children: [
1227
+ /* @__PURE__ */ jsx(Label, { htmlFor: "description", children: "คำอธิบาย" }),
1228
+ /* @__PURE__ */ jsx(
1229
+ Textarea,
1230
+ {
1231
+ id: "description",
1232
+ value: formData.description,
1233
+ onChange: (e) => setFormData({ ...formData, description: e.target.value }),
1234
+ placeholder: "คำอธิบายเพิ่มเติมเกี่ยวกับวัสดุนี้...",
1235
+ rows: 3
1236
+ }
1237
+ )
1238
+ ] }),
1239
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
1240
+ /* @__PURE__ */ jsx(
1241
+ Switch,
1242
+ {
1243
+ id: "active",
1244
+ checked: formData.active,
1245
+ onCheckedChange: (checked) => setFormData({ ...formData, active: checked })
1246
+ }
1247
+ ),
1248
+ /* @__PURE__ */ jsx(Label, { htmlFor: "active", children: "เปิดใช้งาน" })
1249
+ ] }),
1250
+ /* @__PURE__ */ jsxs(Drawer.Footer, { children: [
1251
+ /* @__PURE__ */ jsx(
1252
+ Button,
1253
+ {
1254
+ type: "button",
1255
+ variant: "secondary",
1256
+ onClick: onClose,
1257
+ disabled: isSaving,
1258
+ children: "ยกเลิก"
1259
+ }
1260
+ ),
1261
+ /* @__PURE__ */ jsx(Button, { type: "submit", disabled: isSaving, children: isSaving ? "กำลังบันทึก..." : "บันทึก" })
1262
+ ] })
1263
+ ] }) })
1264
+ ] }) });
1265
+ };
1266
+ const CATEGORIES = [
1267
+ { value: "ALL", label: "ทั้งหมด" },
1268
+ { value: "PACKAGING", label: "วัสดุหีบห่อ" },
1269
+ { value: "PROTECTION", label: "วัสดุป้องกัน" },
1270
+ { value: "FILLING", label: "วัสดุเติมช่องว่าง" },
1271
+ { value: "TAPE", label: "เทป/กาว" },
1272
+ { value: "LABEL", label: "ฉลาก/สติกเกอร์" },
1273
+ { value: "OTHER", label: "อื่นๆ" }
1274
+ ];
1275
+ const MaterialCostsTable = () => {
1276
+ const [materialCosts, setMaterialCosts] = useState([]);
1277
+ const [filteredMaterialCosts, setFilteredMaterialCosts] = useState([]);
1278
+ const [searchTerm, setSearchTerm] = useState("");
1279
+ const [categoryFilter, setCategoryFilter] = useState("ALL");
1280
+ const [isModalOpen, setIsModalOpen] = useState(false);
1281
+ const [editingMaterialCost, setEditingMaterialCost] = useState(null);
1282
+ const [isLoading, setIsLoading] = useState(false);
1283
+ const [deleteMaterialCostId, setDeleteMaterialCostId] = useState(null);
1284
+ const [deleteMaterialCostName, setDeleteMaterialCostName] = useState("");
1285
+ useEffect(() => {
1286
+ fetchMaterialCosts();
1287
+ }, []);
1288
+ useEffect(() => {
1289
+ let filtered = materialCosts;
1290
+ if (categoryFilter !== "ALL") {
1291
+ filtered = filtered.filter((mc) => mc.category === categoryFilter);
1292
+ }
1293
+ if (searchTerm) {
1294
+ filtered = filtered.filter(
1295
+ (mc) => mc.name.toLowerCase().includes(searchTerm.toLowerCase())
1296
+ );
1297
+ }
1298
+ setFilteredMaterialCosts(filtered);
1299
+ }, [searchTerm, categoryFilter, materialCosts]);
1300
+ const fetchMaterialCosts = async () => {
1301
+ setIsLoading(true);
1302
+ try {
1303
+ const response = await fetch("/admin/material-costs", {
1304
+ credentials: "include"
1305
+ });
1306
+ const data = await response.json();
1307
+ setMaterialCosts(data.material_costs || []);
1308
+ } catch (error) {
1309
+ toast.error("ข้อผิดพลาด", {
1310
+ description: "ไม่สามารถโหลดข้อมูลต้นทุนวัสดุได้"
1311
+ });
1312
+ } finally {
1313
+ setIsLoading(false);
1314
+ }
1315
+ };
1316
+ const handleToggleActive = async (materialCost) => {
1317
+ try {
1318
+ const response = await fetch(`/admin/material-costs/${materialCost.id}`, {
1319
+ method: "PUT",
1320
+ headers: { "Content-Type": "application/json" },
1321
+ credentials: "include",
1322
+ body: JSON.stringify({ ...materialCost, active: !materialCost.active })
1323
+ });
1324
+ if (response.ok) {
1325
+ toast.success("สำเร็จ", {
1326
+ description: `${materialCost.active ? "ปิดใช้งาน" : "เปิดใช้งาน"}วัสดุ ${materialCost.name} แล้ว`
1327
+ });
1328
+ fetchMaterialCosts();
1329
+ } else {
1330
+ throw new Error("Failed to update");
1331
+ }
1332
+ } catch (error) {
1333
+ toast.error("ข้อผิดพลาด", {
1334
+ description: "ไม่สามารถอัพเดทสถานะได้"
1335
+ });
1336
+ }
1337
+ };
1338
+ const handleDelete = async () => {
1339
+ if (!deleteMaterialCostId) return;
1340
+ try {
1341
+ const response = await fetch(
1342
+ `/admin/material-costs/${deleteMaterialCostId}`,
1343
+ {
1344
+ method: "DELETE",
1345
+ credentials: "include"
1346
+ }
1347
+ );
1348
+ if (response.ok) {
1349
+ toast.success("สำเร็จ", {
1350
+ description: `ลบวัสดุ ${deleteMaterialCostName} แล้ว`
1351
+ });
1352
+ fetchMaterialCosts();
1353
+ } else {
1354
+ throw new Error("Failed to delete");
1355
+ }
1356
+ } catch (error) {
1357
+ toast.error("ข้อผิดพลาด", {
1358
+ description: "ไม่สามารถลบวัสดุได้"
1359
+ });
1360
+ } finally {
1361
+ setDeleteMaterialCostId(null);
1362
+ setDeleteMaterialCostName("");
1363
+ }
1364
+ };
1365
+ const handleEdit = (materialCost) => {
1366
+ setEditingMaterialCost(materialCost);
1367
+ setIsModalOpen(true);
1368
+ };
1369
+ const handleCreateNew = () => {
1370
+ setEditingMaterialCost(null);
1371
+ setIsModalOpen(true);
1372
+ };
1373
+ const handleModalClose = () => {
1374
+ setIsModalOpen(false);
1375
+ setEditingMaterialCost(null);
1376
+ fetchMaterialCosts();
1377
+ };
1378
+ const openDeletePrompt = (id, name) => {
1379
+ setDeleteMaterialCostId(id);
1380
+ setDeleteMaterialCostName(name);
1381
+ };
1382
+ const getCategoryLabel = (category) => {
1383
+ var _a;
1384
+ if (!category) return "-";
1385
+ return ((_a = CATEGORIES.find((c) => c.value === category)) == null ? void 0 : _a.label) || category;
1386
+ };
1387
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1388
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-4", children: [
1389
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-x-4", children: [
1390
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-x-4 flex-1", children: [
687
1391
  /* @__PURE__ */ jsx(
688
1392
  Input,
689
1393
  {
690
- id: "parcel-box-height",
691
- placeholder: "e.g. 15",
692
- type: "number",
693
- value: formState.height_cm,
694
- onChange: (event) => setFormState((prev) => ({
695
- ...prev,
696
- height_cm: event.target.value
697
- }))
698
- }
699
- )
700
- ] }),
701
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
702
- /* @__PURE__ */ jsx(
703
- Label,
704
- {
705
- className: "text-sm font-medium text-ui-fg-base",
706
- htmlFor: "parcel-box-max-weight",
707
- children: "Max Weight (kg)"
1394
+ type: "search",
1395
+ placeholder: "ค้นหาชื่อวัสดุ...",
1396
+ value: searchTerm,
1397
+ onChange: (e) => setSearchTerm(e.target.value),
1398
+ className: "max-w-md"
708
1399
  }
709
1400
  ),
710
- /* @__PURE__ */ jsx(
711
- Input,
712
- {
713
- id: "parcel-box-max-weight",
714
- placeholder: "e.g. 0.5",
715
- type: "number",
716
- value: formState.max_weight_kg,
717
- onChange: (event) => setFormState((prev) => ({
718
- ...prev,
719
- max_weight_kg: event.target.value
720
- }))
721
- }
722
- )
1401
+ /* @__PURE__ */ jsxs(Select, { value: categoryFilter, onValueChange: setCategoryFilter, children: [
1402
+ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "หมวดหมู่" }) }),
1403
+ /* @__PURE__ */ jsx(Select.Content, { children: CATEGORIES.map((category) => /* @__PURE__ */ jsx(Select.Item, { value: category.value, children: category.label }, category.value)) })
1404
+ ] })
723
1405
  ] }),
724
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
725
- /* @__PURE__ */ jsx(
726
- Label,
727
- {
728
- className: "text-sm font-medium text-ui-fg-base",
729
- htmlFor: "parcel-box-price",
730
- children: "Box Price (THB)"
731
- }
732
- ),
733
- /* @__PURE__ */ jsx(
734
- Input,
735
- {
736
- id: "parcel-box-price",
737
- placeholder: "e.g. 10",
738
- type: "number",
739
- value: formState.price_thb,
740
- onChange: (event) => setFormState((prev) => ({
741
- ...prev,
742
- price_thb: event.target.value
743
- }))
744
- }
745
- )
1406
+ /* @__PURE__ */ jsxs(Button, { onClick: handleCreateNew, children: [
1407
+ /* @__PURE__ */ jsx(Plus, {}),
1408
+ "สร้างรายการใหม่"
746
1409
  ] })
747
1410
  ] }),
748
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
749
- /* @__PURE__ */ jsx(
750
- Switch,
751
- {
752
- checked: formState.active,
753
- onCheckedChange: (active) => setFormState((prev) => ({ ...prev, active }))
754
- }
755
- ),
756
- /* @__PURE__ */ jsx(Text, { className: "text-sm text-ui-fg-subtle", children: "Active" })
757
- ] }),
758
- /* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
759
- /* @__PURE__ */ jsx(Button, { onClick: handleSubmit, disabled: saving, children: formState.id ? "Update" : "Create" }),
760
- formState.id && /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: resetForm, children: "Cancel" })
1411
+ /* @__PURE__ */ jsxs(Table, { children: [
1412
+ /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
1413
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ชื่อ" }),
1414
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "หมวดหมู่" }),
1415
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "หน่วย" }),
1416
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ต้นทุนต่อหน่วย" }),
1417
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "คำอธิบาย" }),
1418
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "สถานะ" }),
1419
+ /* @__PURE__ */ jsx(Table.HeaderCell, { className: "text-right", children: "การจัดการ" })
1420
+ ] }) }),
1421
+ /* @__PURE__ */ jsx(Table.Body, { children: isLoading ? /* @__PURE__ */ jsx(Table.Row, { children: /* @__PURE__ */ jsx(Table.Cell, { className: "text-center", children: "กำลังโหลด..." }) }) : filteredMaterialCosts.length === 0 ? /* @__PURE__ */ jsx(Table.Row, { children: /* @__PURE__ */ jsx(Table.Cell, { className: "text-center", children: "ไม่พบข้อมูลต้นทุนวัสดุ" }) }) : filteredMaterialCosts.map((materialCost) => /* @__PURE__ */ jsxs(Table.Row, { children: [
1422
+ /* @__PURE__ */ jsx(Table.Cell, { children: materialCost.name }),
1423
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Badge, { color: "blue", children: getCategoryLabel(materialCost.category) }) }),
1424
+ /* @__PURE__ */ jsx(Table.Cell, { children: materialCost.unit }),
1425
+ /* @__PURE__ */ jsxs(Table.Cell, { children: [
1426
+ materialCost.cost_per_unit.toFixed(2),
1427
+ " ",
1428
+ materialCost.currency
1429
+ ] }),
1430
+ /* @__PURE__ */ jsx(Table.Cell, { className: "max-w-xs truncate", children: materialCost.description || "-" }),
1431
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
1432
+ Badge,
1433
+ {
1434
+ color: materialCost.active ? "green" : "grey",
1435
+ className: "cursor-pointer",
1436
+ onClick: () => handleToggleActive(materialCost),
1437
+ children: materialCost.active ? "เปิดใช้งาน" : "ปิดใช้งาน"
1438
+ }
1439
+ ) }),
1440
+ /* @__PURE__ */ jsx(Table.Cell, { className: "text-right", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-x-2", children: [
1441
+ /* @__PURE__ */ jsx(
1442
+ Button,
1443
+ {
1444
+ variant: "transparent",
1445
+ size: "small",
1446
+ onClick: () => handleEdit(materialCost),
1447
+ children: /* @__PURE__ */ jsx(Edit, {})
1448
+ }
1449
+ ),
1450
+ /* @__PURE__ */ jsx(
1451
+ Button,
1452
+ {
1453
+ variant: "transparent",
1454
+ size: "small",
1455
+ onClick: () => openDeletePrompt(materialCost.id, materialCost.name),
1456
+ children: /* @__PURE__ */ jsx(Trash, {})
1457
+ }
1458
+ )
1459
+ ] }) })
1460
+ ] }, materialCost.id)) })
761
1461
  ] })
762
1462
  ] }),
763
- /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-md border", children: /* @__PURE__ */ jsxs(Table, { children: [
764
- /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
765
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Name" }),
766
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Dimensions (cm)" }),
767
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Max Weight (kg)" }),
768
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Price" }),
769
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Status" }),
770
- /* @__PURE__ */ jsx(Table.HeaderCell, { className: "text-right", children: "Actions" })
771
- ] }) }),
772
- /* @__PURE__ */ jsx(Table.Body, { children: tableRows })
773
- ] }) })
1463
+ isModalOpen && /* @__PURE__ */ jsx(
1464
+ MaterialCostModal,
1465
+ {
1466
+ materialCost: editingMaterialCost,
1467
+ onClose: handleModalClose
1468
+ }
1469
+ ),
1470
+ /* @__PURE__ */ jsx(
1471
+ Prompt,
1472
+ {
1473
+ variant: "confirmation",
1474
+ open: !!deleteMaterialCostId,
1475
+ onOpenChange: () => {
1476
+ setDeleteMaterialCostId(null);
1477
+ setDeleteMaterialCostName("");
1478
+ },
1479
+ children: /* @__PURE__ */ jsxs(Prompt.Content, { children: [
1480
+ /* @__PURE__ */ jsxs(Prompt.Header, { children: [
1481
+ /* @__PURE__ */ jsx(Prompt.Title, { children: "ลบรายการต้นทุนวัสดุ" }),
1482
+ /* @__PURE__ */ jsxs(Prompt.Description, { children: [
1483
+ 'คุณต้องการลบวัสดุ "',
1484
+ deleteMaterialCostName,
1485
+ '" ใช่หรือไม่? การดำเนินการนี้ไม่สามารถย้อนกลับได้'
1486
+ ] })
1487
+ ] }),
1488
+ /* @__PURE__ */ jsxs(Prompt.Footer, { children: [
1489
+ /* @__PURE__ */ jsx(Prompt.Cancel, { children: "ยกเลิก" }),
1490
+ /* @__PURE__ */ jsx(Prompt.Action, { onClick: handleDelete, children: "ลบ" })
1491
+ ] })
1492
+ ] })
1493
+ }
1494
+ )
774
1495
  ] });
775
1496
  };
776
- const RatesSection = () => {
777
- const [rates, setRates] = useState([]);
778
- const [loading, setLoading] = useState(true);
779
- const [formState, setFormState] = useState({
780
- id: null,
781
- carrier: "COMPANY_TRUCK",
782
- min_weight_kg: "0",
1497
+ const MaterialCostsPage = () => {
1498
+ return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-4", children: [
1499
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsx(Heading, { level: "h1", children: "ต้นทุนวัสดุ" }) }),
1500
+ /* @__PURE__ */ jsx(MaterialCostsTable, {})
1501
+ ] }) });
1502
+ };
1503
+ const config$1 = defineRouteConfig({
1504
+ icon: CurrencyDollarSolid,
1505
+ label: "ต้นทุนวัสดุ"
1506
+ });
1507
+ const CARRIER_TYPES$1 = [
1508
+ { value: "COMPANY_FLEET", label: "บริษัทรถส่งของ (Messenger)" },
1509
+ { value: "COMPANY_TRUCK", label: "รถบริษัท (Same Day)" },
1510
+ { value: "PRIVATE_CARRIER", label: "ขนส่งเอกชน (EMS/Express)" }
1511
+ ];
1512
+ const SERVICE_CODES$1 = [
1513
+ { value: "MESSENGER_3H", label: "ส่งด่วน 3 ชม.", usesHours: true },
1514
+ { value: "SAME_DAY", label: "ส่งด่วนภายในวัน", usesHours: true },
1515
+ { value: "STANDARD_3_5D", label: "EMS 3-5 วัน", usesHours: false },
1516
+ { value: "EXPRESS_1_2D", label: "Express 1-2 วัน", usesHours: false }
1517
+ ];
1518
+ const ShippingRateModal = ({ rate, onClose }) => {
1519
+ const [formData, setFormData] = useState({
1520
+ carrier_type: "COMPANY_FLEET",
1521
+ service_code: "MESSENGER_3H",
783
1522
  max_weight_kg: "",
784
- price_thb: "",
1523
+ price: "",
785
1524
  currency: "THB",
1525
+ priority: "0",
1526
+ eta_hours_min: "",
1527
+ eta_hours_max: "",
1528
+ eta_days_min: "",
1529
+ eta_days_max: "",
786
1530
  active: true
787
1531
  });
788
- const [currencyOptions, setCurrencyOptions] = useState([]);
789
- const [defaultCurrency, setDefaultCurrency] = useState("THB");
790
- const [currenciesLoading, setCurrenciesLoading] = useState(true);
791
- const [saving, setSaving] = useState(false);
792
- const [deletingId, setDeletingId] = useState(null);
793
- const refresh = useCallback(async () => {
794
- setLoading(true);
795
- try {
796
- const response = await sdk.client.fetch("/admin/shipping-config/rates", {
797
- method: "GET"
798
- });
799
- const data = await response.json();
800
- setRates(
801
- (data.rates ?? []).map((rate) => ({
802
- ...rate,
803
- currency: (rate.currency ?? "").toUpperCase()
804
- }))
805
- );
806
- } catch (error) {
807
- toast.error(error.message ?? "Failed to load rates", {
808
- dismissable: true
1532
+ const [isSaving, setIsSaving] = useState(false);
1533
+ const selectedService = SERVICE_CODES$1.find((s) => s.value === formData.service_code);
1534
+ const usesHours = (selectedService == null ? void 0 : selectedService.usesHours) ?? false;
1535
+ useEffect(() => {
1536
+ var _a, _b, _c, _d;
1537
+ if (rate) {
1538
+ setFormData({
1539
+ carrier_type: rate.carrier_type,
1540
+ service_code: rate.service_code,
1541
+ max_weight_kg: rate.max_weight_kg.toString(),
1542
+ price: rate.price.toString(),
1543
+ currency: rate.currency,
1544
+ priority: (rate.priority ?? 0).toString(),
1545
+ eta_hours_min: ((_a = rate.eta_hours_min) == null ? void 0 : _a.toString()) || "",
1546
+ eta_hours_max: ((_b = rate.eta_hours_max) == null ? void 0 : _b.toString()) || "",
1547
+ eta_days_min: ((_c = rate.eta_days_min) == null ? void 0 : _c.toString()) || "",
1548
+ eta_days_max: ((_d = rate.eta_days_max) == null ? void 0 : _d.toString()) || "",
1549
+ active: rate.active
809
1550
  });
810
- } finally {
811
- setLoading(false);
812
1551
  }
813
- }, []);
814
- const loadCurrencies = useCallback(async () => {
815
- var _a, _b, _c;
816
- setCurrenciesLoading(true);
817
- try {
818
- const response = await sdk.client.fetch("/admin/currencies", {
819
- method: "GET"
820
- });
821
- const data = await response.json();
822
- const supported = ((_b = (_a = data.store) == null ? void 0 : _a.supported_currencies) == null ? void 0 : _b.map(
823
- (item) => item.code.toUpperCase()
824
- )) ?? [];
825
- const defaultCode = (((_c = data.store) == null ? void 0 : _c.default_currency_code) ?? supported[0] ?? "THB").toUpperCase();
826
- const options = supported.length > 0 ? supported : [defaultCode];
827
- setCurrencyOptions(options);
828
- setDefaultCurrency(defaultCode);
829
- setFormState((prev) => ({
830
- ...prev,
831
- currency: prev.currency && options.includes(prev.currency.toUpperCase()) ? prev.currency.toUpperCase() : defaultCode
832
- }));
833
- } catch (error) {
834
- toast.error(
835
- error.message ?? "Failed to load store currencies",
836
- { dismissable: true }
837
- );
838
- setCurrencyOptions((prev) => prev.length > 0 ? prev : ["THB"]);
839
- setDefaultCurrency((prev) => prev || "THB");
840
- } finally {
841
- setCurrenciesLoading(false);
1552
+ }, [rate]);
1553
+ const handleSubmit = async (e) => {
1554
+ e.preventDefault();
1555
+ const errors = [];
1556
+ const maxWeight = parseFloat(formData.max_weight_kg);
1557
+ const price = parseFloat(formData.price);
1558
+ const priority = parseInt(formData.priority);
1559
+ if (isNaN(maxWeight) || maxWeight <= 0) {
1560
+ errors.push("น้ำหนักสูงสุดต้องเป็นตัวเลขมากกว่า 0");
842
1561
  }
843
- }, []);
844
- useEffect(() => {
845
- refresh();
846
- }, [refresh]);
847
- useEffect(() => {
848
- loadCurrencies();
849
- }, [loadCurrencies]);
850
- const getInitialCurrency = useCallback(() => {
851
- if (currencyOptions.length === 0) {
852
- return defaultCurrency || "THB";
1562
+ if (isNaN(price) || price < 0) {
1563
+ errors.push("ราคาต้องเป็นตัวเลขไม่ติดลบ");
853
1564
  }
854
- return currencyOptions.includes(defaultCurrency) ? defaultCurrency : currencyOptions[0];
855
- }, [currencyOptions, defaultCurrency]);
856
- const resetForm = useCallback(() => {
857
- setFormState({
858
- id: null,
859
- carrier: "COMPANY_TRUCK",
860
- min_weight_kg: "0",
861
- max_weight_kg: "",
862
- price_thb: "",
863
- currency: getInitialCurrency(),
864
- active: true
865
- });
866
- }, [getInitialCurrency]);
867
- useEffect(() => {
868
- if (!formState.id && !currencyOptions.includes(formState.currency)) {
869
- setFormState((prev) => ({
870
- ...prev,
871
- currency: getInitialCurrency()
872
- }));
1565
+ if (isNaN(priority)) {
1566
+ errors.push("ลำดับความสำคัญต้องเป็นตัวเลข");
873
1567
  }
874
- }, [currencyOptions, formState.currency, formState.id, getInitialCurrency]);
875
- const handleSubmit = async () => {
876
- const min = toNumber(formState.min_weight_kg);
877
- const max = toNumber(formState.max_weight_kg);
878
- const price = toNumber(formState.price_thb);
879
- if (max === null || price === null || max <= 0) {
880
- toast.error("Provide valid max weight and price", { dismissable: true });
881
- return;
1568
+ let etaHoursMin;
1569
+ let etaHoursMax;
1570
+ let etaDaysMin;
1571
+ let etaDaysMax;
1572
+ if (usesHours) {
1573
+ etaHoursMin = formData.eta_hours_min ? parseFloat(formData.eta_hours_min) : void 0;
1574
+ etaHoursMax = formData.eta_hours_max ? parseFloat(formData.eta_hours_max) : void 0;
1575
+ if (etaHoursMin === void 0 || etaHoursMax === void 0) {
1576
+ errors.push("กรุณากรอก ETA เป็นชั่วโมงให้ครบ");
1577
+ } else if (etaHoursMin > etaHoursMax) {
1578
+ errors.push("ETA ชั่วโมงต่ำสุดต้องน้อยกว่าหรือเท่ากับสูงสุด");
1579
+ }
1580
+ } else {
1581
+ etaDaysMin = formData.eta_days_min ? parseFloat(formData.eta_days_min) : void 0;
1582
+ etaDaysMax = formData.eta_days_max ? parseFloat(formData.eta_days_max) : void 0;
1583
+ if (etaDaysMin === void 0 || etaDaysMax === void 0) {
1584
+ errors.push("กรุณากรอก ETA เป็นวันให้ครบ");
1585
+ } else if (etaDaysMin > etaDaysMax) {
1586
+ errors.push("ETA วันต่ำสุดต้องน้อยกว่าหรือเท่ากับสูงสุด");
1587
+ }
882
1588
  }
883
- if (min !== null && max <= min) {
884
- toast.error("Max weight must be greater than min weight", {
885
- dismissable: true
1589
+ if (errors.length > 0) {
1590
+ toast.error("ข้อมูลไม่ถูกต้อง", {
1591
+ description: errors.join(", ")
886
1592
  });
887
1593
  return;
888
1594
  }
889
- const payload = {
890
- carrier: formState.carrier,
891
- min_weight_kg: min ?? 0,
892
- max_weight_kg: max,
893
- price_thb: price,
894
- currency: (formState.currency || getInitialCurrency()).trim().toUpperCase(),
895
- active: formState.active
896
- };
897
- setSaving(true);
1595
+ setIsSaving(true);
898
1596
  try {
899
- if (formState.id) {
900
- await sdk.client.fetch(`/admin/shipping-config/rates/${formState.id}`, {
901
- method: "PUT",
902
- body: payload
903
- });
904
- toast.success("Rate updated", { dismissable: true });
1597
+ const payload = {
1598
+ carrier_type: formData.carrier_type,
1599
+ service_code: formData.service_code,
1600
+ max_weight_kg: maxWeight,
1601
+ price,
1602
+ currency: formData.currency,
1603
+ priority,
1604
+ active: formData.active
1605
+ };
1606
+ if (usesHours) {
1607
+ payload.eta_hours_min = etaHoursMin;
1608
+ payload.eta_hours_max = etaHoursMax;
905
1609
  } else {
906
- await sdk.client.fetch("/admin/shipping-config/rates", {
907
- method: "POST",
908
- body: payload
909
- });
910
- toast.success("Rate created", { dismissable: true });
1610
+ payload.eta_days_min = etaDaysMin;
1611
+ payload.eta_days_max = etaDaysMax;
911
1612
  }
912
- await refresh();
913
- resetForm();
914
- } catch (error) {
915
- toast.error(error.message ?? "Failed to save rate", {
916
- dismissable: true
917
- });
918
- } finally {
919
- setSaving(false);
920
- }
921
- };
922
- const handleDelete = async (id) => {
923
- setDeletingId(id);
924
- try {
925
- await sdk.client.fetch(`/admin/shipping-config/rates/${id}`, {
926
- method: "DELETE"
1613
+ const url = rate ? `/admin/shipping-rates/${rate.id}` : "/admin/shipping-rates";
1614
+ const method = rate ? "PUT" : "POST";
1615
+ const response = await fetch(url, {
1616
+ method,
1617
+ headers: { "Content-Type": "application/json" },
1618
+ credentials: "include",
1619
+ body: JSON.stringify(payload)
927
1620
  });
928
- toast.success("Rate removed", { dismissable: true });
929
- await refresh();
1621
+ if (response.ok) {
1622
+ toast.success("สำเร็จ", {
1623
+ description: rate ? "แก้ไขอัตราค่าขนส่งแล้ว" : "สร้างอัตราค่าขนส่งใหม่แล้ว"
1624
+ });
1625
+ onClose();
1626
+ } else {
1627
+ const error = await response.json();
1628
+ throw new Error(error.message || "Failed to save");
1629
+ }
930
1630
  } catch (error) {
931
- toast.error(error.message ?? "Failed to delete rate", {
932
- dismissable: true
1631
+ toast.error("ข้อผิดพลาด", {
1632
+ description: error instanceof Error ? error.message : "ไม่สามารถบันทึกข้อมูลได้"
933
1633
  });
934
1634
  } finally {
935
- setDeletingId(null);
936
- }
937
- };
938
- const handleToggle = async (rate) => {
939
- try {
940
- await sdk.client.fetch(`/admin/shipping-config/rates/${rate.id}`, {
941
- method: "PUT",
942
- body: { active: !rate.active }
943
- });
944
- await refresh();
945
- } catch (error) {
946
- toast.error(error.message ?? "Failed to update rate status", {
947
- dismissable: true
948
- });
1635
+ setIsSaving(false);
949
1636
  }
950
1637
  };
951
- const tableRows = useMemo(() => {
952
- if (loading) {
953
- 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..." }) }) });
954
- }
955
- if (rates.length === 0) {
956
- 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." }) }) });
957
- }
958
- return rates.map((rate) => /* @__PURE__ */ jsxs(Table.Row, { children: [
959
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Text, { className: "font-medium", children: rate.carrier === "COMPANY_TRUCK" ? "Company Truck" : "Private Carrier" }) }),
960
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs(Text, { className: "text-sm text-ui-fg-subtle", children: [
961
- rate.min_weight_kg,
962
- " - ",
963
- rate.max_weight_kg
964
- ] }) }),
965
- /* @__PURE__ */ jsxs(Table.Cell, { children: [
966
- rate.price_thb,
967
- " ",
968
- rate.currency
969
- ] }),
970
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Badge, { color: rate.active ? "green" : "grey", children: rate.active ? "Active" : "Inactive" }) }),
971
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
972
- /* @__PURE__ */ jsx(
973
- Switch,
1638
+ return /* @__PURE__ */ jsx(Drawer, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs(Drawer.Content, { children: [
1639
+ /* @__PURE__ */ jsx(Drawer.Header, { children: /* @__PURE__ */ jsx(Drawer.Title, { children: rate ? "แก้ไขอัตราค่าขนส่ง" : "สร้างอัตราค่าขนส่งใหม่" }) }),
1640
+ /* @__PURE__ */ jsx(Drawer.Body, { children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-y-4", children: [
1641
+ /* @__PURE__ */ jsxs("div", { children: [
1642
+ /* @__PURE__ */ jsx(Label, { htmlFor: "carrier", children: "ประเภทขนส่ง *" }),
1643
+ /* @__PURE__ */ jsxs(
1644
+ Select,
974
1645
  {
975
- checked: rate.active,
976
- onCheckedChange: () => handleToggle(rate)
1646
+ value: formData.carrier_type,
1647
+ onValueChange: (value) => setFormData({ ...formData, carrier_type: value }),
1648
+ children: [
1649
+ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "เลือกประเภทขนส่ง" }) }),
1650
+ /* @__PURE__ */ jsx(Select.Content, { children: CARRIER_TYPES$1.map((carrier) => /* @__PURE__ */ jsx(Select.Item, { value: carrier.value, children: carrier.label }, carrier.value)) })
1651
+ ]
977
1652
  }
978
- ),
979
- /* @__PURE__ */ jsx(
980
- Button,
1653
+ )
1654
+ ] }),
1655
+ /* @__PURE__ */ jsxs("div", { children: [
1656
+ /* @__PURE__ */ jsx(Label, { htmlFor: "service", children: "บริการจัดส่ง *" }),
1657
+ /* @__PURE__ */ jsxs(
1658
+ Select,
981
1659
  {
982
- variant: "secondary",
983
- size: "small",
984
- onClick: () => setFormState({
985
- id: rate.id,
986
- carrier: rate.carrier,
987
- min_weight_kg: String(rate.min_weight_kg ?? 0),
988
- max_weight_kg: String(rate.max_weight_kg),
989
- price_thb: String(rate.price_thb),
990
- currency: (rate.currency ?? "").toUpperCase(),
991
- active: rate.active
1660
+ value: formData.service_code,
1661
+ onValueChange: (value) => setFormData({
1662
+ ...formData,
1663
+ service_code: value,
1664
+ eta_hours_min: "",
1665
+ eta_hours_max: "",
1666
+ eta_days_min: "",
1667
+ eta_days_max: ""
992
1668
  }),
993
- children: "Edit"
994
- }
995
- ),
996
- /* @__PURE__ */ jsx(
997
- Button,
998
- {
999
- variant: "danger",
1000
- size: "small",
1001
- onClick: () => handleDelete(rate.id),
1002
- disabled: deletingId === rate.id,
1003
- children: "Delete"
1669
+ children: [
1670
+ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "เลือกบริการ" }) }),
1671
+ /* @__PURE__ */ jsx(Select.Content, { children: SERVICE_CODES$1.map((service) => /* @__PURE__ */ jsx(Select.Item, { value: service.value, children: service.label }, service.value)) })
1672
+ ]
1004
1673
  }
1005
1674
  )
1006
- ] }) })
1007
- ] }, rate.id));
1008
- }, [deletingId, loading, rates]);
1009
- return /* @__PURE__ */ jsxs("section", { className: "space-y-6", children: [
1010
- /* @__PURE__ */ jsxs("div", { className: "grid gap-4 rounded-md border p-4", children: [
1011
- /* @__PURE__ */ jsx(Heading, { level: "h2", children: formState.id ? "Edit Shipping Rate" : "Create Shipping Rate" }),
1012
- /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-3", children: [
1675
+ ] }),
1676
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-x-4", children: [
1013
1677
  /* @__PURE__ */ jsxs("div", { children: [
1678
+ /* @__PURE__ */ jsx(Label, { htmlFor: "maxWeight", children: "น้ำหนักสูงสุด (kg) *" }),
1014
1679
  /* @__PURE__ */ jsx(
1015
- Label,
1016
- {
1017
- className: "text-sm font-medium text-ui-fg-base",
1018
- htmlFor: "parcel-rate-carrier",
1019
- children: "Carrier"
1020
- }
1021
- ),
1022
- /* @__PURE__ */ jsxs(
1023
- "select",
1680
+ Input,
1024
1681
  {
1025
- id: "parcel-rate-carrier",
1026
- className: "mt-1 w-full rounded-md border border-ui-border-base bg-ui-bg-field px-3 py-2 text-sm",
1027
- value: formState.carrier,
1028
- onChange: (event) => setFormState((prev) => ({
1029
- ...prev,
1030
- carrier: event.target.value
1031
- })),
1032
- children: [
1033
- /* @__PURE__ */ jsx("option", { value: "COMPANY_TRUCK", children: "Company Truck" }),
1034
- /* @__PURE__ */ jsx("option", { value: "PRIVATE_CARRIER", children: "Private Carrier" })
1035
- ]
1682
+ id: "maxWeight",
1683
+ type: "number",
1684
+ step: "0.1",
1685
+ min: "0",
1686
+ value: formData.max_weight_kg,
1687
+ onChange: (e) => setFormData({ ...formData, max_weight_kg: e.target.value }),
1688
+ placeholder: "เช่น 3",
1689
+ required: true
1036
1690
  }
1037
1691
  )
1038
1692
  ] }),
1039
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
1040
- /* @__PURE__ */ jsx(
1041
- Label,
1042
- {
1043
- className: "text-sm font-medium text-ui-fg-base",
1044
- htmlFor: "parcel-rate-min-weight",
1045
- children: "Min Weight (kg)"
1046
- }
1047
- ),
1693
+ /* @__PURE__ */ jsxs("div", { children: [
1694
+ /* @__PURE__ */ jsx(Label, { htmlFor: "price", children: "ราคา (THB) *" }),
1048
1695
  /* @__PURE__ */ jsx(
1049
1696
  Input,
1050
1697
  {
1051
- id: "parcel-rate-min-weight",
1052
- placeholder: "e.g. 0",
1698
+ id: "price",
1053
1699
  type: "number",
1054
- value: formState.min_weight_kg,
1055
- onChange: (event) => setFormState((prev) => ({
1056
- ...prev,
1057
- min_weight_kg: event.target.value
1058
- }))
1700
+ step: "0.01",
1701
+ min: "0",
1702
+ value: formData.price,
1703
+ onChange: (e) => setFormData({ ...formData, price: e.target.value }),
1704
+ placeholder: "เช่น 80",
1705
+ required: true
1059
1706
  }
1060
1707
  )
1708
+ ] })
1709
+ ] }),
1710
+ /* @__PURE__ */ jsxs("div", { children: [
1711
+ /* @__PURE__ */ jsx(Label, { htmlFor: "priority", children: "ลำดับความสำคัญ" }),
1712
+ /* @__PURE__ */ jsx(
1713
+ Input,
1714
+ {
1715
+ id: "priority",
1716
+ type: "number",
1717
+ value: formData.priority,
1718
+ onChange: (e) => setFormData({ ...formData, priority: e.target.value }),
1719
+ placeholder: "0"
1720
+ }
1721
+ ),
1722
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-ui-fg-subtle mt-1", children: "ตัวเลขสูงกว่าจะถูกเสนอให้เลือกก่อน (ค่าเริ่มต้น 0)" })
1723
+ ] }),
1724
+ /* @__PURE__ */ jsxs("div", { children: [
1725
+ /* @__PURE__ */ jsxs(Label, { children: [
1726
+ usesHours ? "ETA (ชั่วโมง)" : "ETA (วัน)",
1727
+ " *"
1061
1728
  ] }),
1062
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
1729
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-x-4 mt-2", children: usesHours ? /* @__PURE__ */ jsxs(Fragment, { children: [
1063
1730
  /* @__PURE__ */ jsx(
1064
- Label,
1731
+ Input,
1065
1732
  {
1066
- className: "text-sm font-medium text-ui-fg-base",
1067
- htmlFor: "parcel-rate-max-weight",
1068
- children: "Max Weight (kg)"
1733
+ type: "number",
1734
+ placeholder: "ต่ำสุด",
1735
+ value: formData.eta_hours_min,
1736
+ onChange: (e) => setFormData({ ...formData, eta_hours_min: e.target.value }),
1737
+ required: true
1069
1738
  }
1070
1739
  ),
1071
1740
  /* @__PURE__ */ jsx(
1072
1741
  Input,
1073
1742
  {
1074
- id: "parcel-rate-max-weight",
1075
- placeholder: "e.g. 5",
1076
1743
  type: "number",
1077
- value: formState.max_weight_kg,
1078
- onChange: (event) => setFormState((prev) => ({
1079
- ...prev,
1080
- max_weight_kg: event.target.value
1081
- }))
1744
+ placeholder: "สูงสุด",
1745
+ value: formData.eta_hours_max,
1746
+ onChange: (e) => setFormData({ ...formData, eta_hours_max: e.target.value }),
1747
+ required: true
1082
1748
  }
1083
1749
  )
1084
- ] }),
1085
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
1750
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1086
1751
  /* @__PURE__ */ jsx(
1087
- Label,
1752
+ Input,
1088
1753
  {
1089
- className: "text-sm font-medium text-ui-fg-base",
1090
- htmlFor: "parcel-rate-price",
1091
- children: "Price (THB)"
1754
+ type: "number",
1755
+ placeholder: "ต่ำสุด",
1756
+ value: formData.eta_days_min,
1757
+ onChange: (e) => setFormData({ ...formData, eta_days_min: e.target.value }),
1758
+ required: true
1092
1759
  }
1093
1760
  ),
1094
1761
  /* @__PURE__ */ jsx(
1095
1762
  Input,
1096
1763
  {
1097
- id: "parcel-rate-price",
1098
- placeholder: "e.g. 45",
1099
1764
  type: "number",
1100
- value: formState.price_thb,
1101
- onChange: (event) => setFormState((prev) => ({
1102
- ...prev,
1103
- price_thb: event.target.value
1104
- }))
1765
+ placeholder: "สูงสุด",
1766
+ value: formData.eta_days_max,
1767
+ onChange: (e) => setFormData({ ...formData, eta_days_max: e.target.value }),
1768
+ required: true
1105
1769
  }
1106
1770
  )
1107
- ] }),
1108
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
1109
- /* @__PURE__ */ jsx(
1110
- Label,
1111
- {
1112
- className: "text-sm font-medium text-ui-fg-base",
1113
- htmlFor: "parcel-rate-currency",
1114
- children: "Currency"
1115
- }
1116
- ),
1117
- /* @__PURE__ */ jsx(
1118
- "select",
1119
- {
1120
- id: "parcel-rate-currency",
1121
- className: "w-full rounded-md border border-ui-border-base bg-ui-bg-field px-3 py-2 text-sm",
1122
- value: formState.currency,
1123
- onChange: (event) => setFormState((prev) => ({
1124
- ...prev,
1125
- currency: event.target.value
1126
- })),
1127
- disabled: currencyOptions.length === 0,
1128
- children: currencyOptions.map((code) => /* @__PURE__ */ jsx("option", { value: code, children: code }, code))
1129
- }
1130
- ),
1131
- currenciesLoading && /* @__PURE__ */ jsx(Text, { className: "text-xs text-ui-fg-subtle", children: "Loading currencies..." }),
1132
- !currenciesLoading && currencyOptions.length === 0 && /* @__PURE__ */ jsx(Text, { className: "text-xs text-ui-fg-subtle", children: "Configure store currencies to enable selection." })
1133
- ] })
1771
+ ] }) })
1134
1772
  ] }),
1135
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1773
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
1136
1774
  /* @__PURE__ */ jsx(
1137
1775
  Switch,
1138
1776
  {
1139
- checked: formState.active,
1140
- onCheckedChange: (active) => setFormState((prev) => ({ ...prev, active }))
1777
+ id: "active",
1778
+ checked: formData.active,
1779
+ onCheckedChange: (checked) => setFormData({ ...formData, active: checked })
1141
1780
  }
1142
1781
  ),
1143
- /* @__PURE__ */ jsx(Text, { className: "text-sm text-ui-fg-subtle", children: "Active" })
1782
+ /* @__PURE__ */ jsx(Label, { htmlFor: "active", children: "เปิดใช้งาน" })
1144
1783
  ] }),
1145
- /* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
1146
- /* @__PURE__ */ jsx(Button, { onClick: handleSubmit, disabled: saving, children: formState.id ? "Update" : "Create" }),
1147
- formState.id && /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: resetForm, children: "Cancel" })
1784
+ /* @__PURE__ */ jsxs(Drawer.Footer, { children: [
1785
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", onClick: onClose, disabled: isSaving, children: "ยกเลิก" }),
1786
+ /* @__PURE__ */ jsx(Button, { type: "submit", disabled: isSaving, children: isSaving ? "กำลังบันทึก..." : "บันทึก" })
1148
1787
  ] })
1149
- ] }),
1150
- /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-md border", children: /* @__PURE__ */ jsxs(Table, { children: [
1151
- /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
1152
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Carrier" }),
1153
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Weight Range (kg)" }),
1154
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Price" }),
1155
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Status" }),
1156
- /* @__PURE__ */ jsx(Table.HeaderCell, { className: "text-right", children: "Actions" })
1157
- ] }) }),
1158
- /* @__PURE__ */ jsx(Table.Body, { children: tableRows })
1159
1788
  ] }) })
1160
- ] });
1789
+ ] }) });
1161
1790
  };
1162
- const AreasSection = () => {
1163
- const [areas, setAreas] = useState([]);
1164
- const [loading, setLoading] = useState(true);
1165
- const [formState, setFormState] = useState({
1166
- id: null,
1167
- kind: "PROVINCE",
1168
- value: "",
1169
- active: true
1170
- });
1171
- const thaiAddressInputs = useMemo(() => getThaiAddressComponents(), []);
1172
- const ThaiProvinceInput = thaiAddressInputs == null ? void 0 : thaiAddressInputs.Province;
1173
- const ThaiZipcodeInput = thaiAddressInputs == null ? void 0 : thaiAddressInputs.Zipcode;
1174
- const [thaiAddress, setThaiAddress] = useState({
1175
- district: "",
1176
- amphoe: "",
1177
- province: "",
1178
- zipcode: ""
1179
- });
1180
- const [saving, setSaving] = useState(false);
1181
- const [deletingId, setDeletingId] = useState(null);
1182
- const refresh = useCallback(async () => {
1183
- setLoading(true);
1791
+ const CARRIER_TYPES = [
1792
+ { value: "ALL", label: "ทั้งหมด" },
1793
+ { value: "COMPANY_FLEET", label: "บริษัทรถส่งของ (Messenger)" },
1794
+ { value: "COMPANY_TRUCK", label: "รถบริษัท (Same Day)" },
1795
+ { value: "PRIVATE_CARRIER", label: "ขนส่งเอกชน (EMS/Express)" }
1796
+ ];
1797
+ const SERVICE_CODES = [
1798
+ { value: "ALL", label: "ทั้งหมด" },
1799
+ { value: "MESSENGER_3H", label: "ส่งด่วน 3 ชม." },
1800
+ { value: "SAME_DAY", label: "ส่งด่วนภายในวัน" },
1801
+ { value: "STANDARD_3_5D", label: "EMS 3-5 วัน" },
1802
+ { value: "EXPRESS_1_2D", label: "Express 1-2 วัน" }
1803
+ ];
1804
+ const ShippingRatesTable = () => {
1805
+ const [rates, setRates] = useState([]);
1806
+ const [filteredRates, setFilteredRates] = useState([]);
1807
+ const [carrierFilter, setCarrierFilter] = useState("ALL");
1808
+ const [serviceFilter, setServiceFilter] = useState("ALL");
1809
+ const [isModalOpen, setIsModalOpen] = useState(false);
1810
+ const [editingRate, setEditingRate] = useState(null);
1811
+ const [isLoading, setIsLoading] = useState(false);
1812
+ const [deleteRateId, setDeleteRateId] = useState(null);
1813
+ useEffect(() => {
1814
+ fetchRates();
1815
+ }, []);
1816
+ useEffect(() => {
1817
+ let filtered = rates;
1818
+ if (carrierFilter !== "ALL") {
1819
+ filtered = filtered.filter((rate) => rate.carrier_type === carrierFilter);
1820
+ }
1821
+ if (serviceFilter !== "ALL") {
1822
+ filtered = filtered.filter((rate) => rate.service_code === serviceFilter);
1823
+ }
1824
+ setFilteredRates(filtered);
1825
+ }, [carrierFilter, serviceFilter, rates]);
1826
+ const fetchRates = async () => {
1827
+ setIsLoading(true);
1184
1828
  try {
1185
- const response = await sdk.client.fetch("/admin/shipping-config/areas", {
1186
- method: "GET"
1829
+ const response = await fetch("/admin/shipping-rates", {
1830
+ credentials: "include"
1187
1831
  });
1188
1832
  const data = await response.json();
1189
- setAreas(data.areas ?? []);
1833
+ setRates(data.rates || []);
1190
1834
  } catch (error) {
1191
- toast.error(error.message ?? "Failed to load service areas", {
1192
- dismissable: true
1835
+ toast.error("ข้อผิดพลาด", {
1836
+ description: "ไม่สามารถโหลดข้อมูลอัตราค่าขนส่งได้"
1193
1837
  });
1194
1838
  } finally {
1195
- setLoading(false);
1196
- }
1197
- }, []);
1198
- useEffect(() => {
1199
- refresh();
1200
- }, [refresh]);
1201
- const resetForm = useCallback(() => {
1202
- setFormState({
1203
- id: null,
1204
- kind: "PROVINCE",
1205
- value: "",
1206
- active: true
1207
- });
1208
- setThaiAddress({
1209
- district: "",
1210
- amphoe: "",
1211
- province: "",
1212
- zipcode: ""
1213
- });
1214
- }, []);
1215
- const handleSubmit = async () => {
1216
- if (!formState.value.trim()) {
1217
- toast.error("Provide a value", { dismissable: true });
1218
- return;
1839
+ setIsLoading(false);
1219
1840
  }
1220
- const payload = {
1221
- kind: formState.kind,
1222
- value: formState.value.trim(),
1223
- active: formState.active
1224
- };
1225
- setSaving(true);
1841
+ };
1842
+ const handleDelete = async () => {
1843
+ if (!deleteRateId) return;
1226
1844
  try {
1227
- if (formState.id) {
1228
- await sdk.client.fetch(`/admin/shipping-config/areas/${formState.id}`, {
1229
- method: "PUT",
1230
- body: payload
1845
+ const response = await fetch(`/admin/shipping-rates/${deleteRateId}`, {
1846
+ method: "DELETE",
1847
+ credentials: "include"
1848
+ });
1849
+ if (response.ok) {
1850
+ toast.success("สำเร็จ", {
1851
+ description: "ลบอัตราค่าขนส่งแล้ว"
1231
1852
  });
1232
- toast.success("Service area updated", { dismissable: true });
1853
+ fetchRates();
1233
1854
  } else {
1234
- await sdk.client.fetch("/admin/shipping-config/areas", {
1235
- method: "POST",
1236
- body: payload
1237
- });
1238
- toast.success("Service area created", { dismissable: true });
1855
+ throw new Error("Failed to delete");
1239
1856
  }
1240
- await refresh();
1241
- resetForm();
1242
1857
  } catch (error) {
1243
- toast.error(error.message ?? "Failed to save service area", {
1244
- dismissable: true
1858
+ toast.error("ข้อผิดพลาด", {
1859
+ description: "ไม่สามารถลบอัตราค่าขนส่งได้"
1245
1860
  });
1246
1861
  } finally {
1247
- setSaving(false);
1862
+ setDeleteRateId(null);
1248
1863
  }
1249
1864
  };
1250
- const handleDelete = async (id) => {
1251
- setDeletingId(id);
1252
- try {
1253
- await sdk.client.fetch(`/admin/shipping-config/areas/${id}`, {
1254
- method: "DELETE"
1255
- });
1256
- toast.success("Service area removed", { dismissable: true });
1257
- await refresh();
1258
- } catch (error) {
1259
- toast.error(error.message ?? "Failed to delete service area", {
1260
- dismissable: true
1261
- });
1262
- } finally {
1263
- setDeletingId(null);
1264
- }
1865
+ const handleEdit = (rate) => {
1866
+ setEditingRate(rate);
1867
+ setIsModalOpen(true);
1265
1868
  };
1266
- const handleToggle = async (area) => {
1267
- try {
1268
- await sdk.client.fetch(`/admin/shipping-config/areas/${area.id}`, {
1269
- method: "PUT",
1270
- body: { active: !area.active }
1271
- });
1272
- await refresh();
1273
- } catch (error) {
1274
- toast.error(
1275
- error.message ?? "Failed to update service area status",
1276
- { dismissable: true }
1277
- );
1278
- }
1869
+ const handleCreateNew = () => {
1870
+ setEditingRate(null);
1871
+ setIsModalOpen(true);
1279
1872
  };
1280
- const beginEdit = useCallback((area) => {
1281
- setFormState({
1282
- id: area.id,
1283
- kind: area.kind,
1284
- value: area.value,
1285
- active: area.active
1286
- });
1287
- setThaiAddress((prev) => ({
1288
- ...prev,
1289
- province: area.kind === "PROVINCE" ? area.value : prev.province,
1290
- zipcode: area.kind === "POSTCODE_PREFIX" ? area.value : prev.zipcode
1291
- }));
1292
- }, []);
1293
- const handleThaiAddressSelect = useCallback(
1294
- (addressRecord) => {
1295
- const nextAddress = {
1296
- district: addressRecord.district ?? "",
1297
- amphoe: addressRecord.amphoe ?? "",
1298
- province: addressRecord.province ?? "",
1299
- zipcode: addressRecord.zipcode ? String(addressRecord.zipcode) : ""
1300
- };
1301
- setThaiAddress(nextAddress);
1302
- if (formState.kind === "PROVINCE") {
1303
- setFormState((prev) => ({
1304
- ...prev,
1305
- value: nextAddress.province
1306
- }));
1307
- } else {
1308
- setFormState((prev) => ({
1309
- ...prev,
1310
- value: nextAddress.zipcode.slice(0, 3)
1311
- }));
1312
- }
1313
- },
1314
- [formState.kind]
1315
- );
1316
- const handleThaiAddressChange = useCallback(
1317
- (scope) => (rawValue) => {
1318
- const value = typeof rawValue === "number" ? String(rawValue) : rawValue;
1319
- setThaiAddress((prev) => ({
1320
- ...prev,
1321
- [scope]: value
1322
- }));
1323
- if (scope === "province" && formState.kind === "PROVINCE") {
1324
- setFormState((prev) => ({
1325
- ...prev,
1326
- value
1327
- }));
1328
- }
1329
- if (scope === "zipcode" && formState.kind === "POSTCODE_PREFIX") {
1330
- setFormState((prev) => ({
1331
- ...prev,
1332
- value: value.slice(0, 3)
1333
- }));
1334
- }
1335
- },
1336
- [formState.kind]
1337
- );
1338
- const tableRows = useMemo(() => {
1339
- if (loading) {
1340
- 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..." }) }) });
1341
- }
1342
- if (areas.length === 0) {
1343
- 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." }) }) });
1873
+ const handleModalClose = () => {
1874
+ setIsModalOpen(false);
1875
+ setEditingRate(null);
1876
+ fetchRates();
1877
+ };
1878
+ const getCarrierLabel = (type) => {
1879
+ var _a;
1880
+ return ((_a = CARRIER_TYPES.find((c) => c.value === type)) == null ? void 0 : _a.label) || type;
1881
+ };
1882
+ const getServiceLabel = (code) => {
1883
+ var _a;
1884
+ return ((_a = SERVICE_CODES.find((s) => s.value === code)) == null ? void 0 : _a.label) || code;
1885
+ };
1886
+ const formatETA = (rate) => {
1887
+ if (rate.eta_hours_min !== null && rate.eta_hours_min !== void 0 && rate.eta_hours_max !== null && rate.eta_hours_max !== void 0) {
1888
+ return `${rate.eta_hours_min}-${rate.eta_hours_max} ชม.`;
1344
1889
  }
1345
- return areas.map((area) => /* @__PURE__ */ jsxs(Table.Row, { children: [
1346
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Text, { className: "font-medium", children: area.kind === "PROVINCE" ? "Province" : "Postcode Prefix" }) }),
1347
- /* @__PURE__ */ jsx(Table.Cell, { children: area.value }),
1348
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Badge, { color: area.active ? "green" : "grey", children: area.active ? "Active" : "Inactive" }) }),
1349
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
1350
- /* @__PURE__ */ jsx(
1351
- Switch,
1352
- {
1353
- checked: area.active,
1354
- onCheckedChange: () => handleToggle(area)
1355
- }
1356
- ),
1357
- /* @__PURE__ */ jsx(
1358
- Button,
1359
- {
1360
- variant: "secondary",
1361
- size: "small",
1362
- onClick: () => beginEdit(area),
1363
- children: "Edit"
1364
- }
1365
- ),
1366
- /* @__PURE__ */ jsx(
1367
- Button,
1368
- {
1369
- variant: "danger",
1370
- size: "small",
1371
- onClick: () => handleDelete(area.id),
1372
- disabled: deletingId === area.id,
1373
- children: "Delete"
1374
- }
1375
- )
1376
- ] }) })
1377
- ] }, area.id));
1378
- }, [areas, beginEdit, deletingId, handleDelete, handleToggle, loading]);
1379
- const valueLabel = formState.kind === "PROVINCE" ? "Province" : "Postcode Prefix";
1380
- useEffect(() => {
1381
- if (formState.kind === "PROVINCE") {
1382
- setThaiAddress(
1383
- (prev) => prev.province === formState.value ? prev : { ...prev, province: formState.value }
1384
- );
1385
- } else if (formState.kind === "POSTCODE_PREFIX") {
1386
- setThaiAddress(
1387
- (prev) => prev.zipcode === formState.value ? prev : { ...prev, zipcode: formState.value }
1388
- );
1890
+ if (rate.eta_days_min !== null && rate.eta_days_min !== void 0 && rate.eta_days_max !== null && rate.eta_days_max !== void 0) {
1891
+ return `${rate.eta_days_min}-${rate.eta_days_max} วัน`;
1389
1892
  }
1390
- }, [formState.kind, formState.value]);
1391
- return /* @__PURE__ */ jsxs("section", { className: "space-y-6", children: [
1392
- /* @__PURE__ */ jsxs("div", { className: "grid gap-4 rounded-md border p-4", children: [
1393
- /* @__PURE__ */ jsx(Heading, { level: "h2", children: formState.id ? "Edit Service Area" : "Create Service Area" }),
1394
- /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-3", children: [
1395
- /* @__PURE__ */ jsxs("div", { children: [
1396
- /* @__PURE__ */ jsx(
1397
- Label,
1398
- {
1399
- className: "text-sm font-medium text-ui-fg-base",
1400
- htmlFor: "parcel-area-kind",
1401
- children: "Kind"
1402
- }
1403
- ),
1404
- /* @__PURE__ */ jsxs(
1405
- "select",
1406
- {
1407
- id: "parcel-area-kind",
1408
- className: "mt-1 w-full rounded-md border border-ui-border-base bg-ui-bg-field px-3 py-2 text-sm",
1409
- value: formState.kind,
1410
- onChange: (event) => {
1411
- const nextKind = event.target.value;
1412
- setFormState((prev) => ({
1413
- ...prev,
1414
- kind: nextKind,
1415
- value: nextKind === "PROVINCE" ? thaiAddress.province : thaiAddress.zipcode.slice(0, 3)
1416
- }));
1417
- },
1418
- children: [
1419
- /* @__PURE__ */ jsx("option", { value: "PROVINCE", children: "Province" }),
1420
- /* @__PURE__ */ jsx("option", { value: "POSTCODE_PREFIX", children: "Postcode Prefix" })
1421
- ]
1422
- }
1423
- )
1893
+ return "-";
1894
+ };
1895
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1896
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-4", children: [
1897
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-x-4", children: [
1898
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-x-4 items-end", children: [
1899
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-1", children: [
1900
+ /* @__PURE__ */ jsx("label", { className: "text-sm font-medium text-ui-fg-subtle", children: "ประเภทขนส่ง" }),
1901
+ /* @__PURE__ */ jsxs(Select, { value: carrierFilter, onValueChange: setCarrierFilter, children: [
1902
+ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "ทั้งหมด" }) }),
1903
+ /* @__PURE__ */ jsx(Select.Content, { children: CARRIER_TYPES.map((type) => /* @__PURE__ */ jsx(Select.Item, { value: type.value, children: type.label }, type.value)) })
1904
+ ] })
1905
+ ] }),
1906
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-1", children: [
1907
+ /* @__PURE__ */ jsx("label", { className: "text-sm font-medium text-ui-fg-subtle", children: "บริการจัดส่ง" }),
1908
+ /* @__PURE__ */ jsxs(Select, { value: serviceFilter, onValueChange: setServiceFilter, children: [
1909
+ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "ทั้งหมด" }) }),
1910
+ /* @__PURE__ */ jsx(Select.Content, { children: SERVICE_CODES.map((service) => /* @__PURE__ */ jsx(Select.Item, { value: service.value, children: service.label }, service.value)) })
1911
+ ] })
1912
+ ] })
1424
1913
  ] }),
1425
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
1426
- /* @__PURE__ */ jsx(
1427
- Label,
1428
- {
1429
- className: "text-sm font-medium text-ui-fg-base",
1430
- htmlFor: "parcel-area-value",
1431
- children: valueLabel
1432
- }
1433
- ),
1434
- ThaiProvinceInput && ThaiZipcodeInput ? formState.kind === "PROVINCE" ? /* @__PURE__ */ jsx(
1435
- ThaiProvinceInput,
1436
- {
1437
- className: "w-full",
1438
- value: thaiAddress.province,
1439
- onChange: handleThaiAddressChange("province"),
1440
- onSelect: handleThaiAddressSelect,
1441
- autoCompleteProps: {
1442
- style: { width: "100%" },
1443
- dropdownMatchSelectWidth: false,
1444
- placement: "bottomLeft"
1445
- }
1446
- }
1447
- ) : /* @__PURE__ */ jsx(
1448
- ThaiZipcodeInput,
1449
- {
1450
- className: "w-full",
1451
- value: thaiAddress.zipcode,
1452
- onChange: handleThaiAddressChange("zipcode"),
1453
- onSelect: handleThaiAddressSelect,
1454
- autoCompleteProps: {
1455
- style: { width: "100%" },
1456
- dropdownMatchSelectWidth: false,
1457
- placement: "bottomLeft"
1458
- }
1459
- }
1460
- ) : /* @__PURE__ */ jsx(
1461
- Input,
1462
- {
1463
- id: "parcel-area-value",
1464
- placeholder: `Enter ${valueLabel.toLowerCase()}`,
1465
- value: formState.value,
1466
- onChange: (event) => setFormState((prev) => ({
1467
- ...prev,
1468
- value: event.target.value
1469
- }))
1470
- }
1471
- ),
1472
- 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." })
1914
+ /* @__PURE__ */ jsxs(Button, { onClick: handleCreateNew, children: [
1915
+ /* @__PURE__ */ jsx(Plus, {}),
1916
+ "สร้างอัตราใหม่"
1473
1917
  ] })
1474
1918
  ] }),
1475
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1476
- /* @__PURE__ */ jsx(
1477
- Switch,
1478
- {
1479
- checked: formState.active,
1480
- onCheckedChange: (active) => setFormState((prev) => ({ ...prev, active }))
1481
- }
1482
- ),
1483
- /* @__PURE__ */ jsx(Text, { className: "text-sm text-ui-fg-subtle", children: "Active" })
1484
- ] }),
1485
- /* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
1486
- /* @__PURE__ */ jsx(Button, { onClick: handleSubmit, disabled: saving, children: formState.id ? "Update" : "Create" }),
1487
- formState.id && /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: resetForm, children: "Cancel" })
1919
+ /* @__PURE__ */ jsxs(Table, { children: [
1920
+ /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
1921
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ประเภทขนส่ง" }),
1922
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "บริการ" }),
1923
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "น้ำหนัก ≤ (kg)" }),
1924
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ราคา (THB)" }),
1925
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ETA" }),
1926
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "สถานะ" }),
1927
+ /* @__PURE__ */ jsx(Table.HeaderCell, { className: "text-right", children: "การจัดการ" })
1928
+ ] }) }),
1929
+ /* @__PURE__ */ jsx(Table.Body, { children: isLoading ? /* @__PURE__ */ jsx(Table.Row, { children: /* @__PURE__ */ jsx(Table.Cell, { className: "text-center", children: "กำลังโหลด..." }) }) : filteredRates.length === 0 ? /* @__PURE__ */ jsx(Table.Row, { children: /* @__PURE__ */ jsx(Table.Cell, { className: "text-center", children: "ไม่พบข้อมูลอัตราค่าขนส่ง" }) }) : filteredRates.map((rate) => /* @__PURE__ */ jsxs(Table.Row, { children: [
1930
+ /* @__PURE__ */ jsx(Table.Cell, { children: getCarrierLabel(rate.carrier_type) }),
1931
+ /* @__PURE__ */ jsx(Table.Cell, { children: getServiceLabel(rate.service_code) }),
1932
+ /* @__PURE__ */ jsxs(Table.Cell, { children: [
1933
+ "≤ ",
1934
+ rate.max_weight_kg,
1935
+ " kg"
1936
+ ] }),
1937
+ /* @__PURE__ */ jsx(Table.Cell, { children: rate.price.toFixed(2) }),
1938
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Badge, { color: "blue", children: formatETA(rate) }) }),
1939
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Badge, { color: rate.active ? "green" : "grey", children: rate.active ? "เปิดใช้งาน" : "ปิดใช้งาน" }) }),
1940
+ /* @__PURE__ */ jsx(Table.Cell, { className: "text-right", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-x-2", children: [
1941
+ /* @__PURE__ */ jsx(
1942
+ Button,
1943
+ {
1944
+ variant: "transparent",
1945
+ size: "small",
1946
+ onClick: () => handleEdit(rate),
1947
+ children: /* @__PURE__ */ jsx(Edit, {})
1948
+ }
1949
+ ),
1950
+ /* @__PURE__ */ jsx(
1951
+ Button,
1952
+ {
1953
+ variant: "transparent",
1954
+ size: "small",
1955
+ onClick: () => setDeleteRateId(rate.id),
1956
+ children: /* @__PURE__ */ jsx(Trash, {})
1957
+ }
1958
+ )
1959
+ ] }) })
1960
+ ] }, rate.id)) })
1488
1961
  ] })
1489
1962
  ] }),
1490
- /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-md border", children: /* @__PURE__ */ jsxs(Table, { children: [
1491
- /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
1492
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Kind" }),
1493
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Value" }),
1494
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Status" }),
1495
- /* @__PURE__ */ jsx(Table.HeaderCell, { className: "text-right", children: "Actions" })
1496
- ] }) }),
1497
- /* @__PURE__ */ jsx(Table.Body, { children: tableRows })
1498
- ] }) })
1963
+ isModalOpen && /* @__PURE__ */ jsx(ShippingRateModal, { rate: editingRate, onClose: handleModalClose }),
1964
+ /* @__PURE__ */ jsx(
1965
+ Prompt,
1966
+ {
1967
+ variant: "confirmation",
1968
+ open: !!deleteRateId,
1969
+ onOpenChange: () => setDeleteRateId(null),
1970
+ children: /* @__PURE__ */ jsxs(Prompt.Content, { children: [
1971
+ /* @__PURE__ */ jsxs(Prompt.Header, { children: [
1972
+ /* @__PURE__ */ jsx(Prompt.Title, { children: "ลบอัตราค่าขนส่ง" }),
1973
+ /* @__PURE__ */ jsx(Prompt.Description, { children: "คุณต้องการลบอัตราค่าขนส่งนี้ใช่หรือไม่? การดำเนินการนี้ไม่สามารถย้อนกลับได้" })
1974
+ ] }),
1975
+ /* @__PURE__ */ jsxs(Prompt.Footer, { children: [
1976
+ /* @__PURE__ */ jsx(Prompt.Cancel, { children: "ยกเลิก" }),
1977
+ /* @__PURE__ */ jsx(Prompt.Action, { onClick: handleDelete, children: "ลบ" })
1978
+ ] })
1979
+ ] })
1980
+ }
1981
+ )
1499
1982
  ] });
1500
1983
  };
1501
- const ParcelShippingRoute = () => {
1502
- return /* @__PURE__ */ jsx(ParcelShippingPage, {});
1984
+ const RatesPage = () => {
1985
+ return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-4", children: [
1986
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsx(Heading, { level: "h1", children: "อัตราค่าขนส่ง" }) }),
1987
+ /* @__PURE__ */ jsx(ShippingRatesTable, {})
1988
+ ] }) });
1503
1989
  };
1504
1990
  const config = defineRouteConfig({
1505
- label: "Parcel Shipping",
1506
- icon: Directions
1991
+ icon: HandTruck,
1992
+ label: "อัตราค่าขนส่ง"
1507
1993
  });
1508
- const widgetModule = { widgets: [] };
1994
+ const widgetModule = { widgets: [
1995
+ {
1996
+ Component: OrderShippingQuoteWidget,
1997
+ zone: ["order.details.after"]
1998
+ }
1999
+ ] };
1509
2000
  const routeModule = {
1510
2001
  routes: [
1511
2002
  {
1512
- Component: ParcelBoxesRoute,
1513
- path: "/parcel-boxes"
2003
+ Component: AreasPage,
2004
+ path: "/areas"
2005
+ },
2006
+ {
2007
+ Component: BoxesPage,
2008
+ path: "/boxes"
1514
2009
  },
1515
2010
  {
1516
- Component: ParcelShippingRoute,
1517
- path: "/parcel-shipping"
2011
+ Component: MaterialCostsPage,
2012
+ path: "/material-costs"
2013
+ },
2014
+ {
2015
+ Component: RatesPage,
2016
+ path: "/rates"
1518
2017
  }
1519
2018
  ]
1520
2019
  };
1521
2020
  const menuItemModule = {
1522
2021
  menuItems: [
2022
+ {
2023
+ label: config$3.label,
2024
+ icon: config$3.icon,
2025
+ path: "/areas",
2026
+ nested: void 0
2027
+ },
2028
+ {
2029
+ label: config$2.label,
2030
+ icon: config$2.icon,
2031
+ path: "/boxes",
2032
+ nested: void 0
2033
+ },
1523
2034
  {
1524
2035
  label: config$1.label,
1525
2036
  icon: config$1.icon,
1526
- path: "/parcel-boxes",
2037
+ path: "/material-costs",
1527
2038
  nested: void 0
1528
2039
  },
1529
2040
  {
1530
2041
  label: config.label,
1531
2042
  icon: config.icon,
1532
- path: "/parcel-shipping",
2043
+ path: "/rates",
1533
2044
  nested: void 0
1534
2045
  }
1535
2046
  ]