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